File Coverage

blib/lib/MooseX/Struct.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package MooseX::Struct;
2              
3 12     12   352155 use warnings;
  12         37  
  12         422  
4 12     12   71 use strict;
  12         23  
  12         402  
5              
6 12     12   13748 use Moose ();
  0            
  0            
7             use Sub::Install;
8             use Carp;
9              
10             our $VERSION = '0.06';
11              
12             sub import {
13             my $class = shift;
14              
15             strict->import;
16             warnings->import;
17              
18             if (@_) {
19             my ($name, $attributes) = _parse_arg_structure(@_);
20              
21             croak 'Can not build structure out of "main"' if $name eq 'main';
22              
23             Moose->import({into => $name});
24              
25             _build_struct($name, $attributes);
26              
27             Sub::Install::install_sub({
28             into => $name,
29             code => \&immutable,
30             as => 'immutable',
31             });
32             } else {
33             my $caller = caller;
34              
35             Moose->import({into => $caller}) unless $caller eq 'main';
36              
37             Sub::Install::install_sub({
38             into => $caller,
39             code => \&immutable,
40             as => 'immutable',
41             });
42             Sub::Install::install_sub({
43             into => $caller,
44             code => \&struct,
45             as => 'struct',
46             });
47             }
48             }
49              
50             sub _parse_arg_structure {
51             my ($name, $attr);
52              
53             my $caller = (caller(1))[0];
54              
55             # Check POD for cases
56             if (@_ == 1) {
57             # One arg, assume ( {} )
58             ($name,$attr) = ($caller, shift);
59             } elsif (@_ == 2 && ref $_[1] eq 'HASH') {
60             # 2 args, second hashref, assume ('', {})
61             ($name,$attr) = (shift, shift);
62             } else {
63             # True: Odd number of args, assume ('', ( '' => $ ))
64             # False: Even number of args (second one not hashref), ( ( '' => $ ) )
65             $name = (@_ % 2) ? shift : $caller;
66              
67             my ($attr_name, $attr_spec);
68              
69             while (my $attr_name = shift) {
70             $attr_spec = shift;
71             if (ref $attr_name eq 'ARRAY') {
72             $attr->{$_} = $attr_spec foreach @$attr_name;
73             } else {
74             $attr->{$attr_name} = $attr_spec;
75             }
76             }
77             }
78              
79             # simple check for valid args, else print usage.
80             if (!($name || ref $attr eq 'HASH')) {
81             croak _usage();
82             }
83              
84             return ($name, $attr);
85             }
86              
87             sub immutable {
88             my $class = shift || return;
89              
90             return if not eval { $class->can('meta') };
91              
92             $class->meta->make_immutable;
93             }
94              
95             sub struct {
96             my ($name, $attributes) = _parse_arg_structure(@_);
97              
98             return _build_struct($name,$attributes);
99             }
100              
101             sub _build_struct {
102             my $name = shift;
103             my $attributes = shift;
104              
105             ### Initialize $name as a Moose object (inherits, by default, from Moose::Object)
106             Moose::init_meta($name);
107              
108             ### imports moose functions into $name package, not necessarily caller's
109             Moose->import({into => $name});
110              
111             foreach my $attr_name (keys %$attributes) {
112             my $type = $attributes->{$attr_name};
113             my $attr_spec;
114             if (ref $type eq 'HASH') {
115             $attr_spec = $type;
116             } elsif (_types($type)) {
117             $attr_spec = _types($type);
118             } else {
119             ### Else let Class::Mop parse it as an 'isa' value.
120             $attr_spec = { is => 'rw', isa => $type }
121             }
122             if ($attr_name =~ /^ARRAY\([\d\w]x[\d\w]+\)$/) {
123             croak "MooseX::Struct - \n".
124             " It looks like you tried to supply an array reference to ".
125             "declare multiple attributes at once. This is only possible ".
126             "when using parantheses and not curly brackets due to the way ".
127             "perl stringifies hash keys. See perldoc MooseX::Struct for ".
128             "more information";
129             }
130             $name->meta->add_attribute( $attr_name, %$attr_spec );
131             }
132              
133             return $name;
134             }
135            
136             sub _usage {
137             return q/
138             Invalid arguments passed to struct(). MooseX::Struct usage:
139             struct ( ['Object::Name',] %hash|$hashref );
140             e.g.
141             struct 'MyObject' => (
142             attribute => 'Scalar',
143             );
144             /;
145             }
146              
147             {
148             my $map = {
149             '$' => { is => 'rw', isa => 'Value' },
150             '*$' => { is => 'rw', isa => 'ScalarRef' },
151             '@' => { is => 'rw', isa => 'ArrayRef'},
152             '*@' => { is => 'rw', isa => 'ArrayRef'},
153             '%' => { is => 'rw', isa => 'HashRef'},
154             '*%' => { is => 'rw', isa => 'HashRef'},
155             '*' => { is => 'rw', isa => 'GlobRef'},
156             '#' => { is => 'rw', isa => 'Num'},
157             '1' => { is => 'rw', isa => 'Int'},
158             'w' => { is => 'rw', isa => 'Str'},
159             'rx' => { is => 'rw', isa => 'RegexpRef'},
160             '&' => { is => 'rw', isa => 'CodeRef'},
161             '?' => { is => 'rw' },
162             '!' => { is => 'rw', isa => 'Bool'},
163             'rw' => { is => 'rw' },
164             'ro' => { is => 'ro' },
165             };
166              
167             $map->{'array'} = $map->{'@'};
168             $map->{'arrayref'} = $map->{'*@'};
169             $map->{'hash'} = $map->{'%'};
170             $map->{'hashref'} = $map->{'*%'};
171             $map->{'scalar'} = $map->{'$'};
172             $map->{'scalarref'} = $map->{'*$'};
173             $map->{'glob'} = $map->{'*'};
174             $map->{'number'} = $map->{'#'};
175             $map->{'string'} = $map->{'w'};
176             $map->{'regex'} = $map->{'rx'};
177             $map->{'any'} = $map->{'?'};
178             $map->{'bool'} = $map->{'!'};
179             $map->{'boolean'} = $map->{'!'};
180             $map->{'int'} = $map->{'1'};
181             $map->{'integer'} = $map->{'1'};
182             $map->{''} = $map->{'?'};
183              
184             sub _types {
185             my $type = shift;
186             if (defined $type) {
187             return $map->{lc $type} || undef;
188             } else {
189             if (wantarray) {
190             return keys %$map;
191             } else {
192             no warnings 'uninitialized';
193             print "+----------------+-----------------------+\n";
194             print "| MooseX::Struct | Moose/Class::MOP type |\n";
195             print "+----------------+-----------------------+\n";
196             printf("| %14s | %-21s |\n", $_, $map->{$_}->{isa}) foreach sort keys %$map;
197             print "+----------------+-----------------------+\n";
198             }
199             }
200             }
201             }
202              
203             1;
204              
205             __END__
206              
207             =head1 MooseX::Struct
208              
209             MooseX::Struct - Struct-like interface for Moose Object creation
210              
211             =head1 Version
212              
213             Version 0.06
214              
215             =cut
216              
217             =head1 Synopsis
218              
219             use MooseX::Struct;
220              
221             struct 'MyClass::Foo' => (
222             bar => 'Scalar',
223             baz => 'Array',
224             );
225            
226             my $obj = new MyClass::Foo;
227            
228             $obj->bar(44); # sets $obj->{bar} to 44
229            
230             print $obj->bar; # prints 44
231            
232             ### or
233              
234             package MyClass::Foo;
235             use MooseX::Struct;
236              
237             ### This will default to the current package : 'MyClass::Foo'
238              
239             struct (
240             bar => 'Scalar',
241             baz => 'Array',
242             );
243              
244             ### or create your struct at compile-time
245            
246             use MooseX::Struct 'MyClass::Foo' => (
247             bar => 'Scalar',
248             baz => 'Array',
249             );
250              
251             ### Immutable Moose Objects
252              
253             package MyClass::Foo;
254             use MooseX::Struct;
255              
256             immutable struct (
257             bar => 'Scalar',
258             baz => 'Array',
259             );
260              
261             =head1 Description
262              
263             This module is a reimplementation of the core L<Class::Struct> package for
264             the L<Moose> Object System. The original Class::Struct is a very useful
265             package but offers little to no extensibility as soon as you outgrow its
266             features.
267              
268             =head2 For the Class::Struct users:
269              
270             This is not a drop-in replacement (though
271             for most common cases, it I<is> a drop in replacement), it works somewhat
272             differently and has different performance concerns.
273              
274             =head2 For Moose users:
275              
276             This can be used as an alternate way to create Moose objects. All exports
277             that normally come from 'use Moose' are exported to the specified package,
278             or the current package if none given (unless the current package is 'main').
279              
280             A lot of this package passes off work to L<Moose> and L<Class::MOP>, so
281             both of those should be considered good reading recommendations.
282              
283             =head1 Exports
284              
285             MooseX::Struct exports two functions, C<struct> and C<immutable>, to the caller's
286             namespace.
287              
288             =head2 C<immutable>
289              
290             C<immutable()> is a convenience method that takes in a class name and calls
291             CLASS->meta->make_immutable(). Since struct() returns the class name of the
292             object it just defined, you can write out very nice looking code such as:
293              
294             immutable struct 'MyClass' => ( class definition );
295              
296             =head2 C<struct>
297              
298             The C<struct> function can be passed parameters in four forms but boil
299             down to :
300              
301             struct( ['Class Name',] %hash|$hashref );
302              
303             Omitting the 'Class Name' argument allows MooseX::Struct to default to
304             the current package's namespace.
305              
306             Because you do not need parantheses for predefined functions and the
307             C<< => >> is a synonym for C<,>, the above can be written in a more
308             attractive way :
309              
310             struct 'My::Class' => (
311             attribute => 'type',
312             );
313              
314             Thus the following three forms are:
315              
316             struct 'My::Class' => {
317             attribute => 'type',
318             };
319            
320             struct (
321             attribute => 'type',
322             );
323            
324             struct {
325             attribute => 'type',
326             };
327              
328             The last two would default to the current package name.
329              
330             =head1 Compile-time declaration of a struct
331              
332             Like Class::Struct, MooseX::Struct allows you to specify a class at
333             compile time by passing the appropriate definition to MooseX::Struct at import.
334              
335             e.g.
336              
337             use MooseX::Struct 'My::Class' => (
338             attribute => 'type',
339             );
340              
341             Again, like Class::Struct, there is no real time savings, but you do
342             get a more logical flow of events and it does get all of the hard work
343             done at startup.
344              
345             =head1 Attributes
346              
347             Attributes all take the form of a hash key/value pair with the hash key
348             being the name of the attribute and the default name of the accessor,
349             and the value being a predefined type alias (see below). All attributes
350             are read/write by default (is => 'rw'). Advanced attributes can be made
351             by specifying a hashref of acceptible attribute specifications (see
352             C<Class::MOP::Attribute>) instead of a type alias, e.g.
353              
354             struct 'My::Class' => (
355             foo => 'Scalar',
356             bar => { accessor => 'quux' }
357             baz => { is => 'ro', reader => 'get_baz', [etc] }
358             );
359              
360             =head2 Note / Warning / Not a bug
361              
362             Multiple attributes can be declared at once in an array reference B<while being
363             defined within parantheses> as opposed to curly brackets (i.e., as a standard
364             array of arguments as opposed to a hash / hash reference). This is due to perl
365             stringifying references in order to use them as hash keys and the fact that perl
366             can't dereference them after that happens.
367              
368             =head1 Types
369              
370             These are used to constrain an attribute's value to a certain data type
371             (isa => 'Type').
372              
373             Types are case-insensitive for matching purposes, but you can specify a
374             type that is not listed here and it will be passed through unchanged
375             to Moose::Meta::Class / Class::MOP::Class. So if you are familiar with
376             advanced types or have created your own type constraints, you can still
377             use MooseX::Struct.
378            
379             +----------------+-----------------------+
380             | MooseX::Struct | Moose/Class::MOP type |
381             +----------------+-----------------------+
382             | '' | [No type constraint] |
383             | ? | [No type constraint] |
384             | any | [No type constraint] |
385             | ro | [Read Only - No Type] |
386             | rw | [Read/Write - No Type]|
387             | ! | Bool |
388             | # | Num |
389             | 1 | Int |
390             | $ | Value |
391             | *$ | ScalarRef |
392             | @ | ArrayRef |
393             | *@ | ArrayRef |
394             | % | HashRef |
395             | *% | HashRef |
396             | & | CodeRef |
397             | * | GlobRef |
398             | w | Str |
399             | rx | RegexpRef |
400             | int | Int |
401             | integer | Int |
402             | number | Num |
403             | scalar | Value |
404             | scalarref | ScalarRef |
405             | array | ArrayRef |
406             | arrayref | ArrayRef |
407             | hash | HashRef |
408             | hashref | HashRef |
409             | bool | Bool |
410             | boolean | Bool |
411             | glob | GlobRef |
412             | regex | RegexpRef |
413             | string | Str |
414             +----------------+-----------------------+
415              
416             =head1 Notes
417              
418             =head2 strict and warnings are imported automatically
419              
420             By issuing a C<use MooseX::Struct>, same as with C<use>ing Moose, strict
421             and warnings are automatically imported into the calling package.
422              
423             =head2 Differences from Class::Struct
424              
425             The accessors that are created for each attribute are simple read / write
426             accessors. They will attempt to assign any passed value to the attribute,
427             and they will return the whole value on access.
428              
429             # For an object 'foo' with an attribute 'bar' of type ArrayRef:
430              
431             $foo->bar([1,2,3]); # sets bar to [1,2,3]
432              
433             $foo->bar; # returns [1,2,3]
434              
435             $foo->bar(0); # Attempts to set bar to 0 and errors out because
436             # 0 is not an array reference. Class::Struct would
437             # have given you the element at index 0;
438              
439             $foo->bar->[0] # Correct
440              
441             The types have been changed and extended. There are no '%' or '@' types that
442             indicate 'Hash' and 'Array,' respectively. Both of those symbols now refer
443             to the reference of the type.
444              
445             =head1 Author
446              
447             Jarrod Overson, C<< <jsoverson at googlemail.com> >>
448              
449             =head1 Bugs
450              
451             Of course there could be bugs with use cases I hadn't thought of
452             during testing, but most of this module's work passes off to Class::MOP or Moose,
453             so if you find a bug, please do some testing to determine where the actual bug
454             is occurring.
455              
456             Please report any bugs or feature requests to C<bug-moosex-struct at rt.cpan.org>, or through
457             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=MooseX-Struct>. I will be notified, and then you'll
458             automatically be notified of progress on your bug as I make changes.
459              
460             =head1 Support
461              
462             You can find documentation for this module with the perldoc command.
463              
464             perldoc MooseX::Struct
465              
466              
467             You can also look for information at:
468              
469             =over 4
470              
471             =item * RT: CPAN's request tracker
472              
473             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=MooseX-Struct>
474              
475             =item * AnnoCPAN: Annotated CPAN documentation
476              
477             L<http://annocpan.org/dist/MooseX-Struct>
478              
479             =item * CPAN Ratings
480              
481             L<http://cpanratings.perl.org/d/MooseX-Struct>
482              
483             =item * Search CPAN
484              
485             L<http://search.cpan.org/dist/MooseX-Struct>
486              
487             =back
488              
489              
490             =head1 Acknowledgements
491              
492             Thanks to everyone who worked on Class::Struct for providing us a very clean interface
493             for creating intuitive, logical data structures within perl.
494              
495             And thanks to everyone who has worked on Moose for providing a somewhat complicated
496             method of creating extremely powerful and extensible data structures within perl.
497              
498             =head1 Copyright & License
499              
500             Copyright 2008 Jarrod Overson, all rights reserved.
501              
502             This program is free software; you can redistribute it and/or modify it
503             under the same terms as Perl itself.
504              
505             =cut
506