#! /usr/bin/perl
# PODNAME: dpath
# ABSTRACT: cmdline tool around Data::DPath

use 5.008;
use strict;
use warnings;

use App::Rad;
use App::DPath;
use Data::DPath 'dpath';

######################################################################
#
# App::Rad interface
#
######################################################################

BEGIN {
        my $default_cmd = "search";
        unshift @ARGV, $default_cmd unless $ARGV[0] && $ARGV[0] =~ /^(search|help)$/;
}

App::Rad->run();

sub setup
{
        my $c = shift;
        $c->unregister_command("help");
        $c->register_commands("help", "search");
}

sub help
{
        my ($c) = @_;

        return "dpath [-ios] [--fb] [ --fi] <DPath>

        -o
        --outtype        - output format
                           [yaml(default), json, dumper, xml]
        -i
        --intype         - input format
                           [yaml(default), json, dumper, xml, tap, taparchive, ini]
        --yaml-module    - which YAML module to use for format 'yaml'
                           [YAML::XS, YAML, YAML::Old, YAML::Tiny, YAML::Syck]
                           (by default it uses what YAML::Any finds, but not YAML::Syck)
        -s
        --separator      - sub entry separator for output format 'flat'
                           (default=;)
        --fb             - on output format 'flat' use [brackets] around
                           outer arrays
        --fi             - on output format 'flat' prefix outer array lines
                           with index

        See 'perldoc Data::DPath' for how to specify a DPath.
";
}

sub search :Help(search incoming data by DPath (default commmand))
{
        my ($c) = @_;

        _getopt($c);

        my $path    = $c->argv->[0];
        my $file    = $c->argv->[1] || '-';
        my $data    = App::DPath::read_in( $file, $c->options->{intype},
            $c->options->{'yaml-module'} );

        my $out;
        foreach my $datum (ref $data eq ref [] ? @$data : $data) {
            my $result = _match($c, $datum, $path);
            $out .= App::DPath::write_out($c->options, $result);
        }
        return $out;
}

sub default { search(@_) }

######################################################################
#
# Implementation
#
######################################################################
sub _match
{
        my ($c, $data, $path) = @_;

        if (not $data) {
                die "dpath: no input data to match.\n";
        }

        my @resultlist = dpath($path)->match($data);
        return \@resultlist;
}

sub _getopt
{
        my ($c) = @_;

        $c->getopt( "faces|f=i",
                    "times|t=i",
                    "intype|i=s",
                    "outtype|o=s",
                    "separator|s=s",
                    "yaml-module",
                    "fb",
                    "fi",
                  )
         or help() and return undef;
        if (not $c->argv->[0]) {
                die "dpath: please specify a dpath.\n";
        }
}

__END__

=pod

=encoding UTF-8

=head1 NAME

dpath - cmdline tool around Data::DPath

=head1 SYNOPSIS

Query some input data with a DPath to stdout.

Default data format (in and out) is YAML, other formats can be
specified.

  $ dpath '//some/dpath' data.yaml

Use it as filter:

  $ dpath '//some/dpath' < data.yaml > result.yaml
  $ cat data.yaml | dpath '//some/dpath' > result.yaml
  $ cat data.yaml | dpath '//path1' | dpath '//path2' | dpath '//path3'

Specify that output is YAML(default), JSON or Data::Dumper:

  $ dpath -o yaml   '//some/dpath' data.yaml
  $ dpath -o json   '//some/dpath' data.yaml
  $ dpath -o dumper '//some/dpath' data.yaml

Input is JSON:

  $ dpath -i json '//some/dpath' data.json

Input is INI:

  $ dpath -i ini '//some/dpath' data.ini

Input is TAP:

  $ dpath -i tap '//some/dpath' data.tap
  $ perl t/some_test.t | dpath -i tap '//tests_planned'

Input is TAP::Archive:

  $ dpath -i taparchive '//tests_planned' tap.tgz

Input is JSON, Output is Data::Dumper:

  $ dpath -i json -o dumper '//some/dpath' data.json

=head2 Input formats

The following B<input formats> are allowed, with their according
modules used to convert the input into a data structure:

 yaml   - YAML::Any (default; not using YAML::Syck)
 json   - JSON
 xml    - XML::Simple
 ini    - Config::INI::Serializer
 dumper - Data::Dumper (including the leading $VAR1 variable assignment)
 tap    - TAP::DOM
 tap    - TAP::DOM::Archive

=head2 Output formats

The following B<output formats> are allowed:

 yaml   - YAML::Any (default; not using YAML::Syck)
 json   - JSON
 xml    - XML::Simple
 ini    - Config::INI::Serializer
 dumper - Data::Dumper (including the leading $VAR1 variable assignment)
 flat   - pragmatic flat output for typical unixish cmdline usage

=head2 The 'flat' output format

The C<flat> output format is meant to support typical unixish command
line uses. It is not a strong serialization format but works well for
simple values nested max 2 levels.

Output looks like this:

=head3 Plain values

 Affe
 Tiger
 Birne

=head3 Outer hashes

One outer key per line, key at the beginning of line with a colon
(C<:>), inner values separated by semicolon C<;>:

=head4 inner scalars:

 coolness:big
 size:average
 Eric:The flat one from the 90s

=head4 inner hashes:

Tuples of C<key=value> separated by semicolon C<;>:

 Affe:coolness=big;size=average
 Zomtec:coolness=bit anachronistic;size=average

=head4 inner arrays:

Values separated by semicolon C<;>:

 Birne:bissel;hinterher;manchmal

=head3 Outer arrays

One entry per line, entries separated by semicolon C<;>:

=head4 inner scalars:

 single report string
 foo
 bar
 baz

=head4 inner hashes:

Tuples of C<key=value> separated by semicolon C<;>:

 Affe=amazing moves in the jungle;Zomtec=slow talking speed;Birne=unexpected in many respects

=head4 inner arrays:

Entries separated by semicolon C<;>:

 line A-1;line A-2;line A-3;line A-4;line A-5
 line B-1;line B-2;line B-3;line B-4
 line C-1;line C-2;line C-3

=head3 Additional markup for arrays:

 --fb            ... use [brackets] around outer arrays
 --fi            ... prefix outer array lines with index
 --separator=;   ... use given separator between array entries (defaults to ";")

Such additional markup lets outer arrays look like this:

 0:[line A-1;line A-2;line A-3;line A-4;line A-5]
 1:[line B-1;line B-2;line B-3;line B-4]
 2:[line C-1;line C-2;line C-3]
 3:[Affe=amazing moves in the jungle;Zomtec=slow talking speed;Birne=unexpected in many respects]
 4:[single report string]

=head1 SEE ALSO

For more information about the DPath syntax, see

 perldoc Data::DPath

=head1 AUTHOR

Steffen Schwigon <ss5@renormalist.net>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2024 by Steffen Schwigon.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
