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.
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
)};
Related
What I'd ideally like to be able to do is scan a bunch of files that are implicitly importing a bunch of functions via Test::Most. I'd like to explicitly import the functions within the file. So basically I'll check the use statements to see if they already exist and, if they don't, I'd like to add an additional use statement for the function(s) in question. For example, I might add use Test::Differences qw( eq_or_diff ); if there's an eq_or_diff in the file, but no use Test::Differences. It'll get a little more complicated, but that's the basic idea.
As a proof of concept, I've tried to add just a single word into an existing script, but I can't figure it out. insert_after() returns true on success. I only ever get a false value, but I don't see any debugging info as to why the line could not be added.
use strict;
use warnings;
use PPI::Document ();
use PPI::Token::Word ();
use Test::More;
my $script = <<'EOF';
use strict;
use warnings;
use DateTime ();
use Git::Helpers qw( checkout_root );
use LWP::UserAgent ();
my $foo = 'bar';
EOF
my $doc = PPI::Document->new( \$script );
my $includes = $doc->find('PPI::Statement::Include');
my #use = grep { $_->type eq 'use' } #{$includes};
my $second_last = $use[-2];
diag 'Trying to insert after ' . $second_last->module;
my $word = PPI::Token::Word->new('use');
isa_ok( $word, 'PPI::Element', 'word is an Element' );
isa_ok( $second_last, 'PPI::Element', 'use is an Element' );
ok( $second_last->insert_after($word), 'word inserted' );
diag $doc->serialize;
done_testing();
The output of my script is as follows. You'll note that the document does not appear to have been altered:
# trying to insert after Git::Helpers
ok 1 - 'word is an Element' isa 'PPI::Element'
ok 2 - 'use is an Element' isa 'PPI::Element'
not ok 3 - word inserted
# failed test 'word inserted'
# at so.pl line 31.
# use strict;
# use warnings;
#
# use DateTime ();
# use Git::Helpers qw( checkout_root );
# use LWP::UserAgent ();
#
# my $foo = 'bar';
1..3
# looks like you failed 1 test of 3.
Looking at the source of PPI::Statement:
# As above, you can insert a statement, or a non-significant token
sub insert_after {
my $self = shift;
my $Element = _INSTANCE(shift, 'PPI::Element') or return undef;
if ( $Element->isa('PPI::Statement') ) {
return $self->__insert_after($Element);
} elsif ( $Element->isa('PPI::Token') and ! $Element->significant ) {
return $self->__insert_after($Element);
}
'';
}
A "non-significant token" is something like whitespace or a comment.
You are trying to insert a single, significant token at the top level (after a statement). That's not allowed.
You'll have to build a full PPI::Statement::Include element.
Here's some (rather ugly) proof-of-concept code:
# ...
diag 'Trying to insert after ' . $second_last->module;
{
my $insertion_point = $second_last;
for my $new_element (
do {
my $synthetic_use = PPI::Statement::Include->new;
for my $child (
PPI::Token::Word->new('use'),
PPI::Token::Whitespace->new(' '),
PPI::Token::Word->new('Test::Differences'),
PPI::Token::Whitespace->new(' '),
PPI::Token::Quote::Single->new("'eq_or_diff'"),
PPI::Token::Structure->new(';'),
) {
ok $synthetic_use->add_element($child);
}
$synthetic_use
},
PPI::Token::Whitespace->new("\n"),
) {
ok $insertion_point->insert_after($new_element);
}
}
diag $doc->serialize;
But it's much easier to let PPI parse a given fragment and just use those objects:
diag 'Trying to insert after ' . $second_last->module;
{
my $insertion_point = $second_last;
for my $new_element (
reverse PPI::Document->new(\ "\nuse Test::Differences qw( eq_or_diff );")->elements
) {
ok $insertion_point->insert_after($new_element->remove);
}
}
diag $doc->serialize;
Beware: Using $new_element->remove instead of just $new_element is crucial. You need to detach $new_element from its old containing document because otherwise the destruction of the temporary PPI::Document instance will wipe out all child elements, including those already added to $doc.
We're looking at options for converting CamelCase to camel-case and underscores to hyphens and hoping someone can offer some strong examples. (Using hyphens for SEO reasons).
Specifically:
Working on an MMORPG in Catalyst and getting tired of having to write things like this:
sub travel_to ( $self, $c, $star ) : Path('travel-to') Args(1) { ... }
Or this:
package Veure::Controller::Area::StorageLocker {
....
sub index ( $self, $c ) : Path('/area/storage-locker') { ... }
}
Update: Because there's some confusion, I meant that we'd much prefer to have devs write this:
# getting rid of Args() would be nice, but sigs don't have introspection
sub travel_to ( $self, $c, $star ) : Args(1) { ... }
Or this:
package Veure::Controller::Area::StorageLocker {
....
sub index ( $self, $c ) { ... }
}
This is important because for an SEO standpoint, underscores instead of hyphens can dramatically improve your SEO. By having to do extra grunt work to always force hyphens, developers are forgetting to do this and we keep wasting money going back and having to rewrite code where this caveat was forgotten. This is the sort of thing which we should be able to do automatically.
I did a bit of digging in the Catalyst sources.
Cammel case controller names
You can modify class2prefix in Catalyst::Utils to change how the controller names translate to the namespace.
Here is a very quick hack that demonstrates what is going on with a fresh MyApp created with catalyst.pl. I borrowed Borodin's suggestion to implement it.
package MyApp::Controller::FooBar;
use Moose;
use namespace::autoclean;
BEGIN { extends 'Catalyst::Controller'; }
{
require Class::Method::Modifiers;
require String::CamelCase;
Class::Method::Modifiers::around(
'Catalyst::Utils::class2prefix' => sub {
my $orig = shift;
# I borrowed most of this from the original function ...
my $class = shift || '';
my $prefix = shift || 0;
if ( $class =~ /^.+?::([MVC]|Model|View|Controller)::(.+)$/ ) {
$prefix = $2;
$prefix =~ s{::}{/}g;
# ... and this from https://stackoverflow.com/a/37968830/1331451
$prefix = String::CamelCase::decamelize($prefix) =~ tr/_/-/r;
}
return $prefix;
}
);
}
sub index :Path :Args(0) {
my ( $self, $c ) = #_;
$c->response->body('Matched MyApp::Controller::FooBar in FooBar.');
}
1;
I tested this briefly, but cannot guarantee it's not going to break anything else. I believe if it's put into a better place and done in a more appropriate way it could be a viable option.
Underscores in actions
This one looks trickier. My best bet is to fiddle with Catalyst::DispatchType::Path in some way, or create something that installs an ActionClass that modifies it. It's basically replacing the _ with a -. That thing could be built around gather_default_action_roles in Catalyst::Controller (maybe as a subclass) to add that one to all actions. This is highly speculative.
CPAN has the String::CamelCase module, which offers a decamelize function, after which you will need to convert underscores to hyphens using tr/_/-/
I hope this short example helps to answer your question
use strict;
use warnings 'all';
use v.14.1;
use String::CamelCase 'decamelize';
for my $s ( 'travel_to', 'Veure::Controller::Area::StorageLocker' ) {
(my $ss = $s) =~ s|^[\w:]*::Controller(?=::)||;
$ss =~ s|::|/|g;
$ss = decamelize($ss) =~ tr/_/-/r;
say $ss;
}
output
travel-to
/area/storage-locker
Is it possible to provide a hook in Perl to make sure no Hash key lookup fails ?
Example :
use strict;
use warnings;
my %hash_example = ( "a"=>"apple", "b"=>"ball" );
print $hash_example{"a"}; # Goes Fine.
print $hash_example{"c"}; # Throws Warning ( "Use of uninitialized value " ).
Codepad link
Whenever a hash lookup happens, some subroutine could get called which can provide a default value.
I mean, any hash lookup should call a sub ( say "get_hash_value (hash_ref, key) " ) and pass the hash and key to it. A sample of such a sub is shown below :
sub get_hash_value {
my $hash_ref = shift;
my $key = shift;
if ( exists $hash_ref->{$key} ) { # For Normal Lookup.
return $hash_ref->{$key};
}
else {
# This is the interesting place where we could provide our own values.
return "custom_value_based_on_certain_conditions"; # Some value
}
}
Another consequence would be the ability to alter the value returned against a key. We would be able to return a different value than what actually is stored against that key ( in that hash ).
There might not be a valid use case for this but am intrigued and would like to learn if such things are supported in Perl.
As said by Сухой27 in comment, this works fine:
my %hash_example = ( "a"=>"apple", "b"=>"ball" );
print $hash_example{"a"};
print $hash_example{"c"} // "custom_value_based_on_certain_conditions";
Doc on logical defined or
I would suggest that trying to alter how a hash lookup "works" is a really terrible idea, as a good way to create code that's hard to maintain.
However instead I would suggest you look at creating an object instead of a hash. They are basically the same thing, but an object includes code, and there is an expectation that the code within the object is 'doing it's own thing'.
So at a basic level:
#!/usr/bin/env perl
use strict;
use warnings;
package Hash_Ob;
sub new {
my ($class) = #_;
my $self = {};
bless( $self, $class );
return $self;
}
sub get_value {
my ( $self, $valuename ) = #_;
if ( $self->{$valuename} ) {
return $self->{$valuename};
}
else {
#generate your own value here!
$self->{$valuename} = 42;
return $self->{$valuename};
}
}
1;
Which you'd then 'call' using:
#!/usr/bin/env perl
use strict;
use warnings;
use Hash_Ob;
my $magic_hash = Hash_Ob -> new();
print $magic_hash -> get_value('new_value');
This avoids the problem of altering how a 'well known' mechanism actually works, and so future maintenance programmers will not curse your name.
Then maybe you want to use a tied hash. Tying is a mechanism to change the behavior of a builtin data type. See perltie for the gory details.
{
package HashWithDefault;
use Tie::StdHash;
our #ISA = qw(Tie::StdHash); # inherit STORE, FIRST, NEXT, etc.
sub TIEHASH {
my ($pkg,$default_val) = #_;
return bless { __default_val__ => $default_val}, $pkg;
}
sub FETCH {
my ($self,$key) = #_;
exists $self->{$key} ? $self->{$key} : $self->{__default_val__};
}
sub CLEAR { # don't clear the default val
my $self = shift;
%$self = ( __default_val__ => $self->{__default_val__} );
}
}
tie my %hash, 'HashWithDefault', "42";
%hash = (foo => 123, bar => 456);
print $hash{foo}; # 123
print $hash{quux}; # 42
I have some Moose classes that define several small groups of related methods. I would like to make these groups obvious in the package POD.
I use Dist::Zilla and Pod::Weaver with the =method command. Is it possible to insert some =head2-like commands between my =method commands to achieve the desired effect?
I wrote a post on how I did it for Redis::Client here: Falling in Love with Pod::Weaver.
The simplest thing to do is add custom Collect directives to your weaver.ini and organize your methods by giving each type a different custom POD command, like so:
[Collect / FOO METHODS]
command = foo_method
[Collect / BAR METHODS]
command = bar_method
[Collect / BAZ METHODS]
command = baz_method
Then write your POD like this
=foo_method blah blah
and Weaver will automatically collect them under their own =head1.
If you want to do something more complicated than that, you can write your own Pod::Weaver plugin. The gist is to search through the parsed POD for a custom command name and transform them by returning Pod::Elemental objects. Here's the plugin I wrote:
package Pod::Weaver::Plugin::RedisLinks;
# ABSTRACT: Add links to Redis documentation
use Moose;
with 'Pod::Weaver::Role::Transformer';
use Data::Dumper;
use Scalar::Util 'blessed';
use aliased 'Pod::Elemental::Element::Pod5::Ordinary';
sub transform_document {
my ( $self, $doc ) = #_;
my #children = $doc->children;
my #new_children;
foreach my $child( #{ $children[0] } ) {
if ( $child->can( 'command' )
&& $child->command =~ /^(?:key|str|list|hash|set|zset|conn|serv)_method/ ) {
my $meth_name = $child->content;
$meth_name =~ s/^\s*?(\S+)\s*$/$1/;
my $cmd_name = uc $meth_name;
$cmd_name =~ tr/_/ /;
my $link_name = $meth_name;
$link_name =~ tr/_/-/;
my $new_para = Ordinary->new(
content => sprintf 'Redis L<%s|%s> command.',
$cmd_name, 'http://redis.io/commands/' . $link_name );
push #new_children, $child, $new_para;
next;
}
push #new_children, $child;
}
$doc->children( \#new_children );
}
__PACKAGE__->meta->make_immutable;
1;
The transform_document method gets passed the parsed document as a parameter. It then goes through the top-level commands looking for elements labeled /^(?:key|str|list|hash|set|zset|conn|serv)_method/, munges the name a bit, and then builds a new POD paragraph containing the formatted POD content that I want.
I have a perl script (simplified) like so:
my $dh = Stats::Datahandler->new(); ### homebrew module
my %url_map = (
'/(article|blog)/' => \$dh->articleDataHandler,
'/video/' => \$dh->nullDataHandler,
);
Essentially, I'm going to loop through %url_map, and if the current URL matches a key, I want to call the function pointed to by the value of that key:
foreach my $key (keys %url_map) {
if ($url =~ m{$key}) {
$url_map{$key}($url, $visits, $idsite);
$mapped = 1;
last;
}
}
But I'm getting the message:
Can't use string ("/article/") as a subroutine ref while "strict refs" in use at ./test.pl line 236.
Line 236 happens to be the line $url_map{$key}($url, $visits, $idsite);.
I've done similar things in the past, but I'm usually doing it without parameters to the function, and without using a module.
Since this is being answered here despite being a dup, I may as well post the right answer:
What you need to do is store a code reference as the values in your hash. To get a code reference to a method, you can use the UNIVERSAL::can method of all objects. However, this is not enough as the method needs to be passed an invocant. So it is clearest to skip ->can and just write it this way:
my %url_map = (
'/(article|blog)/' => sub {$dh->articleDataHandler(#_)},
'/video/' => sub {$dh->nullDataHandler(#_)},
);
This technique will store code references in the hash that when called with arguments, will in turn call the appropriate methods with those arguments.
This answer omits an important consideration, and that is making sure that caller works correctly in the methods. If you need this, please see the question I linked to above:
How to take code reference to constructor?
You're overthinking the problem. Figure out the string between the two forward slashes, then look up the method name (not reference) in a hash. You can use a scalar variable as a method name in Perl; the value becomes the method you actually call:
%url_map = (
'foo' => 'foo_method',
);
my( $type ) = $url =~ m|\A/(.*?)/|;
my $method = $url_map{$type} or die '...';
$dh->$method( #args );
Try to get rid of any loops where most of the iterations are useless to you. :)
my previous answer, which I don't like even though it's closer to the problem
You can get a reference to a method on a particular object with can (unless you've implemented it yourself to do otherwise):
my $dh = Stats::Datahandler->new(); ### homebrew module
my %url_map = (
'/(article|blog)/' => $dh->can( 'articleDataHandler' ),
'/video/' => $dh->can( 'nullDataHandler' ),
);
The way you have calls the method and takes a reference to the result. That's not what you want for deferred action.
Now, once you have that, you call it as a normal subroutine dereference, not a method call. It already knows its object:
BEGIN {
package Foo;
sub new { bless {}, $_[0] }
sub cat { print "cat is $_[0]!\n"; }
sub dog { print "dog is $_[0]!\n"; }
}
my $foo = Foo->new;
my %hash = (
'cat' => $foo->can( 'cat' ),
'dog' => $foo->can( 'dog' ),
);
my #tries = qw( cat dog catbird dogberg dogberry );
foreach my $try ( #tries ) {
print "Trying $try\n";
foreach my $key ( keys %hash ) {
print "\tTrying $key\n";
if ($try =~ m{$key}) {
$hash{$key}->($try);
last;
}
}
}
The best way to handle this is to wrap your method calls in an anonymous subroutine, which you can invoke later. You can also use the qr operator to store proper regexes to avoid the awkwardness of interpolating patterns into things. For example,
my #url_map = (
{ regex => qr{/(article|blog)/},
method => sub { $dh->articleDataHandler }
},
{ regex => qr{/video/},
method => sub { $dh->nullDataHandler }
}
);
Then run through it like this:
foreach my $map( #url_map ) {
if ( $url =~ $map->{regex} ) {
$map->{method}->();
$mapped = 1;
last;
}
}
This approach uses an array of hashes rather than a flat hash, so each regex can be associated with an anonymous sub ref that contains the code to execute. The ->() syntax dereferences the sub ref and invokes it. You can also pass parameters to the sub ref and they'll be visible in #_ within the sub's block. You can use this to invoke the method with parameters if you want.