[PATCH] MooseX::Getopt - options shouldn't be required if loaded from config

[email protected] (Ryan D Johnson)
Newsgroups perl.moose
Message-ID <[email protected]>
A command-line option shouldn't be required in the case that the given
"required" attribute has been loaded from a config file (using
MooseX::ConfigFromFile).

My solution involves running Getopt::Long twice -- once merely to look
for the configfile flag, then again with a full spec to parse the rest
of the options. I'm not thrilled with this, so please chime in with
better ideas. :-)

I'm attaching a patch with tests. 

If it's more convenient for you, the changes are also at
http://git.innerfence.com/?p=MooseX-Getopt.git;a=summary.

I've subscribed to this list, and I'm rdj on #moose.

/rdj
MooseX-Getopt-AttributesFromConfigNotRequired.diff (text/x-diff, 7.5 KB)
diff --git a/ChangeLog b/ChangeLog
index 8e14e30..444fca5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 Revision history for Perl extension MooseX-Getopt
 
+0.13 Unreleased
+    * MooseX::Getopt
+      - Commandline option shouldn't be required in the
+        case that the given "required" attribute has
+        been loaded from config (MooseX::ConfigFromFile)
+
 0.12 Fri. March 14, 2008
     ~~ updated copyright dates ~~
     
diff --git a/lib/MooseX/Getopt.pm b/lib/MooseX/Getopt.pm
index a8ce4a3..8c6385f 100644
--- a/lib/MooseX/Getopt.pm
+++ b/lib/MooseX/Getopt.pm
@@ -9,7 +9,7 @@ use MooseX::Getopt::Meta::Attribute::NoGetopt;
 use Getopt::Long (); # GLD uses it anyway, doesn't hurt
 use constant HAVE_GLD => not not eval { require Getopt::Long::Descriptive };
 
-our $VERSION   = '0.12';
+our $VERSION   = '0.12_01';
 our $AUTHORITY = 'cpan:STEVAN';
 
 has ARGV       => (is => 'rw', isa => 'ArrayRef', metaclass => "NoGetopt");
@@ -18,33 +18,32 @@ has extra_argv => (is => 'rw', isa => 'ArrayRef', metaclass => "NoGetopt");
 sub new_with_options {
     my ($class, @params) = @_;
 
-    my %processed = $class->_parse_argv( 
-        options => [ 
-            $class->_attrs_to_options( @params ) 
-        ] 
-    );
-
-    my $params = $processed{params};
-
+    my $config_from_file;
     if($class->meta->does_role('MooseX::ConfigFromFile')) {
+        local @ARGV = @ARGV;
+
         my $configfile;
+        my $opt_parser = Getopt::Long::Parser->new( config => [ qw( pass_through ) ] );
+        $opt_parser->getoptions( "configfile=s" => \$configfile );
 
-        if(defined $params->{configfile}) {
-            $configfile = $params->{configfile}
-        }
-        else {
+        if(!defined $configfile) {
             my $cfmeta = $class->meta->get_attribute('configfile');
             $configfile = $cfmeta->default if $cfmeta->has_default;
         }
 
         if(defined $configfile) {
-            %$params = (
-                %{$class->get_config_from_file($configfile)},
-                %$params,
-            );
+            $config_from_file = $class->get_config_from_file($configfile);
         }
     }
 
+    my %processed = $class->_parse_argv(
+        options => [
+            $class->_attrs_to_options( $config_from_file )
+        ]
+    );
+
+    my $params = $config_from_file ? { %$config_from_file, %{$processed{params}} } : $processed{params};
+
     $class->new(
         ARGV       => $processed{argv_copy},
         extra_argv => $processed{argv},
@@ -169,6 +168,7 @@ sub _get_cmd_flags_for_attr {
 
 sub _attrs_to_options {
     my $class = shift;
+    my $config_from_file = shift || {};
 
     my @options;
 
@@ -188,7 +188,7 @@ sub _attrs_to_options {
             name       => $flag,
             init_arg   => $attr->init_arg,
             opt_string => $opt_string,
-            required   => $attr->is_required && !$attr->has_default && !$attr->has_builder,
+            required   => $attr->is_required && !$attr->has_default && !$attr->has_builder && !exists $config_from_file->{$attr->name},
             ( ( $attr->has_default && ( $attr->is_default_a_coderef xor $attr->is_lazy ) ) ? ( default => $attr->default({}) ) : () ),
             ( $attr->has_documentation ? ( doc => $attr->documentation ) : () ),
         }
@@ -423,6 +423,10 @@ Stevan Little E<lt>[email protected]<gt>
 
 Brandon L. Black, E<lt>[email protected]<gt>
 
+=head1 CONTRIBUTORS
+
+Ryan D Johnson, E<lt>[email protected]<gt>
+
 =head1 COPYRIGHT AND LICENSE
 
 Copyright 2007-2008 by Infinity Interactive, Inc.
diff --git a/t/008_configfromfile.t b/t/008_configfromfile.t
new file mode 100644
index 0000000..affcd7e
--- /dev/null
+++ b/t/008_configfromfile.t
@@ -0,0 +1,164 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use Test::Exception;
+use Test::More;
+
+if ( !eval { require MooseX::ConfigFromFile } )
+{
+    plan skip_all => 'Test requires MooseX::ConfigFromFile';
+}
+else
+{
+    plan tests => 24;
+}
+
+{
+    package App;
+
+    use Moose;
+    with 'MooseX::Getopt';
+    with 'MooseX::ConfigFromFile';
+
+    has 'config_from_override' => (
+        is       => 'ro',
+        isa      => 'Bool',
+        default  => 0,
+    );
+
+    has 'optional_from_config' => (
+        is        => 'ro',
+        isa       => 'Str',
+        required  => 0,
+    );
+
+    has 'required_from_config' => (
+        is        => 'ro',
+        isa       => 'Str',
+        required  => 1,
+    );
+
+    has 'required_from_argv' => (
+        is        => 'ro',
+        isa       => 'Str',
+        required  => 1,
+    );
+
+    sub get_config_from_file
+    {
+        my ( $class, $file ) = @_;
+
+        my %config = (
+            required_from_config => 'from_config_1',
+            optional_from_config => 'from_config_2',
+        );
+
+        if ( $file ne '/notused/default' ) {
+            $config{config_from_override} = 1;
+        }
+
+        return \%config;
+    }
+}
+
+{
+    package App::DefaultConfigFile;
+
+    use Moose;
+    extends 'App';
+
+    has '+configfile' => (
+        default => '/notused/default',
+    );
+}
+
+# No config specified
+{
+    local @ARGV = qw( --required_from_argv 1 );
+
+    throws_ok { App->new_with_options } qr/Required option missing: required_from_config/;
+
+    {
+        my $app = App::DefaultConfigFile->new_with_options;
+        isa_ok( $app, 'App::DefaultConfigFile' );
+        app_ok( $app );
+
+        ok(  !$app->config_from_override,
+            '... config_from_override false as expected' );
+
+        is( $app->configfile, '/notused/default',
+            '... configfile is /notused/default as expected' );
+    }
+}
+
+# Config specified
+{
+    local @ARGV = qw( --configfile /notused --required_from_argv 1 );
+
+    {
+        my $app = App->new_with_options;
+        isa_ok( $app, 'App' );
+        app_ok( $app );
+    }
+
+    {
+        my $app = App::DefaultConfigFile->new_with_options;
+        isa_ok( $app, 'App::DefaultConfigFile' );
+        app_ok( $app );
+
+        ok( $app->config_from_override,
+             '... config_from_override true as expected' );
+
+        is( $app->configfile, '/notused',
+            '... configfile is /notused as expected' );
+    }
+}
+
+# Required arg not supplied from cmdline
+{
+    local @ARGV = qw( --configfile /notused );
+    throws_ok { App->new_with_options } qr/Required option missing: required_from_argv/;
+}
+
+# Config file value overriden from cmdline
+{
+    local @ARGV = qw( --configfile /notused --required_from_argv 1 --required_from_config override );
+
+    my $app = App->new_with_options;
+    isa_ok( $app, 'App' );
+
+    is( $app->required_from_config, 'override',
+        '... required_from_config is override as expected' );
+
+    is( $app->optional_from_config, 'from_config_2',
+        '... optional_from_config is from_config_2 as expected' );
+}
+
+# No config file
+{
+    local @ARGV = qw( --required_from_argv 1 --required_from_config noconfig );
+
+    my $app = App->new_with_options;
+    isa_ok( $app, 'App' );
+
+    is( $app->required_from_config, 'noconfig',
+        '... required_from_config is noconfig as expected' );
+
+    ok( !defined $app->optional_from_config,
+        '... optional_from_config is undef as expected' );
+}
+
+sub app_ok {
+    my $app = shift;
+
+    is( $app->required_from_config, 'from_config_1',
+        '... required_from_config is from_config_1 as expected' );
+
+    is( $app->optional_from_config, 'from_config_2',
+        '... optional_from_config is from_config_2 as expected' );
+
+    is( $app->required_from_argv, '1',
+        '... required_from_argv is 1 as expected' );
+}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.