check_please 0.3.0 → 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: 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