Implement package specs files

This commit is contained in:
Nathan Fisher 2023-03-24 17:23:02 -04:00
parent bc1f1ed082
commit 46336cfaff
15 changed files with 654 additions and 38 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target
tags
package.specs

254
Cargo.lock generated
View File

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -47,6 +56,12 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "cc"
version = "1.0.79"
@ -62,6 +77,22 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"serde",
"time",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "clap"
version = "4.1.11"
@ -91,6 +122,22 @@ dependencies = [
"clap",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cpufeatures"
version = "0.2.5"
@ -153,6 +200,50 @@ dependencies = [
"typenum",
]
[[package]]
name = "cxx"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn 2.0.4",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31"
[[package]]
name = "cxxbridge-macro"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.4",
]
[[package]]
name = "darling"
version = "0.14.4"
@ -304,6 +395,7 @@ checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
name = "hpk"
version = "0.1.0"
dependencies = [
"chrono",
"clap",
"cli",
"rayon",
@ -316,6 +408,30 @@ dependencies = [
"zstd",
]
[[package]]
name = "iana-time-zone"
version = "0.1.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
dependencies = [
"cxx",
"cxx-build",
]
[[package]]
name = "ident_case"
version = "1.0.1"
@ -374,18 +490,45 @@ dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
[[package]]
name = "link-cplusplus"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
dependencies = [
"cc",
]
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
@ -401,6 +544,25 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
@ -531,6 +693,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scratch"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
[[package]]
name = "serde"
version = "1.0.158"
@ -634,6 +802,17 @@ dependencies = [
"syn 2.0.4",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@ -693,6 +872,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "url"
version = "2.3.1"
@ -721,6 +906,66 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 1.0.109",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "winapi"
version = "0.3.9"
@ -752,6 +997,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.45.0"

View File

@ -26,6 +26,10 @@ tar = { path = "tar" }
walkdir = "2.3"
zstd = "0.12"
[dependencies.chrono]
version = "0.4"
features = ["serde"]
[dependencies.serde]
version = "1.0"
features = ["derive"]

View File

@ -1,4 +1,4 @@
use clap::{Arg, ArgAction, Command, ValueHint, value_parser};
use clap::{value_parser, Arg, ArgAction, Command, ValueHint};
pub fn cli() -> Command {
Command::new("hpk")
@ -9,6 +9,7 @@ pub fn cli() -> Command {
.arg_required_else_help(true)
.subcommands([
create(),
init(),
search(),
info(),
install(),
@ -42,6 +43,35 @@ fn create() -> Command {
.long("package-version")
.num_args(1)
.required_unless_present("specs"),
Arg::new("release")
.help("release number")
.short('r')
.long("release")
.default_value("1")
.value_parser(value_parser!(u8)),
Arg::new("dependencies")
.help("a comma separated list of dependencies")
.short('d')
.long("dependencies")
.value_delimiter(',')
.num_args(1..),
])
}
fn init() -> Command {
Command::new("init")
.about("Initialize a package `specs.ron` file in the current directory")
.args([
Arg::new("name")
.help("package name")
.short('n')
.long("name")
.num_args(1),
Arg::new("package-version")
.help("package version")
.short('v')
.long("package-version")
.num_args(1),
Arg::new("release")
.help("release number")
.short('r')
@ -78,17 +108,14 @@ fn search() -> Command {
.short('n')
.long("names")
.action(ArgAction::SetTrue),
Arg::new("query")
.use_value_delimiter(false)
.required(true),
Arg::new("query").use_value_delimiter(false).required(true),
])
}
fn info() -> Command {
Command::new("info")
.about("Display information about a specific package")
.arg(Arg::new("name")
.required(true))
.arg(Arg::new("name").required(true))
}
fn install() -> Command {
@ -114,9 +141,7 @@ fn remove() -> Command {
Command::new("remove")
.about("Remove packages")
.visible_aliases(["rm", "del"])
.arg(Arg::new("package")
.required(true)
.num_args(1..))
.arg(Arg::new("package").required(true).num_args(1..))
}
fn upgrade() -> Command {

View File

@ -1,14 +1,40 @@
use std::error::Error;
use std::{
io,
process::{Command, Output},
};
#[non_exhaustive]
/// Defines a set of commands to be run in order to finish package installation
pub enum Hooks {
/// Runs `makewhatis` to update the mandoc database if the package contains
/// any Unix man pages
Man,
/// Runs `glib-compile-schemas` if the package contains any GLib schema files
GlibSchema,
Info,
/// runs `install-info` to update the GNU Texinfo database
Info(String),
}
impl Hooks {
pub fn run(&self) -> Result<(), Box<dyn Error>> {
unimplemented!();
pub fn run(&self) -> Result<Output, io::Error> {
match self {
Self::Man => makeinfo(),
Self::GlibSchema => compile_schemas(),
Self::Info(path) => install_info(&path),
}
}
}
fn makeinfo() -> Result<Output, io::Error> {
Command::new("makewhatis").output()
}
fn compile_schemas() -> Result<Output, io::Error> {
Command::new("glib-compile-schemas")
.arg("/usr/share/glib-2.0/schemas")
.output()
}
fn install_info(path: &str) -> Result<Output, io::Error> {
Command::new("install-info").arg(path).output()
}

View File

@ -1,13 +1,19 @@
use {
clap::ArgMatches,
cli::cli,
std::error::Error,
hpk::Specs,
ron::ser::{to_writer_pretty, PrettyConfig},
std::{error::Error, fs::File, io::BufWriter, path::PathBuf},
};
fn main() -> Result<(), Box<dyn Error>> {
let matches = cli().get_matches();
match matches.subcommand() {
Some(("create", matches)) => create(matches)?,
Some(("init", matches)) => {
let specsfile = init(matches)?;
println!("Created specsfile {}", specsfile.display());
}
Some(("search", matches)) => search(matches)?,
Some(("install", matches)) => install(matches)?,
Some(("remove", matches)) => remove(matches)?,
@ -21,6 +27,16 @@ fn create(_matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
unimplemented!();
}
fn init(matches: &ArgMatches) -> Result<PathBuf, Box<dyn Error>> {
let specsfile = PathBuf::from("package.specs");
let cfg = PrettyConfig::new().struct_names(true);
let buf = File::create(&specsfile)?;
let writer = BufWriter::new(buf);
let specs = Specs::try_from(matches)?;
to_writer_pretty(writer, &specs, cfg)?;
Ok(specsfile)
}
fn search(_matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
unimplemented!();
}

View File

@ -1,6 +1,13 @@
use crate::Entry;
use sha2::{Digest, Sha256};
use std::{error::Error, fmt::Write, fs, path::{Path, PathBuf}, io::Read, os::unix::fs::MetadataExt};
use std::{
error::Error,
fmt::Write,
fs,
io::Read,
os::unix::fs::MetadataExt,
path::{Path, PathBuf},
};
use tar::{Node, Owner};
#[derive(Clone, Debug)]
@ -28,7 +35,12 @@ impl Item {
}
let mode = meta.mode();
Ok(Self {
entry: Entry::File { path, sha256sum, mode, size },
entry: Entry::File {
path,
sha256sum,
mode,
size,
},
node: Node::read_data_to_tar(&data, &filename, &meta, owner)?,
})
} else if meta.is_dir() {

View File

@ -1,15 +1,15 @@
#![warn(clippy::all, clippy::pedantic)]
mod item;
mod hooks;
mod item;
mod package;
mod plist;
mod repository;
mod version;
pub use {
item::Item,
hooks::Hooks,
package::{Dependency, Package},
item::Item,
package::{Dependency, Package, Specs},
plist::*,
repository::Repository,
version::*,

View File

@ -6,8 +6,8 @@ use {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Dependency {
name: String,
version: (Option<Version>, Option<Version>),
pub name: String,
pub version: (Option<Version>, Option<Version>),
}
impl Dependency {

View File

@ -1,9 +1,10 @@
mod dependency;
pub use dependency::Dependency;
mod specs;
use {
crate::{Plist, Version},
serde::{Deserialize, Serialize},
};
pub use {dependency::Dependency, specs::Specs};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct User {
@ -24,7 +25,7 @@ pub struct Package {
/// The `Version` of the package
pub version: Version,
/// The release number for this package
pub release: u32,
pub release: u8,
/// a single line description of the package
pub description: String,
/// a more verbose description of the package

81
src/package/specs.rs Normal file
View File

@ -0,0 +1,81 @@
use {
super::{Group, User},
crate::{Dependency, Version},
clap::ArgMatches,
serde::{Deserialize, Serialize},
std::{env, error::Error},
};
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct Specs {
/// The name of the package minus all version information
pub name: String,
/// The `Version` of the package
pub version: Version,
/// The release number for this package
pub release: u8,
/// a single line description of the package
pub description: String,
/// a more verbose description of the package
pub long_description: String,
/// an optional link to an
/// [AppStream](https://www.freedesktop.org/wiki/Distributions/AppStream/)
/// metadata file
pub appstream_data: Option<String>,
/// all of this package's runtime dependencies
pub dependencies: Vec<Dependency>,
/// an optional list of users to be created upon installation
pub users: Option<Vec<User>>,
/// an optional list of groups to be created upon installation
pub groups: Option<Vec<Group>>,
/// an optional post installation shell script to be run
pub post_install: Option<String>,
}
impl TryFrom<&ArgMatches> for Specs {
type Error = Box<dyn Error>;
fn try_from(matches: &ArgMatches) -> Result<Self, Self::Error> {
let mut specs = Specs::default();
let mut name: Option<String> = None;
let mut version: Option<Version> = None;
let cwd = env::current_dir()?;
if let Some(dir) = cwd.file_name() {
if let Some(n) = dir.to_str() {
if let Some((n, v)) = n.split_once('-') {
name = Some(n.to_string());
if let Ok(v) = v.parse() {
version = Some(v);
}
}
}
}
if let Some(name) = matches.get_one::<String>("name") {
specs.name = name.to_string();
} else {
if let Some(n) = name {
specs.name = n;
}
}
if let Some(version) = matches.get_one::<String>("package-version") {
specs.version = version.parse()?;
} else {
if let Some(v) = version {
specs.version = v;
}
}
if let Some(release) = matches.get_one::<usize>("release") {
specs.release = *release as u8;
}
if let Some(deps) = matches.get_many::<String>("dependencies") {
let deps = deps
.map(|d| Dependency {
name: d.to_string(),
version: (None, None),
})
.collect::<Vec<Dependency>>();
specs.dependencies = deps;
}
Ok(specs)
}
}

View File

@ -89,4 +89,3 @@ impl TryFrom<&Path> for Entry {
}
}
}

View File

@ -1,11 +1,21 @@
use std::cmp::Ordering;
use serde::{Deserialize, Serialize};
use {
chrono::{offset::Utc, DateTime},
serde::{Deserialize, Serialize},
std::{cmp::Ordering, error::Error, fmt, str::FromStr},
};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct GitRev {
hash: String,
datetime: u64,
/// the short revision hash
pub hash: String,
/// the time of the revision commit
pub datetime: DateTime<Utc>,
}
impl fmt::Display for GitRev {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "git_{}.{}", self.hash, self.datetime.format("%Y%m%d"))
}
}
impl PartialOrd for GitRev {
@ -19,3 +29,39 @@ impl Ord for GitRev {
self.datetime.cmp(&other.datetime)
}
}
#[derive(Debug)]
pub struct ParseGitRevError;
impl fmt::Display for ParseGitRevError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error parsing git version")
}
}
impl Error for ParseGitRevError {}
impl From<chrono::ParseError> for ParseGitRevError {
fn from(_value: chrono::ParseError) -> Self {
Self
}
}
impl FromStr for GitRev {
type Err = ParseGitRevError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(gitrev) = s.strip_prefix("git_") {
if let Some((hash, date)) = gitrev.split_once('_') {
if hash.len() == 7 {
let datetime = DateTime::parse_from_str(date, "%Y%m%d")?;
return Ok(Self {
hash: hash.to_string(),
datetime: datetime.into(),
});
}
}
}
Err(ParseGitRevError)
}
}

View File

@ -1,4 +1,8 @@
use serde::{Deserialize, Serialize};
use {
chrono::{offset::Utc, DateTime},
serde::{Deserialize, Serialize},
std::{error::Error, fmt, str::FromStr},
};
mod gitrev;
mod semver;
@ -8,17 +12,103 @@ pub use {gitrev::GitRev, semver::SemVer};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum Version {
Number(u32),
SemVer(SemVer),
Git(GitRev),
SemVer {
major: u8,
minor: u8,
patch: u8,
},
Git {
hash: String,
datetime: DateTime<Utc>,
},
}
impl Default for Version {
fn default() -> Self {
Self::SemVer {
major: 0,
minor: 1,
patch: 0,
}
}
}
impl From<SemVer> for Version {
fn from(value: SemVer) -> Self {
Self::SemVer {
major: value.major,
minor: value.minor,
patch: value.patch,
}
}
}
impl From<GitRev> for Version {
fn from(value: GitRev) -> Self {
Self::Git { hash: value.hash, datetime: value.datetime }
}
}
impl From<u32> for Version {
fn from(value: u32) -> Self {
Self::Number(value)
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(Self::Number(s), Self::Number(o)) => s.partial_cmp(o),
(Self::SemVer(s), Self::SemVer(o)) => s.partial_cmp(o),
(Self::Git(s), Self::Git(o)) => s.partial_cmp(o),
(
Self::SemVer {
major: a,
minor: b,
patch: c,
},
Self::SemVer {
major: d,
minor: e,
patch: f,
},
) => (a, b, c).partial_cmp(&(d, e, f)),
(
Self::Git {
hash: a,
datetime: b,
},
Self::Git {
hash: c,
datetime: d,
},
) => (a, b).partial_cmp(&(c, d)),
_ => None,
}
}
}
#[derive(Debug)]
pub struct ParseVersionError;
impl fmt::Display for ParseVersionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "error parsing version")
}
}
impl Error for ParseVersionError {}
impl FromStr for Version {
type Err = ParseVersionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(v) = s.parse::<SemVer>() {
Ok(v.into())
} else if let Ok(v) = s.parse::<GitRev>() {
Ok(v.into())
} else if let Ok(v) = s.parse::<u32>() {
Ok(v.into())
} else {
Err(ParseVersionError)
}
}
}

View File

@ -1,13 +1,19 @@
use {
serde::{Deserialize, Serialize},
std::cmp::Ordering,
std::{cmp::Ordering, error::Error, fmt, num::ParseIntError, str::FromStr},
};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct SemVer {
pub major: u32,
pub minor: u32,
pub patch: u32,
pub major: u8,
pub minor: u8,
pub patch: u8,
}
impl fmt::Display for SemVer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl PartialOrd for SemVer {
@ -39,3 +45,58 @@ impl Ord for SemVer {
}
}
}
#[derive(Debug, PartialEq)]
pub struct ParseSemVerError;
impl fmt::Display for ParseSemVerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error parsing SemVer")
}
}
impl Error for ParseSemVerError {}
impl From<ParseIntError> for ParseSemVerError {
fn from(_value: ParseIntError) -> Self {
Self
}
}
impl FromStr for SemVer {
type Err = ParseSemVerError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let split = s.split('.').collect::<Vec<_>>();
match split.len() {
3 => {
let major = split.get(0).unwrap().parse::<u8>()?;
let minor = split.get(1).unwrap().parse::<u8>()?;
let patch = split.get(2).unwrap().parse::<u8>()?;
Ok(Self {
major,
minor,
patch,
})
}
_ => Err(ParseSemVerError),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parse_semver() {
assert_eq!(
"1.0.3".parse::<SemVer>(),
Ok(SemVer {
major: 1,
minor: 0,
patch: 3
})
);
}
}