4个版本
0.4.2 | 2021年6月6日 |
---|---|
0.4.1 | 2021年6月6日 |
0.4.0 | 2021年6月6日 |
0.3.0 | 2020年10月31日 |
#2507 在 解析器实现 中
50,201 每月下载量
用于 59 个crate(直接使用4个)
105KB
2K SLoC
宽松语义版本解析器
宽松语义版本号的解析器。
动机
这个crate旨在为semver Version
提供一种替代解析器。
与遵循semver规范不同,这个解析器在允许的内容上更加宽松。差异包括
- 次要版本和路径是可选的,默认为0(例如,“1”解析为“1.0.0”)
- 预发布标识符可以使用
.
分隔(例如,“1.2.3.rc1”解析为“1.2.3-rc1”) - 一些预发布标识符被解析为构建标识符(例如,“1.2.3.Final”解析为“1.2.3+Final”)
- 额外的数字标识符被解析为构建标识符(例如 "1.2.3.4.5" 解析为 "1.2.3+4.5")
- 允许以
v
或V
开头(例如,“v1.2.3”解析为“1.2.3”) - 超过u64的数字被视为字符串(例如,“1.2.3-9876543210987654321098765432109876543210”可以无错误地解析)
此图显示了宽松解析语法
示例
use semver::Version;
let version = lenient_semver::parse("1.2.3");
assert_eq!(version, Ok(Version::new(1, 2, 3)));
// examples of a version that would not be accepted by semver_parser
assert_eq!(
lenient_semver::parse("1.2.M1").unwrap(),
Version::parse("1.2.0-M1").unwrap()
);
assert!(Version::parse("1.2.M1").is_err());
assert_eq!(
lenient_semver::parse("1").unwrap(),
Version::parse("1.0.0").unwrap()
);
assert!(Version::parse("1").is_err());
assert_eq!(
lenient_semver::parse("1.2.3.Final").unwrap(),
Version::parse("1.2.3+Final").unwrap()
);
assert!(Version::parse("1.2.3.Final").is_err());
assert_eq!(
lenient_semver::parse("1.2.3.4.5").unwrap(),
Version::parse("1.2.3+4.5").unwrap()
);
assert!(Version::parse("1.2.3.4.5").is_err());
assert_eq!(
lenient_semver::parse("v1.2.3").unwrap(),
Version::parse("1.2.3").unwrap()
);
assert!(Version::parse("v1.2.3").is_err());
assert_eq!(
lenient_semver::parse("1.2.9876543210987654321098765432109876543210").unwrap(),
Version::parse("1.2.0-9876543210987654321098765432109876543210").unwrap()
);
assert!(Version::parse("1.2.9876543210987654321098765432109876543210").is_err());
解析到自定义版本
解析器不是固定返回semver::Version
,而是解析到lenient_semver::VersionBuilder
。这个crate的默认功能包含了一个用于semver::Version
的VersionBuilder
实现,但任何实现都可以与parse_into
一起使用。
示例
use lenient_semver::VersionBuilder;
/// Simpler version struct that lives only on the stack
#[derive(Debug, Default)]
struct MyVersion {
numbers: [u64; 3],
is_pre_release: bool,
}
/// The VersionBuilder trait is generic over the lifetime of the input string.
/// We don't store references to those strings, so we don't care about the specific lifetime.
impl VersionBuilder<'_> for MyVersion {
/// We will modify the target struct directly
type Out = Self;
/// Construct a new builder instance.
/// One can only expect `set_major` to be called before `build`, all other methods are optional.
fn new() -> Self {
Self::default()
}
/// Construct the final result. In this case, we can just return ourselves.
fn build(self) -> Self::Out {
self
}
/// Called when the major component was found.
fn set_major(&mut self, major: u64) {
self.numbers[0] = major;
}
/// Called when the minor component was found.
fn set_minor(&mut self, minor: u64) {
self.numbers[1] = minor;
}
/// Called when the patch component was found.
fn set_patch(&mut self, patch: u64) {
self.numbers[2] = patch;
}
/// Called when any pre-relase metadata identifier was found.
/// This identifier can just numeric, no attempts at parsing it into a number have been made.
/// For this implementation, we don't care about the value, just it's presence.
fn add_pre_release(&mut self, _pre_release: &str) {
self.is_pre_release = true
}
}
let input = "1.3.3.7-alpha21+build.42";
let my_version = lenient_semver::parse_into::<MyVersion>(input).unwrap();
assert_eq!([1, 3, 3], my_version.numbers);
assert!(my_version.is_pre_release);
VersionBuilder
为各种方法提供了空的默认实现,使其很容易用于除解析之外的用例。以下示例实现了一个函数,该函数检查给定的字符串是否代表任何形式的预发布版本。
use lenient_semver::VersionBuilder;
/// newtype around bool, so we can implement the VersionBuilder trait for it
#[derive(Debug, Default)]
struct IsPreRelease(bool);
impl VersionBuilder<'_> for IsPreRelease {
/// Here we parse into a different value than Self
type Out = bool;
fn new() -> Self {
Self::default()
}
/// Return the wrapped bool
fn build(self) -> Self::Out {
self.0
}
/// We only care about this method and can ignore all the other ones
fn add_pre_release(&mut self, _pre_release: &str) {
self.0 = true;
}
}
/// This method also return false for invalid version strings,
/// which is technically true, as those are not pre-release versions.
/// Usually you would want to have a better error handling.
fn is_pre_release(v: &str) -> bool {
lenient_semver::parse_into::<IsPreRelease>(v).unwrap_or_default()
}
assert!(is_pre_release("1.2.3-pre") == true);
assert!(is_pre_release("1.2.3") == false);
assert!(is_pre_release("1.2.3+build") == false);
功能
lenient_semver
包含了一些功能
功能名称 | 默认启用 | 传递依赖 | 目的 |
---|---|---|---|
semver11 | 是 | semver= "0.11.0" |
为semver = "0.11.0" 提供VersionBuilder 实现。 |
semver10 | 否 | semver= "0.10.0" |
为VersionBuilder 实现提供semver = "0.10.0" 。 |
version_lite | 否 | lenient_version= "*" |
一种自定义的版本,作为对semver::Version 的替代,它补充了一些宽松的特性,例如在补丁之外添加额外的数字。 |
version_semver | 否 | lenient_version= "*" |
将lenient_version 转换为semver::Version 。 |
version_serde | 否 | serde= "1" |
为lenient_version 提供Serde反序列化和序列化实现。 |
示例
semver11
lenient_semver = { version = "*", features = [ "semver11" ] }
use semver::Version as Version11;
// This features is enabled by default and is usable through `parse` directly,
// but can also be used with `parse_into`.
let version = lenient_semver::parse_into::<Version11>("v1.2.3.Final").unwrap();
assert_eq!(version, Version11::parse("1.2.3+Final").unwrap());
semver10
lenient_semver = { version = "*", features = [ "semver10" ] }
// We have both version of semver available, the older one
// is renamed to `semver010`.
use semver010::Version as Version10;
// The default parse is fixed to the latest semver::Version,
// so we need to use `parse_into`.
let version = lenient_semver::parse_into::<Version10>("v1.2.3.Final").unwrap();
assert_eq!(version, Version10::parse("1.2.3+Final").unwrap());
version_lite
lenient_semver = { version = "*", features = [ "version_lite" ] }
有了这些功能,lenient_semver现在有了自己的版本。该特定实现支持直接支持补丁之外的数字。请注意,lenient_semver仍然会解析这些额外的数字而不抱怨,但它们被添加为构建属性到semver版本中。
use lenient_semver::Version;
let version = lenient_semver::parse_into::<Version>("1.3.3.7").unwrap();
assert_eq!(version, Version::parse("1.3.3.7").unwrap()); // Version::parse delegates to this parser
原生支持允许这些版本正确比较,这在semver中是不行的。
use lenient_semver::Version;
let version_a = Version::parse("1.3.3.7").unwrap();
let version_b = Version::parse("1.3.3.8").unwrap();
assert!(version_a < version_b);
// with semver, that fails:
let version_a = lenient_semver::parse("1.3.3.7").unwrap();
let version_b = lenient_semver::parse("1.3.3.8").unwrap();
assert_eq!(version_a < version_b, false);
assert_eq!(version_a, version_b);
此外,Version
不拥有元数据标识符的数据。元数据可以分离,因此版本可以引用不同的所有者。
use lenient_semver::{Version, VersionBuilder};
let input = "1.3.3.7-beta.21+build.42";
// make an owned copy, so we don't cheat by using the 'static lifetime.
let input = String::from(input);
// This version references slices from the `input` String
let version = lenient_semver::parse_into::<Version>(input.as_ref()).unwrap();
// Which prevents us from dropping the input
// drop(input);
// We can disassociate the metadata, which allows the new version to reference something else
let (mut version, pre, build) = version.disassociate_metadata();
// We still get the referenced input slices, so we create owned copies
let pre: Vec<String> = pre.into_iter().map(ToOwned::to_owned).collect();
let build: Vec<String> = build.into_iter().map(ToOwned::to_owned).collect();
// now we can safely drop the input
drop(input);
// We can also re-add the cloned identifiers.
// The version would now be bound to the lifetime of this method.
// Just for fun, we swap pre-release and build
for pre in &pre {
version.add_build(pre.as_ref());
}
for build in &build {
version.add_pre_release(build.as_ref());
}
assert_eq!("1.3.3.7-build.42+beta.21".to_string(), version.to_string());
version_semver
lenient_semver = { version = "*", features = [ "version_semver" ] }
如果您需要存储版本的拥有副本,您应该将其复制到semver::Version
或您自定义的版本类型中。如果您只打算存储版本信息,直接解析到semver::Version
可能更有意义。
use semver::Version;
let input = String::from("v1.3.3.7-beta-21+build-42");
let version = lenient_semver::Version::parse(&input).unwrap();
let version = Version::from(version);
assert_eq!("1.3.3-beta.21+7.build.42", &version.to_string());
version_serde
lenient_semver = { version = "*", features = [ "version_serde" ] }
此功能还启用了version_lite
,并为自有的版本类型提供了serde支持。由于lenient_semver::Version
不拥有元数据标识符的所有权,反序列化结果的生命周期被绑定到输入上。
use lenient_semver::{Version, VersionBuilder};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct DependencySpec<'input> {
/// Refer to name as owned value
name: String,
/// Borrows from the input string
#[serde(borrow)]
version: Version<'input>,
}
let input = "
{
\"name\": \"lenient_semver\",
\"version\": \"1.3.3.7+build.42\"
}";
// make an owned copy, so we don't cheat by using the 'static lifetime.
let input = String::from(input);
// use serde as one would normally do
let dep: DependencySpec = serde_json::from_str(input.as_ref()).unwrap();
println!("{:?}", dep);
// cannot move out of `input` because it is borrowed
// drop(input);
let mut expected = Version::new(1, 3, 3);
expected.add_additional(7);
expected.add_build("build");
expected.add_build("42");
assert_eq!(dep.version, expected);
// now we can drop the input
drop(input);
parse_partial
lenient_semver = { version = "*", features = [ "parse_partial" ] }
此功能启用了解析器的partial
功能。部分解析器不会尝试消耗所有输入。相反,它尽可能地解析版本,并将未消耗的输入与解析的版本一起返回。
use lenient_semver::{Version, VersionBuilder, parser};
let input = "1.2.3 42+build 1.3.3.7 // end";
// parse first version
let (version, remainder) = parser::parse_partial::<Version>(input).unwrap();
let expected = Version::new(1, 2, 3);
assert_eq!(version, expected);
// trailing whitespace is considered part of a version and consumed as well
assert_eq!("42+build 1.3.3.7 // end", remainder);
// parse second version
let (version, remainder) = parser::parse_partial::<Version>(remainder).unwrap();
let mut expected = Version::new(42, 0, 0);
expected.add_build("build");
assert_eq!(version, expected);
assert_eq!("1.3.3.7 // end", remainder);
// parse last version
let (version, remainder) = parser::parse_partial::<Version>(remainder).unwrap();
let mut expected = Version::new(1, 3, 3);
expected.add_additional(7);
assert_eq!(version, expected);
assert_eq!("// end", remainder);
// parse partial still expects to parse something.
// It will fail with `UnexpectedInput` or `MissingMajorNumber` if the input does not match at least a major version.
// let's try to parse the remaining input
let error = parser::parse_partial::<Version>(remainder).unwrap_err();
assert_eq!(error.error_kind(), parser::ErrorKind::UnexpectedInput);
assert_eq!(error.error_line(), "Unexpected `/`");
// or an empty string
let error = parser::parse_partial::<Version>(" ").unwrap_err();
assert_eq!(error.error_kind(), parser::ErrorKind::MissingMajorNumber);
assert_eq!(
error.error_line(),
"Could not parse the major identifier: No input"
);
// The rules of when a certain number will be parsed are even more relaxed
let (version, remainder) = parser::parse_partial::<Version>("1foobar").unwrap();
let expected = Version::new(1, 0, 0);
assert_eq!(version, expected);
assert_eq!(remainder, "foobar");
// Furthermore, the characters `*` and `?` are allowed to appear everywhere where other alphabetic character are allowed.
// This relaxes the rule that only a-z, A-Z, and 0-9 are allowed.
// Those characters have no special meaning and will be parsed as pre-release or build segment.
let (version, remainder) = parser::parse_partial::<Version>("1.2.*+final?").unwrap();
let mut expected = Version::new(1, 2, 0);
expected.add_pre_release("*");
expected.add_build("final?");
assert_eq!(version, expected);
assert_eq!(remainder, "");
许可证:MIT OR Apache-2.0
lib.rs
:
宽松语义版本号的解析器。
依赖关系
~0–295KB