summaryrefslogtreecommitdiff
path: root/lib/MON/Query.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/MON/Query.pm')
-rw-r--r--lib/MON/Query.pm557
1 files changed, 557 insertions, 0 deletions
diff --git a/lib/MON/Query.pm b/lib/MON/Query.pm
new file mode 100644
index 0000000..d7e223b
--- /dev/null
+++ b/lib/MON/Query.pm
@@ -0,0 +1,557 @@
+package MON::Query;
+
+use strict;
+use warnings;
+use v5.10;
+
+use Data::Dumper;
+
+use MON::Display;
+use MON::Config;
+use MON::Utils;
+use MON::QueryBase;
+
+our @ISA = ('MON::QueryBase');
+
+sub new {
+ my ( $class, %opts ) = @_;
+
+ my $self = bless \%opts, $class;
+
+ $self->init();
+
+ return $self;
+}
+
+sub init {
+ my ($self) = @_;
+
+ $self->{querystack} = [];
+ $self->{args} = [ map { s/^V_/:V_/; $_ } @{ $self->{args} } ];
+
+ return undef;
+}
+
+sub tree {
+ my ($self) = @_;
+
+ my $api = $self->{api};
+ my $paths = $api->get_possible_paths();
+
+ my ( $s, $r ) = ( $self, $api );
+
+ my $arr = sub {
+ my ( $keys, $vals ) = @_;
+ map { $_ => shift @$vals } @$keys;
+ };
+
+# _ => By default to run anonymous sub if no other key is specified in command line
+# __ => Always to run anonymous sub in the beginning of the current recursion
+# ___ => Always to run anonymous sub before next recursion
+# __DO => Process recursion right away, only do __ if exists
+# V_FOO => Declare variable FOO
+
+ my $where = {
+ _ => sub {
+ my $d = shift;
+ $s->possible( $r->get_path_params( $d->{V_PATH} ) );
+ },
+ V_KEY => {
+ __ => sub {
+ my $d = shift;
+ $s->check_has( $d->{V_KEY}, $r->get_path_params( $d->{V_PATH} ) );
+ },
+ like => {
+ V_VAL => {
+ _ => sub {
+ my $d = shift;
+ my $path = $d->{V_PATH};
+ $s->out_json( $d->{where_action}($path) );
+ },
+ },
+ ___ => sub { $s->push_querystack('OP_LIKE') }
+ },
+ matches => {
+ V_VAL => {
+ _ => sub {
+ my $d = shift;
+ my $path = $d->{V_PATH};
+ $s->out_json( $d->{where_action}($path) );
+ },
+ },
+ ___ => sub { $s->push_querystack('OP_MATCHES') }
+ },
+ nmatches => {
+ V_VAL => {
+ _ => sub {
+ my $d = shift;
+ my $path = $d->{V_PATH};
+ $s->out_json( $d->{where_action}($path) );
+ },
+ },
+ ___ => sub { $s->push_querystack('OP_NMATCHES') }
+ },
+ eq => {
+ V_VAL => {
+ _ => sub {
+ my $d = shift;
+ my $path = $d->{V_PATH};
+ $s->out_json( $d->{where_action}($path) );
+ },
+ },
+ ___ => sub { $s->push_querystack('OP_EQ') }
+ },
+ ne => {
+ V_VAL => {
+ _ => sub {
+ my $d = shift;
+ my $path = $d->{V_PATH};
+ $s->out_json( $d->{where_action}($path) );
+ },
+ },
+ ___ => sub { $s->push_querystack('OP_NE') }
+ },
+ lt => {
+ V_VAL => {
+ _ => sub {
+ my $d = shift;
+ my $path = $d->{V_PATH};
+ $s->out_json( $d->{where_action}($path) );
+ },
+ },
+ ___ => sub { $s->push_querystack('OP_LT') }
+ },
+ le => {
+ V_VAL => {
+ _ => sub {
+ my $d = shift;
+ my $path = $d->{V_PATH};
+ $s->out_json( $d->{where_action}($path) );
+ },
+ },
+ ___ => sub { $s->push_querystack('OP_LE') }
+ },
+ gt => {
+ V_VAL => {
+ _ => sub {
+ my $d = shift;
+ my $path = $d->{V_PATH};
+ $s->out_json( $d->{where_action}($path) );
+ },
+ },
+ ___ => sub { $s->push_querystack('OP_GT') }
+ },
+ ge => {
+ V_VAL => {
+ _ => sub {
+ my $d = shift;
+ my $path = $d->{V_PATH};
+ $s->out_json( $d->{where_action}($path) );
+ },
+ },
+ ___ => sub { $s->push_querystack('OP_GE') }
+ },
+ },
+ };
+
+ for my $op ( sort qw(like matches nmatches eq ne lt le gt ge) ) {
+ $where->{V_KEY}{$op}{V_VAL}{and}{__DO} = $where;
+ $where->{V_KEY}{$op}{V_VAL}{'V_ALIAS:a'} = $where->{V_KEY}{$op}{V_VAL}{and};
+ }
+
+ $where->{V_KEY}{'V_ALIAS:l'} = $where->{V_KEY}{like};
+ $where->{V_KEY}{'V_ALIAS:~'} = $where->{V_KEY}{like};
+ $where->{V_KEY}{'V_ALIAS:=='} = $where->{V_KEY}{eq};
+ $where->{V_KEY}{'V_ALIAS:!='} = $where->{V_KEY}{ne};
+ $where->{V_KEY}{'V_ALIAS:=~'} = $where->{V_KEY}{matches};
+ $where->{V_KEY}{'V_ALIAS:!~'} = $where->{V_KEY}{nmatches};
+
+ my $set_where = {
+ _ => sub {
+ my $d = shift;
+ $s->possible( $r->get_path_params( $d->{V_PATH} ) );
+ },
+ V_SETKEY => {
+ '=' => {
+ V_SETVAL => {
+ __ => sub {
+ my $d = shift;
+ $d->{where_action} = $d->{set_action};
+ },
+ where => $where,
+ },
+ },
+ },
+ };
+ $set_where->{V_SETKEY}{'='}{V_SETVAL}{and} = $set_where;
+ $set_where->{V_SETKEY}{'='}{V_SETVAL}{'V_ALIAS::'} =
+ $set_where->{V_SETKEY}{'='}{V_SETVAL}{where};
+ $set_where->{V_SETKEY}{'='}{V_SETVAL}{'V_ALIAS:a'} =
+ $set_where->{V_SETKEY}{'='}{V_SETVAL}{and};
+
+ my $set = {
+ _ => sub {
+ my $d = shift;
+ $s->possible( $r->get_path_params( $d->{V_PATH} ) );
+ },
+ V_SETKEY => {
+ '=' => {
+ V_SETVAL => {
+ _ => sub {
+ my $d = shift;
+ my $path = $d->{V_PATH};
+
+ $s->out_json( $d->{set_action}($path) );
+ },
+ },
+ },
+ },
+ };
+ $set->{V_SETKEY}{'='}{V_SETVAL}{and} = $set;
+ $set->{V_SETKEY}{'='}{V_SETVAL}{'V_ALIAS:a'} =
+ $set->{V_SETKEY}{'='}{V_SETVAL}{and};
+
+ my $remove = {
+ _ => sub {
+ my $d = shift;
+ $s->possible( $r->get_path_params( $d->{V_PATH} ) );
+ },
+ V_REMOVEKEY => {
+ __ => sub {
+ my $d = shift;
+ $d->{where_action} = $d->{remove_action};
+ },
+ where => $where,
+ },
+ };
+ $remove->{V_REMOVEKEY}{and} = $remove;
+ $remove->{V_REMOVEKEY}{'V_ALIAS:a'} = $remove->{V_REMOVEKEY}{and};
+
+ my $tree = {
+ get => {
+ _ => sub { $s->possible(@$paths) },
+ V_PATH => {
+ __ => sub {
+ my $d = shift;
+ $s->check_has( $d->{V_PATH}, $paths );
+ $d->{where_action} = sub {
+ my ($path) = @_;
+ $r->fetch_path_json( $path, $s->get_querystack() );
+ };
+ },
+ _ => sub {
+ my $d = shift;
+ $s->out_json(
+ $r->fetch_path_json( $d->{V_PATH}, $s->get_querystack() ) );
+ },
+ where => $where,
+ },
+ },
+ getfmt => {
+ V_FORMAT => {
+ _ => sub { $s->possible(@$paths) },
+ V_PATH => {
+ __ => sub {
+ my $d = shift;
+ $s->check_has( $d->{V_PATH}, $paths );
+ $d->{where_action} = sub {
+ my ($path) = @_;
+ $s->out_format( $d->{V_FORMAT},
+ $r->fetch_path_json( $path, $s->get_querystack() ) );
+ };
+ },
+ _ => sub {
+ my $d = shift;
+ $s->out_format( $d->{V_FORMAT},
+ $r->fetch_path_json( $d->{V_PATH}, $s->get_querystack() ) );
+ },
+ where => $where,
+ },
+ },
+ },
+ edit => {
+ _ => sub { $s->possible(@$paths) },
+ V_PATH => {
+ __ => sub {
+ my $d = shift;
+ $s->check_has( $d->{V_PATH}, $paths );
+ $d->{where_action} = sub {
+ my ($path) = @_;
+ $s->edit_path_data( $path,
+ $r->fetch_path_json( $path, $s->get_querystack() ) );
+ };
+ },
+ _ => sub {
+ my $d = shift;
+ $s->edit_path_data( $d->{V_PATH},
+ $r->fetch_path_json( $d->{V_PATH} ) );
+ },
+ where => $where,
+ },
+ },
+ view => {
+ _ => sub { $s->possible(@$paths) },
+ V_PATH => {
+ __ => sub {
+ my $d = shift;
+ $s->check_has( $d->{V_PATH}, $paths );
+ $d->{where_action} = sub {
+ my ($path) = @_;
+ $s->view_data( $path,
+ $r->fetch_path_json( $path, $s->get_querystack() ) );
+ };
+ },
+ _ => sub {
+ my $d = shift;
+ $s->view_data( $d->{V_PATH}, $r->fetch_path_json( $d->{V_PATH} ) );
+ },
+ where => $where,
+ },
+ },
+ delete => {
+ _ => sub { $s->possible(@$paths) },
+ V_PATH => {
+ __ => sub {
+ my $d = shift;
+ $s->check_has( $d->{V_PATH}, $paths );
+ $d->{where_action} = sub {
+ my ($path) = @_;
+ $s->out_json( $r->delete_path_json( $path, $s->get_querystack() ) );
+ };
+ },
+ _ => sub {
+ my $d = shift;
+ $s->out_json( $r->fetch_path_json( $d->{V_PATH} ) );
+ },
+ where => $where,
+ },
+ },
+ update => {
+ _ => sub { $s->possible(@$paths) },
+ V_PATH => {
+ __ => sub {
+ my $d = shift;
+ $s->check_has( $d->{V_PATH}, $paths );
+ $d->{set_action} = sub {
+ my ($path) = @_;
+ my %set = $arr->( $d->{ALL_V_SETKEY}, $d->{ALL_V_SETVAL} );
+ $s->out_json(
+ $r->update_path_json( $path, $s->get_querystack(), \%set ) );
+ };
+ $d->{remove_action} = sub {
+ my ($path) = @_;
+ my $remove = $d->{ALL_V_REMOVEKEY};
+ $s->out_json(
+ $r->update_remove_path_json(
+ $path, $s->get_querystack(), $remove
+ )
+ );
+ };
+ },
+ set => $set_where,
+ 'delete' => $remove,
+ },
+ },
+ insert => {
+ _ => sub { $s->possible(@$paths) },
+ V_PATH => {
+ __ => sub {
+ my $d = shift;
+ $s->check_has( $d->{V_PATH}, $paths );
+ $d->{set_action} = sub {
+ my ($path) = @_;
+ my %set = $arr->( $d->{ALL_V_SETKEY}, $d->{ALL_V_SETVAL} );
+ $s->out_json( $self->insert_data( $path, \%set ) );
+ };
+ },
+ set => $set,
+ },
+ },
+ post => {
+ _ => sub { $s->possible(@$paths) },
+ V_PATH => {
+ __ => sub {
+ my $d = shift;
+ $s->check_has( $d->{V_PATH}, $paths );
+ },
+ _ => sub {
+ my $d = shift;
+ $s->send_data( $d->{V_PATH}, 'POST' );
+ },
+ from => {
+ V_FILEPATH => {
+ _ => sub {
+ my $d = shift;
+ $s->send_data( $d->{V_PATH}, 'POST', $d->{V_FILEPATH} );
+ }
+ }
+ }
+ },
+ },
+ put => {
+ _ => sub { $s->possible(@$paths) },
+ V_PATH => {
+ __ => sub {
+ my $d = shift;
+ $s->check_has( $d->{V_PATH}, $paths );
+ },
+ _ => sub {
+ my $d = shift;
+ $s->send_data( $d->{V_PATH}, 'PUT' );
+ },
+ from => {
+ V_FILEPATH => {
+ _ => sub {
+ my $d = shift;
+ $s->send_data( $d->{V_PATH}, 'PUT', $d->{V_FILEPATH} );
+ }
+ }
+ }
+ },
+ },
+ verify => sub { $s->verify() },
+ restart => sub { $s->restart() },
+ reload => sub { $s->restart() },
+ 'V_ALIAS:y' => sub { $s->verify() },
+ 'V_ALIAS:r' => sub { $s->restart() },
+ };
+
+ $tree->{delete}{V_PATH}{'V_ALIAS::'} = $tree->{delete}{V_PATH}{where};
+ $tree->{'V_ALIAS:d'} = $tree->{delete};
+
+ $tree->{edit}{V_PATH}{'V_ALIAS::'} = $tree->{edit}{V_PATH}{where};
+ $tree->{'V_ALIAS:e'} = $tree->{edit};
+
+ $tree->{get}{V_PATH}{'V_ALIAS::'} = $tree->{get}{V_PATH}{where};
+ $tree->{'V_ALIAS:g'} = $tree->{get};
+
+ $tree->{getfmt}{V_FORMAT}{V_PATH}{'V_ALIAS::'} =
+ $tree->{getfmt}{V_FORMAT}{V_PATH}{where};
+ $tree->{'V_ALIAS:f'} = $tree->{getfmt};
+
+ $tree->{insert}{V_PATH}{'V_ALIAS:s'} = $tree->{insert}{V_PATH}{set};
+ $tree->{'V_ALIAS:i'} = $tree->{insert};
+
+ $tree->{'V_ALIAS:p'} = $tree->{post};
+
+ $tree->{'V_ALIAS:t'} = $tree->{put};
+
+ $tree->{update}{V_PATH}{'V_ALIAS:d'} = $tree->{update}{V_PATH}{delete};
+ $tree->{update}{V_PATH}{'V_ALIAS:s'} = $tree->{update}{V_PATH}{set};
+ $tree->{'V_ALIAS:u'} = $tree->{update};
+
+ $tree->{view}{V_PATH}{'V_ALIAS::'} = $tree->{view}{V_PATH}{where};
+ $tree->{'V_ALIAS:v'} = $tree->{view};
+
+ $self->debug( 'Abstract syntax tree:', $tree );
+
+ return $tree;
+}
+
+sub parse {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+ my $args = $self->{args};
+
+ # Get > and < operators (only needed by interactive mode)
+ $config->{outfile} = $config->{infile} = undef;
+ if ( defined $args->[-2] ) {
+ given ( $args->[-2] ) {
+ when ('>') {
+ my ( undef, $file ) = splice @$args, -2, 2;
+ open $config->{outfile}, '>', $file or $self->warning("$file: $!");
+ }
+ when ('<') {
+ my ( undef, $file ) = splice @$args, -2, 2;
+ open $config->{infile}, '<', $file or $self->warning("$file: $!");
+ }
+ }
+ }
+
+ my $ret = $self->traverse( $args, $self->tree(), {} );
+
+ close $config->{infile} if defined $config->{infile};
+ close $config->{outfile} if defined $config->{outfile};
+
+ return $ret;
+}
+
+sub traverse {
+ my ( $self, $args, $tree, $data ) = @_;
+
+ $self->debug( 'Traversing args: ' . Dumper $args);
+ $self->debug( 'Traversing data: ' . Dumper $data);
+
+ if ( ref $tree eq 'CODE' ) {
+ $tree->($data);
+ return undef;
+ }
+
+ $tree->{__}->($data) if exists $tree->{__};
+
+ if ( exists $tree->{__DO} ) {
+ $self->traverse( $args, $tree->{__DO}, $data );
+ return undef;
+ }
+
+ my @possible = grep !/^__?$/, sort keys %$tree;
+ my $token = $possible[0];
+
+ unless (@$args) {
+ if ( exists $tree->{_} ) {
+ $tree->{_}->($data);
+ }
+ else {
+ $self->possible(@possible);
+ }
+ }
+ else {
+ my $arg = shift @$args;
+
+ if ( exists $tree->{$arg} ) {
+ $tree->{___}->($data) if exists $tree->{___};
+ $self->traverse( $args, $tree->{$arg}, $data );
+ }
+ elsif ( exists $tree->{"V_ALIAS:$arg"} ) {
+ $tree->{___}->($data) if exists $tree->{___};
+ $self->traverse( $args, $tree->{"V_ALIAS:$arg"}, $data );
+ }
+ elsif ( defined $token && $token =~ /^V_/ && $token !~ /^V_ALIAS:/ ) {
+ $data->{$token} = $arg;
+ $self->push_querystack($arg) if $token =~ /^V_(?:KEY|VAL)/;
+
+ unless ( exists $data->{"ALL_$token"} ) {
+ $data->{"ALL_$token"} = [$arg];
+ }
+ else {
+ push @{ $data->{"ALL_$token"} }, $arg;
+ }
+
+ $tree->{___}->($data) if exists $tree->{___};
+ $self->traverse( $args, $tree->{$token}, $data );
+ }
+ else {
+ $self->error("'$arg' unexpected here");
+ }
+ }
+
+ return undef;
+}
+
+sub push_querystack {
+ my ( $self, $token ) = @_;
+
+ $self->debug("Pushing token '$token' to querystack");
+ push @{ $self->{querystack} }, $token;
+
+ return undef;
+}
+
+sub get_querystack {
+ my ($self) = @_;
+
+ return $self->{querystack};
+}
+
+1;