From db1de9df2eb4fb9262cdf3de7a014177b16d5a61 Mon Sep 17 00:00:00 2001 From: Elizabeth Mattijsen Date: Thu, 24 Nov 2022 14:22:23 +0100 Subject: [PATCH] Add some documentation and tests 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 --- README.md | 28 ++++++++++++++++ doc/App-Rak.rakudoc | 36 ++++++++++++++++++++ lib/App/Rak.rakumod | 37 ++++++++++++++++----- resources/help/pattern.txt | 24 ++++++++++++++ xt/03-json.rakutest | 67 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 184 insertions(+), 8 deletions(-) create mode 100644 xt/03-json.rakutest diff --git a/README.md b/README.md index e80d31a..523b060 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/doc/App-Rak.rakudoc b/doc/App-Rak.rakudoc index 04afabc..69a7ffe 100644 --- a/doc/App-Rak.rakudoc +++ b/doc/App-Rak.rakudoc @@ -112,6 +112,42 @@ Basically a shortcut to specifying C. 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 and C<--type=json-path> syntax are actually syntactic sugar +for calling a dedicated C subroutine that takes a JSON path as its +argument, and returns an instantiated C object. + +This means that: + +=begin code :lang + +$ 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 + +$ 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 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 object, +one should note that all efforts have been made to make the C object +act like a C. + =head3 ^string If the pattern starts with C<^>, then it indicates the string should be at diff --git a/lib/App/Rak.rakumod b/lib/App/Rak.rakumod index 3595795..f4a9e80 100644 --- a/lib/App/Rak.rakumod +++ b/lib/App/Rak.rakumod @@ -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) @@ -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) @@ -608,7 +629,7 @@ 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)' } @@ -616,7 +637,7 @@ my sub codify-curlies(Str:D $code) { } } - my $jp := JP.new: jp => do { + my $jp := JP.new: :$pattern, jp => do { CATCH { if X::AdHoc.ACCEPTS($_) { my $m := .payload; @@ -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; } - $class.new($path) + $class.new($pattern) } - &jp = my sub jp-live($) { $jp } + &jp = my sub jp-live(str $) { $jp } $jp } diff --git a/resources/help/pattern.txt b/resources/help/pattern.txt index 24e667c..5a8a27e 100644 --- a/resources/help/pattern.txt +++ b/resources/help/pattern.txt @@ -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 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 diff --git a/xt/03-json.rakutest b/xt/03-json.rakutest new file mode 100644 index 0000000..e670085 --- /dev/null +++ b/xt/03-json.rakutest @@ -0,0 +1,67 @@ +BEGIN %*ENV = 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