Semantic versioning¶
This notebook takes a look at the grammar and regular expression that are used by Semantic Versioning 2.0.0 to define its precise format for valid version numbers.
References¶
[1]:
import re
import alogos as al
Define the grammar¶
[2]:
bnf = """
<valid semver> ::= <version core>
| <version core> "-" <pre-release>
| <version core> "+" <build>
| <version core> "-" <pre-release> "+" <build>
<version core> ::= <major> "." <minor> "." <patch>
<major> ::= <numeric identifier>
<minor> ::= <numeric identifier>
<patch> ::= <numeric identifier>
<pre-release> ::= <dot-separated pre-release identifiers>
<dot-separated pre-release identifiers> ::= <pre-release identifier>
| <pre-release identifier> "." <dot-separated pre-release identifiers>
<build> ::= <dot-separated build identifiers>
<dot-separated build identifiers> ::= <build identifier>
| <build identifier> "." <dot-separated build identifiers>
<pre-release identifier> ::= <alphanumeric identifier>
| <numeric identifier>
<build identifier> ::= <alphanumeric identifier>
| <digits>
<alphanumeric identifier> ::= <non-digit>
| <non-digit> <identifier characters>
| <identifier characters> <non-digit>
| <identifier characters> <non-digit> <identifier characters>
<numeric identifier> ::= "0"
| <positive digit>
| <positive digit> <digits>
<identifier characters> ::= <identifier character>
| <identifier character> <identifier characters>
<identifier character> ::= <digit>
| <non-digit>
<non-digit> ::= <letter>
| "-"
<digits> ::= <digit>
| <digit> <digits>
<digit> ::= "0"
| <positive digit>
<positive digit> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
<letter> ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J"
| "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T"
| "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d"
| "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n"
| "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x"
| "y" | "z"
"""
grammar = al.Grammar(bnf_text=bnf, start_terminal_symbol='"', end_terminal_symbol='"')
Generate random strings¶
[3]:
for _ in range(20):
print(grammar.generate_string())
2.4.3-100+-1.0
1.40.3-917.30.8300.f80n0.5.-q+0.090
7.3.90064
8.0.649
50.0.0+p.t0C-.0
0.0.0
6.22.2+8.9.079
2.23.0-0+300.h80
7.0.0-6.-m7.9530+B-0.-.00
0.0.3-3+006
4.6.0
5.255.5+0
0.18.5-9.-W6.8.2
0.0.3-2+La-0.0980.0-0.1.0.Pl.00
0.0.2+16605
0.4.88-256+0C
5.76080.0-jcB.0
992.9.6
0.8.688+0-p.07
52.450.9-T-A0
Check if strings generated with the grammar are recognized as valid by the regular expression
[4]:
regex_pattern = (
'^(?P<major>0|[1-9]\d*)\.'
'(?P<minor>0|[1-9]\d*)\.'
'(?P<patch>0|[1-9]\d*)'
'(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?'
'(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
)
n = 2000
for _ in range(n):
random_string = grammar.generate_string()
match = re.match(regex_pattern, random_string)
if not match:
raise Exception('String "{}" was not recognized by the regular expression.'.format(random_string))
else:
print('All {} strings that were randomly generated with the grammar were '
'recognized by the regular expression.'.format(n))
All 2000 strings that were randomly generated with the grammar were recognized by the regular expression.
Parse given strings¶
[5]:
grammar.parse_string('1.0.0')
[5]:
[6]:
grammar.parse_string('11.3.17-rc0+nightly')
[6]: