@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "packie-cli"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "packie"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
chrono = "0.4"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
console = "0.16"
|
||||
nix = { version = "0.31", features = ["sched"] }
|
||||
itertools = "0.15"
|
||||
packie = { path = "../" }
|
||||
rust-i18n = "4"
|
||||
indicatif = "0.18"
|
||||
dialoguer = "0.12"
|
||||
@@ -0,0 +1,78 @@
|
||||
use clap::Parser;
|
||||
use packie::PackieBuilder;
|
||||
use rust_i18n::t;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
#[arg(short, long, default_value = "true")]
|
||||
packages: bool,
|
||||
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let packie = PackieBuilder::new().build()?;
|
||||
|
||||
let repos_dir = packie.cache().repos_dir();
|
||||
|
||||
if cli.packages {
|
||||
clean_packages(&cli, &repos_dir);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clean_packages(cli: &Cli, repos_dir: &PathBuf) {
|
||||
let read_repos_dir_errx =
|
||||
|e: &std::io::Error| eprintln!("{}", t!("clean.warn_repos_dir", error = e));
|
||||
let Ok(repos) = std::fs::read_dir(&repos_dir).inspect_err(read_repos_dir_errx) else {
|
||||
return;
|
||||
};
|
||||
for repo in repos {
|
||||
let Ok(repo) = repo.inspect_err(read_repos_dir_errx) else {
|
||||
break;
|
||||
};
|
||||
let read_repo_dir_errx = |e: &std::io::Error| {
|
||||
eprintln!(
|
||||
"{}",
|
||||
t!(
|
||||
"clean.warn_repo_dir",
|
||||
name = repo.file_name().display().to_string(),
|
||||
error = e
|
||||
)
|
||||
)
|
||||
};
|
||||
let Ok(repo) = std::fs::read_dir(repo.path()).inspect_err(read_repo_dir_errx) else {
|
||||
continue;
|
||||
};
|
||||
for file in repo {
|
||||
let Ok(file) = file.inspect_err(read_repo_dir_errx) else {
|
||||
break;
|
||||
};
|
||||
if file.file_name().as_encoded_bytes().ends_with(b".pkg") {
|
||||
match std::fs::remove_file(file.path()) {
|
||||
Ok(()) => {
|
||||
if cli.verbose {
|
||||
eprintln!(
|
||||
"{}",
|
||||
t!("clean.info_remove_file", path = file.path().display())
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"{}",
|
||||
t!(
|
||||
"clean.warn_remove_file",
|
||||
path = file.path().display(),
|
||||
error = err
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
use chrono::{Local, TimeZone};
|
||||
use console::{Term, style};
|
||||
use indicatif::ProgressStyle;
|
||||
use rust_i18n::t;
|
||||
|
||||
pub fn progress_style_multidownload() -> ProgressStyle {
|
||||
let mut lines = Vec::with_capacity(3);
|
||||
let (_, cols) = Term::stderr().size();
|
||||
|
||||
let short_first_line = format!("{{spinner}} {prefix}", prefix = style("{prefix}").green());
|
||||
let info_line = format!(
|
||||
"{speed}{{bytes_per_sec}} {current}{{bytes}} {total}{{total_bytes}} {eta}{{eta}}",
|
||||
speed = t!("common.progress_style.speed"),
|
||||
current = t!("common.progress_style.current"),
|
||||
total = t!("common.progress_style.total"),
|
||||
eta = t!("common.progress_style.eta"),
|
||||
);
|
||||
let long_first_line = format!("{short_first_line} {info_line}");
|
||||
if cols > 100 {
|
||||
lines.push(&long_first_line[..]);
|
||||
} else {
|
||||
lines.push(&short_first_line[..]);
|
||||
lines.push(&info_line[..]);
|
||||
}
|
||||
|
||||
lines.push("{wide_bar}");
|
||||
|
||||
ProgressStyle::with_template(&lines.join("\n")).unwrap()
|
||||
}
|
||||
|
||||
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>>) {
|
||||
let (_, cols) = Term::stdout().size();
|
||||
let mut line_used: usize = 0;
|
||||
for i in list {
|
||||
let mut i = i.as_ref().to_string();
|
||||
if line_used >= cols as _ {
|
||||
line_used = 0;
|
||||
println!();
|
||||
}
|
||||
if i.contains(' ') {
|
||||
i = format!("'{i}'");
|
||||
}
|
||||
let to_print = format!("{}{}", " ".repeat(indent as _), i);
|
||||
print!("{to_print}");
|
||||
line_used += to_print.len();
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
pub fn format_size(bytes: u64) -> String {
|
||||
match bytes {
|
||||
..1000 => format!("{bytes} B"),
|
||||
..1_000_000 => format!("{} kB", bytes / 1000),
|
||||
..1_000_000_000 => format!("{:.2} MB", (bytes as f64) / 1000000.),
|
||||
_ => format!("{:.3} GB", (bytes as f64) / 1000000000.),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_datetime(timestamp: i64) -> String {
|
||||
let Some(datetime) = Local.timestamp_opt(timestamp, 0).earliest() else {
|
||||
return "?".into();
|
||||
};
|
||||
datetime.to_string()
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
use crate::{common::format_size, error};
|
||||
use clap::Parser;
|
||||
use packie::{
|
||||
PackieBuilder,
|
||||
local::Installation,
|
||||
package::{PkgManifest, PkgSpec},
|
||||
};
|
||||
use rust_i18n::t;
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
pkgspec: PkgSpec,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let mut packie = PackieBuilder::new().readonly(true).build()?;
|
||||
let mut found = packie.search_installation(&cli.pkgspec)?;
|
||||
if found.len() == 0 {
|
||||
return Err(error!("info.no_package_found"));
|
||||
}
|
||||
if found.len() != 1 {
|
||||
let mut note = String::new();
|
||||
writeln!(&mut note, "{}", t!("info.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!("info.error_pkgspec_not_unique").into(),
|
||||
note: Some(note),
|
||||
});
|
||||
}
|
||||
let found = found.remove(0);
|
||||
|
||||
print_pkgmanifest(&found.pkg_manifest);
|
||||
print_install_misc(&found);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_pkgmanifest(pkg_manifest: &PkgManifest) {
|
||||
println!(
|
||||
"{}: {}",
|
||||
t!("info.print_pkgmanifest.name"),
|
||||
pkg_manifest.name
|
||||
);
|
||||
println!(
|
||||
"{}: {}",
|
||||
t!("info.print_pkgmanifest.version"),
|
||||
pkg_manifest.version
|
||||
);
|
||||
println!(
|
||||
"{}: {}",
|
||||
t!("info.print_pkgmanifest.arch"),
|
||||
pkg_manifest.arch
|
||||
);
|
||||
if !pkg_manifest.description.is_empty() {
|
||||
println!(
|
||||
"{}: {}",
|
||||
t!("info.print_pkgmanifest.description"),
|
||||
pkg_manifest.arch
|
||||
);
|
||||
}
|
||||
if !pkg_manifest.maintainers.is_empty() {
|
||||
println!("{}:", t!("info.print_pkgmanifest.maintainers"));
|
||||
for i in pkg_manifest.maintainers.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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_install_misc(installation: &Installation) {
|
||||
println!(
|
||||
"{}: {}",
|
||||
t!("info.print_install_misc.install_pkgspec"),
|
||||
installation.install_pkgspec
|
||||
);
|
||||
println!(
|
||||
"{}: {}",
|
||||
t!("info.print_install_misc.install_date"),
|
||||
crate::common::format_datetime(installation.install_date),
|
||||
);
|
||||
println!(
|
||||
"{}: {}",
|
||||
t!("info.print_install_misc.installed_size"),
|
||||
format_size(installation.installed_size),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
use clap::Parser;
|
||||
use packie::PackieBuilder;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {}
|
||||
|
||||
pub fn main(_: Cli) -> Result<(), crate::Error> {
|
||||
PackieBuilder::new().build()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
use crate::common::{format_size, print_list, yesno};
|
||||
use clap::Parser;
|
||||
use console::style;
|
||||
use indicatif::ProgressBar;
|
||||
use itertools::Itertools;
|
||||
use packie::{
|
||||
PackieBuilder, install::InstallOptions, package::PkgSpec, repo::DownloadPackageEvent,
|
||||
};
|
||||
use rust_i18n::t;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
/// Force the specified items to be files
|
||||
#[arg(short, long)]
|
||||
file: bool,
|
||||
|
||||
/// Don't require manual confirmation
|
||||
#[arg(short, long)]
|
||||
yes: bool,
|
||||
|
||||
/// Items to be installed
|
||||
items: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let mut packie = PackieBuilder::new().build()?;
|
||||
if cli.file {
|
||||
for item in cli.items {
|
||||
packie.install_package_file(item, &InstallOptions::default())?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut packages = Vec::with_capacity(cli.items.len() * 4);
|
||||
for i in cli.items {
|
||||
packages.append(&mut packie.calculate_depgraph(i.parse::<PkgSpec>()?)?);
|
||||
}
|
||||
let packages: Vec<_> = packages
|
||||
.into_iter()
|
||||
.unique_by(|x| x.repo_pkg.pkg_manifest.pkg_ident())
|
||||
.collect();
|
||||
|
||||
if packages.is_empty() {
|
||||
eprintln!("{}", t!("install.no_package_to_install"));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let download_size: u64 = packages.iter().map(|x| x.repo_pkg.download_size).sum();
|
||||
let installed_size: u64 = packages.iter().map(|x| x.repo_pkg.installed_size).sum();
|
||||
|
||||
println!("{}", t!("install.prompt_to_install"));
|
||||
print_list(
|
||||
4,
|
||||
packages
|
||||
.iter()
|
||||
.map(|x| style(&x.pkgspec).green().to_string()),
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
t!("install.download_size", size = format_size(download_size))
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
t!("install.installed_size", size = format_size(installed_size))
|
||||
);
|
||||
if cli.yes {
|
||||
eprintln!("y");
|
||||
} else if !yesno(&t!("install.confirm_installation"), true) {
|
||||
eprintln!("{}", t!("install.aborting"));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let progress_bar =
|
||||
ProgressBar::new(download_size).with_style(crate::common::progress_style_multidownload());
|
||||
let mut package_files = Vec::with_capacity(packages.len());
|
||||
for package in packages.iter() {
|
||||
let init_pos = progress_bar.position();
|
||||
progress_bar.println(format!("Downloading \"{}\"...", package.pkgspec));
|
||||
progress_bar.set_prefix(package.repo_pkg.pkg_manifest.pkg_ident().to_string());
|
||||
let path = packie.download_package(
|
||||
&package.repo_name,
|
||||
&package.repo_pkg.pkg_manifest.pkg_ident(),
|
||||
|ev| match ev {
|
||||
DownloadPackageEvent::Progress(_, sum, _) => {
|
||||
progress_bar.set_position(init_pos + sum);
|
||||
}
|
||||
DownloadPackageEvent::Error(url, err) => {
|
||||
progress_bar
|
||||
.println(format!("error: failed to download from \"{url}\": {err}"));
|
||||
}
|
||||
},
|
||||
)?;
|
||||
package_files.push(path);
|
||||
progress_bar.set_position(init_pos + package.repo_pkg.download_size);
|
||||
}
|
||||
drop(progress_bar);
|
||||
|
||||
let progress_bar = ProgressBar::new(package_files.len() as _);
|
||||
for package in package_files.iter() {
|
||||
progress_bar.println(format!("Installing \"{}\"...", package.display()));
|
||||
packie.install_package_file(package, &InstallOptions::default())?;
|
||||
progress_bar.inc(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
mod clean;
|
||||
mod common;
|
||||
mod info;
|
||||
mod initdb;
|
||||
mod install;
|
||||
mod print;
|
||||
mod remove;
|
||||
mod repo_admin;
|
||||
mod select;
|
||||
mod update;
|
||||
|
||||
use clap::Parser;
|
||||
use console::style;
|
||||
use rust_i18n::t;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
rust_i18n::i18n!("../locales/packie-cli");
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
#[arg(long)]
|
||||
chroot: Option<PathBuf>,
|
||||
|
||||
#[command(subcommand)]
|
||||
subcommand: Subcommand,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
enum Subcommand {
|
||||
/// Clean cache data
|
||||
Clean(clean::Cli),
|
||||
|
||||
/// Get package information
|
||||
Info(info::Cli),
|
||||
|
||||
/// Explicitly initialize the database
|
||||
InitDb(initdb::Cli),
|
||||
|
||||
/// Install one or more packages
|
||||
Install(install::Cli),
|
||||
|
||||
/// Print configuration information
|
||||
Print(print::Cli),
|
||||
|
||||
/// Remove one or more packages
|
||||
Remove(remove::Cli),
|
||||
|
||||
/// Administrate repository service
|
||||
RepoAdmin(repo_admin::Cli),
|
||||
|
||||
/// Update repository indexes
|
||||
Update(update::Cli),
|
||||
|
||||
/// Select global link
|
||||
Select(select::Cli),
|
||||
}
|
||||
|
||||
fn main() {
|
||||
rust_i18n::set_locale(match std::env::var("LANG").as_deref() {
|
||||
Ok("zh_CN.UTF-8") => "zh-CN",
|
||||
Ok("ja_JP.UTF-8") => "ja",
|
||||
_ => "en",
|
||||
});
|
||||
|
||||
let cli = Cli::parse();
|
||||
if let Some(new_root) = cli.chroot
|
||||
&& let Err(err) = chroot(&new_root)
|
||||
{
|
||||
eprintln!("{}", style(t!("main.error", error = err)).red());
|
||||
std::process::exit(1);
|
||||
}
|
||||
let result = match cli.subcommand {
|
||||
Subcommand::Clean(cli) => clean::main(cli),
|
||||
Subcommand::Info(cli) => info::main(cli),
|
||||
Subcommand::InitDb(cli) => initdb::main(cli),
|
||||
Subcommand::Install(cli) => install::main(cli),
|
||||
Subcommand::Print(cli) => print::main(cli),
|
||||
Subcommand::Remove(cli) => remove::main(cli),
|
||||
Subcommand::RepoAdmin(cli) => repo_admin::main(cli),
|
||||
Subcommand::Update(cli) => update::main(cli),
|
||||
Subcommand::Select(cli) => select::main(cli),
|
||||
};
|
||||
if let Err(err) = result {
|
||||
eprintln!("{}", style(t!("main.error", error = err.message)).red());
|
||||
if let Some(note) = err.note {
|
||||
eprintln!("{}", style(t!("main.note", note = note)).blue());
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn chroot(new_root: &Path) -> std::io::Result<()> {
|
||||
nix::sched::unshare(nix::sched::CloneFlags::CLONE_NEWUSER)?;
|
||||
nix::sched::unshare(nix::sched::CloneFlags::CLONE_NEWNS)?;
|
||||
std::os::unix::fs::chroot(new_root)?;
|
||||
std::env::set_current_dir("/")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(target_family = "unix", not(target_os = "linux")))]
|
||||
fn chroot(new_root: &Path) -> std::io::Result<()> {
|
||||
std::os::unix::fs::chroot(new_root)?;
|
||||
std::env::set_current_dir("/")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Error {
|
||||
message: String,
|
||||
note: Option<String>,
|
||||
}
|
||||
impl<E: ToString> From<E> for Error {
|
||||
fn from(value: E) -> Self {
|
||||
Self {
|
||||
message: value.to_string(),
|
||||
note: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($all:tt)*) => {
|
||||
$crate::Error {
|
||||
message: ::rust_i18n::t!($($all)*).into(),
|
||||
note: ::std::option::Option::None,
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use anyhow::anyhow;
|
||||
use clap::Parser;
|
||||
use packie::PackieBuilder;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
key: String,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let key: Vec<&str> = cli.key.split('.').collect();
|
||||
let result = match key.get(0).copied().unwrap() {
|
||||
"profile" => profile(&key[1..]),
|
||||
"package" => package(&key[1..]),
|
||||
unknown => Err(anyhow!("unknown key {unknown} to print")),
|
||||
};
|
||||
println!("{}", result?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn profile(other: &[&str]) -> anyhow::Result<String> {
|
||||
let packie = PackieBuilder::new().readonly(true).build()?;
|
||||
match other {
|
||||
["host_arch"] => Ok(packie.profile().host_arch.clone()),
|
||||
_ => Err(anyhow!("unsupported profile key")),
|
||||
}
|
||||
}
|
||||
|
||||
fn package(other: &[&str]) -> anyhow::Result<String> {
|
||||
let mut packie = PackieBuilder::new().readonly(true).build()?;
|
||||
match other {
|
||||
["prefix", ..] => {
|
||||
let mut found = packie.search_installation(&other[1..].join(".").parse()?)?;
|
||||
if found.is_empty() {
|
||||
return Err(anyhow!("package not found"));
|
||||
}
|
||||
Ok(packie
|
||||
.profile()
|
||||
.pkg_dir
|
||||
.join(found.remove(0).pkg_manifest.pkg_ident().prefix_name())
|
||||
.to_string_lossy()
|
||||
.into())
|
||||
}
|
||||
_ => Err(anyhow!("unsupported profile key")),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use crate::{
|
||||
common::{print_list, yesno},
|
||||
error,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use clap::Parser;
|
||||
use console::style;
|
||||
use packie::{PackieBuilder, package::PkgSpec};
|
||||
use rust_i18n::t;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
#[arg(short, long)]
|
||||
yes: bool,
|
||||
|
||||
items: Vec<PkgSpec>,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let mut packie = PackieBuilder::new().build()?;
|
||||
let mut packages = Vec::with_capacity(cli.items.len());
|
||||
for pkgspec in cli.items {
|
||||
let mut found = packie.search_installation(&pkgspec)?;
|
||||
if found.is_empty() {
|
||||
return Err(error!("remove.package_not_found"));
|
||||
}
|
||||
if found.len() != 1 {
|
||||
return Err(error!("remove.pkgspec_not_unique"));
|
||||
}
|
||||
packages.push(found.remove(0));
|
||||
}
|
||||
println!("{}", t!("remove.prompt_to_remove"));
|
||||
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"), false) {
|
||||
println!("{}", t!("remove.aborting"));
|
||||
return Ok(());
|
||||
}
|
||||
for i in packages {
|
||||
println!(
|
||||
"{}",
|
||||
t!("remove.action_hint", package = i.pkg_manifest.pkg_ident())
|
||||
);
|
||||
packie
|
||||
.remove(&i.pkg_manifest.pkg_ident().into())
|
||||
.map_err(|error| anyhow!("{}", t!("remove.action_failure", error = error)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use anyhow::anyhow;
|
||||
use clap::Parser;
|
||||
use indicatif::ProgressBar;
|
||||
use packie::{
|
||||
package::{Package, PkgIdent},
|
||||
repo::RepoServeDir,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
/// Path of the repository serve directory
|
||||
repo: PathBuf,
|
||||
|
||||
/// Add package(s) to the repository
|
||||
#[arg(long, short)]
|
||||
add_package: Option<Vec<PathBuf>>,
|
||||
|
||||
/// Remove package(s) from the repository
|
||||
#[arg(long, short)]
|
||||
remove_package: Option<Vec<PkgIdent>>,
|
||||
|
||||
/// Clone or pull remote repository
|
||||
#[arg(long, short)]
|
||||
clone: Option<String>,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
if let Some(clone) = cli.clone {
|
||||
let progress_bar = ProgressBar::new(0);
|
||||
RepoServeDir::clone(&clone.parse()?, &cli.repo, |pkg, url, sum, total| {
|
||||
progress_bar.set_length(total);
|
||||
progress_bar.set_position(sum);
|
||||
if let Some(pkg) = pkg {
|
||||
progress_bar.set_prefix(pkg);
|
||||
}
|
||||
progress_bar.set_message(url.to_string());
|
||||
})
|
||||
.map_err(|x| anyhow!("{x}"))?;
|
||||
}
|
||||
|
||||
let mut repo = RepoServeDir::open(cli.repo)?;
|
||||
|
||||
for pkg in cli.remove_package.into_iter().flatten() {
|
||||
repo.remove_package(&pkg)
|
||||
.map_err(|err| anyhow!("\"{pkg}\": {err}"))?;
|
||||
}
|
||||
|
||||
for file in cli.add_package.into_iter().flatten() {
|
||||
let pkg_ident = Package::open(&file)?.manifest()?.pkg_ident();
|
||||
_ = repo.remove_package(&pkg_ident);
|
||||
repo.add_package(&file)
|
||||
.map_err(|err| anyhow!("\"{}\": {}", file.display(), err))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
use crate::error;
|
||||
use clap::Parser;
|
||||
use console::style;
|
||||
use dialoguer::Select;
|
||||
use packie::PackieBuilder;
|
||||
use rust_i18n::t;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let mut packie = PackieBuilder::new().build()?;
|
||||
let file = if cli.file.is_absolute() {
|
||||
cli.file.clone()
|
||||
} else {
|
||||
std::env::current_dir()?.join(&cli.file)
|
||||
};
|
||||
let mut all_pkgs = Vec::with_capacity(1024);
|
||||
let mut candidates = Vec::new();
|
||||
packie.for_each_installation::<anyhow::Error>(|inst| {
|
||||
all_pkgs.push(inst.pkg_manifest.pkg_ident());
|
||||
Ok(())
|
||||
})?;
|
||||
for pkgident in all_pkgs {
|
||||
let links = packie.links_of_pkg(&pkgident);
|
||||
let Ok(links) = links.iter() else {
|
||||
return Ok(());
|
||||
};
|
||||
if let Some(link) = links
|
||||
.filter_map(|x| x.ok())
|
||||
.find(|x| x.to == file.to_string_lossy())
|
||||
{
|
||||
candidates.push((pkgident, link));
|
||||
}
|
||||
}
|
||||
if candidates.is_empty() {
|
||||
return Err(crate::Error {
|
||||
message: t!("select.empty_list").into(),
|
||||
note: Some(t!("select.note_relative_path").into()),
|
||||
});
|
||||
}
|
||||
let items = std::iter::once(t!("select.disabled").into())
|
||||
.chain(candidates.iter().map(|x| x.0.to_string()))
|
||||
.map(|x| style(x).green());
|
||||
let choice = Select::new()
|
||||
.with_prompt(t!("select.prompt_select_package"))
|
||||
.items(items)
|
||||
.interact()?;
|
||||
if choice == 0 {
|
||||
if let Err(err) = packie::link::remove(&file) {
|
||||
if err.kind() == std::io::ErrorKind::InvalidData {
|
||||
return Err(error!("select.not_a_link"));
|
||||
}
|
||||
if err.kind() == std::io::ErrorKind::NotFound {
|
||||
return Ok(());
|
||||
}
|
||||
Err(err)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let (pkgident, linkdes) = candidates.remove(choice - 1);
|
||||
_ = packie::link::remove(&file);
|
||||
_ = packie::link::deactivate_by_description(&mut packie, &pkgident, &linkdes);
|
||||
packie::link::activate_by_description(&mut packie, &pkgident, &linkdes)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use clap::Parser;
|
||||
use indicatif::ProgressBar;
|
||||
use packie::{PackieBuilder, repo::SyncEvent};
|
||||
use rust_i18n::t;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {}
|
||||
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let mut packie = PackieBuilder::new().build()?;
|
||||
let progress_bar =
|
||||
ProgressBar::new(0).with_style(crate::common::progress_style_multidownload());
|
||||
packie
|
||||
.sync_repo()
|
||||
.on_event(|ev| match ev {
|
||||
SyncEvent::BeginRepo(repo) => {
|
||||
progress_bar.set_prefix(repo.clone());
|
||||
progress_bar.println(t!("update.begin_repo", repo = repo));
|
||||
}
|
||||
SyncEvent::Progress(progress) => {
|
||||
progress_bar.set_length(progress.total_bytes);
|
||||
progress_bar.set_position(progress.downloaded_bytes);
|
||||
}
|
||||
SyncEvent::Error(repo, url, err) => match (repo, url) {
|
||||
(Some(repo), Some(url)) => {
|
||||
progress_bar.println(format!(
|
||||
"failed to update repository \"{repo}\" from \"{url}\": {err}"
|
||||
));
|
||||
}
|
||||
(Some(repo), None) => {
|
||||
progress_bar.println(format!("failed to update repository \"{repo}\": {err}"));
|
||||
}
|
||||
_ => {
|
||||
progress_bar.println(format!("failed to update repositories: {err}"));
|
||||
}
|
||||
},
|
||||
})
|
||||
.run();
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user