Skip to content

Commit

Permalink
Add some documentation and tests
Browse files Browse the repository at this point in the history
Specifically on using the jp() sub directly from a code pattern.

Also fix some issues with JP[] and make JP object act more like a Seq
  • Loading branch information
lizmat committed Nov 24, 2022
1 parent 13f617e commit db1de9d
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 8 deletions.
28 changes: 28 additions & 0 deletions README.md
Expand Up @@ -86,6 +86,34 @@ If the pattern start with 'jp:', then interpret the rest of the pattern as a `JS

A query that is not rooted from $ or specified using .. will be evaluated from the document root (that is, same as an explicit $ at the start).

#### Full Raku support

The `jp:path` and `--type=json-path` syntax are actually syntactic sugar for calling a dedicated `jp` subroutine that takes a JSON path as its argument, and returns an instantiated `JP` object.

This means that:

```bash
$ rak --json-per-file jp:foo
$ rak --json-per-file --type=json-path foo
```

are a different way of saying:

```bash
$ rak --json-per-file '{ jp("path").Slip }'
```

using the "pattern is Raku code" syntax.

The following methods can be called on the `JP` object:

.value The first selected value.
.values All selected values as a Seq.
.paths The paths of all selected values as a Seq.
.paths-and-values Interleaved selected paths and values.

Without listing all of the methods that can be called on the `JP` object, one should note that all efforts have been made to make the `JP` object act like a `Seq`.

### ^string

If the pattern starts with `^`, then it indicates the string should be at the **start** of each item. Basically a shortcut to specifying `string --type=starts-with`. Any `--smartcase`, `--smartmark`, `--ignorecase` or `--ignoremark` arguments will be honoured.
Expand Down
36 changes: 36 additions & 0 deletions doc/App-Rak.rakudoc
Expand Up @@ -112,6 +112,42 @@ Basically a shortcut to specifying C<path --type=json-path>.
A query that is not rooted from $ or specified using .. will be evaluated
from the document root (that is, same as an explicit $ at the start).

=head4 Full Raku support

The C<jp:path> and C<--type=json-path> syntax are actually syntactic sugar
for calling a dedicated C<jp> subroutine that takes a JSON path as its
argument, and returns an instantiated C<JP> object.

This means that:

=begin code :lang<bash>

$ rak --json-per-file jp:foo
$ rak --json-per-file --type=json-path foo

=end code

are a different way of saying:

=begin code :lang<bash>

$ rak --json-per-file '{ jp("path").Slip }'

=end code

using the "pattern is Raku code" syntax.

The following methods can be called on the C<JP> object:

.value The first selected value.
.values All selected values as a Seq.
.paths The paths of all selected values as a Seq.
.paths-and-values Interleaved selected paths and values.

Without listing all of the methods that can be called on the C<JP> object,
one should note that all efforts have been made to make the C<JP> object
act like a C<Seq>.

=head3 ^string

If the pattern starts with C<^>, then it indicates the string should be at
Expand Down
37 changes: 29 additions & 8 deletions lib/App/Rak.rakumod
Expand Up @@ -574,16 +574,34 @@ my sub codify-curlies(Str:D $code) {
# Wrapper for JSON::Path object
my class JP {
has $.jp;
has $.pattern;
method value() { $!jp.value($*_) }
method values() { $!jp.values($*_) }
method paths() { $!jp.paths($*_) }
method paths-and-values() { $!jp.paths-and-values($*_) }
method Seq() { $!jp.values($*_) }
method list() { $!jp.values($*_).List }
method List() { $!jp.values($*_).List }
method Slip() { $!jp.values($*_).Slip }
method Str() { $!jp.values($*_).Str }
method gist() { $!jp.values($*_).gist }
method Str() {
$*_
?? $!jp.values($*_).Str
!! meh qq:!c:to/ERROR/.chomp;
Must do something with the JP object *inside* the pattern, such as:
'{jp("$.pattern").Slip}'
to avoid late stringification of the JP object.
ERROR
}
method words() { $!jp.values($*_).Str.words.Slip }
method head(|c) { $!jp.values($*_).head(|c).Slip }
method tail(|c) { $!jp.values($*_).tail(|c).Slip }
method skip(|c) { $!jp.values($*_).skip(|c).Slip }
}
# Allow postcircumfixes on jp($path)
Expand All @@ -594,10 +612,13 @@ my sub codify-curlies(Str:D $code) {
$self.values
}
my multi sub postcircumfix:<[ ]>(JP:D $self, Int:D $pos) {
$self.values[$pos]
$self.values[$pos].Slip
}
my multi sub postcircumfix:<[ ]>(JP:D $self, @pos) {
$self.values[@pos]
$self.values[@pos].Slip
}
my multi sub postcircumfix:<[ ]>(JP:D $self, &pos) {
$self.values[&pos].Slip
}
# Allow for slip jp($path)
Expand All @@ -608,15 +629,15 @@ my sub codify-curlies(Str:D $code) {
# Magic self-installing JSON::Path support
my $class;
my $lock := Lock.new;
my &jp = my sub jp-stub(str $path) {
my &jp = my sub jp-stub(str $pattern) {
$lock.protect: {
if $class<> =:= Any {
CATCH { meh-not-installed "JSON::Path", 'jp(path)' }
$class := 'use JSON::Path:ver<1.7>; JSON::Path'.EVAL;
}
}
my $jp := JP.new: jp => do {
my $jp := JP.new: :$pattern, jp => do {
CATCH {
if X::AdHoc.ACCEPTS($_) {
my $m := .payload;
Expand All @@ -626,16 +647,16 @@ my sub codify-curlies(Str:D $code) {
my $boff := BOFF;
exit note qq:to/ERROR/.chomp;
$m:
$path.substr(0,$pos)$bon$path.substr($pos,1)$boff$path.substr($pos + 1)
$pattern.substr(0,$pos)$bon$pattern.substr($pos,1)$boff$pattern.substr($pos + 1)
{" " x $pos}
ERROR
}
}
meh .Str unless %rak<dont-catch>;
}
$class.new($path)
$class.new($pattern)
}
&jp = my sub jp-live($) { $jp }
&jp = my sub jp-live(str $) { $jp }
$jp
}
Expand Down
24 changes: 24 additions & 0 deletions resources/help/pattern.txt
Expand Up @@ -156,6 +156,30 @@ The following syntax is supported
A query that is not rooted from $ or specified using .. will be evaluated
from the document root (that is, same as an explicit $ at the start).

The "jp:path" and "--type=json-path" syntax are actually syntactic sugar
for calling a dedicated "jp" subroutine that takes a JSON path as its
argument, and returns an instantiated C<JP> object.

This means that:

$ rak --json-per-file jp:foo
$ rak --json-per-file --type=json-path foo

are a different way of saying:

$ rak --json-per-file '{ jp("path").Slip }'

using the "pattern is Raku code" syntax.

The following methods can be called on the "JP" object:

.value The first selected value.
.values All selected values as a Seq.
.paths The paths of all selected values as a Seq.
.paths-and-values Interleaved selected paths and values.

In all other ways, the "JP" object as a a "Seq" object.

Search types on literal strings:

Literal strings matching can be further specialized with other values of the
Expand Down
67 changes: 67 additions & 0 deletions xt/03-json.rakutest
@@ -0,0 +1,67 @@
BEGIN %*ENV<RAKU_TEST_DIE_ON_FAIL> = 1;
use Test;

plan 8;

my $dir = $*PROGRAM.parent;
my $dira = $dir.absolute ~ $*SPEC.dir-sep;
my $rel := $dir.relative ~ $*SPEC.dir-sep;
my $dot = $?FILE.IO.parent.parent;
my $rak := $dot.add("bin").add("rak").relative;

# using paths from here on out
$dot .= relative;
$dir .= absolute;

my sub query-ok(
*@query, # the actual parameters
:$ok is copy, # the expected result
:$head = 1, # whether to do the --only-first test with this number
:$add-human = True, # add human specification
) is test-assertion {
my @args = $*EXECUTABLE.absolute, "-I$dot", $rak, @query.Slip;
@args.push: "--human" if $add-human;
$ok .= chomp;

# Logic to run the query
my sub run-query() {
my $proc := run @args, :out, :err;
my $key = "huh?";

is $proc.err.slurp(:close), "", "is '@query[]' STDERR clean?";
$proc.out.lines.map(*.subst($rel, :g)).join("\n")
}

# Base query
is run-query, $ok, "is '@query[]' result ok?";
}

query-ok <--json-per-file jp:auth --/dir>, ok => qq:to/OK/;
META6.json
zef:lizmat
OK

query-ok <--json-per-file authors --type=json-path --/dir>, ok => qq:to/OK/;
META6.json
Elizabeth Mattijsen
OK

query-ok <--json-per-file {~jp("license")} --/dir>, ok => qq:to/OK/;
META6.json
Artistic-2.0
OK

query-ok <--json-per-file {|jp("tags[*]")} --/dir>, ok => qq:to/OK/;
META6.json
ACK
SEARCH
TEXT
EDITOR
FIND
JSON
BLAME
CSV
GIT
OK

# vim: expandtab shiftwidth=4

0 comments on commit db1de9d

Please sign in to comment.