perlmetaprogramminglanguage-featuresfeaturetoggle

Implementing Feature Toggles in Perl5


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.



Solution

  • 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
    )};