///|
/// The value parser of the version of `update`: `semver::Version::from_str`
/// (semver 1.0.25), with its error messages.
fn parse_semver(v : String) -> Unit raise CliError {
parse_semver_version(v) catch {
@kit.StrError(msg) =>
raise Invalid("invalid value '\{v}' for '[VERSION]': \{msg}")
}
}
///|
/// The semver crate's `Position` display names.
fn semver_position(pos : Int32) -> String {
match pos {
0 => "major version number"
1 => "minor version number"
2 => "patch version number"
3 => "pre-release identifier"
_ => "build metadata"
}
}
///|
/// The semver crate's `QuotedChar` (Rust's `Debug` for a `char`).
fn semver_quoted(c : Char) -> String {
match c {
'\u{0}' => "'\\0'"
'\'' => "'\\''"
'\\' => "'\\\\'"
'\n' => "'\\n'"
'\r' => "'\\r'"
'\t' => "'\\t'"
_ => "'\{c}'"
}
}
///|
/// `semver::Version::from_str` (only the validation).
fn parse_semver_version(text : String) -> Unit raise @kit.StrError {
if text == "" {
raise @kit.StrError("empty string, expected a semver version")
}
let chars = text.to_array()
let mut i = 0
let numeric = (pos : Int32) => {
let start = i
let mut value = 0UL
while i < chars.length() && chars[i] >= '0' && chars[i] <= '9' {
if value == 0UL && i > start {
raise @kit.StrError("invalid leading zero in \{semver_position(pos)}")
}
let digit = (chars[i].to_int() - '0'.to_int()).to_uint64()
if value > (18446744073709551615UL - digit) / 10UL {
raise @kit.StrError("value of \{semver_position(pos)} exceeds u64::MAX")
}
value = value * 10UL + digit
i += 1
}
if i == start {
if i < chars.length() {
raise @kit.StrError(
"unexpected character \{semver_quoted(chars[i])} while parsing \{semver_position(pos)}",
)
}
raise @kit.StrError(
"unexpected end of input while parsing \{semver_position(pos)}",
)
}
}
let dot = (pos : Int32) => {
if i < chars.length() && chars[i] == '.' {
i += 1
} else if i < chars.length() {
raise @kit.StrError(
"unexpected character \{semver_quoted(chars[i])} after \{semver_position(pos)}",
)
} else {
raise @kit.StrError(
"unexpected end of input while parsing \{semver_position(pos)}",
)
}
}
// `identifier`: returns whether it is empty.
let identifier = (pos : Int32) => {
let begin = i
let mut segment_start = i
let mut has_nondigit = false
while true {
let c = if i < chars.length() { Some(chars[i]) } else { None }
match c {
Some(c) if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '-' => {
has_nondigit = true
i += 1
}
Some(c) if c >= '0' && c <= '9' => i += 1
boundary => {
let segment_len = i - segment_start
if segment_len == 0 {
if i == begin && boundary != Some('.') {
return true
}
raise @kit.StrError(
"empty identifier segment in \{semver_position(pos)}",
)
}
if pos == 3 &&
segment_len > 1 &&
!has_nondigit &&
chars[segment_start] == '0' {
raise @kit.StrError(
"invalid leading zero in \{semver_position(pos)}",
)
}
if boundary == Some('.') {
i += 1
segment_start = i
has_nondigit = false
} else {
return false
}
}
}
}
false
}
numeric(0)
dot(0)
numeric(1)
dot(1)
numeric(2)
let mut pos = 2
if i < chars.length() && chars[i] == '-' {
i += 1
pos = 3
if identifier(3) {
raise @kit.StrError("empty identifier segment in \{semver_position(3)}")
}
}
if i < chars.length() && chars[i] == '+' {
i += 1
pos = 4
if identifier(4) {
raise @kit.StrError("empty identifier segment in \{semver_position(4)}")
}
}
if i < chars.length() {
raise @kit.StrError(
"unexpected character \{semver_quoted(chars[i])} after \{semver_position(pos)}",
)
}
}