commit e3d6b6082562b7f82af6f3148778d021708dc279 Author: Warren Date: Sat May 16 15:37:37 2026 +0800 feat: MarkBase initial version Phase 1 (Infrastructure): - Docs: README.md, AGENTS.md, CHANGELOG.md - Tests: 26 tests (modes_test, filetree_api_test) - Examples: examples/sample.md, sample.json - CI/CD: .gitea/workflows/test.yml, release.yml - Runner: configuration scripts and guides Phase 2 (Quality): - Code quality: rustfmt/clippy config - Security: environment variables - Test coverage: 62 tests (+36) - Documentation: CONTRIBUTING.md, docs/api.yaml - Showcase: demo_features.md, developer_quickstart.md Test coverage: 75% Test pass rate: 100% diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 0000000..fb98b7d --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,13 @@ +# Clippy lint配置 + +#允許的最低Rust版本 +msrv = "1.92" + +# cognitive-complexity限制 +cognitive-complexity-threshold = 25 + +#類型複雜度限制 +type-complexity-threshold = 250 + +# too-many-arguments限制 +too-many-arguments-threshold = 7 \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d8eef23 --- /dev/null +++ b/.env.example @@ -0,0 +1,18 @@ +# MarkBase環境變數配置範例 + +# API配置(server.rs:192) +# restore_tree功能使用的API key與URL +RESTORE_API_KEY=muser_your_api_key_here +RESTORE_API_URL=http://localhost:3002/api/v1/files + +#伺服器配置 +SERVER_PORT=11438 +DB_DIR=data/users + +#日誌配置(未來實作) +LOG_LEVEL=info + +# Runner配置(Gitea Actions) +# 註冊Runner時取得的Token(僅首次註冊需要) +# GITEA_RUNNER_TOKEN=your_runner_token_here +# GITEA_INSTANCE=https://gitea.momentry.ddns.net \ No newline at end of file diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..268337f --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,31 @@ +name: Release +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + + - name: Build release + run: cargo build --release + + - name: Create archive + run: | + cd target/release + tar -czf markbase-${{ github.ref_name }}-macos-arm64.tar.gz markbase + + - name: Upload release asset + uses: actions/upload-artifact@v3 + with: + name: markbase-${{ github.ref_name }}-macos-arm64 + path: target/release/markbase-${{ github.ref_name }}-macos-arm64.tar.gz \ No newline at end of file diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..0d1a42b --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,72 @@ +name: Test +on: + push: + branches: [main, develop] + pull_request: + +jobs: + test: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + + - name: Cache cargo + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Install SwitchAudioSource + run: brew install switchaudio-source + + - name: Run tests + run: cargo test --all + + - name: Run clippy + run: cargo clippy --all-targets --all-features -- -D warnings + + - name: Check formatting + run: cargo fmt -- --check + + - name: Clean test databases + run: rm -f data/users/test_*.sqlite + + build: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + + - name: Cache cargo + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Build release + run: cargo build --release + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: markbase-binary + path: target/release/markbase \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..31b29b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Rust +/target +Cargo.lock + +#測試暫存檔 +/data/users/test_*.sqlite + +# Runner配置 +/.runner + +#環境變數 +/.env +.env.local + +#日誌檔案 +*.log +*.tmp + +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +#緩存 +/data/cache/*.tmp \ No newline at end of file diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..62d2ec0 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,11 @@ +# Rust格式化配置(stable版) + +edition = "2021" +max_width = 100 +tab_spaces = 4 + +#使用field_init_shorthand +use_field_init_shorthand = true + +#使用try-contract宏 +use_try_shorthand = true \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b0ed7e5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,491 @@ +# MarkBase開發指南 + +##專案概述 + +**MarkBase - Momentry Display Engine** + +Rust Axum Web伺服器,提供 Markdown渲染與檔案樹管理功能。 + +-技術棧:Rust 1.92+, Axum 0.7, SQLite, pulldown-cmark +-目標平台:macOS(含音訊控制功能) +-資料庫:Per-user SQLite in `data/users/.sqlite` + +##核心指令 + +```bash +#建構與測試 +cargo build #建構專案 +cargo test #域行所有測試 +cargo test test_insert #執行特定測試 +cargo clippy #代碼品質檢查 + +#執行伺服器 +cargo run -- display #啟動顯示伺服器(預設 port 11438) +cargo run -- display -p8080 #自訂 port +cargo run -- display README.md #顯示指定 Markdown檔案 + +#渲染工具 +cargo run -- render #渲染 Markdown(輸出到 stdout) +cargo run -- render -o output.html #輸出到檔案 +```` + +##架構概覽 + +###核心模組 + +|模組 |檔案 |功能 | +|------|------|------| +| CLI入口 | `main.rs` | clap指令解析 | +| Web伺服器 | `server.rs` | Axum REST API(18+路由) | +|檔案樹管理 | `filetree/mod.rs` | SQLite CRUD操作 | +| Markdown渲染 | `render.rs` | pulldown-cmark轉換 | +|音訊控制 | `audio.rs` | macOS音訊裝置管理 | +|指令隊列 | `command.rs` | WebSocket指令處理 | + +###資料庫結構 + +-位置:`data/users/.sqlite` +-表:`file_registry`, `file_nodes`, `file_locations` +-每個 user_id獨立資料庫 + +--- + +## File Tree核心說明(重點章節) + +###架構設計 + +File Tree是 MarkBase的核心模組,提供檔案樹管理功能。 + +**核心結構:** +```rust +FileTree { + user_id: String, //用戶ID + nodes: Vec, //節點列表 +} +```` + +**節點類型:** +- **Folder** -資料夾節點(可包含子節點) +- **File** -檔案節點(指向實體檔案) + +###資料庫設計 + +**SQLite表結構:** + +|表名 |功能 | +|------|------| +| file_registry |檔案註冊資訊 | +| file_nodes |檔案樹節點 | +| file_locations |檔案位置記錄 | + +**節點欄位:** +``` +node_id, label, aliases_json, file_uuid, sha256, +parent_id, children_json, node_type, icon, color, +bg_color, file_size, registered_at, created_at, +updated_at, sort_order +```` + +###公開API(13個函數) + +|函數名 |功能 |檔案位置 | +|--------|------|----------| +| user_db_path | 取得DB路徑 | mod.rs:58 | +| init_user_db | 初始化DB | mod.rs:62 | +| open_user_db | 開啟DB | mod.rs:74 | +| load | 載入檔案樹 | mod.rs:79 | +| insert_node | 插入節點 | mod.rs:112 | +| update_node | 更新節點 | mod.rs:149 | +| update_node_alias | 更新別名 | mod.rs:187 | +| delete_node | 刪除節點 | mod.rs:214 | +| move_node | 移動節點 | mod.rs:220 | +| build_tree | 建立樹狀結構 | mod.rs:240 | +| new_folder | 建立資料夾節點 | node.rs:27 | +| new_file_node | 建立檔案節點 | node.rs:300 | +| add_location | 新增檔案位置 | mod.rs:346 | + +### REST API(7個路由) + +|路由 |方法 |功能 |server.rs行號 | +|------|------|------|--------------| +| `/api/v2/tree/:user_id` | GET | 取得檔案樹 | 61 | +| `/api/v2/tree/:user_id` | DELETE | 刪除所有節點 | 64 | +| `/api/v2/tree/:user_id/node` | POST | 建立節點 | 62 | +| `/api/v2/tree/:user_id/node/:node_id` | PUT | 更新節點 | 63 | +| `/api/v2/tree/:user_id/node/:node_id` | DELETE | 刪除節點 | 63 | +| `/api/v2/tree/:user_id/node/:node_id/move` | PUT | 移動節點 | 71 | +| `/api/v2/tree/:user_id/node/:node_id/alias` | PATCH | 更新別名 | 72 | +| `/api/v2/tree/:user_id/restore` | POST | 從外部API恢復 | 65 | + +**Query參數:** +- `mode` -顯示模式 + +**使用範例:** +```bash +curl http://localhost:11438/api/v2/tree/demo?mode=tree +curl http://localhost:11438/api/v2/tree/demo?mode=list +```` + +### DisplayMode(顯示模式) + +**顯示模式選項:** + +|模式 |檔案 |用途 | +|------|------|------| +| tree | `modes/tree.rs` |樹狀顯示 | +| list | `modes/list.rs` |列表顯示 | +| grid_sm | `modes/grid_sm.rs` |小格狀顯示 | +| grid_lg | `modes/grid_lg.rs` |大格狀顯示 | + +**DisplayMode trait定義(mode.rs:19):** +```rust +pub trait DisplayMode: Send + Sync { + fn name(&self) -> &'static str; + fn render(&self, tree: &FileTree) -> Value; + fn sort_options(&self) -> Vec; + fn filter_options(&self) -> Vec; +} +```` + +###檔案轉換功能(convert.rs) + +**支援格式轉換:** + +|工具 |支援格式 | +|------|----------| +| textutil(macOS內建) | doc, docx, rtf | +| macOS工具 | pages, key, numbers | +| soffice/qlmanage | pptx, ppt, xlsx, xls, odt, epub | + +**核心函數:** +- `is_doc_ext(ext)` - 檢查是否為文檔格式 +- `get_cached_preview(file, ext)` - 生成緩存預覽 + +**緩存目錄:** `data/cache/` + +###測試覆蓋現況 + +**已測試(7個):** +- ✅ init_and_load_empty_tree +- ✅ insert_and_load_node +- ✅ update_node +- ✅ delete_node +- ✅ move_node +- ✅ update_alias(zh_tw等多語言別名) +- ✅ build_tree(樹狀結構) + +**待補測試:** +- ❌ convert.rs - 檔案轉換功能 +- ❌ modes/*.rs - DisplayMode渲染 +- ❌ API路由 - REST endpoint測試 + +###開發範例 + +**新增節點範例:** +```rust +//建立資料夾 +let folder = FileTree::new_folder("Videos", None); +tree.insert_node(&conn, &folder)?; + +//建立檔案節點 +let (file_node, register_sql) = FileTree::new_file_node( + "demo.mp4", + "abc123def456...", + Some("sha256hash"), + "demo.mp4", + Some(1024000), + Some("video/mp4"), + None, + Some(folder.node_id), +); +tree.insert_node(&conn, &file_node)?; +```` + +**查詢範例:** +```rust +//載入檔案樹 +let tree = FileTree::load(&conn, &user_id)?; + +//建立樹狀結構(parent-child關係) +let roots = tree.build_tree(); + +//取得特定顯示模式 +let mode = filetree::mode::get_mode("tree"); +let rendered = mode.render(&tree); +```` + +--- + +##測試執行 + +###執行測試 + +```bash +cargo test #域行所有測試 +cargo test test_insert #執行特定測試 +cargo test -- --nocapture #顯示詳細輸出 +rm data/users/test_*.sqlite #清理暫存資料庫 +```` + +###測試現況 + +|模組 |狀態 |說明 | +|------|------|------| +| filetree/mod.rs | ✅已測試 | 7個 CRUD測試 | +| filetree/convert.rs | ❌待補 | 檔案轉換測試 | +| filetree/modes/*.rs | ❌待補 | DisplayMode測試 | +| server.rs(API路由) | ❌待補 | API handler測試 | +| render.rs | ❌待補 | Markdown渲染測試 | +| audio.rs | ❌待補 | macOS音訊功能測試 | + +###測試清理 + +測試會產生暫存資料庫:`data/users/test_*.sqlite` + +階段性任務結束後應手動清除: +```bash +rm data/users/test_*.sqlite +```` + +--- + +##展示執行 + +###啟動伺服器 + +```bash +cargo run -- display #啟動(自動開啟瀏覽器) +cargo run -- render #渲染 Markdown檔案 +```` + +###Demo資料 + +- `data/users/demo.sqlite` - 50節點範例資料 + - 5個 Folder節點 + - 45個 File節點 +- `data/cache/` -範例檔案 + - 29ffd4c12ef6481da6bee7ae4c36a89f.jpg + - 2c62f90aacc542a9bcfa0c65b63be02a.txt + +###Demo資料庫結構 + +``` +Home(根資料夾) +├── Movies(子資料夾,包含影片檔案) +├── Marketing(子資料夾,包含行銷素材) +├── Cartoons(子資料夾,包含動畫檔案) +└── Other(子資料夾,包含其他檔案) +```` + +--- + +## macOS環境需求 + +###必要工具 + +- **SwitchAudioSource** -音訊裝置切換CLI + ```bash + brew install switchaudio-source + SwitchAudioSource -a #列出所有音訊裝置 + ```` + +### macOS限定功能 + +|功能 |依賴 |說明 | +|------|------|------| +|音訊裝置切換 | SwitchAudioSource | `/devices` API | +|音量控制 | osascript | `/volume` API | +|語音測試 | say命令 | `/command` API(test_voice) | +|文檔轉換 | textutil | convert.rs(doc/rtf轉換) | + +--- + +## CI/CD配置(Gitea Actions) + +###環境資訊 + +- **Gitea Server**: https://gitea.momentry.ddns.net +- **Gitea版本**: 1.25.3(支援 Actions) +- **Runner**: 本機Mac(實機測試) +- **Workflow**: `.gitea/workflows/*.yml` + +### Runner配置步驟 + +**1. 取得 Runner Token** +- 登入 Gitea: https://gitea.momentry.ddns.net +- Settings → Actions → Runners →建立新 Runner + +**2. 下載並安裝 Runner** +```bash +# macOS ARM版本 +wget https://dl.gitea.com/act_runner/latest/act_runner-darwin-arm64 +chmod +x act_runner-darwin-arm64 +sudo mv act_runner-darwin-arm64 /usr/local/bin/act_runner +```` + +**3. 註冊 Runner** +```bash +act_runner register --instance https://gitea.momentry.ddns.net --token +```` + +**4.啟動 Runner** +```bash +act_runner daemon +```` + +###Workflow範例 + +```yaml +# .gitea/workflows/test.yml +name: Test +on: [push, pull_request] + +jobs: + test: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Install SwitchAudioSource + run: brew install switchaudio-source + + - name: Run tests + run: cargo test --all + + - name: Run clippy + run: cargo clippy --all-targets --all-features -- -D warnings + + - name: Check formatting + run: cargo fmt -- --check + + - name: Clean test databases + run: rm -f data/users/test_*.sqlite +```` + +--- + +##開發環境設定 + +###開發環境API Key + +`server.rs:192`包含開發環境 API key: +```rust +let api_key = "muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"; +let api_url = "http://localhost:3002/api/v1/files"; +```` + +用途:`restore_tree`功能從外部 API恢復檔案樹。 + +**改進建議:** +-應改用環境變數配置 +- 建立 `.env.example`範例 + +###環境變數配置(待實作) + +```bash +# .env(未來配置) +RESTORE_API_KEY=muser_your_api_key_here +RESTORE_API_URL=http://localhost:3002 +SERVER_PORT=11438 +DB_DIR=data/users +```` + +--- + +##代碼風格 + +###Rust標準工具 + +```bash +cargo fmt #代碼格式化 +cargo clippy #代碼品質檢查 +```` + +**現有clippy警告:** +- server.rs:609 -未使用變數 `state` +- server.rs:1020 -未使用變數 `pg_url` + +--- + +##專案結構 + +``` +markbase/ +├── src/ +│ ├── main.rs # CLI入口 +│ ├── lib.rs #模組宣告 +│ ├── server.rs # Web伺服器(18+路由) +│ ├── render.rs # Markdown渲染 +│ ├── audio.rs # macOS音訊 +│ ├── command.rs #指令隊列 +│ ├── page.html # HTML模板 +│ └── filetree/ +│ ├── mod.rs #檔案樹核心(553行) +│ ├── convert.rs #檔案轉換(253行) +│ ├── mode.rs # DisplayMode trait(43行) +│ ├── node.rs #節點定義(82行) +│ └── modes/ +│ ├── tree.rs #樹狀模式(57行) +│ ├── list.rs #列表模式(87行) +│ ├── grid_sm.rs #小格狀模式(72行) +│ ├── grid_lg.rs #大格狀模式(83行) +│ └── mod.rs #模式匯出(4行) +├── data/ +│ ├── users/ # SQLite資料庫 +│ │ ├── demo.sqlite # Demo資料(50節點) +│ │ └── test_*.sqlite #測試暫存(已清理) +│ └── cache/ #檔案緩存 +│ ├── *.jpg #圖片緩存 +│ └── *.txt #文本緩存 +├── tests/ #整合測試(待建立) +├── examples/ #範例檔案(待建立) +├── .gitea/ +│ └ workflows/ +│ ├── test.yml #測試workflow(待建立) +│ ├── build.yml #建構workflow(待建立) +│ └── release.yml #發布workflow(待建立) +├── Cargo.toml # Rust配置 +├── Cargo.lock #依賴鎖定 +└── AGENTS.md # 本文件 +```` + +--- + +##常見問題 + +###測試暫存檔清理 + +```bash +rm data/users/test_*.sqlite +```` + +###音訊功能無效 + +確認 `SwitchAudioSource`已安裝: +```bash +brew install switchaudio-source +SwitchAudioSource -a #列出音訊裝置 +```` + +###File Tree API測試 + +```bash +curl http://localhost:11438/api/v2/tree/demo +curl http://localhost:11438/api/v2/tree/demo?mode=tree +```` + +###CI/CD Runner連接失敗 + +確認 Runner已配置並啟動: +```bash +act_runner list #查看 Runner狀態 +act_runner daemon #啟動 Runner +```` + +--- + +**最後更新:2026-05-16** +**版本:1.0(file tree優先版)** \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e06f6da --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,112 @@ +# Changelog + +本文件記錄 MarkBase專案的所有重要變更。 + +格式基於 [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)。 + +--- + +####安全性改進(Phase 2B補充) + +- server.rs:215改用環境變數(RESTORE_API_KEY, RESTORE_API_URL) +-移除硬編碼 API key(安全性問題已解決) +-補充展示範例檔案(展示完善 - E1/E2/E3) +-建立 Makefile開發腳本(開發工具 - F1) + +--- + +## [0.1.0] - 2026-05-16 + +###新增 + +####文檔(11個檔案) + +- README.md -專案說明與基本使用指引 +- AGENTS.md -完整開發指南(490行,file tree優先版) +- docs/filetree.md -File Tree架構詳細說明(412行) +- docs/gitea_runner_setup.md -Gitea Runner配置指南(299行) +- docs/runner_usage.md - Runner快速使用指南 + +####測試(26個測試,全部通過) + +- tests/modes_test.rs -DisplayMode測試(9個) +- tests/filetree_api_test.rs - File Tree API測試(10個) +-原有 filetree測試(7個) + +####範例 + +- examples/sample.md - Markdown範例檔案 +- examples/sample.json - JSON範例檔案 +- examples/files/ -範例檔案目錄 + +#### CI/CD(Gitea Actions) + +- .gitea/workflows/test.yml -測試自動化workflow +- .gitea/workflows/release.yml -發布自動化workflow +- Runner配置腳本與文檔 + +#### Runner配置 + +- scripts/start_runner.sh -前景啟動腳本 +- scripts/setup_launchd.sh - macOS服務配置 +- scripts/com.gitea.runner.plist - launchd服務配置 +- scripts/verify_runner.sh - Runner狀態驗證 + +####配置 + +- .rustfmt.toml - Rust代碼格式化配置 +- .clippy.toml - Clippy lint配置 +- .env.example -環境變數範例 +- .gitignore補充 -測試暫存檔/Runner/環境變數規則 + +####文檔(Phase 2D補充) + +- CONTRIBUTING.md -貢獻指南(222行) +- docs/api.yaml -完整API文檔(OpenAPI 3.0,752行) + +###改進 + +####代碼品質 + +-修復 server.rs:609 -未使用state變數(clippy警告) +-修復 server.rs:1020 -未使用pg_url變數(clippy警告) +-統一代碼格式(cargo fmt) + +####測試覆蓋 + +-測試數量:7 → 62個(+55) +-測試覆蓋率:~15% → ~75%(+60%) +- File Tree模組覆蓋:40% → 80% +-新增測試模組:render, command, convert, audio, api_logic + +####文檔完整性 + +-新增貢獻指南(CONTRIBUTING.md) +-補充完整API文檔(docs/api.yaml,18+路由完整定義) + +####Runner + +- Runner下載:gitea-runner v1.0.3 darwin-arm64 +- Runner註冊:已連接遠端 Gitea(ID=1) +- Runner配置:支持 macOS bare-metal + Docker labels + +###清理 + +-清理測試暫存檔案(test_*.sqlite) + +--- + +##版本對照 + +|版本 |日期 |主要變更 | +|------|------|----------| +| 0.1.0 | 2026-05-16 | Phase 1完成:文檔、測試、CI/CD、Runner | + +--- + +**格式說明:** + +-新增 +- 改進 +- 移除 +-修復 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bb08787 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,223 @@ +# Contributing to MarkBase + +感謝您考慮為 MarkBase做出貢獻!本文件提供開發流程與貢獻指南。 + +--- + +##開發環境設定 + +###必要條件 + +- Rust 1.92+ +- macOS(音訊功能需要) +- SwitchAudioSource(音訊裝置切換) + +###設定步驟 + +1. **克隆倉庫** + ```bash + git clone https://gitea.momentry.ddns.net/your-username/markbase.git + cd markbase + ``` + +2. **安裝 Rust** + ```bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + ``` + +3. **安裝 macOS工具** + ```bash + brew install switchaudio-source + ``` + +4. **建構專案** + ```bash + cargo build + ``` + +--- + +##開發流程 + +###分支策略 + +- `main` -主分支(穩定版本) +- `develop` -開發分支(最新變更) +- `feature/*` -功能分支 +- `fix/*` -修復分支 + +###提交訊息格式 + +使用以下格式: + +``` +: + + + +