Commits

Toby Inkster committed 985d024

initial version based on https://gist.github.com/3926451

Comments (0)

Files changed (15)

+use inc::Module::Package 'RDF:tobyink 0.009';
+

lib/Acme/Constructor/Pythonic.pm

+package Acme::Constructor::Pythonic;
+
+use 5.006;
+use strict;
+use warnings;
+
+BEGIN {
+	$Acme::Constructor::Pythonic::AUTHORITY = 'cpan:TOBYINK';
+	$Acme::Constructor::Pythonic::VERSION   = '0.001';
+}
+
+use Data::OptList qw(mkopt);
+use Sub::Install qw(install_sub);
+use Module::Runtime qw(use_module);
+
+sub import
+{
+	shift;
+	my $caller = caller;
+	
+	for my $arg (@{ mkopt(\@_) })
+	{
+		my ($module, $opts) = @$arg;
+		$opts->{class}       = $module unless defined $opts->{class};
+		$opts->{constructor} = 'new'   unless defined $opts->{constructor};
+		$opts->{alias}       = $module unless defined $opts->{alias};
+		
+		if (!ref $opts->{alias} and $opts->{alias} =~ /::(\w+)$/) {
+			$opts->{alias} = $1;
+		}
+		
+		install_sub
+		{
+			into => $caller,
+			as   => $opts->{alias},
+			code => sub {
+				use_module($module) unless $opts->{no_require};
+				my $method = $opts->{class}->can($opts->{constructor});
+				unshift @_, $opts->{class};
+				goto $method;
+			}
+		}
+	}
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Acme::Constructor::Pythonic - import Python-style constructor functions
+
+=head1 SYNOPSIS
+
+    use Acme::Constructor::Pythonic qw(
+        LWP::UserAgent
+        JSON
+        HTTP::Request
+    );
+    
+    my $json = JSON();
+    my $ua   = UserAgent();
+    my $req  = Request( GET => 'http://www.example.com/foo.json' );
+    
+    my $data = $json->decode( $ua->request($req)->content )
+
+=head1 DESCRIPTION
+
+In Python you import classes like this:
+
+    import BankAccount from banking
+
+And you instantiate them with something looking like a function call:
+
+    acct = BankAccount(9.99)
+
+This module allows Python-like object instantiation in Perl. The example in
+the SYNOPSIS creates three functions C<UserAgent>, C<JSON> and <Request> each
+of which just pass through their arguments to the real object constructors.
+
+=head2 Options
+
+Each argument to the Acme::Constructor::Pythonic is a Perl module name and
+may be followed by a hashref of options:
+
+    use Acme::Constructor::Pythonic
+        'A::Module',
+        'Another::Module' => \%some_options,
+        'Yes::Another::Module',
+    ;
+
+=over
+
+=item *
+
+B<class>
+
+The class to call the constructor on. This is normally the same as the module
+name, and that's the default assumption, so there's no usually much point in
+providing it.
+
+=item *
+
+B<constructor>
+
+The method name for the constructor. The default is C<new> which is usually
+correct.
+
+=item *
+
+B<alias>
+
+The name of the function you want created for you. The default is the last
+component of the module name, which is often sensible.
+
+=item *
+
+B<no_require>
+
+Acme::Constructor::Python will automatically load the module specified. Not
+straight away; it waits until you actually perform an instantiation. If you
+don't want Acme::Constructor::Python to load the module, then set this option
+to true.
+
+=back
+
+=head1 BUGS
+
+Please report any bugs to
+L<http://rt.cpan.org/Dist/Display.html?Queue=Acme-Constructor-Pythonic>.
+
+=head1 SEE ALSO
+
+L<aliased>.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+(Though it was SSCAFFIDI's idea.)
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=head1 DISCLAIMER OF WARRANTIES
+
+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+

meta/changes.pret

+# This file acts as the project's changelog.
+
+`Acme-Constructor-Pythonic 0.001 cpan:TOBYINK`
+	issued  2012-10-22;
+	label   "Initial release".
+
+# This file contains general metadata about the project.
+
+@prefix : <http://usefulinc.com/ns/doap#>.
+
+`Acme-Constructor-Pythonic`
+	:programming-language "Perl" ;
+	:shortdesc            "import Python-style constructor functions";
+	:homepage             <https://metacpan.org/release/Acme-Constructor-Pythonic>;
+	:download-page        <https://metacpan.org/release/Acme-Constructor-Pythonic>;
+	:bug-database         <http://rt.cpan.org/Dist/Display.html?Queue=Acme-Constructor-Pythonic>;
+	:repository           [ a :HgRepository; :browse <https://bitbucket.org/tobyink/p5-acme-constructor-pythonic> ];
+	:created              2012-10-21;
+	:license              <http://dev.perl.org/licenses/>;
+	:maintainer           cpan:TOBYINK;
+	:developer            cpan:TOBYINK.
+
+<http://dev.perl.org/licenses/>
+	dc:title  "the same terms as the perl 5 programming language system itself".
+

meta/makefile.pret

+# This file provides instructions for packaging.
+
+`Acme-Constructor-Pythonic`
+	perl_version_from m`Acme::Constructor::Pythonic`;
+	version_from      m`Acme::Constructor::Pythonic`;
+	readme_from       m`Acme::Constructor::Pythonic`;
+	test_requires     p`Test::More 0.61` ;
+	.
+
+# This file contains data about the project developers.
+
+@prefix : <http://xmlns.com/foaf/0.1/>.
+
+cpan:TOBYINK
+	:name  "Toby Inkster";
+	:mbox  <mailto:tobyink@cpan.org>.
+
+use Test::More tests => 1;
+
+diag "Running on Perl $] $^O";
+
+{
+	package Local::Foo;
+	sub create { bless [] => shift };
+}
+
+use Acme::Constructor::Pythonic
+	main => {
+		class       => 'Local::Foo',
+		constructor => 'create',
+		alias       => 'LocalFu',
+		no_require  => 1,
+	};
+	
+isa_ok( LocalFu(), 'Local::Foo' );
+use strict;
+use warnings;
+use Test::More;
+
+BEGIN {
+	eval 'require Moose; 1' or plan skip_all => 'need Moose';
+	plan tests => 4;
+}
+
+use Acme::Constructor::Pythonic
+	'Moose::Meta::Class' => {
+		constructor    => 'create_anon_class',
+		alias          => 'AnonClass',
+	},
+	'Moose::Meta::Role' => {
+		constructor    => 'create_anon_role',
+		alias          => 'AnonRole',
+	},
+;
+
+my $person_class = AnonClass(
+	superclasses => [ 'Moose::Object' ],
+);
+my $singing_role = AnonRole(
+	methods      => { sing => sub { "lalala!" } },
+);
+my $singer_class = AnonClass(
+	superclasses => [ $person_class->name ],
+	roles        => [ $singing_role->name ],
+);
+
+Acme::Constructor::Pythonic->import(
+	$singer_class->name => {
+		alias      => 'Singer',
+		no_require => 1,
+	},
+);
+
+my $sinatra = Singer();
+
+ok( $sinatra->isa($person_class->name) );
+ok( $sinatra->does($singing_role->name) );
+ok( $sinatra->isa($singer_class->name) );
+
+is($sinatra->sing, 'lalala!');
+use Test::More;
+eval "use Test::Pod 1.00";
+plan skip_all => "Test::Pod 1.00 required for testing POD" if $@;
+all_pod_files_ok();
+

xt/02pod_coverage.t

+use XT::Util;
+use Test::More;
+use Test::Pod::Coverage;
+
+plan skip_all => __CONFIG__->{skip_all}
+	if __CONFIG__->{skip_all};
+
+if ( __CONFIG__->{modules} )
+{
+	my @modules = @{ __CONFIG__->{modules} };
+	pod_coverage_ok($_, "$_ is covered") for @modules;
+	done_testing(scalar @modules);
+}
+else
+{
+	all_pod_coverage_ok();
+}
+

xt/03meta_uptodate.config

+{"package":"Acme-Constructor-Pythonic"}
+

xt/03meta_uptodate.t

+use XT::Util;
+use Test::More tests => 1;
+use Test::RDF::DOAP::Version;
+doap_version_ok(__CONFIG__->{package}, __CONFIG__->{version_from});
+
+use Test::EOL;
+all_perl_files_ok();
+use Test::Tabs;
+all_perl_files_ok();
+use XT::Util;
+use Test::More;
+use Test::HasVersion;
+
+plan skip_all => __CONFIG__->{skip_all}
+	if __CONFIG__->{skip_all};
+
+if ( __CONFIG__->{modules} )
+{
+	my @modules = @{ __CONFIG__->{modules} };
+	pm_version_ok($_, "$_ is covered") for @modules;
+	done_testing(scalar @modules);
+}
+else
+{
+	all_pm_version_ok();
+}
+