1

I have an application where I would like to allow users to specify configuration files in Perl.

What I have in mind is similar to how PKGBUILD is used in Arch: it is a Bash script, which is simply sourced from the "makepkg" tool. It contains variable and function definitions:

pkgbase=somepkg
pkgname=('somepkg')
pkgver=0.0.1
...
prepare() {
    cd "${srcdir}/${pkgbase}-${pkgver}"
    ...
}

I would like to do something like this in Perl. I guess I'm looking for a Perl version of source, which can read a file, evaluate it, and import all the subroutines and variables into the namespace of the caller. With the Bash version, I could conceivably write a PKGBUILD which sources another PKGBUILD and then overrides one or two variables; I want this kind of "inheritance" to be possible in my Perl config files as well.

One problem with Perl's do is that it seems to put the file's variables and subroutines into a separate namespace. Also, I can't figure out how to override true subroutines, only named anonymous ones.

Here's a version that may serve to illustrate what I want to do. It is not very elegant, but it demonstrates overriding both subroutines and variables, as well as calling a previously-defined subroutine:

$ cat test-override
#!/usr/bin/perl

use warnings;
use strict;

my @tables = qw(a b c);

my $print_tables = sub {
   print join(", ", @tables), "\n";
};

eval(`cat "test-conf"`) or die "$@";

&$print_tables();

$ cat test-conf
@tables = qw(d e f);

my $old_print_tables = $print_tables;
$print_tables = sub {
  warn "In test-conf \$print_tables\n";
  &$old_print_tables();
}

$ ./test-override
In test-conf $print_tables
d, e, f

Another way to do this would be to let the configuration file return a hash, with data and subroutines as values. There is also the option of using classes and inheritance. However, I want the configuration files to be as light-weight as possible, syntactically.

The documentation for "do" mentions the possibility of using Perl in configuration files, so I know this problem has been considered before. Is there a canonical example of how to do it in a "user-friendly" manner?

Metamorphic
  • 732
  • 6
  • 16
  • 6
    Please rethink whether you really want to use configuration files which absolutely beg to be exploited for malicious purposes... This is not about perl, the same would apply to all scripting languages. Even to compiled languages, though "I will compile the user-edited config file and then run execute the result." is nicely impressivily illustrating the problem I mean... – Yunnosch Mar 17 '19 at 09:21
  • 3
    You can write configs as regular perl modules and use custom `import` to get all needed subroutines and variables automatically. For security consider using `eval` and https://stackoverflow.com/questions/9144902/perl-safe-eval – UjinT34 Mar 17 '19 at 09:37
  • See also [How can I export a list of modules with my own module?](https://stackoverflow.com/q/30814892/2173773) and [Importing a .pl file](https://stackoverflow.com/q/10196634/2173773) – Håkon Hægland Mar 17 '19 at 10:39

1 Answers1

3

Your description of what you want this "configuration file" to do (import subs and variables into the caller's namespace, override subs, classes, inheritance...) sounds suspiciously like a standard Perl module. So use a standard Perl module.

Note that there is widespread precedent for this approach: The standard cpan command-line client stores its configuration in a module, located at the default path of ~/.cpan/CPAN/MyConfig.pm on *nix-type systems. Granted, cpan's MyConfig.pm is a very simple example which just sets the hashref $CPAN::Config, but there's no reason it couldn't also do all the other things any module does.

But doing it with do is quite simple. I suspect you're just overthinking it:

$ cat test-override 
#!/usr/bin/perl

use warnings;
use strict;

our @tables = qw(a b c);

sub print_tables {
   print join(", ", @tables), "\n";
};

print_tables;

do "test-conf";

print_tables;

print "\@tables is @tables\n";

$ cat test-conf 
@tables = qw(d e f);

sub print_tables {
  print "print_tables from test_conf\n";
}

$ ./test-override 
a, b, c
print_tables from test_conf
@tables is d e f

The important change I made with @tables was to change it from my, which is visible only within the current scope and the current file, to our, which is visible anywhere within the same package (or from other packages if it's qualified with the package name).

But my print_tables from the config file doesn't call the original print_tables, and you're just out of luck on that one. Because there can only be one &main::print_tables, replacing it completely overwrites the original one, which no longer exists. If you want to be able to override it and still be able to call the original, you need to put the two declarations into different packages, which kind of implies using OO Perl (so that you'll be able to polymorphically call the right one).

Also note that use has the same lexical scoping as my, which means that your use strict; use warnings; does not carry over into the conf file. You can easily demonstrate this by adding a use warnings; to my version of test-conf, at which point it will then generate the warning Subroutine print_tables redefined.

Dave Sherohman
  • 45,363
  • 14
  • 64
  • 102
  • 2
    `do "$RealBin/test-conf" or die $@ || $!;` – ikegami Mar 17 '19 at 10:49
  • And if I put `use strict;` at the top of `test-conf`, then it stops working as intended. Nevertheless this seems like a great solution, which I must have overlooked because of a misplaced desire to (eventually) have `use strict;` in there. Anyway thank you for the very helpful answer. If someone else has a version that uses inheritance or some other method then I'd be glad to see it as well, of course. – Metamorphic Mar 18 '19 at 06:22
  • @Metamorphic - You can fix the `strict` violation by adding a second `our @tables` in `test-conf`, or by fully-qualifying it as `@main::tables`, but either of those increases the demands on the user creating the conf file, so they may or may not be suitable for your use case. – Dave Sherohman Mar 18 '19 at 07:56
  • 1
    I also noticed that I can access the original subroutine definition using a `BEGIN` statement in `test-conf`, as in `our $old_print_tables; BEGIN{ $old_print_tables = \&print_tables; }`. Seems to work OK...? – Metamorphic Mar 18 '19 at 12:33