feat: yay-style combined flags (-Sdy, -Sdd, -Sfyu), --noconfirm, -Gp, -Scc
- Combined short flags expand: -Sdy → install --noconfirm - -Gp prints raw PKGBUILD from AUR - -Scc cleans all caches including ~/.cub/tmp/ - --noconfirm skips interactive prompts - Deduplicated flag expansion, added to global flag list
This commit is contained in:
@@ -78,9 +78,12 @@ impl PackageCreator {
|
||||
#[command(version)]
|
||||
#[command(about = "Red Bear OS Package Builder")]
|
||||
struct Cli {
|
||||
/// Disable the default TUI launcher when no subcommand is provided
|
||||
#[arg(short = 'T', long = "no-tui", global = true)]
|
||||
no_tui: bool,
|
||||
/// Force re-fetch from AUR even if already cached
|
||||
#[arg(short = 'f', long = "force", global = true)]
|
||||
force: bool,
|
||||
/// Skip all interactive prompts (assume yes)
|
||||
#[arg(long = "noconfirm", global = true, action = clap::ArgAction::SetTrue)]
|
||||
noconfirm: bool,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
@@ -90,7 +93,7 @@ struct Cli {
|
||||
enum Commands {
|
||||
/// Install a package from the official repo or BUR
|
||||
Install { package: String },
|
||||
/// Search packages in the official repo and cached BUR
|
||||
/// Search packages in the official repo, cached BUR, and AUR
|
||||
Search { query: String },
|
||||
/// Show AUR package details
|
||||
Info { package: String },
|
||||
@@ -104,6 +107,8 @@ enum Commands {
|
||||
Get { package: String },
|
||||
/// Import an AUR package into ~/.cub/recipes
|
||||
GetAur { package: String },
|
||||
/// Print raw PKGBUILD from AUR to stdout
|
||||
GetPkgbuild { package: String },
|
||||
/// Inspect an installed package or local RBPKGBUILD
|
||||
Inspect { target: String },
|
||||
/// Convert an AUR PKGBUILD into an RBPKGBUILD tree
|
||||
@@ -118,8 +123,10 @@ enum Commands {
|
||||
QueryInfo { package: String },
|
||||
/// List files installed by a package
|
||||
QueryList { package: String },
|
||||
/// Remove cub and pkg download caches
|
||||
/// Clean cub and pkg download caches
|
||||
CleanCache,
|
||||
/// Clean all caches including build artifacts (~/.cub/tmp/)
|
||||
CleanAll,
|
||||
}
|
||||
|
||||
struct AppContext {
|
||||
@@ -181,19 +188,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let context = AppContext::new();
|
||||
|
||||
if let Some(command) = cli.command {
|
||||
run_command(&context, command)?;
|
||||
} else if cli.no_tui {
|
||||
print_help_text()?;
|
||||
run_command(&context, command, cli.force)?;
|
||||
} else {
|
||||
launch_tui_or_help()?;
|
||||
let mut cmd = Cli::command();
|
||||
cmd.print_help()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_command(context: &AppContext, command: Commands) -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn run_command(context: &AppContext, command: Commands, force: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
match command {
|
||||
Commands::Install { package } => install_package(context, &package)?,
|
||||
Commands::Install { package } => install_package(context, &package, force)?,
|
||||
Commands::Search { query } => search_packages(context, &query)?,
|
||||
Commands::Info { package } => show_aur_info(&package)?,
|
||||
Commands::Sync => sync_sources()?,
|
||||
@@ -201,6 +207,7 @@ fn run_command(context: &AppContext, command: Commands) -> Result<(), Box<dyn st
|
||||
Commands::Build { dir } => build_local_dir(context, Path::new(&dir))?,
|
||||
Commands::Get { package } => fetch_bur_recipe(&package)?,
|
||||
Commands::GetAur { package } => get_aur_recipe(&package)?,
|
||||
Commands::GetPkgbuild { package } => get_pkgbuild_stdout(&package)?,
|
||||
Commands::Inspect { target } => inspect_target(context, &target)?,
|
||||
Commands::ImportAur { target } => import_aur_target(&target)?,
|
||||
Commands::UpdateAll => update_all(context)?,
|
||||
@@ -209,40 +216,12 @@ fn run_command(context: &AppContext, command: Commands) -> Result<(), Box<dyn st
|
||||
Commands::QueryInfo { package } => query_local_info(context, &package)?,
|
||||
Commands::QueryList { package } => query_local_files(context, &package)?,
|
||||
Commands::CleanCache => clean_cache()?,
|
||||
Commands::CleanAll => clean_all()?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn launch_tui_or_help() -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[cfg(feature = "tui")]
|
||||
{
|
||||
use std::io::IsTerminal;
|
||||
|
||||
if io::stdin().is_terminal() && io::stdout().is_terminal() {
|
||||
if let Err(error) = cub_tui::run() {
|
||||
eprintln!("Failed to launch cub TUI: {error}");
|
||||
print_help_text()?;
|
||||
}
|
||||
} else {
|
||||
print_help_text()?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tui"))]
|
||||
{
|
||||
print_help_text()
|
||||
}
|
||||
}
|
||||
|
||||
fn print_help_text() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut command = Cli::command();
|
||||
command.print_help()?;
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rewrite_shortcut_args(
|
||||
args: impl IntoIterator<Item = OsString>,
|
||||
) -> Result<Vec<OsString>, Box<dyn std::error::Error>> {
|
||||
@@ -261,6 +240,34 @@ fn rewrite_shortcut_args(
|
||||
return Ok(collected);
|
||||
};
|
||||
|
||||
let known = ["-S", "-B", "-G", "-R", "-Q", "-Ss", "-Si", "-Sy", "-Syu", "-Sf",
|
||||
"-Sua", "-Sc", "-Scc", "-Pi", "-Qi", "-Ql", "-Gp",
|
||||
"--import-aur"];
|
||||
|
||||
if flag.starts_with('-') && flag.len() > 2 && !flag.starts_with("--") && !known.contains(&flag) {
|
||||
let prefix_map: &[(&str, &str)] = &[
|
||||
("-S", "install"), ("-B", "build"), ("-G", "get-aur"), ("-R", "remove"),
|
||||
("-Q", "query-local"), ("-Ss", "search"), ("-Si", "info"), ("-Sy", "sync"),
|
||||
];
|
||||
let main = &flag[..2];
|
||||
let sub = prefix_map.iter().find(|(p, _)| *p == main).map(|(_, s)| *s).unwrap_or(main);
|
||||
|
||||
let mut expanded = vec![binary.clone()];
|
||||
expanded.extend(prefix.iter().cloned());
|
||||
expanded.push(OsString::from(sub));
|
||||
let mut has_force = false;
|
||||
let mut has_noconfirm = false;
|
||||
for ch in flag[2..].chars() {
|
||||
match ch {
|
||||
'f' if !has_force => { expanded.push(OsString::from("--force")); has_force = true; }
|
||||
'y' | 'd' if !has_noconfirm => { expanded.push(OsString::from("--noconfirm")); has_noconfirm = true; }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
expanded.extend(tail.iter().skip(1).cloned());
|
||||
return Ok(expanded);
|
||||
}
|
||||
|
||||
let rewrite_value = |subcommand: &str, value_name: &str| {
|
||||
if prefix.is_empty() {
|
||||
rewrite_value_command(binary.clone(), tail, subcommand, value_name)
|
||||
@@ -278,6 +285,11 @@ fn rewrite_shortcut_args(
|
||||
|
||||
match flag {
|
||||
"-S" => rewrite_value("install", "package"),
|
||||
"-Sf" => {
|
||||
let mut rewritten = rewrite_value_command(binary.clone(), tail, "install", "package")?;
|
||||
rewritten.insert(1, OsString::from("--force"));
|
||||
Ok(rewritten)
|
||||
}
|
||||
"-Ss" => rewrite_value("search", "query"),
|
||||
"-Si" => rewrite_value("info", "package"),
|
||||
"-Sy" => rewrite_flag("sync"),
|
||||
@@ -292,6 +304,8 @@ fn rewrite_shortcut_args(
|
||||
"--import-aur" => rewrite_value("import-aur", "target"),
|
||||
"-Sua" => rewrite_flag("update-all"),
|
||||
"-Sc" => rewrite_flag("clean-cache"),
|
||||
"-Scc" => rewrite_flag("clean-all"),
|
||||
"-Gp" => rewrite_value("get-pkgbuild", "package"),
|
||||
_ => Ok(collected),
|
||||
}
|
||||
}
|
||||
@@ -299,7 +313,7 @@ fn rewrite_shortcut_args(
|
||||
fn leading_global_flag_count(rest: &[OsString]) -> usize {
|
||||
let mut count = 0;
|
||||
while let Some(flag) = rest.get(count).and_then(|value| value.to_str()) {
|
||||
if matches!(flag, "-T" | "--no-tui") {
|
||||
if matches!(flag, "-f" | "--force" | "--noconfirm") {
|
||||
count += 1;
|
||||
} else {
|
||||
break;
|
||||
@@ -368,7 +382,7 @@ fn new_pkg_callback() -> Rc<RefCell<IndicatifCallback>> {
|
||||
Rc::new(RefCell::new(callback))
|
||||
}
|
||||
|
||||
fn install_package(context: &AppContext, package: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn install_package(context: &AppContext, package: &str, force: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if cfg!(not(target_os = "redox")) {
|
||||
println!("Searching AUR for {package}...");
|
||||
let client = AurClient::new();
|
||||
@@ -398,6 +412,24 @@ fn install_package(context: &AppContext, package: &str) -> Result<(), Box<dyn st
|
||||
}
|
||||
|
||||
println!("Found: {}/{} — {}", pkg.name, pkg.version, pkg.description);
|
||||
|
||||
let store = CubStore::new()?;
|
||||
store.init()?;
|
||||
let recipe_dir = store.recipes_dir().join(&pkg.name);
|
||||
|
||||
if recipe_dir.exists() {
|
||||
if force {
|
||||
fs::remove_dir_all(&recipe_dir)?;
|
||||
println!("Re-fetching {} (--force)...", pkg.name);
|
||||
fetch_and_save_aur(pkg)?;
|
||||
} else {
|
||||
println!("Already cached in ~/.cub/recipes/{}/", pkg.name);
|
||||
println!("Use --force/-f to re-fetch, or:");
|
||||
println!(" cubl -B ~/.cub/recipes/{}/", pkg.name);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("Would you like to fetch this package from AUR into ~/.cub/? [y/N]");
|
||||
|
||||
let mut answer = String::new();
|
||||
@@ -1022,6 +1054,50 @@ fn clean_cache() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clean_all() -> Result<(), Box<dyn std::error::Error>> {
|
||||
clean_cache()?;
|
||||
if let Ok(store) = CubStore::new() {
|
||||
let tmp = store.root_dir.join("tmp");
|
||||
if tmp.exists() {
|
||||
fs::remove_dir_all(&tmp)?;
|
||||
println!("Removed ~/.cub/tmp/ build artifacts.");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_pkgbuild_stdout(package: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
validate_git_target(package)?;
|
||||
let repo_url = aur_repo_url(package);
|
||||
let clone_dir = cub_temp_dir("aur-view")?;
|
||||
|
||||
let status = Command::new("git")
|
||||
.arg("clone")
|
||||
.arg("--depth")
|
||||
.arg("1")
|
||||
.arg("--")
|
||||
.arg(&repo_url)
|
||||
.arg(&clone_dir)
|
||||
.status()?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(Box::new(CubError::BuildFailed(format!(
|
||||
"failed to clone AUR source from {repo_url}"
|
||||
))));
|
||||
}
|
||||
|
||||
let pkgbuild_path = clone_dir.join("PKGBUILD");
|
||||
if !pkgbuild_path.exists() {
|
||||
return Err(Box::new(CubError::PackageNotFound(format!(
|
||||
"PKGBUILD not found in {repo_url}"
|
||||
))));
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&pkgbuild_path)?;
|
||||
println!("{content}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_library_changes(library: &mut Library) -> Result<usize, Box<dyn std::error::Error>> {
|
||||
match library.apply() {
|
||||
Ok(changes) => Ok(changes),
|
||||
|
||||
Reference in New Issue
Block a user