diff --git a/README.md b/README.md index 2f4e899..89a3e68 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,9 @@ prmaven why -project . prmaven why -project . -format json prmaven why -project . -module payment-core prmaven why -project . -format json -output prmaven-report.json +prmaven -h +prmaven help +prmaven version ``` Stage 1 treats `fails` and `why` as equivalent commands. The distinction is reserved for future UX where `fails` may list failures and `why` may include richer causality evidence. diff --git a/cmd/prmaven/main.go b/cmd/prmaven/main.go index 1ea6c37..a991986 100644 --- a/cmd/prmaven/main.go +++ b/cmd/prmaven/main.go @@ -18,6 +18,11 @@ func main() { } func run(args []string, stdout, stderr io.Writer) int { + if wantsHelp(args) { + writeUsage(stdout) + return 0 + } + command := "fails" if len(args) > 0 && args[0] != "" && args[0][0] != '-' { command = args[0] @@ -26,12 +31,16 @@ func run(args []string, stdout, stderr io.Writer) int { flags := flag.NewFlagSet("prmaven", flag.ContinueOnError) flags.SetOutput(stderr) + flags.Usage = func() { + writeUsage(stderr) + } projectDir := flags.String("project", ".", "Maven project directory") format := flags.String("format", "text", "output format: text or json") moduleFilter := flags.String("module", "", "limit findings to a Maven module path or artifactId") outputPath := flags.String("output", "", "write output to file instead of stdout") if err := flags.Parse(args); err != nil { + writeUsage(stderr) return 2 } @@ -40,13 +49,18 @@ func run(args []string, stdout, stderr io.Writer) int { } switch command { - case "fails", "why", "version": + case "fails", "why", "version", "help": default: fmt.Fprintf(stderr, "unknown command %q\n", command) - fmt.Fprintln(stderr, "available commands: fails, why, version") + writeUsage(stderr) return 2 } + if command == "help" { + writeUsage(stdout) + return 0 + } + if command == "version" { fmt.Fprintln(stdout, version) return 0 @@ -103,6 +117,57 @@ func run(args []string, stdout, stderr io.Writer) int { return 0 } +func wantsHelp(args []string) bool { + if len(args) > 0 && args[0] == "help" { + return true + } + for _, arg := range args { + switch arg { + case "-h", "--help", "-help": + return true + } + } + return false +} + +func writeUsage(w io.Writer) { + fmt.Fprint(w, `PR Maven CLI - Maven-aware PR/CI failure context + +Usage: + prmaven [command] [flags] + +Commands: + fails Analyze local Maven reports and print actionable failure context. + why Analyze local Maven reports; reserved for richer causality context. + version Print the CLI version. + help Print this help. + +Flags: + -project string + Maven project directory to analyze. Defaults to ".". + -format string + Output format: text or json. Defaults to "text". + -module string + Limit findings to a Maven module path or artifactId. + -output string + Write output to a file instead of stdout. + -h, -help, --help + Print this help. + +Examples: + prmaven fails -project . + prmaven fails -project . -format json + prmaven why -project . -module payment-core + prmaven why -project . -format json -output prmaven-report.json + prmaven version + +Exit codes: + 0 Analysis completed with no findings, or help/version completed. + 1 Analysis completed with Maven findings, or analysis/output failed. + 2 Invalid CLI usage. +`) +} + func filterReportByModule(report prmaven.Report, moduleFilter string) prmaven.Report { moduleFilter = strings.TrimSpace(moduleFilter) if moduleFilter == "" { diff --git a/cmd/prmaven/main_test.go b/cmd/prmaven/main_test.go index edb417f..0f08c5a 100644 --- a/cmd/prmaven/main_test.go +++ b/cmd/prmaven/main_test.go @@ -219,6 +219,72 @@ func TestRunReturnsUsageExitCode(t *testing.T) { if !strings.Contains(stderr.String(), `unknown command "unknown"`) { t.Fatalf("stderr missing unknown command\n%s", stderr.String()) } + if !strings.Contains(stderr.String(), "Commands:") { + t.Fatalf("stderr missing usage commands\n%s", stderr.String()) + } +} + +func TestRunHelpIncludesCommandsFlagsAndExamples(t *testing.T) { + tests := [][]string{ + {"-h"}, + {"--help"}, + {"help"}, + } + + for _, args := range tests { + t.Run(strings.Join(args, " "), func(t *testing.T) { + var stdout bytes.Buffer + var stderr bytes.Buffer + + code := run(args, &stdout, &stderr) + if code != 0 { + t.Fatalf("exit code = %d, want 0", code) + } + if stderr.Len() != 0 { + t.Fatalf("stderr = %q, want empty", stderr.String()) + } + + text := stdout.String() + for _, expected := range []string{ + "Usage:", + "Commands:", + "fails Analyze local Maven reports", + "why Analyze local Maven reports", + "Flags:", + "-project string", + "-format string", + "-module string", + "-output string", + "Examples:", + "prmaven why -project . -format json -output prmaven-report.json", + "Exit codes:", + } { + if !strings.Contains(text, expected) { + t.Fatalf("help output missing %q\n%s", expected, text) + } + } + }) + } +} + +func TestCLIEndToEndHelp(t *testing.T) { + command := exec.Command("go", "run", ".", "-h") + output, err := command.CombinedOutput() + if err != nil { + t.Fatalf("CLI help exit error = %v\n%s", err, string(output)) + } + + text := string(output) + for _, expected := range []string{ + "PR Maven CLI - Maven-aware PR/CI failure context", + "Commands:", + "Flags:", + "Examples:", + } { + if !strings.Contains(text, expected) { + t.Fatalf("CLI help output missing %q\n%s", expected, text) + } + } } func TestRunVersion(t *testing.T) { diff --git a/docs/implementation-status.md b/docs/implementation-status.md index ff24341..af73e68 100644 --- a/docs/implementation-status.md +++ b/docs/implementation-status.md @@ -24,6 +24,7 @@ Implemented commands: - `fails`; - `why`; +- `help`; - `version`. Implemented flags: diff --git a/docs/usage.md b/docs/usage.md index 9fe2667..5441561 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -17,17 +17,22 @@ prmaven why -project . prmaven why -project . -format json prmaven why -project . -module payment-core prmaven why -project . -format json -output prmaven-report.json +prmaven -h +prmaven help prmaven version ``` Stage 1 treats `fails` and `why` as equivalent analysis commands. The separate names leave room for future UX where `fails` lists failures and `why` adds richer causality evidence. +Built-in help is available with `prmaven -h`, `prmaven --help`, `prmaven -help`, or `prmaven help`. It lists commands, flags, examples, and exit codes without requiring network access or extra dependencies. + ## Flags - `-project`: Maven project directory to analyze. Defaults to `.`. - `-format`: output format. Supported values are `text` and `json`. Defaults to `text`. - `-module`: optional Maven module filter. Matches either a module path, such as `payment-core`, or a module artifactId. Limits emitted findings to matching modules. - `-output`: optional file path for the generated text or JSON report. When omitted, output is written to stdout. +- `-h`, `-help`, `--help`: print built-in CLI help. When `-module` is set and no module matches, PR Maven CLI emits zero findings for that filtered view.