check_please 0.3.0 → 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: 889dea37051513ced2de5a716db086a1ad1793af6e095704be6cc39a0fc64822
4
- data.tar.gz: 2a21aeeb5a686b46a4657dd2d50163b07e0e6e2f1effb8fdbe652bbc835f5c02
3
+ metadata.gz: c4e0e81da97eb41aade7d58c516519bde3d2527173b64e7d644e46458493450d
4
+ data.tar.gz: c633c13e0a776bdbf1691b9c01d3be3cc978cb445c2fbf8d74e95449ea0b3118
5
5
  SHA512:
6
- metadata.gz: 72b705dc899a841fadce3a9d67c538012d83d7e9fce7e69d8b7da4d6795eb84ac6eb4c8feedc8f721b07c0894c4a87079584081350ade4a3a184aa7ba7fad0d2
7
- data.tar.gz: c13e453a1032c1ad9b57e7e20c272851044dce1c80ca63bde5b173e05f547b2f43c39c3910cd1decebbe4da28eb2e71ba2fbeb4c23f145c40176bf3391292431
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.3.0)
4
+ check_please (0.4.0)
5
5
  table_print
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -52,6 +52,11 @@ like MacOS's `pbpaste` utility.)
52
52
 
53
53
  See [check_please_rspec_matcher](https://github.com/RealGeeks/check_please_rspec_matcher).
54
54
 
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!
59
+
55
60
  ### From Ruby
56
61
 
57
62
  Create two strings, each containing a JSON or YAML document, and pass them to
@@ -1,27 +1,37 @@
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"
9
-
10
1
  require 'yaml'
11
2
  require 'json'
12
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
+
22
+
13
23
  module CheckPlease
14
24
  ELEVATOR_PITCH = "Tool for parsing and diffing two JSON documents."
15
25
 
16
- def self.diff(reference, candidate, options = {})
26
+ def self.diff(reference, candidate, flags = {})
17
27
  reference = maybe_parse(reference)
18
28
  candidate = maybe_parse(candidate)
19
- Comparison.perform(reference, candidate, options)
29
+ Comparison.perform(reference, candidate, flags)
20
30
  end
21
31
 
22
- def self.render_diff(reference, candidate, options = {})
23
- diffs = diff(reference, candidate, options)
24
- Printers.render(diffs, options)
32
+ def self.render_diff(reference, candidate, flags = {})
33
+ diffs = diff(reference, candidate, flags)
34
+ Printers.render(diffs, flags)
25
35
  end
26
36
 
27
37
  class << self
@@ -41,4 +51,78 @@ module CheckPlease
41
51
  return document
42
52
  end
43
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
+
44
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,7 +15,7 @@ module CheckPlease
15
15
  private
16
16
 
17
17
  def compare(ref, can, path, diffs)
18
- if (d = diffs.options[:max_depth])
18
+ if (d = diffs.flags.max_depth)
19
19
  return if path.depth > d + 1
20
20
  end
21
21
 
@@ -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,12 +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
12
  end
13
13
 
14
14
  def attributes
@@ -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
@@ -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.3.0"
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.3.0
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-19 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
@@ -76,18 +76,20 @@ files:
76
76
  - exe/check_please
77
77
  - lib/check_please.rb
78
78
  - lib/check_please/cli.rb
79
- - lib/check_please/cli/flag.rb
80
79
  - lib/check_please/cli/parser.rb
81
80
  - lib/check_please/cli/runner.rb
82
81
  - lib/check_please/comparison.rb
83
82
  - lib/check_please/diff.rb
84
83
  - lib/check_please/diffs.rb
85
84
  - lib/check_please/error.rb
85
+ - lib/check_please/flag.rb
86
+ - lib/check_please/flags.rb
86
87
  - lib/check_please/path.rb
87
88
  - lib/check_please/printers.rb
88
89
  - lib/check_please/printers/base.rb
89
90
  - lib/check_please/printers/json.rb
90
91
  - lib/check_please/printers/table_print.rb
92
+ - lib/check_please/refinements.rb
91
93
  - lib/check_please/version.rb
92
94
  - usage_examples.rb
93
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