From 648f44b4e7b8c7ea89ce68c6c88b5d51ab838b20 Mon Sep 17 00:00:00 2001 From: peefy Date: Mon, 6 Nov 2023 16:23:35 +0800 Subject: [PATCH] feat: add semver module Signed-off-by: peefy --- semver/README.md | 38 +++++++++++ semver/kcl.mod | 4 ++ semver/kcl.mod.lock | 0 semver/main.k | 149 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+) create mode 100644 semver/README.md create mode 100644 semver/kcl.mod create mode 100644 semver/kcl.mod.lock create mode 100644 semver/main.k diff --git a/semver/README.md b/semver/README.md new file mode 100644 index 00000000..bbef3a25 --- /dev/null +++ b/semver/README.md @@ -0,0 +1,38 @@ +## Introduction + +Module `semvar` implements a comparison of semantic version strings. +In this module, semantic version strings must begin with a leading "v", +as in "v1.0.0". + +The general form of a semantic version string accepted by this module is + + vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]] + +where square brackets indicate optional parts of the syntax; +MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros; +PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers +using only alphanumeric characters and hyphens; and +all-numeric PRERELEASE identifiers must not have leading zeros. + +This module follows Semantic Versioning 2.0.0 (see semver.org) +with two exceptions. First, it requires the "v" prefix. Second, it recognizes +vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes) +as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0. + +## How to Use + ++ Add the dependency + +```shell +kcl mod add semver +``` + +```python +import semver + +valid_semver = semver.is_valid("v1.1.0") # True +``` + +## Resource + +The code source and document are [here](https://github.com/kcl-lang/artifacthub/tree/main/semvar) diff --git a/semver/kcl.mod b/semver/kcl.mod new file mode 100644 index 00000000..fa15a21b --- /dev/null +++ b/semver/kcl.mod @@ -0,0 +1,4 @@ +[package] +name = "semver" +version = "0.1.0" +description = "Module semver implements comparison of semantic version strings. In this module, semantic version strings must begin with a leading `v`, as in `v1.0.0`." diff --git a/semver/kcl.mod.lock b/semver/kcl.mod.lock new file mode 100644 index 00000000..e69de29b diff --git a/semver/main.k b/semver/main.k new file mode 100644 index 00000000..d49cd7af --- /dev/null +++ b/semver/main.k @@ -0,0 +1,149 @@ +schema Parsed: + """Parsed returns the parsed form of a semantic version string.""" + major?: str = "" + minor?: str = "" + patch?: str = "" + short?: str = "" + prerelease?: str = "" + build?: str = "" + ok: bool = False + +schema IntParsed: + t: str = "" + rest: str = "" + ok: bool = False + +schema PrereleaseParsed: + t: str = "" + rest: str = "" + ok: bool = False + +schema BuildParsed: + t: str = "" + rest: str = "" + ok: bool = False + +parse = lambda v: str -> Parsed { + result = Parsed {} + + if v and v[0] == "v": + parse_int_result: IntParsed = parse_int(v[1:]) + # Parse major + result.major = parse_int_result.t + if parse_int_result.ok: + rest = parse_int_result.rest + if rest == "": + result.minor = "0" + result.patch = "0" + result.short = ".0.0" + elif rest[0] != ".": + result.ok = False + else: + # Parse minor + parse_int_result = parse_int(rest[1:]) + result.minor = parse_int_result.t + if parse_int_result.ok: + rest = parse_int_result.rest + if rest == "": + result.patch = "0" + result.short = ".0" + else: + # Parse patch + parse_int_result = parse_int(rest[1:]) + result.patch = parse_int_result.t + if parse_int_result.ok: + rest = parse_int_result.rest + if rest and rest[0] == "-": + # Parse prerelease + parse_prerelease_result = parse_prerelease(rest) + if parse_prerelease_result.ok: + result.prerelease = parse_prerelease_result.t + + elif rest and rest[0] == "+": + parse_build_result = parse_build(rest) + if parse_build_result.ok: + result.build = parse_build_result.t + + elif rest: + result.ok = False + else: + result.ok = True + result +} + +parse_int = lambda v: str -> IntParsed { + result = IntParsed {} + + if v and ord("0") <= ord(v[0]) <= ord("9"): + idx = "".join(["1" if ord("0") <= ord(c) <= ord("9") else "0" for i, c in v]).find("0") + if idx == -1: + if v[0] == "0": + result = IntParsed { + t = v[:1] + rest = v[1:] + ok = True + } + else: + result = IntParsed { + t = v[:idx] + rest = v[idx:] + ok = True + } + result +} + +parse_prerelease = lambda v: str -> PrereleaseParsed { + """ A pre-release version MAY be denoted by appending a hyphen and + a series of dot separated identifiers immediately following the patch version. + Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. + Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes. + """ + result = PrereleaseParsed {} + if v and v[0] == "-": + start = 1 + not_ident_idx_list = [i for i, c in v[1:] if c != "+" and not is_ident_char(c) and c != "."] + start_idx_map = {"${i}" = i + 1 for i, c in v[1:] if c != "+" and c == "."} + bad_num_idx_list = [i for i, c in v[1:] if c != "+" and c == "." and not (start_idx_map["${i}"] or 1) == i or is_bad_num(v[(start_idx_map["${i}"] or 1):i])] + if not not_ident_idx_list and not bad_num_idx_list: + idx = len([1 for c in v if c != "+"]) + 1 + result = PrereleaseParsed { + t = v[:idx] + rest = v[idx:] + ok = True + } + result +} + +parse_build = lambda v: str -> BuildParsed { + result = BuildParsed {} + if v and v[0] == "+": + start = 1 + not_ident_idx_list = [i for i, c in v[1:] if not is_ident_char(c) and c != "."] + start_idx_map = {"${i}" = i + 1 for i, c in v[1:] if c == "."} + bad_num_idx_list = [i for i, c in v[1:] if c == "." and not (start_idx_map["${i}"] or 1) == i] + if not not_ident_idx_list and not bad_num_idx_list: + idx = len([1 for c in v]) + 1 + result = BuildParsed { + t = v[:idx] + rest = v[idx:] + ok = True + } + result + +} + +is_ident_char = lambda c: str -> bool { + ord('A') <= ord(c) <= ord('Z') or ord('a') <= ord(c) <= ord('z') or ord('0') <= ord(c) <= ord('9') or c == '-' + +} + +is_bad_num = lambda v: str -> bool { + i = len([1 for c in v if ord("0") <= ord(c) <= ord("9")]) + i == len(v) and i > 1 and v[0] == "0" +} + +is_valid = lambda v: str -> bool { + parse(v).ok +} + +valid_semver = is_valid("v1.1.0") # True