Compare commits
No commits in common. "6037836f6b4485435affae1cbb97a62df5cc524a" and "13a97ff1d8f6b4abed95f21c138783a9426c4f6e" have entirely different histories.
6037836f6b
...
13a97ff1d8
24
Cargo.lock
generated
24
Cargo.lock
generated
@ -544,21 +544,33 @@ checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
|||||||
name = "hpk"
|
name = "hpk"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
|
||||||
"clap",
|
"clap",
|
||||||
"deku",
|
"hpk-package",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"libc",
|
|
||||||
"package-bootstrap",
|
"package-bootstrap",
|
||||||
"rayon",
|
"rayon",
|
||||||
"ron",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
|
"ureq",
|
||||||
|
"url",
|
||||||
|
"walkdir",
|
||||||
|
"zstd",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hpk-package"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://git.hitchhiker-linux.org/jeang3nie/hpk-package.git#e6f392bd7a91ffe3f0b080627e915ea458eade5a"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"deku",
|
||||||
|
"libc",
|
||||||
|
"rayon",
|
||||||
|
"ron",
|
||||||
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"ureq",
|
|
||||||
"url",
|
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"zstd",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -21,19 +21,12 @@ path = "src/bootstrap.rs"
|
|||||||
required-features = ["bootstrap"]
|
required-features = ["bootstrap"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
deku = "0.16"
|
hpk-package = { git = "https://git.hitchhiker-linux.org/jeang3nie/hpk-package.git" }
|
||||||
libc = "0.2"
|
|
||||||
rayon = "1.7"
|
rayon = "1.7"
|
||||||
ron = "0.8"
|
ron = "0.8"
|
||||||
sha2 = "0.10"
|
|
||||||
thiserror = "1.0"
|
|
||||||
walkdir = "2.3"
|
walkdir = "2.3"
|
||||||
zstd = "0.12"
|
zstd = "0.12"
|
||||||
|
|
||||||
[dependencies.chrono]
|
|
||||||
version = "0.4"
|
|
||||||
features = ["serde"]
|
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "4.2"
|
version = "4.2"
|
||||||
optional = true
|
optional = true
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use {
|
use {
|
||||||
crate::{tar, Entry, Item, ItemError, Package, Plist, Specs},
|
crate::{Item, ItemError, Package, Plist, Specs},
|
||||||
deku::DekuError,
|
hpk_package::{deku::DekuError, tar, Entry},
|
||||||
rayon::prelude::{IntoParallelRefIterator, ParallelIterator},
|
rayon::prelude::{IntoParallelRefIterator, ParallelIterator},
|
||||||
std::{
|
std::{
|
||||||
borrow::BorrowMut,
|
borrow::BorrowMut,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use {
|
use {
|
||||||
crate::{Arch, Package, Repository, Version},
|
crate::{Arch, Package, Repository, Version},
|
||||||
|
hpk_package::ron::{self, ser::PrettyConfig},
|
||||||
rayon::prelude::*,
|
rayon::prelude::*,
|
||||||
ron::{self, ser::PrettyConfig},
|
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
std::{
|
std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
use hpk_package::{Group, User};
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{Group, InstallError, User},
|
crate::InstallError,
|
||||||
std::{
|
std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::{Command, Output},
|
process::{Command, Output},
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#![warn(clippy::all, clippy::pedantic)]
|
#![warn(clippy::all, clippy::pedantic)]
|
||||||
#![allow(clippy::missing_errors_doc)]
|
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
use {
|
use {
|
||||||
@ -136,11 +135,10 @@ fn install_local<P: AsRef<OsStr> + fmt::Display>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let mut hooks = vec![];
|
installer.install(sender)?;
|
||||||
installer.install(&mut hooks, sender)?;
|
|
||||||
match handle.join() {
|
match handle.join() {
|
||||||
Ok(package) => {
|
Ok(hooks) => {
|
||||||
println!("hooks: {package:?}");
|
println!("hooks: {hooks:?}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(_) => Err(io::Error::new(ErrorKind::Other, "Unknown thread error").into()),
|
Err(_) => Err(io::Error::new(ErrorKind::Other, "Unknown thread error").into()),
|
||||||
|
@ -2,12 +2,13 @@
|
|||||||
pub use error::InstallError;
|
pub use error::InstallError;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{Hooks, Pinstall},
|
||||||
|
hpk_package::{
|
||||||
|
sha2::{Digest, Sha256},
|
||||||
tar::{Archive, Node},
|
tar::{Archive, Node},
|
||||||
Entry, Group, Hooks, Package, Pinstall, User,
|
Entry, Group, Package, User,
|
||||||
},
|
},
|
||||||
rayon::prelude::{IntoParallelRefIterator, ParallelIterator},
|
rayon::prelude::{IntoParallelRefIterator, ParallelIterator},
|
||||||
sha2::{Digest, Sha256},
|
|
||||||
std::{
|
std::{
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
fmt::Write as _,
|
fmt::Write as _,
|
||||||
@ -260,7 +261,8 @@ fn pop_pinstall(
|
|||||||
|
|
||||||
mod error {
|
mod error {
|
||||||
use super::InstallMessage;
|
use super::InstallMessage;
|
||||||
use crate::{tar, Hooks};
|
use crate::Hooks;
|
||||||
|
use hpk_package::tar;
|
||||||
use ron::error::SpannedError;
|
use ron::error::SpannedError;
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
@ -346,8 +348,8 @@ mod error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crate::tar::Error> for InstallError {
|
impl From<hpk_package::tar::Error> for InstallError {
|
||||||
fn from(value: crate::tar::Error) -> Self {
|
fn from(value: hpk_package::tar::Error) -> Self {
|
||||||
Self::Tar(value)
|
Self::Tar(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
use {
|
use hpk_package::{
|
||||||
crate::{
|
|
||||||
tar::{Error as TarError, Node, Owner},
|
|
||||||
Entry,
|
|
||||||
},
|
|
||||||
deku::DekuError,
|
deku::DekuError,
|
||||||
sha2::{Digest, Sha256},
|
sha2::{Digest, Sha256},
|
||||||
std::{
|
tar::{Error as TarError, Node, Owner},
|
||||||
error::Error,
|
Entry,
|
||||||
ffi::OsStr,
|
};
|
||||||
fmt::{self, Write},
|
use std::{
|
||||||
fs,
|
error::Error,
|
||||||
io::{self, Read},
|
ffi::OsStr,
|
||||||
os::unix::fs::MetadataExt,
|
fmt::{self, Write},
|
||||||
path::{Path, PathBuf},
|
fs,
|
||||||
},
|
io::{self, Read},
|
||||||
|
os::unix::fs::MetadataExt,
|
||||||
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -1,15 +1,10 @@
|
|||||||
#![warn(clippy::all, clippy::pedantic)]
|
#![warn(clippy::all, clippy::pedantic)]
|
||||||
#![allow(clippy::must_use_candidate, clippy::missing_errors_doc)]
|
|
||||||
mod creator;
|
mod creator;
|
||||||
mod db;
|
mod db;
|
||||||
mod hooks;
|
mod hooks;
|
||||||
mod installer;
|
mod installer;
|
||||||
mod item;
|
mod item;
|
||||||
mod package;
|
|
||||||
mod plist;
|
|
||||||
mod repository;
|
mod repository;
|
||||||
pub mod tar;
|
|
||||||
mod version;
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@ -17,12 +12,10 @@ pub use {
|
|||||||
creator::{CreationError, Creator, Message},
|
creator::{CreationError, Creator, Message},
|
||||||
db::Database,
|
db::Database,
|
||||||
hooks::{Hooks, Pinstall},
|
hooks::{Hooks, Pinstall},
|
||||||
|
hpk_package::{tar, Arch, Dependency, GitRev, Package, Plist, Rapid, SemVer, Specs, Version},
|
||||||
installer::{InstallError, InstallMessage, Installer},
|
installer::{InstallError, InstallMessage, Installer},
|
||||||
item::{Item, ItemError},
|
item::{Item, ItemError},
|
||||||
package::{Arch, Dependency, Group, Package, Specs, User},
|
|
||||||
plist::{Entry, Plist},
|
|
||||||
repository::Repository,
|
repository::Repository,
|
||||||
version::{GitRev, Rapid, SemVer, Version},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const DB: [&str; 4] = ["var", "db", "hpk", "db.ron.zstd"];
|
const DB: [&str; 4] = ["var", "db", "hpk", "db.ron.zstd"];
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{error::Error, fmt, str::FromStr};
|
|
||||||
|
|
||||||
#[cfg(target_arch = "arm")]
|
|
||||||
pub const HOST_ARCH: Arch = Arch::armv7l;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
pub const HOST_ARCH: Arch = Arch::aarch64;
|
|
||||||
#[cfg(target_arch = "x86")]
|
|
||||||
pub const HOST_ARCH: Arch = Arch::i486;
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
pub const HOST_ARCH: Arch = Arch::riscv64;
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
pub const HOST_ARCH: Arch = Arch::x86_64;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
pub enum Arch {
|
|
||||||
armv7l,
|
|
||||||
aarch64,
|
|
||||||
i486,
|
|
||||||
i586,
|
|
||||||
i686,
|
|
||||||
riscv64,
|
|
||||||
x86_64,
|
|
||||||
any,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Arch {
|
|
||||||
fn default() -> Self {
|
|
||||||
HOST_ARCH
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Arch {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
match self {
|
|
||||||
Self::armv7l => "armv7l",
|
|
||||||
Self::aarch64 => "aarch64",
|
|
||||||
Self::i486 => "i486",
|
|
||||||
Self::i586 => "i586",
|
|
||||||
Self::i686 => "i686",
|
|
||||||
Self::riscv64 => "riscv64",
|
|
||||||
Self::x86_64 => "x86_64",
|
|
||||||
Self::any => "any",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ParseArchError;
|
|
||||||
|
|
||||||
impl fmt::Display for ParseArchError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "error parsing architecture")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for ParseArchError {}
|
|
||||||
|
|
||||||
impl FromStr for Arch {
|
|
||||||
type Err = ParseArchError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"i486" | "x86" => Ok(Self::i486),
|
|
||||||
"i586" => Ok(Self::i586),
|
|
||||||
"i686" => Ok(Self::i686),
|
|
||||||
"armv7l" | "arm" => Ok(Self::armv7l),
|
|
||||||
"arm64" | "aarch64" | "armv8" => Ok(Self::aarch64),
|
|
||||||
"riscv" | "riscv64" => Ok(Self::riscv64),
|
|
||||||
"any" => Ok(Self::any),
|
|
||||||
_ => Err(ParseArchError),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
use {
|
|
||||||
super::Package,
|
|
||||||
crate::Version,
|
|
||||||
serde::{Deserialize, Serialize},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
||||||
/// Specifies a dependency requirement
|
|
||||||
pub struct Dependency {
|
|
||||||
/// The name of the dependency. All packages must have a unique name.
|
|
||||||
pub name: String,
|
|
||||||
/// The version requirements for this dependency. If the low
|
|
||||||
/// version is `Some`, then the version must be equal to or
|
|
||||||
/// greater than this version. If the high version is `Some`,
|
|
||||||
/// then the version must be less than this version.
|
|
||||||
pub version: (Option<Version>, Option<Version>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dependency {
|
|
||||||
#[allow(clippy::must_use_candidate)]
|
|
||||||
/// Checks whether a package satisfies a given dependency
|
|
||||||
pub fn satisfied(&self, package: &Package) -> bool {
|
|
||||||
if self.name.as_str() == package.name.as_str() {
|
|
||||||
match &self.version {
|
|
||||||
(Some(low), Some(high)) => &package.version >= low && &package.version < high,
|
|
||||||
(Some(low), None) => &package.version >= low,
|
|
||||||
(None, Some(high)) => &package.version < high,
|
|
||||||
// no version requirements
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,129 +0,0 @@
|
|||||||
mod arch;
|
|
||||||
mod dependency;
|
|
||||||
mod specs;
|
|
||||||
|
|
||||||
use {
|
|
||||||
crate::tar::{Node, Owner},
|
|
||||||
crate::{Plist, Version},
|
|
||||||
ron::ser::{to_string_pretty, PrettyConfig},
|
|
||||||
serde::{Deserialize, Serialize},
|
|
||||||
std::{
|
|
||||||
error::Error,
|
|
||||||
fs,
|
|
||||||
fs::File,
|
|
||||||
io::{BufWriter, Write},
|
|
||||||
path::Path,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use {arch::Arch, dependency::Dependency, specs::Specs};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub struct User {
|
|
||||||
pub name: String,
|
|
||||||
pub uid: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub struct Group {
|
|
||||||
pub name: String,
|
|
||||||
pub gid: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
|
||||||
/// the metadata associated with a package
|
|
||||||
pub struct Package {
|
|
||||||
/// The name of the package minus all version information
|
|
||||||
pub name: String,
|
|
||||||
/// The `Version` of the package
|
|
||||||
pub version: Version,
|
|
||||||
/// The architecture the package is built for
|
|
||||||
pub arch: Arch,
|
|
||||||
/// 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,
|
|
||||||
/// a listing of all files, directories and symlinks which are a part of
|
|
||||||
/// this package
|
|
||||||
pub plist: Plist,
|
|
||||||
/// the total size of this package
|
|
||||||
pub size: usize,
|
|
||||||
/// 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 From<Specs> for Package {
|
|
||||||
fn from(value: Specs) -> Self {
|
|
||||||
Package {
|
|
||||||
name: value.name,
|
|
||||||
version: value.version,
|
|
||||||
release: value.release,
|
|
||||||
description: value.description,
|
|
||||||
long_description: value.long_description,
|
|
||||||
dependencies: value.dependencies,
|
|
||||||
users: value.users,
|
|
||||||
groups: value.groups,
|
|
||||||
post_install: value.post_install,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Package {
|
|
||||||
fn as_ron(&self) -> Result<String, ron::Error> {
|
|
||||||
let cfg = PrettyConfig::new().struct_names(true);
|
|
||||||
to_string_pretty(self, cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_ron_and_create_tar_node(&self, outdir: &Path) -> Result<Node, Box<dyn Error>> {
|
|
||||||
if !outdir.exists() {
|
|
||||||
fs::create_dir_all(outdir)?;
|
|
||||||
}
|
|
||||||
let mut outfile = outdir.to_path_buf();
|
|
||||||
outfile.push("package.ron");
|
|
||||||
let fd = File::create(&outfile)?;
|
|
||||||
let s = self.as_ron()?;
|
|
||||||
let mut writer = BufWriter::new(&fd);
|
|
||||||
writer.write_all(s.as_bytes())?;
|
|
||||||
writer.flush()?;
|
|
||||||
let node = Node::read_data_to_tar(
|
|
||||||
s.as_bytes(),
|
|
||||||
"package.ron",
|
|
||||||
&outfile.metadata()?,
|
|
||||||
Some(Owner::default()),
|
|
||||||
)?;
|
|
||||||
Ok(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the formatted full package name including version and release strings
|
|
||||||
pub fn fullname(&self) -> String {
|
|
||||||
format!(
|
|
||||||
"{}-{}_{}_{}",
|
|
||||||
self.name, self.version, self.release, self.arch
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the name of the package archive
|
|
||||||
pub fn archive_name(&self) -> String {
|
|
||||||
format!(
|
|
||||||
"{}-{}_{}_{}.tar.zstd",
|
|
||||||
self.name, self.version, self.release, self.arch
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests whether this package is an update for another
|
|
||||||
pub fn is_upgrade(&self, other: &Self) -> bool {
|
|
||||||
self.arch == other.arch
|
|
||||||
&& self.name == other.name
|
|
||||||
&& (self.version > other.version
|
|
||||||
|| (self.version == other.version && self.release > other.release))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
use {
|
|
||||||
super::{Group, User},
|
|
||||||
crate::{Dependency, Version},
|
|
||||||
serde::{Deserialize, Serialize},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
/// 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>,
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
use {
|
|
||||||
rayon::prelude::*,
|
|
||||||
serde::{Deserialize, Serialize},
|
|
||||||
sha2::{digest::Digest, Sha256},
|
|
||||||
std::{
|
|
||||||
error::Error,
|
|
||||||
fmt::Write,
|
|
||||||
fs,
|
|
||||||
io::Read,
|
|
||||||
os::unix::fs::MetadataExt,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
},
|
|
||||||
walkdir::WalkDir,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub struct Plist {
|
|
||||||
pub entries: Vec<Entry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&Path> for Plist {
|
|
||||||
type Error = Box<dyn Error>;
|
|
||||||
|
|
||||||
fn try_from(value: &Path) -> Result<Self, Self::Error> {
|
|
||||||
let entries = WalkDir::new(value)
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.par_iter()
|
|
||||||
.filter(|x| x.is_ok())
|
|
||||||
.filter_map(|x| {
|
|
||||||
Entry::try_from(x.as_ref().unwrap().path().to_path_buf().as_path()).ok()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
Ok(Self { entries })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub enum Entry {
|
|
||||||
File {
|
|
||||||
path: PathBuf,
|
|
||||||
sha256sum: String,
|
|
||||||
mode: u32,
|
|
||||||
size: usize,
|
|
||||||
},
|
|
||||||
Directory {
|
|
||||||
path: PathBuf,
|
|
||||||
mode: u32,
|
|
||||||
},
|
|
||||||
Link {
|
|
||||||
path: PathBuf,
|
|
||||||
target: PathBuf,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&Path> for Entry {
|
|
||||||
type Error = Box<dyn Error>;
|
|
||||||
|
|
||||||
fn try_from(value: &Path) -> Result<Self, Self::Error> {
|
|
||||||
let mut path = PathBuf::from("/");
|
|
||||||
path.push(value);
|
|
||||||
let meta = fs::metadata(value)?;
|
|
||||||
if meta.is_file() {
|
|
||||||
let mut buf = vec![];
|
|
||||||
let mut fd = fs::File::open(value)?;
|
|
||||||
let size = fd.read_to_end(&mut buf)?;
|
|
||||||
let mut sha256sum = String::new();
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(&buf);
|
|
||||||
let res = hasher.finalize();
|
|
||||||
for c in res {
|
|
||||||
write!(sha256sum, "{c:02x}")?;
|
|
||||||
}
|
|
||||||
let mode = meta.mode();
|
|
||||||
Ok(Self::File {
|
|
||||||
path,
|
|
||||||
sha256sum,
|
|
||||||
mode,
|
|
||||||
size,
|
|
||||||
})
|
|
||||||
} else if meta.is_dir() {
|
|
||||||
let mode = meta.mode();
|
|
||||||
Ok(Self::Directory { path, mode })
|
|
||||||
} else if meta.is_symlink() {
|
|
||||||
let target = fs::read_link(value)?;
|
|
||||||
Ok(Self::Link { path, target })
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
use {
|
use {
|
||||||
crate::Package,
|
crate::Package,
|
||||||
|
hpk_package::ron,
|
||||||
rayon::prelude::*,
|
rayon::prelude::*,
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
std::{
|
std::{
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
## About
|
|
||||||
Derived originally from [minitar](https://github.com/genonullfree/minitar), this
|
|
||||||
crate implements basic functionality for creating and extracting tar archives. It
|
|
||||||
has been adapted to allow creation of a Node (Tar header + 512byte blocks of data)
|
|
||||||
from the raw data plus file metadata. This allows for better efficiency when it
|
|
||||||
is embedded into another application (such as a package manager), as the raw data
|
|
||||||
and metadata about each file can be extracted once and reused for purposes such
|
|
||||||
as generating checksums, getting file sizes and creating packing lists.
|
|
@ -1,22 +0,0 @@
|
|||||||
use std::{fmt, io, num::ParseIntError, str::Utf8Error};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("DekuError: {0}")]
|
|
||||||
Deku(#[from] deku::DekuError),
|
|
||||||
#[error("IoError: {0}")]
|
|
||||||
Io(#[from] io::Error),
|
|
||||||
#[error("Error in conversion of oct_to_dev")]
|
|
||||||
Utf8Error(#[from] Utf8Error),
|
|
||||||
#[error("Error in conversion of oct_to_dev")]
|
|
||||||
ParseIntError(#[from] ParseIntError),
|
|
||||||
#[error("End of tar")]
|
|
||||||
EndOfTar,
|
|
||||||
#[error("Invalid magic")]
|
|
||||||
InvalidMagic,
|
|
||||||
#[error("Invalid Checksum")]
|
|
||||||
InvalidChecksum,
|
|
||||||
#[error("Parse int failed")]
|
|
||||||
Parse(#[from] fmt::Error),
|
|
||||||
}
|
|
@ -1,482 +0,0 @@
|
|||||||
use crate::tar::Error;
|
|
||||||
use deku::prelude::*;
|
|
||||||
use std::{
|
|
||||||
env,
|
|
||||||
ffi::{CStr, OsStr},
|
|
||||||
fmt::{self, Write},
|
|
||||||
fs::{self, Metadata},
|
|
||||||
io,
|
|
||||||
ops::Deref,
|
|
||||||
os::{linux::fs::MetadataExt, unix::fs::FileTypeExt},
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum FileType {
|
|
||||||
Normal = 0x30,
|
|
||||||
Hardlink = 0x31,
|
|
||||||
Symlink = 0x32,
|
|
||||||
Char = 0x33,
|
|
||||||
Block = 0x34,
|
|
||||||
Dir = 0x35,
|
|
||||||
FIFO = 0x36,
|
|
||||||
Unknown = 0x00,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<T> for FileType
|
|
||||||
where
|
|
||||||
T: Deref<Target = Metadata>,
|
|
||||||
{
|
|
||||||
fn from(meta: T) -> Self {
|
|
||||||
if meta.is_dir() {
|
|
||||||
return FileType::Dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_type = meta.file_type();
|
|
||||||
if file_type.is_fifo() {
|
|
||||||
return FileType::FIFO;
|
|
||||||
} else if file_type.is_char_device() {
|
|
||||||
return FileType::Char;
|
|
||||||
} else if file_type.is_block_device() {
|
|
||||||
return FileType::Block;
|
|
||||||
} else if file_type.is_symlink() {
|
|
||||||
return FileType::Symlink;
|
|
||||||
} else if file_type.is_file() {
|
|
||||||
return FileType::Normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileType::Unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Owner {
|
|
||||||
pub uid: u32,
|
|
||||||
pub gid: u32,
|
|
||||||
pub username: String,
|
|
||||||
pub groupname: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Owner {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
uid: 0,
|
|
||||||
gid: 0,
|
|
||||||
username: "root".into(),
|
|
||||||
groupname: "root".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, DekuRead, DekuWrite)]
|
|
||||||
#[deku(endian = "little")]
|
|
||||||
pub struct Header {
|
|
||||||
pub(crate) fname: [u8; 100],
|
|
||||||
pub(crate) mode: [u8; 8],
|
|
||||||
pub(crate) uid: [u8; 8],
|
|
||||||
pub(crate) gid: [u8; 8],
|
|
||||||
pub(crate) size: [u8; 12],
|
|
||||||
pub(crate) mtime: [u8; 12],
|
|
||||||
pub(crate) header_checksum: [u8; 8],
|
|
||||||
pub(crate) link_indicator: [u8; 1],
|
|
||||||
pub(crate) link_name: [u8; 100],
|
|
||||||
pub(crate) ustar_magic: [u8; 6],
|
|
||||||
pub(crate) ustar_version: [u8; 2],
|
|
||||||
pub(crate) username: [u8; 32],
|
|
||||||
pub(crate) groupname: [u8; 32],
|
|
||||||
pub(crate) device_major: [u8; 8],
|
|
||||||
pub(crate) device_minor: [u8; 8],
|
|
||||||
pub(crate) file_prefix: [u8; 155],
|
|
||||||
pub(crate) reserved: [u8; 12],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Header {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
fname: [0; 100],
|
|
||||||
mode: [0; 8],
|
|
||||||
uid: [0; 8],
|
|
||||||
gid: [0; 8],
|
|
||||||
size: [0; 12],
|
|
||||||
mtime: [0; 12],
|
|
||||||
header_checksum: [0x20; 8],
|
|
||||||
link_indicator: [0; 1],
|
|
||||||
link_name: [0; 100],
|
|
||||||
ustar_magic: [0x75, 0x73, 0x74, 0x61, 0x72, 0x20],
|
|
||||||
ustar_version: [0x20, 0x00],
|
|
||||||
username: [0; 32],
|
|
||||||
groupname: [0; 32],
|
|
||||||
device_major: [0; 8],
|
|
||||||
device_minor: [0; 8],
|
|
||||||
file_prefix: [0; 155],
|
|
||||||
reserved: [0; 12],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Header {
|
|
||||||
/// Get the filename of this archive member
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use hpk::tar::Header;
|
|
||||||
///
|
|
||||||
/// let header = Header::new("test/1.txt").unwrap();
|
|
||||||
/// let filename = header.filename().unwrap();
|
|
||||||
/// assert_eq!(filename.as_str(), "1.txt");
|
|
||||||
/// ```
|
|
||||||
pub fn filename(&self) -> Result<String, fmt::Error> {
|
|
||||||
let mut s = String::new();
|
|
||||||
for c in self.fname {
|
|
||||||
if c == 0 {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
write!(s, "{}", char::from(c))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the Unix mode of this archive member
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// use hpk::tar::Header;
|
|
||||||
///
|
|
||||||
/// let header = Header::new("test/1.txt").unwrap();
|
|
||||||
/// let mode = header.mode().unwrap();
|
|
||||||
/// assert_eq!(mode, 420);
|
|
||||||
/// ```
|
|
||||||
pub fn mode(&self) -> Result<u32, Error> {
|
|
||||||
let mut s = String::new();
|
|
||||||
for c in self.mode {
|
|
||||||
if c == 0 {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
write!(s, "{}", char::from(c))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mode = u32::from_str_radix(&s, 8)?;
|
|
||||||
Ok(mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn uid(&self) -> Result<u32, Error> {
|
|
||||||
let mut s = String::new();
|
|
||||||
for c in self.mode {
|
|
||||||
if c == 0 {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
write!(s, "{}", char::from(c))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let uid = u32::from_str_radix(&s, 8)?;
|
|
||||||
Ok(uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gid(&self) -> Result<u32, Error> {
|
|
||||||
let mut s = String::new();
|
|
||||||
for c in self.mode {
|
|
||||||
if c == 0 {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
write!(s, "{}", char::from(c))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let gid = u32::from_str_radix(&s, 8)?;
|
|
||||||
Ok(gid)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn username(&self) -> Result<String, fmt::Error> {
|
|
||||||
let mut s = String::new();
|
|
||||||
for c in self.username {
|
|
||||||
if c == 0 {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
write!(s, "{}", char::from(c))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn groupname(&self) -> Result<String, fmt::Error> {
|
|
||||||
let mut s = String::new();
|
|
||||||
for c in self.groupname {
|
|
||||||
if c == 0 {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
write!(s, "{}", char::from(c))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn owner(&self) -> Result<Owner, Error> {
|
|
||||||
let uid = self.uid()?;
|
|
||||||
let gid = self.gid()?;
|
|
||||||
let username = self.username()?;
|
|
||||||
let groupname = self.groupname()?;
|
|
||||||
Ok(Owner {
|
|
||||||
uid,
|
|
||||||
gid,
|
|
||||||
username,
|
|
||||||
groupname,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the path to the file minus it's final component
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// use hpk::tar::Header;
|
|
||||||
///
|
|
||||||
/// let header = Header::new("test/1.txt").unwrap();
|
|
||||||
/// let prefix = header.prefix().unwrap();
|
|
||||||
/// assert_eq!(prefix.as_str(), "test");
|
|
||||||
/// ```
|
|
||||||
pub fn prefix(&self) -> Option<String> {
|
|
||||||
let mut s = String::new();
|
|
||||||
for c in self.file_prefix {
|
|
||||||
if c == 0 {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
write!(s, "{}", char::from(c)).ok()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the full file path to this archive member.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use hpk::tar::Header;
|
|
||||||
/// use std::path::PathBuf;
|
|
||||||
///
|
|
||||||
/// let header = Header::new("test/1.txt").unwrap();
|
|
||||||
/// let path = header.file_path().unwrap();
|
|
||||||
/// assert_eq!(PathBuf::from("test/1.txt"), path);
|
|
||||||
/// ```
|
|
||||||
pub fn file_path(&self) -> Result<PathBuf, fmt::Error> {
|
|
||||||
let mut path = match self.prefix() {
|
|
||||||
Some(p) => PathBuf::from(&p),
|
|
||||||
None => PathBuf::new(),
|
|
||||||
};
|
|
||||||
let name = self.filename()?;
|
|
||||||
path.push(&name);
|
|
||||||
Ok(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::missing_panics_doc)]
|
|
||||||
pub fn new(filename: &str) -> Result<Self, Error> {
|
|
||||||
let mut header = Header::default();
|
|
||||||
let meta = fs::symlink_metadata(filename)?;
|
|
||||||
let (filename, prefix) = {
|
|
||||||
// Original tar has a maximum file name length of 100 bytes. The ustar
|
|
||||||
// revision allows storing the path prefix separately, with 100 bytes
|
|
||||||
// reserved for the file name and 150 bytes for the rest of the path.
|
|
||||||
let path = PathBuf::from(&filename);
|
|
||||||
let name = match path.file_name().and_then(OsStr::to_str) {
|
|
||||||
Some(n) => n.to_string(),
|
|
||||||
None => {
|
|
||||||
return Err(Error::Io(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"Cannot get file name",
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let dir = path.parent().map(|x| format!("{}", x.display()));
|
|
||||||
(name, dir)
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Fill in metadata */
|
|
||||||
header.fname[..filename.len()].copy_from_slice(filename.as_bytes());
|
|
||||||
let mode = format!("{:07o}", (meta.st_mode() & 0o777));
|
|
||||||
header.mode[..mode.len()].copy_from_slice(mode.as_bytes());
|
|
||||||
let user = format!("{:07o}", meta.st_uid());
|
|
||||||
header.uid[..user.len()].copy_from_slice(user.as_bytes());
|
|
||||||
let group = format!("{:07o}", meta.st_gid());
|
|
||||||
header.gid[..group.len()].copy_from_slice(group.as_bytes());
|
|
||||||
let size = format!("{:011o}", meta.st_size());
|
|
||||||
header.size[..size.len()].copy_from_slice(size.as_bytes());
|
|
||||||
let mtime = format!("{:011o}", meta.st_mtime());
|
|
||||||
header.mtime[..mtime.len()].copy_from_slice(mtime.as_bytes());
|
|
||||||
if let Some(prefix) = prefix {
|
|
||||||
header.file_prefix[..prefix.len()].copy_from_slice(prefix.as_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get the file type and conditional metadata */
|
|
||||||
header.link_indicator[0] = FileType::from(&meta) as u8;
|
|
||||||
if header.link_indicator[0] == FileType::Symlink as u8 {
|
|
||||||
let link = fs::read_link(filename)?.to_str().unwrap().to_string();
|
|
||||||
header.link_name[..link.len()].copy_from_slice(link.as_bytes());
|
|
||||||
} else if header.link_indicator[0] == FileType::Block as u8 {
|
|
||||||
let major = format!("{:07o}", meta.st_dev());
|
|
||||||
header.device_major[..major.len()].copy_from_slice(major.as_bytes());
|
|
||||||
let minor = format!("{:07o}", meta.st_rdev());
|
|
||||||
header.device_minor[..minor.len()].copy_from_slice(minor.as_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Find better way to get username */
|
|
||||||
let key = "USER";
|
|
||||||
if let Ok(val) = env::var(key) {
|
|
||||||
header.username[..val.len()].copy_from_slice(val.as_bytes());
|
|
||||||
}
|
|
||||||
/* TODO: Find way to get groupname */
|
|
||||||
|
|
||||||
/* Update the header checksum value */
|
|
||||||
header.update_checksum()?;
|
|
||||||
|
|
||||||
Ok(header)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::missing_panics_doc)]
|
|
||||||
pub fn new_from_meta(
|
|
||||||
filename: &str,
|
|
||||||
meta: &Metadata,
|
|
||||||
owner: Option<Owner>,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
let mut header = Header::default();
|
|
||||||
let (filename, prefix) = {
|
|
||||||
// Original tar has a maximum file name length of 100 bytes. The ustar
|
|
||||||
// revision allows storing the path prefix separately, with 100 bytes
|
|
||||||
// reserved for the file name and 150 bytes for the rest of the path.
|
|
||||||
let path = PathBuf::from(&filename);
|
|
||||||
let name = match path.file_name().and_then(OsStr::to_str) {
|
|
||||||
Some(n) => n.to_string(),
|
|
||||||
None => {
|
|
||||||
return Err(Error::Io(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"Cannot get file name",
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let dir = path.parent().map(|x| format!("{}", x.display()));
|
|
||||||
(name, dir)
|
|
||||||
};
|
|
||||||
header.fname[..filename.len()].copy_from_slice(filename.as_bytes());
|
|
||||||
let mode = format!("{:07o}", meta.st_mode());
|
|
||||||
header.mode[..mode.len()].copy_from_slice(mode.as_bytes());
|
|
||||||
let owner = match owner {
|
|
||||||
Some(o) => o,
|
|
||||||
None => Owner {
|
|
||||||
uid: meta.st_uid(),
|
|
||||||
gid: meta.st_gid(),
|
|
||||||
username: get_username_for_uid(meta.st_uid())?.into(),
|
|
||||||
groupname: get_groupname_for_gid(meta.st_gid())?.into(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let uid = format!("{:07o}", owner.uid);
|
|
||||||
header.uid[..uid.len()].copy_from_slice(uid.as_bytes());
|
|
||||||
let gid = format!("{:07o}", owner.gid);
|
|
||||||
header.gid[..gid.len()].copy_from_slice(gid.as_bytes());
|
|
||||||
let size = format!("{:011o}", meta.len());
|
|
||||||
header.size[..size.len()].copy_from_slice(size.as_bytes());
|
|
||||||
let mtime = format!("{:011o}", meta.st_mtime());
|
|
||||||
header.mtime[..mtime.len()].copy_from_slice(mtime.as_bytes());
|
|
||||||
if let Some(prefix) = prefix {
|
|
||||||
header.file_prefix[..prefix.len()].copy_from_slice(prefix.as_bytes());
|
|
||||||
}
|
|
||||||
header.link_indicator[0] = FileType::from(meta) as u8;
|
|
||||||
if header.link_indicator[0] == FileType::Symlink as u8 {
|
|
||||||
let link = fs::read_link(filename)?.to_str().unwrap().to_string();
|
|
||||||
header.link_name[..link.len()].copy_from_slice(link.as_bytes());
|
|
||||||
} else if header.link_indicator[0] == FileType::Block as u8 {
|
|
||||||
let major = format!("{:07o}", meta.st_dev());
|
|
||||||
header.device_major[..major.len()].copy_from_slice(major.as_bytes());
|
|
||||||
let minor = format!("{:07o}", meta.st_rdev());
|
|
||||||
header.device_minor[..minor.len()].copy_from_slice(minor.as_bytes());
|
|
||||||
}
|
|
||||||
header.username[..owner.username.len()].copy_from_slice(owner.username.as_bytes());
|
|
||||||
header.groupname[..owner.groupname.len()].copy_from_slice(owner.groupname.as_bytes());
|
|
||||||
header.update_checksum()?;
|
|
||||||
Ok(header)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validates that the magic value received matches the magic value required in the Tar specification.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use hpk::tar::Header;
|
|
||||||
/// let header = Header::default();
|
|
||||||
/// if !header.validate_magic() {
|
|
||||||
/// println!("Magic value is invalid");
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn validate_magic(self) -> bool {
|
|
||||||
self.ustar_magic == "ustar ".as_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validates the header checksum computes to the expected value.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use hpk::tar::Header;
|
|
||||||
/// let header = Header::default();
|
|
||||||
/// if header.validate_checksum().unwrap() {
|
|
||||||
/// println!("Checksum is valid");
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn validate_checksum(self) -> Result<bool, Error> {
|
|
||||||
let mut test = self;
|
|
||||||
let mut new = [0x20u8; 8];
|
|
||||||
test.header_checksum.copy_from_slice(&[0x20; 8]);
|
|
||||||
|
|
||||||
let tmp = format!("{:06o}\x00", test.calc_checksum()?);
|
|
||||||
new[..tmp.len()].copy_from_slice(tmp.as_bytes());
|
|
||||||
|
|
||||||
Ok(self.header_checksum == new)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the header checksum value.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use hpk::tar::Header;
|
|
||||||
/// let mut header = Header::default();
|
|
||||||
///
|
|
||||||
/// /* Fill in header information */
|
|
||||||
///
|
|
||||||
/// header.update_checksum();
|
|
||||||
/// ```
|
|
||||||
pub fn update_checksum(&mut self) -> Result<(), Error> {
|
|
||||||
let checksum = format!("{:06o}\x00", self.calc_checksum()?);
|
|
||||||
self.header_checksum[..checksum.len()].copy_from_slice(checksum.as_bytes());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calc_checksum(self) -> Result<usize, Error> {
|
|
||||||
let out = self.to_bytes()?;
|
|
||||||
let mut checksum = 0;
|
|
||||||
for i in out {
|
|
||||||
checksum += i as usize;
|
|
||||||
}
|
|
||||||
Ok(checksum)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_username_for_uid<'a>(uid: u32) -> Result<&'a str, std::str::Utf8Error> {
|
|
||||||
let user = unsafe {
|
|
||||||
let pw = libc::getpwuid(uid);
|
|
||||||
let name = (*pw).pw_name;
|
|
||||||
CStr::from_ptr(name)
|
|
||||||
};
|
|
||||||
user.to_str()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_groupname_for_gid<'a>(gid: u32) -> Result<&'a str, std::str::Utf8Error> {
|
|
||||||
let group = unsafe {
|
|
||||||
let gr = libc::getgrgid(gid);
|
|
||||||
let name = (*gr).gr_name;
|
|
||||||
CStr::from_ptr(name)
|
|
||||||
};
|
|
||||||
group.to_str()
|
|
||||||
}
|
|
199
src/tar/mod.rs
199
src/tar/mod.rs
@ -1,199 +0,0 @@
|
|||||||
use std::{
|
|
||||||
fs::File,
|
|
||||||
io::{self, BufReader, Write},
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
|
|
||||||
use rayon::prelude::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
|
|
||||||
|
|
||||||
mod error;
|
|
||||||
mod header;
|
|
||||||
mod node;
|
|
||||||
pub use {
|
|
||||||
error::Error,
|
|
||||||
header::{FileType, Header, Owner},
|
|
||||||
node::Node,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Archive {
|
|
||||||
pub nodes: Vec<Node>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Archive {
|
|
||||||
pub fn to_vec(self) -> Result<Vec<u8>, Error> {
|
|
||||||
let mut buf = vec![];
|
|
||||||
for node in self.nodes {
|
|
||||||
buf.extend(node.to_vec()?);
|
|
||||||
}
|
|
||||||
buf.write_all(&[0; 9216])?;
|
|
||||||
Ok(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write out a vector of `TarNodes` to a file or something that implements
|
|
||||||
/// ``std::io::Write`` and ``std::io::Copy``.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::fs::File;
|
|
||||||
/// use hpk::tar::Archive;
|
|
||||||
///
|
|
||||||
/// let data = Archive::new("test/1.txt").unwrap();
|
|
||||||
///
|
|
||||||
/// let out = File::create("test/2.tar".to_string()).unwrap();
|
|
||||||
/// data.write(&out).unwrap();
|
|
||||||
/// ```
|
|
||||||
pub fn write<T: io::Write + Copy>(self, mut input: T) -> Result<usize, Error> {
|
|
||||||
let mut written = 0;
|
|
||||||
for f in self.nodes.clone() {
|
|
||||||
written += f.write(input)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Complete the write with 18 blocks of 512 ``0x00`` bytes per the specification */
|
|
||||||
if !self.nodes.is_empty() {
|
|
||||||
input.write_all(&[0; 9216])?;
|
|
||||||
written += 9216;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(written)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new `TarFile` struct and initialize it with a `filename` file.
|
|
||||||
/// This will read in the file to the `TarFile` struct as a `TarNode`.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use hpk::tar::Archive;
|
|
||||||
///
|
|
||||||
/// let data = Archive::new("test/1.txt").unwrap();
|
|
||||||
/// ```
|
|
||||||
pub fn new(filename: &str) -> Result<Self, Error> {
|
|
||||||
Ok(Self {
|
|
||||||
nodes: vec![Node::read_file_to_tar(filename)?],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append another file to the `TarFile.file` vector. This adds a file to the
|
|
||||||
/// internal representation of the tar file.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use hpk::tar::Archive;
|
|
||||||
///
|
|
||||||
/// let mut data = Archive::new("test/1.txt").unwrap();
|
|
||||||
/// data.append("test/1.txt").unwrap();
|
|
||||||
/// ```
|
|
||||||
pub fn append(&mut self, filename: &str) -> Result<(), Error> {
|
|
||||||
self.nodes.push(Node::read_file_to_tar(filename)?);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Open and load an external tar file into the internal `TarFile` struct. This
|
|
||||||
/// parses and loads up all the files contained within the external tar file.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use hpk::tar::Archive;
|
|
||||||
///
|
|
||||||
/// Archive::open("test/1.tar").unwrap();
|
|
||||||
/// ```
|
|
||||||
pub fn open(filename: &str) -> Result<Self, Error> {
|
|
||||||
let file = File::open(filename)?;
|
|
||||||
let mut reader = BufReader::new(file);
|
|
||||||
Self::read(&mut reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a tar archive from anything which implements `io::Read`.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use hpk::tar::Archive;
|
|
||||||
/// use std::{fs::File, io::BufReader};
|
|
||||||
///
|
|
||||||
/// let file = File::open("test/1.tar").unwrap();
|
|
||||||
/// let mut reader = BufReader::new(file);
|
|
||||||
/// Archive::read(&mut reader).unwrap();
|
|
||||||
/// ```
|
|
||||||
pub fn read<T: io::Read>(mut input: T) -> Result<Self, Error> {
|
|
||||||
let mut out = Self {
|
|
||||||
nodes: Vec::<Node>::new(),
|
|
||||||
};
|
|
||||||
while let Ok(t) = Node::read(&mut input) {
|
|
||||||
out.nodes.push(t);
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove the first file from the Tar that matches the filename and path.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use hpk::tar::Archive;
|
|
||||||
///
|
|
||||||
/// let mut data = Archive::new("test/1.tar").unwrap();
|
|
||||||
/// data.remove("test/1.tar").unwrap();
|
|
||||||
/// ```
|
|
||||||
pub fn remove(&mut self, filename: &str) -> Result<bool, Error> {
|
|
||||||
let mut name = [0u8; 100];
|
|
||||||
name[..filename.len()].copy_from_slice(filename.as_bytes());
|
|
||||||
if let Some(i) = &self
|
|
||||||
.nodes
|
|
||||||
.par_iter()
|
|
||||||
.position_any(|x| x.header.fname == name)
|
|
||||||
{
|
|
||||||
self.nodes.remove(*i);
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the first node from the archive that matches the given filename.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use hpk::tar::Archive;
|
|
||||||
/// let archive = Archive::new("test/1.txt").unwrap();
|
|
||||||
/// let node = archive.get("test/1.txt");
|
|
||||||
/// assert!(node.is_some());
|
|
||||||
/// ```
|
|
||||||
pub fn get(&self, filename: &str) -> Option<Node> {
|
|
||||||
self.nodes
|
|
||||||
.par_iter()
|
|
||||||
.find_any(|x| x.header.file_path() == Ok(PathBuf::from(filename)))
|
|
||||||
.cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop(&mut self, filename: &str) -> Option<Node> {
|
|
||||||
if let Some(i) = self
|
|
||||||
.nodes
|
|
||||||
.par_iter()
|
|
||||||
.position_any(|x| x.header.file_path() == Ok(PathBuf::from(filename)))
|
|
||||||
{
|
|
||||||
let node = self.nodes.get(i).cloned();
|
|
||||||
self.nodes.remove(i);
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn open_get() {
|
|
||||||
let archive = Archive::open("test/1.tar").unwrap();
|
|
||||||
let node = archive.get("1.txt");
|
|
||||||
assert!(node.is_some());
|
|
||||||
}
|
|
||||||
}
|
|
138
src/tar/node.rs
138
src/tar/node.rs
@ -1,138 +0,0 @@
|
|||||||
use crate::tar::{header::Owner, Error, FileType, Header};
|
|
||||||
use deku::prelude::*;
|
|
||||||
use std::{
|
|
||||||
fs::{File, Metadata},
|
|
||||||
io::{self, BufReader},
|
|
||||||
str,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct Node {
|
|
||||||
pub header: Header,
|
|
||||||
pub data: Vec<[u8; 512]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Node {
|
|
||||||
pub fn to_vec(self) -> Result<Vec<u8>, DekuError> {
|
|
||||||
let mut buf = self.header.to_bytes()?;
|
|
||||||
for block in self.data {
|
|
||||||
buf.extend(block.to_vec());
|
|
||||||
}
|
|
||||||
Ok(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write out a single file within the tar to a file or something with a
|
|
||||||
/// ``std::io::Write`` trait.
|
|
||||||
pub fn write<T: io::Write>(&self, mut input: T) -> Result<usize, Error> {
|
|
||||||
input.write_all(&self.header.to_bytes()?)?;
|
|
||||||
let mut written = 512;
|
|
||||||
for d in &self.data {
|
|
||||||
input.write_all(d)?;
|
|
||||||
written += d.len();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(written)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a `Node` in from a file or something with a ``std::io::Read`` trait.
|
|
||||||
pub fn read<T: io::Read>(mut input: T) -> Result<Self, Error> {
|
|
||||||
let mut h = vec![0u8; 512];
|
|
||||||
input.read_exact(&mut h)?;
|
|
||||||
|
|
||||||
let (_, header) = Header::from_bytes((&h, 0))?;
|
|
||||||
if header == Header::default() {
|
|
||||||
return Err(Error::EndOfTar);
|
|
||||||
}
|
|
||||||
if !header.validate_magic() {
|
|
||||||
return Err(Error::InvalidMagic);
|
|
||||||
}
|
|
||||||
if !header.validate_checksum()? {
|
|
||||||
return Err(Error::InvalidChecksum);
|
|
||||||
}
|
|
||||||
|
|
||||||
let chunks = (oct_to_dec(&header.size)? / 512) + 1;
|
|
||||||
Ok(Node {
|
|
||||||
header,
|
|
||||||
data: Node::chunk_file(&mut input, Some(chunks))?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Open and read a file from the ``filename`` argument to a `Node`.
|
|
||||||
pub fn read_file_to_tar(filename: &str) -> Result<Self, Error> {
|
|
||||||
let header = Header::new(filename)?;
|
|
||||||
if header.link_indicator[0] != FileType::Normal as u8 {
|
|
||||||
return Ok(Node {
|
|
||||||
header,
|
|
||||||
data: Vec::<[u8; 512]>::new(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let file = File::open(filename)?;
|
|
||||||
let mut reader = BufReader::new(file);
|
|
||||||
Ok(Node {
|
|
||||||
header,
|
|
||||||
data: Node::chunk_file(&mut reader, None)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a Node from in memory data, given the filename and metadata
|
|
||||||
pub fn read_data_to_tar(
|
|
||||||
data: &[u8],
|
|
||||||
filename: &str,
|
|
||||||
meta: &Metadata,
|
|
||||||
owner: Option<Owner>,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
let header = Header::new_from_meta(filename, meta, owner)?;
|
|
||||||
if header.link_indicator[0] != FileType::Normal as u8 {
|
|
||||||
return Ok(Node {
|
|
||||||
header,
|
|
||||||
data: Vec::<[u8; 512]>::new(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let mut reader = BufReader::new(data);
|
|
||||||
Ok(Node {
|
|
||||||
header,
|
|
||||||
data: Node::chunk_file(&mut reader, None)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read in and split a file into ``512`` byte chunks.
|
|
||||||
fn chunk_file<T: std::io::Read>(
|
|
||||||
file: &mut T,
|
|
||||||
max_chunks: Option<usize>,
|
|
||||||
) -> Result<Vec<[u8; 512]>, Error> {
|
|
||||||
/* Extract the file data from the tar file */
|
|
||||||
let mut out = Vec::<[u8; 512]>::new();
|
|
||||||
let mut n = if let Some(max) = max_chunks {
|
|
||||||
max
|
|
||||||
} else {
|
|
||||||
usize::MAX
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Carve out 512 bytes at a time */
|
|
||||||
let mut buf: [u8; 512] = [0; 512];
|
|
||||||
loop {
|
|
||||||
let len = file.read(&mut buf)?;
|
|
||||||
|
|
||||||
n -= 1;
|
|
||||||
|
|
||||||
/* If read len == 0, we've hit the EOF */
|
|
||||||
if len == 0 || n == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Save this chunk */
|
|
||||||
out.push(buf);
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn oct_to_dec(input: &[u8]) -> Result<usize, Error> {
|
|
||||||
/* Convert the &[u8] to string and remove the null byte */
|
|
||||||
let mut s = str::from_utf8(input)?.to_string();
|
|
||||||
s.pop();
|
|
||||||
|
|
||||||
/* Convert to usize from octal */
|
|
||||||
Ok(usize::from_str_radix(&s, 8)?)
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
use {
|
|
||||||
crate::Version,
|
|
||||||
chrono::{offset::Utc, DateTime},
|
|
||||||
serde::{Deserialize, Serialize},
|
|
||||||
std::{cmp::Ordering, error::Error, fmt, str::FromStr},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
/// Represents a Git revision
|
|
||||||
pub struct GitRev {
|
|
||||||
/// 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 {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
self.datetime.partial_cmp(&other.datetime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for GitRev {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Version> for GitRev {
|
|
||||||
type Error = ParseGitRevError;
|
|
||||||
|
|
||||||
fn try_from(value: Version) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
Version::Git(g) => Ok(g),
|
|
||||||
_ => Err(ParseGitRevError),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use std::{thread, time::Duration};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ord() {
|
|
||||||
let a = GitRev {
|
|
||||||
hash: "aaab".to_string(),
|
|
||||||
datetime: Utc::now(),
|
|
||||||
};
|
|
||||||
thread::sleep(Duration::from_millis(10));
|
|
||||||
let b = GitRev {
|
|
||||||
hash: "aaaa".to_string(),
|
|
||||||
datetime: Utc::now(),
|
|
||||||
};
|
|
||||||
assert!(a < b);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,177 +0,0 @@
|
|||||||
use {
|
|
||||||
serde::{Deserialize, Serialize},
|
|
||||||
std::{error::Error, fmt, str::FromStr},
|
|
||||||
};
|
|
||||||
|
|
||||||
mod gitrev;
|
|
||||||
mod rapid;
|
|
||||||
mod semver;
|
|
||||||
|
|
||||||
pub use {gitrev::GitRev, rapid::Rapid, semver::SemVer};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
/// An enum representing the most common versioning schemes.
|
|
||||||
/// Each scheme must implement `Eq` and `Ord` with itself, and
|
|
||||||
/// may optionally implement `PartialEq` and `PartialOrd` with
|
|
||||||
/// other schemes. `Number`, `Rapid`, and `SemVer` do this, while
|
|
||||||
/// Git can only compare with itself.
|
|
||||||
pub enum Version {
|
|
||||||
/// A single replease number, as in Firefox 102
|
|
||||||
Number(u32),
|
|
||||||
/// Rapid versioning consists of two numbers separated by
|
|
||||||
/// a dot (.) character, and is used notably by Gnome
|
|
||||||
Rapid(Rapid),
|
|
||||||
/// SemVer is the most common versioning scheme and consists
|
|
||||||
/// of three dot (.) separated numbers. The numbers are major,
|
|
||||||
/// minor, and patch respctively.
|
|
||||||
SemVer(SemVer),
|
|
||||||
/// A git revision. Use of this scheme is to be avoided in
|
|
||||||
/// official packaging as it cannot be readily compared with
|
|
||||||
/// other versioning schemes for dependency resolution.
|
|
||||||
Git(GitRev),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Version {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::SemVer(SemVer {
|
|
||||||
major: 0,
|
|
||||||
minor: 1,
|
|
||||||
patch: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Version {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Number(n) => write!(f, "{n}"),
|
|
||||||
Self::SemVer(s) => write!(f, "{s}"),
|
|
||||||
Self::Rapid(r) => write!(f, "{r}"),
|
|
||||||
Self::Git(g) => write!(f, "{g}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SemVer> for Version {
|
|
||||||
fn from(value: SemVer) -> Self {
|
|
||||||
Self::SemVer(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Rapid> for Version {
|
|
||||||
fn from(value: Rapid) -> Self {
|
|
||||||
Self::Rapid(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<GitRev> for Version {
|
|
||||||
fn from(value: GitRev) -> Self {
|
|
||||||
Self::Git(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u32> for Version {
|
|
||||||
fn from(value: u32) -> Self {
|
|
||||||
Self::Number(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for Version {
|
|
||||||
#[allow(clippy::many_single_char_names)]
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
match (self, other) {
|
|
||||||
(Self::Number(s), Self::Number(o)) => s.partial_cmp(o),
|
|
||||||
(Self::Number(s), Self::Rapid(o)) => s.partial_cmp(o),
|
|
||||||
(Self::Number(s), Self::SemVer(o)) => s.partial_cmp(o),
|
|
||||||
(Self::Rapid(s), Self::Number(o)) => s.partial_cmp(o),
|
|
||||||
(Self::Rapid(s), Self::Rapid(o)) => s.partial_cmp(o),
|
|
||||||
(Self::Rapid(s), Self::SemVer(o)) => s.partial_cmp(o),
|
|
||||||
(Self::SemVer(s), Self::Number(o)) => s.partial_cmp(o),
|
|
||||||
(Self::SemVer(s), Self::Rapid(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),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Version {
|
|
||||||
#[allow(clippy::many_single_char_names)]
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
match (self, other) {
|
|
||||||
(Self::Number(s), Self::Number(o)) => s.eq(o),
|
|
||||||
(Self::Number(s), Self::Rapid(o)) => s.eq(o),
|
|
||||||
(Self::Number(s), Self::SemVer(o)) => s.eq(o),
|
|
||||||
(Self::Rapid(s), Self::Number(o)) => s.eq(o),
|
|
||||||
(Self::Rapid(s), Self::Rapid(o)) => s.eq(o),
|
|
||||||
(Self::Rapid(s), Self::SemVer(o)) => s.eq(o),
|
|
||||||
(Self::SemVer(s), Self::Number(o)) => s.eq(o),
|
|
||||||
(Self::SemVer(s), Self::Rapid(o)) => s.eq(o),
|
|
||||||
(Self::SemVer(s), Self::SemVer(o)) => s.eq(o),
|
|
||||||
(Self::Git(s), Self::Git(o)) => s.eq(o),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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::<Rapid>() {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cmp_semver_rapid_gt() {
|
|
||||||
let sem = "1.42.1".parse::<SemVer>().unwrap();
|
|
||||||
let rpd = "1.42".parse::<Rapid>().unwrap();
|
|
||||||
assert!(sem > rpd);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cmp_semver_rapid_eq() {
|
|
||||||
let sem = "1.42.0".parse::<SemVer>().unwrap();
|
|
||||||
let rpd = "1.42".parse::<Rapid>().unwrap();
|
|
||||||
assert!(sem == rpd);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cmp_semver_num_gt() {
|
|
||||||
let sem = Version::SemVer("42.69.0".parse().unwrap());
|
|
||||||
let num = Version::Number(42);
|
|
||||||
assert!(sem > num);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cmp_semver_num_eq() {
|
|
||||||
let sem = Version::SemVer("42.0.0".parse().unwrap());
|
|
||||||
let num = Version::Number(42);
|
|
||||||
assert_eq!(sem, num);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,214 +0,0 @@
|
|||||||
use crate::SemVer;
|
|
||||||
|
|
||||||
use {
|
|
||||||
crate::Version,
|
|
||||||
serde::{Deserialize, Serialize},
|
|
||||||
std::{cmp::Ordering, error::Error, fmt, num::ParseIntError, str::FromStr},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct Rapid {
|
|
||||||
pub major: u32,
|
|
||||||
pub minor: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Rapid {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}.{}", self.major, self.minor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for Rapid {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
match self.major.partial_cmp(&other.major) {
|
|
||||||
Some(Ordering::Greater) => Some(Ordering::Greater),
|
|
||||||
Some(Ordering::Less) => Some(Ordering::Less),
|
|
||||||
Some(Ordering::Equal) => self.minor.partial_cmp(&other.minor),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for Rapid {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
match self.major.cmp(&other.major) {
|
|
||||||
Ordering::Greater => Ordering::Greater,
|
|
||||||
Ordering::Less => Ordering::Less,
|
|
||||||
Ordering::Equal => self.minor.cmp(&other.minor),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<u32> for Rapid {
|
|
||||||
fn eq(&self, other: &u32) -> bool {
|
|
||||||
self.major == *other && self.minor == 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd<u32> for Rapid {
|
|
||||||
fn partial_cmp(&self, other: &u32) -> Option<Ordering> {
|
|
||||||
match self.major.partial_cmp(other) {
|
|
||||||
Some(Ordering::Greater) => Some(Ordering::Greater),
|
|
||||||
Some(Ordering::Less) => Some(Ordering::Less),
|
|
||||||
None => None,
|
|
||||||
Some(Ordering::Equal) => {
|
|
||||||
if self.minor == 0 {
|
|
||||||
Some(Ordering::Equal)
|
|
||||||
} else {
|
|
||||||
Some(Ordering::Greater)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<SemVer> for Rapid {
|
|
||||||
fn eq(&self, other: &SemVer) -> bool {
|
|
||||||
other.eq(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd<SemVer> for Rapid {
|
|
||||||
fn partial_cmp(&self, other: &SemVer) -> Option<Ordering> {
|
|
||||||
match other.partial_cmp(self) {
|
|
||||||
Some(Ordering::Less) => Some(Ordering::Greater),
|
|
||||||
Some(Ordering::Greater) => Some(Ordering::Less),
|
|
||||||
Some(Ordering::Equal) => Some(Ordering::Equal),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<Rapid> for u32 {
|
|
||||||
fn eq(&self, other: &Rapid) -> bool {
|
|
||||||
other.eq(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd<Rapid> for u32 {
|
|
||||||
fn partial_cmp(&self, other: &Rapid) -> Option<Ordering> {
|
|
||||||
match other.partial_cmp(self) {
|
|
||||||
Some(Ordering::Equal) => Some(Ordering::Equal),
|
|
||||||
Some(Ordering::Less) => Some(Ordering::Greater),
|
|
||||||
Some(Ordering::Greater) => Some(Ordering::Less),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct ParseRapidError;
|
|
||||||
|
|
||||||
impl fmt::Display for ParseRapidError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "Error parsing Rapid version")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for ParseRapidError {}
|
|
||||||
|
|
||||||
impl From<ParseIntError> for ParseRapidError {
|
|
||||||
fn from(_value: ParseIntError) -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Rapid {
|
|
||||||
type Err = ParseRapidError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let split = s.split('.').collect::<Vec<_>>();
|
|
||||||
match split.len() {
|
|
||||||
2 => {
|
|
||||||
let major = split.first().unwrap().parse::<u32>()?;
|
|
||||||
let minor = split.get(1).unwrap().parse::<u32>()?;
|
|
||||||
Ok(Self { major, minor })
|
|
||||||
}
|
|
||||||
_ => Err(ParseRapidError),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Version> for Rapid {
|
|
||||||
type Error = ParseRapidError;
|
|
||||||
|
|
||||||
fn try_from(value: Version) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
Version::SemVer(s) => {
|
|
||||||
if s.patch == 0 {
|
|
||||||
Ok(Self {
|
|
||||||
major: s.major,
|
|
||||||
minor: s.minor,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(ParseRapidError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Version::Rapid(s) => Ok(s),
|
|
||||||
Version::Number(major) => Ok(Self { major, minor: 0 }),
|
|
||||||
Version::Git(_) => Err(ParseRapidError),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_semver() {
|
|
||||||
assert_eq!(
|
|
||||||
"93.0".parse::<Rapid>(),
|
|
||||||
Ok(Rapid {
|
|
||||||
major: 93,
|
|
||||||
minor: 0,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rapid_num_eq() {
|
|
||||||
let rapid = Rapid {
|
|
||||||
major: 42,
|
|
||||||
minor: 0,
|
|
||||||
};
|
|
||||||
assert_eq!(rapid, 42);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rapid_num_gt() {
|
|
||||||
let rapid = Rapid {
|
|
||||||
major: 1,
|
|
||||||
minor: 42,
|
|
||||||
};
|
|
||||||
assert!(rapid > 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rapid_semver_eq() {
|
|
||||||
let rapid = Rapid {
|
|
||||||
major: 42,
|
|
||||||
minor: 69,
|
|
||||||
};
|
|
||||||
let semver = SemVer {
|
|
||||||
major: 42,
|
|
||||||
minor: 69,
|
|
||||||
patch: 0,
|
|
||||||
};
|
|
||||||
assert_eq!(rapid, semver);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rapid_semver_lt() {
|
|
||||||
let rapid = Rapid {
|
|
||||||
major: 42,
|
|
||||||
minor: 69,
|
|
||||||
};
|
|
||||||
let semver = SemVer {
|
|
||||||
major: 42,
|
|
||||||
minor: 69,
|
|
||||||
patch: 1,
|
|
||||||
};
|
|
||||||
assert!(rapid < semver);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,205 +0,0 @@
|
|||||||
use {
|
|
||||||
crate::{Rapid, Version},
|
|
||||||
serde::{Deserialize, Serialize},
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
match self.major.partial_cmp(&other.major) {
|
|
||||||
Some(Ordering::Greater) => Some(Ordering::Greater),
|
|
||||||
Some(Ordering::Less) => Some(Ordering::Less),
|
|
||||||
None => None,
|
|
||||||
Some(Ordering::Equal) => match self.minor.partial_cmp(&other.minor) {
|
|
||||||
Some(Ordering::Greater) => Some(Ordering::Greater),
|
|
||||||
Some(Ordering::Less) => Some(Ordering::Less),
|
|
||||||
None => None,
|
|
||||||
Some(Ordering::Equal) => self.patch.partial_cmp(&other.patch),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for SemVer {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
match self.major.cmp(&other.major) {
|
|
||||||
Ordering::Greater => Ordering::Greater,
|
|
||||||
Ordering::Less => Ordering::Less,
|
|
||||||
Ordering::Equal => match self.minor.cmp(&other.minor) {
|
|
||||||
Ordering::Greater => Ordering::Greater,
|
|
||||||
Ordering::Less => Ordering::Less,
|
|
||||||
Ordering::Equal => self.patch.cmp(&other.patch),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<Rapid> for SemVer {
|
|
||||||
fn eq(&self, other: &Rapid) -> bool {
|
|
||||||
self.major == other.major && self.minor == other.minor && self.patch == 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd<Rapid> for SemVer {
|
|
||||||
fn partial_cmp(&self, other: &Rapid) -> Option<std::cmp::Ordering> {
|
|
||||||
match self.major.partial_cmp(&other.major) {
|
|
||||||
Some(Ordering::Greater) => Some(Ordering::Greater),
|
|
||||||
Some(Ordering::Less) => Some(Ordering::Less),
|
|
||||||
None => None,
|
|
||||||
Some(Ordering::Equal) => match self.minor.partial_cmp(&other.minor) {
|
|
||||||
Some(Ordering::Greater) => Some(Ordering::Greater),
|
|
||||||
Some(Ordering::Less) => Some(Ordering::Less),
|
|
||||||
None => None,
|
|
||||||
Some(Ordering::Equal) => match self.patch {
|
|
||||||
0 => Some(Ordering::Equal),
|
|
||||||
_ => Some(Ordering::Greater),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<u32> for SemVer {
|
|
||||||
fn eq(&self, other: &u32) -> bool {
|
|
||||||
self.major == *other && self.minor == 0 && self.patch == 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd<u32> for SemVer {
|
|
||||||
fn partial_cmp(&self, other: &u32) -> Option<Ordering> {
|
|
||||||
match self.major.cmp(other) {
|
|
||||||
Ordering::Greater => Some(Ordering::Greater),
|
|
||||||
Ordering::Less => Some(Ordering::Less),
|
|
||||||
Ordering::Equal => {
|
|
||||||
if self.minor == 0 && self.patch == 0 {
|
|
||||||
Some(Ordering::Equal)
|
|
||||||
} else {
|
|
||||||
Some(Ordering::Greater)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<SemVer> for u32 {
|
|
||||||
fn eq(&self, other: &SemVer) -> bool {
|
|
||||||
other.eq(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd<SemVer> for u32 {
|
|
||||||
fn partial_cmp(&self, other: &SemVer) -> Option<Ordering> {
|
|
||||||
match other.partial_cmp(self) {
|
|
||||||
Some(Ordering::Less) => Some(Ordering::Greater),
|
|
||||||
Some(Ordering::Greater) => Some(Ordering::Less),
|
|
||||||
Some(Ordering::Equal) => Some(Ordering::Equal),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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.first().unwrap().parse::<u32>()?;
|
|
||||||
let minor = split.get(1).unwrap().parse::<u32>()?;
|
|
||||||
let patch = split.get(2).unwrap().parse::<u32>()?;
|
|
||||||
Ok(Self {
|
|
||||||
major,
|
|
||||||
minor,
|
|
||||||
patch,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => Err(ParseSemVerError),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Version> for SemVer {
|
|
||||||
type Error = ParseSemVerError;
|
|
||||||
fn try_from(value: Version) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
Version::SemVer(s) => Ok(SemVer {
|
|
||||||
major: s.major,
|
|
||||||
minor: s.minor,
|
|
||||||
patch: s.patch,
|
|
||||||
}),
|
|
||||||
Version::Rapid(r) => Ok(SemVer {
|
|
||||||
major: r.major,
|
|
||||||
minor: r.minor,
|
|
||||||
patch: 0,
|
|
||||||
}),
|
|
||||||
Version::Number(n) => Ok(Self {
|
|
||||||
major: n,
|
|
||||||
minor: 0,
|
|
||||||
patch: 0,
|
|
||||||
}),
|
|
||||||
Version::Git(_) => 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
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cmp_semver_num_gt() {
|
|
||||||
let sem = "42.69.0".parse::<SemVer>().unwrap();
|
|
||||||
let num = 42;
|
|
||||||
assert!(sem > num);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cmp_semver_num_eq() {
|
|
||||||
let sem = "42.0.0".parse::<SemVer>().unwrap();
|
|
||||||
let num = 42;
|
|
||||||
assert_eq!(sem, num);
|
|
||||||
}
|
|
||||||
}
|
|
BIN
test/1.tar
BIN
test/1.tar
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
This is a test file.
|
|
BIN
test/2.tar
BIN
test/2.tar
Binary file not shown.
Loading…
Reference in New Issue
Block a user