I'm writing a script to help me get proficient in Moose. I've got the following bit of code:
package Dir;
use Moose;
use Modern::Perl;
use File;
has 'dirs' => (is => 'ro', isa => 'HashRef[Dir]' );
has 'files' => (is => 'ro', isa => 'HashRef[File]');
has 'dir_class' => (is => 'ro', isa => 'ClassName', default => 'Dir');
has 'file_class' => (is => 'ro', isa => 'ClassName', default => 'File');
sub BUILD {
my $self = shift;
my $path = $self->path;
my $name = $self->name;
my (%dirs, %files);
# populate dirs attribute with LaborData::Data::Dir objects
opendir my $dh, $path or die "Can't opendir '$path': $!";
# Get files and dirs and separate them out
my #dirs_and_files = grep { ! m{^\.$|^\.\.$} } readdir $dh;
closedir $dh or die "Can't closedir '$path': $!";
my #dir_names = grep { -d "$path/$_" } grep { !m{^\.} } #dirs_and_files;
my #file_names = grep { -f "$path/$_" } grep { !m{^\.} } #dirs_and_files;
# Create objects
map { $dirs{$_} = $self->dir_class->new ( path => $path . '/' . $_ ) } #dir_names;
map { $files{$_} = $self->file_class->new ( path => $path . '/' . $_ ) } #file_names;
# Set attributes
$self->dirs ( \%dirs );
$self->files ( \%files );
}
The code results in the following error: died: Moose::Exception::CannotAssignValueToReadOnlyAccessor (Cannot assign a value to a read-only accessor at reader Dir::dirs
To get around this error, I could either make the attributes rw or use builder methods for the dirs and files attributes. The former solution is undesirable and the latter solution will require duplication of code (for example, the directory will need to be opened twice) and so is also undesirable.
What is the best solution to this problem?
You can assign a writer to your read-only attribute and use that internally from your BUILD. Name it with an _ to indicate it's internal.
package Foo;
use Moose;
has bar => ( is => 'ro', writer => '_set_bar' );
sub BUILD {
my $self = shift;
$self->_set_bar('foobar');
}
package main;
Foo->new;
This will not throw an exception.
It's essentially the same as making it rw, but now the writer is not the same accessor as the reader. The _ indicates that it's internal, so it's less undesirable than just using rw. Remember that you cannot really protect anything in Perl anyway. If your user wants to get to the internals they will.
I found one possible solution, though it's frowned upon:
# Set attributes
$self->{dirs} = \%dirs;
$self->{files} = \%files;
Related
I'm new to MOOSE and Perl OOP, and I'm struggling understanding the execution order of the code.
I want to create a class reading files, so an attribute of the object should be the filehandle and another the filename to be read.
My problem is that the attribute 'filehandle' has a builder that requires $self->filename, but sometimes at runtime 'filename' is not (yet) available when the builder is called.
Thanks for you help
My ideal object creation:
my $file = FASTQ::Reader->new(
filename => "$Bin/test.fastq",
);
Perl module:
has filename => (
is => 'ro', isa => 'Str', required => 1,
);
has fh => (
is => 'ro', isa => 'FileHandle', builder => '_build_file_handler',
);
sub _build_file_handler {
my ($self) = #_;
say Dumper $self;
open(my $fh, "<", $self->filename) or die ("cant open " . $self->filename . "\n");
return $fh;
}
See: https://gist.github.com/telatin/a81a4097913af55c5b86f9e01a2d89ae
If a value of one attribute depends on another attribute, make it lazy.
#!/usr/bin/perl
use warnings;
use strict;
{ package My::Class;
use Moose;
has filename => (is => 'ro', isa => 'Str', required => 1);
has fh => (is => 'rw', isa => 'FileHandle', lazy => 1, builder => '_build_fh');
# ~~~~~~~~~
sub _build_fh {
my ($self) = #_;
open my $fh, '<', $self->filename or die $!;
return $fh
}
}
my $o = 'My::Class'->new(filename => __FILE__);
print while readline $o->fh;
See Laziness in Moose::Manual::Attributes:
if the default value for this attribute depends on some other attributes, then the attribute must be lazy.
I've building a script that recursively builds the names of a directory's subdirectories/files and the names of the files in those subdirectories as objects:
package Dir;
use Moose;
use Modern::Perl;
use File;
use strict;
use warnings;
has 'path' => (is => 'ro', isa => 'Str', required => 1);
has 'name' => (is => 'ro', isa => 'Str', lazy => 1, default => sub { my $self = shift; my ($name) = $self->path =~ /\/([^\/]*)$/; return $name; } );
has 'subdirs' => (is => 'rw', isa => 'ArrayRef[Dir]' );
has 'files' => (is => 'rw', isa => 'ArrayRef[File]' );
has 'num_dirs' => (is => 'ro', isa => 'Int', lazy => 1, default => sub { my $self = shift; scalar #{$self->subdirs}; } );
sub BUILD {
my $self = shift;
my $path = $self->path;
# run some tests
logf('Path to the directory does not exist.') if (!-e $path);
logf('The path should point to a directory, not a file.') if (!-d $path);
# populate subdirs attribute with Dir objects
opendir my $dh, $path or die "Can't opendir '$path': $!";
# Get files and dirs and separate them out into categories
my #dirs_and_files = grep { ! m{^\.$|^\.\.$} } readdir $dh;
closedir $dh or die "Can't closedir '$path': $!";
my #subdir_names = grep { -d "$path/$_" } grep { !m{^\.} } #dirs_and_files;
my #file_names = grep { -f "$path/$_" } grep { !m{^\.} } #dirs_and_files;
# Create objects
my #dir_objects = map { Dir->new ( path => $path . '/' . $_ ) } #subdir_names;
my #file_objects = map { File->new ( path => $path . '/' . $_ ) } #file_names;
# Populate this with file and directory objects
$self->subdirs ( \#dir_objects );
$self->files ( \#file_objects );
}
1;
Notice the code has a files attribute which holds an array of File objects. A File has the following attributes:
has 'path' => (is => 'ro', isa => 'Str', required => 1);
has 'name' => (is => 'ro', isa => 'Str', lazy => 1, default => sub { my $self = shift; my ($name) = $self->path =~ /\/([^\/]*)$/; return $name; } );
The problem is that the name attribute never gets set when a File object is created. I'm not sure why.
EDIT 1: Solution (sort of)
So, I slapped this into the File object to see if it triggered the creation of the attribute:
sub BUILD {
my $self = shift;
}
This did not solve the problem. However, this did:
sub BUILD {
my $self = shift;
$self->name;
}
The question I have, though, is why did I need to do this?
The problem is your pattern is failing if there's a trailing slash.
my ($name) = $self->path =~ /\/([^\/]*)$/;
If $self->path is /some/thing it works. If it's /some/thing/ it "works" but [^\/]* happily matches an empty string. So you get no warning.
You can put in an optional slash, and change it to match ONE or more non-slashes. Also by using alternative delimiters we can clean up all those leaning toothpicks.
my ($name) = $self->path =~ m{/ ([^/]+) /? $}x;
But really one shouldn't be parsing paths with regular expressions. Use one of the many built in modules like File::Basename or File::Spec
return basename($self->path);
Some side notes.
Moose is very slow to start up and is best suited for long running processes like web servers. For something as generic as a File and Dir class, consider using Moo. It's mostly compatible with Moose, much faster, and when used in conjunction with Types::Standard, does types better. It would be good, for example, to make a StrNotEmpty type to avoid this sort of problem.
Unless this is an exercise, Perl already has a great module to do this sort of thing. Look into Path::Tiny.
Attributes with lazy => 1 are only created when their accessor is called, not after construction.
Just a side note:
You incorrectly claim a path doesn't exist if you have no permission to its parent dir. Also, you incorrectly claim a path to a directory isn't one if you have no permission to its parent dir.
You also needlessly stat the file twice. In fact, you needn't stat the file at all since opendir is already making the checks you are making.
Simply replace
logf('Path to the directory does not exist.') if (!-e $path);
logf('The path should point to a directory, not a file.') if (!-d $path);
opendir my $dh, $path or die "Can't opendir '$path': $!";
with
opendir(my $dh, $path)
or do {
logf("Can't open directory \"$path\": $!");
die("Can't open directory \"$path\": $!");
};
This also avoids the race condition in your code, the possibility that the state of things might change between the checks and the opendir.
I of course know that I can rename the init arg for an attribute by setting init_arg (e.g)
package Test {
use Moose;
has attr => (
is => 'ro',
isa => 'Str',
init_arg => 'attribute'
);
}
which would allow me to
Test->new({ attribute => 'foo' });
but not
Test->new({ attr => 'foo' });
at the same time
MooseX::Aliases actually has this behavior, but creating an alias also creates accessors. I'm currently trying to understand the code in that module to see if I can't determine how it does it, so that I can replicate said functionality (in a way I understand). If someone could explain how to do it here with an example that'd be great.
update it appears that MX::Aliases is doing this by way of replacing what's actually passed to the constructor in an around initialize_instance_slot but I'm still not sure how that's actually getting called, because in my test code my around isn't actually getting executed.
update munging in BUILDARGS isn't really an option because what I'm trying to do allow setting of the accessor via the name of the label I'm adding to the attribute via Meta Recipe3. You might say I'm doing
has attr => (
is => 'ro',
isa => 'Str',
alt_init_arg => 'attribute'
);
update
here's what I've managed to work out with what I'm trying to do so far.
use 5.014;
use warnings;
package MooseX::Meta::Attribute::Trait::OtherName {
use Moose::Role;
use Carp;
has other_name => (
isa => 'Str',
predicate => 'has_other_name',
required => 1,
is => 'ro',
);
around initialize_instance_slot => sub {
my $orig = shift;
my $self = shift;
my ( $meta_instance, $instance, $params ) = #_;
confess 'actually calling this code';
return $self->$orig(#_)
unless $self->has_other_name && $self->has_init_arg;
if ( $self->has_other_name ) {
$params->{ $self->init_arg }
= delete $params->{ $self->other_name };
}
};
}
package Moose::Meta::Attribute::Custom::Trait::OtherName {
sub register_implementation { 'MooseX::Meta::Attribute::Trait::OtherName' }
}
package Message {
use Moose;
# use MooseX::StrictConstructor;
has attr => (
traits => [ 'OtherName' ],
is => 'ro',
isa => 'Str',
other_name => 'Attr',
);
__PACKAGE__->meta->make_immutable;
}
package Client {
use Moose;
sub serialize {
my ( $self, $message ) = #_;
confess 'no message' unless defined $message;
my %h;
foreach my $attr ( $message->meta->get_all_attributes ) {
if (
$attr->does('MooseX::Meta::Attribute::Trait::OtherName')
&& $attr->has_other_name
) {
$h{$attr->other_name} = $attr->get_value( $message );
}
}
return \%h;
}
__PACKAGE__->meta->make_immutable;
}
my $message = Message->new( Attr => 'foo' );
my $ua = Client->new;
my %h = %{ $ua->serialize( $message )};
use Data::Dumper::Concise;
say Dumper \%h
problem is that my around block is never being run and I'm not sure why, maybe I'm wrapping it in the wrong place or something.
MooseX::Aliases has several moving parts to make this functionality happen, that's because the behavior needs to be applied to several different places in the MOP. Your code here looks very close to the code in MooseX::Aliases's Trait attribute.
I suspect the reason your code isn't being called is due to something going wrong when you try to register your trait. MooseX::Aliases uses Moose::Util::meta_attribute_alias rather than the old fashioned way you're using here. Try replacing your Moose::Meta::Attribute::Custom::Trait::OtherName section with a call to Moose::Util::meta_attribute_alias 'OtherName'; inside your Role.
Second the code you have here won't work for immutable classes. You'll need to add a second trait to handle those because the immutability code is handled by the class's metaclass and not the attribute's metaclass. You'll need to add some more traits to handle attributes in Roles as well I think. Then you'll need to wire up an Moose::Exporter to make sure that all the traits are applied properly when everything is compiled.
I've gotten a simple version of this working up through immutable. This code is also on github.
First the Attribute trait:
package MooseX::AltInitArg::Meta::Trait::Attribute;
use Moose::Role;
use namespace::autoclean;
Moose::Util::meta_attribute_alias 'AltInitArg';
has alt_init_arg => (
is => 'ro',
isa => 'Str',
predicate => 'has_alt_init_arg',
);
around initialize_instance_slot => sub {
my $orig = shift;
my $self = shift;
my ($meta_instance, $instance, $params) = #_;
return $self->$orig(#_)
# don't run if we haven't set any alt_init_args
# don't run if init_arg is explicitly undef
unless $self->has_alt_init_arg && $self->has_init_arg;
if (my #alternates = grep { exists $params->{$_} } ($self->alt_init_arg)) {
if (exists $params->{ $self->init_arg }) {
push #alternates, $self->init_arg;
}
$self->associated_class->throw_error(
'Conflicting init_args: (' . join(', ', #alternates) . ')'
) if #alternates > 1;
$params->{ $self->init_arg } = delete $params->{ $alternates[0] };
}
$self->$orig(#_);
};
1;
__END__
Next the Class trait.
package MooseX::AltInitArg::Meta::Trait::Class;
use Moose::Role;
use namespace::autoclean;
around _inline_slot_initializer => sub {
my $orig = shift;
my $self = shift;
my ($attr, $index) = #_;
my #orig_source = $self->$orig(#_);
return #orig_source
# only run on aliased attributes
unless $attr->meta->can('does_role')
&& $attr->meta->does_role('MooseX::AltInitArg::Meta::Trait::Attribute');
return #orig_source
# don't run if we haven't set any aliases
# don't run if init_arg is explicitly undef
unless $attr->has_alt_init_arg && $attr->has_init_arg;
my $init_arg = $attr->init_arg;
return (
'if (my #aliases = grep { exists $params->{$_} } (qw('
. $attr->alt_init_arg . '))) {',
'if (exists $params->{' . $init_arg . '}) {',
'push #aliases, \'' . $init_arg . '\';',
'}',
'if (#aliases > 1) {',
$self->_inline_throw_error(
'"Conflicting init_args: (" . join(", ", #aliases) . ")"',
) . ';',
'}',
'$params->{' . $init_arg . '} = delete $params->{$aliases[0]};',
'}',
#orig_source,
);
};
1;
__END__
Finally the Moose::Exporter glue.
package MooseX::AltInitArg;
use Moose();
use Moose::Exporter;
use MooseX::AltInitArg::Meta::Trait::Attribute;
Moose::Exporter->setup_import_methods(
class_metaroles => { class => ['MooseX::AltInitArg::Meta::Trait::Class'] }
);
1;
__END__
An example of how this is used then:
package MyApp;
use 5.10.1;
use Moose;
use MooseX::AltInitArg;
has foo => (
is => 'ro',
traits => ['AltInitArg'],
alt_init_arg => 'bar',
);
my $obj = MyApp->new( bar => 'bar' );
say $obj->foo; # prints bar
Meta-Programming in Moose is incredibly powerful, but because there are a lot of moving parts (many of which have solely to do with maximizing performance) you bite off a lot of work when you dive in.
Good luck.
I could be wrong but I think you might be able to accomplish what I think you are trying to do using the BUILDARGS method. This lets you munge the contructor arguments before they are used to create the object.
#!/usr/bin/env perl
use strict;
use warnings;
{
package MyClass;
use Moose;
has attr => (
is => 'ro',
isa => 'Str',
required => 1,
);
around BUILDARGS => sub {
my $orig = shift;
my $self = shift;
my %args = ref $_[0] ? %{shift()} : #_;
if (exists $args{attribute}) {
$args{attr} = delete $args{attribute};
}
$self->$orig(%args);
};
}
my $one = MyClass->new(attribute => "Hi");
my $two = MyClass->new(attr => "Bye");
print $one->attr, "\n";
print $two->attr, "\n";
So what I'm hearing is that:
At construction time, an attribute should be able to be set by its init_arg and any alternate init_args defined on the attribute.
An attribute should not be able to be manipulated by its alternate init_args except at instance construction; that is, aside from the above, the attribute should behave "normally".
Based on that, this seems like a good match for the MooseX::MultiInitArg attribute trait. Yes? :)
I need some help regarding the arrays in Perl
This is the constructor I have.
BuildPacket.pm
sub new {
my $class = shift;
my $Packet = {
_PacketName => shift,
_Platform => shift,
_Version => shift,
_IncludePath => [#_],
};
bless $Packet, $class;
return $Packet;
}
sub SetPacketName {
my ( $Packet, $PacketName ) = #_;
$Packet->{_PacketName} = $PacketName if defined($PacketName);
return $Packet->{_PacketName};
}
sub SetIncludePath {
my ( $Packet, #IncludePath ) = #_;
$Packet->{_IncludePath} = \#IncludePath;
}
sub GetPacketName {
my( $Packet ) = #_;
return $Packet->{_PacketName};
}
sub GetIncludePath {
my( $Packet ) = #_;
#{ $Packet->{_IncludePath} };
}
(The code has been modified according to the suggestions from 'gbacon', thank you)
I am pushing the relative paths into 'includeobjects' array in a dynamic way. The includepaths are being read from an xml file and are pushed into this array.
# PacketInput.pm
if($element eq 'Include')
{
while( my( $key, $value ) = each( %attrs ))
{
if($key eq 'Path')
push(#includeobjects, $value);
}
}
So, the includeobject will be this way:
#includeobjects = (
"./input/myMockPacketName",
"./input/myPacket/my3/*.txt",
"./input/myPacket/in.html",
);
I am using this line for set include path
$newPacket->SetIncludePath(#includeobjects);
Also in PacketInput.pm, I have
sub CreateStringPath
{
my $packet = shift;
print "printing packet in CreateStringPath".$packet."\n";
my $append = "";
my #arr = #{$packet->GetIncludePath()};
foreach my $inc (#arr)
{
$append = $append + $inc;
print "print append :".$append."\n";
}
}
I have many packets, so I am looping through each packet
# PacketCreation.pl
my #packets = PacketInput::GetPackets();
foreach my $packet (PacketInput::GetPackets())
{
print "printing packet in loop packet".$packet."\n";
PacketInput::CreateStringPath($packet);
$packet->CreateTar($platform, $input);
$packet->GetValidateOutputFile($platform);
}
The get and set methods work fine for PacketName. But since IncludePath is an array, I could not get it to work, I mean the relative paths are not being printed.
If you enable the strict pragma, the code doesn't even compile:
Global symbol "#_IncludePath" requires explicit package name at Packet.pm line 15.
Global symbol "#_IncludePath" requires explicit package name at Packet.pm line 29.
Global symbol "#_IncludePath" requires explicit package name at Packet.pm line 30.
Global symbol "#_IncludePath" requires explicit package name at Packet.pm line 40.
Don't use # unquoted in your keys because it will confuse the parser. I recommend removing them entirely to avoid confusing human readers of your code.
You seem to want to pull all the attribute values from the arguments to the constructor, so continue peeling off the scalar values with shift, and then everything left must be the include path.
I assume that the components of the include path will be simple scalars and not references; if the latter is the case, then you'll want to make deep copies for safety.
sub new {
my $class = shift;
my $Packet = {
_PacketName => shift,
_Platform => shift,
_Version => shift,
_IncludePath => [ #_ ],
};
bless $Packet, $class;
}
Note that there's no need to store the blessed object in a temporary variable and then immediately return it because of the semantics of Perl subs:
If no return is found and if the last statement is an expression, its value is returned.
The methods below will also make use of this feature.
Given the constructor above, GetIncludePath becomes
sub GetIncludePath {
my( $Packet ) = #_;
my #path = #{ $Packet->{_IncludePath} };
wantarray ? #path : \#path;
}
There are a couple of things going on here. First, note that we're careful to return a copy of the include path rather than a direct reference to the internal array. This way, the user can modify the value returned from GetIncludePath without having to worry about mucking up the packet's state.
The wantarray operator allows a sub to determine the context of its call and respond accordingly. In list context, GetIncludePath will return the list of values in the array. Otherwise, it returns a reference to a copy of the array. This way, client code can call it either as in
foreach my $path (#{ $packet->GetIncludePath }) { ... }
or
foreach my $path ($packet->GetIncludePath) { ... }
SetIncludePath is then
sub SetIncludePath {
my ( $Packet, #IncludePath ) = #_;
$Packet->{_IncludePath} = \#IncludePath;
}
Note that you could have used similar code in the constructor rather than removing one parameter at a time with shift.
You might use the class defined above as in
#! /usr/bin/perl
use strict;
use warnings;
use Packet;
sub print_packet {
my($p) = #_;
print $p->GetPacketName, "\n",
map(" - [$_]\n", $p->GetIncludePath),
"\n";
}
my $p = Packet->new("MyName", "platform", "v1.0", qw/ foo bar baz /);
print_packet $p;
my #includeobjects = (
"./input/myMockPacketName",
"./input/myPacket/my3/*.txt",
"./input/myPacket/in.html",
);
$p->SetIncludePath(#includeobjects);
print_packet $p;
print "In scalar context:\n";
foreach my $path (#{ $p->GetIncludePath }) {
print $path, "\n";
}
Output:
MyName
- [foo]
- [bar]
- [baz]
MyName
- [./input/myMockPacketName]
- [./input/myPacket/my3/*.txt]
- [./input/myPacket/in.html]
In scalar context:
./input/myMockPacketName
./input/myPacket/my3/*.txt
./input/myPacket/in.html
Another way to reduce typing is to use Moose.
package Packet;
use Moose::Policy 'Moose::Policy::JavaAccessors';
use Moose;
has 'PacketName' => (
is => 'rw',
isa => 'Str',
required => 1,
);
has 'Platform' => (
is => 'rw',
isa => 'Str',
required => 1,
);
has 'Version' => (
is => 'rw',
isa => 'Int',
required => 1,
);
has 'IncludePath' => (
is => 'ro',
isa => 'ArrayRef[Str]',
default => sub {[]},
traits => [ 'Array' ],
handles => {
getIncludePath => 'elements',
getIncludePathMember => 'get',
setIncludePathMember => 'set',
},
);
__PACKAGE__->meta->make_immutable;
no Moose;
1;
Check out Moose::Manual::Unsweetened for another example of how Moose saves time.
If you are adamant in your desire to learn classical Perl OOP, read the following perldoc articles: perlboot, perltoot, perlfreftut and perldsc.
A great book about classical Perl OO is Damian Conway's Object Oriented Perl. It will give you a sense of the possibilities in Perl's object.
Once you understand #gbacon's answer, you can save some typing by using Class::Accessor::Fast:
#!/usr/bin/perl
package My::Class;
use strict; use warnings;
use base 'Class::Accessor::Fast';
__PACKAGE__->follow_best_practice;
__PACKAGE__->mk_accessors( qw(
IncludePath
PacketName
Platform
Version
));
use overload '""' => 'to_string';
sub to_string {
my $self = shift;
sprintf(
"%s [ %s:%s ]: %s",
$self->get_PacketName,
$self->get_Platform,
$self->get_Version,
join(':', #{ $self->get_IncludePath })
);
}
my $obj = My::Class->new({
PacketName => 'dummy', Platform => 'Linux'
});
$obj->set_IncludePath([ qw( /home/include /opt/include )]);
$obj->set_Version( '1.05b' );
print "$obj\n";
I'm having difficulty using MooseX::Declare properly when calling BUILDARGS.
I'm trying to create an object as an interface for a file. (Specifically, I want an interface to a binary file that lets me peek at the next few bytes in the file then chomp them off for further processing.)
I want to be able to create one of these objects like this
my $f = binary_file_buffer->new( $file_name );
and then use it like this
while( my $block_id = $f->peek( $id_offset, $id_length ) ) {
$block_id = unpack_block_id( $block_id );
$munge_block{ $block_id }->(
$f->pop( $block_size[ $block_id ] )
);
}
My of binary_file_buffer class definition/declaration looks like this
use MooseX::Declare;
class binary_file_buffer {
use FileHandle;
use Carp;
has _file => ( is => 'ro', isa => 'FileHandle' );
has _file_name => ( is => 'ro', isa => 'Str' );
has _buff => ( is => 'rw', isa => 'Str', default => '' );
method BUILDARGS ( Str $file_name ) {
my $file = FileHandle->new( $file_name );
carp "unable to open $file_name : $!" unless defined $file;
$file->binmode;
return (
_file_name => $file_name,
_file => $file,
);
}
# get the next n bytes from the buffer.
method pop ( Int $len ) {
# ... Make sure there is data in _buff
return substr( $self->{_buff}, 0, $len, '' );
}
# Look around inside the buffer without changing the location for pop
method peek ( Int $offset, Int $len ) {
# ... Make sure there is data in _buff
return substr( $self->{_buff}, $offset, $len );
}
}
(There is buffer loading and managing code that I didn't include here. It is fairly straight forward.)
The problem is, I use the keyword method in the BUILDARGS declaration. So, MooseX::Declare expects a binary_file_buffer object as the first argument to BUILDARGS. But BUILDARGS gets the arguments passed to new, so the first argument is the string a 'binary_file_buffer', the name of the package. As a result it fails the type checking and dies when creating an object using new, like I did in the first code snippet. (At least that's my understanding of what is happening.)
The error message I get is:
Validation failed for 'MooseX::Types::Structured::Tuple[MooseX::Types::Structured::Tuple[Object,Str,Bool],MooseX::Types::Structured::Dict[]]' failed with value [ [ "binary_file_buffer", "drap_iono_t1.log", 0 ], { } ], Internal Validation Error is: Validation failed for 'MooseX::Types::Structured::Tuple[Object,Str,Bool]' failed with value [ "binary_file_buffer", "drap_iono_t1.log", 0 ] at C:/bin/perl/site/lib/MooseX/Method/Signatures/Meta/Method.pm line 445
MooseX::Method::Signatures::Meta::Method::validate('MooseX::Method::Signatures::Meta::Method=HASH(0x2a623b4)', 'ARRAY(0x2a62764)') called at C:/bin/perl/site/lib/MooseX/Method/Signatures/Meta/Method.pm line 145
binary_file_buffer::BUILDARGS('binary_file_buffer', 'drap_iono_t1.log') called at generated method (unknown origin) line 5
binary_file_buffer::new('binary_file_buffer', 'drap_iono_t1.log') called at logshred.pl line 13
I like the type checking sugar the method keyword supplies for $file_name, but I don't know how to get it since BUILDARGS isn't technically a method.
Does MooseX::Declare have a way to skip the $self creation, or something like that?
Am I doing this the proper MooseX::Declare way? Or am I missing something?
I think you want something like method BUILDARGS (ClassName $class: Str $filename) { ... } in which you explicitly define the invocant as ClassName $class.
I think you want:
#!/use/bin/perl
use strict;
use warnings;
use MooseX::Declare;
class BinaryFile::Buffer {
use FileHandle;
use Carp;
has file => ( is => 'ro', isa => 'FileHandle');
has file_name => ( is => 'ro', isa => 'Str');
has _buff => (
is => 'rw',
isa => 'Str',
default => '',
init_arg => undef
);
sub BUILDARGS {
my ($class, $file_name) = #_;
my $file = FileHandle->new( $file_name ) or do {
carp "unable to open ", $file_name, " : $!";
return;
};
$file->binmode;
return $class->SUPER::BUILDARGS(
file_name => $file_name,
file => $file
);
}
# get the next n bytes from the buffer.
method pop(Int $len) {
# ... Make sure there is data in _buff
return substr( $self->buff, 0, $len, '' );
}
# Look around inside the buffer without changing the location for pop
method peek(Int $offset, Int $len) {
# ... Make sure there is data in _buff
return substr( $self->buff, $offset, $len );
}
}
my $f = BinaryFile::Buffer->new($0);
print $f->file_name, "\n";
also a really neat way of doing it (just an expansion of the answer before me):
use MooseX::MultiMethods;
multi method BUILDARGS (ClassName $class: Str $filename) {
#do whatever you want to do if only a strg is passed
}
that way, MooseX::MultiMethods will take care that if you do NOT call
FileHandle->new( $file_name ),
but
FileHandle->new(
_filename => $file_name
);
(which is the normal syntax),
it would still work!
Also, you could ( which is not so useful for filenames but in other cases )
add a
multi method ( ClassName $class, Int $some_number ){}
that way, new could now handle hashrefs, integers and strings...
oh the possibilities... ;)