Class: JSON::Repair::CLI
- Inherits:
-
Object
- Object
- JSON::Repair::CLI
- Defined in:
- lib/json/repair/cli.rb,
sig/json/repair/cli.rbs
Constant Summary collapse
- OVERWRITE_DESC =
Marked
private_constantinlib/json/repair/cli.rb. RBS has noprivate_constantsyntax, so the declaration is unavoidably public in the signature; do not rely on it from outside this class. 'Replace the input file in place (requires filename; conflicts with --output)'
Class Method Summary collapse
Instance Method Summary collapse
-
#call(argv) ⇒ ::Integer
Reset per-invocation state so a single instance can be safely reused (e.g.
cli = CLI.new; cli.call(['-v']); cli.call(['x'])). - #define_options(opts) ⇒ void
-
#halt_with(message) ⇒ void
Print to stdout and short-circuit
parser.parseso trailing args after --version/--help do not raise OptionParser::ParseError and flip the exit code (the option text promises "...and exit"). -
#initialize(stdin: $stdin, stdout: $stdout, stderr: $stderr) ⇒ CLI
constructor
A new instance of CLI.
- #parser ⇒ ::OptionParser
- #read_input(input_path) ⇒ ::String
-
#replace_in_place(input_path, repaired) ⇒ void
Write to a uniquely-named tempfile alongside the input, then move it over the original.
- #run(argv) ⇒ ::Integer
- #validate(positional, input_path) ⇒ Boolean
- #validation_error(positional, input_path) ⇒ ::String?
- #write_output(repaired, input_path) ⇒ void
Constructor Details
#initialize(stdin: $stdin, stdout: $stdout, stderr: $stderr) ⇒ CLI
Returns a new instance of CLI.
15 16 17 18 19 |
# File 'lib/json/repair/cli.rb', line 15 def initialize(stdin: $stdin, stdout: $stdout, stderr: $stderr) @stdin = stdin @stdout = stdout @stderr = stderr end |
Class Method Details
.call(argv, stdin: $stdin, stdout: $stdout, stderr: $stderr) ⇒ ::Integer
11 12 13 |
# File 'lib/json/repair/cli.rb', line 11 def self.call(argv, stdin: $stdin, stdout: $stdout, stderr: $stderr) new(stdin: stdin, stdout: stdout, stderr: stderr).call(argv) end |
Instance Method Details
#call(argv) ⇒ ::Integer
Reset per-invocation state so a single instance can be safely reused
(e.g. cli = CLI.new; cli.call(['-v']); cli.call(['x'])).
23 24 25 26 27 28 29 30 31 |
# File 'lib/json/repair/cli.rb', line 23 def call(argv) @output_path = @halt = nil @overwrite = false run(argv) rescue OptionParser::ParseError, JSON::JSONRepairError, SystemCallError, IOError, SystemStackError => e @stderr.puts "json-repair: #{e.}" 1 end |
#define_options(opts) ⇒ void
This method returns an undefined value.
116 117 118 119 120 121 |
# File 'lib/json/repair/cli.rb', line 116 def (opts) opts.on('-o', '--output FILE', 'Write repaired JSON to FILE') { |f| @output_path = f } opts.on('--overwrite', OVERWRITE_DESC) { @overwrite = true } opts.on('-v', '--version', 'Print version and exit') { halt_with(JSON::Repair::VERSION) } opts.on('-h', '--help', 'Print this help and exit') { halt_with(opts.help) } end |
#halt_with(message) ⇒ void
This method returns an undefined value.
Print to stdout and short-circuit parser.parse so trailing args
after --version/--help do not raise OptionParser::ParseError and
flip the exit code (the option text promises "...and exit").
126 127 128 129 130 |
# File 'lib/json/repair/cli.rb', line 126 def halt_with() @stdout.puts @halt = 0 throw :halt end |
#parser ⇒ ::OptionParser
103 104 105 106 107 108 109 110 111 |
# File 'lib/json/repair/cli.rb', line 103 def parser OptionParser.new do |opts| opts. = 'Usage: json-repair [filename] [options]' opts.separator '' opts.separator 'Repair a broken JSON document. Reads stdin when no filename is given.' opts.separator '' (opts) end end |
#read_input(input_path) ⇒ ::String
64 65 66 67 68 69 70 |
# File 'lib/json/repair/cli.rb', line 64 def read_input(input_path) raw = (input_path ? File.read(input_path) : @stdin.read).to_s raw.force_encoding(Encoding::UTF_8) raise JSON::JSONRepairError, 'input is not valid UTF-8' unless raw.valid_encoding? raw end |
#replace_in_place(input_path, repaired) ⇒ void
This method returns an undefined value.
Write to a uniquely-named tempfile alongside the input, then move it over the original. Tempfile.create uses O_EXCL + a random suffix, so the temp path is safe against symlink / clobber races; FileUtils.mv with force: true handles cross-device renames and Windows, where File.rename cannot overwrite an existing destination. The original file's mode is preserved (Tempfile defaults to 0600).
Symlinks are followed via File.realpath so the underlying file is rewritten in place and the link is left pointing at it; otherwise the rename would replace the link itself with a regular file.
92 93 94 95 96 97 98 99 100 101 |
# File 'lib/json/repair/cli.rb', line 92 def replace_in_place(input_path, repaired) real_path = File.realpath(input_path) original_mode = File.stat(real_path).mode Tempfile.create(['json-repair', '.tmp'], File.dirname(real_path)) do |tmp| tmp.write(repaired) tmp.close File.chmod(original_mode, tmp.path) FileUtils.mv(tmp.path, real_path, force: true) end end |
#run(argv) ⇒ ::Integer
35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/json/repair/cli.rb', line 35 def run(argv) positional = catch(:halt) { parser.parse(argv) } halt = @halt return halt if halt input_path = positional.first return 1 unless validate(positional, input_path) repaired = JSON.repair(read_input(input_path)) write_output(repaired, input_path) 0 end |
#validate(positional, input_path) ⇒ Boolean
48 49 50 51 52 53 54 |
# File 'lib/json/repair/cli.rb', line 48 def validate(positional, input_path) error = validation_error(positional, input_path) return true unless error @stderr.puts "json-repair: #{error}" false end |
#validation_error(positional, input_path) ⇒ ::String?
56 57 58 59 60 61 62 |
# File 'lib/json/repair/cli.rb', line 56 def validation_error(positional, input_path) return "unexpected argument: #{positional[1]}" if positional.length > 1 return '--overwrite requires a filename' if @overwrite && input_path.nil? return '--overwrite and --output are mutually exclusive' if @overwrite && @output_path nil end |
#write_output(repaired, input_path) ⇒ void
This method returns an undefined value.
72 73 74 75 76 77 78 79 80 |
# File 'lib/json/repair/cli.rb', line 72 def write_output(repaired, input_path) if @overwrite && input_path replace_in_place(input_path, repaired) elsif (output_path = @output_path) File.write(output_path, repaired) else @stdout.write(repaired, "\n") end end |