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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +28 -28
- data/{bin → exe}/check_please +1 -1
- data/lib/check_please.rb +108 -20
- data/lib/check_please/cli.rb +3 -36
- data/lib/check_please/cli/parser.rb +23 -18
- data/lib/check_please/cli/runner.rb +3 -3
- data/lib/check_please/comparison.rb +6 -5
- data/lib/check_please/diff.rb +5 -13
- data/lib/check_please/diffs.rb +11 -5
- data/lib/check_please/error.rb +4 -0
- data/lib/check_please/flag.rb +78 -0
- data/lib/check_please/flags.rb +46 -0
- data/lib/check_please/path.rb +11 -0
- data/lib/check_please/printers.rb +8 -7
- data/lib/check_please/printers/json.rb +0 -2
- data/lib/check_please/printers/table_print.rb +18 -5
- data/lib/check_please/refinements.rb +16 -0
- data/lib/check_please/version.rb +1 -1
- metadata +8 -5
- data/lib/check_please/cli/flag.rb +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4e0e81da97eb41aade7d58c516519bde3d2527173b64e7d644e46458493450d
|
4
|
+
data.tar.gz: c633c13e0a776bdbf1691b9c01d3be3cc978cb445c2fbf8d74e95449ea0b3118
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad31ad99aef3494a2d2438b422a5fef9385c6fd05572131907d33d9e1e30a94ba0582ca4aae00c997dbe302487719da3d5ddbf715cf23422405086084f7a982c
|
7
|
+
data.tar.gz: 04d8e89b72894e458394ff0428192ffbe6f3b307437ce6ee8496f6e3064236498286b2fb203a399c5245172cec391656646dac39b20f0adc8a345eff13b8f015
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# check_please
|
2
2
|
|
3
|
-
Check for differences between two JSON
|
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
|
-
##
|
22
|
-
|
23
|
-
### Terminology
|
22
|
+
## Terminology
|
24
23
|
|
25
|
-
|
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
|
-
|
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
|
51
|
+
### From RSpec
|
50
52
|
|
51
53
|
See [check_please_rspec_matcher](https://github.com/RealGeeks/check_please_rspec_matcher).
|
52
54
|
|
53
|
-
|
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
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
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.
|
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.**
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
-
*
|
135
|
-
|
136
|
-
*
|
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
|
142
|
+
The second column contains a path expression. This is extremely lo-fi:
|
142
143
|
|
143
|
-
* The
|
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?
|
data/{bin → exe}/check_please
RENAMED
data/lib/check_please.rb
CHANGED
@@ -1,40 +1,128 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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,
|
26
|
+
def self.diff(reference, candidate, flags = {})
|
14
27
|
reference = maybe_parse(reference)
|
15
28
|
candidate = maybe_parse(candidate)
|
16
|
-
Comparison.perform(reference, candidate,
|
29
|
+
Comparison.perform(reference, candidate, flags)
|
17
30
|
end
|
18
31
|
|
19
|
-
def self.render_diff(reference, candidate,
|
20
|
-
diffs = diff(reference, candidate,
|
21
|
-
Printers.render(diffs,
|
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!
|
29
|
-
|
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
|
32
|
-
when String ;
|
33
|
-
else ;
|
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
|
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
|
data/lib/check_please/cli.rb
CHANGED
@@ -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
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
20
|
+
raise InvalidFlag, e.message, cause: e
|
29
21
|
end
|
30
22
|
|
31
23
|
def help
|
32
|
-
|
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
|
-
|
18
|
-
rescue
|
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,
|
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,
|
6
|
+
def perform(reference, candidate, flags = {})
|
7
7
|
root = CheckPlease::Path.new
|
8
|
-
diffs = Diffs.new(
|
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.
|
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
|
53
|
+
else
|
54
|
+
compare ref, can, new_path, diffs
|
54
55
|
end
|
55
56
|
end
|
56
57
|
end
|
data/lib/check_please/diff.rb
CHANGED
@@ -3,20 +3,12 @@ module CheckPlease
|
|
3
3
|
class Diff
|
4
4
|
COLUMNS = %i[ type path reference candidate ]
|
5
5
|
|
6
|
-
attr_reader
|
7
|
-
def initialize(type, reference, candidate
|
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=#{
|
32
|
-
s << " can=#{
|
23
|
+
s << " ref=#{reference.inspect}"
|
24
|
+
s << " can=#{candidate.inspect}"
|
33
25
|
s << ">"
|
34
26
|
s
|
35
27
|
end
|
data/lib/check_please/diffs.rb
CHANGED
@@ -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 :
|
9
|
-
def initialize(diff_list = nil,
|
10
|
-
@
|
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
|
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
|
-
|
47
|
+
return if path.excluded?(flags)
|
48
|
+
self << Diff.new(type, path, ref, can)
|
43
49
|
end
|
44
50
|
|
45
51
|
def data
|
data/lib/check_please/error.rb
CHANGED
@@ -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
|
data/lib/check_please/path.rb
CHANGED
@@ -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,
|
16
|
-
|
17
|
-
printer = PRINTERS_BY_FORMAT[format
|
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
|
@@ -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
|
-
:
|
9
|
-
{ :
|
10
|
-
:
|
11
|
-
:
|
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
|
data/lib/check_please/version.rb
CHANGED
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.
|
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-
|
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
|