diff --git a/jenkins_jobs/alphanum.py b/jenkins_jobs/alphanum.py new file mode 100755 index 000000000..481a0a93b --- /dev/null +++ b/jenkins_jobs/alphanum.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# Copyright (C) 2012 OpenStack, LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Sorts using alphanum algorithm which can be explained as: + +Normal sort: ['a', 'a1', 'a10', 'a2'] +Alphanum sort: ['a', 'a1', 'a2', 'a10'] + +It can sortof many kinds of objects, using name attribe if possible, +otherwise it will try to use str(). + +How to use: + +from alphanum import AlphanumSort + +sorted( foo, key=AlphanumSort) + +""" +import re + + +re_chunk = re.compile("([\D]+|[\d]+)") +re_letters = re.compile("\D+") +re_numbers = re.compile("\d+") + + +def getchunk(item): + itemchunk = re_chunk.match(item) + + # Subtract the matched portion from the original string + # if there was a match, otherwise set it to "" + item = (item[itemchunk.end():] if itemchunk else "") + # Don't return the match object, just the text + itemchunk = (itemchunk.group() if itemchunk else "") + + return (itemchunk, item) + + +def cmp(a, b): + return (a > b) - (a < b) + + +def alphanum(a, b): + a = a.name if hasattr(a, 'name') else str(a) + b = b.name if hasattr(b, 'name') else str(b) + + n = 0 + + while (n == 0): + # Get a chunk and the original string with the chunk subtracted + (ac, a) = getchunk(a) + (bc, b) = getchunk(b) + + # Both items contain only letters + if (re_letters.match(ac) and re_letters.match(bc)): + n = cmp(ac, bc) + else: + # Both items contain only numbers + if (re_numbers.match(ac) and re_numbers.match(bc)): + n = cmp(int(ac), int(bc)) + # item has letters and one item has numbers, or one item is empty + else: + n = cmp(ac, bc) + # Prevent deadlocks + if (n == 0): + n = 1 + return n + + +class AlphanumSort(object): + def __init__(self, obj, *args): + self.obj = obj + + def __lt__(self, other): + return alphanum(self.obj, other.obj) < 0 + + def __gt__(self, other): + return alphanum(self.obj, other.obj) > 0 + + def __eq__(self, other): + return alphanum(self.obj, other.obj) == 0 + + def __le__(self, other): + return alphanum(self.obj, other.obj) <= 0 + + def __ge__(self, other): + return alphanum(self.obj, other.obj) >= 0 + + def __ne__(self, other): + return alphanum(self.obj, other.obj) != 0 + + +if __name__ == "__main__": + + mylist = ['a2', 'a1', 'a10', 'a'] + assert sorted(mylist, key=AlphanumSort) == ['a', 'a1', 'a2', 'a10'] diff --git a/jenkins_jobs/builder.py b/jenkins_jobs/builder.py index 1aea1e2f5..238be9327 100644 --- a/jenkins_jobs/builder.py +++ b/jenkins_jobs/builder.py @@ -19,7 +19,6 @@ import errno import hashlib import io import logging -import operator import os from pprint import pformat import re @@ -28,6 +27,7 @@ import xml.etree.ElementTree as XML import jenkins +from jenkins_jobs.alphanum import AlphanumSort from jenkins_jobs.cache import JobCache from jenkins_jobs.constants import MAGIC_MANAGE_STRING from jenkins_jobs.parallel import concurrent @@ -199,7 +199,7 @@ class JenkinsManager(object): orig = time.time() logger.info("Number of jobs generated: %d", len(xml_jobs)) - xml_jobs.sort(key=operator.attrgetter('name')) + xml_jobs.sort(key=AlphanumSort) if (output and not hasattr(output, 'write') and not os.path.isdir(output)): @@ -360,7 +360,7 @@ class JenkinsManager(object): orig = time.time() logger.info("Number of views generated: %d", len(xml_views)) - xml_views.sort(key=operator.attrgetter('name')) + xml_views.sort(key=AlphanumSort) if output: # ensure only wrapped once diff --git a/tests/base.py b/tests/base.py index aa59aac17..becc211c5 100644 --- a/tests/base.py +++ b/tests/base.py @@ -21,7 +21,6 @@ import doctest import io import json import logging -import operator import os import re import xml.etree.ElementTree as XML @@ -36,6 +35,7 @@ from yaml import safe_dump from jenkins_jobs.config import JJBConfig from jenkins_jobs.errors import InvalidAttributeError import jenkins_jobs.local_yaml as yaml +from jenkins_jobs.alphanum import AlphanumSort from jenkins_jobs.modules import project_externaljob from jenkins_jobs.modules import project_flow from jenkins_jobs.modules import project_matrix @@ -224,7 +224,7 @@ class SingleJobTestCase(BaseScenariosTestCase): xml_generator = XmlJobGenerator(registry) xml_jobs = xml_generator.generateXML(job_data_list) - xml_jobs.sort(key=operator.attrgetter('name')) + xml_jobs.sort(key=AlphanumSort) # Prettify generated XML pretty_xml = u"\n".join(job.output().decode('utf-8')