# coding: utf-8
from __future__ import absolute_import, division, print_function
import attr
import six
from arpeggio import NoMatch, PTNodeVisitor, visit_parse_tree, Terminal
from arpeggio.cleanpeg import ParserPEG
from ._helpers import UNSET
from . import _segments as segment
canonical = r'''
version = epoch? release pre? post? dev? local? EOF
epoch = int "!"
release = int (dot int)*
pre = pre_tag pre_post_num
pre_tag = "a" / "b" / "rc"
post = sep post_tag pre_post_num
pre_post_num = int
post_tag = "post"
dev = sep "dev" int
local = "+" r'([a-zA-Z0-9]+([-_\.][a-zA-Z0-9]+)*)'
sep = dot
dot = "."
int = r'[0-9]+'
alpha = r'[a-zA-Z0-9]'
'''
permissive = r'''
version = v? epoch? release pre? (post / post_implicit)? dev? local? EOF
v = "v"
epoch = int "!"
release = int (dot int)*
pre = sep? pre_tag pre_post_num?
pre_tag = "c" / "rc" / "alpha" / "a" / "beta" / "b" / "preview" / "pre"
post = sep? post_tag pre_post_num?
post_implicit = "-" int
post_tag = "post" / "rev" / "r"
pre_post_num = sep? int
dev = sep? "dev" int?
local = "+" r'([a-zA-Z0-9]+([-_\.][a-zA-Z0-9]+)*)'
sep = dot / "-" / "_"
dot = "."
int = r'[0-9]+'
alpha = r'[a-zA-Z0-9]'
'''
_strict_parser = ParserPEG(canonical, root_rule_name='version', skipws=False)
_permissive_parser = ParserPEG(
permissive, root_rule_name='version', skipws=False, ignore_case=True)
def unwrap_token(value):
if isinstance(value, Token):
return value.value
return value
@attr.s
class Token(object):
value = attr.ib()
def make_token(name):
return type(name, (Token,), dict())
Sep = make_token('Sep')
Tag = make_token('Tag')
VToken = make_token('VToken')
class VersionVisitor(PTNodeVisitor):
def visit_version(self, node, children):
return children
def visit_v(self, node, children):
return segment.V()
def visit_epoch(self, node, children):
return segment.Epoch(children[0])
def visit_release(self, node, children):
return segment.Release(tuple(children))
def visit_pre(self, node, children):
sep1 = UNSET
tag = UNSET
sep2 = UNSET
num = UNSET
for token in children:
if sep1 is UNSET:
if isinstance(token, Sep):
sep1 = token.value
elif isinstance(token, Tag):
sep1 = None
tag = token.value
elif tag is UNSET:
tag = token.value
else:
assert isinstance(token, tuple)
assert len(token) == 2
sep2 = token[0].value
num = token[1]
if sep2 is UNSET:
sep2 = None
num = None
assert sep1 is not UNSET
assert tag is not UNSET
assert sep2 is not UNSET
assert num is not UNSET
return segment.Pre(sep1=sep1, tag=tag, sep2=sep2, value=num)
def visit_pre_post_num(self, node, children):
# when "pre_post_num = int", visit_int isn't called for some reason
# I don't understand. Let's call int() manually
if isinstance(node, Terminal):
return Sep(None), int(node.value)
if len(children) == 1:
return Sep(None), children[0]
else:
return tuple(children[:2])
def visit_pre_tag(self, node, children):
return Tag(node.value)
def visit_post(self, node, children):
sep1 = UNSET
tag = UNSET
sep2 = UNSET
num = UNSET
for token in children:
if sep1 is UNSET:
if isinstance(token, Sep):
sep1 = token.value
elif isinstance(token, Tag):
sep1 = None
tag = token.value
elif tag is UNSET:
tag = token.value
else:
assert isinstance(token, tuple)
assert len(token) == 2
sep2 = token[0].value
num = token[1]
if sep2 is UNSET:
sep2 = None
num = None
assert sep1 is not UNSET
assert tag is not UNSET
assert sep2 is not UNSET
assert num is not UNSET
return segment.Post(sep1=sep1, tag=tag, sep2=sep2, value=num)
def visit_post_tag(self, node, children):
return Tag(node.value)
def visit_post_implicit(self, node, children):
return segment.Post(sep1=UNSET, tag=None, sep2=UNSET, value=children[0])
def visit_dev(self, node, children):
num = None
sep = UNSET
for token in children:
if sep is UNSET:
if isinstance(token, Sep):
sep = token.value
else:
num = token
else:
num = token
if sep is UNSET:
sep = None
return segment.Dev(value=num, sep=sep)
def visit_local(self, node, children):
return segment.Local(''.join(children))
def visit_int(self, node, children):
return int(node.value)
def visit_sep(self, node, children):
return Sep(node.value)
[docs]class ParseError(ValueError):
"""Raised when parsing an invalid version number."""
def parse(version, strict=False):
parser = _strict_parser if strict else _permissive_parser
if not strict:
version = version.lower()
try:
tree = parser.parse(version.strip())
except NoMatch as exc:
six.raise_from(ParseError(str(exc)), None)
return visit_parse_tree(tree, VersionVisitor())