Hello, all:
I've decided to try Moose on my current project, and I've hit a stumbling block. I'm reviewing the documentation, but haven't found what I'm looking for, so I thought I'd see if anyone could offer any suggestions.
I'm building an ad-hoc parser for a file, and am hoping to leverage the BUILDARGS method to parse out the chunks at each level. I've stripped it down to a relatively short example, like so:
# Funky.pm
use strict;
use warnings;
package Funky::Vertex;
use Moose;
use Data::Dump 'pp';
has 'X' => (is=>'rw', isa=>'Num');
has 'Y' => (is=>'rw', isa=>'Num');
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
print "Vertex(<$_[0]>)\n";
if (@_==1 && !ref $_[0]) {
# We were handed a single scalar, so we're expecting a string
+like "##.##,##.##"
# representing the X and Y coordinates
my @list = split /,/, $_[0];
print "\tV: ", pp(\@list), "\n";
return $class->$orig( X=>$list[0], Y=>$list[1] );
}
else {
return $class->$orig(@_);
}
};
package Funky::Rectangle;
use Moose;
use Data::Dump 'pp';
has 'LL' => (is=>'rw', isa=>'Funky::Vertex');
has 'UR' => (is=>'rw', isa=>'Funky::Vertex');
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
print "Rectangle(<$_[0]>)\n";
if (@_==1 && !ref $_[0]) {
# We were handed a single scalar, so we're expecting a string
+representing
# a pair of vertices, like "##.##,##.##;##.##,##.##"
# representing the LL and UR corners
my @list = split /;/, $_[0];
print "\tRB: ", pp(\@list), "\n";
return $class->$orig( LL=>$list[0], UR=>$list[1] ); ### NOTE
+BELOW ###
}
else {
return $class->$orig(@_);
}
};
1;
As you can see, a Funky::Vertex has an X and Y coordinate, but if you construct it with a scalar, it expects a string like "<num>,<num>" and will split that into a hash of { X=><num>, Y=><num> }. Similarly, a Funky::Rectangle has a lower left vertex (LL) and an upper-right vertex (UR). If called with a scalar like "<vertex>;<vertex>", it would split that into a hash of { LL=><vertex>, UR=><vertex> } to build itself.
The problem I'm having is that I was hoping that Moose would recurse through the BUILDARGS as needed to build all the items, but instead it seems to assume that all the arguments are fully built already:
#!env perl
use strict;
use warnings;
use Test::More;
use lib ".";
BEGIN {
use_ok( 'Funky' );
}
require_ok( 'Funky' );
subtest 'Vertex' => sub {
my $t = Funky::Vertex->new("15,22");
isa_ok($t, 'Funky::Vertex');
is($t->X(), 15, "x val");
is($t->Y, 22, "y val");
done_testing();
};
subtest 'Rectangle' => sub {
my $t = Funky::Rectangle->new("10,10;15,25");
isa_ok($t, 'Funky::Rectangle');
is($t->LL->X, 10, "LL x val");
is($t->LL->Y, 10, "LL y val");
is($t->UR->X, 15, "UR x val");
is($t->UR->Y, 25, "UR y val");
done_testing();
};
When I run this example, I'm getting:
$ perl t_File.pl
ok 1 - use Funky;
ok 2 - require Funky;
# Subtest: Vertex
Vertex(<15,22>)
V: [15, 22]
ok 1 - An object of class 'Funky::Vertex' isa 'Funky::Vertex'
ok 2 - x val
ok 3 - y val
1..3
ok 3 - Vertex
# Subtest: Rectangle
Rectangle(<10,10;15,25>)
RB: ["10,10", "15,25"]
1..0
# No tests run!
not ok 4 - No tests run for subtest "Rectangle"
# Failed test 'No tests run for subtest "Rectangle"'
# at t_File.pl line 30.
Attribute (UR) does not pass the type constraint because: Validation f
+ailed for 'Funky::Vertex' with value 15,25 (not isa Funky::Vertex) at
+ /usr/local/lib/perl5/site_perl/5.26/x86_64-cygwin-threads/Moose/Obje
+ct.pm line 24
Moose::Object::new('Funky::Rectangle', '10,10;15,25') called a
+t t_File.pl line 23
main::__ANON__ at /usr/local/share/perl5/site_perl/5.26/Test/B
+uilder.pm line 339
eval {...} at /usr/local/share/perl5/site_perl/5.26/Test/Build
+er.pm line 339
Test::Builder::subtest('Test::Builder=HASH(0x60008c060)', 'Rec
+tangle', 'CODE(0x6007d0e58)') called at /usr/local/share/perl5/site_p
+erl/5.26/Test/More.pm line 807
Test::More::subtest('Rectangle', 'CODE(0x6007d0e58)') called a
+t t_File.pl line 30
# Tests were run but no plan was declared and done_testing() was not s
+een.
# Looks like your test exited with 255 just after 4.
I can make the test pass by changing the line marked "### NOTE BELOW ###" to:
return $class->$orig( LL=>Funky::Vertex->new($list[0]), UR=>Funky::Vertex->new($list[1]) );
However, I find that to be a bit of a problem because:
- As I edit the parser, I find more and more lines that need to be changed, and
- The information is already present in the has entries for which constructor to call for each thing.
It feels like there should be a relatively simple way to get the behavior I want without having to repeat the same information (in has and in BUILDARGS). I've not come up with a solution, though.
Anyone have any suggestions?
...roboticus
When your only tool is a hammer, all problems look like your thumb.