Skip to content
This repository has been archived by the owner on Jun 24, 2024. It is now read-only.

Commit

Permalink
Feeding dotenv via stdin: use "-" as the data file
Browse files Browse the repository at this point in the history
  • Loading branch information
kolypto committed Jul 19, 2019
1 parent 8b89532 commit 26a67e9
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 23 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## 0.3.11 (2019-08-18)
## 0.3.12 (2019-08-18)
* Fix: use `env` format from stdin

## 0.3.10 (2019-06-07)
Expand Down
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,17 @@ Or even read environment variables from a file:

$ j2 --format=env config.j2 data.env

Or pipe it: (note that you'll have to use the "-" in this particular case):

$ j2 --format=env config.j2 - < data.env


# Reference
`j2` accepts the following arguments:

* `template`: Jinja2 template file to render
* `data`: (optional) path to the data used for rendering. The default is `-`: use stdin
* `data`: (optional) path to the data used for rendering.
The default is `-`: use stdin. Specify it explicitly when using env!

Options:

Expand Down Expand Up @@ -166,7 +171,7 @@ Render directly from the current environment variable values:

$ j2 config.j2

Or alternatively, read the values from a file:
Or alternatively, read the values from a dotenv file:

```
NGINX_HOSTNAME=localhost
Expand All @@ -179,7 +184,9 @@ And render with:
$ j2 config.j2 data.env
$ env | j2 --format=env config.j2

This is especially useful with Docker to link containers together.
If you're going to pipe a dotenv file into `j2`, you'll need to use "-" as the second argument to explicitly:

$ j2 config.j2 - < data.env

### ini
INI data input format.
Expand Down
62 changes: 50 additions & 12 deletions j2cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,12 @@ def render_command(cwd, environ, stdin, argv):
parser.add_argument('--undefined', action='store_true', dest='undefined', help='Allow undefined variables to be used in templates (no error will be raised)')
parser.add_argument('-o', metavar='outfile', dest='output_file', help="Output to a file instead of stdout")
parser.add_argument('template', help='Template file to process')
parser.add_argument('data', nargs='?', default='-', help='Input data path')
parser.add_argument('data', nargs='?', default=None, help='Input data file path; "-" to use stdin')
args = parser.parse_args(argv)

# Input: guess format
if args.format == '?':
if args.data == '-':
if args.data is None or args.data == '-':
args.format = 'env'
else:
args.format = {
Expand All @@ -138,10 +138,27 @@ def render_command(cwd, environ, stdin, argv):
}[os.path.splitext(args.data)[1]]

# Input: data
if args.data == '-' and args.format == 'env' and (stdin is None or stdin.isatty()):
input_data_f = None
# We always expect a file;
# unless the user wants 'env', and there's no input file provided.
if args.format == 'env':
# With the "env" format, if no dotenv filename is provided, we have two options:
# either the user wants to use the current environment, or he's feeding a dotenv file at stdin.
# Depending on whether we have data at stdin, we'll need to choose between the two.
#
# The problem is that in Linux, you can't reliably determine whether there is any data at stdin:
# some environments would open the descriptor even though they're not going to feed any data in.
# That's why many applications would ask you to explicitly specify a '-' when stdin should be used.
#
# And this is what we're going to do here as well.
# The script, however, would give the user a hint that they should use '-'
if args.data == '-':
input_data_f = stdin
elif args.data == None:
input_data_f = None
else:
input_data_f = open(args.data)
else:
input_data_f = stdin if args.data == '-' else open(args.data)
input_data_f = stdin if args.data is None or args.data == '-' else open(args.data)

# Python 2: Encode environment variables as unicode
if sys.version_info[0] == 2 and args.format == 'env':
Expand Down Expand Up @@ -183,7 +200,25 @@ def render_command(cwd, environ, stdin, argv):
renderer.register_tests(customize.extra_tests())

# Render
result = renderer.render(args.template, context)
try:
result = renderer.render(args.template, context)
except jinja2.exceptions.UndefinedError as e:
# When there's data at stdin, tell the user they should use '-'
try:
stdin_has_data = stdin is not None and not stdin.isatty()
if args.format == 'env' and args.data == None and stdin_has_data:
extra_info = (
"\n\n"
"If you're trying to pipe a .env file, please run me with a '-' as the data file name:\n"
"$ {cmd} {argv} -".format(cmd=os.path.basename(sys.argv[0]), argv=' '.join(sys.argv[1:]))
)
e.args = (e.args[0] + extra_info,) + e.args[1:]
except:
# The above code is so optional that any, ANY, error, is ignored
pass

# Proceed
raise

# -o
if args.output_file:
Expand All @@ -199,11 +234,14 @@ def render_command(cwd, environ, stdin, argv):

def main():
""" CLI Entry point """
output = render_command(
os.getcwd(),
os.environ,
sys.stdin,
sys.argv[1:]
)
try:
output = render_command(
os.getcwd(),
os.environ,
sys.stdin,
sys.argv[1:]
)
except SystemExit:
return 1
outstream = getattr(sys.stdout, 'buffer', sys.stdout)
outstream.write(output)
6 changes: 4 additions & 2 deletions j2cli/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def _parse_env(data_string):
$ j2 config.j2
Or alternatively, read the values from a file:
Or alternatively, read the values from a dotenv file:
```
NGINX_HOSTNAME=localhost
Expand All @@ -115,7 +115,9 @@ def _parse_env(data_string):
$ j2 config.j2 data.env
$ env | j2 --format=env config.j2
This is especially useful with Docker to link containers together.
If you're going to pipe a dotenv file into `j2`, you'll need to use "-" as the second argument to explicitly:
$ j2 config.j2 - < data.env
"""
# Parse
if isinstance(data_string, basestring):
Expand Down
7 changes: 6 additions & 1 deletion misc/_doc/README.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,17 @@ Or even read environment variables from a file:

$ j2 --format=env config.j2 data.env

Or pipe it: (note that you'll have to use the "-" in this particular case):

$ j2 --format=env config.j2 - < data.env


# Reference
`j2` accepts the following arguments:

* `template`: Jinja2 template file to render
* `data`: (optional) path to the data used for rendering. The default is `-`: use stdin
* `data`: (optional) path to the data used for rendering.
The default is `-`: use stdin. Specify it explicitly when using env!

Options:

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

setup(
name='j2cli',
version='0.3.11',
version='0.3.12b',
author='Mark Vartanyan',
author_email='[email protected]',

Expand Down
13 changes: 10 additions & 3 deletions tests/render-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def test_ini(self):
self._testme_std(['--format=ini', 'resources/nginx.j2', 'resources/data.ini'])
# Stdin
self._testme_std(['--format=ini', 'resources/nginx.j2'], stdin=open('resources/data.ini'))
self._testme_std(['--format=ini', 'resources/nginx.j2', '-'], stdin=open('resources/data.ini'))

def test_json(self):
# Filename
Expand All @@ -78,6 +79,7 @@ def test_json(self):
self._testme_std(['--format=json', 'resources/nginx.j2', 'resources/data.json'])
# Stdin
self._testme_std(['--format=json', 'resources/nginx.j2'], stdin=open('resources/data.json'))
self._testme_std(['--format=json', 'resources/nginx.j2', '-'], stdin=open('resources/data.json'))

def test_yaml(self):
try:
Expand All @@ -92,19 +94,24 @@ def test_yaml(self):
self._testme_std(['--format=yaml', 'resources/nginx.j2', 'resources/data.yml'])
# Stdin
self._testme_std(['--format=yaml', 'resources/nginx.j2'], stdin=open('resources/data.yml'))
self._testme_std(['--format=yaml', 'resources/nginx.j2', '-'], stdin=open('resources/data.yml'))

def test_env(self):
# Filename
self._testme_std(['resources/nginx-env.j2', 'resources/data.env'])
self._testme_std(['--format=env', 'resources/nginx-env.j2', 'resources/data.env'])
self._testme_std([ 'resources/nginx-env.j2', 'resources/data.env'])
# Format
self._testme_std(['--format=env', 'resources/nginx-env.j2', 'resources/data.env'])
self._testme_std([ 'resources/nginx-env.j2', 'resources/data.env'])
# Stdin
self._testme_std(['--format=env', 'resources/nginx-env.j2'], stdin=open('resources/data.env'))
self._testme_std(['--format=env', 'resources/nginx-env.j2', '-'], stdin=open('resources/data.env'))
self._testme_std([ 'resources/nginx-env.j2', '-'], stdin=open('resources/data.env'))

# Environment!
# In this case, it's not explicitly provided, but implicitly gotten from the environment
env = dict(NGINX_HOSTNAME='localhost', NGINX_WEBROOT='/var/www/project', NGINX_LOGS='/var/log/nginx/')
self._testme_std(['--format=env', 'resources/nginx-env.j2'], env=env)
self._testme_std(['--format=env', 'resources/nginx-env.j2'], env=env)
self._testme_std([ 'resources/nginx-env.j2'], env=env)

def test_import_env(self):
# Import environment into a variable
Expand Down
Empty file added tests/resources/data-empty.env
Empty file.

0 comments on commit 26a67e9

Please sign in to comment.