sloplint 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/sloplint/cli.rb CHANGED
@@ -10,7 +10,8 @@ require_relative "output"
10
10
  module Sloplint
11
11
  # Command-line shell: optparse, subcommands, exit codes. See docs/SPEC.md.
12
12
  #
13
- # Exit codes: 0 ran/no notes, 1 ran/notes found, 2 bad arguments.
13
+ # Exit codes: 0 ran/no notes, 1 ran/notes found, 2 bad arguments. Empty
14
+ # input is a 2 as well: an unread draft must not report as a clean one.
14
15
  module CLI
15
16
  module_function
16
17
 
@@ -33,9 +34,9 @@ module Sloplint
33
34
  when "version" then out.puts(VERSION); 0
34
35
  when "help" then out.puts(parser.help); 0
35
36
  else
36
- err.puts("sloplint: unknown command #{command.inspect}")
37
- err.puts(parser.help)
38
- 2
37
+ # Anything else is a path or "-": `sloplint FILE`, `sloplint -`.
38
+ # A mistyped command lands here too, and fails as a missing file.
39
+ cmd_check(argv.unshift(command), opts, out:, err:, stdin:)
39
40
  end
40
41
  rescue OptionParser::ParseError => e
41
42
  err.puts("sloplint: #{e.message}")
@@ -68,20 +69,38 @@ module Sloplint
68
69
  paths = argv.empty? ? ["-"] : argv
69
70
  by_path = paths.reject { |x| x == "-" }.size > 1
70
71
 
71
- all_notes = []
72
+ sources = []
72
73
  paths.each do |path|
74
+ # Read as UTF-8 whatever the locale says. A sandbox with no LANG set
75
+ # leaves Ruby's default external encoding at US-ASCII, and then the
76
+ # first em dash raises "invalid byte sequence in US-ASCII" -- on prose
77
+ # that is perfectly valid UTF-8. Prose is the only input sloplint
78
+ # takes, so UTF-8 is the assumption, not the locale's guess.
73
79
  text =
74
80
  if path == "-"
75
- stdin.read
81
+ stdin.read.force_encoding(Encoding::UTF_8)
76
82
  else
77
83
  unless File.file?(path)
78
84
  err.puts("sloplint: no such file: #{path}")
79
85
  return 2
80
86
  end
81
- File.read(path)
87
+ File.read(path, encoding: Encoding::UTF_8)
82
88
  end
83
- label = path == "-" ? "-" : path
84
- all_notes.concat(Engine.scan(text, rules:, markdown:, path: label))
89
+ sources << [path == "-" ? "-" : path, text]
90
+ end
91
+
92
+ # Tested on the raw text, before --markdown blanks code and URLs: a file
93
+ # that holds only a fenced code block did arrive, and scanning it clean
94
+ # is right. Nothing arriving at all is the trap -- the same one a
95
+ # mistyped rule id sets, and it exits 2 for the same reason.
96
+ if sources.all? { |_, text| text.strip.empty? }
97
+ names = sources.map { |label, _| label == "-" ? "stdin" : label }
98
+ err.puts("sloplint: empty input: nothing to check in #{names.join(", ")}")
99
+ return 2
100
+ end
101
+
102
+ all_notes = sources.flat_map do |label, text|
103
+ Engine.scan(text, rules:, markdown:, path: label)
85
104
  end
86
105
 
87
106
  case opts[:format]
@@ -93,7 +112,10 @@ module Sloplint
93
112
  end
94
113
 
95
114
  all_notes.empty? ? 0 : 1
96
- rescue ArgumentError => e
115
+ # Invalid UTF-8 reaches this two ways: String#strip in the empty check
116
+ # raises Encoding::CompatibilityError, the engine's regexes raise
117
+ # ArgumentError. Both are the same thing to the reader.
118
+ rescue ArgumentError, Encoding::CompatibilityError => e
97
119
  err.puts("sloplint: invalid input: #{e.message}")
98
120
  2
99
121
  end
@@ -187,13 +209,14 @@ module Sloplint
187
209
 
188
210
  # Recommended for agents:
189
211
  cat FILE | sloplint check --markdown -o json -
190
- # exit 0 = clean, 1 = notes found, >1 = error
212
+ # exit 0 = clean, 1 = notes found, >1 = error (empty input is an error)
191
213
  # each note: {path,line,column,severity,rule,category,message,excerpt,context,rationale,suggestion}
192
214
 
193
- usage: sloplint [-o full|json] <command> [args]
215
+ usage: sloplint [-o full|json] [command] [args]
194
216
 
195
217
  commands:
196
218
  check scan paths (or stdin) for AI-slop tells and report notes [default]
219
+ a first argument that is not a command name is taken as a path
197
220
  rules list the rule catalog (add --json for the machine-readable form)
198
221
  explain ID print one rule's message, rationale, and a bad/ok example
199
222
  version print the sloplint version