Регулярное выражение Python для анализа строк версии и просмотра фиксированной ширины

Я пытаюсь написать библиотеку Python для анализа строк формата нашей версии. (Упрощенный) формат строки версии выглядит следующим образом:

<product>-<x>.<y>.<z>[-alpha|beta|rc[.<n>]][.<extra>]][.centos|redhat|win][.snb|ivb]

Это:

  • продукт, т.е. foo
  • числовая версия, например: 0.1.0
  • [необязательно] предварительная информация, например: beta, rc.1, alpha.extrainfo
  • [необязательно] операционная система, например: centos
  • [необязательно] платформа, например: snb, ivb

Таким образом, следующие строки являются допустимыми версиями:

1) foo-1.2.3
2) foo-2.3.4-alpha
3) foo-3.4.5-rc.2
4) foo-4.5.6-rc.2.extra
5) withos-5.6.7.centos
6) osandextra-7.8.9-rc.extra.redhat
7) all-4.4.4-rc.1.extra.centos.ivb

Для всех этих примеров отлично работает следующее регулярное выражение:

^(?P<prod>\w+)-(?P<maj>\d).(?P<min>\d).(?P<bug>\d)(?:-(?P<pre>alpha|beta|rc)(?:\.(?P<pre_n>\d))?(?:\.(?P<pre_x>\w+))?)?(?:\.(?P<os>centos|redhat|win))?(?:\.(?P<plat>snb|ivb))?$

Но проблема возникает в версиях этого типа (без «дополнительной» предварительной информации, но с ОС и/или платформой):

8) issue-0.1.0-beta.redhat.snb

С приведенным выше регулярным выражением для строки № 8 redhat выбирается в предварительной информации pre_x вместо группы os.

Я попытался использовать look-behind, чтобы избежать выбора строк ОС или платформы в pre_x:

...(?:\.(?P<pre_x>\w+))?(?<!centos|redhat|win|ivb|snb))...

То есть:

^(?P<prod>\w+)-(?P<maj>\d).(?P<min>\d).(?P<bug>\d)(?:-(?P<pre>alpha|beta|rc)(?:\.(?P<pre_n>\d))?(?:\.(?P<pre_x>\w+))?(?<!centos|redhat|win|ivb|snb))?(?:\.(?P<os>centos|redhat|win))?(?:\.(?P<plat>snb|ivb))?$

Это могло бы работать нормально, если бы стандартный модуль Python re мог принимать переменную ширину просмотра. Я предпочел бы придерживаться стандартного модуля, а не использовать regex, так как моя библиотека довольно вероятно, будет распространяться на большое количество машин, где я хочу ограничить зависимости.

Я также рассмотрел похожие вопросы: это , это и это не применимо.

Любые идеи о том, как этого добиться?

Моя ссылка на regex101: https://regex101.com/r/bH0qI7/3

[Для тех, кто заинтересован, это полное регулярное выражение, которое я фактически использую: https://regex101.com/r/lX7nI6/2]


person Xabs    schedule 27.05.2015    source источник
comment
Может ли помочь преобразование вашего регулярного выражения для использования просмотра вперед?   -  person rr-    schedule 27.05.2015
comment
Да, я не против использования опережающего просмотра, я просто хочу придерживаться регулярных выражений и стандартного модуля re. TBH, я потерял преобразование этого в предвидение.   -  person Xabs    schedule 27.05.2015


Ответы (2)


Вам нужно использовать отрицательное предварительное утверждение, чтобы (?P<pre_x>\w+) соответствовало любому, кроме centos или redhat.

^(?P<prod>\w+)-(?P<maj>\d)\.(?P<min>\d)\.(?P<bug>\d)(?:-(?P<pre>alpha|beta|rc)(?:\.(?P<pre_n>\d))?(?:\.(?:(?!centos|redhat)\w)+)?)?(?:\.(?P<os>centos|redhat))?(?:\.(?P<plat>snb|ivb))?$

ДЕМО

person Avinash Raj    schedule 27.05.2015
comment
Это было просто!! Большое спасибо - person Xabs; 27.05.2015
comment
Придирка @Avinash Raj? разве это не должно быть \d+ в maj/min/bug? Я перешел по ссылке в демо, например, foo-3.4.15-rc.2 Это не соответствует. (с выпуском раньше и быстрее! ;-) не так уж сложно иметь номера версий, которые могут состоять из двух цифр (если не 3? :-) ). - person gabhijit; 27.05.2015

На самом деле я бы не стал использовать регулярное выражение, так как оно уже выглядит довольно ужасно, а вы сказали нам, что оно только упрощено. Гораздо читабельнее разобрать его вручную:

def extract(text):
    parts = text.split('-')
    ret = {}
    ret['name'] = parts.pop(0)
    ret['version'] = parts.pop(0).split('.')

    if len(parts) > 0:
        rest_parts = parts.pop(0).split('.')
        if rest_parts[-1] in ['snb', 'ivb']:
            ret['platform'] = rest_parts.pop(-1)
        if rest_parts[-1] in ['redhat', 'centos', 'win']:
            ret['os'] = rest_parts.pop(-1)
        ret['extra'] = rest_parts

    return ret

tests = \
[
    'foo-1.2.3',
    'foo-2.3.4-alpha',
    'foo-3.4.5-rc.2',
    'foo-4.5.6-rc.2.extra',
    'withos-5.6.7.centos',
    'osandextra-7.8.9-rc.extra.redhat',
    'all-4.4.4-rc.1.extra.centos.ivb',
    'issue-0.1.0-beta.redhat.snb',
]

for test in tests:
    print(test, extract(test))

Результат:

('foo-1.2.3', {'version': ['1', '2', '3'], 'name': 'foo'})
('foo-2.3.4-alpha', {'version': ['2', '3', '4'], 'name': 'foo', 'extra': ['alpha']})
('foo-3.4.5-rc.2', {'version': ['3', '4', '5'], 'name': 'foo', 'extra': ['rc', '2']})
('foo-4.5.6-rc.2.extra', {'version': ['4', '5', '6'], 'name': 'foo', 'extra': ['rc', '2', 'extra']})
('withos-5.6.7.centos', {'version': ['5', '6', '7', 'centos'], 'name': 'withos'})
('osandextra-7.8.9-rc.extra.redhat', {'version': ['7', '8', '9'], 'os': 'redhat', 'name': 'osandextra', 'extra': ['rc', 'extra']})
('all-4.4.4-rc.1.extra.centos.ivb', {'platform': 'ivb', 'version': ['4', '4', '4'], 'os': 'centos', 'name': 'all', 'extra': ['rc', '1', 'extra']})
('issue-0.1.0-beta.redhat.snb', {'platform': 'snb', 'version': ['0', '1', '0'], 'os': 'redhat', 'name': 'issue', 'extra': ['beta']})
person rr-    schedule 27.05.2015
comment
Спасибо, это действительно выглядит намного чище, но если вы начнете добавлять больше сложности, это довольно быстро станет довольно грязным: то есть: расширить, чтобы обеспечить более гибкий формат версии, такой как foo_1.2.3, foo1.2.3, foo.1.2.3, или даже отсутствующее исправление: foo-1.2 (=foo-1.2.0) ... Продолжайте делать это для каждого токена, и вы получите массивный фрагмент кода, еще более сложный для отладки, чем регулярное выражение - person Xabs; 27.05.2015