From 46336cfaff802bca40b92e2aa05e12de1e519aa3 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Fri, 24 Mar 2023 17:23:02 -0400 Subject: [PATCH] Implement package specs files --- .gitignore | 1 + Cargo.lock | 254 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 + cli/src/lib.rs | 43 +++++-- src/hooks/mod.rs | 34 ++++- src/hpk.rs | 18 ++- src/item/mod.rs | 16 ++- src/lib.rs | 6 +- src/package/dependency.rs | 4 +- src/package/mod.rs | 5 +- src/package/specs.rs | 81 ++++++++++++ src/plist/mod.rs | 1 - src/version/gitrev.rs | 56 ++++++++- src/version/mod.rs | 100 ++++++++++++++- src/version/semver.rs | 69 ++++++++++- 15 files changed, 654 insertions(+), 38 deletions(-) create mode 100644 src/package/specs.rs diff --git a/.gitignore b/.gitignore index c2a9b6f..efc0e73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target tags +package.specs diff --git a/Cargo.lock b/Cargo.lock index 8a07c60..4f1ca0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 79905a9..f670f59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/cli/src/lib.rs b/cli/src/lib.rs index ed44eb2..6726c74 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -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 { diff --git a/src/hooks/mod.rs b/src/hooks/mod.rs index 9646c9b..c8196d0 100644 --- a/src/hooks/mod.rs +++ b/src/hooks/mod.rs @@ -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> { - unimplemented!(); + pub fn run(&self) -> Result { + match self { + Self::Man => makeinfo(), + Self::GlibSchema => compile_schemas(), + Self::Info(path) => install_info(&path), + } } } + +fn makeinfo() -> Result { + Command::new("makewhatis").output() +} + +fn compile_schemas() -> Result { + Command::new("glib-compile-schemas") + .arg("/usr/share/glib-2.0/schemas") + .output() +} + +fn install_info(path: &str) -> Result { + Command::new("install-info").arg(path).output() +} diff --git a/src/hpk.rs b/src/hpk.rs index edba5ec..f9a81a3 100644 --- a/src/hpk.rs +++ b/src/hpk.rs @@ -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> { 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> { unimplemented!(); } +fn init(matches: &ArgMatches) -> Result> { + 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> { unimplemented!(); } diff --git a/src/item/mod.rs b/src/item/mod.rs index 9715ca2..a20d559 100644 --- a/src/item/mod.rs +++ b/src/item/mod.rs @@ -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() { diff --git a/src/lib.rs b/src/lib.rs index d151aa5..2f7c0d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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::*, diff --git a/src/package/dependency.rs b/src/package/dependency.rs index 966c0c1..64cbc29 100644 --- a/src/package/dependency.rs +++ b/src/package/dependency.rs @@ -6,8 +6,8 @@ use { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Dependency { - name: String, - version: (Option, Option), + pub name: String, + pub version: (Option, Option), } impl Dependency { diff --git a/src/package/mod.rs b/src/package/mod.rs index 1e0802d..c6a4f82 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -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 diff --git a/src/package/specs.rs b/src/package/specs.rs new file mode 100644 index 0000000..45b460d --- /dev/null +++ b/src/package/specs.rs @@ -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, + /// all of this package's runtime dependencies + pub dependencies: Vec, + /// an optional list of users to be created upon installation + pub users: Option>, + /// an optional list of groups to be created upon installation + pub groups: Option>, + /// an optional post installation shell script to be run + pub post_install: Option, +} + +impl TryFrom<&ArgMatches> for Specs { + type Error = Box; + + fn try_from(matches: &ArgMatches) -> Result { + let mut specs = Specs::default(); + let mut name: Option = None; + let mut version: Option = 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::("name") { + specs.name = name.to_string(); + } else { + if let Some(n) = name { + specs.name = n; + } + } + if let Some(version) = matches.get_one::("package-version") { + specs.version = version.parse()?; + } else { + if let Some(v) = version { + specs.version = v; + } + } + if let Some(release) = matches.get_one::("release") { + specs.release = *release as u8; + } + if let Some(deps) = matches.get_many::("dependencies") { + let deps = deps + .map(|d| Dependency { + name: d.to_string(), + version: (None, None), + }) + .collect::>(); + specs.dependencies = deps; + } + Ok(specs) + } +} diff --git a/src/plist/mod.rs b/src/plist/mod.rs index 216d810..a7c1fff 100644 --- a/src/plist/mod.rs +++ b/src/plist/mod.rs @@ -89,4 +89,3 @@ impl TryFrom<&Path> for Entry { } } } - diff --git a/src/version/gitrev.rs b/src/version/gitrev.rs index ba18c62..c2e6052 100644 --- a/src/version/gitrev.rs +++ b/src/version/gitrev.rs @@ -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, +} + +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 for ParseGitRevError { + fn from(_value: chrono::ParseError) -> Self { + Self + } +} + +impl FromStr for GitRev { + type Err = ParseGitRevError; + + fn from_str(s: &str) -> Result { + 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) + } +} diff --git a/src/version/mod.rs b/src/version/mod.rs index c8df478..d86bb07 100644 --- a/src/version/mod.rs +++ b/src/version/mod.rs @@ -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, + }, +} + +impl Default for Version { + fn default() -> Self { + Self::SemVer { + major: 0, + minor: 1, + patch: 0, + } + } +} + +impl From for Version { + fn from(value: SemVer) -> Self { + Self::SemVer { + major: value.major, + minor: value.minor, + patch: value.patch, + } + } +} + +impl From for Version { + fn from(value: GitRev) -> Self { + Self::Git { hash: value.hash, datetime: value.datetime } + } +} + +impl From for Version { + fn from(value: u32) -> Self { + Self::Number(value) + } } impl PartialOrd for Version { fn partial_cmp(&self, other: &Self) -> Option { 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 { + if let Ok(v) = s.parse::() { + Ok(v.into()) + } else if let Ok(v) = s.parse::() { + Ok(v.into()) + } else if let Ok(v) = s.parse::() { + Ok(v.into()) + } else { + Err(ParseVersionError) + } + } +} diff --git a/src/version/semver.rs b/src/version/semver.rs index 23c8438..8425f03 100644 --- a/src/version/semver.rs +++ b/src/version/semver.rs @@ -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 for ParseSemVerError { + fn from(_value: ParseIntError) -> Self { + Self + } +} + +impl FromStr for SemVer { + type Err = ParseSemVerError; + + fn from_str(s: &str) -> Result { + let split = s.split('.').collect::>(); + match split.len() { + 3 => { + let major = split.get(0).unwrap().parse::()?; + let minor = split.get(1).unwrap().parse::()?; + let patch = split.get(2).unwrap().parse::()?; + Ok(Self { + major, + minor, + patch, + }) + } + _ => Err(ParseSemVerError), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_semver() { + assert_eq!( + "1.0.3".parse::(), + Ok(SemVer { + major: 1, + minor: 0, + patch: 3 + }) + ); + } +}