feat: introduce more versioning options #2

Open
sisungo wants to merge 1 commits from sisungo/packie:versioning2 into master
9 changed files with 149 additions and 56 deletions
+14 -11
View File
@@ -28,17 +28,20 @@ pub fn progress_style_multidownload() -> ProgressStyle {
ProgressStyle::with_template(&lines.join("\n")).unwrap()
}
pub fn yesno(prompt: &str) -> bool {
eprint!("{prompt} [y/N] ");
["y", "Y"].contains(
&std::io::stdin()
.lines()
.next()
.transpose()
.unwrap_or_default()
.unwrap_or_default()
.trim(),
)
pub fn yesno(prompt: &str, default: bool) -> bool {
if default {
eprint!("{prompt} [Y/n] ");
} else {
eprint!("{prompt} [y/N] ");
}
let line = std::io::stdin()
.lines()
.next()
.transpose()
.unwrap_or_default()
.unwrap_or_default();
let line = line.trim();
["y", "Y"].contains(&line) || (line.is_empty() && default)
}
pub fn print_list(indent: u32, list: impl Iterator<Item = impl AsRef<str>>) {
+17 -6
View File
@@ -25,6 +25,7 @@ pub fn main(cli: Cli) -> Result<(), crate::Error> {
for i in found.iter() {
writeln!(&mut note, " - {}", i.pkg_manifest.pkg_ident()).unwrap();
}
let note = note.trim().into();
return Err(crate::Error {
message: t!("info.error_pkgspec_not_unique").into(),
@@ -62,13 +63,23 @@ fn print_pkgmanifest(pkg_manifest: &PkgManifest) {
pkg_manifest.arch
);
}
println!("{}:", t!("info.print_pkgmanifest.dependencies"));
for i in pkg_manifest.dependencies.iter() {
println!(" - {i}");
if !pkg_manifest.maintainers.is_empty() {
println!("{}:", t!("info.print_pkgmanifest.maintainers"));
for i in pkg_manifest.maintainers.iter() {
println!(" - {i}");
}
}
println!("{}:", t!("info.print_pkgmanifest.recommendations"));
for i in pkg_manifest.recommendations.iter() {
println!(" - {i}");
if !pkg_manifest.dependencies.is_empty() {
println!("{}:", t!("info.print_pkgmanifest.dependencies"));
for i in pkg_manifest.dependencies.iter() {
println!(" - {i}");
}
}
if !pkg_manifest.recommendations.is_empty() {
println!("{}:", t!("info.print_pkgmanifest.recommendations"));
for i in pkg_manifest.recommendations.iter() {
println!(" - {i}");
}
}
}
+1 -1
View File
@@ -65,7 +65,7 @@ pub fn main(cli: Cli) -> Result<(), crate::Error> {
);
if cli.yes {
eprintln!("y");
} else if !yesno(&t!("install.confirm_installation")) {
} else if !yesno(&t!("install.confirm_installation"), true) {
eprintln!("{}", t!("install.aborting"));
return Ok(());
}
+21 -4
View File
@@ -4,8 +4,10 @@ use crate::{
};
use anyhow::anyhow;
use clap::Parser;
use console::style;
use packie::{PackieBuilder, package::PkgSpec};
use rust_i18n::t;
use std::fmt::Write;
#[derive(Debug, Parser)]
pub struct Cli {
@@ -21,18 +23,33 @@ pub fn main(cli: Cli) -> Result<(), crate::Error> {
for pkgspec in cli.items {
let mut found = packie.search_installation(&pkgspec)?;
if found.is_empty() {
return Err(error!("remove.package_not_found"));
return Err(error!("remove.package_not_found", pkgspec = pkgspec));
}
if found.len() != 1 {
return Err(error!("remove.pkgspec_not_unique"));
let mut note = String::new();
writeln!(&mut note, "{}", t!("remove.note_pkgspec_not_unique")).unwrap();
for i in found.iter() {
writeln!(&mut note, " - {}", i.pkg_manifest.pkg_ident()).unwrap();
}
let note = note.trim().into();
return Err(crate::Error {
message: t!("remove.error_pkgspec_not_unique", pkgspec = pkgspec).into(),
note: Some(note),
});
}
packages.push(found.remove(0));
}
println!("{}", t!("remove.prompt_to_remove"));
print_list(4, packages.iter().map(|x| x.install_pkgspec.to_string()));
print_list(
4,
packages
.iter()
.map(|x| style(&x.install_pkgspec).green().to_string()),
);
if cli.yes {
println!("y");
} else if !yesno(&t!("remove.confirm_removal")) {
} else if !yesno(&t!("remove.confirm_removal"), false) {
println!("{}", t!("remove.aborting"));
return Ok(());
}
+10
View File
@@ -59,3 +59,13 @@ BuildPackieError.Lock:
en: "failed to lock packie database: %{error}"
zh-CN: "无法锁定 Packie 数据库:%{error}"
ja: "Packie データベースのロックに失敗しました:%{error}"
version.VersionError.ParseNamespace:
en: "invalid version namespace"
zh-CN: "无效的版本命名空间"
ja: ""
version.VersionError.ParseMain:
en: "invalid version code"
zh-CN: "无效的版本号"
version.VersionError.ParsePrerelease:
en: "invalid version prerelease"
zh-CN: "无效的预发布版本"
+15 -7
View File
@@ -71,6 +71,10 @@ info.print_pkgmanifest.description:
en: "Description"
zh-CN: "描述"
ja: "説明"
info.print_pkgmanifest.maintainers:
en: "Maintainers"
zh-CN: "维护者"
ja: "メンテナー"
info.print_install_misc.install_pkgspec:
en: "Install package spec"
zh-CN: "安装请求"
@@ -84,13 +88,17 @@ info.print_install_misc.install_date:
zh-CN: "安装日期"
ja: "インストール日時"
remove.package_not_found:
en: "The specified package was not found."
zh-CN: "找不到指定的包。"
ja: "指定されたパッケージが見つかりません。"
remove.pkgspec_not_unique:
en: "The specified package spec was not unique."
zh-CN: "指定的包规范匹配了多个包。"
ja: "指定されたパッケージ指定が複数のパッケージに一致しました。"
en: "The specified package \"%{pkgspec}\" was not found."
zh-CN: "找不到指定的包 \"%{pkgspec}\"。"
ja: "指定されたパッケージ \"%{pkgspec}\" が見つかりません。"
remove.error_pkgspec_not_unique:
en: "The specified package spec \"%{pkgspec}\" was not unique."
zh-CN: "指定的包规范 \"%{pkgspec}\" 匹配了多个包。"
ja: "指定されたパッケージ指定 \"%{pkgspec}\" が複数のパッケージに一致しました。"
remove.note_pkgspec_not_unique:
en: "The following packages were matched:"
zh-CN: "匹配到了下面的包:"
ja: "以下のパッケージが一致しました:"
remove.action_failure:
en: "Failed to remove the specified package: %{error}"
zh-CN: "无法移除指定的包:%{error}"
+9
View File
@@ -1,4 +1,5 @@
use rust_i18n::t;
use rustc_hash::FxHashSet;
use std::{
fs::{FileType, ReadDir},
path::{Path, PathBuf},
@@ -65,12 +66,20 @@ pub struct TreeDirEntry {
}
pub fn du_dir(path: impl AsRef<Path>) -> std::io::Result<u64> {
use std::os::unix::fs::MetadataExt;
let mut size = 0;
let mut inodes = FxHashSet::default();
let path = path.as_ref();
for ent in TreeDir::new(path)? {
let ent = ent?;
let metadata = std::fs::symlink_metadata(path.join(&ent.relpath))?;
let inode = (metadata.dev(), metadata.ino());
if inodes.contains(&inode) {
continue;
}
size += metadata.len();
inodes.insert(inode);
}
Ok(size)
}
+32 -3
View File
@@ -88,6 +88,10 @@ pub struct PkgManifest {
#[serde(default)]
pub description: String,
/// Package maintainers.
#[serde(default)]
pub maintainers: Vec<String>,
/// Package dependencies.
#[serde(default)]
pub dependencies: Vec<PkgSpec>,
@@ -122,6 +126,9 @@ impl PkgManifest {
}
}
/// Characters that cannot appear in a package name.
const PKGNAME_FORBIDDEN_CHARS: &str = " ();@'\"*!#$%^&[]{}\\/:<>?~`=\n\t\r";
/// A package specifier.
#[derive(Debug, Clone)]
pub struct PkgSpec {
@@ -149,7 +156,7 @@ impl From<PkgIdent> for PkgSpec {
}
}
impl FromStr for PkgSpec {
type Err = crate::version::VersionError;
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(pkgident) = PkgIdent::from_str(s) {
@@ -202,6 +209,9 @@ impl FromStr for PkgSpec {
arch = Some(arch_str);
}
let name = name.trim_end().to_string();
if name.contains(|ch| PKGNAME_FORBIDDEN_CHARS.contains(ch)) {
return Err(ParseError::Pkgname(name));
}
Ok(Self {
name,
@@ -237,7 +247,7 @@ impl Display for PkgIdent {
}
}
impl FromStr for PkgIdent {
type Err = crate::version::VersionError;
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut name = String::new();
@@ -257,8 +267,12 @@ impl FromStr for PkgIdent {
arch.push(c);
}
}
let name: String = name.trim().into();
if name.contains(|ch| PKGNAME_FORBIDDEN_CHARS.contains(ch)) {
return Err(ParseError::Pkgname(name));
}
Ok(Self {
name: name.trim().into(),
name,
version: version.trim().parse()?,
arch: arch.trim().into(),
})
@@ -294,3 +308,18 @@ impl FromStr for AbsPkgIdent {
}
}
crate::impl_serde_str!(AbsPkgIdent);
/// An error caused by parsing package spec or ident.
#[derive(Debug, thiserror::Error)]
pub enum ParseError {
#[error("invalid package name: {0}")]
Pkgname(String),
#[error("{0}")]
Version(crate::version::VersionError),
}
impl From<crate::version::VersionError> for ParseError {
fn from(value: crate::version::VersionError) -> Self {
Self::Version(value)
}
}
+30 -24
View File
@@ -1,6 +1,7 @@
//! Parsing and matching of software versions.
use std::{cmp::Ordering, fmt::Display, str::FromStr};
use rust_i18n::t;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Version {
@@ -131,6 +132,9 @@ pub enum VersionFilter {
#[default]
Any,
/// The '..., ..., ...' filter.
All(Vec<VersionFilter>),
/// The '<=' filter.
Lte(Version),
@@ -140,11 +144,11 @@ pub enum VersionFilter {
/// The '=' filter.
Eq(Version),
/// The '>=xxx, <=xxx' filter.
Between(Version, Version),
/// The '~' (or default) filter.
Compatible(Version),
/// The '+' filter.
WithMeta(String),
}
impl VersionFilter {
pub fn matches(&self, version: &Version) -> bool {
@@ -153,8 +157,9 @@ impl VersionFilter {
Self::Lte(other) => version <= other,
Self::Gte(other) => version >= other,
Self::Eq(other) => version == other,
Self::Between(a, b) => version >= a && version <= b,
Self::All(cond) => cond.iter().all(|x| x.matches(version)),
Self::Compatible(other) => semver_is_compatible(version, other),
Self::WithMeta(meta) => version.meta.as_deref() == Some(meta),
}
}
}
@@ -165,6 +170,13 @@ impl FromStr for VersionFilter {
if s == "*" {
return Ok(Self::Any);
}
if s.contains(',') {
let mut all = Vec::new();
for i in s.split(',') {
all.push(i.trim().parse()?);
}
return Ok(Self::All(all));
}
if let Some(version) = s.strip_prefix('=') {
return Ok(Self::Eq(version.parse()?));
}
@@ -174,19 +186,8 @@ impl FromStr for VersionFilter {
if let Some(version) = s.strip_prefix("<=") {
return Ok(Self::Lte(version.parse()?));
}
if let Some((a, b)) = s.split_once(',') {
let (mut smaller, mut greater) = (None, None);
for i in [a.trim(), b.trim()] {
if let Some(version) = i.strip_prefix(">=") {
smaller = Some(version.parse()?);
} else if let Some(version) = i.strip_prefix("<=") {
greater = Some(version.parse()?);
}
}
return Ok(Self::Between(
smaller.ok_or(VersionError::InvalidRange)?,
greater.ok_or(VersionError::InvalidRange)?,
));
if let Some(meta) = s.strip_prefix("+") {
return Ok(Self::WithMeta(meta.into()));
}
if let Some(version) = s.strip_prefix("~") {
return Ok(Self::Compatible(version.parse()?));
@@ -201,8 +202,16 @@ impl Display for VersionFilter {
Self::Lte(ver) => write!(f, "<={ver}"),
Self::Gte(ver) => write!(f, ">={ver}"),
Self::Eq(ver) => write!(f, "={ver}"),
Self::Between(a, b) => write!(f, ">={a}, <={b}"),
Self::All(cond) => write!(
f,
"{}",
cond.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ")
),
Self::Compatible(ver) => write!(f, "~{ver}"),
Self::WithMeta(meta) => write!(f, "+{meta}"),
}
}
}
@@ -211,17 +220,14 @@ crate::impl_serde_str!(VersionFilter);
/// An error caused by dealing with version or version filter strings.
#[derive(Debug, Clone, thiserror::Error)]
pub enum VersionError {
#[error("invalid namespace")]
#[error("{}", t!("version.VersionError.ParseNamespace"))]
ParseNamespace,
#[error("invalid main part")]
#[error("{}", t!("version.VersionError.ParseMain"))]
ParseMain,
#[error("invalid prerelease part")]
#[error("{}", t!("version.VersionError.ParsePrerelease"))]
ParsePrerelease,
#[error("invalid version range")]
InvalidRange,
}
fn semver_is_compatible(a: &Version, b: &Version) -> bool {