internal/cli:允許命令旗標與位置參數交錯(#25)
parseFlags 改為先分離旗標與位置參數、再剖析旗標串、最後以第二次 Parse 把位置參數設回 fs.Args;Go 標準 flag 遇位置參數即停止的問題 不再影響「位置參數在前、旗標在後」的用法(teai api --method、 issues/pulls view --comments 等照 README 範例可用了)。 - login.go/workflow_commands.go 直接呼叫 fs.Parse 的六處改走 parseFlags,行為一致(login default/remove 的名稱也可與旗標交錯)。 - errHelp(命令 -h/--help)在 exitCodeFor 視為成功,與其既有註解 宣稱一致;命令層 -h 結束碼由 1/2 修正為 0。 - 新增 interleaved_flags_test.go 回歸測試:api/issues view/ pulls view/stalled/login default/login remove 的交錯組合。
This commit is contained in:
+59
-4
@@ -348,24 +348,79 @@ func dispatch(env *Env, name string, args []string) int {
|
||||
// 用法,靜默成功結束即可。
|
||||
var errHelp = errors.New("help requested")
|
||||
|
||||
// parseFlags 剖析命令旗標:-h/--help 回 errHelp(由 exitCodeFor 視為成功),
|
||||
// 其他剖析錯誤回 ErrUsage。
|
||||
// parseFlags 剖析命令旗標,允許旗標與位置參數交錯(#25):Go 標準
|
||||
// flag 在遇到第一個非旗標參數後即停止剖析,其後的旗標會被當成位置
|
||||
// 參數。做法:先掃描 args 把「旗標(含其值)」與「位置參數」分離成
|
||||
// 兩串,再以旗標串呼叫 fs.Parse,最後以第二次 Parse 把位置參數設回
|
||||
// fs.Args(Parse 遇到 "--" 即停止並收下其餘引數,第一次剖析已設好
|
||||
// 的旗標值不受影響)。
|
||||
// -h/--help 回 errHelp(由 exitCodeFor 視為成功),其他剖析錯誤回 ErrUsage。
|
||||
func parseFlags(fs *flag.FlagSet, args []string) error {
|
||||
if err := fs.Parse(args); err != nil {
|
||||
var flags, positional []string
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
if arg == "--" {
|
||||
// "--" 之後全部是位置參數(不再剖析旗標;同標準 flag)。
|
||||
positional = append(positional, args[i+1:]...)
|
||||
break
|
||||
}
|
||||
if len(arg) < 2 || arg[0] != '-' {
|
||||
positional = append(positional, arg)
|
||||
continue
|
||||
}
|
||||
flags = append(flags, arg)
|
||||
// 判斷旗標是否需要「下一個引數」當值(布林旗標與 inline
|
||||
// 形式 --name=value 不需要)。未知或格式錯誤的旗標不消費
|
||||
// 下一個引數,交給 fs.Parse 產生標準錯誤訊息。
|
||||
numMinuses := 1
|
||||
if arg[1] == '-' {
|
||||
numMinuses = 2
|
||||
}
|
||||
name := arg[numMinuses:]
|
||||
if name == "" || name[0] == '-' || name[0] == '=' {
|
||||
continue // ---x/--=…:bad flag syntax,由 fs.Parse 報錯
|
||||
}
|
||||
flagName, _, hasInline := strings.Cut(name, "=")
|
||||
if !hasInline {
|
||||
if fl := fs.Lookup(flagName); fl != nil && !isBoolFlag(fl) && i+1 < len(args) {
|
||||
i++
|
||||
flags = append(flags, args[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := fs.Parse(flags); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return errHelp
|
||||
}
|
||||
return &ErrUsage{Msg: err.Error()}
|
||||
}
|
||||
if len(positional) > 0 {
|
||||
if err := fs.Parse(append([]string{"--"}, positional...)); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return errHelp
|
||||
}
|
||||
return &ErrUsage{Msg: err.Error()}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isBoolFlag 回傳旗標是否為布林型(不需要值;同 flag 包內部判斷)。
|
||||
func isBoolFlag(fl *flag.Flag) bool {
|
||||
bf, ok := fl.Value.(interface{ IsBoolFlag() bool })
|
||||
return ok && bf.IsBoolFlag()
|
||||
}
|
||||
|
||||
// exitCodeFor 把命令錯誤映射到結束碼:用法錯誤 → 2,API 錯誤 → 3,
|
||||
// 其他(內部)→ 1。
|
||||
// 其他(內部)→ 1。errHelp(命令的 -h/--help)視為成功(#25:
|
||||
// 與 parseFlags 註解宣稱一致,FlagSet 已印出用法說明)。
|
||||
func exitCodeFor(err error) ExitCode {
|
||||
if err == nil {
|
||||
return ExitOK
|
||||
}
|
||||
if err == errHelp {
|
||||
return ExitOK
|
||||
}
|
||||
var usage *ErrUsage
|
||||
if errors.As(err, &usage) {
|
||||
return ExitUsage
|
||||
|
||||
Reference in New Issue
Block a user