72 lines
4.6 KiB
Markdown
72 lines
4.6 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
## 專案性質
|
||
|
||
抓取台灣銀行 CSV 匯率並即時換算成台幣的跨平台 GUI 小工具。目標是「使用者雙擊即跑、零 runtime 依賴」,所以主程式是 Rust(`bot_rates_rs/src/main.rs`);根目錄下的 `bot_rates.py` / `bot_rates_gui.py` / `build.bat` 是已被取代的早期 Python 版本,修改時請以 Rust 版為準。
|
||
|
||
Android 版獨立在 `android/`(Kotlin + Jetpack Compose),用純 CLI(`gradle` + `sdkmanager`)建置,不依賴 Android Studio。核心邏輯(CSV col 13、TARGETS)與 Rust 版對齊,改動時兩邊要同步。詳細見 `android/README.md`。
|
||
|
||
## 常用指令
|
||
|
||
所有 cargo 指令都要在 `bot_rates_rs/` 下執行,並帶 `RUST_MIN_STACK=16777216`(少了這個 LLVM 在 release 優化 `eframe` 依賴時會堆疊溢位 SIGSEGV)。
|
||
|
||
```bash
|
||
cd bot_rates_rs
|
||
|
||
# macOS 版 release build
|
||
RUST_MIN_STACK=16777216 cargo build --release
|
||
# 輸出:target/release/bot_rates
|
||
|
||
# Windows 版(從 macOS 交叉編譯,需先 brew install mingw-w64 並 rustup target add x86_64-pc-windows-gnu)
|
||
RUST_MIN_STACK=16777216 cargo build --release --target x86_64-pc-windows-gnu
|
||
# 輸出:target/x86_64-pc-windows-gnu/release/bot_rates.exe
|
||
|
||
# 快速 debug 跑一次(不用設 RUST_MIN_STACK)
|
||
cargo run
|
||
```
|
||
|
||
部署(複製編譯產物到專案根目錄的散佈位置):
|
||
|
||
```bash
|
||
# macOS:塞進 .app bundle 並清 quarantine
|
||
cp target/release/bot_rates ../BotRates.app/Contents/MacOS/BotRates
|
||
xattr -cr ../BotRates.app
|
||
|
||
# Windows:複製 exe 到根目錄並重新壓 zip
|
||
cp target/x86_64-pc-windows-gnu/release/bot_rates.exe ../bot_rates.exe
|
||
cd .. && zip -9 bot_rates.zip bot_rates.exe
|
||
```
|
||
|
||
專案目前沒有測試、沒有 lint 設定、沒有 CI。
|
||
|
||
## 架構要點
|
||
|
||
單一檔案 Rust app(~410 行 `src/main.rs`),關鍵分層:
|
||
|
||
- **資料抓取**:`fetch_rates()` 用 `ureq` + `rustls`(刻意避開 `reqwest` / `native-tls`,因為交叉編譯會牽扯 OpenSSL / SChannel)抓 `https://rate.bot.com.tw/xrt/flcsv/0/day`。**CSV 解析用位置索引而非欄名**,因為台銀 CSV 有重複欄名(買入/賣出區段都叫「匯率/現金/即期」),`csv::DictReader` 式的解析會只保留最後一個。目前讀 `cols[13]` 即期賣出;買入在 `cols[3]`,現金買入 `cols[2]`、現金賣出 `cols[12]`。
|
||
- **UI**:`eframe` / `egui` 0.27 immediate mode。`App::update` 每幀重繪;抓匯率走背景 thread + `mpsc::Receiver`,`update` 用 `try_recv` 無阻塞輪詢,沒資料時 `request_repaint_after(100ms)` 自我喚醒。
|
||
- **輸入狀態**:`App.amounts: HashMap<String, String>`,存字串不是 `f64`,這樣才能保留「`100.`」「空字串」「`1,000`」這類輸入中間態。`parse_amount` 才做清洗與 parse。
|
||
- **字型**:egui 預設字型沒中文,會變豆腐。`setup_fonts` 在執行期讀 OS 字型(Windows `msjh.ttc` / macOS `PingFang.ttc`,有 fallback 鏈),**刻意不 bundle 字型**以免 binary 胖 5–15 MB。如果在沒有這些字型的環境跑就會回到豆腐。
|
||
- **時間**:`now_string` / `civil_from_days` 自己實作 UTC→台北時間換算與 Gregorian 日期計算,避免引入 `chrono` / `time` 把 binary 撐大。
|
||
- **複製**:「複製」按鈕用 `ui.output_mut(|o| o.copied_text = raw)` 寫純數字(`3162.00`,不含 `NT$` / 逗號)到剪貼簿,設計是為了貼到 Excel 會被當數字。TWD label 另外有 `.selectable(true)` 讓使用者自行選取。
|
||
|
||
## 擴充時的常見改點
|
||
|
||
| 目標 | 位置 |
|
||
|------|------|
|
||
| 新增幣別 | `TARGETS` 常數,加一行 `(代碼, 中文名)` 即可,其他地方會自動處理 |
|
||
| 改成即期買入/現金匯率 | `fetch_rates` 裡的 `cols[13]` 換成對應 index(見上方資料抓取段) |
|
||
| 視窗尺寸/不可 resize | `main()` 的 `ViewportBuilder` |
|
||
| 預設輸入金額 | `App::new` 裡的 `"100".to_string()` |
|
||
| 配色 | `update` 開頭的 `bg` / `card` / `rate_color` / `twd_color` 等常數 |
|
||
|
||
## Release profile 的硬性約束
|
||
|
||
`Cargo.toml` 的 `[profile.release]` 是 `opt-level = 3, lto = "thin", codegen-units = 1, strip = true, panic = "abort"`。歷史上試過 `opt-level = "z"` + `lto = true` 會觸發 LLVM DeadArgumentElimination 的 SIGSEGV,所以不要回頭改成更激進的設定。`RUST_MIN_STACK=16777216` 同樣是必備而非可選。
|
||
|
||
## macOS `.app` bundle
|
||
|
||
`BotRates.app/Contents/{Info.plist, MacOS/BotRates}` 是手動組的;沒有 cargo-bundle 或腳本自動化。改版本/顯示名稱要直接編輯 `Info.plist`。首次散佈給使用者前跑 `xattr -cr BotRates.app` 避免 Gatekeeper 擋下。
|