File Coverage

blib/lib/Config/AWS.pm
Criterion Covered Total %
statement 34 34 100.0
branch 12 12 100.0
condition 10 13 76.9
subroutine 15 15 100.0
pod 6 9 66.6
total 77 83 92.7


line stmt bran cond sub pod time code
1             package Config::AWS;
2             # ABSTRACT: Parse AWS config files
3              
4 5     5   1092492 use strict;
  5         42  
  5         166  
5 5     5   27 use warnings;
  5         11  
  5         241  
6              
7 5     5   30 use Carp ();
  5         11  
  5         107  
8 5     5   2534 use Ref::Util;
  5         9328  
  5         275  
9 5     5   37 use Scalar::Util;
  5         12  
  5         269  
10 5         41 use Exporter::Shiny qw(
11             read
12             read_all
13             list_profiles
14             read_file
15             read_string
16             read_handle
17             config_file
18             credentials_file
19             default_profile
20 5     5   2825 );
  5         21746  
21              
22             our $VERSION = '0.10';
23             our %EXPORT_TAGS = (
24             ini => [qw( read_file read_string read_handle )],
25             aws => [qw( config_file default_profile credentials_file )],
26             read => [qw( read read_all list_profiles )],
27             all => [qw( :ini :aws :read )],
28             );
29              
30             # Internal methods for parsing and validation
31              
32             my $prepare = sub {
33             # Input is given argument or credentials file (if exists) or config file.
34             my $input = shift // do {
35             my $cred_file = credentials_file();
36             -r $cred_file ? $cred_file : config_file();
37             };
38              
39             unless ( Ref::Util::is_ref $input ) {
40             require Path::Tiny;
41             my @lines = eval { Path::Tiny::path( $input )->lines };
42             if ($@) {
43             Carp::croak "Cannot read from $input: $@->{err}"
44             if ref $@ && $@->isa('Path::Tiny::Error');
45             Carp::croak $@;
46             }
47             return \@lines;
48             }
49              
50             return [ $input->getlines ] if Scalar::Util::openhandle $input;
51              
52             if ( Ref::Util::is_blessed_ref $input ) {
53             return [ $input->slurp ] if $input->isa('Path::Class::File');
54             return [ $input->lines ] if $input->isa('Path::Tiny');
55              
56             Carp::croak 'Cannot read from objects of type ', ref $input;
57             }
58              
59             return [ split /\R/, $$input ] if Ref::Util::is_scalarref $input;
60             return $input if Ref::Util::is_arrayref $input;
61              
62             Carp::croak "Could not use $input as source for ", (caller 1)[3];
63             };
64              
65             my $read = sub {
66             my ($lines, $target_profile) = @_;
67              
68             Carp::carp 'Reading config with only one line or less. Faulty input?'
69             if @$lines <= 1;
70              
71             my $hash = {};
72             my $nested = {};
73             my $profile = '';
74              
75             for my $i (0 .. $#$lines) {
76             my $line = $lines->[$i];
77             $line =~ s/\R$//;
78              
79             if ($line =~ /^\[(?:profile )?([\w-]+)\]/) {
80             $profile = $1;
81             next;
82             }
83              
84             next if $target_profile && $profile ne $target_profile;
85              
86             next unless my ($indent, $key, $value) = $line =~ /^(\s*)(\w+)\s*=\s*(.*)/;
87              
88             if (length $indent) {
89             $nested->{$key} = $value;
90             }
91             else {
92             # Add nested hash if the value is empty and next line is indented.
93             $hash->{$profile}{$key}
94             = !length $value && $i < $#$lines && $lines->[$i + 1] =~ /^\s+/
95             ? $nested : $value;
96              
97             $nested = {} if keys %$nested;
98             }
99             }
100              
101             return $target_profile ? ( $hash->{$target_profile} // {} ) : $hash;
102             };
103              
104             # Config parsing interface
105              
106             sub read {
107 37   66 37 1 240988 $read->( $prepare->(shift), shift // default_profile() );
108             }
109              
110             sub read_all {
111 3     3 1 28910 $read->( &$prepare );
112             }
113              
114             sub list_profiles {
115 8     8 1 124808 map /^\[(?:profile )?([\w-]+)\]/, @{ &$prepare };
  8         19  
116             }
117              
118             # AWS information methods
119              
120             sub default_profile {
121 20   100 20 1 80414 $ENV{AWS_DEFAULT_PROFILE} // 'default';
122             }
123              
124             sub credentials_file {
125 13   66 13 1 4214 $ENV{AWS_SHARED_CREDENTIALS_FILE} // ( glob '~/.aws/credentials' )[0];
126             }
127              
128             sub config_file {
129 11   66 11 1 4357 $ENV{AWS_CONFIG_FILE} // ( glob '~/.aws/config' )[0];
130             }
131              
132             # Methods for compatibility with Config::INI interface
133              
134             sub read_file {
135 6 100   6 0 22722 Carp::croak 'Filename is missing' unless @_;
136 5 100       245 Carp::croak 'Argument was not a string' if Ref::Util::is_ref $_[0];
137 3         17 $read->( $prepare->(shift), @_ );
138             }
139              
140             sub read_string {
141 6 100   6 0 130091 Carp::croak 'String is missing' unless @_;
142 5 100       238 Carp::croak 'Argument was not a string' if Ref::Util::is_ref $_[0];
143 3   100     65 $read->( [ split /\R/, shift // '' ], @_ );
144             }
145              
146             sub read_handle {
147 5 100   5 0 25270 Carp::croak 'Handle is missing' unless @_;
148 4 100       248 Carp::croak 'Argument was not a handle' unless Scalar::Util::openhandle $_[0];
149 2         100 $read->( [ shift->getlines ], @_ );
150             }
151              
152             1;
153              
154             __END__