initial skeleton
This commit is contained in:
parent
dc79fa2448
commit
71f89dde9c
60 changed files with 3480 additions and 0 deletions
31
crates/aim-cli/src/cli/args.rs
Normal file
31
crates/aim-cli/src/cli/args.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use clap::Parser;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "aim")]
|
||||
#[command(about = "AppImage Manager")]
|
||||
pub struct Cli {
|
||||
#[arg(global = true, long = "system", conflicts_with = "user")]
|
||||
pub system: bool,
|
||||
|
||||
#[arg(global = true, long = "user", conflicts_with = "system")]
|
||||
pub user: bool,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Command>,
|
||||
|
||||
pub query: Option<String>,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn is_review_update_flow(&self) -> bool {
|
||||
matches!(self.command, Some(Command::Update))
|
||||
|| (self.command.is_none() && self.query.is_none())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub enum Command {
|
||||
Remove { query: String },
|
||||
List,
|
||||
Update,
|
||||
}
|
||||
1
crates/aim-cli/src/cli/mod.rs
Normal file
1
crates/aim-cli/src/cli/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod args;
|
||||
108
crates/aim-cli/src/lib.rs
Normal file
108
crates/aim-cli/src/lib.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
pub mod cli;
|
||||
pub mod ui;
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use aim_core::app::add::build_add_plan;
|
||||
use aim_core::app::list::{ListRow, build_list_rows};
|
||||
use aim_core::app::remove::remove_registered_app;
|
||||
use aim_core::app::update::build_update_plan;
|
||||
use aim_core::domain::source::SourceRef;
|
||||
use aim_core::domain::update::UpdatePlan;
|
||||
use aim_core::registry::model::Registry;
|
||||
use aim_core::registry::store::RegistryStore;
|
||||
|
||||
pub use cli::args::Cli;
|
||||
|
||||
pub fn parse() -> Cli {
|
||||
<Cli as clap::Parser>::parse()
|
||||
}
|
||||
|
||||
pub fn dispatch(cli: Cli) -> Result<DispatchResult, DispatchError> {
|
||||
let registry_path = registry_path();
|
||||
let store = RegistryStore::new(registry_path);
|
||||
let registry = store.load()?;
|
||||
let apps = registry.apps.clone();
|
||||
|
||||
if cli.is_review_update_flow() {
|
||||
return Ok(DispatchResult::UpdatePlan(build_update_plan(&apps)?));
|
||||
}
|
||||
|
||||
if let Some(command) = cli.command {
|
||||
return match command {
|
||||
cli::args::Command::List => Ok(DispatchResult::List(build_list_rows(&apps))),
|
||||
cli::args::Command::Remove { query } => {
|
||||
let removal = remove_registered_app(&query, &apps)?;
|
||||
store.save(&Registry {
|
||||
version: registry.version,
|
||||
apps: removal.remaining_apps,
|
||||
})?;
|
||||
Ok(DispatchResult::Removed(removal.removed.display_name))
|
||||
}
|
||||
cli::args::Command::Update => Ok(DispatchResult::UpdatePlan(build_update_plan(&apps)?)),
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(query) = cli.query {
|
||||
return Ok(DispatchResult::AddPlan(
|
||||
build_add_plan(&query)?.resolution.source,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(DispatchResult::Noop)
|
||||
}
|
||||
|
||||
pub fn render(result: &DispatchResult) -> String {
|
||||
ui::render::render_dispatch_result(result)
|
||||
}
|
||||
|
||||
fn registry_path() -> PathBuf {
|
||||
if let Some(path) = env::var_os("AIM_REGISTRY_PATH") {
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
|
||||
let home = env::var_os("HOME").unwrap_or_else(|| ".".into());
|
||||
PathBuf::from(home).join(".local/share/aim/registry.toml")
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum DispatchResult {
|
||||
AddPlan(SourceRef),
|
||||
List(Vec<ListRow>),
|
||||
Removed(String),
|
||||
UpdatePlan(UpdatePlan),
|
||||
Noop,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DispatchError {
|
||||
AddPlan(aim_core::app::add::BuildAddPlanError),
|
||||
RemovePlan(aim_core::app::remove::ResolveRegisteredAppError),
|
||||
Registry(aim_core::registry::store::RegistryStoreError),
|
||||
UpdatePlan(aim_core::app::update::BuildUpdatePlanError),
|
||||
}
|
||||
|
||||
impl From<aim_core::app::add::BuildAddPlanError> for DispatchError {
|
||||
fn from(value: aim_core::app::add::BuildAddPlanError) -> Self {
|
||||
Self::AddPlan(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<aim_core::app::update::BuildUpdatePlanError> for DispatchError {
|
||||
fn from(value: aim_core::app::update::BuildUpdatePlanError) -> Self {
|
||||
Self::UpdatePlan(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<aim_core::app::remove::ResolveRegisteredAppError> for DispatchError {
|
||||
fn from(value: aim_core::app::remove::ResolveRegisteredAppError) -> Self {
|
||||
Self::RemovePlan(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<aim_core::registry::store::RegistryStoreError> for DispatchError {
|
||||
fn from(value: aim_core::registry::store::RegistryStoreError) -> Self {
|
||||
Self::Registry(value)
|
||||
}
|
||||
}
|
||||
15
crates/aim-cli/src/main.rs
Normal file
15
crates/aim-cli/src/main.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
fn main() {
|
||||
let cli = aim_cli::parse();
|
||||
match aim_cli::dispatch(cli) {
|
||||
Ok(result) => {
|
||||
let output = aim_cli::render(&result);
|
||||
if !output.is_empty() {
|
||||
println!("{output}");
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
eprintln!("{error:?}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
crates/aim-cli/src/ui/mod.rs
Normal file
2
crates/aim-cli/src/ui/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod prompt;
|
||||
pub mod render;
|
||||
3
crates/aim-cli/src/ui/prompt.rs
Normal file
3
crates/aim-cli/src/ui/prompt.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
use aim_core::app::interaction::InteractionRequest;
|
||||
|
||||
pub fn handle_interaction(_request: &InteractionRequest) {}
|
||||
39
crates/aim-cli/src/ui/render.rs
Normal file
39
crates/aim-cli/src/ui/render.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use aim_core::domain::source::SourceRef;
|
||||
|
||||
use crate::DispatchResult;
|
||||
|
||||
pub fn render_update_summary(total: usize, selected: usize, failed: usize) -> String {
|
||||
format!("updates found: {total}, selected: {selected}, failed: {failed}",)
|
||||
}
|
||||
|
||||
pub fn render_dispatch_result(result: &DispatchResult) -> String {
|
||||
match result {
|
||||
DispatchResult::AddPlan(source) => render_add_plan(source),
|
||||
DispatchResult::List(rows) => render_list(rows),
|
||||
DispatchResult::Removed(display_name) => format!("removed: {display_name}"),
|
||||
DispatchResult::UpdatePlan(plan) => {
|
||||
render_update_summary(plan.items.len(), plan.items.len(), 0)
|
||||
}
|
||||
DispatchResult::Noop => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_add_plan(source: &SourceRef) -> String {
|
||||
format!(
|
||||
"resolved source: {} {}",
|
||||
source.kind.as_str(),
|
||||
source.locator
|
||||
)
|
||||
}
|
||||
|
||||
fn render_list(rows: &[aim_core::app::list::ListRow]) -> String {
|
||||
if rows.is_empty() {
|
||||
return "installed apps: none".to_owned();
|
||||
}
|
||||
|
||||
let mut output = String::from("installed apps:\n");
|
||||
for row in rows {
|
||||
output.push_str(&format!("- {} ({})\n", row.display_name, row.stable_id));
|
||||
}
|
||||
output.trim_end().to_owned()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue