feat: introduce more versioning options
Signed-off-by: sisungo <[email protected]>
This commit is contained in:
+14
-11
@@ -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
@@ -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
@@ -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
@@ -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(());
|
||||
}
|
||||
|
||||
@@ -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: "无效的预发布版本"
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user