1 |
commit: 0cea0f9464dabaa8ad1ba0b17dd25be1ee19a58a |
2 |
Author: André Erdmann <dywi <AT> mailerd <DOT> de> |
3 |
AuthorDate: Sun Jan 26 18:58:26 2014 +0000 |
4 |
Commit: André Erdmann <dywi <AT> mailerd <DOT> de> |
5 |
CommitDate: Sun Jan 26 18:58:26 2014 +0000 |
6 |
URL: http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=0cea0f94 |
7 |
|
8 |
helper for creating/editing config files |
9 |
|
10 |
./bin/py/query_config (currently accessible via ./bin/query_config in the |
11 |
git repo, and not available if roverlay is installed) is a script for |
12 |
(a) accessing roverlay's config in shell-usable format: |
13 |
eval $(query_config OVERLAY_DIR OVERLAY_NAME) |
14 |
|
15 |
(b) replacing variables in file templates (@@VARNAME@@): |
16 |
|
17 |
query_config --config-file ~user/roverlay/R-overlay.conf \ |
18 |
--from-file <template file> -O <outfile> |
19 |
|
20 |
This can be used to create config file templates without having to worry |
21 |
about the actual file paths / server address / etc., for example (nginx, |
22 |
server{} block): |
23 |
|
24 |
server { |
25 |
listen @@NGINX_SERVER_ADDR@@; |
26 |
server_name @@NGINX_SERVER_NAME@@; |
27 |
|
28 |
... |
29 |
|
30 |
# package mirror dir |
31 |
root @@OVERLAY_DISTDIR_ROOT@; |
32 |
|
33 |
... |
34 |
} |
35 |
|
36 |
(NGINX_SERVER_ADDR and NGINX_SERVER_NAME would have to be given with |
37 |
the -v/--variable switch when calling query_config). |
38 |
|
39 |
The exit code indicates whether all variables could be replaced or not. |
40 |
|
41 |
TODO: |
42 |
* make config option aliases available for (b) (e.g. DISTDIR) |
43 |
* doc |
44 |
* script name |
45 |
* config templates |
46 |
|
47 |
--- |
48 |
bin/py/query_config.py | 352 +++++++++++++++++++++++++++++++++++++++++++++++++ |
49 |
bin/query_config | 1 + |
50 |
2 files changed, 353 insertions(+) |
51 |
|
52 |
diff --git a/bin/py/query_config.py b/bin/py/query_config.py |
53 |
new file mode 100644 |
54 |
index 0000000..bd13674 |
55 |
--- /dev/null |
56 |
+++ b/bin/py/query_config.py |
57 |
@@ -0,0 +1,352 @@ |
58 |
+#!/usr/bin/python |
59 |
+# -*- coding: utf-8 -*- |
60 |
+# |
61 |
+# *** TODO: script name *** |
62 |
+# |
63 |
+# Script for querying roverlay's config / editing template files. |
64 |
+# |
65 |
+# Usage: |
66 |
+# * query-config -h |
67 |
+# |
68 |
+# * query-config -l |
69 |
+# |
70 |
+# Lists all known config options. |
71 |
+# (Note: it's not possible to query all of these options) |
72 |
+# |
73 |
+# * query-config [-C <config_file>] [-u] [-a|{option[=varname]}] |
74 |
+# |
75 |
+# Prints roverlay's config options in shell usable format (without relying |
76 |
+# on roverlay-sh). Prints all options if -a/--all is specified or no |
77 |
+# option[=varname] is given. options can be renamed with "=varname". |
78 |
+# |
79 |
+# Usage example: |
80 |
+# |
81 |
+# $ eval $(query-config -C R-overlay.conf.tmp OVERLAY_DIR=OVERLAY OVERLAY_NAME) |
82 |
+# $ echo $OVERLAY |
83 |
+# $ echo $OVERLAY_NAME |
84 |
+# |
85 |
+# * query-config [-C <config_file>] [-u] -f <infile> [-O <outfile>|-] {-v VAR[=VALUE]} |
86 |
+# |
87 |
+# Replaces occurences of @@VARIABLES@@ in <infile> with values taken |
88 |
+# from roverlay's config and writes the result to <outfile> or stdout. |
89 |
+# (variables may also be specified with -v VAR=VALUE, which take precedence |
90 |
+# over roverlay's config, e.g. "-v SERVER_NAME=roverlay"). |
91 |
+# |
92 |
+# Usage example: |
93 |
+# |
94 |
+# $ query-config -C ~roverlay_user/roverlay/R-overlay.conf \ |
95 |
+# -f nginx.conf.in -O nginx.conf -v SERVER_ADDR=... -v SERVER_NAME=... |
96 |
+# |
97 |
+# A non-zero exit code indicates that one or more variables could not be |
98 |
+# replaced. |
99 |
+# |
100 |
+# |
101 |
+from __future__ import print_function, unicode_literals |
102 |
+ |
103 |
+import argparse |
104 |
+import logging |
105 |
+import re |
106 |
+import os |
107 |
+import sys |
108 |
+ |
109 |
+import roverlay.core |
110 |
+import roverlay.strutil |
111 |
+from roverlay.config.entryutil import iter_config_keys |
112 |
+ |
113 |
+EX_OK = os.EX_OK |
114 |
+EX_ERR = os.EX_OK^1 |
115 |
+EX_MISS = os.EX_OK^2 |
116 |
+EX_IRUPT = os.EX_OK^130 |
117 |
+ |
118 |
+RE_VAR_REF = re.compile ( "@@([a-zA-Z_]+)@@" ) |
119 |
+RE_VARNAME = re.compile ( "^[a-zA-Z_]+$" ) |
120 |
+ |
121 |
+ |
122 |
+class VarnameArgumentError ( argparse.ArgumentTypeError ): |
123 |
+ def __init__ ( self, name ): |
124 |
+ super ( VarnameArgumentError, self ).__init__ ( |
125 |
+ "invalid variable name: {!r}".format ( name ) |
126 |
+ ) |
127 |
+# --- end of VarnameArgumentError --- |
128 |
+ |
129 |
+def get_value_str ( value, list_join_seq=" " ): |
130 |
+ if value is None: |
131 |
+ return "" |
132 |
+ elif hasattr ( value, '__iter__' ) and not isinstance ( value, str ): |
133 |
+ return list_join_seq.join ( map ( str, value ) ) |
134 |
+ elif isinstance ( value, bool ): |
135 |
+ return "1" if value else "0" |
136 |
+ else: |
137 |
+ return str ( value ) |
138 |
+# --- end of get_value_str (...) --- |
139 |
+ |
140 |
+def format_variables ( vardict, append_newline=True ): |
141 |
+ retstr = "\n".join ( |
142 |
+ "{varname!s}=\"{value!s}\"".format ( varname=k, value=v ) |
143 |
+ for k, v in sorted ( vardict.items(), key=lambda kv: kv[0] ) |
144 |
+ ) |
145 |
+ return ( retstr + "\n" ) if append_newline else retstr |
146 |
+# --- end of format_variables (...) --- |
147 |
+ |
148 |
+def get_parser(): |
149 |
+ def arg_couldbe_file ( value ): |
150 |
+ if value is None or value == '-': |
151 |
+ return value |
152 |
+ elif value: |
153 |
+ f = os.path.abspath ( value ) |
154 |
+ if not os.path.exists ( f ) or not os.path.isdir ( f ): |
155 |
+ return f |
156 |
+ raise argparse.ArgumentTypeError ( |
157 |
+ "{!r} cannot be a file.".format ( value ) |
158 |
+ ) |
159 |
+ # --- end of arg_couldbe_file (...) --- |
160 |
+ |
161 |
+ def arg_is_filepath_or_none ( value ): |
162 |
+ if value: |
163 |
+ f = os.path.abspath ( value ) |
164 |
+ if os.path.isfile ( f ): |
165 |
+ return f |
166 |
+ elif value is None: |
167 |
+ return value |
168 |
+ raise argparse.ArgumentTypeError ( |
169 |
+ "{!r} is not a file.".format ( value ) |
170 |
+ ) |
171 |
+ # --- end of arg_is_filepath_or_none (...) --- |
172 |
+ |
173 |
+ def arg_is_varname ( value ): |
174 |
+ if value: |
175 |
+ vname, sepa, valias = value.partition ( '=' ) |
176 |
+ if not RE_VARNAME.match ( vname ): |
177 |
+ raise VarnameArgumentError ( vname ) |
178 |
+ elif sepa: |
179 |
+ if not RE_VARNAME.match ( valias ): |
180 |
+ raise VarnameArgumentError ( valias ) |
181 |
+ else: |
182 |
+ return ( vname, valias ) |
183 |
+ else: |
184 |
+ return vname |
185 |
+ #return ( vname, vname ) |
186 |
+ else: |
187 |
+ return None |
188 |
+ # --- end of arg_is_varname (...) --- |
189 |
+ |
190 |
+ def arg_is_variable ( value ): |
191 |
+ if value: |
192 |
+ key, sepa, value = value.partition ( "=" ) |
193 |
+ if sepa: |
194 |
+ return ( key, roverlay.strutil.unquote ( value ) ) |
195 |
+ else: |
196 |
+ return ( key, "" ) |
197 |
+ raise argparse.ArgumentTypeError ( value ) |
198 |
+ # --- end of arg_is_variable (...) --- |
199 |
+ |
200 |
+ parser = argparse.ArgumentParser ( |
201 |
+ description = ( |
202 |
+ "query config options and output them in shell-usable format" |
203 |
+ ), |
204 |
+ epilog = ( |
205 |
+ 'Exit codes:\n' |
206 |
+ '* {EX_OK}: success\n' |
207 |
+ '* {EX_ERR}: unspecified error, e.g. invalid config entry map\n' |
208 |
+ '* {EX_MISS}: one or more config keys could not be found\n' |
209 |
+ ).format ( EX_OK=EX_OK, EX_MISS=EX_MISS, EX_ERR=EX_ERR ), |
210 |
+ formatter_class = argparse.RawDescriptionHelpFormatter |
211 |
+ ) |
212 |
+ |
213 |
+ parser.add_argument ( |
214 |
+ "config_keys", metavar="<config_key>", type=arg_is_varname, nargs="*", |
215 |
+ help="config key (or <config_key>=<alias_key>)" |
216 |
+ ) |
217 |
+ |
218 |
+ parser.add_argument ( |
219 |
+ "-C", "--config-file", metavar="<file>", default=None, |
220 |
+ type=arg_is_filepath_or_none, |
221 |
+ help="path to the config file", |
222 |
+ ) |
223 |
+ |
224 |
+ parser.add_argument ( |
225 |
+ "-a", "--all", dest="print_all", default=False, action="store_true", |
226 |
+ help="print all options" |
227 |
+ ) |
228 |
+ |
229 |
+ parser.add_argument ( |
230 |
+ "-l", "--list-all", dest="list_all", default=False, action="store_true", |
231 |
+ help="instead of printing options: list all keys" |
232 |
+ ) |
233 |
+ |
234 |
+ parser.add_argument ( |
235 |
+ "-u", "--empty-missing", default=False, action="store_true", |
236 |
+ help="set missing variables to the empty string" |
237 |
+ ) |
238 |
+ |
239 |
+ parser.add_argument ( |
240 |
+ "-f", "--from-file", metavar="<file>", default=None, |
241 |
+ type=arg_is_filepath_or_none, |
242 |
+ help="read config keys from <file>" |
243 |
+ ) |
244 |
+ |
245 |
+ parser.add_argument ( |
246 |
+ "-O", "--outfile", metavar="<file>", default=None, |
247 |
+ type=arg_couldbe_file, |
248 |
+ help=( |
249 |
+ 'in conjunction with --from-file: replace variable references and ' |
250 |
+ 'write the resulting text to <file>' |
251 |
+ ) |
252 |
+ ) |
253 |
+ |
254 |
+ parser.add_argument ( |
255 |
+ "-v", "--variable", metavar="<key=\"value\">", dest="extra_vars", |
256 |
+ default=[], action="append", type=arg_is_variable, |
257 |
+ help="additional variables (only with --outfile)" |
258 |
+ ) |
259 |
+ |
260 |
+ return parser |
261 |
+# --- end of get_parser (...) --- |
262 |
+ |
263 |
+def get_all_config_keys(): |
264 |
+ return [ k.upper() for k in iter_config_keys() ] |
265 |
+# --- end of get_all_config_keys (...) --- |
266 |
+ |
267 |
+def get_vardict ( config, argv, keys ): |
268 |
+ return config.query_by_name ( |
269 |
+ keys, empty_missing=argv.empty_missing, convert_value=get_value_str |
270 |
+ ) |
271 |
+# --- end of get_vardict (...) --- |
272 |
+ |
273 |
+def main__print_variables ( config, argv, stream, config_keys ): |
274 |
+ num_missing, cvars = get_vardict ( config, argv, config_keys ) |
275 |
+ if cvars: |
276 |
+ stream.write ( format_variables ( cvars ) ) |
277 |
+ return num_missing |
278 |
+# --- end of main__print_variables (...) --- |
279 |
+ |
280 |
+def main ( is_installed=False ): |
281 |
+ parser = get_parser() |
282 |
+ argv = parser.parse_args() |
283 |
+ stream = sys.stdout |
284 |
+ |
285 |
+ # setup |
286 |
+ ## logging |
287 |
+ roverlay.core.force_console_logging ( log_level=logging.WARNING ) |
288 |
+ |
289 |
+ ## main config |
290 |
+ if argv.config_file is None: |
291 |
+ config_file = roverlay.core.locate_config_file ( False ) |
292 |
+ else: |
293 |
+ config_file = argv.config_file |
294 |
+ |
295 |
+ # passing installed=True|False doesn't really matter |
296 |
+ config = roverlay.core.load_config_file ( |
297 |
+ config_file, extraconf={ 'installed': is_installed, }, |
298 |
+ setup_logger=False, load_main_only=True |
299 |
+ ) |
300 |
+ |
301 |
+ # perform actions as requested |
302 |
+ |
303 |
+ # --list-all: print all config keys and exit |
304 |
+ if argv.list_all: |
305 |
+ stream.write ( "\n".join ( sorted ( get_all_config_keys() ) ) + "\n" ) |
306 |
+ return EX_OK |
307 |
+ |
308 |
+ # --all or no config keys specified: print all config options as variables |
309 |
+ elif argv.print_all or not any (( argv.from_file, argv.config_keys, )): |
310 |
+ main__print_variables ( config, argv, stream, get_all_config_keys() ) |
311 |
+ # don't return EX_MISS if --all was specified |
312 |
+ return EX_OK |
313 |
+ |
314 |
+ # --from-file with --outfile: |
315 |
+ # replace @@VARIABLES@@ in file and write to --outfile (or stdout) |
316 |
+ elif argv.from_file and argv.outfile: |
317 |
+ # COULDFIX: exit code when --variable is used |
318 |
+ # |
319 |
+ # (a) get_vardict(): return a list of missing vars and compare it |
320 |
+ # to the final cvars |
321 |
+ # (b) check the resulting str for missing vars (RE_VAR_REF.search) |
322 |
+ # |
323 |
+ # Using (b) for now (and unconditionally, so that the output |
324 |
+ # always gets verified). |
325 |
+ # |
326 |
+ |
327 |
+ # list of 2-tuples ( line, set<varnames> ) |
328 |
+ input_lines = list() |
329 |
+ config_keys = set() |
330 |
+ with open ( argv.from_file, 'rt' ) as FH: |
331 |
+ for line in FH.readlines(): |
332 |
+ varnames = set ( RE_VAR_REF.findall ( line ) ) |
333 |
+ input_lines.append ( ( line, varnames ) ) |
334 |
+ config_keys |= varnames |
335 |
+ # -- end for |
336 |
+ # -- end with |
337 |
+ |
338 |
+ num_missing, cvars = get_vardict ( config, argv, config_keys ) |
339 |
+ del num_missing |
340 |
+ |
341 |
+ if argv.extra_vars: |
342 |
+ for k, v in argv.extra_vars: |
343 |
+ cvars[k] = v |
344 |
+ # -- end if extra vars |
345 |
+ |
346 |
+ # create a dict<varname => (regex for replacing varname,replacement)> |
347 |
+ # where (re_object,replacement) := (re<@@varname@@>,value) |
348 |
+ re_repl = { |
349 |
+ k : ( re.compile ( "@@" + k + "@@" ), v ) for k, v in cvars.items() |
350 |
+ } |
351 |
+ |
352 |
+ # iterate through input_lines a second time, replacing @@VARNAMES@@ |
353 |
+ # (COULDFIX: could be done in one loop // create cvars on-the-fly (defaultdict etc)) |
354 |
+ output_lines = [] |
355 |
+ vars_missing = set() |
356 |
+ for line, varnames in input_lines: |
357 |
+ # apply replace operations as needed |
358 |
+ for varname in varnames: |
359 |
+ try: |
360 |
+ re_obj, repl = re_repl [varname] |
361 |
+ except KeyError: |
362 |
+ # cannot replace varname |
363 |
+ vars_missing.add ( varname ) |
364 |
+ else: |
365 |
+ line = re_obj.sub ( repl, line ) |
366 |
+ # -- end for <varname // replace> |
367 |
+ |
368 |
+ output_lines.append ( line ) |
369 |
+ # -- end for <input_lines> |
370 |
+ |
371 |
+ # write output_lines |
372 |
+ if argv.outfile == '-': |
373 |
+ stream.write ( ''.join ( output_lines ) ) |
374 |
+ else: |
375 |
+ with open ( argv.outfile, 'wt' ) as FH: |
376 |
+ FH.write ( ''.join ( output_lines ) ) |
377 |
+ # -- end write output_lines |
378 |
+ |
379 |
+ return EX_MISS if vars_missing else EX_OK |
380 |
+ |
381 |
+ # --from-file (without --outfile): read config keys from file |
382 |
+ elif argv.from_file: |
383 |
+ config_keys = set() |
384 |
+ with open ( argv.from_file, 'rt' ) as FH: |
385 |
+ for line in FH.readlines(): |
386 |
+ config_keys.update ( RE_VAR_REF.findall ( line ) ) |
387 |
+ # -- end with |
388 |
+ |
389 |
+ if main__print_variables ( config, argv, stream, config_keys ): |
390 |
+ return EX_MISS |
391 |
+ else: |
392 |
+ return EX_OK |
393 |
+ |
394 |
+ # else filter out False/None values |
395 |
+ elif main__print_variables ( |
396 |
+ config, argv, stream, [ kx for kx in argv.config_keys if kx ] |
397 |
+ ): |
398 |
+ return EX_MISS |
399 |
+ else: |
400 |
+ return EX_OK |
401 |
+ |
402 |
+# --- end of main (...) --- |
403 |
+ |
404 |
+if __name__ == '__main__': |
405 |
+ try: |
406 |
+ sys.exit ( main() ) |
407 |
+ except KeyboardInterrupt: |
408 |
+ sys.exit ( EX_IRUPT ) |
409 |
+# -- end __main__ |
410 |
|
411 |
diff --git a/bin/query_config b/bin/query_config |
412 |
new file mode 120000 |
413 |
index 0000000..3f5df48 |
414 |
--- /dev/null |
415 |
+++ b/bin/query_config |
416 |
@@ -0,0 +1 @@ |
417 |
+invoke_pyscript.bash |
418 |
\ No newline at end of file |