File Coverage

blib/lib/Env/Dot.pm
Criterion Covered Total %
statement 52 54 96.3
branch 12 20 60.0
condition 4 7 57.1
subroutine 10 10 100.0
pod 1 1 100.0
total 79 92 85.8


line stmt bran cond sub pod time code
1             ## no critic (ValuesAndExpressions::ProhibitConstantPragma)
2             package Env::Dot;
3 6     6   1557761 use strict;
  6         11  
  6         272  
4 6     6   54 use warnings;
  6         49  
  6         368  
5 6     6   124 use 5.010;
  6         19  
6              
7 6     6   7778 use English qw( -no_match_vars ); # Avoids regex performance penalty in perl 5.18 and earlier
  6         8783  
  6         44  
8 6     6   2596 use Carp;
  6         16  
  6         658  
9              
10             # ABSTRACT: Read environment variables from .env file
11              
12             our $VERSION = '0.020';
13              
14             # We define our own import routine because
15             # this is the point (when `use Env::Dot` is called)
16             # when we do our magic.
17              
18             {
19 6     6   40 no warnings 'redefine'; ## no critic [TestingAndDebugging::ProhibitNoWarnings]
  6         11  
  6         1375  
20              
21             sub import {
22 13     13   71080 my ( $class, $cmd, $args ) = @_;
23              
24             # We also allow only: 'use Env::Dot;'
25 13 50 66     115 croak "Unknown argument '$cmd'" if ( $cmd && $cmd ne 'read' );
26              
27 13 50 100     30 if ( !load_vars( %{ $args // {} } ) ) {
  13         1474  
28 0         0 croak 'Errors in environment detected.';
29             }
30 11         298866 return;
31             }
32             }
33              
34 6         941 use Env::Dot::Functions qw(
35             get_dotenv_vars
36             interpret_dotenv_filepath_var
37             get_envdot_filepaths_var_name
38             extract_error_msg
39             create_error_msg
40 6     6   3065 );
  6         21  
41              
42             use constant {
43 6         4910 OPTION_FILE_TYPE => q{file:type},
44             OPTION_FILE_TYPE_PLAIN => q{plain},
45             OPTION_FILE_TYPE_SHELL => q{shell},
46             DEFAULT_OPTION_FILE_TYPE => q{shell},
47             DEFAULT_ENVDOT_FILEPATHS => q{.env},
48             DEFAULT_ENVDOT_FILE_REQUIRED => q{0},
49             INDENT => q{ },
50 6     6   51 };
  6         10  
51              
52             sub load_vars {
53 13     13 1 39 my (%args) = @_;
54 13         60 my %allowed_args = ( 'dotenv_file' => 1, 'required' => 1, );
55 13         38 foreach my $arg ( keys %args ) {
56 1 50       5 croak "Illegal argument '$arg'" if ( !exists $allowed_args{$arg} );
57             }
58 13         24 my @dotenv_filepaths;
59 13 100       82 if ( $args{'dotenv_file'} ) {
    100          
60 1         3 @dotenv_filepaths = ( $args{'dotenv_file'} );
61             }
62             elsif ( exists $ENV{ get_envdot_filepaths_var_name() } ) {
63 5         12 @dotenv_filepaths = interpret_dotenv_filepath_var( $ENV{ get_envdot_filepaths_var_name() } );
64             }
65             else {
66 7 50 0     180 if ( -f DEFAULT_ENVDOT_FILEPATHS ) {
    0          
67 7         21 @dotenv_filepaths = (DEFAULT_ENVDOT_FILEPATHS); # The CLI parameter
68             }
69             elsif ( $args{'required'} // DEFAULT_ENVDOT_FILE_REQUIRED ) {
70 0         0 croak 'No .env file found';
71             }
72             }
73              
74 13         31 my @vars;
75 13 100       24 eval { @vars = get_dotenv_vars(@dotenv_filepaths); 1; } or do {
  13         52  
  11         44  
76 2         9 my $e = $EVAL_ERROR;
77 2         9 my ( $err, $l, $fp ) = extract_error_msg($e);
78 2 50       270 croak 'Error: ' . $err . ( $l ? qq{ line $l} : q{} ) . ( $fp ? qq{ file '$fp'} : q{} );
    50          
79             };
80 11         23 my %new_env;
81              
82             # Populate new env with the dotenv variables.
83 11         29 foreach my $var (@vars) {
84 79         230 $new_env{ $var->{'name'} } = $var->{'value'};
85             }
86 11         145 foreach my $var_name ( sort keys %ENV ) {
87 172         452 $new_env{$var_name} = $ENV{$var_name};
88             }
89              
90             # We need to replace the current %ENV, not change individual values.
91             ## no critic [Variables::RequireLocalizedPunctuationVars]
92 11         701 %ENV = %new_env;
93 11         2984 return \%ENV;
94             }
95              
96             1;
97              
98             __END__
99              
100             =pod
101              
102             =encoding UTF-8
103              
104             =head1 NAME
105              
106             Env::Dot - Read environment variables from .env file
107              
108             =head1 VERSION
109              
110             version 0.020
111              
112             =head1 SYNOPSIS
113              
114             =head1 DESCRIPTION
115              
116             More flexibility in how you manage and use your F<.env> file.
117              
118             =head1 STATUS
119              
120             This module is currently being developed so changes in the API are possible,
121             though not likely.
122              
123             =for test_synopsis BEGIN { die 'SKIP: no .env file here' }
124              
125             # If your dotenv file is `.env`:
126             use Env::Dot;
127             # or
128             use Env::Dot 'read';
129              
130             print $ENV{'VAR_DEFINED_IN_DOTENV_FILE'};
131              
132             # If you have a dotenv file in a different filepath:
133             use Env::Dot read => {
134             dotenv_file => '/other/path/my_environment.env',
135             };
136              
137             # When you absolutely require `.env` file:
138             use Env::Dot read => {
139             required => 1,
140             };
141              
142             =for stopwords dotenv
143              
144             B<Attn. Existing environment variables always take precedence to dotenv variables!>
145             A dotenv variable (variable from a file) does not overwrite
146             an existing environment variable. This is by design because
147             a dotenv file is to augment the environment, not to replace it.
148              
149             This means that you can override a variable in `.env` file by creating
150             its counterpart in the environment. For instance:
151              
152             unset VAR
153             echo "VAR='Good value'" >> .env
154             perl -e 'use Env::Dot; print "VAR:$ENV{VAR}\n";'
155             # VAR:Good value
156             VAR='Better value'; export VAR
157             perl -e 'use Env::Dot; print "VAR:$ENV{VAR}\n";'
158             # VAR:Better value
159              
160             =head2 Features
161              
162             =over 8
163              
164             =item If no B<.env> file is present, then do nothing
165              
166             By default, Env::Dot will do nothing if there is no
167             B<.env> file.
168             You can also configure Env::Dot to
169             break execution, if there is no `.env` file
170             in the current working directory.
171              
172             use Env::Dot read => {
173             required => 1,
174             };
175              
176             =item Specify other dotenv files with path
177              
178             If your B<.env> file is located in another path,
179             not the current working directory,
180             you can use the environment variable
181             B<ENVDOT_FILEPATHS> to tell where your dotenv file is located.
182             You can specify several file paths; just separate
183             them by B<:>. Env::Dot will load the files in the B<reverse order>,
184             starting from the last. This is the same ordering as used in B<PATH> variable:
185             the first overrules the following ones, that is, when reading from the last path
186             to the first path, if same variable is present in more than one file, the later
187             one replaces the one already read.
188              
189             Attn. If you are using Windows, separate the paths by B<;>!
190              
191             For example, if you have the following directory structure:
192              
193             project-root
194             | .env
195             + - sub-project
196             | .env
197              
198             and you specify B<ENVDOT_FILEPATHS=project-root/sub-project/.env:project-root/.env>,
199             then the variables in file B<project-root/.env> will get replaced
200             by the more specific variables in B<project-root/sub-project/.env>.
201              
202             In Windows, this would be B<ENVDOT_FILEPATHS=project-root\sub-project\.env;project-root\.env>
203              
204             N.B. The ordering has changed in version 0.0.9.
205              
206             =item Support different types of .env files
207              
208             =for stopwords dotenv
209              
210             Unix Shell I<source> command compatible dotenv files use double or single quotation marks
211             (B<"> or B<'>) to define a variable which has spaces. But, for instance,
212             Docker compatible F<.env> files do not use quotation marks. The variable's
213             value begins with B<=> sign and ends with linefeed.
214              
215             =for stopwords dotenv
216              
217             You can specify in the dotenv file itself - by using meta commands -
218             which type of file it is.
219              
220             =for stopwords envdot
221              
222             =item Use executable B<envdot> to bring the variables into your shell
223              
224             The executable is distributed together with Env::Dot package.
225             It is in the directory I<script>.
226              
227             =for stopwords envdot
228              
229             The executable I<script/envdot> is not Windows compatible!
230              
231             =for stopwords Powershell envdot
232              
233             A Windows (MS Command and Powershell compatible) version, I<script\envdot.bat>, is possible
234             in a future release. Please contact the author if you are interested in it.
235              
236             eval "$(envdot)"
237              
238             N.B. If your B<.env> file(s) contain variables which need interpolating,
239             for example, to combine their value from other variables or execute a command
240             to produce their value, you have to use the B<envdot> program.
241             B<Env::Dot> does not do any interpolating. It cannot because that would involve
242             running the variable in the shell context.
243              
244             =back
245              
246             =for stopwords DotEnv Powershell dotenv env envdot shdotenv
247              
248             =head2 DotEnv File Meta Commands
249              
250             The B<var:> commands affect only the subsequent variable definition.
251             If there is another B<envdot> command, the second overwrites the first
252             and default values are applied again.
253              
254             =over 8
255              
256             =item read:from_parent
257              
258             By setting this option to B<true>, B<Env::Dot> or B<envdot> command
259             will search for F<.env> files in the file system tree upwards.
260             It will load the first F<.env> file it finds from
261             the current directory upwards to root.
262              
263             Using B<read:from_parent> will only find and read
264             one B<.env> file in a parent directory.
265             If you want to chain the B<.env> files,
266             they all must set B<read:from_parent> - except the top one.
267              
268             This functionality can be useful in situations where you have
269             parallel projects which share common environment variables
270             in one F<.env> file in a parent directory.
271              
272             If there is no parent F<.env> file, Env::Dot will break execution
273             and give an error.
274              
275             By default this setting is off.
276              
277             =item read:allow_missing_parent
278              
279             When using option B<read:from_parent>, if the parent F<.env> file does not exist,
280             by default Env::Dot will emit an error and break execution.
281             In some situations, it might be normal that a parent F<.env> file
282             could be missing. Turn on option B<read:allow_missing_parent> if you
283             do not want an error in that case.
284              
285             By default this setting is off.
286              
287             =item file:type
288              
289             Changes how B<Env::Dot> reads lines below from this commands. Default is:
290              
291             # envdot (file:type=shell)
292             VAR="value"
293              
294             Other possible value of B<file:type> is:
295              
296             # envdot (file:type=plain)
297             VAR=My var value
298              
299             =item var:allow_interpolate
300              
301             By default, when writing variable definitions for the shell,
302             every variable is treated as static and surrounded with
303             single quotation marks B<'> in Unix shell which means
304             shell will read the variable content as is.
305             By setting this to B<1> or B<true>, you allow shell
306             to interpolate.
307             This meta command is only useful when running B<envdot> command
308             to create variable definitions for B<eval> command to read.
309              
310             # envdot (var:allow_interpolate)
311             DYNAMIC_VAR="$(pwd)/${ANOTHER_VAR}"
312              
313             =back
314              
315             =head1 DEPENDENCIES
316              
317             No external dependencies outside Perl's standard distribution.
318              
319             =head1 FUNCTIONS
320              
321             No functions exported to the calling namespace.
322              
323             =head2 load_vars
324              
325             Load variables from F<.env> file or files in environment variable
326             B<ENVDOT_FILEPATHS>.
327              
328             =head1 SEE ALSO
329              
330             L<Env::Assert> will verify that you certainly have those environmental
331             variables you need. It also has an executable which can, for example,
332             perform the check in the beginning of a B<docker> container run.
333              
334             L<Dotenv> and L<ENV::Util|https://metacpan.org/pod/ENV::Util>
335             are packages which also implement functionality to use
336             F<.env> files in Perl.
337              
338             L<Config::ENV> and L<Config::Layered::Source::ENV> provide other means
339             to configure application with the help of environment variables.
340              
341             =for stopwords dotenv shdotenv
342              
343             L<shdotenv|https://github.com/ko1nksm/shdotenv> is a project to provide dotenv
344             for shells with support for POSIX-compliant and multiple .env file syntax.
345              
346             =head1 AUTHOR
347              
348             Mikko Koivunalho <mikkoi@cpan.org>
349              
350             =head1 COPYRIGHT AND LICENSE
351              
352             This software is copyright (c) 2023 by Mikko Koivunalho.
353              
354             This is free software; you can redistribute it and/or modify it under
355             the same terms as the Perl 5 programming language system itself.
356              
357             =cut