diff options
Diffstat (limited to 'pomop/pom.py')
-rw-r--r-- | pomop/pom.py | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/pomop/pom.py b/pomop/pom.py new file mode 100644 index 0000000..126a47f --- /dev/null +++ b/pomop/pom.py @@ -0,0 +1,298 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2013 Guido Günther <agx@sigxcpu.org> +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Some Pom parsing handling + +import tempfile +import os +from lxml import etree + +from . version import Version + +class Pom(object): + """ + Represents a maven POM + + for documentation check I{test_pom.py} + """ + ns = "http://maven.apache.org/POM/4.0.0" + root = "/{%s}project" % ns + + groupid_node = "/{%s}groupId" % ns + artifactid_node = "/{%s}artifactId" % ns + version_node = "/{%s}version" % ns + triple_nodes = (groupid_node, artifactid_node, version_node) + + parent_node = "/{%s}parent" % ns + deps_node = "/{%s}dependencies" % ns + dep_node = "/{%s}dependency" % ns + + version_path = root + version_node + artifactid_path = root + artifactid_node + groupid_path = root + groupid_node + parent_path = root + parent_node + deps_path = root + deps_node + dep_path = root + deps_node + dep_node + + def __init__(self, tree, path=None): + self._path = path + self._tree = tree + + @property + def path(self): + """The path in the filesystem""" + return self._path + + def get_version(self): + searcher = etree.ETXPath(self.version_path) + try: + return Version(searcher(self._tree)[0].text) + except IndexError: + return None + + def set_version(self, version): + searcher = etree.ETXPath(self.version_path) + searcher(self._tree)[0].text = version + + version = property(get_version, set_version) + + @property + def artifactid(self): + """ + The artifactid of this pom + """ + searcher = etree.ETXPath(self.artifactid_path) + return searcher(self._tree)[0].text.strip() + + @property + def groupid(self): + """ + The groupid of this pom. Might be empty. + """ + searcher = etree.ETXPath(self.groupid_path) + try: + return searcher(self._tree)[0].text.strip() + except IndexError: + return None + + def _parent_path(self, node): + return self.root + self.parent_node + node + + def get_parent(self): + """ + Return the POMs parent + + returns: groupid, artifactid, version + """ + p = [] + try: + for node in self.triple_nodes: + searcher = etree.ETXPath(self._parent_path(node)) + p.append(searcher(self._tree)[0].text.strip()) + except IndexError: + return None + return tuple(p) + + def set_parent(self, parent): + """ + Return the POMs parent + + @param parent: groupid, artifactid, version + @type parent: C{tuple} + """ + old_parent = self.parent + if not old_parent: # no parent pom so far + if None in parent: + raise ValueError("Need full parent triple") + searcher = etree.ETXPath(self.root) + root = searcher(self._tree)[0] + # add an empty parent element + parent_elem = etree.SubElement(root, self.parent_node[1:]) + parent_elem.text='\n' + + for node, val in zip(self.triple_nodes, parent): + if not val: + continue + + searcher = etree.ETXPath(self._parent_path(node)) + n = searcher(self._tree) + if n: + n[0].text = val + else: # if the element is missing, add it + n = etree.SubElement(parent_elem, node[1:]) + n.text = val + + parent = property(get_parent, set_parent, doc="The POMs parent as triplet") + + @property + def triplet(self): + """ + groupid, artifactid and version triplet + + The version and groupid can be C{None} which means + "use the parents value" + """ + return (self.groupid, self.artifactid, self.version) + + @property + def full_triplet(self): + """ + groupid, artifactid and version triplet + + All values are guaranteed to be different from C{None}. + since missing values are fetched from the parent + """ + return ((self.groupid or self.parent[0], + self.artifactid or self.parent[1], + self.version or self.parent[2])) + + def is_parent_of(self, child): + """ + Check wheter this is the parent of pom I{child} + + @param child: the pom to check + @type child: L{Pom} + @return: C{True} if L{self} is the parent of L{child}, C{False} + otherwise + @rtype: C{bool} + """ + c = child.parent + if not c: + return False + + t = self.full_triplet + + return (c[0] == t[0] and + c[1] == t[1] and + c[2] == t[2]) + + def is_child_of(self, parent): + """ + Check wheter this is the child of pom I{parent} + + @param parent: the pom to check + @type parent: L{Pom} + @return: C{True} if L{self} is the child of L{parent}, C{False} + otherwise + @rtype: C{bool} + """ + return parent.is_parent_of(self) + + def find_parent(self, poms): + """ + Given a list of poms find our parent in it. + + @param poms: list of poms + @type poms: C{list} of L{Pom}s + @return: the parent pom + @rtype: L{Pom} or None + """ + for pom in poms: + if self.is_child_of(pom): + return pom + return None + + def _strip_ns(self, tag): + """ + Stip the namespace from an XML tag + """ + return tag[len(self.ns)+2:] + + def _parse_dep(self, depelem): + """ + Parse the XML of a POM dependency + + @param depelem: a dependency element + @type depelem: lxml.etree._Element + """ + dep = {} + for child in depelem: + # FIXME: this strips comments + if isinstance(child.tag, (str, unicode)): + tag = self._strip_ns(child.tag) + dep[tag] = child.text + return dep + + def get_dependencies(self): + """ + Get the dependencies as a list + """ + return [ self._parse_dep(e) + for e in self._get_nodes(self.dep_path) if e is not None ] + + def _get_nodes(self, path): + """Return all nodes at xpath I{path} as an Iterator""" + searcher = etree.ETXPath(path) + return searcher(self._tree) + + def _add_dependency(self, dep): + searcher = etree.ETXPath(self.deps_path) + deps = searcher(self._tree)[0] + dep_elem = etree.SubElement(deps, self.dep_node[1:]) + dep_elem.text='\n' + + for node, val in dep.items(): + if not val: + continue + n = etree.SubElement(dep_elem, '{%s}%s' % (self.ns, node)) + n.text = val + n.tail = '\n' + + def _remove_dependencies(self): + searcher = etree.ETXPath(self.deps_path) + deps = searcher(self._tree)[0] + if deps is not None: + deps.clear() + + def set_dependencies(self, deps): + """ + Set the dependencies + """ + if not self.dependencies: + searcher = etree.ETXPath(self.root) + root = searcher(self._tree)[0] + # add an empty parent element + parent_elem = etree.SubElement(root, self.deps_node[1:]) + parent_elem.text='\n' + else: + self._remove_dependencies() + + for dep in deps: + self._add_dependency(dep) + + dependencies = property(get_dependencies, set_dependencies) + + def write(self, path=None): + if path: + self._path = path + + if not self.path: + raise ValueError("Illegal path name") + tmp, name = tempfile.mkstemp(dir=os.path.dirname(self.path)) + self._tree.write(name) + os.chmod(name, 0o644) + os.rename(name, self.path) + + @classmethod + def read(klass, path, sloppy=False): + parser = etree.XMLParser(recover=True) if sloppy else None + return klass(etree.parse(path, parser=parser), path) + + @classmethod + def parse(klass, xml, sloppy=False): + parser = etree.XMLParser(recover=True) if sloppy else None + return klass(etree.fromstring(xml, parser=parser)) |