check_please 0.2.0 → 0.3.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: 1c16110449be99d5ff509c720c4365b2fe6f176f23094784628db2de0e5f3bf9
4
- data.tar.gz: b45c9d1b7dea9b4405f4ca80dbce5f04d0aec14ad50e4e919802e09b469c86c8
3
+ metadata.gz: 889dea37051513ced2de5a716db086a1ad1793af6e095704be6cc39a0fc64822
4
+ data.tar.gz: 2a21aeeb5a686b46a4657dd2d50163b07e0e6e2f1effb8fdbe652bbc835f5c02
5
5
  SHA512:
6
- metadata.gz: 023b9fdd6f227f8381dfba06a82d30961adab9b2ab5a2b5a0d820c8b8697d300f59d416c801808e88e7e625720776f1cec4cf0a6f57d1eec07e6cb656881d4b8
7
- data.tar.gz: eca955971798d00dd45bf5e068779aebeb33b304fe592d84534081d0068eaa981f6b765fd5c4ea9a30e7d65b2edd456ed9093c039964e3691c646e0c7d23b2b0
6
+ metadata.gz: 72b705dc899a841fadce3a9d67c538012d83d7e9fce7e69d8b7da4d6795eb84ac6eb4c8feedc8f721b07c0894c4a87079584081350ade4a3a184aa7ba7fad0d2
7
+ data.tar.gz: c13e453a1032c1ad9b57e7e20c272851044dce1c80ca63bde5b173e05f547b2f43c39c3910cd1decebbe4da28eb2e71ba2fbeb4c23f145c40176bf3391292431
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- check_please (0.2.0)
4
+ check_please (0.3.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,16 @@ 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
54
-
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).)
55
+ ### From Ruby
58
56
 
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!
57
+ Create two strings, each containing a JSON or YAML document, and pass them to
58
+ `CheckPlease.render_diff`. You'll get back a third string containing a report
59
+ of all the differences CheckPlease found in the two JSON strings. (See also:
60
+ [./usage_examples.rb](usage_examples.rb).)
62
61
 
63
62
  ### Understanding the Output
64
63
 
@@ -70,9 +69,7 @@ tool because you want a human-friendly summary of all the places that your
70
69
  **candidate** fell short.
71
70
 
72
71
  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.)
72
+ describe any discrepancies it encounters.
76
73
 
77
74
  An example would probably help here.
78
75
 
@@ -124,23 +121,22 @@ CheckPlease defines:
124
121
  * **type_mismatch** means that both the **reference** and the **candidate** had
125
122
  a value at the given path, but one value was an Array or a Hash and the other
126
123
  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.)_
124
+ anything "below" the given path.** _(Technical note: CheckPlease uses a
125
+ "recursive descent" strategy to traverse the **reference** data structure,
126
+ and it stops when it encounters a type mismatch in order to avoid producing a
127
+ lot of "garbage" diff output.)_
132
128
  * **mismatch** means that both the **reference** and the **candidate** had a
133
129
  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
130
+ * **extra** means that, inside an Array or a Hash, the **candidate** contained
131
+ values that were not found in the **reference**.
132
+ * **missing** is the opposite of **extra**: inside an Array or a Hash, the
137
133
  **reference** contained values that were not found in the **candidate**.
138
134
 
139
135
  #### Paths
140
136
 
141
- The second column contains a path expression. This is extremely basic:
137
+ The second column contains a path expression. This is extremely lo-fi:
142
138
 
143
- * The first element in the data structure is defined as "/".
139
+ * The root of the data structure is defined as "/".
144
140
  * If an element in the data structure is an array, its child elements will have
145
141
  a **one-based** index appended to their parent's path.
146
142
  * If an element in the data structure is an object ("Hash" in Ruby), the key
@@ -163,10 +159,7 @@ print diffs as JSON to facilitate parsing. In Ruby, pass `format: :json` to
163
159
  ## TODO
164
160
 
165
161
  * command line flags for :allthethings:!
166
- * --fail-fast
167
- * limit to first N
168
162
  * sort by path?
169
- * max depth (for iterative refinement?)
170
163
  * detect timestamps and compare after parsing?
171
164
  * ignore sub-second precision (option / CLI flag)?
172
165
  * possibly support plugins for other folks to add custom coercions?
data/Rakefile CHANGED
@@ -1,6 +1,21 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
- RSpec::Core::RakeTask.new(:spec)
4
+ namespace :spec do
5
+ RSpec::Core::RakeTask.new(:all)
6
+
7
+ RSpec::Core::RakeTask.new(:not_cli) do |t|
8
+ t.rspec_opts = "--tag ~cli"
9
+ end
10
+ task fast: :not_cli
11
+
12
+ # These are much slower than the rest, since they use Kernel#`
13
+ RSpec::Core::RakeTask.new(:cli) do |t|
14
+ t.rspec_opts = "--tag cli"
15
+ end
16
+ end
17
+
18
+ # By default, `rake spec` should run fast specs first, then cli if those all pass
19
+ task :spec => [ "spec:not_cli", "spec:cli" ]
5
20
 
6
21
  task :default => :spec
@@ -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__)
@@ -7,17 +7,20 @@ require_relative "check_please/diffs"
7
7
  require_relative "check_please/printers"
8
8
  require_relative "check_please/cli"
9
9
 
10
+ require 'yaml'
11
+ require 'json'
12
+
10
13
  module CheckPlease
11
14
  ELEVATOR_PITCH = "Tool for parsing and diffing two JSON documents."
12
15
 
13
- def self.diff(reference, candidate)
16
+ def self.diff(reference, candidate, options = {})
14
17
  reference = maybe_parse(reference)
15
18
  candidate = maybe_parse(candidate)
16
- Comparison.perform(reference, candidate)
19
+ Comparison.perform(reference, candidate, options)
17
20
  end
18
21
 
19
22
  def self.render_diff(reference, candidate, options = {})
20
- diffs = diff(reference, candidate)
23
+ diffs = diff(reference, candidate, options)
21
24
  Printers.render(diffs, options)
22
25
  end
23
26
 
@@ -25,16 +28,17 @@ module CheckPlease
25
28
  private
26
29
 
27
30
  # 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)
31
+ # Heck, maybe you even gave us some YAML! We just don't know!
32
+ # That's what makes it so exciting!
33
+ def maybe_parse(document)
30
34
 
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
35
+ case document
36
+ when String ; return YAML.load(document) # don't worry, if this raises we'll assume you've already parsed it
37
+ else ; return document
34
38
  end
35
39
 
36
- rescue JSON::ParserError
37
- return maybe_json
40
+ rescue JSON::ParserError, Psych::SyntaxError
41
+ return document
38
42
  end
39
43
  end
40
44
  end
@@ -13,17 +13,33 @@ module CheckPlease
13
13
 
14
14
 
15
15
  FLAGS = []
16
- def self.flag(*args, &block)
17
- flag = Flag.new(*args, &block)
16
+ def self.flag(long:, short: nil, &block)
17
+ flag = Flag.new(short, long, &block)
18
18
  FLAGS << flag
19
19
  end
20
20
 
21
21
  ##### Define CLI flags here #####
22
22
 
23
- flag "-f FORMAT", "--format FORMAT" do |f|
23
+ flag short: "-f FORMAT", long: "--format FORMAT" do |f|
24
24
  f.desc = "format in which to present diffs (available options: [#{CheckPlease::Printers::FORMATS.join(", ")}])"
25
25
  f.set_key :format, :to_sym
26
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
+
27
43
  end
28
44
 
29
45
  end
@@ -12,6 +12,7 @@ module CLI
12
12
  yield self if block_given?
13
13
 
14
14
  missing = ATTR_NAMES.select { |e| self.send(e).nil? }
15
+ missing -= %i[ short ] # short is optional!
15
16
  if missing.any?
16
17
  raise ArgumentError, "Missing attributes: #{missing.join(', ')}"
17
18
  end
@@ -3,16 +3,22 @@ module CheckPlease
3
3
  module Comparison
4
4
  extend self
5
5
 
6
- def perform(reference, candidate)
6
+ def perform(reference, candidate, options = {})
7
7
  root = CheckPlease::Path.new
8
- diffs = Diffs.new
9
- compare reference, candidate, root, diffs
8
+ diffs = Diffs.new(options: options)
9
+ catch(:max_diffs_reached) do
10
+ compare reference, candidate, root, diffs
11
+ end
10
12
  diffs
11
13
  end
12
14
 
13
15
  private
14
16
 
15
17
  def compare(ref, can, path, diffs)
18
+ if (d = diffs.options[:max_depth])
19
+ return if path.depth > d + 1
20
+ end
21
+
16
22
  case types(ref, can)
17
23
  when [ :array, :array ] ; compare_arrays ref, can, path, diffs
18
24
  when [ :hash, :hash ] ; compare_hashes ref, can, path, diffs
@@ -11,14 +11,6 @@ module CheckPlease
11
11
  @path = path.to_s
12
12
  end
13
13
 
14
- def ref_display
15
- reference.inspect
16
- end
17
-
18
- def can_display
19
- candidate.inspect
20
- end
21
-
22
14
  def attributes
23
15
  Hash[ COLUMNS.map { |name| [ name, send(name) ] } ]
24
16
  end
@@ -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
@@ -5,7 +5,9 @@ module CheckPlease
5
5
  # Custom collection class for Diff instances.
6
6
  # Can retrieve members using indexes or paths.
7
7
  class Diffs
8
- def initialize(diff_list = nil)
8
+ attr_reader :options
9
+ def initialize(diff_list = nil, options: {})
10
+ @options = options
9
11
  @list = []
10
12
  @hash = {}
11
13
  Array(diff_list).each do |diff|
@@ -27,6 +29,11 @@ module CheckPlease
27
29
  end
28
30
 
29
31
  def <<(diff)
32
+ if (n = options[:max_diffs])
33
+ # It seems no one can help me now / I'm in too deep, there's no way out
34
+ throw :max_diffs_reached if length >= n
35
+ end
36
+
30
37
  @list << diff
31
38
  @hash[diff.path] = diff
32
39
  end
@@ -11,6 +11,10 @@ module CheckPlease
11
11
  self.class.new( Array(@segments) + Array(new_basename.to_s) )
12
12
  end
13
13
 
14
+ def depth
15
+ 1 + @segments.length
16
+ end
17
+
14
18
  def to_s
15
19
  SEPARATOR + @segments.join(SEPARATOR)
16
20
  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
@@ -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.0"
4
+ VERSION = "0.3.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.0
4
+ version: 0.3.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-13 00:00:00.000000000 Z
11
+ date: 2020-11-19 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,10 +70,10 @@ 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
79
  - lib/check_please/cli/flag.rb