package app import ( "flag" "fmt" "io" "strconv" ) type boundedRunRequest struct { Config commonConfigFlags Plan BoundedPlan Force bool SelectedArtifacts []string } type singletonStringFlag struct { name string value string set bool } func (f *singletonStringFlag) String() string { return f.value } func (f *singletonStringFlag) Set(value string) error { if f.set { return fmt.Errorf("--%s may be specified only once", f.name) } f.value = value f.set = true return nil } type singletonBoolFlag struct { name string value bool set bool } func (f *singletonBoolFlag) String() string { return strconv.FormatBool(f.value) } func (f *singletonBoolFlag) IsBoolFlag() bool { return true } func (f *singletonBoolFlag) Set(raw string) error { if f.set { return fmt.Errorf("--%s may be specified only once", f.name) } value, err := strconv.ParseBool(raw) if err != nil { return fmt.Errorf("--%s requires a boolean value: %w", f.name, err) } f.value = value f.set = true return nil } func parseBoundedRunRequest(command string, args []string, help io.Writer) (boundedRunRequest, error) { fs := flag.NewFlagSet(command, flag.ContinueOnError) fs.SetOutput(help) var configFlags commonConfigFlags var from singletonStringFlag var through singletonStringFlag var force singletonBoolFlag var selectedArtifacts artifactSelectionFlag from.name = "from" through.name = "through" force.name = "force" addCommonConfigFlags(fs, &configFlags) fs.Var(&from, "from", "first canonical stage to select (inclusive)") fs.Var(&through, "through", "last canonical stage to select (inclusive)") fs.Var(&force, "force", "rerun selected stages even when already succeeded") fs.Var(&selectedArtifacts, "artifacts", "configured artifact names to execute and publish (comma-separated or repeatable)") fs.Usage = func() { invocation := "narratio run" if command == "plan" { invocation = "narratio session plan" } _, _ = fmt.Fprintf(help, "Usage: %s [--from ] [--through ] [--force] [--artifacts ] [common config flags]\n\n", invocation) _, _ = fmt.Fprintln(help, "Bounds are inclusive; omitted --from or --through selects the beginning or end of the canonical pipeline.") _, _ = fmt.Fprintln(help) _, _ = fmt.Fprintln(help, "Flags:") fs.PrintDefaults() } if err := parseSessionAwareFlags(command, fs, args, &configFlags.sessionID); err != nil { return boundedRunRequest{}, err } if configFlags.sessionID == "" { return boundedRunRequest{}, fmt.Errorf("%s: session_id is required", command) } plan, err := BuildBoundedPlan(from.value, through.value) if err != nil { return boundedRunRequest{}, fmt.Errorf("%s: %w", command, err) } normalizedArtifacts, err := selectedArtifacts.Normalize() if err != nil { return boundedRunRequest{}, fmt.Errorf("%s: invalid --artifacts: %w", command, err) } if len(normalizedArtifacts) > 0 && !plan.Contains("analyze") && !plan.Contains("publish") { return boundedRunRequest{}, fmt.Errorf("%s: --artifacts requires a selected range containing analyze or publish", command) } return boundedRunRequest{ Config: configFlags, Plan: plan, Force: force.value, SelectedArtifacts: normalizedArtifacts, }, nil }