public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
From: "André Erdmann" <dywi@mailerd.de>
To: gentoo-commits@lists.gentoo.org
Subject: [gentoo-commits] proj/R_overlay:master commit in: bin/, bin/py/
Date: Sun, 26 Jan 2014 19:06:56 +0000 (UTC)	[thread overview]
Message-ID: <1390762706.0cea0f9464dabaa8ad1ba0b17dd25be1ee19a58a.dywi@gentoo> (raw)

commit:     0cea0f9464dabaa8ad1ba0b17dd25be1ee19a58a
Author:     André Erdmann <dywi <AT> mailerd <DOT> de>
AuthorDate: Sun Jan 26 18:58:26 2014 +0000
Commit:     André Erdmann <dywi <AT> mailerd <DOT> de>
CommitDate: Sun Jan 26 18:58:26 2014 +0000
URL:        http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=0cea0f94

helper for creating/editing config files

./bin/py/query_config (currently accessible via ./bin/query_config in the
git repo, and not available if roverlay is installed) is a script for
(a) accessing roverlay's config in shell-usable format:
     eval $(query_config OVERLAY_DIR OVERLAY_NAME)

(b) replacing variables in file templates (@@VARNAME@@):

     query_config --config-file ~user/roverlay/R-overlay.conf \
        --from-file <template file> -O <outfile>

    This can be used to create config file templates without having to worry
    about the actual file paths / server address / etc., for example (nginx,
    server{} block):

server {
      listen @@NGINX_SERVER_ADDR@@;
      server_name @@NGINX_SERVER_NAME@@;

      ...

      # package mirror dir
      root @@OVERLAY_DISTDIR_ROOT@;

      ...
}

    (NGINX_SERVER_ADDR and NGINX_SERVER_NAME would have to be given with
    the -v/--variable switch when calling query_config).

    The exit code indicates whether all variables could be replaced or not.

TODO:
* make config option aliases available for (b) (e.g. DISTDIR)
* doc
* script name
* config templates

---
 bin/py/query_config.py | 352 +++++++++++++++++++++++++++++++++++++++++++++++++
 bin/query_config       |   1 +
 2 files changed, 353 insertions(+)

diff --git a/bin/py/query_config.py b/bin/py/query_config.py
new file mode 100644
index 0000000..bd13674
--- /dev/null
+++ b/bin/py/query_config.py
@@ -0,0 +1,352 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# *** TODO: script name ***
+#
+# Script for querying roverlay's config / editing template files.
+#
+#  Usage:
+#  * query-config -h
+#
+#  * query-config -l
+#
+#     Lists all known config options.
+#     (Note: it's not possible to query all of these options)
+#
+#  * query-config [-C <config_file>] [-u] [-a|{option[=varname]}]
+#
+#     Prints roverlay's config options in shell usable format (without relying
+#     on roverlay-sh). Prints all options if -a/--all is specified or no
+#     option[=varname] is given. options can be renamed with "=varname".
+#
+#     Usage example:
+#
+#       $ eval $(query-config -C R-overlay.conf.tmp OVERLAY_DIR=OVERLAY OVERLAY_NAME)
+#       $ echo $OVERLAY
+#       $ echo $OVERLAY_NAME
+#
+#  * query-config [-C <config_file>] [-u] -f <infile> [-O <outfile>|-] {-v VAR[=VALUE]}
+#
+#     Replaces occurences of @@VARIABLES@@ in <infile> with values taken
+#     from roverlay's config and writes the result to <outfile> or stdout.
+#     (variables may also be specified with -v VAR=VALUE, which take precedence
+#     over roverlay's config, e.g. "-v SERVER_NAME=roverlay").
+#
+#     Usage example:
+#
+#       $ query-config -C ~roverlay_user/roverlay/R-overlay.conf \
+#          -f nginx.conf.in -O nginx.conf -v SERVER_ADDR=... -v SERVER_NAME=...
+#
+#     A non-zero exit code indicates that one or more variables could not be
+#     replaced.
+#
+#
+from __future__ import print_function, unicode_literals
+
+import argparse
+import logging
+import re
+import os
+import sys
+
+import roverlay.core
+import roverlay.strutil
+from roverlay.config.entryutil import iter_config_keys
+
+EX_OK    = os.EX_OK
+EX_ERR   = os.EX_OK^1
+EX_MISS  = os.EX_OK^2
+EX_IRUPT = os.EX_OK^130
+
+RE_VAR_REF = re.compile ( "@@([a-zA-Z_]+)@@" )
+RE_VARNAME = re.compile ( "^[a-zA-Z_]+$" )
+
+
+class VarnameArgumentError ( argparse.ArgumentTypeError ):
+   def __init__ ( self, name ):
+      super ( VarnameArgumentError, self ).__init__ (
+         "invalid variable name: {!r}".format ( name )
+      )
+# --- end of VarnameArgumentError ---
+
+def get_value_str ( value, list_join_seq=" " ):
+   if value is None:
+      return ""
+   elif hasattr ( value, '__iter__' ) and not isinstance ( value, str ):
+      return list_join_seq.join ( map ( str, value ) )
+   elif isinstance ( value, bool ):
+      return "1" if value else "0"
+   else:
+      return str ( value )
+# --- end of get_value_str (...) ---
+
+def format_variables ( vardict, append_newline=True ):
+   retstr = "\n".join (
+      "{varname!s}=\"{value!s}\"".format ( varname=k, value=v )
+         for k, v in sorted ( vardict.items(), key=lambda kv: kv[0] )
+   )
+   return ( retstr + "\n" ) if append_newline else retstr
+# --- end of format_variables (...) ---
+
+def get_parser():
+   def arg_couldbe_file ( value ):
+      if value is None or value == '-':
+         return value
+      elif value:
+         f = os.path.abspath ( value )
+         if not os.path.exists ( f ) or not os.path.isdir ( f ):
+            return f
+      raise argparse.ArgumentTypeError (
+         "{!r} cannot be a file.".format ( value )
+      )
+   # --- end of arg_couldbe_file (...) ---
+
+   def arg_is_filepath_or_none ( value ):
+      if value:
+         f = os.path.abspath ( value )
+         if os.path.isfile ( f ):
+            return f
+      elif value is None:
+         return value
+      raise argparse.ArgumentTypeError (
+         "{!r} is not a file.".format ( value )
+      )
+   # --- end of arg_is_filepath_or_none (...) ---
+
+   def arg_is_varname ( value ):
+      if value:
+         vname, sepa, valias = value.partition ( '=' )
+         if not RE_VARNAME.match ( vname ):
+            raise VarnameArgumentError ( vname )
+         elif sepa:
+            if not RE_VARNAME.match ( valias ):
+               raise VarnameArgumentError ( valias )
+            else:
+               return ( vname, valias )
+         else:
+            return vname
+            #return ( vname, vname )
+      else:
+         return None
+   # --- end of arg_is_varname (...) ---
+
+   def arg_is_variable ( value ):
+      if value:
+         key, sepa, value = value.partition ( "=" )
+         if sepa:
+            return ( key, roverlay.strutil.unquote ( value ) )
+         else:
+            return ( key, "" )
+      raise argparse.ArgumentTypeError ( value )
+   # --- end of arg_is_variable (...) ---
+
+   parser = argparse.ArgumentParser (
+      description = (
+         "query config options and output them in shell-usable format"
+      ),
+      epilog = (
+         'Exit codes:\n'
+         '* {EX_OK}: success\n'
+         '* {EX_ERR}: unspecified error, e.g. invalid config entry map\n'
+         '* {EX_MISS}: one or more config keys could not be found\n'
+      ).format ( EX_OK=EX_OK, EX_MISS=EX_MISS, EX_ERR=EX_ERR ),
+      formatter_class = argparse.RawDescriptionHelpFormatter
+   )
+
+   parser.add_argument (
+      "config_keys", metavar="<config_key>", type=arg_is_varname, nargs="*",
+      help="config key (or <config_key>=<alias_key>)"
+   )
+
+   parser.add_argument (
+      "-C", "--config-file", metavar="<file>", default=None,
+      type=arg_is_filepath_or_none,
+      help="path to the config file",
+   )
+
+   parser.add_argument (
+      "-a", "--all", dest="print_all", default=False, action="store_true",
+      help="print all options"
+   )
+
+   parser.add_argument (
+      "-l", "--list-all", dest="list_all", default=False, action="store_true",
+      help="instead of printing options: list all keys"
+   )
+
+   parser.add_argument (
+      "-u", "--empty-missing", default=False, action="store_true",
+      help="set missing variables to the empty string"
+   )
+
+   parser.add_argument (
+      "-f", "--from-file", metavar="<file>", default=None,
+      type=arg_is_filepath_or_none,
+      help="read config keys from <file>"
+   )
+
+   parser.add_argument (
+      "-O", "--outfile", metavar="<file>", default=None,
+      type=arg_couldbe_file,
+      help=(
+         'in conjunction with --from-file: replace variable references and '
+         'write the resulting text to <file>'
+      )
+   )
+
+   parser.add_argument (
+      "-v", "--variable", metavar="<key=\"value\">", dest="extra_vars",
+      default=[], action="append", type=arg_is_variable,
+      help="additional variables (only with --outfile)"
+   )
+
+   return parser
+# --- end of get_parser (...) ---
+
+def get_all_config_keys():
+   return [ k.upper() for k in iter_config_keys() ]
+# --- end of get_all_config_keys (...) ---
+
+def get_vardict ( config, argv, keys ):
+   return config.query_by_name (
+      keys, empty_missing=argv.empty_missing, convert_value=get_value_str
+   )
+# --- end of get_vardict (...) ---
+
+def main__print_variables ( config, argv, stream, config_keys ):
+   num_missing, cvars = get_vardict ( config, argv, config_keys )
+   if cvars:
+      stream.write ( format_variables ( cvars ) )
+   return num_missing
+# --- end of main__print_variables (...) ---
+
+def main ( is_installed=False ):
+   parser = get_parser()
+   argv   = parser.parse_args()
+   stream = sys.stdout
+
+   # setup
+   ## logging
+   roverlay.core.force_console_logging ( log_level=logging.WARNING )
+
+   ## main config
+   if argv.config_file is None:
+      config_file = roverlay.core.locate_config_file ( False )
+   else:
+      config_file = argv.config_file
+
+   # passing installed=True|False doesn't really matter
+   config = roverlay.core.load_config_file (
+      config_file, extraconf={ 'installed': is_installed, },
+      setup_logger=False, load_main_only=True
+   )
+
+   # perform actions as requested
+
+   # --list-all: print all config keys and exit
+   if argv.list_all:
+      stream.write ( "\n".join ( sorted ( get_all_config_keys() ) ) + "\n" )
+      return EX_OK
+
+   # --all or no config keys specified: print all config options as variables
+   elif argv.print_all or not any (( argv.from_file, argv.config_keys, )):
+      main__print_variables ( config, argv, stream, get_all_config_keys() )
+      # don't return EX_MISS if --all was specified
+      return EX_OK
+
+   # --from-file with --outfile:
+   #   replace @@VARIABLES@@ in file and write to --outfile (or stdout)
+   elif argv.from_file and argv.outfile:
+      # COULDFIX: exit code when --variable is used
+      #
+      # (a) get_vardict(): return a list of missing vars and compare it
+      #                    to the final cvars
+      # (b) check the resulting str for missing vars (RE_VAR_REF.search)
+      #
+      #  Using (b) for now (and unconditionally, so that the output
+      #  always gets verified).
+      #
+
+      # list of 2-tuples ( line, set<varnames> )
+      input_lines = list()
+      config_keys = set()
+      with open ( argv.from_file, 'rt' ) as FH:
+         for line in FH.readlines():
+            varnames = set ( RE_VAR_REF.findall ( line ) )
+            input_lines.append ( ( line, varnames ) )
+            config_keys |= varnames
+         # -- end for
+      # -- end with
+
+      num_missing, cvars = get_vardict ( config, argv, config_keys )
+      del num_missing
+
+      if argv.extra_vars:
+         for k, v in argv.extra_vars:
+            cvars[k] = v
+      # -- end if extra vars
+
+      # create a dict<varname => (regex for replacing varname,replacement)>
+      #  where (re_object,replacement) := (re<@@varname@@>,value)
+      re_repl = {
+         k : ( re.compile ( "@@" + k + "@@" ), v ) for k, v in cvars.items()
+      }
+
+      # iterate through input_lines a second time, replacing @@VARNAMES@@
+      # (COULDFIX: could be done in one loop // create cvars on-the-fly (defaultdict etc))
+      output_lines = []
+      vars_missing = set()
+      for line, varnames in input_lines:
+         # apply replace operations as needed
+         for varname in varnames:
+            try:
+               re_obj, repl = re_repl [varname]
+            except KeyError:
+               # cannot replace varname
+               vars_missing.add ( varname )
+            else:
+               line = re_obj.sub ( repl, line )
+         # -- end for <varname // replace>
+
+         output_lines.append ( line )
+      # -- end for <input_lines>
+
+      # write output_lines
+      if argv.outfile == '-':
+         stream.write ( ''.join ( output_lines ) )
+      else:
+         with open ( argv.outfile, 'wt' ) as FH:
+            FH.write ( ''.join ( output_lines ) )
+      # -- end write output_lines
+
+      return EX_MISS if vars_missing else EX_OK
+
+   # --from-file (without --outfile): read config keys from file
+   elif argv.from_file:
+      config_keys = set()
+      with open ( argv.from_file, 'rt' ) as FH:
+         for line in FH.readlines():
+            config_keys.update ( RE_VAR_REF.findall ( line ) )
+      # -- end with
+
+      if main__print_variables ( config, argv, stream, config_keys ):
+         return EX_MISS
+      else:
+         return EX_OK
+
+   # else filter out False/None values
+   elif main__print_variables (
+      config, argv, stream, [ kx for kx in argv.config_keys if kx ]
+   ):
+      return EX_MISS
+   else:
+      return EX_OK
+
+# --- end of main (...) ---
+
+if __name__ == '__main__':
+   try:
+      sys.exit ( main() )
+   except KeyboardInterrupt:
+      sys.exit ( EX_IRUPT )
+# -- end __main__

diff --git a/bin/query_config b/bin/query_config
new file mode 120000
index 0000000..3f5df48
--- /dev/null
+++ b/bin/query_config
@@ -0,0 +1 @@
+invoke_pyscript.bash
\ No newline at end of file


                 reply	other threads:[~2014-01-26 19:07 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1390762706.0cea0f9464dabaa8ad1ba0b17dd25be1ee19a58a.dywi@gentoo \
    --to=dywi@mailerd.de \
    --cc=gentoo-commits@lists.gentoo.org \
    --cc=gentoo-dev@lists.gentoo.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox