3 个版本
0.4.2 | 2021 年 6 月 6 日 |
---|---|
0.4.1 | 2021 年 6 月 6 日 |
0.4.0 | 2021 年 6 月 6 日 |
#2929 在 解析器实现
50,138 每月下载量
用于 60 个crate(直接使用 3 个)
18KB
342 行
宽松语义版本号解析器
宽松解析语义版本号。
动机
此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
:
宽松解析语义版本号。
依赖关系
~66–350KB