File Coverage

blib/lib/Env/Dot/Functions.pm
Criterion Covered Total %
statement 78 81 96.3
branch 20 28 71.4
condition 4 8 50.0
subroutine 13 13 100.0
pod 2 2 100.0
total 117 132 88.6


line stmt bran cond sub pod time code
1             ## no critic (ValuesAndExpressions::ProhibitConstantPragma)
2             package Env::Dot::Functions;
3 3     3   466007 use strict;
  3         15  
  3         98  
4 3     3   15 use warnings;
  3         6  
  3         86  
5 3     3   2029 use Data::Dumper;
  3         19934  
  3         181  
6              
7 3     3   36 use Exporter 'import';
  3         7  
  3         182  
8             our @EXPORT_OK = qw(
9             get_dotenv_vars
10             interpret_dotenv_filepath_var
11             );
12             our %EXPORT_TAGS = ( 'all' => [qw( get_dotenv_vars interpret_dotenv_filepath_var )], );
13              
14 3     3   999 use English qw( -no_match_vars ); # Avoids regex performance penalty in perl 5.18 and earlier
  3         7510  
  3         21  
15 3     3   1077 use Carp;
  3         19  
  3         285  
16              
17             # ABSTRACT: Read environment variables from .env file
18              
19             our $VERSION = '0.005'; # VERSION: generated by DZP::OurPkgVersion
20              
21             use constant {
22 3         3788 OPTION_FILE_TYPE => q{file:type},
23             OPTION_FILE_TYPE_PLAIN => q{plain},
24             OPTION_FILE_TYPE_SHELL => q{shell},
25             DEFAULT_OPTION_FILE_TYPE => q{shell},
26 3     3   22 };
  3         20  
27              
28             my %DOTENV_OPTIONS = (
29             'file:type' => 1,
30             'var:allow_interpolate' => 1,
31             );
32              
33             sub get_dotenv_vars {
34 1     1 1 3 my @dotenv_filepaths = @_;
35              
36 1         2 my @vars;
37 1         2 foreach my $filepath (@dotenv_filepaths) {
38 1 50       28 if ( -f $filepath ) {
39 1         4 my @rows = _read_dotenv_file($filepath);
40 1         10 my @tmp = _interpret_dotenv(@rows);
41 1         5 push @vars, @tmp;
42             }
43             else {
44 0         0 carp "No file found: '$filepath'";
45             }
46             }
47 1         7 return @vars;
48             }
49              
50             sub interpret_dotenv_filepath_var { ## no critic (Subroutines::RequireArgUnpacking)
51 7     7 1 9954 return split qr{:}msx, $_[0];
52             }
53              
54             # Private subroutines
55              
56             sub _interpret_dotenv {
57 4     4   12974 my (@rows) = @_;
58 4         16 my %options = (
59             'file:type' => DEFAULT_OPTION_FILE_TYPE,
60             'var:allow_interpolate' => 0,
61             ); # Options related to reading the file. Applied as they are read.
62             # my %vars;
63 4         6 my @vars;
64 4         10 foreach (@rows) {
65             ## no critic (ControlStructures::ProhibitCascadingIfElse)
66             ## no critic (RegularExpressions::ProhibitComplexRegexes)
67 33 100       200 if (
    100          
    100          
    50          
68             # This is envdot meta command
69             # The var: options can only apply to one subsequent var row.
70             m{
71             ^ [[:space:]]{0,} [#]{1}
72             [[:space:]]{1,} envdot [[:space:]]{1,}
73             [(] (? [^)]{0,}) [)]
74             [[:space:]]{0,} $
75             }msx
76             )
77             {
78 6         39 my $opts = _interpret_opts( $LAST_PAREN_MATCH{opts} );
79 6         22 _validate_opts($opts);
80 6         8 $options{'var:allow_interpolate'} = 0;
81 6         10 foreach ( keys %{$opts} ) {
  6         13  
82 6         25 $options{$_} = $opts->{$_};
83             }
84             }
85             elsif (
86             # This is comment row
87             m{
88             ^ [[:space:]]{0,} [#]{1} .* $
89             }msx
90             )
91             {
92 5         10 1;
93             }
94             elsif (
95             # This is empty row
96             m{
97             ^ [[:space:]]{0,} $
98             }msx
99             )
100             {
101 4         8 1;
102             }
103             elsif (
104             # This is env var description
105             m{
106             ^ (? [^=]{1,}) = (? .*) $
107             }msx
108             )
109             {
110 18         120 my ( $name, $value ) = ( $LAST_PAREN_MATCH{name}, $LAST_PAREN_MATCH{value} );
111 18 100       66 if ( $options{'file:type'} eq OPTION_FILE_TYPE_SHELL ) {
    50          
112 11 100       546 if (
113             $value =~ m{
114             ^
115             ['"]{1} (? .*) ["']{1} # Get value from between quotes
116             (?: [;] [[:space:]]{0,} export [[:space:]]{1,} $name)? # optional
117             [[:space:]]{0,} # optional whitespace at the end
118             $
119             }msx
120             )
121             {
122 6         39 ($value) = $LAST_PAREN_MATCH{value};
123             }
124              
125             # "export" can also be at the start. Only for TYPE_SHELL
126 11 100       53 if ( $name =~ m{^ [[:space:]]{0,} export [[:space:]]{1,} }msx ) {
127 1         13 $name =~ m{
128             ^
129             [[:space:]]{0,} export [[:space:]]{1,} (? .*)
130             $
131             }msx;
132 1         6 $name = $LAST_PAREN_MATCH{name};
133             }
134             }
135             elsif ( $options{'file:type'} eq OPTION_FILE_TYPE_PLAIN ) {
136 7         10 1;
137             }
138 18         81 my %opts = ( allow_interpolate => $options{'var:allow_interpolate'}, );
139 18         60 push @vars, { name => $name, value => $value, opts => \%opts, };
140 18         42 $options{'var:allow_interpolate'} = 0;
141             }
142             else {
143 0         0 carp "Uninterpretable row: $_";
144             }
145             }
146 4         21 return @vars;
147             }
148              
149             sub _validate_opts {
150 6     6   27 my ($opts) = @_;
151 6         10 foreach my $key ( keys %{$opts} ) {
  6         32  
152 6 50       23 if ( !exists $DOTENV_OPTIONS{$key} ) {
153 0         0 croak "Unknown envdot option: $key";
154             }
155             }
156 6         13 return;
157             }
158              
159             sub _interpret_opts {
160 12     12   9795 my ($opts_str) = @_;
161 12         102 my @opts = split qr{
162             [[:space:]]{0,} [,] [[:space:]]{0,}
163             }msx, $opts_str;
164 12         31 my %opts;
165 12         23 foreach (@opts) {
166             ## no critic (ControlStructures::ProhibitPostfixControls)
167 17         77 my ( $key, $val ) = split qr/=/msx;
168 17   100     53 $val = $val // 1;
169 17 50 33     68 $val = 1 if ( $val eq 'true' || $val eq 'True' );
170 17 50 33     50 $val = 0 if ( $val eq 'false' || $val eq 'False' );
171 17         65 $opts{$key} = $val;
172             }
173 12         34 return \%opts;
174             }
175              
176             sub _read_dotenv_file {
177 1     1   3 my ($filepath) = @_;
178 1 50       59 open my $fh, q{<}, $filepath or croak "Cannot open file '$filepath'";
179 1         3 my @dotenv_rows;
180 1         46 while (<$fh>) { chomp; push @dotenv_rows, $_; }
  10         16  
  10         30  
181 1 50       14 close $fh or croak "Cannot close file '$filepath'";
182 1         11 return @dotenv_rows;
183             }
184              
185             1;
186              
187             __END__