Skip to content

Commit

Permalink
Basic and naive role implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
atoomic committed Mar 31, 2020
1 parent d5d8051 commit 78d5589
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 4 deletions.
3 changes: 3 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{$NEXT}}

- Add basic/naive implementation of roles using 'with'
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,32 @@
# NAME
Simple::Accessor - very simple, light and powerful accessor

# SYNOPSIS

```perl
package Role::Color;
use Simple::Accessor qw{color};

sub _build_color { 'red' } # default color

package Car;

# that s all what you need ! no more line required
use Simple::Accessor qw{brand hp};

with 'Role::Color';

sub _build_hp { 2 }
sub _build_brand { 'unknown' }

package main;

my $c = Car->new( brand => 'zebra' );

is $c->brand, 'zebra';
is $c->color, 'red';
```

# DESCRIPTION

Simple::Accessor provides a simple object layer without any dependency.
Expand Down
5 changes: 4 additions & 1 deletion dist.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = Simple-Accessor
version = 1.11
version = 1.12
author = Nicolas R. <[email protected]>
license = Perl_5
copyright_holder = Nicolas R.
Expand All @@ -16,6 +16,9 @@ copyright_holder = Nicolas R.
[GatherDir]
exclude_filename = Makefile.PL

[NextRelease]
filename = Changes

[MetaJSON]
;[AutoMetaResources]
;bugtracker.rt = 1
Expand Down
77 changes: 74 additions & 3 deletions lib/Simple/Accessor.pm
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
package Simple::Accessor;

use strict;
use warnings;

Expand All @@ -8,6 +7,30 @@ use warnings;
=head1 NAME
Simple::Accessor - very simple, light and powerful accessor
=head1 SYNOPSIS
package Role::Color;
use Simple::Accessor qw{color};
sub _build_color { 'red' } # default color
package Car;
# that s all what you need ! no more line required
use Simple::Accessor qw{brand hp};
with 'Role::Color';
sub _build_hp { 2 }
sub _build_brand { 'unknown' }
package main;
my $c = Car->new( brand => 'zebra' );
is $c->brand, 'zebra';
is $c->color, 'red';
=head1 DESCRIPTION
Simple::Accessor provides a simple object layer without any dependency.
Expand Down Expand Up @@ -109,13 +132,49 @@ None. The only public method provided is the classical import.
=cut

my $INFO;

sub import {
my ( $class, @attr ) = @_;

my $from = caller();

$INFO = {} unless defined $INFO;
$INFO->{$from} = {} unless defined $INFO->{$from};
$INFO->{$from}->{'attributes'} = [ @attr ];

_add_with($from);
_add_new($from);
_add_accessors( to => $from, attributes => \@attr );

return;
}

sub _add_with {
my $class = shift;
return unless $class;

my $with = $class . '::with';
{
no strict 'refs';
*$with = sub {
my ( @what ) = @_;

$INFO->{$class}->{'with'} = [] unless $INFO->{$class}->{'with'};
push @{$INFO->{$class}->{'with'}}, @what;

foreach my $module ( @what ) {
eval qq[require $module; 1] or die $@;
_add_accessors(
to => $class,
attributes => $INFO->{$module}->{attributes},
from_role => $module
);
}

return;
};
}
}

sub _add_new {
Expand Down Expand Up @@ -157,12 +216,16 @@ sub _add_new {
sub _add_accessors {
my (%opts) = @_;

return unless $opts{to};
return unless my $class = $opts{to};
my @attributes = @{ $opts{attributes} };
return unless @attributes;

my $from_role = $opts{from_role};

foreach my $att (@attributes) {
my $accessor = $opts{to} . "::$att";
my $accessor = $class . "::" . $att;

die "$class: attribute '$att' is already defined." if $class->can($att);

# allow symbolic refs to typeglob
no strict 'refs';
Expand All @@ -177,6 +240,10 @@ sub _add_accessors {
my $sub = '_' . $_ . '_' . $att;
if ( $self->can( $sub ) ) {
return unless $self->$sub($v);
} elsif ( $from_role ) {
if ( my $code = $from_role->can( $sub ) ) {
return unless $code->( $self, $v );
}
}
}
}
Expand All @@ -187,6 +254,10 @@ sub _add_accessors {
my $sub = '_' . $builder . '_' . $att;
if ( $self->can( $sub ) ) {
return $self->{$att} = $self->$sub();
} elsif ( $from_role ) {
if ( my $code = $from_role->can( $sub ) ) {
return $self->{$att} = $code->( $self );
}
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions t/lib/Role/Age.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package Role::Age;

use strict;
use warnings;

use Simple::Accessor qw{age};

sub _build_age { 42 }

1;
10 changes: 10 additions & 0 deletions t/lib/Role/Time.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package Role::Time;

use strict;
use warnings;

use Simple::Accessor qw{date};

sub _build_date { '20210102' }

1;
13 changes: 13 additions & 0 deletions t/lib/TestRole.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package TestRole;

use strict;
use warnings;

use Simple::Accessor qw{name};

with 'Role::Age';
with 'Role::Time';

sub _build_name { 'default-name' }

1;
15 changes: 15 additions & 0 deletions t/roles.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use warnings;

use Test::More tests => 4;
use FindBin;

use lib $FindBin::Bin. '/lib';

use_ok 'TestRole';

my $o = TestRole->new();
is $o->name, 'default-name';
is $o->age, 42, 'default age';
is $o->date, '20210102', 'default date';

1;

0 comments on commit 78d5589

Please sign in to comment.