Sindbad~EG File Manager
#
from __future__ import print_function
import re
import shutil
import sys
import optparse
import six
import io
from lxml import etree
from os import path
from textwrap import dedent
from javapackages.common.exception import JavaPackagesToolsException
# all macro fuctions that can be called from external world
macros = {}
def annotate(elements):
begin_comment = etree.Comment(' begin of code added by maintainer ')
end_comment = etree.Comment(' end of code added by maintainer ')
if isinstance(elements, etree._Element):
if elements.text and elements.text.strip():
begin_comment.tail = elements.text.strip()
elements.text = ''
elements[:0] = [begin_comment]
elements.append(end_comment)
return elements
return [begin_comment] + elements + [end_comment]
class PomException(JavaPackagesToolsException):
pass
class PomQueryNoMatch(PomException):
pass
class PomQueryAmbigous(PomException):
pass
class PomQueryInvalid(PomException):
pass
def MetaArtifact(specification, attributes=False, namespace=None, **defaults):
parts = specification.split(':')
class Artifact(object):
def __init__(self, **values):
self.values = values
@classmethod
def from_mvn_str(cls, string):
values = {}
for key, value in zip(parts, string.split(':')):
if value:
values[key] = value
return cls(**values)
def update(self, artifact):
for key, value in six.iteritems(artifact.values):
if key not in parts:
raise KeyError(key + ' not defined')
if value:
self[key] = value
def __getitem__(self, key):
return self.values.get(key, '')
def __setitem__(self, key, value):
if key not in parts:
raise KeyError(key + ' not defined')
self.values[key] = value
class NodeArtifact(Artifact):
@classmethod
def from_xml(cls, element):
values = {}
for part in parts:
if namespace:
subelements = element.xpath('ns:' + part,
namespaces={'ns': namespace})
else:
subelements = element.xpath(part)
if subelements:
values[part] = subelements[0].text.strip()
return cls(**values)
def get_xml(self, node='artifact', extra=''):
xml = ['<{0}>'.format(node)]
for key in parts:
value = self.values.get(key, defaults.get(key, ''))
if value:
xml.append("<{0}>{1}</{0}>".format(key, value))
xml.append(extra)
xml.append('</{0}>'.format(node))
return etree.fromstring('\n'.join(xml))
def merge_into_xml(self, element):
for key in parts:
value = self.values.get(key, defaults.get(key, ''))
if value:
child = element.xpath('ns:' + key,
namespaces={'ns': namespace})
if child:
if value == '-':
element.remove(child[0])
else:
child[0].text = value
elif value != '-':
child = element.makeelement(key)
child.text = value
element.append(child)
def get_xpath_condition(self):
expr = "normalize-space(pom:{0})='{1}'"
conditions = []
for key in parts:
value = self.values.get(key, '')
if value and value != '*':
conditions.append(expr.format(key, value))
return ' and '.join(conditions) if conditions else 'true()'
class AttributeArtifact(Artifact):
@classmethod
def from_xml(cls, element):
values = dict([(key, val) for key, val
in six.iteritems(element.attrib) if key in parts])
return cls(**values)
def get_xml(self, node='artifact', extra=''):
xml = []
for key in parts:
value = self.values.get(key, defaults.get(key, ''))
if value:
xml.append('{0}="{1}"'.format(key, value))
return etree.fromstring('<{0} {1}>{2}</{0}>'
.format(node, ' '.join(xml), extra))
def merge_into_xml(self, element):
for key in parts:
value = self.values.get(key, defaults.get(key, ''))
if value == '-':
del element.attrib[key]
elif value:
element.attrib[key] = value
def get_xpath_condition(self):
expr = "@{0}='{1}'"
conditions = []
for key in parts:
value = self.values.get(key, '')
if value and value != '*':
conditions.append(expr.format(key, value))
return ' and '.join(conditions) if conditions else 'true()'
return AttributeArtifact if attributes else NodeArtifact
def find_xml(xmlpath):
if path.isfile(xmlpath):
return xmlpath
if path.isdir(xmlpath):
subxmlpath = path.join(xmlpath, 'pom.xml')
if path.isfile(subxmlpath):
return subxmlpath
subxmlpath = path.join(xmlpath, 'ivy.xml')
if path.isfile(subxmlpath):
return subxmlpath
raise PomException("Couldn't locate XML file using pattern '{0}'"\
.format(xmlpath))
def submodule_info(module_xml, module_path):
module_xpath = '/pom:project/pom:modules/pom:module |'\
'/pom:project/pom:profile/pom:modules/pom:module'
submodules = module_xml.xpath(module_xpath, namespaces=Pom.NSMAP)
submodules = [node.text.strip() for node in submodules]
if module_path:
submod_paths = [path.join(path.dirname(module_path), submod)
for submod in submodules]
else:
submod_paths = list(submodules)
return submodules, submod_paths
def find_xml_recursive(module_path):
try:
module_path = find_xml(module_path)
module_xml = etree.parse(module_path)
_, submod_paths = submodule_info(module_xml, module_path)
found = [module_path]
for submod_path in submod_paths:
found += find_xml_recursive(submod_path)
return found
except IOError:
raise PomException("Cannot read POM file '{0}'".format(module_path))
def get_indent(node):
if node is None or not node.text:
return ''
text = node.text.split('\n')[-1]
return re.sub(r'\S.*', '', text)
def print_usage(function):
print("Usage: %{name} {doc}".format(name=function.__name__, doc=function.__doc__),
file=sys.stderr)
def parse_args(function, args, nargs, last_xml_string=False):
option_parser = optparse.OptionParser()
option_parser.add_option('-r', '--recursive', action="store_true")
option_parser.add_option('-f', '--force', action="store_true")
options, arguments = option_parser.parse_args(list(args))
if len(arguments) < nargs:
raise PomException('Too few arguments given to {0}'.format(function.__name__))
fnargs = arguments[:nargs]
fnkwargs = {}
poms = arguments[nargs:]
if last_xml_string and poms:
last = poms[-1]
if '<' in last:
del poms[-1]
fnkwargs['xml_string'] = last
if not poms:
poms = ['.']
return options, fnargs, fnkwargs, poms
class XmlFile(object):
default_name = None
NSMAP = {}
XMLNS = ''
def __init__(self, xmlpath):
self.xmlpath = xmlpath
encoding = etree.parse(xmlpath).docinfo.encoding
with io.open(self.xmlpath, encoding=encoding) as raw_xml:
raw_xml = raw_xml.read()
raw_xml = self._preprocess_raw(raw_xml)
self.xml_declaration = re.match(r'\<\?xml\s[^?]*\?\>', raw_xml)
tmpfile = self.xmlpath + '.tmp'
with io.open(tmpfile, 'w', encoding=encoding) as prepared:
prepared.write(raw_xml)
self.document = etree.parse(tmpfile)
self.tab = get_indent(self.root)
def _preprocess_raw(self, raw_xml):
return raw_xml
@property
def root(self):
return self.document.getroot()
def write(self, filename):
info = self.document.docinfo
self.document.write(filename, encoding=info.encoding,
xml_declaration=bool(self.xml_declaration))
with io.open(filename, 'ab') as xmlfile:
xmlfile.write(b'\n')
def patch(self, function, fnargs, fnkwargs):
xmldir = path.dirname(self.xmlpath)
xmlfile = path.basename(self.xmlpath)
self.write(path.join(xmldir, xmlfile + '.tmp'))
function(*fnargs, **fnkwargs)
origfile = path.join(xmldir, xmlfile + '.orig')
shutil.move(self.xmlpath, origfile)
self.write(self.xmlpath)
def inject_xml(self, parent, content):
items = len(content)
parent[:0] = content
self.reformat(parent, parent[:items])
def replace_xml(self, replaced, content):
parent = replaced.getparent()
if not isinstance(replaced, etree._Element):
parent.extend(content)
if content.tag is not etree.Comment:
parent.text = content.text
return
idx = parent.index(replaced)
items = len(content)
del parent[idx]
for i, element in enumerate(content):
parent.insert(idx + i, element)
self.reformat(parent, parent[idx: idx + items])
def replace_xml_content(self, parent, content):
if hasattr(parent, 'is_attribute'):
if parent.is_attribute:
parent.getparent().attrib[parent.attrname] = content.text
return
parent[:] = content
parent.text = content.text
self.reformat(parent, parent)
def reformat(self, parent_node, elements):
level = 0
element = parent_node
while element is not None:
level += 1
element = element.getparent()
base = self.tab * level
def prettify_node(node, parent, indent):
if parent[0] == node:
text = parent.text or ''
parent.text = text.strip() + '\n' + indent
tail = node.tail or ''
if parent[-1] == node:
node.tail = tail.strip() + '\n' + indent[:len(indent) - len(self.tab)]
else:
node.tail = tail.strip() + '\n' + indent
for child in node:
prettify_node(child, node, indent + self.tab)
for element in elements:
prettify_node(element, parent_node, base)
def xpath_query_element(self, query):
query_result = self.xpath_query(query)
if len(query_result) > 1:
raise PomQueryAmbigous("XPath query '{0}' matched more than one nodes."\
.format(query))
return query_result[0]
def xpath_query(self, query, boolean=False):
if not query.startswith('/'):
query = '//' + query
if boolean:
query = 'boolean({0})'.format(query)
try:
nsmap = dict(self.NSMAP)
nsmap.update(self.root.nsmap)
if None in nsmap:
nsmap['default'] = nsmap[None]
del nsmap[None]
query_result = self.root.xpath(query, namespaces=nsmap)
except etree.XPathEvalError as error:
raise PomQueryInvalid("XPath query '{0}': {1}.".format(query,
error))
if not boolean:
if len(query_result) == 0:
raise PomQueryNoMatch(dedent("""\
XPath query '{0}' didn't match any node.
(Did you forget to specify 'pom:' namespace?)""").format(query))
for i, element in enumerate(query_result):
if hasattr(element, 'is_attribute') and element.is_attribute:
if not hasattr(element, 'attrname'):
element.attrname = self.root.xpath('name({q}[{i1}])'.format(q=query, i1=i + 1),
namespaces=nsmap)
return query_result
def subtree_from_string(self, xml_string):
document = "<root {ns}>{0}</root>".format(xml_string, ns=self.XMLNS)
try:
tree = etree.fromstring(document)
except etree.XMLSyntaxError as error:
raise PomException("Syntax error in injected XML: {0}.".format(error))
return tree
def make_path(self, node, elements):
if elements:
elem = elements[0]
children = node.xpath(elem, namespaces=self.NSMAP)
if not children:
name = elements[0]
for ns, url in six.iteritems(self.NSMAP):
ns_token = ns + ':'
url_token = '{' + url + '}'
name = name.replace(ns_token, url_token)
child = etree.Element(name)
node.insert(0, child)
node.insert(0, etree.Comment(" section added by maintainer "))
self.reformat(node, node[:2])
else:
child = children[0]
return self.make_path(child, elements[1:])
return node
def inject_artifact(self, where, node_name, artifact, aux_content=''):
path_parts = [part for part in where.split('/') if part]
parent = self.make_path(self.root, path_parts)
content = annotate([artifact.get_xml(node_name, aux_content)])
self.inject_xml(parent, content)
class Pom(XmlFile):
default_name = 'pom.xml'
NSMAP = {'pom': 'http://maven.apache.org/POM/4.0.0',
'xsi': 'http://www.w3.org/2001/XMLSchema-instance'}
NS = '{' + NSMAP['pom'] + '}'
XMLNS = ('xmlns="http://maven.apache.org/POM/4.0.0" '
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
'xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 '
'http://maven.apache.org/xsd/maven-4.0.0.xsd"')
DEPENDENCY_NODE = 'pom:dependency'
DEPENDENCIES_NODE = 'pom:dependencies'
def __init__(self, pompath):
super(Pom, self).__init__(pompath)
def _preprocess_raw(self, raw_xml):
return re.sub(r'\<\s*project\s*\>',
u'<project {ns}>'.format(ns=self.XMLNS), raw_xml)
@classmethod
def create_artifact(cls, *args, **kwargs):
plugin = kwargs.get('plugin', False)
if 'plugin' in kwargs:
del kwargs['plugin']
if plugin:
pom_plugin_spec = 'groupId:artifactId:version'
ArtifactClass = MetaArtifact(pom_plugin_spec,
namespace=cls.NSMAP['pom'],
version='any',
groupId='org.apache.maven.plugins')
else:
pom_dependency_spec = 'groupId:artifactId:version:scope:classifier'
ArtifactClass = MetaArtifact(pom_dependency_spec,
namespace=cls.NSMAP['pom'],
version='any')
return ArtifactClass(*args, **kwargs)
class Ivy(XmlFile):
DEPENDENCY_NODE = 'dependency'
DEPENDENCIES_NODE = 'dependencies'
def __init__(self, pompath):
super(Ivy, self).__init__(pompath)
@classmethod
def create_artifact(cls, *args, **kwargs):
if 'plugin' in kwargs:
del kwargs['plugin']
ivy_dependency_spec = 'org:name:rev:conf:transitive'
IvyDependency = MetaArtifact(ivy_dependency_spec, attributes=True)
return IvyDependency(*args, **kwargs)
def create_xml_object(filepath):
tree = etree.parse(filepath)
root = tree.getroot()
pom_detect = '/pom:project/pom:modelVersion|/project/modelVersion'
if root.xpath(pom_detect, namespaces=Pom.NSMAP):
return Pom(filepath)
if root.tag == 'ivy-module':
return Ivy(filepath)
return XmlFile(filepath)
def macro(types=(XmlFile,), nargs=1, last_xml_string=False):
def decorator(function):
def decorated(*args):
try:
xmlpath = None
options, fnargs, fnkwargs, xmls = parse_args(
function, args, nargs, last_xml_string
)
stored_exception = None
for xmlspec in xmls:
if options.recursive:
xmlpaths = find_xml_recursive(xmlspec)
else:
xmlpaths = [find_xml(xmlspec)]
matches = 0
for xmlpath in xmlpaths:
try:
xml = create_xml_object(xmlpath)
if not any([isinstance(xml, allowed) for allowed in types]):
raise PomException('Operation not supported on '
'given file type.')
fnkwargs['pom'] = xml
xml.patch(function, fnargs, fnkwargs)
matches += 1
except PomQueryNoMatch as exception:
stored_exception = exception
if matches == 0 and not options.force:
# pylint: disable=E0702
raise stored_exception
except (PomException, etree.XMLSyntaxError, IOError) as exception:
if xmlpath:
print("Error in processing {0}".format(xmlpath), file=sys.stderr)
print(exception, file=sys.stderr)
print_usage(function)
sys.exit(3)
macros[function.__name__] = decorated
return decorated
return decorator
def disable_module(pom, module):
xpath = "//pom:module[normalize-space(text())='{0}']".format(module)
elements = pom.xpath_query(xpath)
for element in elements:
pom.replace_xml(element, etree.Comment(" module removed by maintainer: {0} ".format(module)))
@macro(nargs=2)
def pom_xpath_inject(where, xml_string, pom=None):
"""<XPath> <XML code> [POM location]"""
for element in pom.xpath_query(where):
pom.inject_xml(element, annotate(pom.subtree_from_string(xml_string)))
@macro(nargs=2)
def pom_xpath_replace(where, xml_string, pom=None):
"""<XPath> <XML code> [POM location]"""
for element in pom.xpath_query(where):
pom.replace_xml(element, annotate(pom.subtree_from_string(xml_string)))
@macro(nargs=1)
def pom_xpath_remove(where, pom=None):
"""<XPath> [POM location]"""
for element in pom.xpath_query(where):
if hasattr(element, 'is_attribute') and element.is_attribute:
del element.getparent().attrib[element.attrname]
else:
pom.replace_xml(element, etree.Comment(" element removed by maintainer: {0} ".format(where)))
@macro(nargs=2)
def pom_xpath_set(where, content, pom=None):
"""<XPath> <new contents> [POM location]"""
for element in pom.xpath_query(where):
pom.replace_xml_content(element, pom.subtree_from_string(content))
@macro(types=(Pom,), nargs=1)
def pom_xpath_disable(when, pom=None):
"""<XPath> [POM location]"""
to_disable = []
def disable_recursive(pom):
if pom.xpath_query(when, boolean=True):
return True
for submodule, submod_path in zip(*submodule_info(pom.root, pom.xmlpath)):
realpath = find_xml(submod_path)
subpom = Pom(realpath)
if disable_recursive(subpom):
to_disable.append((pom, submodule))
if disable_recursive(pom):
raise PomException("Main POM satisfies the condition")
if not to_disable:
raise PomQueryNoMatch("Condition didn't match any module")
for pom, module in to_disable:
pom.patch(disable_module, [pom, module], {})
@macro(types=(Pom, Ivy), nargs=1)
def pom_remove_dep(dep, pom=None):
"""[groupId]:[artifactId] [POM location]"""
try:
artifact = pom.create_artifact().from_mvn_str(dep)
xpath = "//{0}[{1}]".format(pom.DEPENDENCY_NODE, artifact.get_xpath_condition())
elements = pom.xpath_query(xpath)
for element in elements:
pom.replace_xml(element, etree.Comment(" dependency removed by maintainer: {0} ".format(dep)))
except PomQueryNoMatch:
raise PomQueryNoMatch("Dependency '{0}' not found.".format(dep))
@macro(types=(Pom,), nargs=1)
def pom_remove_plugin(plugin, pom=None):
"""[groupId]:[artifactId] [POM location]"""
try:
artifact = pom.create_artifact(plugin=True).from_mvn_str(plugin)
xpath = "//pom:plugin[{0}]".format(artifact.get_xpath_condition())
elements = pom.xpath_query(xpath)
for element in elements:
pom.replace_xml(element, etree.Comment(" plugin removed by maintainer: {0} ".format(plugin)))
except PomQueryNoMatch:
raise PomQueryNoMatch("Plugin '{0}' not found.".format(plugin))
@macro(types=(Pom,), nargs=1)
def pom_disable_module(module, pom=None):
"""<module name> [POM location]"""
try:
disable_module(pom, module)
except PomQueryNoMatch:
raise PomQueryNoMatch("Module '{0}' not found.".format(module))
@macro(types=(Pom,), nargs=1)
def pom_add_parent(parent, pom=None):
"""groupId:artifactId[:version] [POM location]"""
if pom.xpath_query('/pom:parent', boolean=True):
raise PomException("POM already has a parent.")
artifact = pom.create_artifact().from_mvn_str(parent)
pom.inject_artifact('', 'parent', artifact)
@macro(types=(Pom,), nargs=0)
def pom_remove_parent(pom=None):
"""[POM location]"""
try:
pom.replace_xml(pom.xpath_query_element("/pom:project/pom:parent"),
etree.Comment(" parent POM reference removed by maintainer "))
except PomQueryNoMatch:
raise PomQueryNoMatch("POM doesn't specify parent.")
@macro(types=(Pom,), nargs=1)
def pom_set_parent(parent, pom=None):
"""groupId:artifactId[:version] [POM location]"""
try:
artifact = pom.create_artifact().from_mvn_str(parent)
element = pom.xpath_query_element("/pom:project/pom:parent")
pom.replace_xml_content(element, artifact.get_xml())
except PomQueryNoMatch:
raise PomQueryNoMatch("POM doesn't specify parent.")
@macro(types=(Pom, Ivy), nargs=1, last_xml_string=True)
def pom_add_dep(dep, pom=None, xml_string=''):
"""groupId:artifactId[:version[:scope]] [POM location] [extra XML]"""
artifact = pom.create_artifact().from_mvn_str(dep)
pom.inject_artifact(pom.DEPENDENCIES_NODE, 'dependency', artifact,
xml_string)
@macro(types=(Pom,), nargs=1, last_xml_string=True)
def pom_add_dep_mgmt(dep, pom=None, xml_string=''):
"""groupId:artifactId[:version[:scope]] [POM location] [extra XML]"""
artifact = pom.create_artifact().from_mvn_str(dep)
pom.inject_artifact('pom:dependencyManagement/pom:dependencies',
'dependency', artifact, xml_string)
@macro(types=(Pom,), nargs=1, last_xml_string=True)
def pom_add_plugin(plugin, pom=None, xml_string=''):
"""groupId:artifactId[:version] [POM location] [extra XML]"""
artifact = pom.create_artifact(plugin=True).from_mvn_str(plugin)
pom.inject_artifact('pom:build/pom:plugins', 'plugin', artifact,
xml_string)
@macro(nargs=2, last_xml_string=True)
def pom_change_dep(old, new, pom=None, xml_string=''):
"""groupId:artifactId[:version] groupId:artifactId[:version[:scope]]
[POM location] [extra XML]"""
try:
old_artifact = pom.create_artifact().from_mvn_str(old)
xpath = "//{0}[{1}]".format(pom.DEPENDENCY_NODE, old_artifact.get_xpath_condition())
elements = pom.xpath_query(xpath)
for element in elements:
new_artifact = pom.create_artifact().from_xml(element)
new_artifact.update(pom.create_artifact().from_mvn_str(new))
new_artifact.merge_into_xml(element)
except PomQueryNoMatch:
raise PomQueryNoMatch("Dependency '{0}' not found.".format(old))
if __name__ == '__main__':
if len(sys.argv) <= 1:
print("Usage:\n\t{0} command {{arguments}}".format(sys.argv[0]),
file=sys.stderr)
sys.exit(1)
try:
macros[sys.argv[1]](*sys.argv[2:])
except JavaPackagesToolsException as e:
sys.exit(e)
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists