67 lines
3.3 KiB
Go
67 lines
3.3 KiB
Go
package main
|
||
|
||
import (
|
||
"flag"
|
||
"fmt"
|
||
"io"
|
||
|
||
"textmachine/backend/internal/pipeline"
|
||
)
|
||
|
||
// invocation.go: разбор аргументов CLI, выделенный в чистую функцию (пакет №4 —
|
||
// у tmctl было 0 тестов на контрактные инварианты: bad-flag→exit 1, а не 2;
|
||
// селектор redrive; порядок валидации). Контракт флагов/текстов ошибок ЗАМОРОЖЕН
|
||
// (D12/D15.3) — эта функция только переносит его в тестируемое место.
|
||
|
||
// invocation is one parsed tmctl command line.
|
||
type invocation struct {
|
||
cmd string
|
||
cfgPath string
|
||
resnapshot bool
|
||
asJSON bool
|
||
asPlaintext bool
|
||
sel pipeline.RedriveSelector
|
||
}
|
||
|
||
// parseInvocation parses os.Args[1:] into an invocation. flagOut receives the
|
||
// stdlib's flag diagnostics ("flag provided but not defined" + usage) — main
|
||
// passes os.Stderr, tests a buffer; the bytes and their destination are part of
|
||
// the frozen contract.
|
||
//
|
||
// Deliberately preserved quirks (менять = менять контракт):
|
||
// - the command NAME is not validated here — `tmctl bogus --config x` reaches
|
||
// the dispatch switch (after dotenv/ctx setup) and errors there, while
|
||
// `tmctl bogus` without --config errors "--config book.yaml is required";
|
||
// - ContinueOnError, НЕ ExitOnError: стдлибовский ExitOnError звал бы
|
||
// os.Exit(2) на плохом флаге и КОЛЛАПСИРОВАЛ бы с exit-кодом 2
|
||
// (completed-with-flags); возврат ошибки ведёт плохой флаг в default-ветку
|
||
// main() → exit 1, оставляя 2 эксклюзивным для флагнутых чанков.
|
||
func parseInvocation(args []string, flagOut io.Writer) (invocation, error) {
|
||
if len(args) < 1 {
|
||
return invocation{}, fmt.Errorf("usage: tmctl <translate|report|status|export|redrive> --config book.yaml")
|
||
}
|
||
cmd, rest := args[0], args[1:]
|
||
|
||
fs := flag.NewFlagSet(cmd, flag.ContinueOnError)
|
||
fs.SetOutput(flagOut)
|
||
cfgPath := fs.String("config", "", "path to book.yaml")
|
||
resnapshot := fs.Bool("resnapshot", false, "re-pin existing jobs to the current config snapshot (пере-перевод оплаченных чанков — явное согласие)")
|
||
asJSON := fs.Bool("json", false, "status: emit the projection as JSON (stable disposition/flag_reason enums) for CI/IDE")
|
||
asPlaintext := fs.Bool("plaintext", false, "export: emit the concatenated human text instead of the default stable JSON")
|
||
chapter := fs.Int("chapter", -1, "redrive: restrict to this chapter (default: any)")
|
||
chunk := fs.Int("chunk", -1, "redrive: restrict to this chunk index within the chapter (default: any)")
|
||
reason := fs.String("reason", "", "redrive: restrict to this flag_reason (default: any)")
|
||
dryRun := fs.Bool("dry-run", false, "redrive: report what would be re-attacked without touching anything")
|
||
if err := fs.Parse(rest); err != nil {
|
||
return invocation{}, err
|
||
}
|
||
if *cfgPath == "" {
|
||
return invocation{}, fmt.Errorf("--config book.yaml is required")
|
||
}
|
||
return invocation{
|
||
cmd: cmd, cfgPath: *cfgPath, resnapshot: *resnapshot, asJSON: *asJSON, asPlaintext: *asPlaintext,
|
||
sel: pipeline.RedriveSelector{
|
||
Chapter: *chapter, ChunkIdx: *chunk, Reason: *reason, DryRun: *dryRun,
|
||
},
|
||
}, nil
|
||
}
|