Feeding dotenv via stdin: use "-" as the data file

This commit is contained in:
Mark Vartanyan 2019-07-20 02:23:08 +03:00
parent 8b89532b40
commit 26a67e9419
8 changed files with 82 additions and 23 deletions

View File

@ -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)

View File

@ -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:
@ -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
@ -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.

View File

@ -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 = {
@ -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':
@ -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:
@ -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)

View File

@ -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
@ -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):

View File

@ -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:

View File

@ -26,7 +26,7 @@ if sys.version_info[:2] == (2, 6):
setup(
name='j2cli',
version='0.3.11',
version='0.3.12b',
author='Mark Vartanyan',
author_email='kolypto@gmail.com',

View File

@ -70,6 +70,7 @@ class RenderTest(unittest.TestCase):
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
@ -78,6 +79,7 @@ class RenderTest(unittest.TestCase):
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:
@ -92,19 +94,24 @@ class RenderTest(unittest.TestCase):
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

View File