diff options
Diffstat (limited to 'bin/SConsDoc.py')
-rw-r--r-- | bin/SConsDoc.py | 218 |
1 files changed, 135 insertions, 83 deletions
diff --git a/bin/SConsDoc.py b/bin/SConsDoc.py index e44a8db..f1de48a 100644 --- a/bin/SConsDoc.py +++ b/bin/SConsDoc.py @@ -26,7 +26,8 @@ # from __future__ import print_function -__doc__ = """ + +__doc__ = r""" This module parses home-brew XML files that document various things in SCons. Right now, it handles Builders, functions, construction variables, and Tools, but we expect it to get extended in the future. @@ -117,16 +118,18 @@ import re import sys import copy +PY2 = sys.version_info[0] == 2 + # Do we have libxml2/libxslt/lxml? has_libxml2 = True try: import libxml2 import libxslt -except: +except ImportError: has_libxml2 = False try: import lxml - except: + except ImportError: raise ImportError("Failed to import either libxml2/libxslt or lxml") has_etree = False @@ -155,13 +158,16 @@ if not has_etree: except ImportError: raise ImportError("Failed to import ElementTree from any known place") -re_entity = re.compile(r"\&([^;]+);") +# patterns to help trim XML passed in as strings +re_entity = re.compile(r"&([^;]+);") re_entity_header = re.compile(r"<!DOCTYPE\s+sconsdoc\s+[^\]]+\]>") # Namespace for the SCons Docbook XSD -dbxsd="http://www.scons.org/dbxsd/v1.0" +dbxsd = "http://www.scons.org/dbxsd/v1.0" +# Namsespace pattern to help identify an scons-xml file read as bytes +dbxsdpat = b'xmlns="%s"' % dbxsd.encode('utf-8') # Namespace map identifier for the SCons Docbook XSD -dbxid="dbx" +dbxid = "dbx" # Namespace for schema instances xsi = "http://www.w3.org/2001/XMLSchema-instance" @@ -170,19 +176,22 @@ copyright_comment = """ Copyright (c) 2001 - 2019 The SCons Foundation This file is processed by the bin/SConsDoc.py module. -See its __doc__ string for a discussion of the format. +See its docstring for a discussion of the format. """ def isSConsXml(fpath): - """ Check whether the given file is a SCons XML file, i.e. it - contains the default target namespace definition. + """ Check whether the given file is an SCons XML file. + + It is SCons XML if it contains the default target namespace definition + described by dbxsdpat + """ try: - with open(fpath,'r') as f: + with open(fpath, 'rb') as f: content = f.read() - if content.find('xmlns="%s"' % dbxsd) >= 0: + if content.find(dbxsdpat) >= 0: return True - except: + except Exception: pass return False @@ -195,10 +204,11 @@ def remove_entities(content): return content -default_xsd = os.path.join('doc','xsd','scons.xsd') +default_xsd = os.path.join('doc', 'xsd', 'scons.xsd') ARG = "dbscons" + class Libxml2ValidityHandler: def __init__(self): @@ -224,10 +234,11 @@ class DoctypeEntity: def getEntityString(self): txt = """ <!ENTITY %(perc)s %(name)s SYSTEM "%(uri)s"> %(perc)s%(name)s; -""" % {'perc' : perc, 'name' : self.name, 'uri' : self.uri} +""" % {'perc': perc, 'name': self.name, 'uri': self.uri} return txt + class DoctypeDeclaration: def __init__(self, name_=None): self.name = name_ @@ -253,14 +264,16 @@ class DoctypeDeclaration: return content if not has_libxml2: - class TreeFactory: + class TreeFactory(object): def __init__(self): pass - def newNode(self, tag): + @staticmethod + def newNode(tag): return etree.Element(tag) - def newEtreeNode(self, tag, init_ns=False): + @staticmethod + def newEtreeNode(tag, init_ns=False): if init_ns: NSMAP = {None: dbxsd, 'xsi' : xsi} @@ -268,47 +281,66 @@ if not has_libxml2: return etree.Element(tag) - def copyNode(self, node): + @staticmethod + def copyNode(node): return copy.deepcopy(node) - def appendNode(self, parent, child): + @staticmethod + def appendNode(parent, child): parent.append(child) - def hasAttribute(self, node, att): + @staticmethod + def hasAttribute(node, att): return att in node.attrib - def getAttribute(self, node, att): + @staticmethod + def getAttribute(node, att): return node.attrib[att] - def setAttribute(self, node, att, value): + @staticmethod + def setAttribute(node, att, value): node.attrib[att] = value - def getText(self, root): + @staticmethod + def getText(root): return root.text - def setText(self, root, txt): + @staticmethod + def setText(root, txt): root.text = txt - def writeGenTree(self, root, fp): + @staticmethod + def writeGenTree(root, fp): dt = DoctypeDeclaration() - fp.write(etree.tostring(root, xml_declaration=True, - encoding="UTF-8", pretty_print=True, + try: + encfun = unicode # PY2 + except NameError: + encfun = str + fp.write(etree.tostring(root, encoding=encfun, + pretty_print=True, doctype=dt.createDoctype())) - def writeTree(self, root, fpath): - with open(fpath, 'w') as fp: - fp.write(etree.tostring(root, xml_declaration=True, - encoding="UTF-8", pretty_print=True)) - - def prettyPrintFile(self, fpath): - with open(fpath,'r') as fin: + @staticmethod + def writeTree(root, fpath): + try: + encfun = unicode # PY2 + except NameError: + encfun = "utf-8" + with open(fpath, 'wb') as fp: + fp.write(etree.tostring(root, encoding=encfun, + pretty_print=True)) + + @staticmethod + def prettyPrintFile(fpath): + with open(fpath,'rb') as fin: tree = etree.parse(fin) pretty_content = etree.tostring(tree, pretty_print=True) - with open(fpath,'w') as fout: + with open(fpath,'wb') as fout: fout.write(pretty_content) - def decorateWithHeader(self, root): + @staticmethod + def decorateWithHeader(root): root.attrib["{"+xsi+"}schemaLocation"] = "%s %s/scons.xsd" % (dbxsd, dbxsd) return root @@ -316,12 +348,12 @@ if not has_libxml2: """ Return a XML file tree with the correct namespaces set, the element root as top entry and the given header comment. """ - NSMAP = {None: dbxsd, - 'xsi' : xsi} + NSMAP = {None: dbxsd, 'xsi' : xsi} t = etree.Element(root, nsmap=NSMAP) return self.decorateWithHeader(t) - def validateXml(self, fpath, xmlschema_context): + @staticmethod + def validateXml(fpath, xmlschema_context): # Use lxml xmlschema = etree.XMLSchema(xmlschema_context) try: @@ -339,19 +371,22 @@ if not has_libxml2: return False return True - def findAll(self, root, tag, ns=None, xp_ctxt=None, nsmap=None): + @staticmethod + def findAll(root, tag, ns=None, xp_ctxt=None, nsmap=None): expression = ".//{%s}%s" % (nsmap[ns], tag) if not ns or not nsmap: expression = ".//%s" % tag return root.findall(expression) - def findAllChildrenOf(self, root, tag, ns=None, xp_ctxt=None, nsmap=None): + @staticmethod + def findAllChildrenOf(root, tag, ns=None, xp_ctxt=None, nsmap=None): expression = "./{%s}%s/*" % (nsmap[ns], tag) if not ns or not nsmap: expression = "./%s/*" % tag return root.findall(expression) - def convertElementTree(self, root): + @staticmethod + def convertElementTree(root): """ Convert the given tree of etree.Element entries to a list of tree nodes for the current XML toolkit. @@ -359,53 +394,63 @@ if not has_libxml2: return [root] else: - class TreeFactory: + class TreeFactory(object): def __init__(self): pass - def newNode(self, tag): + @staticmethod + def newNode(tag): return libxml2.newNode(tag) - def newEtreeNode(self, tag, init_ns=False): + @staticmethod + def newEtreeNode(tag, init_ns=False): return etree.Element(tag) - def copyNode(self, node): + @staticmethod + def copyNode(node): return node.copyNode(1) - def appendNode(self, parent, child): + @staticmethod + def appendNode(parent, child): if hasattr(parent, 'addChild'): parent.addChild(child) else: parent.append(child) - def hasAttribute(self, node, att): + @staticmethod + def hasAttribute(node, att): if hasattr(node, 'hasProp'): return node.hasProp(att) return att in node.attrib - def getAttribute(self, node, att): + @staticmethod + def getAttribute(node, att): if hasattr(node, 'prop'): return node.prop(att) return node.attrib[att] - def setAttribute(self, node, att, value): + @staticmethod + def setAttribute(node, att, value): if hasattr(node, 'setProp'): node.setProp(att, value) else: node.attrib[att] = value - def getText(self, root): + @staticmethod + def getText(root): if hasattr(root, 'getContent'): return root.getContent() return root.text - def setText(self, root, txt): + @staticmethod + def setText(root, txt): if hasattr(root, 'setContent'): root.setContent(txt) else: root.text = txt - def writeGenTree(self, root, fp): + @staticmethod + def writeGenTree(root, fp): doc = libxml2.newDoc('1.0') dtd = doc.newDtd("sconsdoc", None, None) doc.addChild(dtd) @@ -420,23 +465,26 @@ else: fp.write(content) doc.freeDoc() - def writeTree(self, root, fpath): - with open(fpath, 'w') as fp: + @staticmethod + def writeTree(root, fpath): + with open(fpath, 'wb') as fp: doc = libxml2.newDoc('1.0') doc.setRootElement(root) fp.write(doc.serialize("UTF-8", 1)) doc.freeDoc() - def prettyPrintFile(self, fpath): + @staticmethod + def prettyPrintFile(fpath): # Read file and resolve entities doc = libxml2.readFile(fpath, None, libxml2d.XML_PARSE_NOENT) - with open(fpath, 'w') as fp: + with open(fpath, 'wb') as fp: # Prettyprint fp.write(doc.serialize("UTF-8", 1)) # Cleanup doc.freeDoc() - def decorateWithHeader(self, root): + @staticmethod + def decorateWithHeader(root): # Register the namespaces ns = root.newNs(dbxsd, None) xi = root.newNs(xsi, 'xsi') @@ -453,7 +501,8 @@ else: t = libxml2.newNode(root) return self.decorateWithHeader(t) - def validateXml(self, fpath, xmlschema_context): + @staticmethod + def validateXml(fpath, xmlschema_context): retval = True # Create validation context @@ -479,7 +528,8 @@ else: return retval - def findAll(self, root, tag, ns=None, xpath_context=None, nsmap=None): + @staticmethod + def findAll(root, tag, ns=None, xpath_context=None, nsmap=None): if hasattr(root, 'xpathEval') and xpath_context: # Use the xpath context xpath_context.setContextNode(root) @@ -493,7 +543,8 @@ else: expression = ".//%s" % tag return root.findall(expression) - def findAllChildrenOf(self, root, tag, ns=None, xpath_context=None, nsmap=None): + @staticmethod + def findAllChildrenOf(root, tag, ns=None, xpath_context=None, nsmap=None): if hasattr(root, 'xpathEval') and xpath_context: # Use the xpath context xpath_context.setContextNode(root) @@ -562,20 +613,18 @@ else: tf = TreeFactory() -class SConsDocTree: +class SConsDocTree(object): def __init__(self): - self.nsmap = {'dbx' : dbxsd} + self.nsmap = {'dbx': dbxsd} self.doc = None self.root = None self.xpath_context = None def parseContent(self, content, include_entities=True): - """ Parses the given content as XML file. This method - is used when we generate the basic lists of entities - for the builders, tools and functions. - So we usually don't bother about namespaces and resolving - entities here...this is handled in parseXmlFile below - (step 2 of the overall process). + """ Parses the given text content as XML + + This is the setup portion, called from parseContent in + an SConsDocHandler instance - see the notes there. """ if not include_entities: content = remove_entities(content) @@ -583,7 +632,6 @@ class SConsDocTree: self.root = etree.fromstring(content) def parseXmlFile(self, fpath): - nsmap = {'dbx' : dbxsd} if not has_libxml2: # Create domtree from file domtree = etree.parse(fpath) @@ -604,7 +652,7 @@ class SConsDocTree: if self.xpath_context is not None: self.xpath_context.xpathFreeContext() -perc="%" +perc = "%" def validate_all_xml(dpaths, xsdfile=default_xsd): xmlschema_context = None @@ -620,7 +668,7 @@ def validate_all_xml(dpaths, xsdfile=default_xsd): fpaths = [] for dp in dpaths: if dp.endswith('.xml') and isSConsXml(dp): - path='.' + path = '.' fpaths.append(dp) else: for path, dirs, files in os.walk(dp): @@ -633,8 +681,8 @@ def validate_all_xml(dpaths, xsdfile=default_xsd): fails = [] for idx, fp in enumerate(fpaths): fpath = os.path.join(path, fp) - print("%.2f%s (%d/%d) %s" % (float(idx+1)*100.0/float(len(fpaths)), - perc, idx+1, len(fpaths),fp)) + print("%.2f%s (%d/%d) %s" % (float(idx + 1) * 100.0 /float(len(fpaths)), + perc, idx + 1, len(fpaths), fp)) if not tf.validateXml(fp, xmlschema_context): fails.append(fp) @@ -649,6 +697,7 @@ def validate_all_xml(dpaths, xsdfile=default_xsd): return True + class Item(object): def __init__(self, name): self.name = name @@ -668,21 +717,25 @@ class Item(object): def __lt__(self, other): return self.sort_name < other.sort_name + class Builder(Item): pass + class Function(Item): - def __init__(self, name): - super(Function, self).__init__(name) + pass + class Tool(Item): def __init__(self, name): Item.__init__(self, name) self.entity = self.name.replace('+', 'X') + class ConstructionVariable(Item): pass + class Arguments(object): def __init__(self, signature, body=None): if not body: @@ -692,7 +745,7 @@ class Arguments(object): def __str__(self): s = ''.join(self.body).strip() result = [] - for m in re.findall('([a-zA-Z/_]+|[^a-zA-Z/_]+)', s): + for m in re.findall(r'([a-zA-Z/_]+|[^a-zA-Z/_]+)', s): if ' ' in m: m = '"%s"' % m result.append(m) @@ -700,6 +753,7 @@ class Arguments(object): def append(self, data): self.body.append(data) + class SConsDocHandler(object): def __init__(self): self.builders = {} @@ -794,7 +848,7 @@ class SConsDocHandler(object): self.parseDomtree(t.root, t.xpath_context, t.nsmap) # lifted from Ka-Ping Yee's way cool pydoc module. -if sys.version_info[0] == 2: +if PY2: def importfile(path): """Import a Python source file or compiled file given its path.""" import imp @@ -817,11 +871,9 @@ if sys.version_info[0] == 2: else: # PY3 version, from newer pydoc def importfile(path): """Import a Python source file or compiled file given its path.""" - import importlib - from pydoc import ErrorDuringImport - magic = importlib.util.MAGIC_NUMBER + from importlib.util import MAGIC_NUMBER with open(path, 'rb') as ifp: - is_bytecode = magic == ifp.read(len(magic)) + is_bytecode = MAGIC_NUMBER == ifp.read(len(MAGIC_NUMBER)) filename = os.path.basename(path) name, ext = os.path.splitext(filename) if is_bytecode: @@ -832,7 +884,7 @@ else: # PY3 version, from newer pydoc spec = importlib.util.spec_from_file_location(name, path, loader=loader) try: return importlib._bootstrap._load(spec) - except: + except ImportError: raise ErrorDuringImport(path, sys.exc_info()) # Local Variables: |