File Coverage

bin/envdot
Criterion Covered Total %
statement 72 73 98.6
branch 16 26 61.5
condition 1 2 50.0
subroutine 13 13 100.0
pod n/a
total 102 114 89.4


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             ## no critic (ControlStructures::ProhibitPostfixControls)
3             ## no critic (ValuesAndExpressions::ProhibitConstantPragma)
4 8     8   50694 use strict;
  8         17  
  8         425  
5 8     8   35 use warnings;
  8         18  
  8         654  
6 8     8   188 use 5.010;
  8         28  
7 8     8   4302 use open ':std', IO => ':encoding(UTF-8)';
  8         12044  
  8         52  
8              
9             # ABSTRACT: Read .env file and turn its content into environment variables for different shells.
10              
11             # PODNAME: envdot
12              
13 8         914491 our $VERSION = '0.020';
14              
15 8     8   214047 use English qw( -no_match_vars ); # Avoids regex performance penalty in perl 5.18 and earlier
  8         28272  
  8         48  
16 8     8   9421 use Getopt::Long qw( :config auto_version auto_help );
  8         149028  
  8         56  
17 8     8   2063 use Carp;
  8         15  
  8         638  
18 8     8   88 use Errno;
  8         16  
  8         341  
19 8     8   4446 use Pod::Usage;
  8         475580  
  8         1145  
20              
21 8     8   4896 use Env::Dot::Functions qw(:all);
  8         39  
  8         1399  
22 8     8   4152 use Env::Dot::ScriptFunctions qw( convert_variables_into_commands );
  8         25  
  8         1026  
23              
24 8         69 local $OUTPUT_AUTOFLUSH = 1;
25              
26             use constant {
27 8 50       687228 DEFAULT_OPTION_DOTENV_FILENAME => '.env',
    50          
28             DEFAULT_OPTION_SHELL => q{sh},
29             DEFAULT_OPTION_READ_FROM_STDIN => 0,
30             EXIT_SUCCESS => 0,
31             EXIT_ERROR_NO_FILE => ( exists &Errno::ENOENT ? Errno::ENOENT : 255 ),
32             EXIT_ERROR_OTHER_ERROR => ( exists &Errno::EINVAL ? Errno::EINVAL : 255 ),
33 8     8   54 };
  8         10  
34              
35 8         104 my %SHELL_ALTERNATIVES = (
36             sh => 'sh',
37             bash => 'sh',
38             dash => 'sh',
39             ksh => 'sh',
40             csh => 'csh',
41             tcsh => 'csh',
42             fish => 'fish',
43             );
44              
45 8         17 my $man = 0;
46 8         16 my $export = 1;
47 8   50     1026 my $shell = $SHELL_ALTERNATIVES{ $ENV{SHELL} } // DEFAULT_OPTION_SHELL;
48 8         40 my $dotenv_filepath = DEFAULT_OPTION_DOTENV_FILENAME;
49 8         18 my $read_from_stdin = DEFAULT_OPTION_READ_FROM_STDIN;
50 8 50       94 GetOptions(
51             'man' => \$man,
52             'export!' => \$export,
53             'shell|s=s' => \$shell,
54             'dotenv|e=s' => \$dotenv_filepath,
55             '' => \$read_from_stdin, ## no critic (ValuesAndExpressions::ProhibitEmptyQuotes)
56             ) or pod2usage(2);
57 6 50       6455 pod2usage( -exitval => 0, -verbose => 2 ) if $man;
58              
59             sub main {
60 6     6   40 my $var_name = get_envdot_filepaths_var_name();
61 6         14 my @dotenv_filepaths;
62 6 100       111 if ( exists $ENV{$var_name} ) {
    50          
63 2         11 @dotenv_filepaths = interpret_dotenv_filepath_var( $ENV{$var_name} );
64             }
65             elsif ($read_from_stdin) {
66 0         0 croak 'Error: Option not implemented';
67             }
68             else {
69 4 100       171 if ( !-f $dotenv_filepath ) {
70 1 50       2 print {*STDERR} "Error: File not found: '$dotenv_filepath'\n"
  1         8  
71             or croak 'Cannot print error message';
72 1         0 return EXIT_ERROR_NO_FILE;
73             }
74 3         11 @dotenv_filepaths = ($dotenv_filepath); # The CLI parameter
75             }
76              
77 5         30 my @vars;
78 5         13 foreach my $dotenv_filepath ( reverse @dotenv_filepaths ) {
79 5         61 local $EVAL_ERROR = undef;
80 5         9 my @these_vars;
81 5 100       12 eval { @these_vars = get_dotenv_vars($dotenv_filepath); 1; } or do {
  5         27  
  4         18  
82 1         4 my $e = $EVAL_ERROR;
83 1         4 my ( $err, $l, $fp ) = extract_error_msg($e);
84 1 50       2 print {*STDERR} 'Error: ' . $err . ( $l ? qq{ line $l} : q{} ) . ( $fp ? qq{ file '$fp'} : q{} ) . "\n"
  1 50       14  
    50          
85             or croak 'Cannot print error message';
86 1         0 return EXIT_ERROR_OTHER_ERROR;
87             };
88 4         12 push @vars, @these_vars;
89             }
90 4         22 $_->{'opts'}->{'export'} = $export foreach (@vars);
91              
92 4 50       8 print {*STDOUT} convert_variables_into_commands( $shell, @vars )
  4         33  
93             or croak 'Cannot print variables to STDOUT';
94              
95 4           return EXIT_SUCCESS;
96             }
97              
98 6         31 exit main(@ARGV);
99              
100             __END__
101              
102             =pod
103              
104             =encoding UTF-8
105              
106             =head1 NAME
107              
108             envdot - Read .env file and turn its content into environment variables for different shells.
109              
110             =head1 VERSION
111              
112             version 0.020
113              
114             =head1 SYNOPSIS
115              
116             envdot [options]
117              
118             eval `envdot`
119              
120             Options:
121             --help
122             --man
123             --version
124             --export --no-export
125             --shell -s
126             --dotenv -e
127              
128             =head2 CLI interface without dependencies
129              
130             The F<envdot> command is also available
131             as a self contained executable.
132             You can download it and run it as it is without
133             additional installation of CPAN packages.
134             Of course, you still need Perl, but Perl comes with any
135             normal Linux installation.
136              
137             This can be convenient if you want to, for instance,
138             include F<envdot> in a docker container build.
139              
140             curl -LSs -o envdot https://raw.githubusercontent.com/mikkoi/env-dot/main/envdot.self-contained
141             chmod +x ./envdot
142              
143             =head1 DESCRIPTION
144              
145             B<envdot> reads your F<.env> file and converts it
146             into environment variable commands suitable for
147             different shells (shell families): B<sh>, B<csh> and B<fish>.
148              
149             F<.env> files can be written in different flavors.
150             B<envdot> supports the often used B<sh> compatible flavor and
151             the B<docker> flavor which are not compatible with each other.
152              
153             If you have several F<.env> files, you can read them in at one go
154             with the help of the environment variable B<ENVDOT_FILEPATHS>.
155             Separate the full paths with 'B<:>' character.
156              
157             Env::Dot will load the files in the B<reverse order>,
158             starting from the last. This is the same ordering as used in B<PATH> variable:
159             the first overrules the following ones, that is, when reading from the last path
160             to the first path, if same variable is present in more than one file, the later
161             one replaces the one already read.
162              
163             If you have set the variable ENVDOT_FILEPATHS, then B<envdot> will use that.
164             Otherwise, it uses the command line parameter.
165             If no parameter, then default value is used. Default is the file
166             F<.env> in the current directory.
167              
168             =for stopwords dotenv env envdot shdotenv subshells
169              
170             =head1 NAME
171              
172             envdot - Read .env file and turn its content into environment variables for different shells.
173              
174             =head1 OPTIONS
175              
176             =over 8
177              
178             =item B<--help>
179              
180             Print a brief help message and exits.
181              
182             =item B<--man>
183              
184             Prints the manual page and exits.
185              
186             =item B<--version>
187              
188             Prints the version and exits.
189              
190             =item B<--export>, B<--no-export>
191              
192             Write commands to set variables for local shell or for exporting them.
193             You usually want to export the variables
194             to all subsequent programs and subshells, i.e.
195             make them into I<environment variables>.
196              
197             Default: export
198              
199             =item B<-s>, B<--shell>
200              
201             Which shell (family) are you using? Supported: sh, csh, fish.
202              
203             Default: sh
204              
205             =item B<-e>, B<--dotenv>
206              
207             Path to F<.env> file.
208              
209             Default: current directory F<.env>
210              
211             =back
212              
213             =head1 EXAMPLES
214              
215             eval `envdot --no-export --shell csh`
216              
217             eval `envdot --dotenv subdir/.env`
218              
219             ENVDOT_FILEPATHS='../.env:subdir/.env:.env' eval `envdot`
220              
221             =head1 DEPENDENCIES
222              
223             No external dependencies outside Perl's standard distribution.
224              
225             =head1 SEE ALSO
226              
227             L<Env::Assert> will verify that you certainly have those environmental
228             variables you need. It also has an executable which can, for example,
229             perform the check in the beginning of a B<docker> container run.
230              
231             L<Dotenv> and L<ENV::Util|https://metacpan.org/pod/ENV::Util>
232             are packages which also implement functionality to use
233             F<.env> files in Perl.
234              
235             L<Config::ENV> and L<Config::Layered::Source::ENV> provide other means
236             to configure application with the help of environment variables.
237              
238             L<shdotenv|https://github.com/ko1nksm/shdotenv> is a project to provide dotenv
239             for shells with support for POSIX-compliant and multiple .env file syntax.
240              
241             =head1 AUTHOR
242              
243             Mikko Koivunalho <mikkoi@cpan.org>
244              
245             =head1 COPYRIGHT AND LICENSE
246              
247             This software is copyright (c) 2023 by Mikko Koivunalho.
248              
249             This is free software; you can redistribute it and/or modify it under
250             the same terms as the Perl 5 programming language system itself.
251              
252             =cut