3

i'd like to be able to create "ghost" packages and subs. I have a configuration (ini) file with entries like this:

[features]
sys.ext.latex = off
gui.super.duper.elastic = off
user.login.rsa = on

This file is parsed, and later developers can ask questions like:

if ( MyApp::Feature->enabled ( 'user.login.rsa' ) { ... }

(The whole idea is based on Martin Fowler's FeatureToggle http://martinfowler.com/bliki/FeatureToggle.html)

Using AUTOLOAD for catching calls in MyApp::Feature, and BEGIN block for parsing ini file we are able to provide this API:

if ( MyApp::Feature->user_login_rsa ) { ... }

The question is: Is it possible to create following API:

if ( MyApp::Feature::User::Login::RSA ) { ... }

having only MyApp::Feature?

Lower,upper case can be modified in the config file, that's not the issue here. And make it clear, implementation is decoupled from the configuration, there is no MyApp::Feature::User::Login::RSA and never will be. Implementation for this feature lies f.e. in MyApp::Humans.

I am aware that putting MyApp::Feature::Foo::Bar suggests there must be such Package. But developers know the convention that Feature package manages feature toggles and they would have no problems with that. I find the first example (using enabled( $string ) bit too complex to read

if ( package::package->method ( string ) )

the second one better:

if ( package::package->method )

the third would be even easier:

if ( package::package::package )

So, is it possible to simulate AUTOLOAD on the package level?

Greetings, Rob.


Ian Ringrose
  • 51,220
  • 55
  • 213
  • 317
Robert
  • 4,324
  • 2
  • 18
  • 22
  • Devil in the details: `MyApp::Feature::User::Login::RSA` does not pass strict, it's a bareword. You could make the last part a function call, thus: `MyApp::Feature::User::Login::RSA()` – daxim Mar 14 '12 at 23:21

1 Answers1

4

So it sounds like you have a list of multi-word keys that you want to install into a namespace.

BEGIN {
    my %states = ( # the values that should be transformed
        on  => sub () {1},
        off => sub () {''},
    );
    sub install_config {
        my ($package, $config) = @_;
        for my $key (keys %$config) {
            my @parts = map ucfirst, split /\./, $key;
            my $name  = join '::' => $package, @parts;
            no strict 'refs';
            *{$name} = $states{$$config{$key}} # use a tranformed value
                    || sub () {$$config{$key}} # or the value itself
        }
    }
}

BEGIN {
    my %config = qw(
        sys.ext.latex            off
        gui.super.duper.elastic  off
        user.login.rsa           on
        some.other.config        other_value
    );
    install_config 'MyApp::Feature' => \%config;
}

say MyApp::Feature::Sys::Ext::Latex ? 'ON' : 'OFF';             # OFF
say MyApp::Feature::Gui::Super::Duper::Elastic ? 'ON' : 'OFF';  # OFF
say MyApp::Feature::User::Login::Rsa ? 'ON' : 'OFF';            # ON
say MyApp::Feature::Some::Other::Config;                        # other_value

The constant subroutines installed here are will be inlined by perl when applicable.

You can make install_config a bit easier to use by putting it into a package's import function:

BEGIN {$INC{'Install/Config.pm'}++} # fool require

sub Install::Config::import {shift; goto &install_config}

use Install::Config 'MyApp::Feature' => {qw(
    sys.ext.latex            off
    gui.super.duper.elastic  off
    user.login.rsa           on
    some.other.config        other_value
)};
Eric Strom
  • 39,821
  • 2
  • 80
  • 152