diff --git a/cmd/audita/main.go b/cmd/audita/main.go new file mode 100644 index 0000000..c299502 --- /dev/null +++ b/cmd/audita/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "os" + + "gitea.maximumdirect.net/eric/audita/internal/cli" +) + +func main() { + os.Exit(cli.Run(os.Args[1:], os.Stdout, os.Stderr)) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e17d515 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gitea.maximumdirect.net/eric/audita + +go 1.22 diff --git a/internal/cli/run.go b/internal/cli/run.go new file mode 100644 index 0000000..07b5195 --- /dev/null +++ b/internal/cli/run.go @@ -0,0 +1,127 @@ +package cli + +import ( + "errors" + "flag" + "fmt" + "io" +) + +const processNotImplementedMessage = "process command is not implemented yet" + +// Run executes the Audita CLI with the provided arguments and streams. +func Run(args []string, stdout, stderr io.Writer) int { + if len(args) == 0 { + writeRootUsage(stdout) + return 0 + } + + if isHelpCommand(args) { + writeRootUsage(stdout) + return 0 + } + + if args[0] == "process" { + return runProcess(args[1:], stdout, stderr) + } + + fmt.Fprintf(stderr, "audita: unknown command %q\n\n", args[0]) + writeRootUsage(stderr) + return 2 +} + +func runProcess(args []string, stdout, stderr io.Writer) int { + if isHelpCommand(args) || hasHelpFlag(args) { + writeProcessUsage(stdout) + return 0 + } + + fs := flag.NewFlagSet("process", flag.ContinueOnError) + fs.SetOutput(stderr) + + glossaryPath := fs.String("glossary", "", "Path to glossary YAML file") + outputPath := fs.String("output", "", "Path to corrected transcript JSON output file") + reportJSONPath := fs.String("report-json", "", "Path to machine-readable report JSON output file") + modules := fs.String("modules", "", "Comma-separated module sequence override") + + _ = glossaryPath + _ = outputPath + _ = reportJSONPath + _ = modules + + if err := fs.Parse(args); err != nil { + if errors.Is(err, flag.ErrHelp) { + writeProcessUsage(stdout) + return 0 + } + return 2 + } + + remaining := fs.Args() + if len(remaining) != 1 { + fmt.Fprintln(stderr, "audita process: expected exactly 1 transcript JSON path argument") + fmt.Fprintln(stderr) + writeProcessUsage(stderr) + return 2 + } + + fmt.Fprintf(stderr, "audita process: %s\n", processNotImplementedMessage) + return 1 +} + +func isHelpCommand(args []string) bool { + if len(args) == 0 { + return false + } + if len(args) == 1 { + switch args[0] { + case "help", "-h", "--help": + return true + } + } + if len(args) == 2 && args[0] == "help" { + switch args[1] { + case "process": + return true + } + } + return false +} + +func hasHelpFlag(args []string) bool { + for _, arg := range args { + if arg == "-h" || arg == "--help" { + return true + } + } + return false +} + +func writeRootUsage(w io.Writer) { + fmt.Fprintln(w, "Audita is a transcript polishing CLI.") + fmt.Fprintln(w) + fmt.Fprintln(w, "Usage:") + fmt.Fprintln(w, " audita [options]") + fmt.Fprintln(w) + fmt.Fprintln(w, "Commands:") + fmt.Fprintln(w, " process Process a transcript JSON file") + fmt.Fprintln(w) + fmt.Fprintln(w, "Example:") + fmt.Fprintln(w, " audita process transcript.json --glossary glossary.yaml --output corrected.json") +} + +func writeProcessUsage(w io.Writer) { + fmt.Fprintln(w, "Process a transcript JSON file.") + fmt.Fprintln(w) + fmt.Fprintln(w, "Usage:") + fmt.Fprintln(w, " audita process [flags]") + fmt.Fprintln(w) + fmt.Fprintln(w, "Flags:") + fmt.Fprintln(w, " --glossary Path to glossary YAML file") + fmt.Fprintln(w, " --output Path to corrected transcript JSON output file") + fmt.Fprintln(w, " --report-json Path to machine-readable report JSON output file") + fmt.Fprintln(w, " --modules Comma-separated module sequence override") + fmt.Fprintln(w) + fmt.Fprintln(w, "Example:") + fmt.Fprintln(w, " audita process transcript.json --glossary glossary.yaml --output corrected.json") +} diff --git a/internal/cli/run_test.go b/internal/cli/run_test.go new file mode 100644 index 0000000..eb8ea57 --- /dev/null +++ b/internal/cli/run_test.go @@ -0,0 +1,77 @@ +package cli + +import ( + "bytes" + "strings" + "testing" +) + +func TestRunRootHelp(t *testing.T) { + var stdout bytes.Buffer + var stderr bytes.Buffer + + exitCode := Run([]string{"--help"}, &stdout, &stderr) + if exitCode != 0 { + t.Fatalf("expected exit code 0, got %d", exitCode) + } + if !strings.Contains(stdout.String(), "audita [options]") { + t.Fatalf("expected root usage in stdout, got %q", stdout.String()) + } + if !strings.Contains(stdout.String(), "process") { + t.Fatalf("expected process command in root help, got %q", stdout.String()) + } + if stderr.Len() != 0 { + t.Fatalf("expected empty stderr, got %q", stderr.String()) + } +} + +func TestRunProcessHelp(t *testing.T) { + var stdout bytes.Buffer + var stderr bytes.Buffer + + exitCode := Run([]string{"process", "--help"}, &stdout, &stderr) + if exitCode != 0 { + t.Fatalf("expected exit code 0, got %d", exitCode) + } + if !strings.Contains(stdout.String(), "audita process [flags]") { + t.Fatalf("expected process usage in stdout, got %q", stdout.String()) + } + if !strings.Contains(stdout.String(), "--glossary") { + t.Fatalf("expected glossary flag in process help, got %q", stdout.String()) + } + if stderr.Len() != 0 { + t.Fatalf("expected empty stderr, got %q", stderr.String()) + } +} + +func TestRunUnknownCommand(t *testing.T) { + var stdout bytes.Buffer + var stderr bytes.Buffer + + exitCode := Run([]string{"unknown"}, &stdout, &stderr) + if exitCode != 2 { + t.Fatalf("expected exit code 2, got %d", exitCode) + } + if stdout.Len() != 0 { + t.Fatalf("expected empty stdout, got %q", stdout.String()) + } + if !strings.Contains(stderr.String(), "unknown command") { + t.Fatalf("expected unknown command error in stderr, got %q", stderr.String()) + } +} + +func TestRunProcessNotImplemented(t *testing.T) { + var stdout bytes.Buffer + var stderr bytes.Buffer + + exitCode := Run([]string{"process", "transcript.json"}, &stdout, &stderr) + if exitCode == 0 { + t.Fatalf("expected nonzero exit code for not-implemented process") + } + if stdout.Len() != 0 { + t.Fatalf("expected empty stdout, got %q", stdout.String()) + } + if !strings.Contains(stderr.String(), processNotImplementedMessage) { + t.Fatalf("expected not-implemented message in stderr, got %q", stderr.String()) + } +}