File Coverage

blib/lib/App/Sqitch/Command/config.pm
Criterion Covered Total %
statement 150 150 100.0
branch 58 62 93.5
condition 35 39 89.7
subroutine 46 46 100.0
pod 15 15 100.0
total 304 312 97.4


line stmt bran cond sub pod time code
1             package App::Sqitch::Command::config;
2              
3 4     4   6660 use 5.010;
  4         26  
4 4     4   24 use strict;
  4         11  
  4         106  
5 4     4   21 use warnings;
  4         9  
  4         118  
6 4     4   24 use utf8;
  4         10  
  4         27  
7 4     4   114 use Path::Class ();
  4         14  
  4         104  
8 4     4   25 use Try::Tiny;
  4         12  
  4         636  
9 4     4   38 use Locale::TextDomain qw(App-Sqitch);
  4         11  
  4         40  
10 4     4   884 use App::Sqitch::X qw(hurl);
  4         10  
  4         29  
11 4     4   1321 use List::Util qw(first);
  4         13  
  4         257  
12 4     4   43 use Moo;
  4         7  
  4         41  
13 4     4   1905 use App::Sqitch::Types qw(Str Dir Maybe);
  4         11  
  4         72  
14 4     4   4677 use Type::Utils qw(enum);
  4         15  
  4         33  
15 4     4   2403 use namespace::autoclean;
  4         10  
  4         27  
16             extends 'App::Sqitch::Command';
17              
18             our $VERSION = 'v1.4.0'; # VERSION
19              
20             has file => (
21             is => 'ro',
22             lazy => 1,
23             default => sub {
24             my $self = shift;
25             my $meth = ( $self->context || 'local' ) . '_file';
26             return $self->sqitch->config->$meth;
27             }
28             );
29              
30             has action => (
31             is => 'ro',
32             isa => enum([qw(
33             get
34             get_all
35             get_regex
36             set
37             unset
38             list
39             edit
40             add
41             replace_all
42             unset_all
43             rename_section
44             remove_section
45             )]),
46             );
47              
48             has context => (
49             is => 'ro',
50             isa => Maybe[enum([qw(
51             local
52             user
53             system
54             )])],
55             );
56              
57             has type => ( is => 'ro', isa => enum( [qw(int num bool bool-or-int)] ) );
58              
59             sub options {
60 2     2 1 5794 return qw(
61             file|config-file|f=s
62             local
63             user|global
64             system
65              
66             int
67             bool
68             bool-or-int
69             num
70              
71             get
72             get-all
73             get-regex|get-regexp
74             add
75             replace-all
76             unset
77             unset-all
78             rename-section
79             remove-section
80             list|l
81             edit|e
82             );
83             }
84              
85             sub configure {
86 27     27 1 22198 my ( $class, $config, $opt ) = @_;
87              
88             # Make sure we are accessing only one file.
89 27         72 my @file = grep { $opt->{$_} } qw(local user system file);
  108         201  
90 27 100       88 $class->usage('Only one config file at a time.') if @file > 1;
91              
92             # Make sure we have only one type.
93 22         41 my @type = grep { $opt->{$_} } qw(bool int num bool_or_int);
  88         150  
94 22 100       55 $class->usage('Only one type at a time.') if @type > 1;
95              
96             # Make sure we are performing only one action.
97 18         31 my @action = grep { $opt->{$_} } qw(
  198         294  
98             get
99             get_all
100             get_regex
101             unset
102             list
103             edit
104             add
105             replace_all
106             unset_all
107             rename_section
108             remove_section
109             );
110 18 100       62 $class->usage('Only one action at a time.') if @action > 1;
111              
112             # Get the action and context.
113 5     12   32 my $context = first { $opt->{$_} } qw(local user system);
  12         22  
114              
115             # Make it so.
116             return {
117             ( $action[0] ? ( action => $action[0] ) : () ),
118             ( $type[0] ? ( type => $type[0] ) : () ),
119             ( $context ? ( context => $context ) : () ),
120 5 50       61 ( $opt->{file} ? ( file => $opt->{file} ) : () ),
    50          
    100          
    50          
121             };
122             }
123              
124             sub execute {
125 94     94 1 138611 my $self = shift;
126 94   66     582 my $action = $self->action || ( @_ > 1 ? 'set' : 'get' );
127 94         486 $action =~ s/-/_/g;
128 94 50       727 my $meth = $self->can($action) or hurl config => __x(
129             'Unknown config action: {action}',
130             action => $action,
131             );
132 94         460 return $self->$meth(@_);
133             }
134              
135             sub get {
136 31     31 1 4494 my ( $self, $key, $rx ) = @_;
137 31 100 100     258 $self->usage('Wrong number of arguments.') if !defined $key || $key eq '';
138              
139             my $val = try {
140 29     29   4289 $self->sqitch->config->get(
141             key => $key,
142             filter => $rx,
143             as => $self->type,
144             human => 1,
145             );
146             }
147             catch {
148 5 100   5   1251 hurl config => __x(
149             'More then one value for the key "{key}"',
150             key => $key,
151             ) if /^\QMultiple values/i;
152 4         29 hurl config => $_;
153 29         457 };
154              
155 24 100       12811 hurl {
156             ident => 'config',
157             message => '',
158             exitval => 1,
159             } unless defined $val;
160 19         121 $self->emit($val);
161 19         317 return $self;
162             }
163              
164             sub get_all {
165 20     20 1 148 my ( $self, $key, $rx ) = @_;
166 20 100 100     137 $self->usage('Wrong number of arguments.') if !defined $key || $key eq '';
167              
168             my @vals = try {
169 18     18   2052 $self->sqitch->config->get_all(
170             key => $key,
171             filter => $rx,
172             as => $self->type,
173             human => 1,
174             );
175             }
176             catch {
177 4     4   803 hurl config => $_;
178 18         180 };
179 14 100       3157 hurl {
180             ident => 'config',
181             message => '',
182             exitval => 1,
183             } unless @vals;
184 13         121 $self->emit( join "\n", @vals );
185 13         183 return $self;
186             }
187              
188             sub get_regex {
189 19     19 1 169 my ( $self, $key, $rx ) = @_;
190 19 100 100     134 $self->usage('Wrong number of arguments.') if !defined $key || $key eq '';
191              
192 17         555 my $config = $self->sqitch->config;
193             my %vals = try {
194 17     17   1811 $config->get_regexp(
195             key => $key,
196             filter => $rx,
197             as => $self->type,
198             human => 1,
199             );
200             }
201             catch {
202 4     4   669 hurl config => $_;
203 17         314 };
204 13 100       3414 hurl {
205             ident => 'config',
206             message => '',
207             exitval => 1,
208             } unless %vals;
209 12         58 my @out;
210 12         72 for my $key ( sort keys %vals ) {
211 19 100       87 if ( defined $vals{$key} ) {
212 18 100       94 if ( $config->is_multiple($key) ) {
213 1         165 push @out => "$key=[" . join( ', ', @{ $vals{$key} } ) . ']';
  1         12  
214             }
215             else {
216 17         2279 push @out => "$key=$vals{$key}";
217             }
218             }
219             else {
220 1         3 push @out => $key;
221             }
222             }
223 12         102 $self->emit( join "\n" => @out );
224              
225 12         196 return $self;
226             }
227              
228             sub set {
229 9     9 1 15916 my ( $self, $key, $value, $rx ) = @_;
230 9         60 $self->_set( $key, $value, $rx, multiple => 0 );
231             }
232              
233             sub add {
234 15     15 1 34363 my ( $self, $key, $value ) = @_;
235 15         131 $self->_set( $key, $value, undef, multiple => 1 );
236             }
237              
238             sub replace_all {
239 2     2 1 22 my ( $self, $key, $value, $rx ) = @_;
240 2         37 $self->_set( $key, $value, $rx, multiple => 1, replace_all => 1 );
241             }
242              
243             sub _set {
244 26     26   187 my ( $self, $key, $value, $rx, @p ) = @_;
245 26 100 100     390 $self->usage('Wrong number of arguments.')
      100        
246             if !defined $key || $key eq '' || !defined $value;
247              
248 20         123 $self->_touch_dir;
249             try {
250 20     20   3476 $self->sqitch->config->set(
251             key => $key,
252             value => $value,
253             filename => $self->file,
254             filter => $rx,
255             as => $self->type,
256             @p,
257             );
258             }
259             catch {
260 2 100   2   898 hurl config => __(
261             'Cannot overwrite multiple values with a single value'
262             ) if /^Multiple occurrences/i;
263 1         24 hurl config => $_;
264 20         728 };
265 18         26179 return $self;
266             }
267              
268             sub _file_config {
269 5     5   151 my $file = shift->file;
270 5 100       1219 return unless -e $file;
271 3         298 my $config = App::Sqitch::Config->new;
272 3         3042 $config->load_file($file);
273 3         33822 return $config;
274             }
275              
276             sub unset {
277 8     8 1 13460 my ( $self, $key, $rx ) = @_;
278 8 100 100     112 $self->usage('Wrong number of arguments.') if !defined $key || $key eq '';
279 6         41 $self->_touch_dir;
280              
281             try {
282 6     6   1114 $self->sqitch->config->set(
283             key => $key,
284             filename => $self->file,
285             filter => $rx,
286             multiple => 0,
287             );
288             }
289             catch {
290 2 100   2   1303 hurl config => __(
291             'Cannot unset key with multiple values'
292             ) if /^Multiple occurrences/i;
293 1         19 hurl config => $_;
294 6         302 };
295 4         6178 return $self;
296             }
297              
298             sub unset_all {
299 5     5 1 18892 my ( $self, $key, $rx ) = @_;
300 5 100 100     81 $self->usage('Wrong number of arguments.') if !defined $key || $key eq '';
301              
302 3         46 $self->_touch_dir;
303 3         159 $self->sqitch->config->set(
304             key => $key,
305             filename => $self->file,
306             filter => $rx,
307             multiple => 1,
308             );
309 3         4325 return $self;
310             }
311              
312             sub list {
313 6     6 1 44 my $self = shift;
314 6 100       181 my $config = $self->context
315             ? $self->_file_config
316             : $self->sqitch->config;
317 6 100       229 $self->emit( scalar $config->dump ) if $config;
318 6         10547 return $self;
319             }
320              
321             sub edit {
322 1     1 1 2 my $self = shift;
323              
324             # Let the editor deal with locking.
325 1         44 $self->shell(
326             $self->sqitch->editor . ' ' . $self->quote_shell( $self->file )
327             );
328             }
329              
330             sub rename_section {
331 6     6 1 3356 my ( $self, $old_name, $new_name ) = @_;
332 6 100 66     139 $self->usage('Wrong number of arguments.')
      100        
      100        
333             unless defined $old_name && $old_name ne ''
334             && defined $new_name && $new_name ne '';
335              
336             try {
337 3     3   578 $self->sqitch->config->rename_section(
338             from => $old_name,
339             to => $new_name,
340             filename => $self->file
341             );
342             }
343             catch {
344 2 100   2   753 hurl config => __ 'No such section!' if /\Qno such section/i;
345 1         431 hurl config => $_;
346 3         70 };
347 1         1334 return $self;
348             }
349              
350             sub remove_section {
351 4     4 1 3376 my ( $self, $section ) = @_;
352 4 100 66     81 $self->usage('Wrong number of arguments.')
353             unless defined $section && $section ne '';
354             try {
355 3     3   493 $self->sqitch->config->remove_section(
356             section => $section,
357             filename => $self->file
358             );
359             }
360             catch {
361 2 100   2   640 hurl config => __ 'No such section!' if /\Qno such section/i;
362 1         18 hurl config => $_;
363 3         624 };
364 1         1270 return $self;
365             }
366              
367             sub _touch_dir {
368 29     29   95 my $self = shift;
369 29 100       233 unless ( -e $self->file ) {
370 2         251 require File::Basename;
371 2         11 my $dir = File::Basename::dirname( $self->file );
372 2 100 66     378 unless ( -e $dir && -d _ ) {
373 1         18 require File::Path;
374 1         276 File::Path::make_path($dir);
375             }
376             }
377             }
378              
379             1;
380              
381             __END__
382              
383             =head1 Name
384              
385             App::Sqitch::Command::config - Get and set local, user, or system Sqitch options
386              
387             =head1 Synopsis
388              
389             my $cmd = App::Sqitch::Command::config->new(\%params);
390             $cmd->execute;
391              
392             =head1 Description
393              
394             You can query/set/replace/unset Sqitch options with this command. The name is
395             actually the section and the key separated by a dot, and the value will be
396             escaped.
397              
398             =head1 Interface
399              
400             =head2 Class Methods
401              
402             =head3 C<options>
403              
404             my @opts = App::Sqitch::Command::config->options;
405              
406             Returns a list of L<Getopt::Long> option specifications for the command-line
407             options for the C<config> command.
408              
409             =head3 C<configure>
410              
411             my $params = App::Sqitch::Command::config->configure(
412             $config,
413             $options,
414             );
415              
416             Processes the configuration and command options and returns a hash suitable
417             for the constructor. Exits with an error on option specification errors.
418              
419             =head2 Constructor
420              
421             =head3 C<new>
422              
423             my $config = App::Sqitch::Command::config->new($params);
424              
425             Creates and returns a new C<config> command object. The supported parameters
426             include:
427              
428             =over
429              
430             =item C<sqitch>
431              
432             The core L<Sqitch|App::Sqitch> object.
433              
434             =item C<file>
435              
436             Configuration file to read from and write to.
437              
438             =item C<action>
439              
440             The action to be executed. May be one of:
441              
442             =over
443              
444             =item * C<get>
445              
446             =item * C<get-all>
447              
448             =item * C<get-regexp>
449              
450             =item * C<set>
451              
452             =item * C<add>
453              
454             =item * C<replace-all>
455              
456             =item * C<unset>
457              
458             =item * C<unset-all>
459              
460             =item * C<list>
461              
462             =item * C<edit>
463              
464             =item * C<rename-section>
465              
466             =item * C<remove-section>
467              
468             =back
469              
470             If not specified, the action taken by C<execute()> will depend on the number
471             of arguments passed to it. If only one, the action will be C<get>. If two or
472             more, the action will be C<set>.
473              
474             =item C<context>
475              
476             The configuration file context. Must be one of:
477              
478             =over
479              
480             =item * C<local>
481              
482             =item * C<user>
483              
484             =item * C<system>
485              
486             =back
487              
488             =item C<type>
489              
490             The type to cast a value to be set to or fetched as. May be one of:
491              
492             =over
493              
494             =item * C<bool>
495              
496             =item * C<int>
497              
498             =item * C<num>
499              
500             =item * C<bool-or-int>
501              
502             =back
503              
504             If not specified or C<undef>, no casting will be performed.
505              
506             =back
507              
508             =head2 Instance Methods
509              
510             These methods are mainly provided as utilities for the command subclasses to
511             use.
512              
513             =head3 C<execute>
514              
515             $config->execute($property, $value);
516              
517             Executes the config command. Pass the name of the property and the value to
518             be assigned to it, if applicable.
519              
520             =head3 C<get>
521              
522             $config->get($key);
523             $config->get($key, $regex);
524              
525             Emits the value for the specified key. The optional second argument is a
526             regular expression that the value to be returned must match. Exits with an
527             error if the is more than one value for the specified key, or if the key does
528             not exist.
529              
530             =head3 C<get_all>
531              
532             $config->get_all($key);
533             $config->get_all($key, $regex);
534              
535             Like C<get()>, but emits all of the values for the given key, rather then
536             exiting with an error when there is more than one value.
537              
538             =head3 C<get_regex>
539              
540             $config->get_regex($key);
541             $config->get_regex($key, $regex);
542              
543             Like C<get_all()>, but the first parameter is a regular expression that will
544             be matched against all keys.
545              
546             =head3 C<set>
547              
548             $config->set($key, $value);
549             $config->set($key, $value, $regex);
550              
551             Sets the value for a key. Exits with an error if the key already exists and
552             has multiple values.
553              
554             =head3 C<add>
555              
556             $config->add($key, $value);
557              
558             Adds a value for a key. If the key already exists, the value will be added as
559             an additional value.
560              
561             =head3 C<replace_all>
562              
563             $config->replace_all($key, $value);
564             $config->replace_all($key, $value, $regex);
565              
566             Replace all matching values.
567              
568             =head3 C<unset>
569              
570             $config->unset($key);
571             $config->unset($key, $regex);
572              
573             Unsets a key. If the optional second argument is passed, the key will be unset
574             only if the value matches the regular expression. If the key has multiple
575             values, C<unset()> will exit with an error.
576              
577             =head3 C<unset_all>
578              
579             $config->unset_all($key);
580             $config->unset_all($key, $regex);
581              
582             Like C<unset()>, but will not exit with an error if the key has multiple
583             values.
584              
585             =head3 C<rename_section>
586              
587             $config->rename_section($old_name, $new_name);
588              
589             Renames a section. Exits with an error if the section does not exist or if
590             either name is not a valid section name.
591              
592             =head3 C<remove_section>
593              
594             $config->remove_section($section);
595              
596             Removes a section. Exits with an error if the section does not exist.
597              
598             =head3 C<list>
599              
600             $config->list;
601              
602             Lists all of the values in the configuration. If the context is C<local>,
603             C<user>, or C<system>, only the settings set for that context will be emitted.
604             Otherwise, all settings will be listed.
605              
606             =head3 C<edit>
607              
608             $config->edit;
609              
610             Opens the context-specific configuration file in a text editor for direct
611             editing. If no context is specified, the local config file will be opened. The
612             editor is determined by L<Sqitch/editor>.
613              
614             =head2 Instance Accessors
615              
616             =head3 C<file>
617              
618             my $file_name = $config->file;
619              
620             Returns the path to the configuration file to be acted upon. If the context is
621             C<system>, then the value returned is C<$($etc_prefix)/sqitch.conf>. If the
622             context is C<user>, then the value returned is C<~/.sqitch/sqitch.conf>.
623             Otherwise, the default is F<./sqitch.conf>.
624              
625             =head1 See Also
626              
627             =over
628              
629             =item L<sqitch-config>
630              
631             Help for the C<config> command to the Sqitch command-line client.
632              
633             =item L<sqitch>
634              
635             The Sqitch command-line client.
636              
637             =back
638              
639             =head1 Author
640              
641             David E. Wheeler <david@justatheory.com>
642              
643             =head1 License
644              
645             Copyright (c) 2012-2023 iovation Inc., David E. Wheeler
646              
647             Permission is hereby granted, free of charge, to any person obtaining a copy
648             of this software and associated documentation files (the "Software"), to deal
649             in the Software without restriction, including without limitation the rights
650             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
651             copies of the Software, and to permit persons to whom the Software is
652             furnished to do so, subject to the following conditions:
653              
654             The above copyright notice and this permission notice shall be included in all
655             copies or substantial portions of the Software.
656              
657             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
658             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
659             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
660             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
661             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
662             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
663             SOFTWARE.
664              
665             =cut
666