check_please 0.2.3 → 0.5.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: f794a7a96790b642f2afc6f66bebb761a788c2b2a95025662e4ec8e654e612ef
4
- data.tar.gz: 98a088234aa843abf60668d8ee6722de318f60d19e29352f8332338d28268e7c
3
+ metadata.gz: fe333e55fbfdb89d0ea68b3f9ceefde98f3aa4b798792e9dd13745b76757a499
4
+ data.tar.gz: cf8cfc0205d3b8d7a3d10ec1c0c5487ac140dd7bc41c0c640eb2d72ec3d1e3f7
5
5
  SHA512:
6
- metadata.gz: 376fe8e75cf2c952d0cbaf42c32ca659c4f96488919eda781fc29cb50b259ddd3a91d60aa4bbae48022ccce96ab04148e60938079920a7657b7e288061b707dc
7
- data.tar.gz: dd4c24bd01c391fa70e626bd9753efd3601a4a9a92dfe92956ce5742c0d8771e53a32a3ef05cbd1e45ac75eff30342230ba2b3dd238274fbe00bea2291f7d148
6
+ metadata.gz: 918e8b6edec715a8096872c46fa12adc029244b886525c704e9b769fecae6595d38d0b658ebe64a53ebe3474d6ddba76f7d8222baefbef582da4c7dec27e686e
7
+ data.tar.gz: 4e0da6a791cc4e539ed75f893a3950336bcf5703e6dbb6435e02bac23f94e3207c06dca5cf13551a4a81980474e4e796a911ea333bfec42b5d97aa1e6ef0f3c8
data/.gitignore CHANGED
@@ -10,4 +10,6 @@
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
12
  /vendor/
13
- spec/examples.txt
13
+ spec/examples.txt
14
+ README.md.orig.*
15
+ README.md.toc.*
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- check_please (0.2.3)
4
+ check_please (0.5.0)
5
5
  table_print
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -3,7 +3,36 @@
3
3
  Check for differences between two JSON documents, YAML documents, or Ruby data
4
4
  structures parsed from either of those.
5
5
 
6
- ## Installation
6
+ <!-- start of auto-generated TOC; see https://github.com/ekalinin/github-markdown-toc -->
7
+ <!--ts-->
8
+ * [check_please](#check_please)
9
+ * [Installation](#installation)
10
+ * [Terminology](#terminology)
11
+ * [Usage](#usage)
12
+ * [From the Terminal / Command Line Interface (CLI)](#from-the-terminal--command-line-interface-cli)
13
+ * [From RSpec](#from-rspec)
14
+ * [From Ruby](#from-ruby)
15
+ * [Understanding the Output](#understanding-the-output)
16
+ * [Diff Types](#diff-types)
17
+ * [Paths](#paths)
18
+ * [Output Formats](#output-formats)
19
+ * [Flags](#flags)
20
+ * [Setting Flags in the CLI](#setting-flags-in-the-cli)
21
+ * [Setting Flags in Ruby](#setting-flags-in-ruby)
22
+ * ["Reentrant" Flags](#reentrant-flags)
23
+ * [Expanded Documentation for Specific Flags](#expanded-documentation-for-specific-flags)
24
+ * [Flag: match_by_key](#flag-match_by_key)
25
+ * [TODO (maybe)](#todo-maybe)
26
+ * [Development](#development)
27
+ * [Contributing](#contributing)
28
+ * [License](#license)
29
+ * [Code of Conduct](#code-of-conduct)
30
+
31
+
32
+ <!--te-->
33
+ <!-- end of auto-generated TOC -->
34
+
35
+ # Installation
7
36
 
8
37
  Add this line to your application's Gemfile:
9
38
 
@@ -19,11 +48,10 @@ Or install it yourself as:
19
48
 
20
49
  $ gem install check_please
21
50
 
22
- ## Usage
51
+ # Terminology
23
52
 
24
- ### Terminology
25
-
26
- CheckPlease uses a few words in a jargony way:
53
+ I know, you just want to see how to use this thing. Feel free to scroll down,
54
+ but be aware that CheckPlease uses a few words in a jargony way:
27
55
 
28
56
  * **Reference** is always used to refer to the "target" or "source of truth."
29
57
  We assume you're comparing two things because you want one of them to be like
@@ -36,7 +64,14 @@ CheckPlease uses a few words in a jargony way:
36
64
  **reference** and the **candidate**. More on this in "Understanding the Output",
37
65
  below.
38
66
 
39
- ### CLI
67
+ Also, even though this gem was born from a need to compare JSON documents, I'll
68
+ be talking about "hashes" instead of "objects", because I assume this will
69
+ mostly be used by Ruby developers. Feel free to substitute "object" wherever
70
+ you see "hash" if that's easier for you. :)
71
+
72
+ # Usage
73
+
74
+ ## From the Terminal / Command Line Interface (CLI)
40
75
 
41
76
  Use the `bin/check_please` executable. (To get started, run it with the '-h' flag.)
42
77
 
@@ -47,21 +82,28 @@ of giving it a second filename as the argument. (This is especially useful if
47
82
  you're copying an XHR response out of a web browser's dev tools and have a tool
48
83
  like MacOS's `pbpaste` utility.)
49
84
 
50
- ### RSpec Matcher
85
+ ## From RSpec
51
86
 
52
87
  See [check_please_rspec_matcher](https://github.com/RealGeeks/check_please_rspec_matcher).
53
88
 
54
- ### From Within Ruby
89
+ If you'd like more control over the output formatting, and especially if you'd
90
+ like to provide custom logic for diffing your own classes, you might be better
91
+ served by the [super_diff](https://github.com/mcmire/super_diff) gem. Check it
92
+ out!
93
+
94
+ ## From Ruby
95
+
96
+ See also: [./usage_examples.rb](usage_examples.rb).
55
97
 
56
- Create two JSON strings and pass them to `CheckPlease.render_diff`. You'll get
57
- back a third string containing a nicely formatted report of all the differences
58
- CheckPlease found in the two JSON strings. (See also: [./usage_examples.rb](usage_examples.rb).)
98
+ Create two strings, each containing a JSON or YAML document, and pass them to
99
+ `CheckPlease.render_diff`. You'll get back a third string containing a report
100
+ of all the differences CheckPlease found in the two JSON strings.
59
101
 
60
- (You can also parse the JSON strings yourself and pass the resulting data
61
- structures in, if you're into that. I mean, I wrote this to help compare JSON
62
- data that's too big and complicated to scan through visually, but you do you!
102
+ Or, if you'd like to inspect the diffs in your own way, use `CheckPlease.diff`
103
+ instead. You'll get back a `CheckPlease::Diffs` custom collection that
104
+ contains `CheckPlease::Diff` instances.
63
105
 
64
- ### Understanding the Output
106
+ ## Understanding the Output
65
107
 
66
108
  CheckPlease follows the Unix philosophy of "no news is good news". If your
67
109
  **candidate** matches your **reference**, you'll get an empty message.
@@ -71,9 +113,7 @@ tool because you want a human-friendly summary of all the places that your
71
113
  **candidate** fell short.
72
114
 
73
115
  When CheckPlease compares your two samples, it generates a list of diffs to
74
- describe any discrepancies it encounters. (By default, it prints that list in a
75
- tabular format, but if you want to incorporate this into another toolchain,
76
- CheckPlease can also print these diffs as JSON to facilitate parsing.)
116
+ describe any discrepancies it encounters.
77
117
 
78
118
  An example would probably help here.
79
119
 
@@ -117,7 +157,7 @@ mismatch | /meta/foo | spam | foo
117
157
 
118
158
  Let's start with the leftmost column...
119
159
 
120
- #### Diff Types
160
+ ### Diff Types
121
161
 
122
162
  The above example is intended to illustrate every possible type of diff that
123
163
  CheckPlease defines:
@@ -125,23 +165,23 @@ CheckPlease defines:
125
165
  * **type_mismatch** means that both the **reference** and the **candidate** had
126
166
  a value at the given path, but one value was an Array or a Hash and the other
127
167
  was not. **When CheckPlease encounters a type mismatch, it does not compare
128
- anything "below" the given path.** producing a lot of "garbage" diffs.
129
- _(Technical note: CheckPlease uses a "recursive descent" strategy to
130
- traverse the **reference** data structure, and it stops when it encounters a
131
- type mismatch in order to avoid producing a lot of "garbage" diff output.
132
- Also, the way these get displayed is likely to change.)_
168
+ anything "below" the given path.** _(Technical note: CheckPlease uses a
169
+ "recursive descent" strategy to traverse the **reference** data structure,
170
+ and it stops when it encounters a type mismatch in order to avoid producing a
171
+ lot of "garbage" diff output.)_
133
172
  * **mismatch** means that both the **reference** and the **candidate** had a
134
- value at the given path, and neither value was an Array or a Hash.
135
- * **extra** means that, inside an Array or a Hash, the **candidate**
136
- contained values that were not found in the **reference**.
173
+ value at the given path, and neither value was an Array or a Hash, and the
174
+ two values were not equal.
175
+ * **extra** means that, inside an Array or a Hash, the **candidate** contained
176
+ elements that were not found in the **reference**.
137
177
  * **missing** is the opposite of **extra**: inside an Array or a Hash, the
138
- **reference** contained values that were not found in the **candidate**.
178
+ **reference** contained elements that were not found in the **candidate**.
139
179
 
140
- #### Paths
180
+ ### Paths
141
181
 
142
- The second column contains a path expression. This is extremely basic:
182
+ The second column contains a path expression. This is extremely lo-fi:
143
183
 
144
- * The first element in the data structure is defined as "/".
184
+ * The root of the data structure is defined as "/".
145
185
  * If an element in the data structure is an array, its child elements will have
146
186
  a **one-based** index appended to their parent's path.
147
187
  * If an element in the data structure is an object ("Hash" in Ruby), the key
@@ -152,33 +192,265 @@ _**Being primarily a Ruby developer, I'm quite ignorant of conventions in the
152
192
  JS community; if there's an existing convention for paths, please open an
153
193
  issue!**_
154
194
 
155
- #### Output Formats
195
+ ### Output Formats
156
196
 
157
197
  CheckPlease produces tabular output by default. (It leans heavily on the
158
198
  amazing [table_print](http://tableprintgem.com) gem for this.)
159
199
 
160
200
  If you want to incorporate CheckPlease into some other toolchain, it can also
161
- print diffs as JSON to facilitate parsing. In Ruby, pass `format: :json` to
162
- `CheckPlease.render_diff`; in the CLI, use the `-f`/`--format` switch.
201
+ print diffs as JSON to facilitate parsing. How you do this depends on whether
202
+ you're using CheckPlease from the command line or in Ruby, which is a good time
203
+ to talk about...
204
+
205
+ ## Flags
206
+
207
+ CheckPlease has several flags that control its behavior.
208
+
209
+ For quick help on which flags are available, as well as some terse help text,
210
+ you can run the `check_please` executable with no arguments (or the `-h` or
211
+ `--help` flags if that makes you feel better).
212
+
213
+ While of course we aspire to keep this README up to date, it's probably best to
214
+ believe things in the following priority order:
215
+
216
+ * observed behavior
217
+ * the code (start from `./lib/check_please.rb` and search for `Flags.define`,
218
+ then trace through as needed)
219
+ * the tests (`spec/check_please/flags_spec.rb` describes how the flags work;
220
+ from there, you'll have to search on the flag's name to see how it shows up
221
+ in code)
222
+ * the output of `check_please --help`
223
+ * this README :)
224
+
225
+ All flags have exactly one "Ruby name" and one or more "CLI names". When the
226
+ CLI runs, it parses the values in `ARGV` (using Ruby's native `OptionParser`)
227
+ and uses that information to build a `CheckPlease::Flags` instance. After that
228
+ point, a flag will be referred to within the CheckPlease code exclusively by
229
+ its "Ruby name".
230
+
231
+ For example, the flag that controls the format in which diffs are displayed has
232
+ a Ruby name of `format`, and CLI names of `-f` and `--format`.
233
+
234
+ ### Setting Flags in the CLI
235
+
236
+ This should behave more or less as an experienced Unix CLI user might expect.
237
+
238
+ As such, you can specify, e.g., that you want output in JSON format using
239
+ either `--format json` or `-f json`.
240
+
241
+ (I might expand this section some day. In the meantime, if you are not yet an
242
+ experienced Unix CLI user, feel free to ask for help! You can either open an
243
+ issue or look for emails in the `.gemspec` file...)
244
+
245
+ ### Setting Flags in Ruby
246
+
247
+ All external API entry points allow you to specify flags using their Ruby names
248
+ in the idiomatic "options Hash at the end of the argument list" that should be
249
+ familiar to most Rubyists. (Again, I assume that, if you're using this tool, I
250
+ don't need to explain this further, but feel free to ask for help if you need
251
+ it.)
252
+
253
+ (Internally, CheckPlease immediately converts that options hash into a
254
+ `CheckPlease::Flags` object, but that should be considered an implementation
255
+ detail unless you're interested in hacking on CheckPlease itself.)
256
+
257
+ For example, to get back a String containing the diffs between two data
258
+ structures in JSON format, you might do:
259
+
260
+ ```
261
+ reference = { "foo" => "wibble" }
262
+ candidate = { "bar" => "wibble" }
263
+ puts CheckPlease.render_diff(
264
+ reference,
265
+ candidate,
266
+ format: :json # <--- flags
267
+ )
268
+ ```
269
+
270
+ ### Repeatable Flags
271
+
272
+ Several flags **may** be specified more than once when invoking the CLI. I've
273
+ tried to make both the CLI and the Ruby API follow their respective
274
+ environment's conventions.
275
+
276
+ For example, if you want to specify a path to ignore using the
277
+ `--reject-paths` flag, you'd invoke the CLI like this:
278
+
279
+ * `[bundle exec] check_please reference.json candidate.json --select-paths /foo`
280
+
281
+ And if you want to specify more than one path, that would look like:
282
+
283
+ * `[bundle exec] check_please reference.json candidate.json --select-paths /foo --select-paths /bar`
284
+
285
+ In Ruby, you can specify this in the options hash as a single key with an Array
286
+ value:
287
+
288
+ * `CheckPlease.render_diff(reference, candidate, select_paths: [ "/foo", "/bar" ])`
289
+
290
+ _(NOTE TO MAINTAINERS: internally, the way `CheckPlease::CLI::Parser` uses
291
+ Ruby's `OptionParser` leads to some less than obvious behavior. Search
292
+ [./spec/check_please/flags_spec.rb](spec/check_please/flags_spec.rb) for the
293
+ word "surprising" for details.)_
294
+
295
+ ### Expanded Documentation for Specific Flags
296
+
297
+ #### Flag: `match_by_key`
298
+
299
+ > **I know this looks like a LOT of information, but it's really not that
300
+ > bad!** This feature just requires specific examples to describe, and talking
301
+ > about it in English (rather than code) is hard. Take a moment for some deep
302
+ > breaths if you need it. :)
303
+
304
+ > _If you're comfortable reading RSpec and/or want to check out all the edge
305
+ > cases, go look in `./spec/check_please/comparison_spec.rb` and check out the
306
+ > `describe` block labeled `"comparing arrays by keys"`._
307
+
308
+ The `match_by_key` flag allows you to match up arrays of hashes using the value
309
+ of a single key that is treated as the identifier for each hash.
310
+
311
+ There's a lot going on in that sentence, so let's unpack it a bit.
312
+
313
+ Imagine you're comparing two documents that contain the same data, but in
314
+ different orders. To use a contrived example, let's say that both documents
315
+ consist of a single array of two simple hashes, but the reference array and the
316
+ candidate array are reversed:
163
317
 
164
- ## TODO
318
+ ```ruby
319
+ # REFERENCE
320
+ [ { "id" => 1, "foo" => "bar" }, { "id" => 2, "foo" => "spam" } ]
321
+
322
+ # CANDIDATE
323
+ [ { "id" => 2, "foo" => "spam" }, { "id" => 1, "foo" => "bar" } ]
324
+ ```
325
+
326
+ By default, CheckPlease will match up array elements by their position in the
327
+ array, resulting in a diff report like this:
328
+
329
+ ```
330
+ TYPE | PATH | REFERENCE | CANDIDATE
331
+ ---------|--------|-----------|----------
332
+ mismatch | /1/id | 1 | 2
333
+ mismatch | /1/foo | "bar" | "bat"
334
+ mismatch | /2/id | 2 | 1
335
+ mismatch | /2/foo | "bat" | "bar"
336
+ ```
337
+
338
+ To solve this problem, CheckPlease adds a **key expression** to its (very
339
+ simple) path syntax that lets you specify a **key** to use to match up elements
340
+ in both lists, rather than simply comparing elements by position.
341
+
342
+ Continuing with the above example, if we give `match_by_key` a value of
343
+ `["/:id"]`, it will use the "id" value in both hashes (remember, A's `id` is
344
+ `1` and B's `id` is `2`) to identify every element in both the reference array
345
+ and the candidate array, and correctly match A and B, giving you an empty list
346
+ of diffs.
347
+
348
+ Please note that the CLI and Ruby implementations of these are a bit different
349
+ (see "Setting Flags in the CLI" versus "Setting Flags in Ruby"), so if you're
350
+ doing this from the command line, it'll look like: `--match-by-key /:id`
351
+
352
+ Here, have another example. If you want to specify a match_by_key expression
353
+ below the root of the document, you can put the **key expression** further down
354
+ the path: `/books/:isbn`
355
+
356
+ This would correctly match up the following documents:
357
+
358
+ ```ruby
359
+ # REFERENCE
360
+ {
361
+ "books" => [
362
+ { "isbn" => "12345", "title" => "Who Am I, Really?" },
363
+ { "isbn" => "67890", "title" => "Who Are Any Of Us, Really?" },
364
+ ]
365
+ }
366
+
367
+ # CANDIDATE
368
+ {
369
+ "books" => [
370
+ { "isbn" => "67890", "title" => "Who Are Any Of Us, Really?" },
371
+ { "isbn" => "12345", "title" => "Who Am I, Really?" },
372
+ ]
373
+ }
374
+ ```
165
375
 
376
+ Finally, if you have deeply nested data with arrays of hashes at multiple
377
+ levels, you can specify more than one **key expression** in a single path,
378
+ like: `/authors/:id/books/:isbn`
379
+
380
+ This would correctly match up the following documents:
381
+
382
+ ```ruby
383
+ # REFERENCE
384
+ {
385
+ "authors" => [
386
+ {
387
+ "id" => 1,
388
+ "name" => "Anne Onymous",
389
+ "books" => [
390
+ { "isbn" => "12345", "title" => "Who Am I, Really?" },
391
+ { "isbn" => "67890", "title" => "Who Are Any Of Us, Really?" },
392
+ ]
393
+ },
394
+ ]
395
+ }
396
+
397
+ # CANDIDATE
398
+ {
399
+ "authors" => [
400
+ {
401
+ "id" => 1,
402
+ "name" => "Anne Onymous",
403
+ "books" => [
404
+ { "isbn" => "67890", "title" => "Who Are Any Of Us, Really?" },
405
+ { "isbn" => "12345", "title" => "Who Am I, Really?" },
406
+ ]
407
+ },
408
+ ]
409
+ }
410
+ ```
411
+
412
+ Finally, if there are any diffs to report, CheckPlease uses a **key/value
413
+ expression** to report mismatches.
414
+
415
+ Using the last example above (the one with `/authors/:id/books/:isbn`), if the
416
+ reference had Anne Onymous' book title as "Who Am I, Really?" and the candidate
417
+ listed it as "Who The Heck Am I?", CheckPlease would show the mismatch using
418
+ the following path expression: `/authors/id=1/books/isbn=12345`
419
+
420
+ **This syntax is intended to be readable by humans first.** If you need to
421
+ build tooling that consumes it... well, I'm open to suggestions. :)
422
+
423
+ -----
424
+
425
+ # TODO (maybe)
426
+
427
+ * document flags for rspec matcher
166
428
  * command line flags for :allthethings:!
429
+ * change display width for table format
430
+ (for example, "2020-07-16T19:42:41.312978" gets cut off)
167
431
  * sort by path?
168
- * max depth (for iterative refinement?)
169
432
  * detect timestamps and compare after parsing?
170
433
  * ignore sub-second precision (option / CLI flag)?
171
434
  * possibly support plugins for other folks to add custom coercions?
172
- * support expressions of specific paths to ignore
173
- * wildcards? `#` for indexes, `**` to match one or more path segments?
174
- (This could get ugly fast.)
175
435
  * display filters? (e.g., { a: 1, b: 2 } ==> "Hash#3")
176
436
  * shorter descriptions of values with different classes
177
437
  (but maybe just the existing :type_mismatch diffs?)
178
438
  * another "possibly support plugins" expansion point here
179
439
  * more output formats, maybe?
440
+ * [0xABAD1DEA] support wildcards in --select-paths and --reject-paths?
441
+ * `#` for indexes, `**` to match one or more path segments?
442
+ (This could get ugly fast.)
443
+ * [0xABAD1DEA] look for a config file in ./.check_please_config or ~/.check_please_config,
444
+ combine flags found there with those in ARGV in order of precedence:
445
+ 1) ARGV
446
+ 2) ./.check_please
447
+ 3) ~/.check_please
448
+ * but this may not actually be worth the time and complexity to implement, so
449
+ think about this first...
180
450
 
181
- ## Development
451
+ -----
452
+
453
+ # Development
182
454
 
183
455
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
184
456
  `rake spec` to run the tests. You can also run `bin/console` for an interactive
@@ -190,22 +462,21 @@ release a new version, update the version number in `version.rb`, and then run
190
462
  git commits and tags, and push the `.gem` file to
191
463
  [rubygems.org](https://rubygems.org).
192
464
 
193
- ## Contributing
465
+ # Contributing
194
466
 
195
467
  Bug reports and pull requests are welcome on GitHub at
196
- https://github.com/[USERNAME]/check_please. This project is intended to be a
468
+ https://github.com/RealGeeks/check_please. This project is intended to be a
197
469
  safe, welcoming space for collaboration, and contributors are expected to
198
470
  adhere to the [code of
199
- conduct](https://github.com/[USERNAME]/check_please/blob/master/CODE_OF_CONDUCT.md).
200
-
471
+ conduct](https://github.com/RealGeeks/check_please/blob/master/CODE_OF_CONDUCT.md).
201
472
 
202
- ## License
473
+ # License
203
474
 
204
475
  The gem is available as open source under the terms of the [MIT
205
476
  License](https://opensource.org/licenses/MIT).
206
477
 
207
- ## Code of Conduct
478
+ # Code of Conduct
208
479
 
209
480
  Everyone interacting in the CheckPlease project's codebases, issue trackers,
210
481
  chat rooms and mailing lists is expected to follow the [code of
211
- conduct](https://github.com/[USERNAME]/check_please/blob/master/CODE_OF_CONDUCT.md).
482
+ conduct](https://github.com/RealGeeks/check_please/blob/master/CODE_OF_CONDUCT.md).