初始化專案:Go 模組骨架、CLI 進入點、README、測試
- go.mod(module gitea.alterminal.com/alterminal/teai,Go 1.26) - cmd/teai/main.go:程式進入點,結束碼交由 internal/cli.Run 處理 - internal/cli:命令列架構(引數剖析、子命令分派、version 命令) - internal/cli/cli_test.go:用法輸出、version、未知命令等基本測試 - README.md:專案說明、建置與開發慣例(繁體中文) - .gitignore:建置產物、測試輸出、環境檔 - 僅使用標準庫,可離線建置;授權待維護者選定 Refs #1
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
# 建置產物
|
||||
/teai
|
||||
/teai.exe
|
||||
/bin/
|
||||
/dist/
|
||||
|
||||
# 測試與覆蓋率
|
||||
*.test
|
||||
*.out
|
||||
coverage.*
|
||||
|
||||
# 環境與編輯器
|
||||
.env
|
||||
.env.*
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
|
||||
# 作業系統
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,54 @@
|
||||
# teai
|
||||
|
||||
teai 是 Gitea 的命令列(CLI)輔助工具,補足官方 `tea` CLI 缺少的功能。
|
||||
名稱取「**te**a + **AI**」:這套工具主要為了讓 AI agent 能以更完整、更可靠的
|
||||
方式操作 Gitea(查詢、回覆、審核、自動化……)。
|
||||
|
||||
- **語言:** Go(與 Gitea 本體一致)
|
||||
- **目標站點:** `https://gitea.alterminal.com`
|
||||
- **狀態:** 專案初始化中,功能尚未開始開發
|
||||
|
||||
## 安裝與建置
|
||||
|
||||
需要 Go 1.26 以上。
|
||||
|
||||
```sh
|
||||
go build -o teai ./cmd/teai
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
```sh
|
||||
# 目前僅提供骨架
|
||||
./teai # 顯示說明
|
||||
./teai version # 顯示版本資訊
|
||||
```
|
||||
|
||||
離線環境可直接建置:本專案目前僅使用 Go 標準庫,沒有外部相依。
|
||||
|
||||
## 開發
|
||||
|
||||
```sh
|
||||
go build ./... # 建置全部套件
|
||||
go test ./... # 跑全部測試
|
||||
go vet ./... # 靜態檢查
|
||||
```
|
||||
|
||||
套件結構:
|
||||
|
||||
| 路徑 | 內容 |
|
||||
| --- | --- |
|
||||
| `cmd/teai/` | 程式進入點(`main`) |
|
||||
| `internal/cli/` | 命令列架構(flag 剖析、子命令分派、輸出) |
|
||||
| `internal/` 其他子套件 | 之後依功能新增(如 `internal/gitea`、`internal/cmds/`) |
|
||||
|
||||
### 慣例
|
||||
|
||||
- 文件與溝通一律使用**繁體中文**
|
||||
- 對外行為(CLI 輸出、錯誤訊息)以英文為主,方便除錯與自動化剖析
|
||||
- 變更一律走 **feature 分支 + Pull Request**,由審核者合併;不直推 `main`
|
||||
- Commit 訊息與 PR 標題使用繁體中文或英文皆可,但需簡潔明確
|
||||
|
||||
## 授權
|
||||
|
||||
尚未決定(待維護者選定,例如 MIT/Apache-2.0)。
|
||||
@@ -0,0 +1,16 @@
|
||||
// 套件 main 是 teai 的程式進入點。
|
||||
//
|
||||
// teai 是 Gitea 的命令列輔助工具(tea + AI),補足官方 tea CLI 缺少的功能。
|
||||
// 實際的命令分派與執行邏輯位於 internal/cli 套件;main 僅負責把標準
|
||||
// 輸入輸出接上,並以內部錯誤碼結束行程。
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"gitea.alterminal.com/alterminal/teai/internal/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Exit(cli.Run(os.Stdout, os.Stderr, os.Args[1:]))
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// 套件 cli 提供 teai 的命令列架構:引數剖析、子命令分派與輸出。
|
||||
//
|
||||
// 設計目標是讓之後新增子命令時只需註冊一個 command 結構,
|
||||
// 不需要改動分派邏輯;也讓核心功能可以被測試(Run 接受 io.Writer)。
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Version 是目前開發中的版本號。採用語意化版本;正式發佈前以 0 開頭。
|
||||
var Version = "0.1.0-dev"
|
||||
|
||||
// ExitCode 是 Run 回傳的行程結束碼。
|
||||
type ExitCode int
|
||||
|
||||
// 常見的結束碼定義。0 表示成功,其餘對應常見的命令列錯誤情境。
|
||||
const (
|
||||
ExitOK ExitCode = iota // 成功
|
||||
ExitUsage // 引數或子命令錯誤
|
||||
ExitInternal // 內部錯誤(不該發生)
|
||||
)
|
||||
|
||||
// command 定義一個子命令:名稱、一行說明與實作。
|
||||
type command struct {
|
||||
name string
|
||||
usage string
|
||||
run func(env *Env, args []string) error
|
||||
}
|
||||
|
||||
// Env 聚集一次執行所需的輸出目標,便於測試時替換。
|
||||
type Env struct {
|
||||
// Out 是一般輸出(命令結果)。
|
||||
Out io.Writer
|
||||
// Err 是診斷輸出(錯誤、警告)。
|
||||
Err io.Writer
|
||||
}
|
||||
|
||||
// commands 是已註冊的子命令表。新增功能時在這裡註冊即可。
|
||||
var commands = map[string]*command{
|
||||
"version": {
|
||||
name: "version",
|
||||
usage: "顯示版本資訊",
|
||||
run: runVersion,
|
||||
},
|
||||
}
|
||||
|
||||
// Run 剖析引數並分派到對應子命令,回傳行程結束碼。
|
||||
//
|
||||
// 無引數或要求說明(-h/--help)時印出用法;未知子命令回 ExitUsage。
|
||||
func Run(stdout, stderr io.Writer, args []string) int {
|
||||
env := &Env{Out: stdout, Err: stderr}
|
||||
|
||||
if len(args) == 0 {
|
||||
printUsage(env.Out)
|
||||
return int(ExitOK)
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "-h", "--help", "help":
|
||||
printUsage(env.Out)
|
||||
return int(ExitOK)
|
||||
case "-v", "--version", "version":
|
||||
return dispatch(env, "version", args[1:])
|
||||
default:
|
||||
name := args[0]
|
||||
if _, ok := commands[name]; !ok {
|
||||
fmt.Fprintf(stderr, "teai: unknown command %q\n\n", name)
|
||||
printUsage(stderr)
|
||||
return int(ExitUsage)
|
||||
}
|
||||
return dispatch(env, name, args[1:])
|
||||
}
|
||||
}
|
||||
|
||||
// dispatch 執行已註冊的子命令,把錯誤轉成結束碼並輸出。
|
||||
func dispatch(env *Env, name string, args []string) int {
|
||||
cmd := commands[name]
|
||||
fs := flag.NewFlagSet("teai "+name, flag.ContinueOnError)
|
||||
fs.SetOutput(env.Err)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return int(ExitUsage)
|
||||
}
|
||||
if err := cmd.run(env, fs.Args()); err != nil {
|
||||
fmt.Fprintf(env.Err, "teai %s: %v\n", name, err)
|
||||
return int(ExitInternal)
|
||||
}
|
||||
return int(ExitOK)
|
||||
}
|
||||
|
||||
// runVersion 輸出版本資訊。
|
||||
func runVersion(env *Env, args []string) error {
|
||||
fmt.Fprintf(env.Out, "teai version %s\n", Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
// printUsage 印出用法與已註冊的子命令清單(依名稱排序)。
|
||||
func printUsage(w io.Writer) {
|
||||
fmt.Fprintf(w, "teai — Gitea CLI 輔助工具(tea + AI)\n\n")
|
||||
fmt.Fprintf(w, "用法:\n teai [命令] [參數]\n\n命令:\n")
|
||||
names := make([]string, 0, len(commands))
|
||||
for name := range commands {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
fmt.Fprintf(w, " %-10s %s\n", name, commands[name].usage)
|
||||
}
|
||||
fmt.Fprintf(w, "\n說明:\n -h, --help 顯示說明\n -v, --version 顯示版本\n")
|
||||
fmt.Fprintf(w, "\n更多資訊:https://gitea.alterminal.com/alterminal/teai\n")
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// cli_test.go 驗證命令列架構的基本行為:用法輸出、子命令分派、
|
||||
// 版本輸出與未知命令的錯誤處理。
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// run 是測試輔助:以 buffer 收集輸出並執行 Run。
|
||||
func run(args ...string) (stdout, stderr string, code int) {
|
||||
var out, errb bytes.Buffer
|
||||
code = Run(&out, &errb, args)
|
||||
return out.String(), errb.String(), code
|
||||
}
|
||||
|
||||
func TestRunNoArgsShowsUsage(t *testing.T) {
|
||||
stdout, _, code := run()
|
||||
if code != 0 {
|
||||
t.Errorf("無引數應回 0,得到 %d", code)
|
||||
}
|
||||
if !strings.Contains(stdout, "teai") || !strings.Contains(stdout, "命令") {
|
||||
t.Errorf("無引數應印出用法說明,得到:\n%s", stdout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunHelpFlags(t *testing.T) {
|
||||
for _, flagArg := range []string{"-h", "--help", "help"} {
|
||||
stdout, _, code := run(flagArg)
|
||||
if code != 0 {
|
||||
t.Errorf("%s 應回 0,得到 %d", flagArg, code)
|
||||
}
|
||||
if !strings.Contains(stdout, "用法") {
|
||||
t.Errorf("%s 應印出用法,得到:\n%s", flagArg, stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunVersion(t *testing.T) {
|
||||
for _, arg := range []string{"version", "-v", "--version"} {
|
||||
stdout, _, code := run(arg)
|
||||
if code != 0 {
|
||||
t.Errorf("%s 應回 0,得到 %d", arg, code)
|
||||
}
|
||||
if !strings.Contains(stdout, "teai version ") {
|
||||
t.Errorf("%s 應印出版本,得到:%q", arg, stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunUnknownCommand(t *testing.T) {
|
||||
_, stderr, code := run("no-such-command")
|
||||
if code != int(ExitUsage) {
|
||||
t.Errorf("未知命令應回 ExitUsage(%d),得到 %d", int(ExitUsage), code)
|
||||
}
|
||||
if !strings.Contains(stderr, "unknown command") {
|
||||
t.Errorf("未知命令應在 stderr 說明,得到:%q", stderr)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user