--- /dev/null
+#!/usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+
+###
+# Command line tool to manage SALOME configuration.
+# Usage: type "scfg help" to learn how to use tool.
+###
+
+import sys
+import os
+from sconfig.salome_config import CfgTool, defaultConfFile
+
+###
+# TODO
+# 1. Improve parsing of command line: each command should be defined as:
+# - command: name of the command
+# - help: help string as appears in the short description of a command in a list
+# of commands and as a first line of command's help
+# - options: (dict) see below
+# - parameters: (list or dict) see below
+# Each option should be defined with:
+# - title: one letter signature
+# - help: help string
+# - parameter: name of parameter if option requires a parameter
+# Each parameter should be defined with:
+# - title: as appears in help information
+# - optional: flag (boolean) showing if this parameter is optional
+# - help: help description
+#
+# Parser of command line should then automatically check validity of the command,
+# its parameters and options.
+#
+# 2. Usage and help information should be automaticall generated and formatted
+# from command description as described above.
+#
+# 3. Protect from adding same named option to a command.
+#
+# 4. Improve formatting help for command's parameters (see 1) - namely, wrapping
+# into a block.
+###
+
+##
+# function: get_tool_name
+#
+# gets a name of this command line tool (string)
+##
+
+def get_tool_name():
+ return os.path.basename(sys.argv[0])
+
+##
+# function: verbose
+#
+# returns True when in debug mode (env var VERBOSE is set to 1)
+##
+
+def verbose():
+ return bool(os.getenv("VERBOSE", False))
+
+##
+# function: debug
+#
+# prints debug information (for debug purposes)
+#
+# Parameters:
+# args: output data to be printed to the screen (any printable objects)
+##
+
+def debug(*args):
+ if verbose():
+ print "[DEBUG]",
+ for a in args: print a,
+ print
+ pass
+
+##
+# function: error_exit
+#
+# prints error information and exits with non-zero status
+#
+# Parameters:
+# msg: error message (string)
+##
+
+def error_exit(msg):
+ print "%s: %s" % (get_tool_name(), msg)
+ sys.exit(1)
+ pass
+
+##
+# function: get_commands
+#
+# get supported commands
+#
+# return value is a dictionary:
+# { cmd : ( help, opt_map ) }
+# here:
+# - cmd is a command (string)
+# - help is a command's general help information on a command (string)
+# - opt_map is a disctionary of allowed command's options:
+# { opt: opt_help }, where
+# - opt is option ("a:" if option requires a parameter or just "a" elsewhere, where
+# "a" is any letter)
+# - opt_help is a help information on the option (string).
+#
+# See also: add_command
+##
+
+def get_commands():
+ if not globals().get("__commands__"): globals()["__commands__"] = {}
+ return globals().get("__commands__")
+
+##
+# function: add_command
+#
+# adds command to the tool
+#
+# Parameters:
+# cmd: command (string)
+# help_string: help information on the command (string)
+# options: options supported by the command (map {opt: opt_help}, see get_commands)
+##
+
+def add_command(cmd, help_string, options):
+ if cmd in get_commands():
+ debug("command %s already exists" % cmd)
+ else:
+ get_commands()[ cmd ] = ( help_string, options )
+ pass
+
+##
+# function: get_opts
+#
+# gets supported options for the command
+#
+# Parameters:
+# cmd: command (string)
+#
+# return value is map {opt: opt_help} (see get_commands)
+##
+
+def get_opts(cmd):
+ if cmd is None: cmd = ""
+ opts = {}
+ try:
+ opts = get_commands()[cmd][1]
+ except:
+ pass
+ return opts
+
+##
+# function: get_help
+#
+# gets help string for the command
+#
+# Parameters:
+# cmd: command (string)
+#
+# return value is unformatted string
+##
+
+def get_help(cmd):
+ if cmd is None: cmd = ""
+ help_string = ""
+ try:
+ help_string = get_commands()[cmd][0]
+ except:
+ pass
+ return help_string
+
+##
+# function: format_string
+#
+# formats string into block according to the given block width
+#
+# Parameters:
+# s: string data
+# width: requested width of resulting string block
+#
+# return formatted string block
+##
+
+def format_string(s, width):
+ blocks = s.split("\n")
+ result = []
+ for block in blocks:
+ block = block.replace("\t", " [[TAB]] ")
+ words = block.split()
+ current_string = ""
+ for word in words:
+ word = word if word != "[[TAB]]" else " "*2
+ if not current_string:
+ current_string = word
+ else:
+ if len(current_string + " " + word) <= width:
+ current_string += " " + word
+ else:
+ result.append(current_string)
+ current_string = word
+ pass
+ pass
+ pass
+ result.append(current_string)
+ pass
+ return "\n".join(result)
+
+##
+# function: format_commands
+#
+# formats short help on all supported commands
+#
+# return string that contains formatted help on commands
+##
+
+def format_commands():
+ commands = get_commands()
+ # filter out tool itself
+ commands = filter(lambda a: a != "", commands)
+ # sort commands alphabetically
+ commands.sort()
+ # get max command's length
+ max_length = max([ len(i) for i in commands])
+ # generate formatting string
+ prefix = " "
+ separator = " "
+ fmt_string = prefix + "%" + "-%ds" % max_length + separator
+ def _format(_c):
+ _h = get_help(_c).split("\n")[0]
+ _h = format_string(_h, 80-(max_length+len(prefix)+len(separator)))
+ _h = _h.split("\n")
+ _t = prefix + " " * max_length + separator
+ _r = []
+ _r.append(_h[0])
+ for _i in _h[1:]: _r.append(_t + _i)
+ return "\n".join(_r)
+ return "\n".join([ fmt_string % i + _format(i) for i in commands])
+
+##
+# function: format_list_options
+#
+# formats short help on commands's options to be inserted into short help info on a command
+#
+# Parameters:
+# cmd: command (string)
+#
+# return string that contains formatted help
+##
+
+def format_list_options(cmd):
+ opts = get_opts(cmd)
+ return "".join([" [%s]" % i for i in opts.keys()])
+
+##
+# function: format_options
+#
+# formats help on commands's options to be inserted after short help info on a command
+#
+# Parameters:
+# cmd: command (string)
+#
+# return string that contains formatted help
+##
+
+def format_options(cmd):
+ opts = get_opts(cmd)
+ opts_names = opts.keys()
+ if not opts: return ""
+ # get max command's length
+ max_length = max([ len(i) for i in opts_names])
+ # generate formatting string
+ prefix = " "
+ separator = " "
+ fmt_string = prefix + "%" + "-%ds" % max_length + separator
+ def _format(_o):
+ _h = opts[_o]
+ _h = format_string(_h, 80-(max_length+len(prefix)+len(separator)))
+ _h = _h.split("\n")
+ _t = prefix + " " * max_length + separator
+ _r = []
+ _r.append(_h[0])
+ for _i in _h[1:]: _r.append(_t + _i)
+ return "\n".join(_r)
+ return "\n" + "\n".join([ fmt_string % i + _format(i) for i in opts_names]) + "\n"
+
+##
+# function: usage
+#
+# prints help and exits with zero status
+#
+# Parameters:
+# cmd: command (string)
+##
+
+def usage(cmd=None):
+ if cmd is None: cmd = ""
+ if cmd not in get_commands(): error_exit("unknown command: %s" % cmd)
+ fmt = {}
+ fmt[ "prg" ] = get_tool_name()
+ fmt[ "cmd" ] = get_tool_name() if not cmd else cmd
+ fmt[ "cmd_list" ] = format_commands()
+ fmt[ "opts" ] = format_list_options(cmd)
+ fmt[ "opt_list" ] = format_options(cmd)
+ help_string = get_help(cmd)
+ help_string = help_string.replace("\t", " ")
+ print help_string.format(**fmt)
+ sys.exit(0)
+ pass
+
+##
+# function: parse_cmd_line
+#
+# parses command line; prints error and exits if unsupported command
+# or option is specified
+#
+# Parameters:
+# args: arguments being parsed (list of strings)
+##
+
+def parse_cmd_line(args):
+ tool_opts, cmd, cmd_opts, tgt, params = {}, None, {}, None, []
+ args.reverse()
+ commands = get_commands()
+ while args:
+ a = args.pop()
+ if a == "-":
+ # empty option
+ error_exit("empty option is not allowed")
+ if a.startswith("-"):
+ allowed_opts = get_opts(cmd).keys()
+ allowed_opts = dict([(i[1], len(i)>2) for i in allowed_opts])
+ processed_opts = tool_opts if cmd is None else cmd_opts
+ cmd_descr = "" if cmd is None else " for command '%s'" % cmd
+ opts = a[1:]
+ while opts:
+ o = opts[0]
+ opts = opts[1:]
+ if o == "-":
+ # "--" format is not supported
+ error_exit("invalid format of option")
+ elif o in allowed_opts.keys():
+ if allowed_opts[o]:
+ # supported option; requires parameter
+ if opts:
+ processed_opts[ o ] = opts
+ opts = []
+ elif args and not args[-1].startswith("-") and not args[-1] in commands:
+ processed_opts[ o ] = args.pop()
+ else:
+ error_exit("option %s%s requires argument" % (o, cmd_descr))
+ else:
+ # supported option; does not require parameter
+ processed_opts[ o ] = None
+ pass
+ pass
+ else:
+ # unsupported option
+ error_exit("unknown option: -%s%s" % (o, cmd_descr))
+ pass # while opts
+ pass
+ elif not cmd:
+ cmd = a
+ if cmd not in commands:
+ # unsupported command
+ error_exit("unknown command: %s" % cmd)
+ pass
+ elif not tgt:
+ tgt = a
+ pass
+ else:
+ params += [ a ]
+ return tool_opts, cmd, cmd_opts, tgt, params
+
+##
+# function: main
+#
+# main entry point of the tool
+##
+
+def main():
+ # set-up commands
+
+ # - tool itself
+ help_string = "Command line tool to manage SALOME configuration.\n\n"
+ help_string += "Usage: {prg}{opts} COMMAND [ARGS]\n"
+ help_string += "{opt_list}\n"
+ help_string += "Supported commands:\n"
+ help_string += "{cmd_list}\n\n"
+ help_string += "See '{prg} help COMMAND' for more information on a specific command."
+ options = {}
+ options [ "-s FILE" ] = "Path to the configuration file; default: %s." % defaultConfFile()
+ add_command("",
+ help_string,
+ options)
+
+ # - help command
+ help_string = "Display help information about this tool.\n\n"
+ help_string += "Usage: {prg} {cmd}{opts}\n"
+ help_string += "{opt_list}"
+ add_command("help",
+ help_string,
+ {})
+
+ # - set command
+ help_string = "Create or modify configuration object.\n\n"
+ help_string += "Usage: {prg} {cmd}{opts} TARGET [PARAM VALUE ...]\n\n"
+ help_string += "\tTARGET: configuration object being created / modified\n"
+ help_string += "\tPARAM: parameter of the target object\n"
+ help_string += "\tVALUE: value to be assigned to the attribute of the target object\n"
+ help_string += "{opt_list}"
+ add_command("set",
+ help_string,
+ {})
+
+ # - get command
+ help_string = "Get parameter of configuration object.\n\n"
+ help_string += "Usage: {prg} {cmd}{opts} TARGET PARAM\n\n"
+ help_string += "\tTARGET: configuration object being inspected\n"
+ help_string += "\tPARAM: parameter of the target object\n"
+ help_string += "{opt_list}"
+ add_command("get",
+ help_string,
+ {})
+
+ # - remove command
+ help_string = "Remove configuration object or its attribute(s).\n\n"
+ help_string += "Usage: {prg} {cmd}{opts} TARGET [PARAM ...]\n\n"
+ help_string += "\tTARGET: configuration object\n"
+ help_string += "\tPARAM: attribute of the target object\n"
+ help_string += "{opt_list}"
+ add_command("remove",
+ help_string,
+ {})
+
+ # - dump command
+ help_string = "Dump configuration.\n\n"
+ help_string += "Usage: {prg} {cmd}{opts} [TARGET]\n\n"
+ help_string += "\tTARGET: optional configuration object (if not specified,\n"
+ help_string += "\t all configuration is dumped).\n"
+ help_string += "{opt_list}"
+ add_command("dump",
+ help_string,
+ {})
+
+ # - verify command
+ help_string = "Verify configuration.\n\n"
+ help_string += "Usage: {prg} {cmd}{opts} [TARGET]\n\n"
+ help_string += "\tTARGET: optional configuration object (if not specified,\n"
+ help_string += "\t all configuration is verified).\n"
+ help_string += "{opt_list}"
+ add_command("verify",
+ help_string,
+ {})
+
+ # - clean command
+ help_string = "Clean configuration.\n\n"
+ help_string += "Usage: {prg} {cmd}{opts}\n"
+ help_string += "{opt_list}"
+ add_command("clean",
+ help_string,
+ {})
+
+ # parse command line
+
+ opts, cmd, cmd_opts, tgt, params = parse_cmd_line(sys.argv[1:])
+ debug("parse command line: options =", opts, "; command =", cmd, "; command options =", cmd_opts, "; target =", tgt, "; arguments =", params)
+
+ # process command
+
+ if not cmd or cmd == "help":
+ # show help and exit
+ usage(tgt)
+ pass
+
+ try:
+ # check if custom source file is specified
+
+ src_file = opts["s"] if opts.has_key("s") else None
+ debug("source file:", src_file)
+
+ # create config tool
+
+ cfg_tool = CfgTool(src_file)
+ debug("cfg tool:", cfg_tool)
+
+ # TODO: we should not check commands in a switch-like block below;
+ # instead processing should be done automatically basing on the
+ # predefined set of commands (TODO)
+
+ if cmd == "get":
+ if not tgt:
+ error_exit("%s: target is not specified!" % cmd)
+ if len(params) < 1:
+ error_exit("%s: parameter of target %s is not specified!" % (cmd, tgt))
+ # only one parameter can be read!
+ if len(params) > 1:
+ error_exit("%s: can't read more than one parameter at once!" % cmd)
+ def _toString(v):
+ if v is None: return ""
+ elif type(v) is list: return " ".join(v)
+ else: return v
+ print _toString(cfg_tool.get(tgt, params[0]))
+ pass
+ elif cmd == "set":
+ if not tgt:
+ error_exit("%s: target is not specified!" % cmd)
+ # empty parameters list is allowed!
+ cfg_tool.set(tgt, *params)
+ pass
+ elif cmd == "remove":
+ if not tgt:
+ error_exit("%s: target is not specified!" % cmd)
+ # empty parameters list is allowed!
+ cfg_tool.remove(tgt, *params)
+ pass
+ elif cmd == "dump":
+ if len(params) > 0:
+ error_exit("%s: command does not support parameters!" % cmd)
+ # empty target is allowed!
+ cfg_tool.dump(tgt)
+ pass
+ elif cmd == "verify":
+ if len(params) > 0:
+ error_exit("%s: command does not support parameters!" % cmd)
+ # empty target is allowed!
+ errors = []
+ if cfg_tool.verify(tgt, errors):
+ msg = " (no products found)" if not cfg_tool.get("cfg", "products") else ""
+ print "Configuration is valid%s." % msg
+ else:
+ raise Exception("Configuration is invalid:\n"+ "\n".join(errors))
+ pass
+ elif cmd == "clean":
+ if tgt:
+ error_exit("%s: command does not support target!" % cmd)
+ if len(params) > 0:
+ error_exit("%s: command does not support parameters!" % cmd)
+ # empty target is allowed!
+ cfg_tool.clean()
+ pass
+ else:
+ # unknown command: normally we should not go here
+ error_exit("unknown command: %s" % cmd)
+ pass
+ pass
+ except Exception, e:
+ error_exit(e)
+ pass
+
+if __name__ == "__main__":
+ main()
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+
+###
+# Command line tool to compare two SALOME configurations.
+# Usage: type "scfgcmp help" to learn how to use tool.
+###
+
+import sys
+import os
+from sconfig.salome_config import *
+import getopt
+
+def get_tool_name():
+ return os.path.basename(sys.argv[0])
+
+def error_exit(msg):
+ print "%s: %s" % (get_tool_name(), msg)
+ print "Try '%s --help' for more information." % get_tool_name()
+ sys.exit(2)
+ pass
+
+def usage():
+ print "Command line tool to compare two SALOME configurations."
+ print
+ print "Usage: %s [options] CFGFILE1 [CFGFILE2]" % get_tool_name()
+ print
+ print " CFGFILE1 First configuration file to compare."
+ print " CFGFILE2 Second configuration file to compare; if not specified,"
+ print " default one is used: %s." % defaultConfFile()
+ print
+ print "Options:"
+ print " --help (-h) Prints this help information."
+ print " --force (-f) Perform full comparation, including optional parameters."
+ print " --verbose (-v) Print results of comparison to the screen."
+ print
+
+def main():
+ # parse command line
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "hfv", ["help", "force", "verbose"])
+ except getopt.GetoptError as err:
+ error_exit(str(err))
+ pass
+ opt_keys = [i[0] for i in opts]
+ isusage = "-h" in opt_keys or "--help" in opt_keys
+ isverbose = "-v" in opt_keys or "--verbose" in opt_keys
+ isforce = "-f" in opt_keys or "--force" in opt_keys
+
+ if isusage:
+ usage()
+ pass
+
+ cfgfile1 = args[0] if len(args) > 0 else defaultConfFile()
+ cfgfile2 = args[1] if len(args) > 1 else defaultConfFile()
+
+ if not cfgfile1:
+ error_exit("missing configuration file")
+ pass
+
+ if cfgfile1 and not os.path.isfile(cfgfile1):
+ error_exit("configuration file %s is not found" % cfgfile1)
+ pass
+
+ if cfgfile2 and not os.path.isfile(cfgfile2):
+ error_exit("configuration file %s is not found" % cfgfile2)
+ pass
+
+ if cfgfile1 and cfgfile2 and os.path.samefile(cfgfile1, cfgfile2):
+ error_exit("cannot compare the file with itself")
+ pass
+
+ # create config tools
+
+ try:
+ tool1 = CfgTool(cfgfile1)
+ tool2 = CfgTool(cfgfile2)
+ except Exception, e:
+ error_exit(str(e))
+ pass
+
+ # diff config files
+
+ errors = []
+
+ def _cmpAttrs(_what, _where):
+ _attrs = tagAttributes(_what)
+ for _attr in _attrs:
+ try:
+ _v1 = tool1.get(_where, _attr)
+ except:
+ _v1 = None
+ pass
+ try:
+ _v2 = tool2.get(_where, _attr)
+ except:
+ _v2 = None
+ pass
+ if (isforce or _attrs[_attr]) and _v1 != _v2:
+ errors.append("attribute '%s' of '%s' differs: '%s' in %s vs '%s' in %s" % (_attr, _where, str(_v1), tool1.cfgFile, str(_v2), tool2.cfgFile))
+ pass
+ pass
+ pass
+
+ # - compare parameters of configuration
+ _cmpAttrs(configTag(), configTag())
+
+ # - compare products
+
+ products1 = tool1.get(configTag(), softwareAlias())
+ products2 = tool2.get(configTag(), softwareAlias())
+ all_products = list(set(products1+products2))
+
+ for product in all_products:
+ if product in products1 and product not in products2:
+ errors.append("%s '%s' is only in '%s'" % (softwareTag(), product, tool1.cfgFile))
+ pass
+ elif product in products2 and product not in products1:
+ errors.append("%s '%s' is only in '%s'" % (softwareTag(), product, tool2.cfgFile))
+ pass
+ else:
+ # -- compare parameters of products
+ _cmpAttrs(softwareTag(), product)
+ # -- compare patches
+ patches1 = tool1.get(product, patchesAlias())
+ patches2 = tool2.get(product, patchesAlias())
+ all_patches = list(set(patches1+patches2))
+ for patch in all_patches:
+ if patch in patches1 and patch not in patches2:
+ errors.append("%s '%s' for '%s' is only in '%s'" % (patchTag(), patch, product, tool1.cfgFile))
+ pass
+ if patch in patches2 and patch not in patches1:
+ errors.append("%s '%s' for '%s' is only in '%s'" % (patchTag(), patch, product, tool2.cfgFile))
+ pass
+ else:
+ # -- compare parameters of products
+ _cmpAttrs(patchTag(), "%s.%s.%s" % (product, patchesTag(), patch))
+ pass
+ pass
+ pass
+ pass
+
+ # print results
+
+ if errors:
+ if isverbose:
+ print "Files %s and %s differ:" % (tool1.cfgFile, tool2.cfgFile)
+ print "\n".join([" " + i for i in errors])
+ pass
+ return 1
+
+ return 0
+
+# -----------------------------------------------------------------------------
+
+if __name__ == '__main__':
+ sys.exit(main())
--- /dev/null
+# -*- coding: iso-8859-1 -*-
+
+"""
+Manage SALOME configuration.
+
+Typical usage:
+
+tool = CfgTool()
+# Create configuration and set its parameters
+tool.set("cfg", "name", "V7_6_0", "comment", "SALOME version 7.6.0")
+# Add product
+tool.set("boost", "version", "1.52.2", "url", "https://sourceforge.net/projects/boost/files/boost/1.52.0/boost_1_52_0.tar.gz", "comment", "Set of libraries for the C++ programming language")
+# Add patches to the product (note: patch file should be manually put to the patches directory)
+tool.set("boost.patches.boost_patch_1.patch", "comment", "Fixes compilation problems on some platforms")
+tool.set("boost.patches.boost_patch_2.patch", "comment", "gcc 5 compatibility")
+# Inspect configuration: give all products
+tool.get("cfg", "products")
+# Inspect configuration: give parameters of the configuration
+tool.get("cfg", "name")
+tool.get("cfg", "comment")
+# Inspect configuration: give parameters of the product
+tool.get("boost", "version")
+tool.get("boost", "url")
+# Inspect configuration: give patches for the product
+tool.get("boost", "patches")
+# Verify configuration
+conf_ok = tool.verify()
+# Verify product
+boost_ok = tool.verify("boost")
+# Dump configuration
+tool.dump()
+# Dump product
+tool.dump("boost")
+# Remove parameters of configuration
+tool.remove("cfg", "comment")
+# Remove parameters of product
+tool.remove("boost", "url")
+# Remove patch from product
+tool.remove("boost.patches.boost_patch_2.patch")
+# Remove product
+tool.remove("boost")
+# Clean configuration
+tool.clean()
+"""
+
+import os
+import xml.etree.ElementTree as ET
+try:
+ exceptionClass = ET.ParseError
+except:
+ import xml.parsers.expat
+ exceptionClass = xml.parsers.expat.ExpatError
+
+__all__ = [
+ "defaultConfFile",
+ "configTag",
+ "softwareTag",
+ "patchesTag",
+ "patchTag",
+ "nameAttr",
+ "commentAttr",
+ "versionAttr",
+ "urlAttr",
+ "supportedTags",
+ "supportedAttributes",
+ "tagAttributes",
+ "tagChildren",
+ "softwareAlias",
+ "patchesAlias",
+ "childAlias",
+ "pathSeparator",
+ "CfgTool",
+ ]
+
+def defaultConfFile():
+ """
+ Return path to the default SALOME configuration file (string).
+ """
+ return os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "salome.xml"))
+
+def configTag():
+ """
+ Return XML tag for configuration (string).
+ """
+ return "config"
+
+def softwareTag():
+ """
+ Return XML tag for software (string).
+ """
+ return "product"
+
+def patchesTag():
+ """
+ Return XML tag for patches set (string).
+ """
+ return "patches"
+
+def patchTag():
+ """
+ Return XML tag for patch (string).
+ """
+ return "patch"
+
+def nameAttr():
+ """
+ Return XML attribute for name parameter (string).
+ """
+ return "name"
+
+def commentAttr():
+ """
+ Return XML attribute for comment parameter (string).
+ """
+ return "comment"
+
+def versionAttr():
+ """
+ Return XML attribute for version parameter (string).
+ """
+ return "version"
+
+def urlAttr():
+ """
+ Return XML attribute for url parameter (string).
+ """
+ return "url"
+
+def supportedTags():
+ """
+ Return list of all supported XML tags (list of strings).
+ """
+ return [configTag(), softwareTag(), patchesTag(), patchTag()]
+
+def supportedAttributes():
+ """
+ Return list of all supported XML attributes (list of strings).
+ """
+ return [nameAttr(), commentAttr(), versionAttr(), urlAttr()]
+
+def tagAttributes(tag, force = False):
+ """
+ Return list of attributes supported for the specified XML tag.
+
+ Parameters:
+ tag: XML tag.
+ force: if True, all supported attributes are returned, including "special" ones.
+
+ Return value is list of strings.
+ """
+ attrs = {}
+ if tag == configTag():
+ # config
+ attrs[nameAttr()] = False # optional
+ attrs[commentAttr()] = False # optional
+ pass
+ elif tag == softwareTag():
+ # software
+ # note: name for software is specified implicitly via the target path
+ if force:
+ attrs[nameAttr()] = True # mandatory
+ pass
+ attrs[versionAttr()] = True # mandatory
+ attrs[urlAttr()] = False # optional
+ attrs[commentAttr()] = False # optional
+ pass
+ elif tag == patchTag():
+ # patch
+ # note: name for patch is specified implicitly via the target path
+ if force:
+ attrs[nameAttr()] = True # mandatory
+ pass
+ pass
+ attrs[urlAttr()] = False
+ attrs[commentAttr()] = False
+ return attrs
+
+def tagChildren(tag):
+ """
+ Return supported child nodes' tags for given XML element.
+ Note: None means root 'config' XML element.
+
+ Parameters:
+ tag: XML tag.
+
+ Return value is list of strings.
+ """
+ ctags = []
+ if tag == configTag(): ctags += [softwareTag()]
+ elif tag == softwareTag(): ctags += [patchesTag()]
+ elif tag == patchesTag(): ctags += [patchTag()]
+ elif tag is None: ctags += [configTag()]
+ return ctags
+
+def softwareAlias():
+ """
+ Return parameter's alias for list of software are to be used with 'get' command (string).
+ """
+ return softwareTag()+"s"
+
+def patchesAlias():
+ """
+ Return parameter's alias for list patches to be used with 'get' command (string).
+ """
+ return patchesTag()
+
+def childAlias(tag, param):
+ """
+ Return children node tag for children list alias.
+
+ Parameters:
+ tag: XML tag.
+ param: children list alias.
+
+ Return child node tag name or None if alias is unsupported.
+ """
+ ctag = None
+ if tag == configTag():
+ if param == softwareAlias(): ctag = softwareTag()
+ pass
+ elif tag == softwareTag():
+ if param == patchesAlias(): ctag = patchTag()
+ pass
+ return ctag
+
+def pathSeparator():
+ """
+ Return string used as a separator of path's component (string).
+ """
+ return "."
+
+class CfgTool(object):
+ """
+ A tool to manage SALOME configuration files.
+ """
+ def __init__(self, cfgFile=None):
+ """
+ Constructor.
+
+ Parameters:
+ cfgFile: a path to the configuration file (string);
+ if not specified, default one is used.
+ """
+ self.enc = "utf-8"
+ self.cfgFile = cfgFile if cfgFile else defaultConfFile()
+ try:
+ self.tree = ET.parse(self.cfgFile).getroot()
+ self._checkConfig()
+ pass
+ except IOError, e:
+ self.tree = self._new()
+ pass
+ except exceptionClass, e:
+ if e.code == 3: # no element found, it's OK
+ self.tree = self._new()
+ else:
+ raise Exception("bad XML file %s: %s" % (self.cfgFile, str(e)))
+ pass
+ except Exception, e:
+ raise Exception("unkwnown error: %s" % str(e))
+ pass
+
+ def encoding(self):
+ """
+ Return current encoding of the configuration file (string).
+ Default is "utf-8".
+ """
+ return self.enc
+
+ def setEncoding(self, value):
+ """
+ Set encoding for configuration file..
+ Parameters:
+ value: new encoding to be used when writing configuration file (string).
+ """
+ self.enc = value
+ self._write()
+ pass
+
+ def get(self, target, param):
+ """
+ Get value of specified object's parameter.
+ Parameter can be a specific keyword that refers to the list of
+ child nodes. In this case the function returns list that
+ contains names of all child nodes.
+
+ Parameters:
+ target: object being checked (string).
+ param: parameter which value is being inspected.
+
+ Return value is string or list of strings.
+ """
+ path = self._processTarget(target)
+ tag = path[-1][0]
+ elem = self._findPath(path)
+ if elem is None:
+ raise Exception("no such target %s" % target)
+ result = None
+ if childAlias(tag, param):
+ result = self._children(elem, childAlias(tag, param))
+ pass
+ elif param in tagAttributes(tag):
+ result = elem.get(param) if elem is not None and elem.get(param) else ""
+ pass
+ else:
+ raise Exception("unsupported parameter %s for target %s" % (param, target))
+ return result
+
+ def set(self, target = None, *args, **kwargs):
+ """
+ Create or modify an object in the SALOME configuration.
+
+ Parameters:
+ target: object being created or modified (string); if not specified,
+ parameters of config itself will be modified.
+ args: positional arguments that describe parameters to be set (couple);
+ each couple of arguments specifies a parameter and its value
+ (strings).
+ kwargs: keyword arguments - same as 'args' but specified in form of
+ dictionary.
+ """
+ path = self._processTarget(target)
+ tag = path[-1][0]
+ params = {}
+ # process keyword arguments
+ for param, value in kwargs.items():
+ if param not in tagAttributes(tag):
+ raise Exception("unsupported parameter %s for target %s" % (param, target))
+ params[param] = value
+ pass
+ # process positional arguments
+ i = 0
+ while i < len(args):
+ param = args[i]
+ if param not in tagAttributes(tag):
+ raise Exception("unsupported parameter %s for target %s" % (param, target))
+ value = ""
+ if i+1 < len(args) and args[i+1] not in tagAttributes(tag):
+ value = args[i+1]
+ i += 1
+ pass
+ params[param] = value
+ i += 1
+ pass
+ # create / modify target
+ elem = self._findPath(path, True)
+ for param, value in params.items():
+ elem.set(param, value)
+ pass
+ self._write()
+ pass
+
+ def remove(self, target, *args):
+ """
+ Remove object or its parameter(s).
+
+ Parameters:
+ target: object (string).
+ args: list of parameters which have to be removed (strings).
+
+ Return value is string.
+ """
+ path = self._processTarget(target)
+ tag = path[-1][0]
+ elem = self._findPath(path)
+ if elem is None:
+ raise Exception("no such target %s" % target)
+ if args:
+ # remove attributes of the target
+ # first check that all attributes are valid
+ for param in args:
+ if param not in tagAttributes(tag):
+ raise Exception("unsupported parameter %s for target %s" % (param, target))
+ elif param not in elem.attrib:
+ raise Exception("parameter %s is not set for target %s" % (param, target))
+ pass
+ # now remove all attributes
+ for param in args:
+ elem.attrib.pop(param)
+ pass
+ pass
+ else:
+ # remove target
+ if elem == self.tree:
+ self.tree = self._new()
+ pass
+ else:
+ path = path[:-1]
+ parent = self._findPath(path)
+ if parent is not None: parent.remove(elem)
+ pass
+ pass
+ self._write()
+ pass
+
+ def dump(self, target = None):
+ """
+ Dump the configuration.
+
+ Parameters:
+ target: object (string); if not specified, all configuration is dumped.
+ """
+ if target is not None:
+ path = self._processTarget(target)
+ elem = self._findPath(path)
+ if elem is None:
+ raise Exception("no such target %s" % target)
+ pass
+ else:
+ elem = self.tree
+ pass
+ self._dump(elem)
+ pass
+
+ def verify(self, target = None, errors = None):
+ """
+ Verify configuration
+
+ Parameters:
+ target: object (string); if not specified, all configuration is verified.
+
+ Returns True if object is valid or False otherwise.
+ """
+ if errors is None: errors = []
+ if target is not None:
+ path = self._processTarget(target)
+ elem = self._findPath(path)
+ if elem is None:
+ raise Exception("no such target %s" % target)
+ pass
+ else:
+ elem = self.tree
+ pass
+ return self._verifyTag(elem, errors)
+
+ def clean(self):
+ """
+ Clean the configuration.
+ """
+ self.tree = self._new()
+ self._write()
+ pass
+
+ def patchesDir(self):
+ """
+ Return path to the patches directory (string).
+ """
+ return os.path.join(os.path.dirname(self.cfgFile), "patches")
+
+ def _new(self):
+ """
+ (internal)
+ Create and return new empty root element.
+
+ Return values is an XML element (xml.etree.ElementTree.Element).
+ """
+ return ET.Element(configTag())
+
+ def _makeChild(self, elem, tag):
+ """
+ (internal)
+ Create child element for given parent element.
+
+ Parameters:
+ elem: XML element (xml.etree.ElementTree.Element).
+ tag: tag of the child element
+
+ Return value is new XML element (xml.etree.ElementTree.Element).
+ """
+ child = ET.SubElement(elem, tag)
+ child._parent = elem # set parent!!!
+ return child
+
+ def _processTarget(self, target):
+ """
+ (internal)
+ Check target and return XML path for it.
+
+ Parameters:
+ target: target path.
+
+ Return value is a list of tuples; each tuple is a couple
+ of path component and optional component's name.
+ """
+ if target is None: target = ""
+ comps = [i.strip() for i in target.split(pathSeparator())]
+ path = []
+ # add root to the path
+ path.append((configTag(), None))
+ if comps[0] in ["", "cfg", configTag()]: comps = comps[1:]
+ if comps:
+ # second component of path can be only "software"
+ if not comps[0] or comps[0] in supportedTags() + supportedAttributes() + ["cfg"]:
+ raise Exception("badly specified target '%s'" % target)
+ path.append((softwareTag(), comps[0]))
+ comps = comps[1:]
+ pass
+ if comps:
+ # third component of path can be only "patches" or patch
+ if comps[0] not in [patchesTag(), patchTag()]:
+ raise Exception("badly specified target '%s'" % target)
+ path.append((patchesTag(), None))
+ comps = comps[1:]
+ pass
+ if comps:
+ # fourth component of path can be only a patch name
+ path.append((patchTag(), pathSeparator().join(comps)))
+ pass
+ return path
+
+ def _findPath(self, path, create=False):
+ """
+ (internal)
+ Find and return XML element specified by its path.
+ If path does not exist and 'create' is True, XML element will be created.
+
+ Parameters:
+ path: XML element's path data (see _processTarget()).
+ create: flag that forces creating XML element if it does not exist
+ (default is False).
+
+ Return value is an XML element (xml.etree.ElementTree.Element).
+ """
+ if len(path) == 1:
+ if path[0][0] != configTag():
+ raise Exception("error parsing target path")
+ return self.tree
+ elem = self.tree
+ for tag, name in path[1:]:
+ if name:
+ children = filter(lambda i: i.tag == tag and i.get(nameAttr()) == name, elem.getchildren())
+ if len(children) > 1:
+ raise Exception("error parsing target path: more than one child element found")
+ elif len(children) == 1:
+ elem = children[0]
+ pass
+ elif create:
+ elem = self._makeChild(elem, tag)
+ elem.set(nameAttr(), name)
+ pass
+ else:
+ return None
+ pass
+ else:
+ children = filter(lambda i: i.tag == tag, elem.getchildren())
+ if len(children) > 1:
+ raise Exception("error parsing target path: more than one child element found")
+ elif len(children) == 1:
+ elem = children[0]
+ pass
+ elif create:
+ elem = self._makeChild(elem, tag)
+ pass
+ else:
+ return None
+ pass
+ pass
+ return elem
+
+ def _path(self, elem):
+ """
+ (internal)
+ Construct path to the XML element.
+
+ Parameters:
+ elem: XML element (xml.etree.ElementTree.Element).
+
+ Return value is string.
+ """
+ def _mkname(_obj):
+ _name = _obj.tag
+ attrs = tagAttributes(_obj.tag, True)
+ if nameAttr() in attrs and attrs[nameAttr()]:
+ if nameAttr() not in _obj.keys(): _name += " [unnamed]"
+ else: _name += " [%s]" % _obj.get(nameAttr())
+ pass
+ return _name
+ path = []
+ while elem is not None:
+ path.append(_mkname(elem))
+ elem = elem._parent if hasattr(elem, "_parent") else None
+ pass
+ path.reverse()
+ return pathSeparator().join(path)
+
+ def _children(self, elem, param):
+ """
+ (internal)
+ Get names of children nodes for element.
+
+ Parameters:
+ elem: XML element (xml.etree.ElementTree.Element).
+ param: name of the children' tag.
+
+ Return value is a list of names of child elements (strings).
+ """
+ result = []
+ result += [i.get(nameAttr()) for i in \
+ filter(lambda i: i.tag == param and i.get(nameAttr()), elem.getchildren())]
+ for c in elem.getchildren():
+ result += self._children(c, param)
+ pass
+ return result
+
+ def _write(self):
+ """
+ (internal)
+ Write data tree content to the associated XML file.
+ """
+ try:
+ with open(self.cfgFile, 'w') as f:
+ # write header
+ f.write('<?xml version="1.0" encoding="%s" ?>\n' % self.encoding() )
+ f.write('<!DOCTYPE config>\n')
+ # prettify content
+ self._prettify(self.tree)
+ # write content
+ et = ET.ElementTree(self.tree)
+ et.write(f, self.encoding())
+ pass
+ pass
+ except IOError, e:
+ raise Exception("can't write to %s: %s" % (self.cfgFile, e.strerror))
+ pass
+
+ def _prettify(self, elem, level=0, hasSiblings=False):
+ """
+ (internal)
+ Prettify XML file content.
+
+ Parameters:
+ elem: XML element (xml.etree.ElementTree.Element).
+ level: indentation level.
+ hasSiblings: True when item has siblings (i.e. this is not the last item
+ in the parent's children list).
+ """
+ indent = " "
+ children = elem.getchildren()
+ tail = "\n"
+ if hasSiblings: tail += indent * level
+ elif level > 0: tail += indent * (level-1)
+ text = None
+ if children: text = "\n" + indent * (level+1)
+ elem.tail = tail
+ elem.text = text
+ for i in range(len(children)):
+ self._prettify(children[i], level+1, len(children)>1 and i+1<len(children))
+ pass
+ pass
+
+ def _dump(self, elem, level=0):
+ """
+ (internal)
+ Dump XML element.
+
+ Parameters:
+ elem: XML element (xml.etree.ElementTree.Element).
+ level: indentation level.
+ """
+ if elem is None:
+ return
+ indent = " "
+ # dump element
+ print "%s%s" % (indent * level, elem.tag)
+ attrs = tagAttributes(elem.tag, True)
+ format = "%" + "-%ds" % max([len(i) for i in supportedAttributes()]) + " : %s"
+ for a in attrs:
+ if a in elem.attrib.keys():
+ print indent*(level+1) + format % (a, elem.get(a))
+ pass
+ pass
+ print
+ # dump all childrens recursively
+ for c in elem.getchildren():
+ self._dump(c, level+1)
+ pass
+ pass
+
+ def _checkConfig(self):
+ """
+ (internal)
+ Verify configuration (used to check validity of associated XML file).
+ """
+ errors = []
+ self._checkTag(self.tree, None, errors)
+ if errors:
+ errors = ["Bad XML format:"] + ["- %s" % i for i in errors]
+ raise Exception("\n".join(errors))
+ pass
+
+ def _checkTag(self, elem, tag, errors):
+ """
+ (internal)
+ Check if format of given XML element is valid.
+
+ Parameters:
+ elem: XML element (xml.etree.ElementTree.Element).
+ tag: expected XML element's tag (string).
+ errors: output list to collect error messages (strings).
+ """
+ if elem.tag not in tagChildren(tag):
+ errors.append("bad XML element: %s" % elem.tag)
+ else:
+ # check attributes
+ attrs = elem.keys()
+ for attr in attrs:
+ if attr not in tagAttributes(elem.tag, True):
+ errors.append("unsupported attribute '%s' for XML element '%s'" % (attr, elem.tag))
+ pass
+ pass
+ # check all childrens recursively
+ children = elem.getchildren()
+ for child in children:
+ child._parent = elem # set parent!!!
+ self._checkTag(child, elem.tag, errors)
+ pass
+ pass
+ pass
+
+ def _verifyTag(self, elem, errors):
+ """
+ (internal)
+ Verify given XML element is valid in terms of SALOME configuration.
+
+ Parameters:
+ elem: XML element (xml.etree.ElementTree.Element).
+ errors: output list to collect error messages (strings).
+ """
+ attrs = tagAttributes(elem.tag, True)
+ # check mandatory attributes
+ for attr in attrs:
+ if attrs[attr] and (attr not in elem.keys() or not elem.get(attr).strip()):
+ errors.append("mandatory parameter '%s' of object '%s' is not set" % (attr, self._path(elem)))
+ pass
+ pass
+ # specific check for particular XML element
+ try:
+ self._checkObject(elem)
+ except Exception, e:
+ errors.append("%s : %s" % (self._path(elem), str(e)))
+ # check all childrens recursively
+ for c in elem.getchildren():
+ self._verifyTag(c, errors)
+ pass
+ return len(errors) == 0
+
+ def _checkObject(self, elem):
+ """
+ (internal)
+ Perform specific check for given XML element.
+
+ Raises an exception that if object is invalid.
+
+ Parameters:
+ elem: XML element (xml.etree.ElementTree.Element).
+ """
+ if elem.tag == patchTag():
+ filename = elem.get(nameAttr())
+ url = elem.get(urlAttr())
+ if filename and not url:
+ # if url is not given, we should check that file is present locally
+ filepath = os.path.join(self.patchesDir(), filename)
+ if not os.path.exists(filepath):
+ raise Exception("patch file %s is not found" % filepath)
+ pass
+ else:
+ # TODO: we might check validity of URL here (see urlparse)!
+ pass
+ pass
+ pass
+
+ pass # class CfgTool