ARGV-Struct

 view release on metacpan or  search on metacpan

lib/ARGV/Struct.pm  view on Meta::CPAN

package ARGV::Struct;
  use Moo;
  use Types::Standard qw/ArrayRef/;

  our $VERSION = '0.06';

  has argv => (
    is => 'ro', 
    isa => ArrayRef, 
    default => sub { [ @ARGV ] }, 
  );

  sub argcount {
    my $self = shift;
    return scalar(@{ $self->argv });
  }

  sub arg {
    my ($self, $i) = @_;
    return $self->argv->[ $i ];
  }

  sub args {
    my $self = shift;
    return @{ $self->argv };
  }

  sub parse {
    my ($self) = @_;
    my $substruct = $self->_parse_argv($self->args);
    die "Trailing values after structure" if (scalar(@{ $substruct->{ leftover } }));
    return $substruct->{ struct };
  }

  sub _parse_list {
    my ($self, @args) = @_;
    my $list = [];
    while (my $token = shift @args) {
      if ($token eq '[') {
        my $substruct = $self->_parse_list(@args);
        push @$list, $substruct->{ struct };
        @args = @{ $substruct->{ leftover } };
      } elsif($token eq '{') {
        my $substruct = $self->_parse_hash(@args);
        push @$list, $substruct->{ struct };
        @args = @{ $substruct->{ leftover } };
      } elsif ($token eq ']') {
        return { struct => $list, leftover => [ @args ] };
      } else {
        push @$list, $token;
      }
    }
    die "Unclosed list";
  };

  sub _parse_hash {
    my ($self, @args) = @_;
    my $hash = {};
    while (my $token = shift @args) {
      if ($token eq '}') {
        return { struct => $hash, leftover => [ @args ] };
      }

      my ($k, $v) = ($token, shift @args);

      substr($k,-1,1) = '' if (substr($k,-1,1) eq ':');
      die "Repeated $k in hash" if (exists $hash->{ $k });

      die "Key $k doesn't have a value" if (not defined $v);
      if ($v eq '{'){
        my $substruct = $self->_parse_hash(@args);
        $hash->{ $k } = $substruct->{ struct };
        @args = @{ $substruct->{ leftover } };
      } elsif ($v eq '[') {
        my $substruct = $self->_parse_list(@args);
        $hash->{ $k } = $substruct->{ struct };
        @args = @{ $substruct->{ leftover } };
      } else {
        $hash->{ $k } = $v;
      }
    }
    die "Unclosed hash";
  }

  sub _parse_argv {
    my ($self, @args) = @_;

    my $token = shift @args;

    if ($token eq '[') {
      return $self->_parse_list(@args);
    } elsif($token eq '{') {
      return $self->_parse_hash(@args);
    } else {
      die "Expecting { or [";
    }
  }

1;
#################### main pod documentation begin ###################

=head1 NAME

ARGV::Struct - Parse complex data structures passed in ARGV

=head1 SYNOPSIS

  use ARGV::Struct;
  my $struct = ARGV::Struct->new->parse;

=head1 DESCRIPTION

Have you ever felt that you need something different than Getopt?

Are you tired of shoehorning Getopt style arguments into your commandline scripts?

Are you trying to express complex datastructures via command line?

then ARGV::Struct is for you!

It's designed so the users of your command line utilities won't hate you when things
get complex.

=head1 THE PAIN

I've had to use some command-line utilities that had to do creative stuff to transmit
deeply nested arguments, or datastructure-like information. Here are some strategies that
I've found over time: 

=head2 Complex arguments codified as JSON

JSON is horrible for the command line because you have to escape the quotes. It's a nightmare.

  command --complex_arg "{\"key1\":\"value1\",\"key2\":\"value2\"}"

=head2 Arguments encoded via some custom scheme

These schemes fail when you have to make values complex (lists, or other key/values)

  command --complex_arg key1,value1:key2,value2

=head2 Repeating Getopt arguments

Getopt friendly, but too verbose



( run in 0.720 second using v1.01-cache-2.11-cpan-13bb782fe5a )