Gentoo Archives: gentoo-commits

From: "André Erdmann" <dywi@×××××××.de>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/R_overlay:master commit in: bin/, bin/py/
Date: Sun, 26 Jan 2014 19:07:03
Message-Id: 1390762706.0cea0f9464dabaa8ad1ba0b17dd25be1ee19a58a.dywi@gentoo
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