File Coverage

blib/lib/App/Sqitch/Plan/Line.pm
Criterion Covered Total %
statement 64 65 98.4
branch 12 14 85.7
condition 6 10 60.0
subroutine 16 16 100.0
pod 7 8 87.5
total 105 113 92.9


line stmt bran cond sub pod time code
1              
2             use 5.010;
3 50     50   25461 use utf8;
  50         172  
4 50     50   292 use namespace::autoclean;
  50         92  
  50         252  
5 50     50   1031 use Moo;
  50         97  
  50         247  
6 50     50   2891 use App::Sqitch::Types qw(Str Plan);
  50         82  
  50         256  
7 50     50   14326 use App::Sqitch::X qw(hurl);
  50         103  
  50         426  
8 50     50   34115 use Locale::TextDomain qw(App-Sqitch);
  50         101  
  50         339  
9 50     50   12579  
  50         102  
  50         362  
10             our $VERSION = 'v1.3.1'; # VERSION
11              
12             has name => (
13             is => 'ro',
14             isa => Str,
15             required => 1,
16             );
17              
18             has operator => (
19             is => 'ro',
20             isa => Str,
21             default => '',
22             );
23              
24             has lspace => (
25             is => 'ro',
26             isa => Str,
27             default => '',
28             );
29              
30             has rspace => (
31             is => 'rwp',
32             isa => Str,
33             default => '',
34             );
35              
36             has lopspace => (
37             is => 'ro',
38             isa => Str,
39             default => '',
40             );
41              
42             has ropspace => (
43             is => 'ro',
44             isa => Str,
45             default => '',
46             );
47              
48             has note => (
49             is => 'rw',
50             isa => Str,
51             default => '',
52             );
53              
54             after note => sub {
55             my $self = shift;
56             $self->_set_rspace(' ') if $_[0] && !$self->rspace;
57             };
58              
59             has plan => (
60             is => 'ro',
61             isa => Plan,
62             weak_ref => 1,
63             required => 1,
64             handles => [qw(sqitch project uri target)],
65             );
66              
67             my %escape = (
68             "\n" => '\\n',
69             "\r" => '\\r',
70             '\\' => '\\\\',
71             );
72              
73             my %unescape = reverse %escape;
74              
75             my $class = shift;
76             my $p = @_ == 1 && ref $_[0] ? { %{ +shift } } : { @_ };
77 2527     2527 0 772260 if (my $note = $p->{note}) {
78 2527 50 33     16010 # Trim and then encode newlines.
  0         0  
79 2527 100       6435 $note =~ s/\A\s+//;
80             $note =~ s/\s+\z//;
81 560         1502 $note =~ s/(\\[\\nr])/$unescape{$1}/g;
82 560         1787 $p->{note} = $note;
83 560         1127 $p->{rspace} //= ' ' if $note && $p->{name};
84 560         906 }
85 560 100 100     2722 return $p;
      66        
86             }
87 2527         34587  
88             my ( $self, %p ) = @_;
89             my $note = $self->note // '';
90             return $note if $note =~ /\S/;
91 3     3 1 791  
92 3   50     62 # Edit in a file.
93 3 100       31 require File::Temp;
94             my $tmp = File::Temp->new;
95             binmode $tmp, ':utf8_strict';
96 2         11 ( my $prompt = $self->note_prompt(%p) ) =~ s/^/# /gms;
97 2         14 $tmp->print( "\n", $prompt, "\n" );
98 2     1   964 $tmp->close;
  1         7  
  1         2  
  1         7  
99 2         830  
100 2         137 my $sqitch = $self->sqitch;
101 2         52 $sqitch->shell( $sqitch->editor . ' ' . $sqitch->quote_shell($tmp) );
102              
103 2         160 open my $fh, '<:utf8_strict', $tmp or hurl add => __x(
104 2         84 'Cannot open {file}: {error}',
105             file => $tmp,
106 2 50       1117 error => $!
107             );
108              
109             $note = join '', grep { $_ !~ /^\s*#/ } <$fh>;
110             hurl {
111             ident => 'plan',
112 2         131 message => __ 'Aborting due to empty note',
  6         32  
113 2 100       14 exitval => 1,
114             } unless $note =~ /\S/;
115              
116             # Trim the note.
117             $note =~ s/\A\v+//;
118             $note =~ s/\v+\z//;
119              
120 1         4 # Set the note.
121 1         7 $self->note($note);
122             return $note;
123             }
124 1         25  
125 1         58 my ( $self, %p ) = @_;
126             __x(
127             "Write a {command} note.\nLines starting with '#' will be ignored.",
128             command => $p{for}
129 5     5 1 2023 );
130             }
131              
132             shift->name;
133 5         22 }
134              
135             my $self = shift;
136             join '', $self->lopspace, $self->operator, $self->ropspace;
137 2195     2195 1 83018 }
138              
139             my $self = shift;
140             join '', $self->format_operator, $self->format_name;
141 447     447 1 586 }
142 447         2074  
143             my $note = shift->note;
144             return '' unless length $note;
145             $note =~ s/([\r\n\\])/$escape{$1}/g;
146 312     312 1 388 return "# $note";
147 312         555 }
148              
149             my $self = shift;
150             return $self->lspace
151 416     416 1 8838 . $self->format_content
152 416 100       3201 . $self->rspace
153 106         324 . $self->format_note;
154 106         642 }
155              
156             1;
157              
158 310     310 1 6659  
159 310         1036 =head1 Name
160              
161             App::Sqitch::Plan::Line - Sqitch deployment plan line
162              
163             =head1 Synopsis
164              
165             my $plan = App::Sqitch::Plan->new( sqitch => $sqitch );
166             for my $line ($plan->lines) {
167             say $line->as_string;
168             }
169              
170             =head1 Description
171              
172             An App::Sqitch::Plan::Line represents a single line from a Sqitch plan file.
173             Each object managed by an L<App::Sqitch::Plan> object is derived from this
174             class. This is actually an abstract base class. See
175             L<App::Sqitch::Plan::Change>, L<App::Sqitch::Plan::Tag>, and
176             L<App::Sqitch::Plan::Blank> for concrete subclasses.
177              
178             =head1 Interface
179              
180             =head2 Constructors
181              
182             =head3 C<new>
183              
184             my $plan = App::Sqitch::Plan::Line->new(%params);
185              
186             Instantiates and returns a App::Sqitch::Plan::Line object. Parameters:
187              
188             =over
189              
190             =item C<plan>
191              
192             The L<App::Sqitch::Plan> object with which the line is associated.
193              
194             =item C<name>
195              
196             The name of the line. Should be empty for blank lines. Tags names should
197             not include the leading C<@>.
198              
199             =item C<lspace>
200              
201             The white space from the beginning of the line, if any.
202              
203             =item C<lopspace>
204              
205             The white space to the left of the operator, if any.
206              
207             =item C<operator>
208              
209             An operator, if any.
210              
211             =item C<ropspace>
212              
213             The white space to the right of the operator, if any.
214              
215             =item C<rspace>
216              
217             The white space after the name until the end of the line or the start of a
218             note.
219              
220             =item C<note>
221              
222             A note. Does not include the leading C<#>, but does include any white space
223             immediate after the C<#> when the plan file is parsed.
224              
225             =back
226              
227             =head2 Accessors
228              
229             =head3 C<plan>
230              
231             my $plan = $line->plan;
232              
233             Returns the plan object with which the line object is associated.
234              
235             =head3 C<name>
236              
237             my $name = $line->name;
238              
239             Returns the name of the line. Returns an empty string if there is no name.
240              
241             =head3 C<lspace>
242              
243             my $lspace = $line->lspace.
244              
245             Returns the white space from the beginning of the line, if any.
246              
247             =head3 C<rspace>
248              
249             my $rspace = $line->rspace.
250              
251             Returns the white space after the name until the end of the line or the start
252             of a note.
253              
254             =head3 C<note>
255              
256             my $note = $line->note.
257              
258             Returns the note. Does not include the leading C<#>, but does include any
259             white space immediate after the C<#> when the plan file is parsed. Returns the
260             empty string if there is no note.
261              
262             =head2 Instance Methods
263              
264             =head3 C<format_name>
265              
266             my $formatted_name = $line->format_name;
267              
268             Returns the name of the line properly formatted for output. For
269             L<tags|App::Sqitch::Plan::Tag>, it's the name with a leading C<@>. For all
270             other lines, it is simply the name.
271              
272             =head3 C<format_operator>
273              
274             my $formatted_operator = $line->format_operator;
275              
276             Returns the formatted representation of the operator. This is just the
277             operator an its associated white space. If neither the operator nor its white
278             space exists, an empty string is returned. Used internally by C<as_string()>.
279              
280             =head3 C<format_content>
281              
282             my $formatted_content $line->format_content;
283              
284             Formats and returns the main content of the line. This consists of an operator
285             and its associated white space, if any, followed by the formatted name.
286              
287             =head3 C<format_note>
288              
289             my $note = $line->format_note;
290              
291             Returns the note formatted for output. That is, with a leading C<#> and
292             newlines encoded.
293              
294             =head3 C<as_string>
295              
296             my $string = $line->as_string;
297              
298             Returns the full stringification of the line, suitable for output to a plan
299             file.
300              
301             =head3 C<request_note>
302              
303             my $note = $line->request_note( for => 'add' );
304              
305             Request the note from the user. Pass in the name of the command for which the
306             note is requested via the C<for> parameter. If there is a note, it is simply
307             returned. Otherwise, an editor will be launched and the user asked to write
308             one. Once the editor exits, the note will be retrieved from the file, saved,
309             and returned. If no note was written, an exception will be thrown with an
310             C<exitval> of 1.
311              
312             =head3 C<note_prompt>
313              
314             my $prompt = $line->note_prompt( for => 'tag' );
315              
316             Returns a localized string for use in the temporary file created by
317             C<request_note()>. Pass in the name of the command for which to prompt via the
318             C<for> parameter.
319              
320             =head1 See Also
321              
322             =over
323              
324             =item L<App::Sqitch::Plan>
325              
326             Class representing a plan.
327              
328             =item L<sqitch>
329              
330             The Sqitch command-line client.
331              
332             =back
333              
334             =head1 Author
335              
336             David E. Wheeler <david@justatheory.com>
337              
338             =head1 License
339              
340             Copyright (c) 2012-2022 iovation Inc., David E. Wheeler
341              
342             Permission is hereby granted, free of charge, to any person obtaining a copy
343             of this software and associated documentation files (the "Software"), to deal
344             in the Software without restriction, including without limitation the rights
345             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
346             copies of the Software, and to permit persons to whom the Software is
347             furnished to do so, subject to the following conditions:
348              
349             The above copyright notice and this permission notice shall be included in all
350             copies or substantial portions of the Software.
351              
352             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
353             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
354             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
355             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
356             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
357             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
358             SOFTWARE.
359              
360             =cut