check_please 0.2.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33a26fc2e249e7ff6bba107ba2da856f0f70a9e232862b3650893b1844117cac
4
- data.tar.gz: c8624e0cfc38a4f7bcd9db9af22215f0d2d6311ab061ec156318894fa8a01b60
3
+ metadata.gz: c4e0e81da97eb41aade7d58c516519bde3d2527173b64e7d644e46458493450d
4
+ data.tar.gz: c633c13e0a776bdbf1691b9c01d3be3cc978cb445c2fbf8d74e95449ea0b3118
5
5
  SHA512:
6
- metadata.gz: 93c0b1d909dbdaf606a0376f38062eaaa26bd19573413ce0d81002b120aa247176d7a564752b83f6ac9a359974df32a7a5c2997f20468f95d683b28369d5548b
7
- data.tar.gz: 78fe71950e42986ef0b9857984310fa51955d08a5b1b9b432d2b277fb9191b188b173f78575d0f88a927d4668065ac35fc32f1d74bba082894f897c0d9896267
6
+ metadata.gz: ad31ad99aef3494a2d2438b422a5fef9385c6fd05572131907d33d9e1e30a94ba0582ca4aae00c997dbe302487719da3d5ddbf715cf23422405086084f7a982c
7
+ data.tar.gz: 04d8e89b72894e458394ff0428192ffbe6f3b307437ce6ee8496f6e3064236498286b2fb203a399c5245172cec391656646dac39b20f0adc8a345eff13b8f015
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- check_please (0.2.1)
4
+ check_please (0.4.0)
5
5
  table_print
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # check_please
2
2
 
3
- Check for differences between two JSON strings (or Ruby data structures parsed from them).
3
+ Check for differences between two JSON documents, YAML documents, or Ruby data
4
+ structures parsed from either of those.
4
5
 
5
6
  ## Installation
6
7
 
@@ -18,11 +19,10 @@ Or install it yourself as:
18
19
 
19
20
  $ gem install check_please
20
21
 
21
- ## Usage
22
-
23
- ### Terminology
22
+ ## Terminology
24
23
 
25
- CheckPlease uses a few words in a jargony way:
24
+ I know, you just want to see how to use this thing. Feel free to scroll down,
25
+ but be aware that CheckPlease uses a few words in a jargony way:
26
26
 
27
27
  * **Reference** is always used to refer to the "target" or "source of truth."
28
28
  We assume you're comparing two things because you want one of them to be like
@@ -35,7 +35,9 @@ CheckPlease uses a few words in a jargony way:
35
35
  **reference** and the **candidate**. More on this in "Understanding the Output",
36
36
  below.
37
37
 
38
- ### CLI
38
+ ## Usage
39
+
40
+ ### From the Terminal
39
41
 
40
42
  Use the `bin/check_please` executable. (To get started, run it with the '-h' flag.)
41
43
 
@@ -46,19 +48,21 @@ of giving it a second filename as the argument. (This is especially useful if
46
48
  you're copying an XHR response out of a web browser's dev tools and have a tool
47
49
  like MacOS's `pbpaste` utility.)
48
50
 
49
- ### RSpec Matcher
51
+ ### From RSpec
50
52
 
51
53
  See [check_please_rspec_matcher](https://github.com/RealGeeks/check_please_rspec_matcher).
52
54
 
53
- ### From Within Ruby
55
+ If you'd like more control over the output formatting, and especially if you'd
56
+ like to provide custom logic for diffing your own classes, you might be better
57
+ served by the [super_diff](https://github.com/mcmire/super_diff) gem. Check it
58
+ out!
54
59
 
55
- Create two JSON strings and pass them to `CheckPlease.render_diff`. You'll get
56
- back a third string containing a nicely formatted report of all the differences
57
- CheckPlease found in the two JSON strings. (See also: [./usage_examples.rb](usage_examples.rb).)
60
+ ### From Ruby
58
61
 
59
- (You can also parse the JSON strings yourself and pass the resulting data
60
- structures in, if you're into that. I mean, I wrote this to help compare JSON
61
- data that's too big and complicated to scan through visually, but you do you!
62
+ Create two strings, each containing a JSON or YAML document, and pass them to
63
+ `CheckPlease.render_diff`. You'll get back a third string containing a report
64
+ of all the differences CheckPlease found in the two JSON strings. (See also:
65
+ [./usage_examples.rb](usage_examples.rb).)
62
66
 
63
67
  ### Understanding the Output
64
68
 
@@ -70,9 +74,7 @@ tool because you want a human-friendly summary of all the places that your
70
74
  **candidate** fell short.
71
75
 
72
76
  When CheckPlease compares your two samples, it generates a list of diffs to
73
- describe any discrepancies it encounters. (By default, it prints that list in a
74
- tabular format, but if you want to incorporate this into another toolchain,
75
- CheckPlease can also print these diffs as JSON to facilitate parsing.)
77
+ describe any discrepancies it encounters.
76
78
 
77
79
  An example would probably help here.
78
80
 
@@ -124,23 +126,22 @@ CheckPlease defines:
124
126
  * **type_mismatch** means that both the **reference** and the **candidate** had
125
127
  a value at the given path, but one value was an Array or a Hash and the other
126
128
  was not. **When CheckPlease encounters a type mismatch, it does not compare
127
- anything "below" the given path.** producing a lot of "garbage" diffs.
128
- _(Technical note: CheckPlease uses a "recursive descent" strategy to
129
- traverse the **reference** data structure, and it stops when it encounters a
130
- type mismatch in order to avoid producing a lot of "garbage" diff output.
131
- Also, the way these get displayed is likely to change.)_
129
+ anything "below" the given path.** _(Technical note: CheckPlease uses a
130
+ "recursive descent" strategy to traverse the **reference** data structure,
131
+ and it stops when it encounters a type mismatch in order to avoid producing a
132
+ lot of "garbage" diff output.)_
132
133
  * **mismatch** means that both the **reference** and the **candidate** had a
133
134
  value at the given path, and neither value was an Array or a Hash.
134
- * "**extra**" means that, inside an Array or a Hash, the **candidate**
135
- contained values that were not found in the **reference**.
136
- * "**missing**" is the opposite of **extra**: inside an Array or a Hash, the
135
+ * **extra** means that, inside an Array or a Hash, the **candidate** contained
136
+ values that were not found in the **reference**.
137
+ * **missing** is the opposite of **extra**: inside an Array or a Hash, the
137
138
  **reference** contained values that were not found in the **candidate**.
138
139
 
139
140
  #### Paths
140
141
 
141
- The second column contains a path expression. This is extremely basic:
142
+ The second column contains a path expression. This is extremely lo-fi:
142
143
 
143
- * The first element in the data structure is defined as "/".
144
+ * The root of the data structure is defined as "/".
144
145
  * If an element in the data structure is an array, its child elements will have
145
146
  a **one-based** index appended to their parent's path.
146
147
  * If an element in the data structure is an object ("Hash" in Ruby), the key
@@ -164,7 +165,6 @@ print diffs as JSON to facilitate parsing. In Ruby, pass `format: :json` to
164
165
 
165
166
  * command line flags for :allthethings:!
166
167
  * sort by path?
167
- * max depth (for iterative refinement?)
168
168
  * detect timestamps and compare after parsing?
169
169
  * ignore sub-second precision (option / CLI flag)?
170
170
  * possibly support plugins for other folks to add custom coercions?
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../lib/check_please'
3
+ require 'check_please'
4
4
  CheckPlease::CLI.run(__FILE__)
@@ -1,40 +1,128 @@
1
- require_relative "check_please/version"
2
- require_relative "check_please/error"
3
- require_relative "check_please/path"
4
- require_relative "check_please/comparison"
5
- require_relative "check_please/diff"
6
- require_relative "check_please/diffs"
7
- require_relative "check_please/printers"
8
- require_relative "check_please/cli"
1
+ require 'yaml'
2
+ require 'json'
3
+
4
+
5
+ # easier to just require these
6
+ require "check_please/error"
7
+ require "check_please/version"
8
+
9
+ module CheckPlease
10
+ autoload :CLI, "check_please/cli"
11
+ autoload :Comparison, "check_please/comparison"
12
+ autoload :Diff, "check_please/diff"
13
+ autoload :Diffs, "check_please/diffs"
14
+ autoload :Flag, "check_please/flag"
15
+ autoload :Flags, "check_please/flags"
16
+ autoload :Path, "check_please/path"
17
+ autoload :Printers, "check_please/printers"
18
+ autoload :Refinements, "check_please/refinements"
19
+ end
20
+
21
+
9
22
 
10
23
  module CheckPlease
11
24
  ELEVATOR_PITCH = "Tool for parsing and diffing two JSON documents."
12
25
 
13
- def self.diff(reference, candidate, options = {})
26
+ def self.diff(reference, candidate, flags = {})
14
27
  reference = maybe_parse(reference)
15
28
  candidate = maybe_parse(candidate)
16
- Comparison.perform(reference, candidate, options)
29
+ Comparison.perform(reference, candidate, flags)
17
30
  end
18
31
 
19
- def self.render_diff(reference, candidate, options = {})
20
- diffs = diff(reference, candidate, options)
21
- Printers.render(diffs, options)
32
+ def self.render_diff(reference, candidate, flags = {})
33
+ diffs = diff(reference, candidate, flags)
34
+ Printers.render(diffs, flags)
22
35
  end
23
36
 
24
37
  class << self
25
38
  private
26
39
 
27
40
  # Maybe you gave us JSON strings, maybe you gave us Ruby objects.
28
- # We just don't know! That's what makes it so exciting!
29
- def maybe_parse(maybe_json)
41
+ # Heck, maybe you even gave us some YAML! We just don't know!
42
+ # That's what makes it so exciting!
43
+ def maybe_parse(document)
30
44
 
31
- case maybe_json
32
- when String ; JSON.parse(maybe_json) # don't worry, if this raises we'll assume you've already parsed it
33
- else ; maybe_json
45
+ case document
46
+ when String ; return YAML.load(document) # don't worry, if this raises we'll assume you've already parsed it
47
+ else ; return document
34
48
  end
35
49
 
36
- rescue JSON::ParserError
37
- return maybe_json
50
+ rescue JSON::ParserError, Psych::SyntaxError
51
+ return document
38
52
  end
39
53
  end
54
+
55
+
56
+
57
+ Flags.define :format do |flag|
58
+ allowed_values = CheckPlease::Printers::FORMATS.sort
59
+
60
+ flag.coerce &:to_sym
61
+ flag.default = CheckPlease::Printers::DEFAULT_FORMAT
62
+ flag.validate { |flags, value| allowed_values.include?(value) }
63
+
64
+ flag.cli_long = "--format FORMAT"
65
+ flag.cli_short = "-f FORMAT"
66
+ flag.description = [
67
+ "Format in which to present diffs.",
68
+ " (Allowed values: [#{allowed_values.join(", ")}])",
69
+ ]
70
+ end
71
+
72
+ Flags.define :max_diffs do |flag|
73
+ flag.coerce &:to_i
74
+ flag.validate { |flags, value| value.to_i > 0 }
75
+
76
+ flag.cli_long = "--max-diffs MAX_DIFFS"
77
+ flag.cli_short = "-n MAX_DIFFS"
78
+ flag.description = "Stop after encountering a specified number of diffs."
79
+ end
80
+
81
+ Flags.define :fail_fast do |flag|
82
+ flag.default = false
83
+ flag.coerce { |value| !!value }
84
+
85
+ flag.cli_long = "--fail-fast"
86
+ flag.description = [
87
+ "Stop after encountering the first diff.",
88
+ " (equivalent to '--max-diffs 1')",
89
+ ]
90
+ end
91
+
92
+ Flags.define :max_depth do |flag|
93
+ flag.coerce &:to_i
94
+ flag.validate { |flags, value| value.to_i > 0 }
95
+
96
+ flag.cli_long = "--max_depth MAX_DEPTH"
97
+ flag.cli_short = "-d MAX_DEPTH"
98
+ flag.description = [
99
+ "Limit the number of levels to descend when comparing documents.",
100
+ " (NOTE: root has depth = 1)",
101
+ ]
102
+ end
103
+
104
+ Flags.define :select_paths do |flag|
105
+ flag.reentrant
106
+ flag.mutually_exclusive_to :reject_paths
107
+
108
+ flag.cli_long = "--select-paths PATH_EXPR"
109
+ flag.description = [
110
+ "ONLY record diffs matching the provided PATH expression.",
111
+ " May be repeated; values will be treated as an 'OR' list.",
112
+ " Can't be combined with --reject-paths.",
113
+ ]
114
+ end
115
+
116
+ Flags.define :reject_paths do |flag|
117
+ flag.reentrant
118
+ flag.mutually_exclusive_to :select_paths
119
+
120
+ flag.cli_long = "--reject-paths PATH_EXPR"
121
+ flag.description = [
122
+ "DON'T record diffs matching the provided PATH expression.",
123
+ " May be repeated; values will be treated as an 'OR' list.",
124
+ " Can't be combined with --select-paths.",
125
+ ]
126
+ end
127
+
40
128
  end
@@ -1,45 +1,12 @@
1
- require_relative 'cli/flag'
2
- # require_relative 'cli/flags'
3
- require_relative 'cli/parser'
4
- require_relative 'cli/runner'
5
-
6
1
  module CheckPlease
7
2
 
8
3
  module CLI
4
+ autoload :Runner, "check_please/cli/parser"
5
+ autoload :Parser, "check_please/cli/runner"
6
+
9
7
  def self.run(exe_file_name)
10
8
  Runner.new(__FILE__).run(*ARGV.dup)
11
9
  end
12
-
13
-
14
-
15
- FLAGS = []
16
- def self.flag(long:, short: nil, &block)
17
- flag = Flag.new(short, long, &block)
18
- FLAGS << flag
19
- end
20
-
21
- ##### Define CLI flags here #####
22
-
23
- flag short: "-f FORMAT", long: "--format FORMAT" do |f|
24
- f.desc = "format in which to present diffs (available options: [#{CheckPlease::Printers::FORMATS.join(", ")}])"
25
- f.set_key :format, :to_sym
26
- end
27
-
28
- flag short: "-n MAX_DIFFS", long: "--max-diffs MAX_DIFFS" do |f|
29
- f.desc = "Stop after encountering a specified number of diffs"
30
- f.set_key :max_diffs, :to_i
31
- end
32
-
33
- flag long: "--fail-fast" do |f|
34
- f.desc = "Stop after encountering the very first diff"
35
- f.set_key(:max_diffs) { 1 }
36
- end
37
-
38
- flag short: "-d MAX_DEPTH", long: "--max-depth MAX_DEPTH" do |f|
39
- f.desc = "Limit the number of levels to descend when comparing documents (NOTE: root has depth=1)"
40
- f.set_key :max_depth, :to_i
41
- end
42
-
43
10
  end
44
11
 
45
12
  end
@@ -4,36 +4,41 @@ module CheckPlease
4
4
  module CLI
5
5
 
6
6
  class Parser
7
- class UnrecognizedOption < StandardError
8
- include CheckPlease::Error
9
- end
10
-
11
7
  def initialize(exe_file_name)
12
8
  @exe_file_name = exe_file_name
13
- @optparse = OptionParser.new
14
- @optparse.banner = banner
15
-
16
- @options = {} # yuck
17
- CheckPlease::CLI::FLAGS.each do |flag|
18
- flag.visit_option_parser(@optparse, @options)
19
- end
20
9
  end
21
10
 
22
- # Unfortunately, OptionParser *really* wants to use closures.
23
- # I haven't yet figured out how to get around this...
24
- def consume_flags!(args)
25
- @optparse.parse!(args) # removes recognized flags from `args`
26
- return @options
11
+ # Unfortunately, OptionParser *really* wants to use closures. I haven't
12
+ # yet figured out how to get around this, but at least it's closing on a
13
+ # local instead of an ivar... progress?
14
+ def flags_from_args!(args)
15
+ flags = Flags.new
16
+ optparse = option_parser(flags: flags)
17
+ optparse.parse!(args) # removes recognized flags from `args`
18
+ return flags
27
19
  rescue OptionParser::InvalidOption, OptionParser::AmbiguousOption => e
28
- raise UnrecognizedOption, e.message, cause: e
20
+ raise InvalidFlag, e.message, cause: e
29
21
  end
30
22
 
31
23
  def help
32
- @optparse.help
24
+ option_parser.help
33
25
  end
34
26
 
35
27
  private
36
28
 
29
+ # NOTE: if flags is nil, you'll get something that can print help, but will explode when sent :parse
30
+ def option_parser(flags: nil)
31
+ OptionParser.new.tap do |optparse|
32
+ optparse.banner = banner
33
+ CheckPlease::Flags.each_flag do |flag|
34
+ args = [ flag.cli_short, flag.cli_long, flag.description ].flatten.compact
35
+ optparse.on(*args) do |value|
36
+ flags.send "#{flag.name}=", value
37
+ end
38
+ end
39
+ end
40
+ end
41
+
37
42
  def banner
38
43
  <<~EOF
39
44
  Usage: #{@exe_file_name} <reference> <candidate> [FLAGS]
@@ -14,8 +14,8 @@ module CLI
14
14
  print_help_and_exit if args.empty?
15
15
 
16
16
  begin
17
- options = @parser.consume_flags!(args)
18
- rescue Parser::UnrecognizedOption => e
17
+ flags = @parser.flags_from_args!(args)
18
+ rescue InvalidFlag => e
19
19
  print_help_and_exit e.message
20
20
  end
21
21
 
@@ -31,7 +31,7 @@ module CLI
31
31
  or print_help_and_exit "Missing <candidate> argument, AND nothing was piped in"
32
32
 
33
33
  # Looks like we're good to go!
34
- diff_view = CheckPlease.render_diff(reference, candidate, options)
34
+ diff_view = CheckPlease.render_diff(reference, candidate, flags)
35
35
  puts diff_view
36
36
  end
37
37
 
@@ -3,9 +3,9 @@ module CheckPlease
3
3
  module Comparison
4
4
  extend self
5
5
 
6
- def perform(reference, candidate, options = {})
6
+ def perform(reference, candidate, flags = {})
7
7
  root = CheckPlease::Path.new
8
- diffs = Diffs.new(options: options)
8
+ diffs = Diffs.new(flags: flags)
9
9
  catch(:max_diffs_reached) do
10
10
  compare reference, candidate, root, diffs
11
11
  end
@@ -15,8 +15,8 @@ module CheckPlease
15
15
  private
16
16
 
17
17
  def compare(ref, can, path, diffs)
18
- if (d = diffs.options[:max_depth])
19
- return if path.depth > d
18
+ if (d = diffs.flags.max_depth)
19
+ return if path.depth > d + 1
20
20
  end
21
21
 
22
22
  case types(ref, can)
@@ -50,7 +50,8 @@ module CheckPlease
50
50
  case
51
51
  when ref_array.length < n ; diffs.record ref, can, new_path, :extra
52
52
  when can_array.length < n ; diffs.record ref, can, new_path, :missing
53
- else ; compare ref, can, new_path, diffs
53
+ else
54
+ compare ref, can, new_path, diffs
54
55
  end
55
56
  end
56
57
  end
@@ -3,20 +3,12 @@ module CheckPlease
3
3
  class Diff
4
4
  COLUMNS = %i[ type path reference candidate ]
5
5
 
6
- attr_reader :type, :reference, :candidate, :path
7
- def initialize(type, reference, candidate, path)
6
+ attr_reader(*COLUMNS)
7
+ def initialize(type, path, reference, candidate)
8
8
  @type = type
9
+ @path = path.to_s
9
10
  @reference = reference
10
11
  @candidate = candidate
11
- @path = path.to_s
12
- end
13
-
14
- def ref_display
15
- reference.inspect
16
- end
17
-
18
- def can_display
19
- candidate.inspect
20
12
  end
21
13
 
22
14
  def attributes
@@ -28,8 +20,8 @@ module CheckPlease
28
20
  s << self.class.name
29
21
  s << " type=#{type}"
30
22
  s << " path=#{path}"
31
- s << " ref=#{ref_display}"
32
- s << " can=#{can_display}"
23
+ s << " ref=#{reference.inspect}"
24
+ s << " can=#{candidate.inspect}"
33
25
  s << ">"
34
26
  s
35
27
  end
@@ -1,13 +1,14 @@
1
1
  require 'forwardable'
2
2
 
3
3
  module CheckPlease
4
+ using Refinements
4
5
 
5
6
  # Custom collection class for Diff instances.
6
7
  # Can retrieve members using indexes or paths.
7
8
  class Diffs
8
- attr_reader :options
9
- def initialize(diff_list = nil, options: {})
10
- @options = options
9
+ attr_reader :flags
10
+ def initialize(diff_list = nil, flags: {})
11
+ @flags = Flags(flags)
11
12
  @list = []
12
13
  @hash = {}
13
14
  Array(diff_list).each do |diff|
@@ -29,7 +30,11 @@ module CheckPlease
29
30
  end
30
31
 
31
32
  def <<(diff)
32
- if (n = options[:max_diffs])
33
+ if flags.fail_fast && length > 0
34
+ throw :max_diffs_reached
35
+ end
36
+
37
+ if (n = flags.max_diffs)
33
38
  # It seems no one can help me now / I'm in too deep, there's no way out
34
39
  throw :max_diffs_reached if length >= n
35
40
  end
@@ -39,7 +44,8 @@ module CheckPlease
39
44
  end
40
45
 
41
46
  def record(ref, can, path, type)
42
- self << Diff.new(type, ref, can, path)
47
+ return if path.excluded?(flags)
48
+ self << Diff.new(type, path, ref, can)
43
49
  end
44
50
 
45
51
  def data
@@ -6,4 +6,8 @@ module CheckPlease
6
6
  # instead....
7
7
  end
8
8
 
9
+ class InvalidFlag < ArgumentError
10
+ include CheckPlease::Error
11
+ end
12
+
9
13
  end
@@ -0,0 +1,78 @@
1
+ module CheckPlease
2
+
3
+ class Flag
4
+ attr_accessor :name
5
+ attr_writer :default # reader is defined below
6
+ attr_accessor :default_proc
7
+ attr_accessor :description
8
+ attr_accessor :cli_long
9
+ attr_accessor :cli_short
10
+
11
+ def initialize(attrs = {})
12
+ @validators = []
13
+ attrs.each do |name, value|
14
+ set_attribute! name, value
15
+ end
16
+ yield self if block_given?
17
+ freeze
18
+ end
19
+
20
+ def default
21
+ if default_proc
22
+ default_proc.call
23
+ else
24
+ @default
25
+ end
26
+ end
27
+
28
+ def coerce(&block)
29
+ @coercer = block
30
+ end
31
+
32
+ def mutually_exclusive_to(flag_name)
33
+ @validators << ->(flags, _) { flags.send(flag_name).empty? }
34
+ end
35
+
36
+ def reentrant
37
+ @reentrant = true
38
+ self.default_proc = ->{ Array.new }
39
+ end
40
+
41
+ def validate(&block)
42
+ @validators << block
43
+ end
44
+
45
+ protected
46
+
47
+ def __set__(value, on:, flags:)
48
+ val = _coerce(value)
49
+ _validate(flags, val)
50
+ if @reentrant
51
+ on[name] ||= []
52
+ on[name].concat(Array(val))
53
+ else
54
+ on[name] = val
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def _coerce(value)
61
+ return value if @coercer.nil?
62
+ @coercer.call(value)
63
+ end
64
+
65
+ def _validate(flags, value)
66
+ return if @validators.empty?
67
+ return if @validators.all? { |block| block.call(flags, value) }
68
+ raise InvalidFlag, "#{value.inspect} is not a legal value for #{name}"
69
+ end
70
+
71
+ def set_attribute!(name, value)
72
+ self.send "#{name}=", value
73
+ rescue NoMethodError
74
+ raise ArgumentError, "unrecognized attribute: #{name}"
75
+ end
76
+ end
77
+
78
+ end
@@ -0,0 +1,46 @@
1
+ module CheckPlease
2
+
3
+ # NOTE: this gets all of its attributes defined (via .define) in ../check_please.rb
4
+
5
+ class Flags
6
+ BY_NAME = {} ; private_constant :BY_NAME
7
+
8
+ def self.[](name)
9
+ BY_NAME[name.to_sym]
10
+ end
11
+
12
+ def self.define(name, &block)
13
+ flag = Flag.new(name: name.to_sym, &block)
14
+ BY_NAME[flag.name] = flag
15
+ define_accessors flag
16
+
17
+ nil
18
+ end
19
+
20
+ def self.each_flag
21
+ BY_NAME.each do |_, flag|
22
+ yield flag
23
+ end
24
+ end
25
+
26
+ def self.define_accessors(flag)
27
+ getter = flag.name
28
+ define_method(getter) {
29
+ @attributes.fetch(flag.name) { flag.default }
30
+ }
31
+
32
+ setter = :"#{flag.name}="
33
+ define_method(setter) { |value|
34
+ flag.send :__set__, value, on: @attributes, flags: self
35
+ }
36
+ end
37
+
38
+ def initialize(attrs = {})
39
+ @attributes = {}
40
+ attrs.each do |name, value|
41
+ send "#{name}=", value
42
+ end
43
+ end
44
+ end
45
+
46
+ end
@@ -15,6 +15,17 @@ module CheckPlease
15
15
  1 + @segments.length
16
16
  end
17
17
 
18
+ def excluded?(flags)
19
+ s = to_s ; matches = ->(path_expr) { s.include?(path_expr) }
20
+ if flags.select_paths.length > 0
21
+ return flags.select_paths.none?(&matches)
22
+ end
23
+ if flags.reject_paths.length > 0
24
+ return flags.reject_paths.any?(&matches)
25
+ end
26
+ false
27
+ end
28
+
18
29
  def to_s
19
30
  SEPARATOR + @segments.join(SEPARATOR)
20
31
  end
@@ -1,10 +1,11 @@
1
- require_relative 'printers/base'
2
- require_relative 'printers/json'
3
- require_relative 'printers/table_print'
4
-
5
1
  module CheckPlease
2
+ using Refinements
6
3
 
7
4
  module Printers
5
+ autoload :Base, "check_please/printers/base"
6
+ autoload :JSON, "check_please/printers/json"
7
+ autoload :TablePrint, "check_please/printers/table_print"
8
+
8
9
  PRINTERS_BY_FORMAT = {
9
10
  table: Printers::TablePrint,
10
11
  json: Printers::JSON,
@@ -12,9 +13,9 @@ module CheckPlease
12
13
  FORMATS = PRINTERS_BY_FORMAT.keys.sort
13
14
  DEFAULT_FORMAT = :table
14
15
 
15
- def self.render(diffs, options = {})
16
- format = options[:format] || DEFAULT_FORMAT
17
- printer = PRINTERS_BY_FORMAT[format.to_sym]
16
+ def self.render(diffs, flags = {})
17
+ flags = Flags(flags)
18
+ printer = PRINTERS_BY_FORMAT[flags.format]
18
19
  printer.render(diffs)
19
20
  end
20
21
  end
@@ -1,5 +1,3 @@
1
- require 'json'
2
-
3
1
  module CheckPlease
4
2
  module Printers
5
3
 
@@ -4,21 +4,30 @@ module CheckPlease
4
4
  module Printers
5
5
 
6
6
  class TablePrint < Base
7
+ InspectStrings = Object.new.tap do |obj|
8
+ def obj.format(value)
9
+ value.is_a?(String) ? value.inspect : value
10
+ end
11
+ end
12
+
13
+ PATH_MAX_WIDTH = 250 # if you hit this limit, you have other problems
14
+
7
15
  TP_OPTS = [
8
- :type,
9
- { :path => { width: 250 } }, # if you hit this limit, you have other problems
10
- :reference,
11
- :candidate,
16
+ { type: { display_name: "Type" } },
17
+ { path: { display_name: "Path", width: PATH_MAX_WIDTH } },
18
+ { reference: { display_name: "Reference", formatters: [ InspectStrings ] } },
19
+ { candidate: { display_name: "Candidate", formatters: [ InspectStrings ] } },
12
20
  ]
13
21
 
14
22
  def to_s
15
23
  return "" if diffs.empty?
16
24
 
17
- build_string do |io|
25
+ out = build_string do |io|
18
26
  switch_tableprint_io(io) do
19
27
  tp diffs.data, *TP_OPTS
20
28
  end
21
29
  end
30
+ strip_trailing_whitespace(out)
22
31
  end
23
32
 
24
33
  private
@@ -31,6 +40,10 @@ module Printers
31
40
  ensure
32
41
  config.io = @old_io
33
42
  end
43
+
44
+ def strip_trailing_whitespace(s)
45
+ s.lines.map(&:rstrip).join("\n")
46
+ end
34
47
  end
35
48
 
36
49
  end
@@ -0,0 +1,16 @@
1
+ module CheckPlease
2
+
3
+ module Refinements
4
+ refine Kernel do
5
+ def Flags(flags_or_hash)
6
+ case flags_or_hash
7
+ when Flags ; return flags_or_hash
8
+ when Hash ; return Flags.new(flags_or_hash)
9
+ else
10
+ raise ArgumentError, "Expected either a CheckPlease::Flags or a Hash; got #{flags_or_hash.inspect}"
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ end
@@ -1,5 +1,5 @@
1
1
  module CheckPlease
2
2
  # NOTE: 'check_please_rspec_matcher' depends on this,
3
3
  # so try to keep them roughly in sync
4
- VERSION = "0.2.1"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: check_please
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Livingston-Gray
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-14 00:00:00.000000000 Z
11
+ date: 2020-11-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: table_print
@@ -56,7 +56,8 @@ description: Check for differences between two JSON strings (or Ruby data struct
56
56
  parsed from them)
57
57
  email:
58
58
  - geeksam@gmail.com
59
- executables: []
59
+ executables:
60
+ - check_please
60
61
  extensions: []
61
62
  extra_rdoc_files: []
62
63
  files:
@@ -69,24 +70,26 @@ files:
69
70
  - LICENSE.txt
70
71
  - README.md
71
72
  - Rakefile
72
- - bin/check_please
73
73
  - bin/console
74
74
  - bin/setup
75
75
  - check_please.gemspec
76
+ - exe/check_please
76
77
  - lib/check_please.rb
77
78
  - lib/check_please/cli.rb
78
- - lib/check_please/cli/flag.rb
79
79
  - lib/check_please/cli/parser.rb
80
80
  - lib/check_please/cli/runner.rb
81
81
  - lib/check_please/comparison.rb
82
82
  - lib/check_please/diff.rb
83
83
  - lib/check_please/diffs.rb
84
84
  - lib/check_please/error.rb
85
+ - lib/check_please/flag.rb
86
+ - lib/check_please/flags.rb
85
87
  - lib/check_please/path.rb
86
88
  - lib/check_please/printers.rb
87
89
  - lib/check_please/printers/base.rb
88
90
  - lib/check_please/printers/json.rb
89
91
  - lib/check_please/printers/table_print.rb
92
+ - lib/check_please/refinements.rb
90
93
  - lib/check_please/version.rb
91
94
  - usage_examples.rb
92
95
  homepage: https://github.com/RealGeeks/check_please
@@ -1,40 +0,0 @@
1
- module CheckPlease
2
- module CLI
3
-
4
- class Flag
5
- ATTR_NAMES = %i[ short long desc key block ]
6
- attr_accessor(*ATTR_NAMES)
7
-
8
- def initialize(*args)
9
- self.short = args.shift if args.any?
10
- self.long = args.shift if args.any?
11
-
12
- yield self if block_given?
13
-
14
- missing = ATTR_NAMES.select { |e| self.send(e).nil? }
15
- missing -= %i[ short ] # short is optional!
16
- if missing.any?
17
- raise ArgumentError, "Missing attributes: #{missing.join(', ')}"
18
- end
19
- end
20
-
21
- def visit_option_parser(parser, options)
22
- parser.on(short, long, desc) do |value|
23
- block.call options, value
24
- end
25
- end
26
-
27
- def set_key(key, message = nil, &b)
28
- raise ArgumentError if message && b
29
- raise ArgumentError if !message && !b
30
-
31
- self.key = key
32
- self.block = ->(options, value) {
33
- b ||= message.to_sym.to_proc
34
- options[key] = b.call(value)
35
- }
36
- end
37
- end
38
-
39
- end
40
- end