File Coverage

blib/lib/Config/GitLike.pm
Criterion Covered Total %
statement 442 475 93.0
branch 232 290 80.0
condition 75 112 66.9
subroutine 46 53 86.7
pod 27 27 100.0
total 822 957 85.8


line stmt bran cond sub pod time code
1             package Config::GitLike;
2 5     5   255168 use Moo;
  5         10981  
  5         30  
3 5     5   2911 use MooX::Types::MooseLike::Base qw(Bool HashRef ArrayRef Maybe Str Int);
  5         6178  
  5         439  
4              
5 5     5   33 use File::Spec;
  5         5  
  5         126  
6 5     5   19 use Cwd;
  5         9  
  5         336  
7 5     5   32 use Scalar::Util qw(openhandle);
  5         10  
  5         316  
8 5     5   26 use Fcntl qw(O_CREAT O_EXCL O_WRONLY);
  5         7  
  5         250  
9 5     5   124 use 5.008;
  5         14  
  5         19185  
10              
11             our $VERSION = '1.16';
12              
13              
14             has 'confname' => (
15             is => 'rw',
16             required => 1,
17             isa => Str,
18             );
19              
20             # not defaulting to {} allows the predicate is_loaded
21             # to determine whether data has been loaded yet or not
22             has 'data' => (
23             is => 'rw',
24             predicate => 'is_loaded',
25             isa => HashRef,
26             );
27              
28             # key => bool
29             has 'multiple' => (
30             is => 'rw',
31             isa => HashRef,
32             default => sub { +{} },
33             );
34              
35             has 'casing' => (
36             is => 'rw',
37             isa => HashRef,
38             default => sub { +{} },
39             );
40              
41             # filename where the definition of each key was loaded from
42             has 'origins' => (
43             is => 'rw',
44             isa => HashRef,
45             default => sub { +{} },
46             );
47              
48             has 'config_files' => (
49             is => 'rw',
50             isa => ArrayRef,
51             default => sub { [] },
52             );
53              
54             # default to being more relaxed than git, but allow enforcement
55             # of only-write-things-that-git-config-can-read if you want to
56             has 'compatible' => (
57             is => 'rw',
58             isa => Bool,
59             default => sub { 0 },
60             );
61              
62             has 'cascade' => (
63             is => 'rw',
64             isa => Bool,
65             default => sub { 0 },
66             );
67              
68             has 'encoding' => (
69             is => 'rw',
70             isa => Maybe[Str],
71             );
72              
73             has 'include' => (
74             is => 'rw',
75             isa => Str,
76             default => sub { "include.path" },
77             );
78              
79             has 'max_depth' => (
80             is => 'rw',
81             isa => Int,
82             default => sub { 10 },
83             );
84              
85             sub set_multiple {
86 72     72 1 4690 my $self = shift;
87 72         171 my ($name, $mult) = (@_, 1);
88 72         1370 $self->multiple->{ $self->canonical_case( $name ) } = $mult;
89             }
90              
91             sub is_multiple {
92 302     302 1 682 my $self = shift;
93 302         327 my $name = shift;
94 302 50       535 return if !defined $name;
95 302         5158 return $self->multiple->{ $self->canonical_case( $name ) };
96             }
97              
98             sub load {
99 60     60 1 8314 my $self = shift;
100 60   33     140965 my $path = shift || Cwd::cwd;
101 60         3749 $self->data({});
102 60         8019 $self->multiple({});
103 60         5687 $self->config_files([]);
104 60         5065 $self->load_global;
105 60         834 $self->load_user;
106 60         1022 $self->load_dirs( $path );
107 51 50       802 return wantarray ? %{$self->data} : \%{$self->data};
  0         0  
  51         1035  
108             }
109              
110             sub dir_file {
111 0     0 1 0 my $self = shift;
112 0         0 return "." . $self->confname;
113             }
114              
115             sub load_dirs {
116 60     60 1 198 my $self = shift;
117 60         136 my $path = shift;
118 60         819 my($vol, $dirs, undef) = File::Spec->splitpath( $path, 1 );
119 60         721 my @dirs = File::Spec->splitdir( $dirs );
120 60         91 my @found;
121 60         180 while (@dirs) {
122 240         1726 my $path = File::Spec->catpath(
123             $vol, File::Spec->catdir(@dirs), $self->dir_file
124             );
125 240 100       17066 if (-f $path) {
126 56         228 push @found, $path;
127 56 50       1523 last unless $self->cascade;
128             }
129 184         439 pop @dirs;
130             }
131 60         3373 $self->load_file( $_ ) for reverse @found;
132             }
133              
134             sub global_file {
135 0     0 1 0 my $self = shift;
136 0         0 return "/etc/" . $self->confname;
137             }
138              
139             sub load_global {
140 60     60 1 121 my $self = shift;
141 60         423 return $self->load_file( $self->global_file );
142             }
143              
144             sub user_file {
145 0     0 1 0 my $self = shift;
146             return
147 0         0 File::Spec->catfile( "~", "." . $self->confname );
148             }
149              
150             sub load_user {
151 60     60 1 139 my $self = shift;
152 60         493 return $self->load_file( $self->user_file );
153             }
154              
155             # returns undef if the file was unable to be opened
156             sub _read_config {
157 289     289   511 my $self = shift;
158 289         361 my $filename = shift;
159              
160 289 100 66     6534 return unless -f $filename and -r $filename;
161 162 50       5053 open(my $fh, '<', $filename) or return;
162 162 100       4579 if (my $encoding = $self->encoding) {
163 1         38 binmode $fh, ":encoding($encoding)";
164             }
165              
166 162         1165 my $c = do {local $/; <$fh>};
  162         562  
  162         2459  
167              
168 162         2390 $c =~ s/\n*$/\n/; # Ensure it ends with a newline
169              
170 162         1967 return $c;
171             }
172              
173             sub load_file {
174 178     178 1 13662 my $ref = shift;
175              
176 178         192 my $self;
177 178 50       483 if (ref $ref) {
178 178         245 $self = $ref;
179             } else {
180             # Set up a temporary object
181 0         0 $self = $ref->new( confname => "" );
182             }
183              
184 178 50       703 unshift @_, "filename" if @_ % 2;
185 178         386849 my %args = (
186             filename => undef,
187             silent => 0,
188             relative => Cwd::cwd(),
189             depth => 0,
190             force => 0,
191             includes => 1,
192             @_,
193             );
194              
195 178         1284 my $filename = $args{filename};
196              
197             # Do some canonicalization
198 178         477 $filename =~ s/^~/$ENV{'HOME'}/g;
199 178   66     495 $filename = eval { Cwd::abs_path( File::Spec->rel2abs($filename, $args{relative}) ) }
200             || $filename;
201 178         655 $filename = File::Spec->canonpath( $filename );
202              
203 178 50 33     239 return $self->data if grep {$_ eq $filename} @{$self->config_files}
  9         116  
  178         8833  
204             and not $args{force};
205              
206 178         2497 my $c = $self->_read_config($filename);
207 178 50 66     1150 return $self->data if not $c and $args{silent};
208 178 100       427 unless (defined $c) {
209 115 50       436 die "Failed to load $filename: $!\n" if not ref $ref;
210 115         768 return;
211             }
212              
213             # Note this filename as having been loaded
214 63         115 push @{$self->config_files}, $filename;
  63         1374  
215              
216 63 50 33     1743 $self->set_multiple( $self->include ) if $self->include
217             and $args{includes};
218              
219 63 50       455 $self->data({}) unless $self->is_loaded;
220             $self->parse_content(
221             content => $c,
222             callback => sub {
223 334     334   1241 my %def = @_;
224 334         898 $self->define(@_, origin => $filename);
225              
226 334 50 33     6654 return unless $self->include and $args{includes};
227 334         7583 my ($sec, $subsec, $name) = _split_key($self->include);
228 334 50 50     2039 return unless lc( $def{section} || '') eq lc( $sec || '');
      50        
229 0 0 0     0 return unless ($def{subsection} || '') eq ($subsec || '');
      0        
230 0 0 0     0 return unless lc( $def{name} || '') eq lc( $name || '');
      0        
231              
232 0 0       0 die "Exceeded maximum include depth (".$self->max_depth.") ".
233             "while including $def{value} from $filename"
234             if $args{depth} > $self->max_depth;
235              
236 0         0 my (undef, $dir, undef) = File::Spec->splitpath($filename);
237              
238 0         0 $self->load_file(
239             filename => $def{value},
240             silent => 1,
241             relative => $dir,
242             depth => $args{depth}+1,
243             force => 1,
244             );
245             },
246             error => sub {
247 9     9   39 error_callback( @_, filename => $filename );
248             },
249 63         1142 );
250              
251 54         1653 return $self->data;
252             }
253              
254             sub error_callback {
255 9     9 1 29 my %args = @_;
256              
257 9         29 my $offset_of_prev_newline = rindex( $args{content}, "\n", $args{offset} );
258 9         27 my $offset_of_next_newline = index( $args{content}, "\n", $args{offset} );
259 9         30 my $line = substr(
260             $args{content},
261             $offset_of_prev_newline + 1,
262             $offset_of_next_newline - ($offset_of_prev_newline + 1),
263             );
264              
265 9         12 my $line_number = 1;
266 9         10 my $current_offset = 0;
267              
268 9         22 while ($current_offset <= $args{offset}) {
269             # nibble off a line of content
270 14         47 $args{content} =~ s/(.*\n)//;
271 14         13 $line_number++;
272 14         77 $current_offset += length $1;
273             }
274 9         14 my $position = (length $line) - ($current_offset - ($args{offset} + 1));
275 9         255 die "Error parsing $args{filename} at line $line_number, position $position."
276             ."\nBad line was: '$line'\n";
277             }
278              
279             sub parse_content {
280 157     157 1 267 my $self = shift;
281             my %args = (
282             content => '',
283 0     0   0 callback => sub {},
284 0     0   0 error => sub {},
285 157         1615 @_,
286             );
287 157         606 my $c = $args{content};
288 157 100       367 return if !$c; # nothing to do if content is empty
289 146         235 my $length = length $c;
290              
291 146 100       3210 my $section_regex
292             = $self->compatible ? qr/\A\[([0-9a-z.-]+)(?:[\t ]*"([^\n]*?)")?\]/im
293             : qr/\A\[([^\s\[\]"]+)(?:[\t ]*"([^\n]*?)")?\]/im;
294              
295 146 100       3667 my $key_regex
296             = $self->compatible ? qr/\A([a-z][0-9a-z-]*)[\t ]*(?:[#;].*)?$/im
297             : qr/\A([^\[=\n][^=\n]*?)[\t ]*(?:[#;].*)?$/im;
298              
299 146 100       3014 my $key_value_regex
300             = $self->compatible ? qr/\A([a-z][0-9a-z-]*)[\t ]*=[\t ]*/im
301             : qr/\A([^\[=\n][^=\n]*?)[\t ]*=[\t ]*/im;
302              
303 146         1118 my($section, $prev) = (undef, '');
304 146         159 while (1) {
305             # drop leading white space and blank lines
306 1083         2723 $c =~ s/\A\s*//im;
307              
308 1083         1399 my $offset = $length - length($c);
309             # drop to end of line on comments
310 1083 100       9944 if ($c =~ s/\A[#;].*?$//im) {
    100          
    100          
    100          
    100          
311 90         125 next;
312             }
313             # [sub]section headers of the format [section "subsection"] (with
314             # unlimited whitespace between) or [section.subsection] variable
315             # definitions may directly follow the section header, on the same line!
316             # - rules for sections: not case sensitive, only alphanumeric
317             # characters, -, and . allowed
318             # - rules for subsections enclosed in ""s: case sensitive, can
319             # contain any character except newline, " and \ must be escaped
320             # - rules for subsections with section.subsection alternate syntax:
321             # same rules as for sections
322             elsif ($c =~ s/$section_regex//) {
323 323         586 $section = lc $1;
324 323 100       686 if ($2) {
325 72         110 my $subsection = $2;
326 72         131 my $check = $2;
327 72         141 $check =~ s{\\\\}{}g;
328 72         90 $check =~ s{\\"}{}g;
329 72 50       255 return $args{error}->(
330             content => $args{content},
331             offset => $offset,
332              
333             # don't allow quoted subsections to contain unescaped
334             # double-quotes or backslashes
335             ) if $check =~ /\\|"/;
336              
337 72         141 $subsection =~ s{\\\\}{\\}g;
338 72         89 $subsection =~ s{\\"}{"}g;
339 72         102 $section .= ".$subsection";
340             }
341              
342 323         826 $args{callback}->(
343             section => $section,
344             offset => $offset,
345             length => ($length - length($c)) - $offset,
346             );
347             }
348             # keys followed by a unlimited whitespace and (optionally) a comment
349             # (no value)
350             #
351             # for keys, we allow any characters that won't screw up the parsing
352             # (= and newline) in non-compatible mode, and match non-greedily to
353             # allow any trailing whitespace to be dropped
354             #
355             # in compatible mode, keys can contain only 0-9a-z-
356             elsif ($c =~ s/$key_regex//) {
357 5 50       17 return $args{error}->(
358             content => $args{content},
359             offset => $offset,
360             ) unless defined $section;
361 5         17 $args{callback}->(
362             section => $section,
363             name => $1,
364             offset => $offset,
365             length => ($length - length($c)) - $offset,
366             );
367             }
368             # key/value pairs (this particular regex matches only the key part and
369             # the =, with unlimited whitespace around the =)
370             elsif ($c =~ s/$key_value_regex//) {
371 519 50       925 return $args{error}->(
372             content => $args{content},
373             offset => $offset,
374             ) unless defined $section;
375 519         695 my $name = $1;
376 519         424 my $value = "";
377             # parse the value
378 519         374 while (1) {
379             # comment or no content left on line
380 1292 100       8442 if ($c =~ s/\A([ \t]*[#;].*?)?$//im) {
    100          
    100          
    100          
    100          
    50          
    50          
    50          
    100          
    50          
381 519         511 last;
382             }
383             # any amount of whitespace between words becomes a single space
384             elsif ($c =~ s/\A[\t ]+//im) {
385 57         57 $value .= ' ';
386             }
387             # line continuation (\ character followed by new line)
388             elsif ($c =~ s/\A\\\r?\n//im) {
389 2         5 next;
390             }
391             # escaped backslash characters is translated to actual \
392             elsif ($c =~ s/\A\\\\//im) {
393 90         89 $value .= '\\';
394             }
395             # escaped quote characters are part of the value
396             elsif ($c =~ s/\A\\(['"])//im) {
397 32         44 $value .= $1;
398             }
399             # escaped newline in config is translated to actual newline
400             elsif ($c =~ s/\A\\n//im) {
401 0         0 $value .= "\n";
402             }
403             # escaped tab in config is translated to actual tab
404             elsif ($c =~ s/\A\\t//im) {
405 0         0 $value .= "\t";
406             }
407             # escaped backspace in config is translated to actual backspace
408             elsif ($c =~ s/\A\\b//im) {
409 0         0 $value .= "\b";
410             }
411             # quote-delimited value (possibly containing escape codes)
412             elsif ($c =~ s/\A"([^"\\]*(?:(?:\\\n|\\[tbn"\\])[^"\\]*)*)"//im) {
413 12         18 my $v = $1;
414             # remove all continuations (\ followed by a newline)
415 12         16 $v =~ s/\\\n//g;
416             # swap escaped newlines with actual newlines
417 12         15 $v =~ s/\\n/\n/g;
418             # swap escaped tabs with actual tabs
419 12         105 $v =~ s/\\t/\t/g;
420             # swap escaped backspaces with actual backspaces
421 12         12 $v =~ s/\\b/\b/g;
422             # swap escaped \ with actual \
423 12         10 $v =~ s/\\\\/\\/g;
424 12         16 $value .= $v;
425             }
426             # valid value (no escape codes)
427             elsif ($c =~ s/\A([^\t \\\n"]+)//im) {
428 580         889 $value .= $1;
429             # unparseable
430             }
431             else {
432             # Note that $args{content} is the _original_
433             # content, not the nibbled $c, which is the
434             # remaining unparsed content
435 0         0 return $args{error}->(
436             content => $args{content},
437             offset => $offset,
438             );
439             }
440             }
441 519         1333 $args{callback}->(
442             section => $section,
443             name => $name,
444             value => $value,
445             offset => $offset,
446             length => ($length - length($c)) - $offset,
447             );
448             }
449             # end of content string; all done now
450             elsif (not length $c) {
451 137         536 last;
452             }
453             # unparseable
454             else {
455             # Note that $args{content} is the _original_ content, not
456             # the nibbled $c, which is the remaining unparsed content
457 9         29 return $args{error}->(
458             content => $args{content},
459             offset => $offset,
460             );
461             }
462             }
463             }
464              
465             sub define {
466 334     334 1 360 my $self = shift;
467 334         1339 my %args = (
468             section => undef,
469             name => undef,
470             value => undef,
471             origin => undef,
472             @_,
473             );
474 334 100 66     1705 return unless defined $args{section} and defined $args{name};
475 206         446 my $original_key = join(".", @args{qw/section name/});
476 206         355 $args{name} = lc $args{name};
477 206         371 my $key = join(".", @args{qw/section name/});
478              
479             # we're either adding a whole new key or adding a multiple key from
480             # the same file
481 206 100 100     4186 if ( !defined $self->origins->{$key}
482             || $self->origins->{$key} eq $args{origin} ) {
483 193 100       5897 if ($self->is_multiple($key)) {
    100          
484 1   50     1 push @{$self->data->{$key} ||= []}, $args{value};
  1         23  
485 1   50     11 push @{$self->casing->{$key} ||= []}, $original_key;
  1         18  
486             }
487             elsif (exists $self->data->{$key}) {
488 8         58 $self->set_multiple($key);
489 8         145 $self->data->{$key} = [$self->data->{$key}, $args{value}];
490 8         202 $self->casing->{$key} = [$self->casing->{$key}, $original_key];
491             }
492             else {
493 184         4178 $self->data->{$key} = $args{value};
494 184         3859 $self->casing->{$key} = $original_key;
495             }
496             }
497             # we're overriding a key set previously from a different file
498             else {
499             # un-mark as multiple if it was previously marked as such
500 13 50       444 $self->set_multiple( $key, 0 ) if $self->is_multiple( $key );
501              
502             # set the new value
503 13         304 $self->data->{$key} = $args{value};
504 13         268 $self->casing->{$key} = $original_key;
505             }
506 206         5945 $self->origins->{$key} = $args{origin};
507             }
508              
509             sub cast {
510 106     106 1 125 my $self = shift;
511 106         372 my %args = (
512             value => undef,
513             as => undef, # bool, int, or num
514             human => undef, # true value / false value
515             @_,
516             );
517              
518             use constant {
519 5         3278 BOOL_TRUE_REGEX => qr/^(?:true|yes|on|-?0*1)$/i,
520             BOOL_FALSE_REGEX => qr/^(?:false|no|off|0*)$/i,
521             NUM_REGEX => qr/^-?[0-9]*\.?[0-9]*[kmg]?$/,
522 5     5   48 };
  5         7  
523              
524 106 100 100     398 if (defined $args{as} && $args{as} eq 'bool-or-int') {
525 14 100 66     188 if ( $args{value} =~ NUM_REGEX ) {
    50          
    0          
526 6         8 $args{as} = 'int';
527             }
528             elsif ( $args{value} =~ BOOL_TRUE_REGEX ||
529             $args{value} =~ BOOL_FALSE_REGEX ) {
530 8         12 $args{as} = 'bool';
531             }
532             elsif ( !defined $args{value} ) {
533 0         0 $args{as} = 'bool';
534             }
535             else {
536 0         0 die "Invalid bool-or-int '$args{value}'\n";
537             }
538             }
539              
540 106         154 my $v = $args{value};
541 106 100       637 return $v unless defined $args{as};
542 48 100       228 if ($args{as} =~ /bool/i) {
    50          
543 34 100       60 return 1 unless defined $v;
544 33 100       147 if ( $v =~ BOOL_TRUE_REGEX ) {
    100          
545 16 100       30 if ( $args{human} ) {
546 9         44 return 'true';
547             }
548             else {
549 7         31 return 1;
550             }
551             }
552             elsif ($v =~ BOOL_FALSE_REGEX ) {
553 15 100       25 if ( $args{human} ) {
554 8         30 return 'false';
555             }
556             else {
557 7         35 return 0;
558             }
559             }
560             else {
561 2         23 die "Invalid bool '$args{value}'\n";
562             }
563             }
564             elsif ($args{as} =~ /int|num/) {
565 14 100       69 die "Invalid unit while casting to $args{as}\n"
566             unless $v =~ NUM_REGEX;
567              
568 13 100       42 if ($v =~ s/([kmg])$//) {
569 3 100       11 $v *= 1024 if $1 eq "k";
570 3 100       8 $v *= 1024*1024 if $1 eq "m";
571 3 50       7 $v *= 1024*1024*1024 if $1 eq "g";
572             }
573              
574 13 100       101 return $args{as} eq 'int' ? int $v : $v + 0;
575             }
576             }
577              
578             sub _get {
579 75     75   94 my $self = shift;
580 75         258 my %args = (
581             key => undef,
582             filter => '',
583             @_,
584             );
585 75 50       240 $self->load unless $self->is_loaded;
586              
587 75         191 $args{key} = $self->canonical_case( $args{key} );
588              
589 75 50       1584 return () unless exists $self->data->{$args{key}};
590 75         1580 my $v = $self->data->{$args{key}};
591 75 100       464 my @values = ref $v ? @{$v} : ($v);
  8         20  
592 75 100 66     465 if (defined $args{filter} and length $args{filter}) {
593 5 50       28 if ($args{filter} eq "!") {
    100          
594 0         0 @values = ();
595             }
596             elsif ($args{filter} =~ s/^!//) {
597 2   66     4 @values = grep { not defined or not m/$args{filter}/i } @values;
  4         38  
598             }
599             else {
600 3 100       8 @values = grep { defined and m/$args{filter}/i } @values;
  4         36  
601             }
602             }
603 75         242 return @values;
604             }
605              
606             # I'm pretty sure that someone can come up with an edge case where stripping
607             # all balanced quotes like this is not the right thing to do, but I don't
608             # see it actually being a problem in practice.
609             sub _remove_balanced_quotes {
610 1962     1962   2449 my $key = shift;
611              
612 5     5   28 no warnings 'uninitialized';
  5         41  
  5         16625  
613 1962         4834 $key = join '', map { s/"(.*)"/$1/; $_ } split /("[^"]+"|[^.]+)/, $key;
  4110         3320  
  4110         5443  
614 1962         4358 $key = join '', map { s/'(.*)'/$1/; $_ } split /('[^']+'|[^.]+)/, $key;
  4110         3079  
  4110         4318  
615              
616 1962         4180 return $key;
617             }
618              
619             sub get {
620 70     70 1 2496 my $self = shift;
621 70         422 my %args = (
622             key => undef,
623             as => undef,
624             human => undef,
625             filter => '',
626             @_,
627             );
628              
629 70         386 my @v = $self->_get( %args );
630 70 100       175 return undef unless @v;
631 69 100       151 die "Multiple values" if @v > 1;
632              
633 68         319 return $self->cast( value => $v[0], as => $args{as},
634             human => $args{human} );
635             }
636              
637             sub get_all {
638 5     5 1 429 my $self = shift;
639 5         34 my %args = (
640             key => undef,
641             as => undef,
642             human => undef,
643             filter => '',
644             @_,
645             );
646              
647 5         28 my @v = $self->_get( %args );
648 5         17 @v = map {$self->cast( value => $_, as => $args{as}, human => $args{human} )} @v;
  10         33  
649 5 50       50 return wantarray ? @v : \@v;
650             }
651              
652             sub get_regexp {
653 5     5 1 652 my $self = shift;
654              
655 5         28 my %args = (
656             key => undef,
657             as => undef,
658             human => undef,
659             filter => '',
660             @_,
661             );
662              
663 5 50       21 $self->load unless $self->is_loaded;
664              
665 5 50 33     32 $args{key} = '.' unless defined $args{key} and length $args{key};
666              
667 5         7 my %results;
668 5         4 for my $key (keys %{$self->data}) {
  5         109  
669 16 100       326 $results{$key} = $self->data->{$key} if $key =~ m/$args{key}/i;
670             }
671              
672 5 100 66     51 if (defined $args{filter} and length $args{filter}) {
673 1 50       11 if ($args{filter} eq "!") {
    50          
674 0         0 %results = ();
675             }
676             elsif ($args{filter} =~ s/^!//) {
677 1         5 for (keys %results) {
678 3 100 66     26 delete $results{$_} if defined $results{$_}
679             and $results{$_} =~ m/$args{filter}/i;
680             }
681             }
682             else {
683 0         0 for (keys %results) {
684 0 0 0     0 delete $results{$_} if not defined $results{$_}
685             or $results{$_} !~ m/$args{filter}/i;
686             }
687             }
688             }
689              
690 8         27 @results{keys %results} =
691 5         15 map { $self->cast(
692             value => $results{$_},
693             as => $args{as},
694             human => $args{human},
695             ); } keys %results;
696 5 50       43 return wantarray ? %results : \%results;
697             }
698              
699             sub original_key {
700 6     6 1 11 my $self = shift;
701 6         7 my ($key) = @_;
702 6         144 return $self->casing->{ $self->canonical_case( $key ) };
703             }
704              
705             sub canonical_case {
706 544     544 1 2440 my $self = shift;
707 544         616 my ($key) = @_;
708 544         964 my ($section, $subsection, $name) = _split_key($key);
709 1632         6661 return join( '.',
710 544         1097 grep { defined } (lc $section, $subsection, lc $name),
711             );
712             }
713              
714             sub dump {
715 5     5 1 44 my $self = shift;
716              
717 5 50       23 $self->load unless $self->is_loaded;
718              
719 5 100       19 return %{$self->data} if wantarray;
  2         41  
720              
721 3         7 my $data = '';
722 3         6 for my $key (sort keys %{$self->data}) {
  3         52  
723 9         42 my $str;
724 9 50       148 if (defined $self->data->{$key}) {
725             # For git compat, we intentionally always write out in
726             # canonical (i.e. lower) case.
727 9         53 $str = "$key=";
728 9 50       18 if ( $self->is_multiple($key) ) {
729 0         0 $str .= '[';
730 0         0 $str .= join(', ', @{$self->data->{$key}});
  0         0  
731 0         0 $str .= "]\n";
732             }
733             else {
734 9         176 $str .= $self->data->{$key}."\n";
735             }
736             }
737             else {
738 0         0 $str = "$key\n";
739             }
740 9 50       67 if (!defined wantarray) {
741 0         0 print $str;
742             }
743             else {
744 9         20 $data .= $str;
745             }
746             }
747              
748 3 50       43 return $data if defined wantarray;
749             }
750              
751             sub format_section {
752 32     32 1 54 my $self = shift;
753              
754 32         135 my %args = (
755             section => undef,
756             bare => undef,
757             @_,
758             );
759              
760 32 100       110 if ($args{section} =~ /^(.*?)\.(.*)$/) {
761 7         31 my ($section, $subsection) = ($1, $2);
762 7         24 my $ret = qq|[$section "$subsection"]|;
763 7 100       23 $ret .= "\n" unless $args{bare};
764 7         21 return $ret;
765             }
766             else {
767 25         56 my $ret = qq|[$args{section}]|;
768 25 50       70 $ret .= "\n" unless $args{bare};
769 25         66 return $ret;
770             }
771             }
772              
773             sub format_definition {
774 83     83 1 105 my $self = shift;
775 83         308 my %args = (
776             key => undef,
777             value => undef,
778             bare => undef,
779             @_,
780             );
781 83 100       481 my $quote = $args{value} =~ /(^\s|;|#|\s$)/ ? '"' : '';
782 83         153 $args{value} =~ s/\\/\\\\/g;
783 83         135 $args{value} =~ s/"/\\"/g;
784 83         103 $args{value} =~ s/\t/\\t/g;
785 83         129 $args{value} =~ s/\n/\\n/g;
786 83         329 my $ret = "$args{key} = $quote$args{value}$quote";
787 83 100       211 $ret = "\t$ret\n" unless $args{bare};
788 83         423 return $ret;
789             }
790              
791             # Given a key, return its variable name, section, and subsection
792             # parts. Doesn't do any lowercase transformation.
793             sub _split_key {
794 981     981   2071 my $key = shift;
795              
796 981         937 my ($name, $section, $subsection);
797             # allow quoting of the key to, for example, preserve
798             # . characters in the key
799 981 100       2721 if ( $key =~ s/\.["'](.*)["']$// ) {
800 4         11 $name = $1;
801 4         9 $section = $key;
802             }
803             else {
804 977         2955 $key =~ /^(.*)\.(.*)$/;
805             # If we wanted, we could interpret quoting of the section name to
806             # allow for setting keys with section names including . characters.
807             # But git-config doesn't do that, so we won't bother for now. (Right
808             # now it will read these section names correctly but won't set them.)
809 977         1226 ($section, $name) = map { _remove_balanced_quotes($_) } ($1, $2);
  1954         2731  
810             }
811              
812             # Make sure the section name we're comparing against has
813             # case-insensitive section names and case-sensitive subsection names.
814 981         2777 $section =~ m/^([^.]+)(?:\.(.*))?$/;
815 981         1520 ($section, $subsection) = ($1, $2);
816              
817 981         1983 return ($section, $subsection, $name);
818             }
819              
820             sub group_set {
821 102     102 1 1150 my $self = shift;
822 102         124 my ($filename, $args_ref) = @_;
823              
824 102         259 my $c = $self->_read_config($filename); # undef if file doesn't exist
825              
826             # loop through each value to set, modifying the content to be written
827             # or erroring out as we go
828 102         130 for my $args_hash (@{$args_ref}) {
  102         276  
829 103         99 my %args = %{$args_hash};
  103         525  
830              
831 103         311 my ($section, $subsection, $name) = _split_key($args{key});
832              
833 103 100       285 die "No section given in key or invalid key $args{key}\n"
834             unless defined $section;
835              
836 99 100       381 die "Invalid variable name $name\n"
837             if $self->_invalid_variable_name($name);
838              
839 92 100       383 die "Invalid section name $section\n"
840             if $self->_invalid_section_name($section);
841              
842             # if the subsection to write contains unescaped \ or ", escape them
843             # automatically
844 90         130 my $unescaped_subsection;
845 90 100       150 if ( defined $subsection ) {
846 5         15 $unescaped_subsection = $subsection;
847 5         13 $subsection =~ s{\\}{\\\\}g;
848 5         9 $subsection =~ s{"}{\\"}g;
849             }
850              
851 90 100 100     518 $args{value} = $self->cast(
852             value => $args{value},
853             as => $args{as},
854             human => 1,
855             ) if defined $args{value} && defined $args{as};
856              
857 89         93 my $new;
858             my @replace;
859              
860 89         278 my $key = $self->canonical_case( $args{key} );
861              
862 89 100       409 $args{multiple} = $self->is_multiple($key)
863             unless defined $args{multiple};
864              
865             # use this for comparison
866 89 100       324 my $cmp_section =
867             defined $unescaped_subsection
868             ? join( '.', lc $section, $unescaped_subsection )
869             : lc $section;
870             # ...but this for writing (don't lowercase)
871 89 100       165 my $combined_section
872             = defined $subsection ? join('.', $section, $subsection)
873             : $section;
874              
875             # There's not really a good, simple way to get around parsing the
876             # content for each of the values we're setting. If we wanted to
877             # extract the offsets for every single one using only a single parse
878             # run, we'd end up having to munge all the offsets afterwards as we
879             # did the actual replacement since every time we did a replacement it
880             # would change the offsets for anything that was formerly to be added
881             # at a later offset. Which I'm not sure is any better than just
882             # parsing it again.
883             $self->parse_content(
884             content => $c,
885             callback => sub {
886 481     481   1170 my %got = @_;
887 481 100       1187 return unless $got{section} eq $cmp_section;
888 204         207 $new = $got{offset} + $got{length};
889 204 100       405 return unless defined $got{name};
890              
891 146         125 my $matched = 0;
892             # variable names are case-insensitive
893 146 100       316 if (lc $name eq lc $got{name}) {
894 28 100 66     87 if (defined $args{filter} and length $args{filter}) {
895             # copy the filter arg here since this callback may
896             # be called multiple times and we don't want to
897             # modify the original value
898 8         10 my $filter = $args{filter};
899 8 50       60 if ($filter eq "!") {
    100          
    100          
900             # Never matches
901             }
902             elsif ($filter =~ s/^!//) {
903 3 100       39 $matched = 1 if ($got{value} !~ m/$filter/i);
904             }
905             elsif ($got{value} =~ m/$filter/i) {
906 3         6 $matched = 1;
907             }
908             }
909             else {
910 20         29 $matched = 1;
911             }
912             }
913              
914 146 100       433 push @replace, {offset => $got{offset}, length => $got{length}}
915             if $matched;
916             },
917             error => sub {
918 0     0   0 error_callback(@_, filename => $args{filename})
919             },
920 89         1036 );
921              
922 89 100 100     900 die "Multiple occurrences of non-multiple key?"
923             if @replace > 1 && !$args{multiple};
924              
925             # We're only replacing the first occurrance unless they said
926             # to replace them all.
927 88 100 100     327 @replace = ($replace[0]) if @replace and $args{value} and not $args{replace_all};
      100        
928              
929 88 100       164 if (defined $args{value}) {
930 83 100 100     327 if (@replace
    100 66        
931             && (!$args{multiple} || $args{filter} || $args{replace_all})) {
932             # Replacing existing value(s)
933              
934             # if the string we're replacing with is not the same length as
935             # what's being replaced, any offsets following will be wrong.
936             # save the difference between the lengths here and add it to
937             # any offsets that follow.
938 11         20 my $difference = 0;
939              
940             # when replacing multiple values, we combine them all into one,
941             # which is kept at the position of the last one
942 11         17 my $last = pop @replace;
943              
944             # kill all values that are not last
945 11         60 ($c, $difference) = _unset_variables(\@replace, $c,
946             $difference);
947              
948             # substitute the last occurrence with the new value
949 11         79 substr(
950             $c,
951             $last->{offset}-$difference,
952             $last->{length},
953             $self->format_definition(
954             key => $name,
955             value => $args{value},
956             bare => 1,
957             ),
958             );
959             }
960             elsif (defined $new) {
961             # Adding a new value to the end of an existing block
962 42         250 substr(
963             $c,
964             index($c, "\n", $new)+1,
965             0,
966             $self->format_definition(
967             key => $name,
968             value => $args{value}
969             )
970             );
971             }
972             else {
973             # Adding a new section
974 30         153 $c .= $self->format_section( section => $combined_section );
975 30         147 $c .= $self->format_definition(
976             key => $name,
977             value => $args{value},
978             );
979             }
980             }
981             else {
982             # Removing an existing value (unset / unset-all)
983 5 100       20 die "No occurrence of $args{key} found to unset in $filename\n"
984             unless @replace;
985              
986 4         13 ($c, undef) = _unset_variables(\@replace, $c, 0);
987             }
988             }
989 86         311 return $self->_write_config( $filename, $c );
990             }
991              
992             sub set {
993 101     101 1 37478 my $self = shift;
994 101         736 my (%args) = (
995             key => undef,
996             value => undef,
997             filename => undef,
998             filter => undef,
999             as => undef,
1000             multiple => undef,
1001             @_
1002             );
1003              
1004 101         136 my $filename = $args{filename};
1005 101         211 delete $args{filename};
1006              
1007 101         431 return $self->group_set( $filename, [ \%args ] );
1008             }
1009              
1010             sub _unset_variables {
1011 15     15   29 my ($variables, $c, $difference) = @_;
1012              
1013 15         13 for my $var (@{$variables}) {
  15         35  
1014             # start from either the last newline or the last section
1015             # close bracket, since variable definitions can occur
1016             # immediately following a section header without a \n
1017 6         17 my $newline = rindex($c, "\n", $var->{offset}-$difference);
1018             # need to add 1 here to not kill the ] too
1019 6         13 my $bracket = rindex($c, ']', $var->{offset}-$difference) + 1;
1020 6 100       13 my $start = $newline > $bracket ? $newline : $bracket;
1021              
1022 6         12 my $length =
1023             index($c, "\n", $var->{offset}-$difference+$var->{length})-$start;
1024              
1025 6         15 substr(
1026             $c,
1027             $start,
1028             $length,
1029             '',
1030             );
1031 6         12 $difference += $length;
1032             }
1033              
1034 15         46 return ($c, $difference);
1035             }
1036              
1037             # In non-git-compatible mode, variables names can contain any characters that
1038             # aren't newlines or = characters, but cannot start or end with whitespace.
1039             #
1040             # Allowing . characters in variable names actually makes it so you
1041             # can get collisions between identifiers for things that are not
1042             # actually the same.
1043             #
1044             # For example, you could have a collision like this:
1045             # [section "foo"] bar.com = 1
1046             # [section] foo.bar.com = 1
1047             #
1048             # Both of these would be turned into 'section.foo.bar.com'. But it's
1049             # unlikely to ever actually come up, since you'd have to have
1050             # a *need* to have two things like this that are very similar
1051             # and yet different.
1052             sub _invalid_variable_name {
1053 99     99   132 my ($self, $name) = @_;
1054              
1055 99 100       2007 if ($self->compatible) {
1056 7         97 return $name !~ /^[a-z][0-9a-z-]*$/i;
1057             }
1058             else {
1059 92   100     4442 return $name !~ /^[^=\n\[][^=\n]*$/ || $name =~ /(?:^[ \t]+|[ \t+]$)/;
1060             }
1061             }
1062              
1063             # section, NOT subsection!
1064             sub _invalid_section_name {
1065 92     92   147 my ($self, $section) = @_;
1066              
1067 92 100       1504 if ($self->compatible) {
1068 3         39 return $section !~ /^[0-9a-z-.]+$/i;
1069             }
1070             else {
1071 89         746 return $section =~ /\s|\[|\]|"/;
1072             }
1073             }
1074              
1075             # write config with locking
1076             sub _write_config {
1077 94     94   127 my $self = shift;
1078 94         128 my($filename, $content) = @_;
1079              
1080             # allow nested symlinks but only within reason
1081 94         103 my $max_depth = 5;
1082              
1083             # resolve symlinks
1084 94         269 while ($max_depth--) {
1085 470         2920 my $readlink = readlink $filename;
1086 470 100       1049 $filename = $readlink if defined $readlink;
1087             }
1088              
1089             # write new config file to temp file
1090             # (the only reason we call it .lock is because that's the
1091             # way git does it)
1092 94 50       6544 sysopen(my $fh, "${filename}.lock", O_CREAT|O_EXCL|O_WRONLY)
1093             or die "Can't open ${filename}.lock for writing: $!\n";
1094 94 100       2515 if (my $encoding = $self->encoding) {
1095 1     1   683 binmode $fh, ":encoding($encoding)";
  1         9  
  1         1  
  1         10  
1096             }
1097 94         15590 print $fh $content;
1098 94         3298 close $fh;
1099              
1100             # atomic rename
1101 94 50       6392 rename("${filename}.lock", ${filename})
1102             or die "Can't rename ${filename}.lock to ${filename}: $!\n";
1103             }
1104              
1105             sub rename_section {
1106 5     5 1 3008 my $self = shift;
1107              
1108 5         34 my (%args) = (
1109             from => undef,
1110             to => undef,
1111             filename => undef,
1112             @_
1113             );
1114              
1115 5 50       19 die "No section to rename from given\n" unless defined $args{from};
1116              
1117 5         22 my $c = $self->_read_config($args{filename});
1118             # file couldn't be opened = nothing to rename
1119 5 50       18 return if !defined($c);
1120              
1121 8         15 ($args{from}, $args{to}) = map { _remove_balanced_quotes($_) }
  10         20  
1122 5         13 grep { defined $_ } ($args{from}, $args{to});
1123              
1124 5         9 my @replace;
1125 5         6 my $prev_matched = 0;
1126             $self->parse_content(
1127             content => $c,
1128             callback => sub {
1129 32     32   78 my %got = @_;
1130              
1131 32 100 100     92 $replace[-1]->{section_is_last} = 0
1132             if (@replace && !defined($got{name}));
1133              
1134 32 100       67 if (lc($got{section}) eq lc($args{from})) {
1135 14 100       22 if (defined $got{name}) {
1136             # if we're removing rather than replacing and
1137             # there was a previous section match, increase
1138             # its length so it will kill this variable
1139             # assignment too
1140 7 100 66     29 if ($prev_matched && !defined $args{to} ) {
1141 4         19 $replace[-1]->{length} += ($got{offset} + $got{length})
1142             - ($replace[-1]{offset} + $replace[-1]->{length});
1143             }
1144             }
1145             else {
1146             # if we're removing rather than replacing, increase
1147             # the length of the previous match so when it's
1148             # replaced it will kill all the way up to the
1149             # beginning of this next section (this will kill
1150             # any leading whitespace on the line of the
1151             # next section, but that's OK)
1152 7 100 100     31 $replace[-1]->{length} += $got{offset} -
      100        
1153             ($replace[-1]->{offset} + $replace[-1]->{length})
1154             if @replace && $prev_matched && !defined($args{to});
1155              
1156 7         21 push @replace, {offset => $got{offset}, length =>
1157             $got{length}, section_is_last => 1};
1158 7         15 $prev_matched = 1;
1159             }
1160             }
1161             else {
1162             # if we're removing rather than replacing and there was
1163             # a previous section match, increase its length to kill all
1164             # the way up to this non-matching section (takes care
1165             # of newlines between here and there, etc.)
1166 18 100 100     55 $replace[-1]->{length} += $got{offset} -
      100        
1167             ($replace[-1]->{offset} + $replace[-1]->{length})
1168             if @replace && $prev_matched && !defined($args{to});
1169 18         37 $prev_matched = 0;
1170             }
1171             },
1172             error => sub {
1173 0     0   0 error_callback( @_, filename => $args{filename} );
1174             },
1175 5         61 );
1176 5 100       59 die "No such section '$args{from}'\n"
1177             unless @replace;
1178              
1179             # if the string we're replacing with is not the same length as what's
1180             # being replaced, any offsets following will be wrong. save the difference
1181             # between the lengths here and add it to any offsets that follow.
1182 4         6 my $difference = 0;
1183              
1184             # rename ALL section headers that matched to
1185             # (there may be more than one)
1186 4 100       20 my $replace_with = defined $args{to} ?
1187             $self->format_section( section => $args{to}, bare => 1 ) : '';
1188              
1189 4         12 for my $header (@replace) {
1190 7 100 100     37 substr(
1191             $c,
1192             $header->{offset} + $difference,
1193             # if we're removing the last section, just kill all the way to the
1194             # end of the file
1195             !defined($args{to}) && $header->{section_is_last} ? length($c) -
1196             ($header->{offset} + $difference) : $header->{length},
1197             $replace_with,
1198             );
1199 7         16 $difference += (length($replace_with) - $header->{length});
1200             }
1201              
1202 4         23 return $self->_write_config($args{filename}, $c);
1203             }
1204              
1205             sub remove_section {
1206 2     2 1 2307 my $self = shift;
1207              
1208 2         13 my (%args) = (
1209             section => undef,
1210             filename => undef,
1211             @_
1212             );
1213              
1214 2 50       11 die "No section given to remove\n" unless $args{section};
1215              
1216             # remove section is just a rename to nothing
1217 2         26 return $self->rename_section( from => $args{section}, filename =>
1218             $args{filename} );
1219             }
1220              
1221             sub add_comment {
1222 4     4 1 1022 my $self = shift;
1223 4         24 my (%args) = (
1224             comment => undef,
1225             filename => undef,
1226             indented => undef,
1227             semicolon => undef,
1228             @_
1229             );
1230              
1231 4 50       13 my $filename = $args{filename} or die "No filename passed to add_comment()";
1232 4 50       10 die "No comment to add\n" unless defined $args{comment};
1233              
1234             # Comment, preserving leading whitespace.
1235 4 100       8 my $chars = $args{indented} ? '[[:blank:]]*' : '';
1236 4 100       8 my $char = $args{semicolon} ? ';' : '#';
1237 4         70 (my $comment = $args{comment}) =~ s/^($chars)/$1$char /mg;
1238 4 50       14 $comment .= "\n" if $comment !~ /\n\z/;
1239              
1240 4         11 my $c = $self->_read_config($filename);
1241 4 100       11 $c = '' unless defined $c;
1242              
1243 4         22 return $self->_write_config( $filename, $c . $comment );
1244             }
1245              
1246             1;
1247              
1248             __END__