File Coverage

blib/lib/App/Sqitch/Command/log.pm
Criterion Covered Total %
statement 52 52 100.0
branch 10 10 100.0
condition n/a
subroutine 13 13 100.0
pod 1 1 100.0
total 76 76 100.0


line stmt bran cond sub pod time code
1              
2             use 5.010;
3 2     2   1762 use strict;
  2         7  
4 2     2   10 use warnings;
  2         6  
  2         34  
5 2     2   10 use utf8;
  2         4  
  2         52  
6 2     2   11 use Locale::TextDomain qw(App-Sqitch);
  2         5  
  2         7  
7 2     2   57 use App::Sqitch::X qw(hurl);
  2         8  
  2         20  
8 2     2   360 use Moo;
  2         4  
  2         17  
9 2     2   578 use Types::Standard qw(Str Int ArrayRef Bool);
  2         16  
  2         11  
10 2     2   705 use Type::Utils qw(class_type);
  2         5  
  2         19  
11 2     2   2107 use App::Sqitch::ItemFormatter;
  2         8  
  2         15  
12 2     2   1532 use namespace::autoclean;
  2         5  
  2         55  
13 2     2   11 use Try::Tiny;
  2         3  
  2         11  
14 2     2   114  
  2         4  
  2         1637  
15             extends 'App::Sqitch::Command';
16             with 'App::Sqitch::Role::ConnectingCommand';
17              
18             our $VERSION = 'v1.3.1'; # VERSION
19              
20             my %FORMATS;
21             $FORMATS{raw} = <<EOF;
22             %{:event}C%e %H%{reset}C%T
23             name %n
24             project %o
25             %{requires}a%{conflicts}aplanner %{name}p <%{email}p>
26             planned %{date:raw}p
27             committer %{name}c <%{email}c>
28             committed %{date:raw}c
29              
30             %{ }B
31             EOF
32              
33             $FORMATS{full} = <<EOF;
34             %{:event}C%L %h%{reset}C%T
35             %{name}_ %n
36             %{project}_ %o
37             %R%X%{planner}_ %p
38             %{planned}_ %{date}p
39             %{committer}_ %c
40             %{committed}_ %{date}c
41              
42             %{ }B
43             EOF
44              
45             $FORMATS{long} = <<EOF;
46             %{:event}C%L %h%{reset}C%T
47             %{name}_ %n
48             %{project}_ %o
49             %{planner}_ %p
50             %{committer}_ %c
51              
52             %{ }B
53             EOF
54              
55             $FORMATS{medium} = <<EOF;
56             %{:event}C%L %h%{reset}C
57             %{name}_ %n
58             %{committer}_ %c
59             %{date}_ %{date}c
60              
61             %{ }B
62             EOF
63              
64             $FORMATS{short} = <<EOF;
65             %{:event}C%L %h%{reset}C
66             %{name}_ %n
67             %{committer}_ %c
68              
69             %{ }s
70             EOF
71              
72             $FORMATS{oneline} = '%{:event}C%h %l%{reset}C %o:%n %s';
73              
74             has target => (
75             is => 'ro',
76             isa => Str,
77             );
78              
79             has event => (
80             is => 'ro',
81             isa => ArrayRef,
82             );
83              
84             has change_pattern => (
85             is => 'ro',
86             isa => Str,
87             );
88              
89             has project_pattern => (
90             is => 'ro',
91             isa => Str,
92             );
93              
94             has committer_pattern => (
95             is => 'ro',
96             isa => Str,
97             );
98              
99             has max_count => (
100             is => 'ro',
101             isa => Int,
102             );
103              
104             has skip => (
105             is => 'ro',
106             isa => Int,
107             );
108              
109             has reverse => (
110             is => 'ro',
111             isa => Bool,
112             default => 0,
113             );
114              
115             has headers => (
116             is => 'ro',
117             isa => Bool,
118             default => 1,
119             );
120              
121             has format => (
122             is => 'ro',
123             isa => Str,
124             default => $FORMATS{medium},
125             );
126              
127             has formatter => (
128             is => 'ro',
129             isa => class_type('App::Sqitch::ItemFormatter'),
130             lazy => 1,
131             default => sub { App::Sqitch::ItemFormatter->new },
132             );
133              
134             return qw(
135             event=s@
136             target|t=s
137             change-pattern|change=s
138             project-pattern|project=s
139             committer-pattern|committer=s
140             format|f=s
141             date-format|date=s
142             max-count|n=i
143             skip=i
144             reverse!
145             color=s
146             no-color
147             abbrev=i
148             oneline
149             headers!
150             );
151             }
152              
153             my ( $class, $config, $opt ) = @_;
154              
155             # Set base values if --oneline.
156             if ($opt->{oneline}) {
157             $opt->{format} ||= 'oneline';
158             $opt->{abbrev} //= 6;
159             }
160              
161             # Determine and validate the date format.
162             my $date_format = delete $opt->{date_format} || $config->get(
163             key => 'log.date_format'
164             );
165             if ($date_format) {
166             require App::Sqitch::DateTime;
167             App::Sqitch::DateTime->validate_as_string_format($date_format);
168             } else {
169             $date_format = 'iso';
170             }
171              
172             # Make sure the log format is valid.
173             if (my $format = $opt->{format}
174             || $config->get(key => 'log.format')
175             ) {
176             if ($format =~ s/^format://) {
177             $opt->{format} = $format;
178             } else {
179             $opt->{format} = $FORMATS{$format} or hurl log => __x(
180             'Unknown log format "{format}"',
181             format => $format
182             );
183             }
184             }
185              
186             # Determine how to handle ANSI colors.
187             my $color = delete $opt->{no_color} ? 'never'
188             : delete $opt->{color} || $config->get(key => 'log.color');
189              
190             $opt->{formatter} = App::Sqitch::ItemFormatter->new(
191             ( $date_format ? ( date_format => $date_format ) : () ),
192             ( $color ? ( color => $color ) : () ),
193             ( $opt->{abbrev} ? ( abbrev => delete $opt->{abbrev} ) : () ),
194             );
195              
196             return $class->SUPER::configure( $config, $opt );
197             }
198              
199             my $self = shift;
200             my ($targets) = $self->parse_args(
201             target => $self->target,
202             args => \@_,
203 8     8 1 5089 );
204 8         86  
205             # Warn on multiple targets.
206             my $target = shift @{ $targets };
207             $self->warn(__x(
208             'Too many targets specified; connecting to {target}',
209             target => $target->name,
210 8         16 )) if @{ $targets };
  8         14  
211             my $engine = $target->engine;
212              
213             # Exit with status 1 on uninitialized database, probably not expected.
214 8 100       13 hurl {
  8         24  
215 8         391 ident => 'log',
216             exitval => 1,
217             message => __x(
218 8 100       1428 'Database {db} has not been initialized for Sqitch',
219             db => $engine->registry_destination,
220             ),
221             } unless $engine->initialized;
222              
223             # Exit with status 1 on no events, probably not expected.
224             my $iter = $engine->search_events(limit => 1);
225             hurl {
226             ident => 'log',
227             exitval => 1,
228 7         46 message => __x(
229 7 100       52 'No events logged for {db}',
230             db => $engine->destination,
231             ),
232             } unless $iter->();
233              
234             # Search the event log.
235             $iter = $engine->search_events(
236             event => $self->event,
237             change => $self->change_pattern,
238             project => $self->project_pattern,
239 6 100       74 committer => $self->committer_pattern,
240             limit => $self->max_count,
241             offset => $self->skip,
242             direction => $self->reverse ? 'ASC' : 'DESC',
243             );
244              
245             # Send the results.
246             my $formatter = $self->formatter;
247             my $format = $self->format;
248             $self->page( __x 'On database {db}', db => $engine->destination )
249             if $self->headers;
250 6         121 while ( my $change = $iter->() ) {
251 6         342 $self->page( $formatter->format( $format, $change ) );
252 6 100       50 }
253              
254 6         572 return $self;
255 7         239 }
256              
257             1;
258 5         1061  
259              
260             =head1 Name
261              
262             App::Sqitch::Command::log - Show a database event log
263              
264             =head1 Synopsis
265              
266             my $cmd = App::Sqitch::Command::log->new(%params);
267             $cmd->execute;
268              
269             =head1 Description
270              
271             If you want to know how to use the C<log> command, you probably want to be
272             reading C<sqitch-log>. But if you really want to know how the C<log> command
273             works, read on.
274              
275             =head1 Interface
276              
277             =head2 Attributes
278              
279             =head3 C<change_pattern>
280              
281             Regular expression to match against change names.
282              
283             =head3 C<committer_pattern>
284              
285             Regular expression to match against committer names.
286              
287             =head3 C<project_pattern>
288              
289             Regular expression to match against project names.
290              
291             =head3 C<event>
292              
293             Event type buy which to filter entries to display.
294              
295             =head3 C<format>
296              
297             Display format template.
298              
299             =head3 C<max_count>
300              
301             Maximum number of entries to display.
302              
303             =head3 C<reverse>
304              
305             Reverse the usual order of the display of entries.
306              
307             =head3 C<headers>
308              
309             Output headers. Defaults to true.
310              
311             =head3 C<skip>
312              
313             Number of entries to skip before displaying entries.
314              
315             =head3 C<target>
316              
317             The database target from which to read the log.
318              
319             =head2 Instance Methods
320              
321             =head3 C<execute>
322              
323             $log->execute;
324              
325             Executes the log command. The current log for the target database will be
326             searched and the resulting change history displayed.
327              
328             =head1 See Also
329              
330             =over
331              
332             =item L<sqitch-log>
333              
334             Documentation for the C<log> command to the Sqitch command-line client.
335              
336             =item L<sqitch>
337              
338             The Sqitch command-line client.
339              
340             =back
341              
342             =head1 Author
343              
344             David E. Wheeler <david@justatheory.com>
345              
346             =head1 License
347              
348             Copyright (c) 2012-2022 iovation Inc., David E. Wheeler
349              
350             Permission is hereby granted, free of charge, to any person obtaining a copy
351             of this software and associated documentation files (the "Software"), to deal
352             in the Software without restriction, including without limitation the rights
353             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
354             copies of the Software, and to permit persons to whom the Software is
355             furnished to do so, subject to the following conditions:
356              
357             The above copyright notice and this permission notice shall be included in all
358             copies or substantial portions of the Software.
359              
360             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
361             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
362             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
363             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
364             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
365             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
366             SOFTWARE.
367              
368             =cut