Skip to content

Commit 7500a51

Browse files
committed
Docs, take one (this was harder than I expected)
1 parent fa3c41a commit 7500a51

File tree

3 files changed

+212
-21
lines changed

3 files changed

+212
-21
lines changed

README.md

Lines changed: 201 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,9 @@ but be aware that CheckPlease uses a few words in a jargony way:
3535
**reference** and the **candidate**. More on this in "Understanding the Output",
3636
below.
3737

38-
TODO: document --match-by-key, including "key expressions" and "key/value expressions"
39-
4038
## Usage
4139

42-
### From the Terminal
40+
### From the Terminal / Command Line Interface (CLI)
4341

4442
Use the `bin/check_please` executable. (To get started, run it with the '-h' flag.)
4543

@@ -165,9 +163,208 @@ CheckPlease produces tabular output by default. (It leans heavily on the
165163
amazing [table_print](http://tableprintgem.com) gem for this.)
166164

167165
If you want to incorporate CheckPlease into some other toolchain, it can also
168-
print diffs as JSON to facilitate parsing. In Ruby, pass `format: :json` to
166+
print diffs as JSON to facilitate parsing. How you do this depends on whether
167+
you're using CheckPlease from the command line or in Ruby, which is a good time
168+
to talk about...
169+
170+
171+
In Ruby, pass `format: :json` to
169172
`CheckPlease.render_diff`; in the CLI, use the `-f`/`--format` switch.
170173

174+
### Flags
175+
176+
CheckPlease has several flags that control its behavior.
177+
178+
For quick help on which flags are available, as well as some terse help text,
179+
you can run the `check_please` executable with no arguments (or the `-h` or
180+
`--help` flags if that makes you feel better).
181+
182+
While of course we aspire to keep this README up to date, it's probably best to
183+
believe things in the following priority order:
184+
185+
* observed behavior
186+
* the code (start from `./lib/check_please.rb` and search for `Flags.define`,
187+
then trace through as needed)
188+
* the tests (`spec/check_please/flags_spec.rb` describes how the flags work;
189+
from there, you'll have to search on the flag's name to see how it shows up
190+
in code)
191+
* the output of `check_please --help`
192+
* this README :)
193+
194+
All flags have exactly one "Ruby name" and one or more "CLI names". When the
195+
CLI runs, it parses the values in `ARGV` (using Ruby's native `OptionParser`)
196+
and uses that information to build a `CheckPlease::Flags` instance. After that
197+
point, a flag will be referred to within the CheckPlease code exclusively by
198+
its "Ruby name".
199+
200+
For example, the flag that controls the format in which diffs are displayed has
201+
a Ruby name of `format`, and CLI names of `-f` and `--format`.
202+
203+
#### Setting Flags in the CLI
204+
205+
This should behave more or less as an experienced Unix CLI user might expect.
206+
207+
As such, you can specify, e.g., that you want output in JSON format using
208+
either `--format json` or `-f json`.
209+
210+
(I might expand this section some day. In the meantime, if you are not yet an
211+
experienced Unix CLI user, feel free to ask for help! You can either open an
212+
issue or look for emails in the `.gemspec` file...)
213+
214+
#### Setting Flags in Ruby
215+
216+
All external API entry points allow you to specify flags using their Ruby names
217+
in the idiomatic "options Hash at the end of the argument list" that should be
218+
familiar to most Rubyists. (Again, I assume that, if you're using this tool, I
219+
don't need to explain this further, but feel free to ask for help if you need
220+
it.)
221+
222+
(Internally, CheckPlease immediately converts that options hash into a
223+
`CheckPlease::Flags` object, but that should be considered an implementation
224+
detail unless you're interested in hacking on CheckPlease itself.)
225+
226+
For example, to get back a String containing the diffs between two data
227+
structures in JSON format, you might do:
228+
229+
```
230+
reference = { "foo" => "wibble" }
231+
candidate = { "bar" => "wibble" }
232+
puts CheckPlease.render_diff(
233+
reference,
234+
candidate,
235+
format: :json # <--- flags
236+
)
237+
```
238+
239+
#### "Reentrant" Flags
240+
241+
Several flags are "reentrant". This means that the flag and its associated
242+
value **may** appear more than once in the CLI. I've tried to make both the
243+
CLI and the Ruby API follow their respective environment's conventions.
244+
245+
For example, if you want to specify a path to ignore using the
246+
`--reject-paths` flag, you'd invoke the CLI like this:
247+
248+
* `[bundle exec] check_please reference.json candidate.json --select-paths /foo`
249+
250+
And if you want to specify more than one path, that would look like:
251+
252+
* `[bundle exec] check_please reference.json candidate.json --select-paths /foo --select-paths /bar`
253+
254+
In Ruby, you can specify this in the options hash as a single key with an Array
255+
value:
256+
257+
* `CheckPlease.render_diff(reference, candidate, select_paths: [ "/foo", "/bar" ])`
258+
259+
_(NOTE TO MAINTAINERS: internally, the way `CheckPlease::CLI::Parser` uses
260+
Ruby's `OptionParser` leads to some less than obvious behavior. Search
261+
`./spec/check_please/flags_spec.rb` for the word "surprising" for details.)_
262+
263+
#### Expanded Documentation for Specific Flags
264+
265+
##### `match_by_key`
266+
267+
Okay. This gets... a bit weird, and as of this writing it's still a bit experimental.
268+
269+
For all the details on exactly how this behaves, go look in
270+
`./spec/check_please/comparison_spec.rb` and check out the `describe` block
271+
labeled `"comparing arrays by keys"`.
272+
273+
The short version, though, is that this allows you to match up two lists of
274+
hashes using the value of a single key that is treated as the identifier for
275+
that hash.
276+
277+
There's a lot going on in that sentence, so let's unpack it a bit.
278+
279+
Imagine that you're comparing two documents that actually contain the same
280+
data, but in a different order. To use a very simple example, let's say that
281+
both documents consist of a single array of two simple hashes, which we'll call
282+
"A" and "B":
283+
284+
* "A" looks like this: `{ "id" => 1, "foo" => "bar" }`
285+
* "B" looks like this: `{ "id" => 2, "foo" => "spam" }`
286+
287+
Normally, if your reference document is the list [A, B] and your candidate
288+
document is the list [B, A], CheckPlease will compare A and B by their position
289+
in the array, and you'll get a diff report that looks like this:
290+
291+
```
292+
TYPE | PATH | REFERENCE | CANDIDATE
293+
---------|--------|-----------|----------
294+
mismatch | /1/id | 1 | 2
295+
mismatch | /1/foo | "bar" | "bat"
296+
mismatch | /2/id | 2 | 1
297+
mismatch | /2/foo | "bat" | "bar"
298+
```
299+
300+
To solve this problem, CheckPlease adds a **key expression** to its (very
301+
simple) path syntax that lets you specify a **key** to use to match up elements
302+
in both lists, rather than simply comparing elements by position.
303+
304+
Continuing with the above example, if we give `match_by_key` a value of
305+
`["/:id"]`, it will use the "id" value in both hashes (remember, A's `id` is
306+
`1` and B's `id` is `2`) to identify every element in both the reference array
307+
and the candidate array, and correctly match A and B, giving you an empty list
308+
of diffs.
309+
310+
Please note that the CLI and Ruby implementations of these are a bit different
311+
(see the '"Reentrant" Flags' section).
312+
313+
Here are some examples of how that looks on the command line:
314+
315+
* `--match-by-key /:id` -- using the A and B documents above, this will expect
316+
the top-level element to be an array that contains only hashes, and use the
317+
"id" value in each hash to match up reference/candidate pairs.
318+
319+
* `--match-by-key /books/:isbn` -- this will expect the top-level element to be
320+
a hash with a 'books' key that refers to an array, and will use the "isbn"
321+
value in each hash to match up reference/candidate pairs.
322+
323+
* `--match-by-key /authors/:id/books/:isbn` -- see below.
324+
325+
For that last one, the structure of the reference document will look like this:
326+
327+
```ruby
328+
{
329+
"authors" => [
330+
{
331+
"id" => 1,
332+
"name" => "Anne Onymous",
333+
"books" => [
334+
{ "isbn" => "12345", "title" => "Who Am I, Really?" },
335+
# ...
336+
]
337+
},
338+
{
339+
"id" => 2,
340+
"name" => "Pseud Onymous",
341+
"books" => [
342+
{ "isbn" => "67890", "title" => "You'll Never Know" },
343+
# ...
344+
]
345+
},
346+
# ...
347+
]
348+
}
349+
```
350+
351+
At the top level, CheckPlease will match up hash elements by key. When it gets
352+
to the "authors" key, it will look at the `match_by_key` expression, see that
353+
it's supposed to use the "id" key to compare elements in an array, and do so.
354+
Further down, when it encounters the "books" key in both authors 1 and 2, it
355+
will use the "isbn" key to match up elements in the "books" array.
356+
357+
Finally, if there are any diffs to report, CheckPlease uses a **key/value
358+
expression** to report mismatches. For example, if the reference had Anne
359+
Onymous' book title as "Who Am I, Really?" and the candidate listed it as "Who
360+
The Heck Am I?", CheckPlease would show this using the following path
361+
expression: `/authors/id=1/books/isbn=12345`
362+
363+
**This syntax is intended to be readable by humans first.** If you need to
364+
build tooling on it... well, I'm open to suggestions. :)
365+
366+
367+
171368
## TODO (maybe)
172369

173370
* document flags for rspec matcher

lib/check_please.rb

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ def maybe_parse(document)
8383
Flags.define :fail_fast do |flag|
8484
flag.default = false
8585
flag.coerce { |value| !!value }
86-
8786
flag.cli_long = "--fail-fast"
8887
flag.description = [
8988
"Stop after encountering the first diff.",
@@ -138,21 +137,10 @@ def maybe_parse(document)
138137
flag.cli_long = "--match-by-key FOO"
139138
flag.description = [
140139
"Specify how to match reference/candidate pairs in arrays.",
141-
"NOTE: this does not yet handle non-string keys."
140+
" May be repeated; values will be treated as an 'OR' list.",
141+
" See the README for details on how to actually use this.",
142+
" This does not yet handle non-string keys."
142143
]
143-
###############################################
144-
## ##
145-
## ######## ##### ###### ##### ##
146-
## ## ## ## ## ## ## ## ##
147-
## ## ## ## ## ## ## ## ##
148-
## ## ## ## ## ## ## ## ##
149-
## ## ## ## ## ## ## ## ##
150-
## ## ## ## ## ## ## ## ##
151-
## ## ##### ###### ##### ##
152-
## ##
153-
###############################################
154-
# TODO: finish the description above
155-
###############################################
156144
end
157145

158146
end

usage_examples.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
require 'check_please'
22

3-
reference = { foo: "wibble" }
4-
candidate = { bar: "wibble" }
3+
reference = { "foo" => "wibble" }
4+
candidate = { "bar" => "wibble" }
55

66

77

@@ -28,3 +28,9 @@
2828
#
2929
# If you come up with a useful way to present these, feel free to submit a PR
3030
# with a new class in `lib/check_please/printers` !
31+
32+
# To print these in the console, you can just do:
33+
puts diffs
34+
35+
# If for some reason you want to print the JSON version, it gets a little more verbose:
36+
puts diffs.to_s(format: :json)

0 commit comments

Comments
 (0)