Module: Sloplint::CLI
- Defined in:
- lib/sloplint/cli.rb
Overview
Command-line shell: optparse, subcommands, exit codes. See docs/SPEC.md.
Exit codes: 0 ran/no notes, 1 ran/notes found, 2 bad arguments.
Class Method Summary collapse
-
.cmd_check(argv, opts, out:, err:, stdin:) ⇒ Object
── check ───────────────────────────────────────────────────────────────.
-
.cmd_explain(argv, out:, err:) ⇒ Object
── explain ID ────────────────────────────────────────────────────────.
-
.cmd_rules(argv, out:) ⇒ Object
── rules ───────────────────────────────────────────────────────────────.
- .global_parser(opts, out:) ⇒ Object
- .run(argv, out: $stdout, err: $stderr, stdin: $stdin) ⇒ Object
-
.select_rules(select, ignore) ⇒ Object
--select/--ignore accept rule ids or category names.
-
.unknown_rule_refs(refs) ⇒ Object
── helpers ───────────────────────────────────────────────────────────── Ids/categories in refs that match no rule in the catalog.
Class Method Details
.cmd_check(argv, opts, out:, err:, stdin:) ⇒ Object
── check ───────────────────────────────────────────────────────────────
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/sloplint/cli.rb', line 46 def cmd_check(argv, opts, out:, err:, stdin:) markdown = false select = nil ignore = nil p = OptionParser.new do |o| o. = "usage: sloplint check [options] [paths...] (\"-\" or no paths = stdin)" o.on("-o", "--output-format FORMAT", %w[full json], "Output format: 'full' or 'json' (may also be given before the command).") { |v| opts[:format] = v } o.on("--markdown", "Skip fenced/inline code spans and URLs before scanning.") { markdown = true } o.on("--select IDS", "Only run these rules (comma-separated rule ids or category names).") { |v| select = v.split(",").map(&:strip) } o.on("--ignore IDS", "Skip these rules (comma-separated rule ids or category names).") { |v| ignore = v.split(",").map(&:strip) } end p.order!(argv) unknown = unknown_rule_refs(select) + unknown_rule_refs(ignore) unless unknown.empty? err.puts("sloplint: unknown rule or category: #{unknown.join(", ")}") err.puts("run `sloplint rules` to list them.") return 2 end rules = select_rules(select, ignore) paths = argv.empty? ? ["-"] : argv by_path = paths.reject { |x| x == "-" }.size > 1 all_notes = [] paths.each do |path| text = if path == "-" stdin.read else unless File.file?(path) err.puts("sloplint: no such file: #{path}") return 2 end File.read(path) end label = path == "-" ? "-" : path all_notes.concat(Engine.scan(text, rules:, markdown:, path: label)) end case opts[:format] when "json" out.puts(Output.format_json(all_notes, by_path:)) else text = Output.format_human(all_notes) out.puts(text) unless text.empty? end all_notes.empty? ? 0 : 1 rescue ArgumentError => e err.puts("sloplint: invalid input: #{e.}") 2 end |
.cmd_explain(argv, out:, err:) ⇒ Object
── explain ID ────────────────────────────────────────────────────────
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/sloplint/cli.rb', line 125 def cmd_explain(argv, out:, err:) id = argv.shift unless id err.puts("usage: sloplint explain RULE_ID") return 2 end rule = RULES.find { |r| r.id == id } unless rule err.puts("sloplint: no such rule: #{id}") err.puts("run `sloplint rules` to list them.") return 2 end out.puts(<<~TXT) #{rule.id} (#{rule.category}, #{rule.severity}#{rule.default_on ? "" : ", off by default"}) #{rule.} Why: #{rule.rationale} Fix: #{rule.suggestion} Flags: #{rule.examples_bad.join("\n ")} Does not: #{rule.examples_ok.join("\n ")} TXT 0 end |
.cmd_rules(argv, out:) ⇒ Object
── rules ───────────────────────────────────────────────────────────────
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/sloplint/cli.rb', line 102 def cmd_rules(argv, out:) as_json = false OptionParser.new do |o| o. = "usage: sloplint rules [--json]" o.on("--json", "Emit the catalog as JSON for machine enumeration.") { as_json = true } end.order!(argv) if as_json payload = RULES.map do |r| { id: r.id, category: r.category, severity: r.severity, message: r., suggestion: r.suggestion, default_on: r.default_on } end out.puts(JSON.pretty_generate(payload)) else RULES.each do |r| off = r.default_on ? "" : " [off by default]" out.puts("#{r.id.ljust(24)} #{r.category.ljust(14)} #{r.severity.ljust(8)} #{r.}#{off}") end end 0 end |
.global_parser(opts, out:) ⇒ Object
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/sloplint/cli.rb', line 175 def global_parser(opts, out:) OptionParser.new do |o| o. = <<~BANNER sloplint — flag the rhetorical tics and puffery that mark AI-generated prose. # Recommended for agents: cat FILE | sloplint check --markdown -o json - # exit 0 = clean, 1 = notes found, >1 = error # each note: {path,line,column,severity,rule,category,message,excerpt,suggestion} usage: sloplint [-o full|json] <command> [args] commands: check scan paths (or stdin) for AI-slop tells and report notes [default] rules list the rule catalog (add --json for the machine-readable form) explain ID print one rule's message, rationale, and a bad/ok example version print the sloplint version global options: BANNER o.on("-o", "--output-format FORMAT", %w[full json], "Output format: 'full' (human-readable text) or 'json' (default: full).") do |v| opts[:format] = v end o.on("-h", "--help", "Show this help, including the copy-paste agent recipe above.") do out.puts(o.help) opts[:help_shown] = true end o.on("-v", "--version", "Print the sloplint version.") do out.puts(VERSION) opts[:version_shown] = true end o.separator "" o.separator "See `sloplint explain <id>` for any rule, or docs/SPEC.md for the JSON contract." end end |
.run(argv, out: $stdout, err: $stderr, stdin: $stdin) ⇒ Object
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/sloplint/cli.rb', line 17 def run(argv, out: $stdout, err: $stderr, stdin: $stdin) opts = { format: "full" } parser = global_parser(opts, out:) # Split global options from the subcommand and its args. parser.order!(argv) return 0 if opts[:help_shown] || opts[:version_shown] command = argv.shift # Bare invocation with piped stdin behaves as `check -`. command ||= (stdin.tty? ? "help" : "check") case command when "check" then cmd_check(argv, opts, out:, err:, stdin:) when "rules" then cmd_rules(argv, out:) when "explain" then cmd_explain(argv, out:, err:) when "version" then out.puts(VERSION); 0 when "help" then out.puts(parser.help); 0 else err.puts("sloplint: unknown command #{command.inspect}") err.puts(parser.help) 2 end rescue OptionParser::ParseError => e err.puts("sloplint: #{e.}") 2 end |
.select_rules(select, ignore) ⇒ Object
--select/--ignore accept rule ids or category names. Default set excludes default_on:false rules unless they are explicitly selected.
163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/sloplint/cli.rb', line 163 def select_rules(select, ignore) rules = if select RULES.select { |r| select.include?(r.id) || select.include?(r.category) } else RULES.select(&:default_on) end if ignore rules = rules.reject { |r| ignore.include?(r.id) || ignore.include?(r.category) } end rules end |
.unknown_rule_refs(refs) ⇒ Object
── helpers ───────────────────────────────────────────────────────────── Ids/categories in refs that match no rule in the catalog. nil (no --select or --ignore given) passes through as no unknowns.
154 155 156 157 158 159 |
# File 'lib/sloplint/cli.rb', line 154 def unknown_rule_refs(refs) return [] unless refs known = RULES.flat_map { |r| [r.id, r.category] }.uniq refs - known end |