00001 from xml.dom.minidom import parse
00002 import collections
00003 import operator
00004 import re
00005
00006 DEPEND_ORDERING = ['buildtool_depend', 'depend', 'build_depend', 'build_export_depend',
00007 'run_depend', 'exec_depend', 'test_depend', 'doc_depend']
00008
00009 ORDERING = ['name', 'version', 'description',
00010 ['maintainer', 'license', 'author', 'url']] + DEPEND_ORDERING + ['export']
00011
00012 INDENT_PATTERN = re.compile('\n *')
00013
00014 PEOPLE_TAGS = ['maintainer', 'author']
00015
00016
00017 def get_ordering_index(name, whiny=True):
00018 for i, o in enumerate(ORDERING):
00019 if type(o) == list:
00020 if name in o:
00021 return i
00022 elif name == o:
00023 return i
00024 if name and whiny:
00025 print('\tUnsure of ordering for', name)
00026 return len(ORDERING)
00027
00028
00029 def get_package_tag_index(s, key='<package'):
00030 if key not in s:
00031 return 0
00032 return s.index(key)
00033
00034
00035 def count_trailing_spaces(s):
00036 c = 0
00037 while c < len(s) and s[-c - 1] == ' ':
00038 c += 1
00039 return c
00040
00041
00042 class PackageXML:
00043 def __init__(self, fn):
00044 self.fn = fn
00045 self.tree = parse(fn)
00046 self.root = self.tree.getElementsByTagName('package')[0]
00047 contents = open(fn).read()
00048 self.header = contents[:get_package_tag_index(contents)]
00049 self._name = None
00050 self._format = None
00051 self._std_tab = None
00052 self.changed = False
00053
00054 @property
00055 def name(self):
00056 if self._name is not None:
00057 return self._name
00058 name_tags = self.root.getElementsByTagName('name')
00059 if not name_tags:
00060 return
00061 name_tag = name_tags[0]
00062 self._name = name_tag.childNodes[0].nodeValue
00063 return self._name
00064
00065 @property
00066 def format(self):
00067 if self._format is not None:
00068 return self._format
00069 if not self.root.hasAttribute('format'):
00070 self._format = 1
00071 else:
00072 self._format = int(self.root.attributes['format'].value)
00073 return self._format
00074
00075 @property
00076 def std_tab(self):
00077 if self._std_tab is not None:
00078 return self._std_tab
00079 tab_ct = collections.defaultdict(int)
00080 for c in self.root.childNodes:
00081 if c.nodeType == c.TEXT_NODE:
00082 spaces = count_trailing_spaces(c.data)
00083 tab_ct[spaces] += 1
00084 if len(tab_ct) == 0:
00085 self._std_tab = 4
00086 else:
00087 self._std_tab = max(tab_ct.iteritems(), key=operator.itemgetter(1))[0]
00088 return self._std_tab
00089
00090 def get_packages_by_tag(self, tag):
00091 pkgs = []
00092 for el in self.root.getElementsByTagName(tag):
00093 pkgs.append(el.childNodes[0].nodeValue)
00094 return pkgs
00095
00096 def get_packages(self, mode='build'):
00097 keys = []
00098 if mode == 'build':
00099 keys.append('build_depend')
00100 if self.format == 1 and mode == 'run':
00101 keys.append('run_depend')
00102 if self.format == 2 and mode != 'test':
00103 keys.append('depend')
00104 if mode == 'run':
00105 keys.append('exec_depend')
00106 if mode == 'test':
00107 keys.append('test_depend')
00108 pkgs = []
00109 for key in keys:
00110 pkgs += self.get_packages_by_tag(key)
00111 return set(pkgs)
00112
00113 def get_tab_element(self, tabs=1):
00114 return self.tree.createTextNode('\n' + ' ' * (self.std_tab * tabs))
00115
00116 def get_child_indexes(self):
00117 """
00118 Return a dictionary where the keys are the types of nodes in the xml (build_depend, maintainer, etc)
00119 and the values are arrays marking the range of elements in the xml root that match that tag.
00120
00121 For example, tags[build_depend] = [(5, 9), (11, 50)] means that elements [5, 9) and [11, 50) are
00122 either build_depend elements (or the strings between them)
00123 """
00124 tags = collections.defaultdict(list)
00125 i = 0
00126 current = None
00127 current_start = 0
00128 current_last = 0
00129 while i < len(self.root.childNodes):
00130 child = self.root.childNodes[i]
00131 if child.nodeType == child.TEXT_NODE:
00132 i += 1
00133 continue
00134
00135 name = child.nodeName
00136 if name != current:
00137 if current:
00138 tags[current].append((current_start, current_last))
00139 current_start = i
00140 current = name
00141 current_last = i
00142 i += 1
00143 if current:
00144 tags[current].append((current_start, current_last))
00145 return dict(tags)
00146
00147 def get_insertion_index(self, tag, tag_value=None):
00148 """ Returns the index where to insert a new element with the given tag type.
00149 If there are already elements of that type, then either insert after the last matching element,
00150 or if the list is alphabetized, insert it in the correct place alphabetically using the tag_value.
00151 Otherwise, look at the existing elements, and find ones that are supposed to come the closest
00152 before the given tag, and insert after them. If none found, add at the end.
00153 """
00154 indexes = self.get_child_indexes()
00155 if tag in indexes:
00156 if len(indexes[tag]) == 1 and tag in DEPEND_ORDERING:
00157 start, end = indexes[tag][0]
00158 sub_indexes = []
00159 tag_values = []
00160 my_index = start
00161 value = None
00162 for i in range(start, end):
00163 child = self.root.childNodes[i]
00164 if child.nodeType == child.TEXT_NODE:
00165 continue
00166 sub_indexes.append(i)
00167 value = child.firstChild.data
00168 tag_values.append(value)
00169 if tag_value >= value:
00170 my_index = i
00171 if sorted(tag_values) == tag_values and tag_value <= value:
00172 return my_index
00173 return indexes[tag][-1][1]
00174
00175 max_index = get_ordering_index(tag, whiny=False)
00176 best_tag = None
00177 best_index = None
00178 for tag in indexes:
00179 ni = get_ordering_index(tag, whiny=False)
00180 if ni >= max_index:
00181
00182 continue
00183
00184 if best_tag is None or ni > best_index or indexes[tag][-1] > indexes[best_tag][-1]:
00185 best_tag = tag
00186 best_index = ni
00187
00188 if best_tag is None:
00189 return len(self.root.childNodes)
00190 else:
00191 return indexes[best_tag][-1][1]
00192
00193 def insert_new_tag(self, tag):
00194 if tag.tagName in DEPEND_ORDERING:
00195 value = tag.firstChild.data
00196 else:
00197 value = None
00198
00199 index = self.get_insertion_index(tag.tagName, value)
00200 before = self.root.childNodes[:index + 1]
00201 after = self.root.childNodes[index + 1:]
00202 self.root.childNodes = before + [self.get_tab_element(), tag] + after
00203 self.changed = True
00204
00205 def insert_new_tags(self, tags):
00206 for tag in tags:
00207 self.insert_new_tag(tag)
00208
00209 def insert_new_tag_inside_another(self, parent, tag, depth=2):
00210 all_elements = []
00211 all_elements.append(self.get_tab_element(depth))
00212 all_elements.append(tag)
00213
00214 if len(parent.childNodes) == 0:
00215 parent.childNodes = all_elements + [self.get_tab_element()]
00216 else:
00217 parent.childNodes = parent.childNodes[:-1] + all_elements + parent.childNodes[-1:]
00218 self.changed = True
00219
00220 def insert_new_packages(self, tag, values):
00221 for pkg in sorted(values):
00222 print('\tInserting %s: %s' % (tag, pkg))
00223 node = self.tree.createElement(tag)
00224 node.appendChild(self.tree.createTextNode(pkg))
00225 self.insert_new_tag(node)
00226
00227 def add_packages(self, build_depends, run_depends, test_depends=None, prefer_depend_tag=True):
00228 if self.format == 1:
00229 run_depends.update(build_depends)
00230 existing_build = self.get_packages('build')
00231 existing_run = self.get_packages('run')
00232 build_depends = build_depends - existing_build
00233 run_depends = run_depends - existing_run
00234 if self.format == 1:
00235 self.insert_new_packages('build_depend', build_depends)
00236 self.insert_new_packages('run_depend', run_depends)
00237 elif prefer_depend_tag:
00238 depend_tags = build_depends.union(run_depends)
00239
00240
00241 self.remove_dependencies('build_depend', existing_build.intersection(depend_tags))
00242 self.remove_dependencies('exec_depend', existing_run.intersection(depend_tags))
00243
00244
00245 self.insert_new_packages('depend', depend_tags)
00246 else:
00247 both = build_depends.intersection(run_depends)
00248 self.insert_new_packages('depend', both)
00249 self.insert_new_packages('build_depend', build_depends - both)
00250 self.insert_new_packages('exec_depend', build_depends - both - existing_run)
00251 self.insert_new_packages('exec_depend', run_depends - both)
00252
00253 if test_depends is not None and len(test_depends) > 0:
00254 existing_test = self.get_packages('test')
00255 test_depends = set(test_depends) - existing_build - build_depends - existing_test
00256 self.insert_new_packages('test_depend', test_depends)
00257
00258 def remove_element(self, element):
00259 """ Remove the given element AND the text element before it if it is just an indentation """
00260 parent = element.parentNode
00261 index = parent.childNodes.index(element)
00262 if index > 0:
00263 previous = parent.childNodes[index - 1]
00264 if previous.nodeType == previous.TEXT_NODE and INDENT_PATTERN.match(previous.nodeValue):
00265 parent.removeChild(previous)
00266 parent.removeChild(element)
00267 self.changed = True
00268
00269 def remove_dependencies(self, name, pkgs, quiet=False):
00270 for el in self.root.getElementsByTagName(name):
00271 pkg = el.childNodes[0].nodeValue
00272 if pkg in pkgs:
00273 if not quiet:
00274 print('\tRemoving %s %s' % (name, pkg))
00275 self.remove_element(el)
00276
00277 def get_elements_by_tags(self, tags):
00278 elements = []
00279 for tag in tags:
00280 elements += self.root.getElementsByTagName(tag)
00281 return elements
00282
00283 def get_people(self):
00284 people = []
00285 for el in self.get_elements_by_tags(PEOPLE_TAGS):
00286 name = el.childNodes[0].nodeValue
00287 email = el.getAttribute('email')
00288 people.append((name, email))
00289 return people
00290
00291 def update_people(self, target_name, target_email=None, search_name=None, search_email=None):
00292 for el in self.get_elements_by_tags(PEOPLE_TAGS):
00293 name = el.childNodes[0].nodeValue
00294 email = el.getAttribute('email') if el.hasAttribute('email') else ''
00295 if (search_name is None or name == search_name) and (search_email is None or email == search_email):
00296 el.childNodes[0].nodeValue = target_name
00297 if target_email:
00298 el.setAttribute('email', target_email)
00299 print('\tReplacing %s %s/%s with %s/%s' % (el.nodeName, name, email, target_name, target_email))
00300 self.changed = True
00301
00302 def get_license_element(self):
00303 els = self.root.getElementsByTagName('license')
00304 if len(els) == 0:
00305 return None
00306 return els[0]
00307
00308 def get_license(self):
00309 el = self.get_license_element()
00310 return el.childNodes[0].nodeValue
00311
00312 def set_license(self, license):
00313 el = self.get_license_element()
00314 if license != el.childNodes[0].nodeValue:
00315 el.childNodes[0].nodeValue = license
00316 self.changed = True
00317
00318 def is_metapackage(self):
00319 for node in self.root.getElementsByTagName('export'):
00320 for child in node.childNodes:
00321 if child.nodeType == child.ELEMENT_NODE:
00322 if child.nodeName == 'metapackage':
00323 return True
00324 return False
00325
00326 def get_plugin_xmls(self):
00327 """ Returns a mapping from the package name to a list of the relative path(s) for the plugin xml(s) """
00328 xmls = collections.defaultdict(list)
00329 export = self.root.getElementsByTagName('export')
00330 if len(export) == 0:
00331 return xmls
00332 for ex in export:
00333 for n in ex.childNodes:
00334 if n.nodeType == self.root.ELEMENT_NODE:
00335 plugin = n.getAttribute('plugin').replace('${prefix}/', '')
00336 xmls[n.nodeName].append(plugin)
00337 return xmls
00338
00339 def add_plugin_export(self, pkg_name, xml_path):
00340 """ Adds the plugin configuration if not found. Adds export tag as needed.
00341 Returns the export tag it was added to."""
00342 export_tags = self.root.getElementsByTagName('export')
00343 if len(export_tags) == 0:
00344 export_tag = self.tree.createElement('export')
00345 self.insert_new_tag(export_tag)
00346 export_tags = [export_tag]
00347
00348 attr = '${prefix}/' + xml_path
00349 for ex_tag in export_tags:
00350 for tag in ex_tag.childNodes:
00351 if tag.nodeName != pkg_name:
00352 continue
00353 plugin = tag.attributes.get('plugin')
00354 if plugin and plugin.value == attr:
00355 return
00356
00357 ex_el = export_tags[0]
00358 pe = self.tree.createElement(pkg_name)
00359 pe.setAttribute('plugin', attr)
00360 self.insert_new_tag_inside_another(ex_el, pe)
00361 return ex_el
00362
00363 def write(self, new_fn=None):
00364 if new_fn is None:
00365 new_fn = self.fn
00366
00367 if new_fn == self.fn and not self.changed:
00368 return
00369
00370 s = self.tree.toxml(self.tree.encoding)
00371 index = get_package_tag_index(s)
00372 s = self.header + s[index:]
00373
00374 with open(new_fn, 'w') as f:
00375 f.write(s.encode('UTF-8'))
00376 f.write('\n')