1 # -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2016-2020 OPEN CASCADE
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
22 Manage SALOME configuration.
27 # Create configuration and set its parameters
28 tool.set("cfg", "name", "V7_6_0", "comment", "SALOME version 7.6.0")
30 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")
31 # Add patches to the product (note: patch file should be manually put to the patches directory)
32 tool.set("boost.patches.boost_patch_1.patch", "comment", "Fixes compilation problems on some platforms")
33 tool.set("boost.patches.boost_patch_2.patch", "comment", "gcc 5 compatibility")
34 # Inspect configuration: give all products
35 tool.get("cfg", "products")
36 # Inspect configuration: give parameters of the configuration
37 tool.get("cfg", "name")
38 tool.get("cfg", "comment")
39 # Inspect configuration: give parameters of the product
40 tool.get("boost", "version")
41 tool.get("boost", "url")
42 # Inspect configuration: give patches for the product
43 tool.get("boost", "patches")
44 # Verify configuration
45 conf_ok = tool.verify()
47 boost_ok = tool.verify("boost")
52 # Remove parameters of configuration
53 tool.remove("cfg", "comment")
54 # Remove parameters of product
55 tool.remove("boost", "url")
56 # Remove patch from product
57 tool.remove("boost.patches.boost_patch_2.patch")
65 import xml.etree.ElementTree as ET
67 exceptionClass = ET.ParseError
69 import xml.parsers.expat
70 exceptionClass = xml.parsers.expat.ExpatError
83 "supportedAttributes",
93 def defaultConfFile():
95 Return path to the default SALOME configuration file (string).
97 return os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "salome.xml"))
101 Return XML tag for configuration (string).
107 Return XML tag for software (string).
113 Return XML tag for patches set (string).
119 Return XML tag for patch (string).
125 Return XML attribute for name parameter (string).
131 Return XML attribute for comment parameter (string).
137 Return XML attribute for version parameter (string).
143 Return XML attribute for url parameter (string).
149 Return list of all supported XML tags (list of strings).
151 return [configTag(), softwareTag(), patchesTag(), patchTag()]
153 def supportedAttributes():
155 Return list of all supported XML attributes (list of strings).
157 return [nameAttr(), commentAttr(), versionAttr(), urlAttr()]
159 def tagAttributes(tag, force = False):
161 Return list of attributes supported for the specified XML tag.
165 force: if True, all supported attributes are returned, including "special" ones.
167 Return value is list of strings.
170 if tag == configTag():
172 attrs[nameAttr()] = False # optional
173 attrs[commentAttr()] = False # optional
175 elif tag == softwareTag():
177 # note: name for software is specified implicitly via the target path
179 attrs[nameAttr()] = True # mandatory
181 attrs[versionAttr()] = True # mandatory
182 attrs[urlAttr()] = False # optional
183 attrs[commentAttr()] = False # optional
185 elif tag == patchTag():
187 # note: name for patch is specified implicitly via the target path
189 attrs[nameAttr()] = True # mandatory
192 attrs[urlAttr()] = False
193 attrs[commentAttr()] = False
196 def tagChildren(tag):
198 Return supported child nodes' tags for given XML element.
199 Note: None means root 'config' XML element.
204 Return value is list of strings.
207 if tag == configTag(): ctags += [softwareTag()]
208 elif tag == softwareTag(): ctags += [patchesTag()]
209 elif tag == patchesTag(): ctags += [patchTag()]
210 elif tag is None: ctags += [configTag()]
215 Return parameter's alias for list of software are to be used with 'get' command (string).
217 return softwareTag()+"s"
221 Return parameter's alias for list patches to be used with 'get' command (string).
225 def childAlias(tag, param):
227 Return children node tag for children list alias.
231 param: children list alias.
233 Return child node tag name or None if alias is unsupported.
236 if tag == configTag():
237 if param == softwareAlias(): ctag = softwareTag()
239 elif tag == softwareTag():
240 if param == patchesAlias(): ctag = patchTag()
246 Return string used as a separator of path's component (string).
250 class CfgTool(object):
252 A tool to manage SALOME configuration files.
254 def __init__(self, cfgFile=None):
259 cfgFile: a path to the configuration file (string);
260 if not specified, default one is used.
263 self.cfgFile = cfgFile if cfgFile else defaultConfFile()
265 self.tree = ET.parse(self.cfgFile).getroot()
269 self.tree = self._new()
271 except exceptionClass as e:
272 if e.code == 3: # no element found, it's OK
273 self.tree = self._new()
275 raise Exception("bad XML file %s: %s" % (self.cfgFile, str(e)))
277 except Exception as e:
278 raise Exception("unkwnown error: %s" % str(e))
283 Return current encoding of the configuration file (string).
288 def setEncoding(self, value):
290 Set encoding for configuration file..
292 value: new encoding to be used when writing configuration file (string).
298 def get(self, target, param):
300 Get value of specified object's parameter.
301 Parameter can be a specific keyword that refers to the list of
302 child nodes. In this case the function returns list that
303 contains names of all child nodes.
306 target: object being checked (string).
307 param: parameter which value is being inspected.
309 Return value is string or list of strings.
311 path = self._processTarget(target)
313 elem = self._findPath(path)
315 raise Exception("no such target %s" % target)
317 if childAlias(tag, param):
318 result = self._children(elem, childAlias(tag, param))
320 elif param in tagAttributes(tag):
321 result = elem.get(param) if elem is not None and elem.get(param) else ""
324 raise Exception("unsupported parameter %s for target %s" % (param, target))
327 def set(self, target = None, *args, **kwargs):
329 Create or modify an object in the SALOME configuration.
332 target: object being created or modified (string); if not specified,
333 parameters of config itself will be modified.
334 args: positional arguments that describe parameters to be set (couple);
335 each couple of arguments specifies a parameter and its value
337 kwargs: keyword arguments - same as 'args' but specified in form of
340 path = self._processTarget(target)
343 # process keyword arguments
344 for param, value in list(kwargs.items()):
345 if param not in tagAttributes(tag):
346 raise Exception("unsupported parameter %s for target %s" % (param, target))
347 params[param] = value
349 # process positional arguments
353 if param not in tagAttributes(tag):
354 raise Exception("unsupported parameter %s for target %s" % (param, target))
356 if i+1 < len(args) and args[i+1] not in tagAttributes(tag):
360 params[param] = value
363 # create / modify target
364 elem = self._findPath(path, True)
365 for param, value in list(params.items()):
366 elem.set(param, value)
371 def remove(self, target, *args):
373 Remove object or its parameter(s).
376 target: object (string).
377 args: list of parameters which have to be removed (strings).
379 Return value is string.
381 path = self._processTarget(target)
383 elem = self._findPath(path)
385 raise Exception("no such target %s" % target)
387 # remove attributes of the target
388 # first check that all attributes are valid
390 if param not in tagAttributes(tag):
391 raise Exception("unsupported parameter %s for target %s" % (param, target))
392 elif param not in elem.attrib:
393 raise Exception("parameter %s is not set for target %s" % (param, target))
395 # now remove all attributes
397 elem.attrib.pop(param)
402 if elem == self.tree:
403 self.tree = self._new()
407 parent = self._findPath(path)
408 if parent is not None: parent.remove(elem)
414 def dump(self, target = None):
416 Dump the configuration.
419 target: object (string); if not specified, all configuration is dumped.
421 if target is not None:
422 path = self._processTarget(target)
423 elem = self._findPath(path)
425 raise Exception("no such target %s" % target)
433 def verify(self, target = None, errors = None):
438 target: object (string); if not specified, all configuration is verified.
440 Returns True if object is valid or False otherwise.
442 if errors is None: errors = []
443 if target is not None:
444 path = self._processTarget(target)
445 elem = self._findPath(path)
447 raise Exception("no such target %s" % target)
452 return self._verifyTag(elem, errors)
456 Clean the configuration.
458 self.tree = self._new()
462 def patchesDir(self):
464 Return path to the patches directory (string).
466 return os.path.join(os.path.dirname(self.cfgFile), "patches")
471 Create and return new empty root element.
473 Return values is an XML element (xml.etree.ElementTree.Element).
475 return ET.Element(configTag())
477 def _makeChild(self, elem, tag):
480 Create child element for given parent element.
483 elem: XML element (xml.etree.ElementTree.Element).
484 tag: tag of the child element
486 Return value is new XML element (xml.etree.ElementTree.Element).
488 child = ET.SubElement(elem, tag)
489 child._parent = elem # set parent!!!
492 def _processTarget(self, target):
495 Check target and return XML path for it.
500 Return value is a list of tuples; each tuple is a couple
501 of path component and optional component's name.
503 if target is None: target = ""
504 comps = [i.strip() for i in target.split(pathSeparator())]
506 # add root to the path
507 path.append((configTag(), None))
508 if comps[0] in ["", "cfg", configTag()]: comps = comps[1:]
510 # second component of path can be only "software"
511 if not comps[0] or comps[0] in supportedTags() + supportedAttributes() + ["cfg"]:
512 raise Exception("badly specified target '%s'" % target)
513 path.append((softwareTag(), comps[0]))
517 # third component of path can be only "patches" or patch
518 if comps[0] not in [patchesTag(), patchTag()]:
519 raise Exception("badly specified target '%s'" % target)
520 path.append((patchesTag(), None))
524 # fourth component of path can be only a patch name
525 path.append((patchTag(), pathSeparator().join(comps)))
529 def _findPath(self, path, create=False):
532 Find and return XML element specified by its path.
533 If path does not exist and 'create' is True, XML element will be created.
536 path: XML element's path data (see _processTarget()).
537 create: flag that forces creating XML element if it does not exist
540 Return value is an XML element (xml.etree.ElementTree.Element).
543 if path[0][0] != configTag():
544 raise Exception("error parsing target path")
547 for tag, name in path[1:]:
549 children = [i for i in elem.getchildren() if i.tag == tag and i.get(nameAttr()) == name]
550 if len(children) > 1:
551 raise Exception("error parsing target path: more than one child element found")
552 elif len(children) == 1:
556 elem = self._makeChild(elem, tag)
557 elem.set(nameAttr(), name)
563 children = [i for i in elem.getchildren() if i.tag == tag]
564 if len(children) > 1:
565 raise Exception("error parsing target path: more than one child element found")
566 elif len(children) == 1:
570 elem = self._makeChild(elem, tag)
578 def _path(self, elem):
581 Construct path to the XML element.
584 elem: XML element (xml.etree.ElementTree.Element).
586 Return value is string.
590 attrs = tagAttributes(_obj.tag, True)
591 if nameAttr() in attrs and attrs[nameAttr()]:
592 if nameAttr() not in list(_obj.keys()): _name += " [unnamed]"
593 else: _name += " [%s]" % _obj.get(nameAttr())
597 while elem is not None:
598 path.append(_mkname(elem))
599 elem = elem._parent if hasattr(elem, "_parent") else None
602 return pathSeparator().join(path)
604 def _children(self, elem, param):
607 Get names of children nodes for element.
610 elem: XML element (xml.etree.ElementTree.Element).
611 param: name of the children' tag.
613 Return value is a list of names of child elements (strings).
616 result += [i.get(nameAttr()) for i in \
617 [i for i in elem.getchildren() if i.tag == param and i.get(nameAttr())]]
618 for c in elem.getchildren():
619 result += self._children(c, param)
626 Write data tree content to the associated XML file.
629 with open(self.cfgFile, 'w') as f:
631 f.write('<?xml version="1.0" encoding="%s" ?>\n' % self.encoding() )
632 f.write('<!DOCTYPE config>\n')
634 self._prettify(self.tree)
636 et = ET.ElementTree(self.tree)
637 et.write(f, self.encoding())
641 raise Exception("can't write to %s: %s" % (self.cfgFile, e.strerror))
644 def _prettify(self, elem, level=0, hasSiblings=False):
647 Prettify XML file content.
650 elem: XML element (xml.etree.ElementTree.Element).
651 level: indentation level.
652 hasSiblings: True when item has siblings (i.e. this is not the last item
653 in the parent's children list).
656 children = elem.getchildren()
658 if hasSiblings: tail += indent * level
659 elif level > 0: tail += indent * (level-1)
661 if children: text = "\n" + indent * (level+1)
664 for i in range(len(children)):
665 self._prettify(children[i], level+1, len(children)>1 and i+1<len(children))
669 def _dump(self, elem, level=0):
675 elem: XML element (xml.etree.ElementTree.Element).
676 level: indentation level.
682 print("%s%s" % (indent * level, elem.tag))
683 attrs = tagAttributes(elem.tag, True)
684 format = "%" + "-%ds" % max([len(i) for i in supportedAttributes()]) + " : %s"
686 if a in list(elem.attrib.keys()):
687 print(indent*(level+1) + format % (a, elem.get(a)))
691 # dump all childrens recursively
692 for c in elem.getchildren():
693 self._dump(c, level+1)
697 def _checkConfig(self):
700 Verify configuration (used to check validity of associated XML file).
703 self._checkTag(self.tree, None, errors)
705 errors = ["Bad XML format:"] + ["- %s" % i for i in errors]
706 raise Exception("\n".join(errors))
709 def _checkTag(self, elem, tag, errors):
712 Check if format of given XML element is valid.
715 elem: XML element (xml.etree.ElementTree.Element).
716 tag: expected XML element's tag (string).
717 errors: output list to collect error messages (strings).
719 if elem.tag not in tagChildren(tag):
720 errors.append("bad XML element: %s" % elem.tag)
723 attrs = list(elem.keys())
725 if attr not in tagAttributes(elem.tag, True):
726 errors.append("unsupported attribute '%s' for XML element '%s'" % (attr, elem.tag))
729 # check all childrens recursively
730 children = elem.getchildren()
731 for child in children:
732 child._parent = elem # set parent!!!
733 self._checkTag(child, elem.tag, errors)
738 def _verifyTag(self, elem, errors):
741 Verify given XML element is valid in terms of SALOME configuration.
744 elem: XML element (xml.etree.ElementTree.Element).
745 errors: output list to collect error messages (strings).
747 attrs = tagAttributes(elem.tag, True)
748 # check mandatory attributes
750 if attrs[attr] and (attr not in list(elem.keys()) or not elem.get(attr).strip()):
751 errors.append("mandatory parameter '%s' of object '%s' is not set" % (attr, self._path(elem)))
754 # specific check for particular XML element
756 self._checkObject(elem)
757 except Exception as e:
758 errors.append("%s : %s" % (self._path(elem), str(e)))
759 # check all childrens recursively
760 for c in elem.getchildren():
761 self._verifyTag(c, errors)
763 return len(errors) == 0
765 def _checkObject(self, elem):
768 Perform specific check for given XML element.
770 Raises an exception that if object is invalid.
773 elem: XML element (xml.etree.ElementTree.Element).
775 if elem.tag == patchTag():
776 filename = elem.get(nameAttr())
777 url = elem.get(urlAttr())
778 if filename and not url:
779 # if url is not given, we should check that file is present locally
780 filepath = os.path.join(self.patchesDir(), filename)
781 if not os.path.exists(filepath):
782 raise Exception("patch file %s is not found" % filepath)
785 # TODO: we might check validity of URL here (see urlparse)!