(path string, idx int, ex Example, doc *Document, root *cobra.Command)
| 42 | } |
| 43 | |
| 44 | func validateExample(path string, idx int, ex Example, doc *Document, root *cobra.Command) []string { |
| 45 | tokens, err := shellSplit(ex.Cmd) |
| 46 | if err != nil { |
| 47 | return []string{fmt.Sprintf("%s example[%d]: cannot tokenize %q: %v", path, idx, ex.Cmd, err)} |
| 48 | } |
| 49 | if len(tokens) == 0 { |
| 50 | return []string{fmt.Sprintf("%s example[%d]: empty cmd string", path, idx)} |
| 51 | } |
| 52 | |
| 53 | // Token 0 should be the binary's name. Examples that begin with |
| 54 | // something else (e.g. `cat ~/foo.json | jq`) are documentation |
| 55 | // snippets showing related output, not pad invocations the |
| 56 | // validator can check. Skip them rather than fail — the drift |
| 57 | // contract is for pad-invocation drift specifically. |
| 58 | binary := root.Name() |
| 59 | if tokens[0] != binary { |
| 60 | return nil |
| 61 | } |
| 62 | |
| 63 | // Pass the full post-binary token stream to cobra's Find. Cobra |
| 64 | // knows each command's flag set and skips flag/value pairs while |
| 65 | // matching subcommand names — so examples that interleave flags |
| 66 | // with subcommands (e.g. `pad --workspace foo item create task`) |
| 67 | // resolve to `item create`, not the root. |
| 68 | target, _, ferr := root.Find(tokens[1:]) |
| 69 | if ferr != nil || target == nil { |
| 70 | return []string{fmt.Sprintf("%s example[%d]: command path doesn't resolve: %s", path, idx, ex.Cmd)} |
| 71 | } |
| 72 | |
| 73 | // Validate every --flag/-f against target's flag tree. |
| 74 | // Note: flags can appear before the subcommand path on cobra |
| 75 | // (e.g. `pad --workspace foo item create ...`), so scan all tokens. |
| 76 | var findings []string |
| 77 | skipNext := false |
| 78 | for _, t := range tokens[1:] { |
| 79 | if skipNext { |
| 80 | skipNext = false |
| 81 | continue |
| 82 | } |
| 83 | if !strings.HasPrefix(t, "-") { |
| 84 | continue |
| 85 | } |
| 86 | // Strip leading dashes and any =value suffix. |
| 87 | name := strings.TrimLeft(t, "-") |
| 88 | if eq := strings.IndexByte(name, '='); eq >= 0 { |
| 89 | name = name[:eq] |
| 90 | } |
| 91 | if name == "" { |
| 92 | continue // bare "--" terminator |
| 93 | } |
| 94 | // Walk target up to root, accept the flag if any level has it. |
| 95 | // Boolean flags don't consume the next token; non-bool flags |
| 96 | // do — but we only need the name check for drift detection, |
| 97 | // so don't bother with the value-consumption walk except to |
| 98 | // note that the "next token" might be a value rather than |
| 99 | // another flag (no special handling needed here). |
| 100 | if !flagExists(target, name) { |
| 101 | // Try negate-flag form: --no-<rest>. |
no test coverage detected