File Coverage

blib/lib/App/Sqitch/Plan/Depend.pm
Criterion Covered Total %
statement 55 56 98.2
branch 29 30 96.6
condition 22 24 91.6
subroutine 17 17 100.0
pod 7 8 87.5
total 130 135 96.3


line stmt bran cond sub pod time code
1             package App::Sqitch::Plan::Depend;
2              
3 50     50   1039 use 5.010;
  50         210  
4 50     50   306 use utf8;
  50         116  
  50         316  
5 50     50   1145 use Moo;
  50         134  
  50         311  
6 50     50   17853 use App::Sqitch::Types qw(Str Bool Maybe Plan);
  50         144  
  50         420  
7 50     50   48853 use App::Sqitch::Plan;
  50         129  
  50         1849  
8 50     50   332 use App::Sqitch::X qw(hurl);
  50         131  
  50         380  
9 50     50   14358 use Locale::TextDomain qw(App-Sqitch);
  50         138  
  50         383  
10 50     50   9462 use namespace::autoclean;
  50         153  
  50         350  
11              
12             our $VERSION = 'v1.4.0'; # VERSION
13              
14             has conflicts => (
15             is => 'ro',
16             isa => Bool,
17             default => 0,
18             );
19              
20             has got_id => (
21             is => 'ro',
22             isa => Bool,
23             required => 1
24             );
25              
26             has got_project => (
27             is => 'ro',
28             isa => Bool,
29             required => 1
30             );
31              
32             has project => (
33             is => 'ro',
34             isa => Maybe[Str],
35             lazy => 1,
36             default => sub {
37             my $self = shift;
38             my $plan = $self->plan;
39              
40             # Local project is the default unless an ID was passed.
41             return $plan->project unless $self->got_id;
42              
43             # Local project is default if passed ID is in plan.
44             return $plan->project if $plan->find( $self->id );
45              
46             # Otherwise, the project is unknown (and external).
47             return undef;
48             }
49             );
50              
51             has change => (
52             is => 'ro',
53             isa => Maybe[Str],
54             );
55              
56             has tag => (
57             is => 'ro',
58             isa => Maybe[Str],
59             );
60              
61             has plan => (
62             is => 'ro',
63             isa => Plan,
64             weak_ref => 1,
65             required => 1,
66             );
67              
68             has id => (
69             is => 'ro',
70             isa => Maybe[Str],
71             lazy => 1,
72             default => sub {
73             my $self = shift;
74             my $plan = $self->plan;
75             my $proj = $self->project // return undef;
76             return undef if $proj ne $plan->project;
77             my $change = $plan->find( $self->key_name ) // hurl plan => __x(
78             'Unable to find change "{change}" in plan {file}',
79             change => $self->key_name,
80             file => $plan->file,
81             );
82             return $change->id;
83             }
84             );
85              
86             has resolved_id => (
87             is => 'rw',
88             isa => Maybe[Str],
89             );
90              
91             has is_external => (
92             is => 'ro',
93             isa => Bool,
94             lazy => 1,
95             default => sub {
96             my $self = shift;
97              
98             # If no project, then it must be external.
99             my $proj = $self->project // return 1;
100              
101             # Just compare to the local project.
102             return $proj eq $self->plan->project ? 0 : 1;
103             },
104             );
105              
106 20 100   20 1 150 sub type { shift->conflicts ? 'conflict' : 'require' }
107 20 100   20 1 167 sub required { shift->conflicts ? 0 : 1 }
108 24 100   24 1 9380 sub is_internal { shift->is_external ? 0 : 1 }
109              
110             sub BUILDARGS {
111 495     495 0 140958 my $class = shift;
112 495 50 33     2761 my $p = @_ == 1 && ref $_[0] ? { %{ +shift } } : { @_ };
  0         0  
113             hurl 'Depend object must have either "change", "tag", or "id" defined'
114 495 100 100     2376 unless length($p->{change} // '') || length($p->{tag} // '') || $p->{id};
      100        
      100        
      100        
115              
116             hurl 'Depend object cannot contain both an ID and a tag or change'
117 494 100 100     1398 if $p->{id} && (length($p->{change} // '') || length($p->{tag} // ''));
      100        
118              
119 491 100       1341 $p->{got_id} = defined $p->{id} ? 1 : 0;
120 491 100       1260 $p->{got_project} = defined $p->{project} ? 1 : 0;
121              
122 491         8374 return $p;
123             }
124              
125             sub parse {
126 449     449 1 162760 my ( $class, $string ) = @_;
127 449         1493 my $name_re = Plan->class->name_regex;
128 449 100       13822 return undef if $string !~ /
129             \A # Beginning of string
130             (?<conflicts>!?) # Optional negation
131             (?:(?<project>$name_re)[:])? # Optional project + :
132             (?: # Followed by...
133             (?<id>[0-9a-f]{40}) # SHA1 hash
134             | # - OR -
135             (?<change>$name_re) # Change name
136             (?:[@](?<tag>$name_re))? # Optional tag
137             | # - OR -
138             (?:[@](?<tag>$name_re))? # Tag
139             ) # ... required
140             \z # End of string
141             /x;
142              
143 50 100   50   72406 return { %+, conflicts => $+{conflicts} ? 1 : 0 };
  50         21372  
  50         14645  
  439         11472  
144             }
145              
146             sub key_name {
147 856     856 1 7856 my $self = shift;
148 856         1457 my @parts;
149              
150 856 100       2600 if (defined (my $change = $self->change)) {
151 808         1692 push @parts => $change;
152             }
153              
154 856 100       2366 if (defined (my $tag = $self->tag)) {
155 274         838 push @parts => '@' . $tag;
156             }
157              
158 856 100 100     2373 if ( !@parts && defined ( my $id = $self->id ) ) {
159 16         162 push @parts, $id;
160             }
161              
162 856         4373 return join '' => @parts;
163             }
164              
165             sub as_string {
166 529     529 1 12586 my $self = shift;
167 529   100     9634 my $proj = $self->project // return $self->key_name;
168 523 100       21803 return $self->key_name if $proj eq $self->plan->project;
169 58         564 return "$proj:" . $self->key_name;
170             }
171              
172             sub as_plan_string {
173 144     144 1 9029 my $self = shift;
174 144 100       621 return ($self->conflicts ? '!' : '') . $self->as_string;
175             }
176              
177             1;
178              
179             __END__
180              
181             =head1 Name
182              
183             App::Sqitch::Plan::Depend - Sqitch dependency specification
184              
185             =head1 Synopsis
186              
187             my $depend = App::Sqitch::Plan::Depend->new(
188             plan => $plan,
189             App::Sqitch::Plan::Depend->parse('!proj:change@tag')
190             );
191              
192             =head1 Description
193              
194             An App::Sqitch::Plan::Line represents a single dependency from the dependency
195             list for a planned change. Is is constructed by L<App::Sqitch::Plan> and
196             included in L<App::Sqitch::Plan::Change> objects C<conflicts> and C<requires>
197             attributes.
198              
199             =head1 Interface
200              
201             =head2 Constructors
202              
203             =head3 C<new>
204              
205             my $depend = App::Sqitch::Plan::Depend->new(%params);
206              
207             Instantiates and returns a App::Sqitch::Plan::Line object. Parameters:
208              
209             =over
210              
211             =item C<plan>
212              
213             The plan with which the dependency is associated. Required.
214              
215             =item C<project>
216              
217             Name of the project. Required.
218              
219             =item C<conflicts>
220              
221             Boolean to indicate whether the dependency is a conflicting dependency.
222              
223             =item C<change>
224              
225             The name of the change.
226              
227             =item C<tag>
228              
229             The name of the tag claimed as the dependency.
230              
231             =item C<id>
232              
233             The ID of a change. Mutually exclusive with C<change> and C<tag>.
234              
235             =back
236              
237             =head3 C<parse>
238              
239             my %params = App::Sqitch::Plan::Depend->parse($string);
240              
241             Parses a dependency specification as extracted from a plan and returns a hash
242             reference of parameters suitable for passing to C<new()>. Returns C<undef> if
243             the string is not a properly-formatted dependency.
244              
245             =head2 Accessors
246              
247             =head3 C<plan>
248              
249             my $plan = $depend->plan;
250              
251             Returns the L<App::Sqitch::Plan> object with which the dependency
252             specification is associated.
253              
254             =head3 C<conflicts>
255              
256             say $depend->as_string, ' conflicts' if $depend->conflicts;
257              
258             Returns true if the dependency is a conflicting dependency, and false if it
259             is not (in which case it is a required dependency).
260              
261             =head3 C<required>
262              
263             say $depend->as_string, ' required' if $depend->required;
264              
265             Returns true if the dependency is a required, and false if it is not (in which
266             case it is a conflicting dependency).
267              
268             =head3 C<type>
269              
270             say $depend->type;
271              
272             Returns a string indicating the type of dependency, either "require" or
273             "conflict".
274              
275             =head3 C<project>
276              
277             my $proj = $depend->project;
278              
279             Returns the name of the project with which the dependency is associated.
280              
281             =head3 C<got_project>
282              
283             Returns true if the C<project> parameter was passed to the constructor with a
284             defined value, and false if it was not passed to the constructor.
285              
286             =head3 C<change>
287              
288             my $change = $depend->change;
289              
290             Returns the name of the change, if any. If C<undef> is returned, the dependency
291             is a tag-only dependency.
292              
293             =head3 C<tag>
294              
295             my $tag = $depend->tag;
296              
297             Returns the name of the tag, if any. If C<undef> is returned, the dependency
298             is a change-only dependency.
299              
300             =head3 C<id>
301              
302             Returns the ID of the change if the dependency was specified as an ID, or if
303             the dependency is a local dependency.
304              
305             =head3 C<got_id>
306              
307             Returns true if the C<id> parameter was passed to the constructor with a
308             defined value, and false if it was not passed to the constructor.
309              
310             =head3 C<resolved_id>
311              
312             Change ID used by the engine when deploying a change. That is, if the
313             dependency is in the database, it will be assigned this ID from the database.
314             If it is not in the database, C<resolved_id> will be undef.
315              
316             =head3 C<is_external>
317              
318             Returns true if the dependency references a change external to the current
319             project, and false if it is part of the current project.
320              
321             =head3 C<is_internal>
322              
323             The opposite of C<is_external()>: returns true if the dependency is in the
324             internal (current) project, and false if not.
325              
326             =head2 Instance Methods
327              
328             =head3 C<key_name>
329              
330             Returns the key name of the dependency, with the change name and/or tag,
331             properly formatted for passing to the C<find()> method of
332             L<App::Sqitch::Plan>. If the dependency was specified as an ID, rather than a
333             change or tag, then the ID will be returned.
334              
335             =head3 C<as_string>
336              
337             Returns the project-qualified key name. That is, if there is a project name,
338             it returns a string with the project name, a colon, and the key name. If there
339             is no project name, the key name is returned.
340              
341             =head3 C<as_plan_string>
342              
343             my $string = $depend->as_string;
344              
345             Returns the full stringification of the dependency, suitable for output to a
346             plan file. That is, the same as C<as_string> unless C<conflicts> returns true,
347             in which case it is prepended with "!".
348              
349             =head1 See Also
350              
351             =over
352              
353             =item L<App::Sqitch::Plan>
354              
355             Class representing a plan.
356              
357             =item L<sqitch>
358              
359             The Sqitch command-line client.
360              
361             =back
362              
363             =head1 Author
364              
365             David E. Wheeler <david@justatheory.com>
366              
367             =head1 License
368              
369             Copyright (c) 2012-2023 iovation Inc., David E. Wheeler
370              
371             Permission is hereby granted, free of charge, to any person obtaining a copy
372             of this software and associated documentation files (the "Software"), to deal
373             in the Software without restriction, including without limitation the rights
374             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
375             copies of the Software, and to permit persons to whom the Software is
376             furnished to do so, subject to the following conditions:
377              
378             The above copyright notice and this permission notice shall be included in all
379             copies or substantial portions of the Software.
380              
381             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
382             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
383             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
384             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
385             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
386             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
387             SOFTWARE.
388              
389             =cut