check_please 0.2.1 → 0.4.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.
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