File Coverage

blib/lib/Config/MVP/Assembler.pm
Criterion Covered Total %
statement 38 38 100.0
branch 13 18 72.2
condition 2 3 66.6
subroutine 11 11 100.0
pod 6 6 100.0
total 70 76 92.1


line stmt bran cond sub pod time code
1             package Config::MVP::Assembler;
2             # ABSTRACT: multivalue-property config-loading state machine
3             $Config::MVP::Assembler::VERSION = '2.200012';
4 4     4   3538 use Moose;
  4         477153  
  4         35  
5              
6 4     4   32241 use Config::MVP::Error;
  4         19  
  4         222  
7 4     4   2310 use Config::MVP::Sequence;
  4         18  
  4         181  
8 4     4   36 use Config::MVP::Section;
  4         9  
  4         2300  
9              
10             #pod =head1 DESCRIPTION
11             #pod
12             #pod First, you should probably read the L<example of using
13             #pod Config::MVP|Config::MVP/EXAMPLE>. If you already know how it works, keep
14             #pod going.
15             #pod
16             #pod Config::MVP::Assembler is a helper for constructing a Config::MVP::Sequence
17             #pod object. It's a very simple state machine that lets you signal what kind of
18             #pod events you've encountered while reading configuration.
19             #pod
20             #pod =head1 TYPICAL USE
21             #pod
22             #pod my $assembler = Config::MVP::Assembler->new;
23             #pod
24             #pod # Maybe you want a starting section:
25             #pod my $starting_section = $assembler->section_class->new({ name => '_' });
26             #pod $assembler->sequence->add_section($section_starting);
27             #pod
28             #pod # We'll add some values, which will go to the starting section:
29             #pod $assembler->add_value(x => 10);
30             #pod $assembler->add_value(y => 20);
31             #pod
32             #pod # Change to a new section...
33             #pod $assembler->change_section($moniker);
34             #pod
35             #pod # ...and add values to that section.
36             #pod $assembler->add_value(x => 100);
37             #pod $assembler->add_value(y => 200);
38             #pod
39             #pod The code above creates an assembler and populates it step by step. In the end,
40             #pod to get values, you could do something like this:
41             #pod
42             #pod my @output;
43             #pod
44             #pod for my $section ($assembler->sequence->sections) {
45             #pod push @output, [ $section->name, $section->package, $section->payload ];
46             #pod }
47             #pod
48             #pod When changing sections, the given section "moniker" is used for the new section
49             #pod name. The result of passing that moniker to the assembler's
50             #pod C<L</expand_package>> method is used as the section's package name. (By
51             #pod default, this method does nothing.) The new section's C<multivalue_args> and
52             #pod C<aliases> are determined by calling the C<mvp_multivalue_args> and
53             #pod C<mvp_aliases> methods on the package.
54             #pod
55             #pod =attr sequence_class
56             #pod
57             #pod This attribute stores the name of the class to be used for the assembler's
58             #pod sequence. It defaults to Config::MVP::Sequence.
59             #pod
60             #pod =cut
61              
62             has sequence_class => (
63             is => 'ro',
64             isa => 'ClassName',
65             lazy => 1,
66             default => 'Config::MVP::Sequence',
67             );
68              
69             #pod =attr section_class
70             #pod
71             #pod This attribute stores the name of the class to be used for sections created by
72             #pod the assembler. It defaults to Config::MVP::Section.
73             #pod
74             #pod =cut
75              
76             has section_class => (
77             is => 'ro',
78             isa => 'ClassName',
79             lazy => 1,
80             default => 'Config::MVP::Section',
81             );
82              
83             #pod =attr sequence
84             #pod
85             #pod This is the sequence that the assembler is assembling. It defaults to a new
86             #pod instance of the assembler's C<sequence_class>.
87             #pod
88             #pod =cut
89              
90             has sequence => (
91             is => 'ro',
92             isa => 'Config::MVP::Sequence',
93             default => sub { $_[0]->sequence_class->new({ assembler => $_[0] }) },
94             init_arg => undef,
95             handles => [ qw(is_finalized finalize) ],
96             );
97              
98             before finalize => sub {
99             my ($self) = @_;
100              
101             $self->end_section if $self->current_section;
102             };
103              
104             #pod =method begin_section
105             #pod
106             #pod $assembler->begin_section($package_moniker, $name);
107             #pod
108             #pod $assembler->begin_section($package_moniker);
109             #pod
110             #pod $assembler->begin_section( \$package );
111             #pod
112             #pod This method tells the assembler that it should begin work on a new section with
113             #pod the given identifier. If it is already working on a section, an error will be
114             #pod raised. See C<L</change_section>> for a method to begin a new section, ending
115             #pod the current one if needed.
116             #pod
117             #pod The package moniker is expanded by the C<L</expand_package>> method. The name,
118             #pod if not given, defaults to the package moniker. These data are used to create a
119             #pod new section and the section is added to the end of the sequence. If the
120             #pod package argument is a reference, it is used as the literal value for the
121             #pod package, and no expansion is performed. If it is a reference to undef, a
122             #pod section with no package is created.
123             #pod
124             #pod =cut
125              
126             has _between_sections => (
127             is => 'rw',
128             isa => 'Bool',
129             default => 0,
130             );
131              
132             sub begin_section {
133 18     18 1 2131 my ($self, $package_moniker, $name) = @_;
134              
135 18 50       66 Config::MVP::Error->throw("can't begin a new section while a section is open")
136             if $self->current_section;
137              
138 18 100 66     95 $name = $package_moniker unless defined $name and length $name;
139              
140 18 50       96 my $package = ref($package_moniker)
141             ? $$package_moniker
142             : $self->expand_package($package_moniker);
143              
144 18 50       483 my $section = $self->section_class->new({
145             name => $name,
146             (defined $package ? (package => $package) : ()),
147             });
148              
149 16         667 $self->_between_sections(0);
150 16         435 $self->sequence->add_section($section);
151             }
152              
153             #pod =method end_section
154             #pod
155             #pod $assembler->end_section;
156             #pod
157             #pod This ends the current section. If there is no current section, an exception is
158             #pod raised.
159             #pod
160             #pod =cut
161              
162             sub end_section {
163 16     16 1 1055 my ($self) = @_;
164              
165 16 50       36 Config::MVP::Error->throw("can't end a section when no section is active")
166             unless $self->current_section;
167              
168 16         48 $self->current_section->finalize;
169              
170 16         458 $self->_between_sections(1);
171             }
172              
173             #pod =method change_section
174             #pod
175             #pod $assembler->change_section($package_moniker, $name);
176             #pod
177             #pod $assembler->change_section($package_moniker);
178             #pod
179             #pod This method calls C<begin_section>, first calling C<end_section> if needed.
180             #pod
181             #pod =cut
182              
183             sub change_section {
184 11     11 1 5813 my $self = shift;
185              
186 11 100       59 $self->end_section if $self->current_section;
187 11         75 $self->begin_section(@_);
188             }
189              
190             #pod =method add_value
191             #pod
192             #pod $assembler->add_value( $name => $value );
193             #pod
194             #pod This method tells the assembler that it has encountered a named value and
195             #pod should add it to the current section. If there is no current section, an
196             #pod exception is raised. (If this is not the first time we've seen the name in the
197             #pod section and it's not a multivalue property, the section class will raise an
198             #pod exception on its own.)
199             #pod
200             #pod =cut
201              
202             sub add_value {
203 36     36 1 334 my ($self, $name, $value) = @_;
204              
205 36 50       79 Config::MVP::Error->throw("can't set value when no section is active")
206             unless my $section = $self->current_section;
207              
208 36         109 $section->add_value($name => $value);
209             }
210              
211             #pod =method expand_package
212             #pod
213             #pod This method is passed a short identifier for a package and is expected to
214             #pod return the full name of the module to load and package to interrogate. By
215             #pod default it simply returns the name it was passed, meaning that package names
216             #pod must be given whole to the C<change_section> method.
217             #pod
218             #pod =cut
219              
220 18     18 1 39 sub expand_package { $_[1] }
221              
222             #pod =method current_section
223             #pod
224             #pod This returns the section object onto which the assembler is currently adding
225             #pod values. If no section has yet been created, this method will return false.
226             #pod
227             #pod =cut
228              
229             sub current_section {
230 99     99 1 170 my ($self) = @_;
231              
232 99 100       2934 return if $self->_between_sections;
233 87         2259 my (@sections) = $self->sequence->sections;
234 87 100       2489 return $sections[ -1 ] if @sections;
235              
236 10         31 return;
237             }
238              
239 4     4   34 no Moose;
  4         9  
  4         29  
240             1;
241              
242             __END__
243              
244             =pod
245              
246             =encoding UTF-8
247              
248             =head1 NAME
249              
250             Config::MVP::Assembler - multivalue-property config-loading state machine
251              
252             =head1 VERSION
253              
254             version 2.200012
255              
256             =head1 DESCRIPTION
257              
258             First, you should probably read the L<example of using
259             Config::MVP|Config::MVP/EXAMPLE>. If you already know how it works, keep
260             going.
261              
262             Config::MVP::Assembler is a helper for constructing a Config::MVP::Sequence
263             object. It's a very simple state machine that lets you signal what kind of
264             events you've encountered while reading configuration.
265              
266             =head1 ATTRIBUTES
267              
268             =head2 sequence_class
269              
270             This attribute stores the name of the class to be used for the assembler's
271             sequence. It defaults to Config::MVP::Sequence.
272              
273             =head2 section_class
274              
275             This attribute stores the name of the class to be used for sections created by
276             the assembler. It defaults to Config::MVP::Section.
277              
278             =head2 sequence
279              
280             This is the sequence that the assembler is assembling. It defaults to a new
281             instance of the assembler's C<sequence_class>.
282              
283             =head1 METHODS
284              
285             =head2 begin_section
286              
287             $assembler->begin_section($package_moniker, $name);
288              
289             $assembler->begin_section($package_moniker);
290              
291             $assembler->begin_section( \$package );
292              
293             This method tells the assembler that it should begin work on a new section with
294             the given identifier. If it is already working on a section, an error will be
295             raised. See C<L</change_section>> for a method to begin a new section, ending
296             the current one if needed.
297              
298             The package moniker is expanded by the C<L</expand_package>> method. The name,
299             if not given, defaults to the package moniker. These data are used to create a
300             new section and the section is added to the end of the sequence. If the
301             package argument is a reference, it is used as the literal value for the
302             package, and no expansion is performed. If it is a reference to undef, a
303             section with no package is created.
304              
305             =head2 end_section
306              
307             $assembler->end_section;
308              
309             This ends the current section. If there is no current section, an exception is
310             raised.
311              
312             =head2 change_section
313              
314             $assembler->change_section($package_moniker, $name);
315              
316             $assembler->change_section($package_moniker);
317              
318             This method calls C<begin_section>, first calling C<end_section> if needed.
319              
320             =head2 add_value
321              
322             $assembler->add_value( $name => $value );
323              
324             This method tells the assembler that it has encountered a named value and
325             should add it to the current section. If there is no current section, an
326             exception is raised. (If this is not the first time we've seen the name in the
327             section and it's not a multivalue property, the section class will raise an
328             exception on its own.)
329              
330             =head2 expand_package
331              
332             This method is passed a short identifier for a package and is expected to
333             return the full name of the module to load and package to interrogate. By
334             default it simply returns the name it was passed, meaning that package names
335             must be given whole to the C<change_section> method.
336              
337             =head2 current_section
338              
339             This returns the section object onto which the assembler is currently adding
340             values. If no section has yet been created, this method will return false.
341              
342             =head1 TYPICAL USE
343              
344             my $assembler = Config::MVP::Assembler->new;
345              
346             # Maybe you want a starting section:
347             my $starting_section = $assembler->section_class->new({ name => '_' });
348             $assembler->sequence->add_section($section_starting);
349              
350             # We'll add some values, which will go to the starting section:
351             $assembler->add_value(x => 10);
352             $assembler->add_value(y => 20);
353              
354             # Change to a new section...
355             $assembler->change_section($moniker);
356              
357             # ...and add values to that section.
358             $assembler->add_value(x => 100);
359             $assembler->add_value(y => 200);
360              
361             The code above creates an assembler and populates it step by step. In the end,
362             to get values, you could do something like this:
363              
364             my @output;
365              
366             for my $section ($assembler->sequence->sections) {
367             push @output, [ $section->name, $section->package, $section->payload ];
368             }
369              
370             When changing sections, the given section "moniker" is used for the new section
371             name. The result of passing that moniker to the assembler's
372             C<L</expand_package>> method is used as the section's package name. (By
373             default, this method does nothing.) The new section's C<multivalue_args> and
374             C<aliases> are determined by calling the C<mvp_multivalue_args> and
375             C<mvp_aliases> methods on the package.
376              
377             =head1 AUTHOR
378              
379             Ricardo Signes <rjbs@cpan.org>
380              
381             =head1 COPYRIGHT AND LICENSE
382              
383             This software is copyright (c) 2021 by Ricardo Signes.
384              
385             This is free software; you can redistribute it and/or modify it under
386             the same terms as the Perl 5 programming language system itself.
387              
388             =cut