1 # -*- coding: iso-8859-1 -*-
4 Manage SALOME configuration.
9 # Create configuration and set its parameters
10 tool.set("cfg", "name", "V7_6_0", "comment", "SALOME version 7.6.0")
12 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")
13 # Add patches to the product (note: patch file should be manually put to the patches directory)
14 tool.set("boost.patches.boost_patch_1.patch", "comment", "Fixes compilation problems on some platforms")
15 tool.set("boost.patches.boost_patch_2.patch", "comment", "gcc 5 compatibility")
16 # Inspect configuration: give all products
17 tool.get("cfg", "products")
18 # Inspect configuration: give parameters of the configuration
19 tool.get("cfg", "name")
20 tool.get("cfg", "comment")
21 # Inspect configuration: give parameters of the product
22 tool.get("boost", "version")
23 tool.get("boost", "url")
24 # Inspect configuration: give patches for the product
25 tool.get("boost", "patches")
26 # Verify configuration
27 conf_ok = tool.verify()
29 boost_ok = tool.verify("boost")
34 # Remove parameters of configuration
35 tool.remove("cfg", "comment")
36 # Remove parameters of product
37 tool.remove("boost", "url")
38 # Remove patch from product
39 tool.remove("boost.patches.boost_patch_2.patch")
47 import xml.etree.ElementTree as ET
49 exceptionClass = ET.ParseError
51 import xml.parsers.expat
52 exceptionClass = xml.parsers.expat.ExpatError
65 "supportedAttributes",
75 def defaultConfFile():
77 Return path to the default SALOME configuration file (string).
79 return os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "salome.xml"))
83 Return XML tag for configuration (string).
89 Return XML tag for software (string).
95 Return XML tag for patches set (string).
101 Return XML tag for patch (string).
107 Return XML attribute for name parameter (string).
113 Return XML attribute for comment parameter (string).
119 Return XML attribute for version parameter (string).
125 Return XML attribute for url parameter (string).
131 Return list of all supported XML tags (list of strings).
133 return [configTag(), softwareTag(), patchesTag(), patchTag()]
135 def supportedAttributes():
137 Return list of all supported XML attributes (list of strings).
139 return [nameAttr(), commentAttr(), versionAttr(), urlAttr()]
141 def tagAttributes(tag, force = False):
143 Return list of attributes supported for the specified XML tag.
147 force: if True, all supported attributes are returned, including "special" ones.
149 Return value is list of strings.
152 if tag == configTag():
154 attrs[nameAttr()] = False # optional
155 attrs[commentAttr()] = False # optional
157 elif tag == softwareTag():
159 # note: name for software is specified implicitly via the target path
161 attrs[nameAttr()] = True # mandatory
163 attrs[versionAttr()] = True # mandatory
164 attrs[urlAttr()] = False # optional
165 attrs[commentAttr()] = False # optional
167 elif tag == patchTag():
169 # note: name for patch is specified implicitly via the target path
171 attrs[nameAttr()] = True # mandatory
174 attrs[urlAttr()] = False
175 attrs[commentAttr()] = False
178 def tagChildren(tag):
180 Return supported child nodes' tags for given XML element.
181 Note: None means root 'config' XML element.
186 Return value is list of strings.
189 if tag == configTag(): ctags += [softwareTag()]
190 elif tag == softwareTag(): ctags += [patchesTag()]
191 elif tag == patchesTag(): ctags += [patchTag()]
192 elif tag is None: ctags += [configTag()]
197 Return parameter's alias for list of software are to be used with 'get' command (string).
199 return softwareTag()+"s"
203 Return parameter's alias for list patches to be used with 'get' command (string).
207 def childAlias(tag, param):
209 Return children node tag for children list alias.
213 param: children list alias.
215 Return child node tag name or None if alias is unsupported.
218 if tag == configTag():
219 if param == softwareAlias(): ctag = softwareTag()
221 elif tag == softwareTag():
222 if param == patchesAlias(): ctag = patchTag()
228 Return string used as a separator of path's component (string).
232 class CfgTool(object):
234 A tool to manage SALOME configuration files.
236 def __init__(self, cfgFile=None):
241 cfgFile: a path to the configuration file (string);
242 if not specified, default one is used.
245 self.cfgFile = cfgFile if cfgFile else defaultConfFile()
247 self.tree = ET.parse(self.cfgFile).getroot()
251 self.tree = self._new()
253 except exceptionClass as e:
254 if e.code == 3: # no element found, it's OK
255 self.tree = self._new()
257 raise Exception("bad XML file %s: %s" % (self.cfgFile, str(e)))
259 except Exception as e:
260 raise Exception("unkwnown error: %s" % str(e))
265 Return current encoding of the configuration file (string).
270 def setEncoding(self, value):
272 Set encoding for configuration file..
274 value: new encoding to be used when writing configuration file (string).
280 def get(self, target, param):
282 Get value of specified object's parameter.
283 Parameter can be a specific keyword that refers to the list of
284 child nodes. In this case the function returns list that
285 contains names of all child nodes.
288 target: object being checked (string).
289 param: parameter which value is being inspected.
291 Return value is string or list of strings.
293 path = self._processTarget(target)
295 elem = self._findPath(path)
297 raise Exception("no such target %s" % target)
299 if childAlias(tag, param):
300 result = self._children(elem, childAlias(tag, param))
302 elif param in tagAttributes(tag):
303 result = elem.get(param) if elem is not None and elem.get(param) else ""
306 raise Exception("unsupported parameter %s for target %s" % (param, target))
309 def set(self, target = None, *args, **kwargs):
311 Create or modify an object in the SALOME configuration.
314 target: object being created or modified (string); if not specified,
315 parameters of config itself will be modified.
316 args: positional arguments that describe parameters to be set (couple);
317 each couple of arguments specifies a parameter and its value
319 kwargs: keyword arguments - same as 'args' but specified in form of
322 path = self._processTarget(target)
325 # process keyword arguments
326 for param, value in list(kwargs.items()):
327 if param not in tagAttributes(tag):
328 raise Exception("unsupported parameter %s for target %s" % (param, target))
329 params[param] = value
331 # process positional arguments
335 if param not in tagAttributes(tag):
336 raise Exception("unsupported parameter %s for target %s" % (param, target))
338 if i+1 < len(args) and args[i+1] not in tagAttributes(tag):
342 params[param] = value
345 # create / modify target
346 elem = self._findPath(path, True)
347 for param, value in list(params.items()):
348 elem.set(param, value)
353 def remove(self, target, *args):
355 Remove object or its parameter(s).
358 target: object (string).
359 args: list of parameters which have to be removed (strings).
361 Return value is string.
363 path = self._processTarget(target)
365 elem = self._findPath(path)
367 raise Exception("no such target %s" % target)
369 # remove attributes of the target
370 # first check that all attributes are valid
372 if param not in tagAttributes(tag):
373 raise Exception("unsupported parameter %s for target %s" % (param, target))
374 elif param not in elem.attrib:
375 raise Exception("parameter %s is not set for target %s" % (param, target))
377 # now remove all attributes
379 elem.attrib.pop(param)
384 if elem == self.tree:
385 self.tree = self._new()
389 parent = self._findPath(path)
390 if parent is not None: parent.remove(elem)
396 def dump(self, target = None):
398 Dump the configuration.
401 target: object (string); if not specified, all configuration is dumped.
403 if target is not None:
404 path = self._processTarget(target)
405 elem = self._findPath(path)
407 raise Exception("no such target %s" % target)
415 def verify(self, target = None, errors = None):
420 target: object (string); if not specified, all configuration is verified.
422 Returns True if object is valid or False otherwise.
424 if errors is None: errors = []
425 if target is not None:
426 path = self._processTarget(target)
427 elem = self._findPath(path)
429 raise Exception("no such target %s" % target)
434 return self._verifyTag(elem, errors)
438 Clean the configuration.
440 self.tree = self._new()
444 def patchesDir(self):
446 Return path to the patches directory (string).
448 return os.path.join(os.path.dirname(self.cfgFile), "patches")
453 Create and return new empty root element.
455 Return values is an XML element (xml.etree.ElementTree.Element).
457 return ET.Element(configTag())
459 def _makeChild(self, elem, tag):
462 Create child element for given parent element.
465 elem: XML element (xml.etree.ElementTree.Element).
466 tag: tag of the child element
468 Return value is new XML element (xml.etree.ElementTree.Element).
470 child = ET.SubElement(elem, tag)
471 child._parent = elem # set parent!!!
474 def _processTarget(self, target):
477 Check target and return XML path for it.
482 Return value is a list of tuples; each tuple is a couple
483 of path component and optional component's name.
485 if target is None: target = ""
486 comps = [i.strip() for i in target.split(pathSeparator())]
488 # add root to the path
489 path.append((configTag(), None))
490 if comps[0] in ["", "cfg", configTag()]: comps = comps[1:]
492 # second component of path can be only "software"
493 if not comps[0] or comps[0] in supportedTags() + supportedAttributes() + ["cfg"]:
494 raise Exception("badly specified target '%s'" % target)
495 path.append((softwareTag(), comps[0]))
499 # third component of path can be only "patches" or patch
500 if comps[0] not in [patchesTag(), patchTag()]:
501 raise Exception("badly specified target '%s'" % target)
502 path.append((patchesTag(), None))
506 # fourth component of path can be only a patch name
507 path.append((patchTag(), pathSeparator().join(comps)))
511 def _findPath(self, path, create=False):
514 Find and return XML element specified by its path.
515 If path does not exist and 'create' is True, XML element will be created.
518 path: XML element's path data (see _processTarget()).
519 create: flag that forces creating XML element if it does not exist
522 Return value is an XML element (xml.etree.ElementTree.Element).
525 if path[0][0] != configTag():
526 raise Exception("error parsing target path")
529 for tag, name in path[1:]:
531 children = [i for i in elem.getchildren() if i.tag == tag and i.get(nameAttr()) == name]
532 if len(children) > 1:
533 raise Exception("error parsing target path: more than one child element found")
534 elif len(children) == 1:
538 elem = self._makeChild(elem, tag)
539 elem.set(nameAttr(), name)
545 children = [i for i in elem.getchildren() if i.tag == tag]
546 if len(children) > 1:
547 raise Exception("error parsing target path: more than one child element found")
548 elif len(children) == 1:
552 elem = self._makeChild(elem, tag)
560 def _path(self, elem):
563 Construct path to the XML element.
566 elem: XML element (xml.etree.ElementTree.Element).
568 Return value is string.
572 attrs = tagAttributes(_obj.tag, True)
573 if nameAttr() in attrs and attrs[nameAttr()]:
574 if nameAttr() not in list(_obj.keys()): _name += " [unnamed]"
575 else: _name += " [%s]" % _obj.get(nameAttr())
579 while elem is not None:
580 path.append(_mkname(elem))
581 elem = elem._parent if hasattr(elem, "_parent") else None
584 return pathSeparator().join(path)
586 def _children(self, elem, param):
589 Get names of children nodes for element.
592 elem: XML element (xml.etree.ElementTree.Element).
593 param: name of the children' tag.
595 Return value is a list of names of child elements (strings).
598 result += [i.get(nameAttr()) for i in \
599 [i for i in elem.getchildren() if i.tag == param and i.get(nameAttr())]]
600 for c in elem.getchildren():
601 result += self._children(c, param)
608 Write data tree content to the associated XML file.
611 with open(self.cfgFile, 'w') as f:
613 f.write('<?xml version="1.0" encoding="%s" ?>\n' % self.encoding() )
614 f.write('<!DOCTYPE config>\n')
616 self._prettify(self.tree)
618 et = ET.ElementTree(self.tree)
619 et.write(f, self.encoding())
623 raise Exception("can't write to %s: %s" % (self.cfgFile, e.strerror))
626 def _prettify(self, elem, level=0, hasSiblings=False):
629 Prettify XML file content.
632 elem: XML element (xml.etree.ElementTree.Element).
633 level: indentation level.
634 hasSiblings: True when item has siblings (i.e. this is not the last item
635 in the parent's children list).
638 children = elem.getchildren()
640 if hasSiblings: tail += indent * level
641 elif level > 0: tail += indent * (level-1)
643 if children: text = "\n" + indent * (level+1)
646 for i in range(len(children)):
647 self._prettify(children[i], level+1, len(children)>1 and i+1<len(children))
651 def _dump(self, elem, level=0):
657 elem: XML element (xml.etree.ElementTree.Element).
658 level: indentation level.
664 print("%s%s" % (indent * level, elem.tag))
665 attrs = tagAttributes(elem.tag, True)
666 format = "%" + "-%ds" % max([len(i) for i in supportedAttributes()]) + " : %s"
668 if a in list(elem.attrib.keys()):
669 print(indent*(level+1) + format % (a, elem.get(a)))
673 # dump all childrens recursively
674 for c in elem.getchildren():
675 self._dump(c, level+1)
679 def _checkConfig(self):
682 Verify configuration (used to check validity of associated XML file).
685 self._checkTag(self.tree, None, errors)
687 errors = ["Bad XML format:"] + ["- %s" % i for i in errors]
688 raise Exception("\n".join(errors))
691 def _checkTag(self, elem, tag, errors):
694 Check if format of given XML element is valid.
697 elem: XML element (xml.etree.ElementTree.Element).
698 tag: expected XML element's tag (string).
699 errors: output list to collect error messages (strings).
701 if elem.tag not in tagChildren(tag):
702 errors.append("bad XML element: %s" % elem.tag)
705 attrs = list(elem.keys())
707 if attr not in tagAttributes(elem.tag, True):
708 errors.append("unsupported attribute '%s' for XML element '%s'" % (attr, elem.tag))
711 # check all childrens recursively
712 children = elem.getchildren()
713 for child in children:
714 child._parent = elem # set parent!!!
715 self._checkTag(child, elem.tag, errors)
720 def _verifyTag(self, elem, errors):
723 Verify given XML element is valid in terms of SALOME configuration.
726 elem: XML element (xml.etree.ElementTree.Element).
727 errors: output list to collect error messages (strings).
729 attrs = tagAttributes(elem.tag, True)
730 # check mandatory attributes
732 if attrs[attr] and (attr not in list(elem.keys()) or not elem.get(attr).strip()):
733 errors.append("mandatory parameter '%s' of object '%s' is not set" % (attr, self._path(elem)))
736 # specific check for particular XML element
738 self._checkObject(elem)
739 except Exception as e:
740 errors.append("%s : %s" % (self._path(elem), str(e)))
741 # check all childrens recursively
742 for c in elem.getchildren():
743 self._verifyTag(c, errors)
745 return len(errors) == 0
747 def _checkObject(self, elem):
750 Perform specific check for given XML element.
752 Raises an exception that if object is invalid.
755 elem: XML element (xml.etree.ElementTree.Element).
757 if elem.tag == patchTag():
758 filename = elem.get(nameAttr())
759 url = elem.get(urlAttr())
760 if filename and not url:
761 # if url is not given, we should check that file is present locally
762 filepath = os.path.join(self.patchesDir(), filename)
763 if not os.path.exists(filepath):
764 raise Exception("patch file %s is not found" % filepath)
767 # TODO: we might check validity of URL here (see urlparse)!