diff options
| author | Paul Buetow <paul@buetow.org> | 2015-01-02 14:00:56 +0100 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2015-01-02 14:00:56 +0100 |
| commit | 4f27f3ea59baa6cfca0ac1df96b1dfedbd83706c (patch) | |
| tree | 79754e3c49216f80cb3ad5579851fca0bf1a31c9 /lib/MON | |
initial
Diffstat (limited to 'lib/MON')
| -rw-r--r-- | lib/MON/Cache.pm | 55 | ||||
| -rw-r--r-- | lib/MON/Config.pm | 176 | ||||
| -rw-r--r-- | lib/MON/Display.pm | 360 | ||||
| -rw-r--r-- | lib/MON/Filter.pm | 166 | ||||
| -rw-r--r-- | lib/MON/JSON.pm | 51 | ||||
| -rw-r--r-- | lib/MON/Options.pm | 163 | ||||
| -rw-r--r-- | lib/MON/Query.pm | 557 | ||||
| -rw-r--r-- | lib/MON/QueryBase.pm | 232 | ||||
| -rw-r--r-- | lib/MON/RESTlos.pm | 471 | ||||
| -rw-r--r-- | lib/MON/Syslogger.pm | 77 | ||||
| -rw-r--r-- | lib/MON/Utils.pm | 80 |
11 files changed, 2388 insertions, 0 deletions
diff --git a/lib/MON/Cache.pm b/lib/MON/Cache.pm new file mode 100644 index 0000000..21b59f5 --- /dev/null +++ b/lib/MON/Cache.pm @@ -0,0 +1,55 @@ +package MON::Cache; + +use strict; +use warnings; +use v5.10; +use autodie; + +use Data::Dumper; + +use MON::Display; +use MON::Config; +use MON::Utils; + +our @ISA = ('MON::Display'); + +sub new { + my ( $class, %opts ) = @_; + + my $self = bless \%opts, $class; + + $self->init(); + + return $self; +} + +sub init { + my ($self) = @_; + + $self->clear(); + + return undef; +} + +sub clear { + my ($self) = @_; + + $self->{cache} = {}; + + return undef; +} + +sub magic { + my ( $self, $key, $sub ) = @_; + + my $cache = $self->{cache}; + + if ( exists $cache->{$key} ) { + $self->verbose("Delivering '$key' from cache"); + return $cache->{$key}; + } + + return $cache->{$key} = $sub->(); +} + +1; diff --git a/lib/MON/Config.pm b/lib/MON/Config.pm new file mode 100644 index 0000000..dc83911 --- /dev/null +++ b/lib/MON/Config.pm @@ -0,0 +1,176 @@ +package MON::Config; + +use strict; +use warnings; +use v5.10; +use autodie; + +use IO::File; +use Data::Dumper; + +use MON::Display; +use MON::Utils; + +#use MON::Options; + +use MIME::Base64 qw( decode_base64 ); + +our @ISA = ('MON::Display'); + +sub new { + my ( $class, %opts ) = @_; + + my $self = bless \%opts, $class; + my $options = $self->{options}; + + $options->store_first($self); + + $self->SUPER::init(%opts); + + for ( @{ $options->{unknown} } ) { + $self->error("Unknown option: $_"); + } + + if ( $self->{'config'} ne '' ) { + $self->read_config( $self->{'config'} ); + + } + elsif ( exists $ENV{MON_CONFIG} ) { + $self->read_config( $ENV{MON_CONFIG} ); + + } + else { + $self->read_config('/etc/mon.conf'); + $self->read_config($_) for sort glob("/etc/mon.d/*.conf"); + + $self->read_config("$ENV{HOME}/.mon.conf"); + $self->read_config($_) for sort glob("$ENV{HOME}/.mon.d/*.conf"); + } + + $options->store_after($self); + + unless ( exists $self->{config_was_read} ) { + $self->verbose("No config file found, but this might be OK"); + } + + $self->_set_defaults(); + + return $self; +} + +sub _set_defaults { + my ($self) = @_; + + my $set_default = sub { + my ( $key, $val ) = @_; + + unless ( exists $self->{$key} ) { + $self->{$key} = $val; + $self->verbose( + "Since $key is not specified setting its default value to $val"); + } + }; + + $set_default->( 'backups.dir' => "$ENV{HOME}/.mon" ); + $set_default->( 'backups.disable' => 1 ); + $set_default->( 'backups.keep.days' => 7 ); + $set_default->( 'restlos.api.port' => '443' ); + $set_default->( 'restlos.api.protocol' => 'https' ); + $set_default->( 'restlos.auth.realm' => 'Login Required' ); + $set_default->( 'restlos.auth.username' => $ENV{USER} ); +} + +sub read_config { + my ( $self, $config_file ) = @_; + + return undef if not defined $config_file or not -f $config_file; + + my $fh = IO::File->new( $config_file, 'r' ); + $self->error("Could not open file $config_file") unless defined $fh; + + $self->verbose("Reading config $config_file"); + + while ( my $line = $fh->getline() ) { + next if $line =~ /^#/; + + # Ignore comments + $line =~ s/(.*);.*/$1/; + + # Parse only matching lines + if ( $line =~ /^(.*):(.*)/ ) { + my ( $key, $val ) = ( lc trim $1, trim $2); + $self->verbose("Reading conf value $key"); + + # Handle ~ + $val =~ s/~/$ENV{HOME}/g; + $self->set( $key, $val ); + } + } + + $fh->close(); + $self->{config_was_read} = 1; + + return undef; +} + +sub get { + my ( $self, $key ) = @_; + $key = lc $key; + + $self->{$key} //= do { + my $key = uc $key; + $key =~ s/\./_/g; + + exists $ENV{$key} ? $ENV{$key} : undef; + }; + + if ( not exists $self->{$key} + or not defined $self->{$key} + or $self->{$key} eq '' ) + { + $self->error("$key not configured"); + } + + return $self->{$key}; +} + +sub get_maybe_encoded { + my ( $self, $key ) = @_; + + return $self->get($key) if exists $self->{$key}; + + $self->error("$key or $key.enc not configured") + unless exists $self->{"$key.enc"}; + + my $enc = $self->get("$key.enc"); + + return decode_base64($enc); +} + +sub bool { + my ( $self, $key ) = @_; + + my $val = $self->get($key); + + return $val != 0; +} + +sub array { + my ( $self, $key ) = @_; + + my $val = $self->get($key); + + return map { trim $_ } split ',', $val; +} + +sub set { + my ( $self, $key, $val ) = @_; + $key = lc $key; + + $self->verbose("$key already configured, overwriting it with its new value") + if exists $self->{$key}; + + return $self->{$key} = $val; +} + +1; diff --git a/lib/MON/Display.pm b/lib/MON/Display.pm new file mode 100644 index 0000000..9bf8115 --- /dev/null +++ b/lib/MON/Display.pm @@ -0,0 +1,360 @@ +package MON::Display; + +use strict; +use warnings; +use v5.10; +use autodie; + +use Data::Dumper; +use Term::ANSIColor; + +use MON::Config; +use MON::JSON; +use MON::Utils; + +our $VERBOSE = 0; +our $DEBUG = 0; +our $COLORFUL = 0; +our $QUIET = 0; +our $LOGGER = undef; +our $INTERACTIVE = undef; + +sub init { + my ( $self, %opts ) = @_; + + $VERBOSE = $self->{'verbose'} == 1; + $DEBUG = $self->{'debug'} == 1; + $QUIET = $self->{'quiet'} == 1; + $LOGGER = $opts{logger}; + $INTERACTIVE = $opts{interactive}; + + $self->{logglevel} = 'info'; + + if ( $self->{'nocolor'} == 1 ) { + $COLORFUL = 0; + } + else { + $COLORFUL = $ENV{MON_COLORFUL} // 1; + } + + $VERBOSE = $DEBUG = $COLORFUL = 0 if $QUIET == 1; + + return undef; +} + +sub is_verbose { + my ($self) = @_; + + return $VERBOSE == 1; +} + +sub is_debug { + my ($self) = @_; + + return $DEBUG == 1; +} + +sub is_quiet { + my ($self) = @_; + + return $QUIET == 1; +} + +sub _display { + my ( $self, $msg, $fh, $level ) = @_; + + return undef unless defined $msg; + + $LOGGER->logg( $self->{logglevel}, $msg ) if defined $LOGGER; + + return undef if $QUIET; + + $fh = *STDERR unless defined $fh; + + print $fh $msg; + + return undef; +} + +sub info_no_nl { + my ( $self, $msg ) = @_; + + print STDERR color 'bold blue' if $COLORFUL; + $self->_display($msg); + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub out_json { + my ( $self, $out ) = @_; + + return undef unless defined $out; + my $config = $self->{config}; + + local $, = "\n"; + + my $json = MON::JSON->new()->decode($out); + my $num_results = ref $json eq 'ARRAY' ? @$json : undef; + + # Don't _display meta aka custom variables unless -m or --meta is specified + unless ( $config->{'meta'} ) { + if ( ref $json eq 'ARRAY' ) { + @$json = map { + if ( ref $_ eq 'HASH' ) + { + my $h = $_; + delete $h->{$_} for grep /^_/, keys %$h; + $h; + } + else { + $_; + } + } @$json; + } + } + + # Sort and pretty print all the JSON pretty pretty please + unless ( defined $config->{outfile} ) { + print MON::JSON->new()->encode_canonical($json) unless $QUIET; + } + else { + my $outfile = $config->{outfile}; + print $outfile MON::JSON->new()->encode_canonical($json); + print STDERR color 'bold green' if $COLORFUL; + $self->_display("Wrote JSON to file\n"); + print STDERR color 'reset' if $COLORFUL; + } + + $LOGGER->logg( 'info', JSON->new()->encode($json) ) if defined $LOGGER; + + print STDERR color 'bold green' if $COLORFUL; + $self->_display("Found $num_results entries\n") if defined $num_results; + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub out_format { + my ( $self, $format, $out ) = @_; + + return undef unless defined $out; + + my $config = $self->{config}; + my $options = $self->{options}; + my $json = MON::JSON->new()->decode($out); + my $num_results = ref $json eq 'ARRAY' ? @$json : undef; + + $self->error("Expected an JSON Array") if ref $json ne 'ARRAY'; + + my @vars1 = $format =~ /\$(\w+)/g; + my @vars2 = $format =~ /\$\{(\w+)\}/g; + my @vars3 = $format =~ /\@(\w+)/g; + my @vars4 = $format =~ /\@\{(\w+)\}/g; + + my %vars; + $vars{$_} = '' for @vars1, @vars2, @vars3, @vars4; + my @out; + my %empty; + + for my $obj (@$json) { + my %obj_vars = %vars; + my $obj_format = $format; + + for my $var ( keys %obj_vars ) { + if ( $var eq 'HOSTNAME' ) { + my $val = exists $obj->{host_name} ? $obj->{host_name} : ''; + + if ( $val eq '' ) { + $empty{$var} = 1; + } + else { + $val =~ s/\..*//; + } + + $obj_format =~ s/\$$var/$val/g; + + } + else { + my $val = exists $obj->{$var} ? $obj->{$var} : ''; + $empty{$var} = 1 if $val eq ''; + + $obj_format =~ s/\$$var/$val/g; + $obj_format =~ s/\$\{$var\}/$val/g; + $obj_format =~ s/\@$var/$val/g; + $obj_format =~ s/\@\{$var\}/$val/g; + } + } + + push @out, $obj_format if $obj_format =~ /^.*\w+.*$/; + } + + if (@out) { + + if ( $config->{'unique'} ) { + my %lines; + @out = grep { exists $lines{$_} ? 0 : ( $lines{$_} = 1 ) } sort @out; + $num_results = @out; + } + else { + @out = sort @out; + } + + if ( $QUIET == 0 ) { + local $, = "\n"; + print @out; + say ''; + } + elsif ( defined $LOGGER ) { + $LOGGER->logg( 'info', $_ ) for @out; + } + } + + $self->warning( "Some objects dont have such a field or have empty strings: " + . join( ' ', sort keys %empty ) ) + if keys %empty; + + print STDERR color 'bold green' if $COLORFUL; + $self->_display("Found $num_results entries\n") if defined $num_results; + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub info { + my ( $self, $msg ) = @_; + + my $str = "$msg\n"; + $self->{logglevel} = 'info'; + + print STDERR color 'bold blue' if $COLORFUL; + $self->_display($str); + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub nl { + my ($self) = @_; + + $self->_display("\n"); + + return undef; +} + +sub error { + my ( $self, $msg ) = @_; + + $self->error_no_exit($msg); + + exit 3 unless $INTERACTIVE; +} + +sub error_no_exit { + my ( $self, $msg ) = @_; + + $self->{logglevel} = 'warning'; + print STDERR color 'bold red' if $COLORFUL; + $self->_display( "! ERROR: $msg\n", *STDERR ); + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub possible { + my ( $self, @params ) = @_; + + my $config = $self->{config}; + my $options = $self->{options}; + + push @params, $options->get_keys() + if $config->{'help'}; + + my $msg = ''; + + if (@params) { + for ( grep !/^V_ALIAS/, @params ) { + if ( ref $_ eq 'ARRAY' ) { + $msg .= join "\n", @$_; + $msg .= "\n"; + } + else { + $msg .= "$_\n"; + } + } + } + else { + $msg .= "\n"; + } + + $self->{logglevel} = 'info'; + $self->_display($msg); + + exit 0 unless $INTERACTIVE; +} + +sub warning { + my ( $self, $msg ) = @_; + + my $str = "! $msg\n"; + + print STDERR color 'red' if $COLORFUL; + $self->_display( $str, *STDERR ); + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub verbose { + my ( $self, @msgs ) = @_; + + print STDERR color 'cyan' if $COLORFUL; + $self->{logglevel} = 'info'; + + if ( $self->is_verbose() ) { + for my $msg (@msgs) { + if ( $self->is_debug() ) { + my @caller = caller; + $self->_display("@caller: $msg\n"); + } + else { + $self->_display("$msg\n"); + } + } + } + + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub dump { + my ( $self, $msg ) = @_; + + $self->{logglevel} = 'warning'; + $self->_display( Dumper $msg ); + + return undef; +} + +sub debug { + my ( $self, @msgs ) = @_; + + my @caller = caller; + + if ( $self->is_debug() ) { + for my $msg (@msgs) { + $msg = Dumper $msg if ref $msg ne ''; + + my $str = "@caller: $msg\n"; + + $self->{logglevel} = 'debug'; + $self->_display($str); + } + } + + return undef; +} + +1; + diff --git a/lib/MON/Filter.pm b/lib/MON/Filter.pm new file mode 100644 index 0000000..d16d1c5 --- /dev/null +++ b/lib/MON/Filter.pm @@ -0,0 +1,166 @@ +package MON::Filter; + +use strict; +use warnings; +use v5.10; +use autodie; + +use Data::Dumper; + +use MON::Display; +use MON::Config; +use MON::Utils; + +our @ISA = ('MON::Display'); + +sub new { + my ( $class, %opts ) = @_; + + my $self = bless \%opts, $class; + + $self->init(); + + return $self; +} + +sub init { + my ($self) = @_; + + $self->{query_string} = ''; + $self->{filters} = {}; + $self->{num_filters} = 0; + $self->{is_computed} = 0; + $self->{or} = []; + + return undef; +} + +# Create filters with params +sub compute { + my ( $self, $params ) = @_; + + $self->debug( 'Computing filter using', $params ); + return undef if $self->{is_computed}; + + my %likes; + + if ( defined $params and ref $params eq 'ARRAY' ) { + while (@$params) { + my $op_token = pop @$params; + given ($op_token) { + when (/^OP_LIKE$/) { + my $arg2 = pop @$params; + my $arg1 = pop @$params; + + if ( exists $likes{$arg1} ) { + $self->error( +"Can not run multiple 'like's on '$arg1', since it is used for the API query_string" + ); + } + else { + $likes{$arg1} = "$arg1=$arg2"; + } + + } + when (/^OP_/) { + $self->{filters}{$_} = [] unless exists $self->{filters}{$_}; + my $arg2 = pop @$params; + my $arg1 = pop @$params; + push @{ $self->{filters}{$_} }, [ $arg1, $arg2 ]; + $self->{num_filters}++; + } + default { + $self->error("Inernal error: Operator expected instead of $_"); + } + } + } + } + + $self->{query_string} = '?' . join( '&', values %likes ); + $self->{is_computed} = 1; + + $self->debug( 'Computed filter:', $self->{filters} ); + $self->verbose( "Computed query string is: " . $self->{query_string} ); + + return undef; +} + +sub filter { + my ( $self, $objects ) = @_; + + my $config = $self->{config}; + my $json = $self->{json}; + + return $objects unless $self->{num_filters}; + + my $num = sub { + my $str = shift; + $str =~ s/\D//g; + $str = 0 if $str eq ''; + return int $str; + }; + + while ( my ( $op, $vals ) = each %{ $self->{filters} } ) { + for my $val (@$vals) { + my ( $key, $val ) = @$val; + + @$objects = grep { + my $object = $_; + + if ( exists $object->{$key} ) { + if ( $op eq 'OP_MATCHES' and $object->{$key} =~ /$val/ ) { + 1; + + } + elsif ( $op eq 'OP_NMATCHES' and $object->{$key} !~ /$val/ ) { + 1; + + } + elsif ( $op eq 'OP_EQ' and $object->{$key} eq $val ) { + 1; + + } + elsif ( $op eq 'OP_NE' and $object->{$key} ne $val ) { + 1; + + } + elsif ( $op eq 'OP_LT' + and $num->( $object->{$key} ) < $num->($val) ) + { + 1; + + } + elsif ( $op eq 'OP_LE' + and $num->( $object->{$key} ) <= $num->($val) ) + { + 1; + + } + elsif ( $op eq 'OP_GT' + and $num->( $object->{$key} ) > $num->($val) ) + { + 1; + + } + elsif ( $op eq 'OP_GE' + and $num->( $object->{$key} ) >= $num->($val) ) + { + 1; + + } + else { + 0; + } + } + else { + 0; + } + } @$objects; + } + } + + return $objects; +} + +1; + diff --git a/lib/MON/JSON.pm b/lib/MON/JSON.pm new file mode 100644 index 0000000..e12b1ce --- /dev/null +++ b/lib/MON/JSON.pm @@ -0,0 +1,51 @@ +package MON::JSON; + +use strict; +use warnings; +use v5.10; +use autodie; + +use JSON; + +use MON::Display; +use MON::Utils; + +our @ISA = ('MON::Display'); + +our $JSON_XS = JSON::XS->new(); + +sub new { + my ( $class, %opts ) = @_; + + my $self = bless \%opts, $class; + + $self->init(); + + return $self; +} + +sub init { + my ($self) = @_; + + return undef; +} + +sub decode { + my ( $self, $json ) = @_; + + return $JSON_XS->allow_nonref()->decode($json); +} + +sub encode { + my ( $self, $vals ) = @_; + + return $JSON_XS->pretty()->encode($vals); +} + +sub encode_canonical { + my ( $self, $vals ) = @_; + + return $JSON_XS->canonical()->pretty()->encode($vals); +} + +1; diff --git a/lib/MON/Options.pm b/lib/MON/Options.pm new file mode 100644 index 0000000..b798f56 --- /dev/null +++ b/lib/MON/Options.pm @@ -0,0 +1,163 @@ +package MON::Options; + +use strict; +use warnings; +use v5.10; +use autodie; + +use Data::Dumper; +use Scalar::Util qw(looks_like_number); + +use MON::Utils; + +sub new { + my ( $class, %opts ) = @_; + + my $self = bless \%opts, $class; + + $self->init(); + $self->parse(); + + return $self; +} + +sub init { + my ($self) = @_; + + my %opts = ( + opts => { + config => '', + debug => 0, + dry => 0, + help => 0, + interactive => 0, + meta => 0, + nocolor => 0, + quiet => 0, + syslog => 0, + unique => 0, + verbose => 0, + version => 0, + errfile => '', + }, + opts_short => { + c => 'config', + D => 'debug', + d => 'dry', + i => 'interactive', + h => 'help', + m => 'meta', + n => 'nocolor', + q => 'quiet', + s => 'syslog', + u => 'unique', + v => 'verbose', + V => 'version', + R => 'errfile', + }, + unknown => [], + ); + + $self->{$_} = $opts{$_} for keys %opts; + + return undef; +} + +sub parse { + my ($self) = @_; + + my $opts_passed = $self->{opts_passed}; + + for my $opt (@$opts_passed) { + my ( $k, $v ) = split /=/, $opt; + + # Longopt + if ( $k =~ s/^--// && isin $k, keys %{ $self->{opts} } ) { + if ( defined $v ) { + $self->{opts}{$k} = $v; + } + else { + $self->{opts}{$k} = 1; + } + } + + # Shortopt + elsif ( $k =~ s/^-// && isin $k, keys %{ $self->{opts_short} } ) { + if ( defined $v ) { + $self->{opts}{ $self->{opts_short}{$k} } = $v; + } + else { + $self->{opts}{ $self->{opts_short}{$k} } = 1; + } + + } + elsif ( $k !~ /\./ ) { + + # If key is not separated by dot, it is unknown + push @{ $self->{unknown} }, $opt; + + } + else { + + # Otherise it might overwrite a value of mon.conf + $self->{opts}{$k} = $v; + } + } + + # Help implies dry mode + $self->{opts}{dry} = 1 if $self->{opts}{help}; + + # Debug implies verbose mode + $self->{opts}{verbose} = 1 if $self->{opts}{debug}; + + return undef; +} + +sub get_keys { + my ($self) = @_; + my @keys; + + while ( my ( $k, $v ) = each %{ $self->{opts_short} } ) { + if ( looks_like_number( $self->{opts}{$v} ) ) { + push @keys, "--$v -$k"; + } + else { + push @keys, "--$v=VAL -$k=VAL"; + } + } + + return @keys; +} + +sub store { + my ( $self, $config ) = @_; + + $self->store_first($config); + $self->store_after($config); + + return undef; +} + +# Only store values which are not separated by dots +sub store_first { + my ( $self, $config ) = @_; + + for ( grep !/\./, keys %{ $self->{opts} } ) { + $config->{$_} = $self->{opts}{$_}; + } + + return undef; +} + +# Only store values which are separated by dots +sub store_after { + my ( $self, $config ) = @_; + + for ( grep /\./, keys %{ $self->{opts} } ) { + $config->{$_} = $self->{opts}{$_}; + } + + return undef; +} + +1; 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 => { |
