aboutsummaryrefslogtreecommitdiff
path: root/pomop/pom.py
diff options
context:
space:
mode:
Diffstat (limited to 'pomop/pom.py')
-rw-r--r--pomop/pom.py298
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))