views:

338

answers:

2

I'm using a config file (in YAML) to define types that are used later on to validate other config values required for my app:

---
action: >
        use List::MoreUtils;
        my $value = $_;
        any { $value eq $_ } qw(fatal keep merge non-fatal replace);
dir   : return defined $_ ? -d $_ : -1;
file  : return defined $_ ? -f $_ : -1;
string: 1;


---
config-element:
    value: foo
    type : file
etc ...

The idea is to eval each type definition, throw them into a hash and then call to validate configuration data (the following is schematic for easy comprehensibility):

#throw sub refs into hash
my %type_sub;
foreach my $key (keys %$type_def_ref) {
    my $sub_str = "sub {$type_def_ref->{$key}}";
    $type_sub{$key} = eval $sub_str;

}

#validate (myfile is a real file in the cwd)
print $type_sub{file}->('myfile'),"\n";
print $type_sub{action}->('fatal'), "\n";

The problem is that the subroutines in %type_sub don't seem to accept parameters. In the above case, the first print statement outputs -1 while the second outputs:

Use of uninitialized value $value in string eq at (eval 15) line 1.
Use of uninitialized value $_ in string eq at (eval 15) line 1.
Can't call method "any" without a package or object reference at 
(eval 15) line 1.

which is not at all what I expect, yet the subroutines are being called.

What am I doing wrong?

EDIT: I was being sloppy and everything works fine now. Thanks to Friedo.

+3  A: 

Your subroutine parameters will be in the @_ array, not $_. To get the first parameter, look in $_[0] or do my $foo = shift;. (shift operates on @_ by default.)

As for any, I believe the problem is due to any not being able to load its prototype at runtime (subroutine prototypes can only be called at compile-time.) You may need to use explicit parens and an explicit subroutine reference:

any( sub { $value eq $_ }, qw(fatal keep merge non-fatal replace) );
friedo
Wow. I've always used @_ with shift or list assignment and with $_ being so common I completely forgot that it isn't automatically set.
gvkv
I've just tried your solution for 'any' but no luck. There's a more general question here of how to define a subroutine with a string using eval or some other method. Perhaps I'll ask that later.
gvkv
I fixed it. List::MoreUtils doesn't export anything by default.
gvkv
+5  A: 

Don't write code in configuration. Create a library with the code and simply configure which subroutine name you want to use. That should save you an huge amount of work translating strings to code and managing the process. It also saves you a ton of time tracking down problems when someone adjusts the configuration and introduces a syntax error.

I talk about this extensively in the "Configuration" chapter in Mastering Perl, as well as the chapters on dynamic subroutines.

Code doesn't belong in configuration. Say that until you believe it.

brian d foy
You're right of course, but I figured that one-liners aren't a big deal (and I've violated that principle anyway).
gvkv
The other problem is that I don't know how to do that without turning off strict refs.
gvkv