inji.cli   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 173
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 20
eloc 104
dl 0
loc 173
ccs 70
cts 70
cp 1
rs 10
c 0
b 0
f 0

6 Functions

Rating   Name   Duplication   Size   Complexity  
A _version() 0 2 1
A pkg_location() 0 2 1
A cli_location() 0 2 1
A sigint_handler() 0 4 1
B cli_args() 0 55 5
C main() 0 72 11
1
#!/usr/bin/env python3
2
3
# -*- coding: utf-8 -*-
4
5
# NAME
6
7
# inji - Render jina2 templates to stdout
8
9 1
import ast
10 1
import argparse
11 1
import atexit
12 1
import fnmatch
13 1
import json
14 1
import locale
15 1
import os
16 1
from   os.path import abspath, dirname, join
17 1
import pkg_resources
18 1
import re
19 1
import shutil
20 1
import signal
21 1
import sys
22 1
import tempfile
23
24 1
from .engine import TemplateEngine
25 1
from . import utils
26
27 1
def pkg_location():
28 1
  return abspath(dirname(__file__))
29
30 1
def cli_location():
31 1
  return abspath(join(pkg_location(), '../bin/inji'))
32
33 1
def _version():
34 1
  return pkg_resources.require(__package__)[0].version
35
36 1
__version__ = _version()
37
38 1
def cli_args():
39 1
  parser = argparse.ArgumentParser(
40
      description='inji - render jinja templates'
41
    )
42 1
  required = parser.add_argument_group('required arguments')
43
44 1
  parser.add_argument('-j', '--json-config', '-c',
45
    action = 'store', required=False,
46
    type=lambda x: utils.json_parse(x),
47
    dest='json_string',
48
    help="-c '{ \"foo\": \"bar\", \"fred\": \"wilma\" }'"
49
  )
50
51 1
  parser.add_argument('-k', '--kv-config', '-d',
52
    action = 'append', required=False,
53
    type=lambda x: utils.kv_parse(x),
54
    dest='kv_pair',
55
    help='-d foo=bar -d fred=wilma'
56
  )
57
58 1
  parser.add_argument('-o', '--overlay-dir',
59
    action = 'append', required=False, type=lambda p, t='dir': utils.path(p, t),
60
    dest='overlay_dir', default=[],
61
    help='/path/to/overlay/'
62
  )
63
64 1
  parser.add_argument('-v', '-p', '--vars-file', '--vars',
65
    action = 'append', required=False, type=lambda p, t='file': utils.path(p, t),
66
    dest='vars_file', default=[],
67
    help='/path/to/vars.yaml'
68
  )
69
70 1
  parser.add_argument('--strict-mode', '-s',
71
    action = 'store', required=False, type=str,
72
    dest='undefined_variables_mode', default='strict',
73
    choices=[ 'strict', 'empty', 'keep',
74
              'StrictUndefined', 'Undefined', 'DebugUndefined' ],
75
    help='Refer to http://jinja.pocoo.org/docs/2.10/api/#undefined-types'
76
  )
77
78 1
  parser.add_argument('--version',
79
    action = 'version',
80
    version=_version(),
81
    help='print version number ({})'.format(_version())
82
  )
83
84 1
  parser.add_argument('template',
85
    nargs='*',
86
    action = 'store',
87
    type=utils.file_or_stdin,
88
    default='-',
89
    help='/path/to/template.j2 (defaults to -)'
90
  )
91
92 1
  return parser.parse_args()
93
94
95
def sigint_handler(signum, frame):  # pragma: no cover # despite being covered
96
  """ Handle SIGINT, ctrl-c gracefully """
97
  signal.signal(signum, signal.SIG_IGN) # ignore subsequent ctrl-c's
98
  sys.exit( 128 + signal.SIGINT )       # 130 by convention
99
100
101 1
def main():
102
  """ Our main method """
103
104
  # cleanly handle ctrl-c's
105 1
  signal.signal(signal.SIGINT, sigint_handler)
106
107 1
  assert sys.version_info >= (3,5), 'Python version ({}.{} !>= 3.5)'.format(
108
    sys.version_info.major,
109
    sys.version_info.minor
110
  )
111
112 1
  args = cli_args()
113
114
  # this holds all the possible vars files we are told about or imply
115 1
  vars_files = []
116
117
  # context in the local configuration files - p5
118 1
  vars_files += fnmatch.filter(os.listdir('.'), "*inji.y*ml")
119
120
  # context in the overlay directories - p4
121 1
  for d in args.overlay_dir:
122 1
    files = list(utils.recursive_iglob(d, '*.y*ml'))
123 1
    if len(files):
124 1
      loc = locale.getlocale()
125 1
      locale.setlocale(locale.LC_ALL, 'C') # Using LC_ALL=C POSIX locale
126 1
      files.sort()                         # We force the sort collation of files
127 1
      locale.setlocale(locale.LC_ALL, loc) # And then reset it back
128 1
      vars_files += files
129
130
  # context from named vars files - p3
131 1
  vars_files += args.vars_file
132
133
  # This will hold the final vars dict merged from various available sources
134 1
  context = {}
135 1
  for file in vars_files:
136 1
    context.update(utils.read_context(file))
137
138
  # context from environment variables - p2
139 1
  context.update(os.environ)
140
141
  # context at the command line (either JSON or KV type) - p1
142 1
  if args.json_string:
143 1
    context.update(args.json_string)
144
145 1
  if args.kv_pair:
146
    # we've appended dicts into args.kv_pair .. unpack them in order
147 1
    for d in args.kv_pair:
148 1
      context.update(d)
149
150 1
  if '-' in args.template:
151
    # Template passed in via stdin. Create template as a tempfile and use it
152
    # instead but since includes are possible (though not likely), we have to do
153
    # this in an isolated tmpdir container to prevent inadvertent reading of
154
    # includes not meant to be read.
155 1
    tmpdir = tempfile.mkdtemp(prefix=__name__)
156 1
    atexit.register(shutil.rmtree, tmpdir)
157
158 1
    _, tmpfile = tempfile.mkstemp(prefix='stdin-', dir=tmpdir, text=True)
159 1
    atexit.register(os.remove, tmpfile)
160
161 1
    with open(tmpfile, "a+") as f:
162 1
      f.write(sys.stdin.read())
163
164
    # Yes, even if user specifies multiple other templates, the fact he
165
    # specified '-' just once means we only deal with one template i.e. '-'
166 1
    args.template = [tmpfile]
167
168 1
  engine = TemplateEngine( undefined_variables_mode_behaviour=args.undefined_variables_mode )
169
170 1
  for template in args.template:
171 1
    for block in engine.render( template=template, context=context ):
172
      print(block)
173