github source v1
This commit is contained in:
parent
71f89dde9c
commit
caf870d05e
50 changed files with 4139 additions and 131 deletions
|
|
@ -4,12 +4,13 @@ pub mod ui;
|
|||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use aim_core::app::add::build_add_plan;
|
||||
use aim_core::app::add::{AddPlan, build_add_plan, materialize_app_record};
|
||||
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::app::AppRecord;
|
||||
use aim_core::domain::source::SourceRef;
|
||||
use aim_core::domain::update::UpdatePlan;
|
||||
use aim_core::domain::update::{ArtifactCandidate, UpdatePlan};
|
||||
use aim_core::registry::model::Registry;
|
||||
use aim_core::registry::store::RegistryStore;
|
||||
|
||||
|
|
@ -45,9 +46,29 @@ pub fn dispatch(cli: Cli) -> Result<DispatchResult, DispatchError> {
|
|||
}
|
||||
|
||||
if let Some(query) = cli.query {
|
||||
return Ok(DispatchResult::AddPlan(
|
||||
build_add_plan(&query)?.resolution.source,
|
||||
));
|
||||
let mut plan = build_add_plan(&query)?;
|
||||
if !plan.interactions.is_empty() {
|
||||
match ui::prompt::resolve_add_plan_interactions(plan.clone())? {
|
||||
Some(resolved) => {
|
||||
plan = resolved;
|
||||
}
|
||||
None => return Ok(DispatchResult::PendingAdd(plan)),
|
||||
}
|
||||
}
|
||||
|
||||
let record = materialize_app_record(&query, &plan)?;
|
||||
let mut updated_apps = registry.apps.clone();
|
||||
upsert_app_record(&mut updated_apps, record.clone());
|
||||
store.save(&Registry {
|
||||
version: registry.version,
|
||||
apps: updated_apps,
|
||||
})?;
|
||||
|
||||
return Ok(DispatchResult::Added(AddedApp {
|
||||
record,
|
||||
selected_artifact: plan.selected_artifact,
|
||||
source: plan.resolution.source,
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(DispatchResult::Noop)
|
||||
|
|
@ -68,16 +89,26 @@ fn registry_path() -> PathBuf {
|
|||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum DispatchResult {
|
||||
AddPlan(SourceRef),
|
||||
Added(AddedApp),
|
||||
List(Vec<ListRow>),
|
||||
PendingAdd(AddPlan),
|
||||
Removed(String),
|
||||
UpdatePlan(UpdatePlan),
|
||||
Noop,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct AddedApp {
|
||||
pub record: AppRecord,
|
||||
pub selected_artifact: ArtifactCandidate,
|
||||
pub source: SourceRef,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DispatchError {
|
||||
AddPlan(aim_core::app::add::BuildAddPlanError),
|
||||
AddRecord(aim_core::app::add::MaterializeAddRecordError),
|
||||
Prompt(ui::prompt::PromptError),
|
||||
RemovePlan(aim_core::app::remove::ResolveRegisteredAppError),
|
||||
Registry(aim_core::registry::store::RegistryStoreError),
|
||||
UpdatePlan(aim_core::app::update::BuildUpdatePlanError),
|
||||
|
|
@ -89,6 +120,18 @@ impl From<aim_core::app::add::BuildAddPlanError> for DispatchError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<aim_core::app::add::MaterializeAddRecordError> for DispatchError {
|
||||
fn from(value: aim_core::app::add::MaterializeAddRecordError) -> Self {
|
||||
Self::AddRecord(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ui::prompt::PromptError> for DispatchError {
|
||||
fn from(value: ui::prompt::PromptError) -> Self {
|
||||
Self::Prompt(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<aim_core::app::update::BuildUpdatePlanError> for DispatchError {
|
||||
fn from(value: aim_core::app::update::BuildUpdatePlanError) -> Self {
|
||||
Self::UpdatePlan(value)
|
||||
|
|
@ -106,3 +149,15 @@ impl From<aim_core::registry::store::RegistryStoreError> for DispatchError {
|
|||
Self::Registry(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn upsert_app_record(apps: &mut Vec<AppRecord>, record: AppRecord) {
|
||||
if let Some(existing) = apps
|
||||
.iter_mut()
|
||||
.find(|item| item.stable_id == record.stable_id)
|
||||
{
|
||||
*existing = record;
|
||||
return;
|
||||
}
|
||||
|
||||
apps.push(record);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,113 @@
|
|||
use aim_core::app::interaction::InteractionRequest;
|
||||
use std::env;
|
||||
use std::io::IsTerminal;
|
||||
|
||||
pub fn handle_interaction(_request: &InteractionRequest) {}
|
||||
use aim_core::app::add::{AddPlan, prefer_latest_tracking};
|
||||
use aim_core::app::interaction::{InteractionKind, InteractionRequest};
|
||||
use dialoguer::{Select, theme::ColorfulTheme};
|
||||
|
||||
const TRACKING_PREFERENCE_ENV: &str = "AIM_TRACKING_PREFERENCE";
|
||||
|
||||
pub fn render_interaction(request: &InteractionRequest) -> String {
|
||||
match &request.kind {
|
||||
InteractionKind::SelectRegisteredApp { query, matches } => format!(
|
||||
"multiple installed apps match '{query}': {}",
|
||||
matches.join(", ")
|
||||
),
|
||||
InteractionKind::ChooseTrackingPreference {
|
||||
requested_version,
|
||||
latest_version,
|
||||
} => format!(
|
||||
"tracking preference required: requested {requested_version}, latest available {latest_version}",
|
||||
),
|
||||
InteractionKind::SelectArtifact { candidates } => {
|
||||
format!("artifact selection required: {}", candidates.join(", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_interactions(requests: &[InteractionRequest]) -> String {
|
||||
requests
|
||||
.iter()
|
||||
.map(render_interaction)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
pub fn resolve_add_plan_interactions(plan: AddPlan) -> Result<Option<AddPlan>, PromptError> {
|
||||
let mut resolved = plan;
|
||||
|
||||
for request in resolved.interactions.clone() {
|
||||
match &request.kind {
|
||||
InteractionKind::ChooseTrackingPreference {
|
||||
requested_version,
|
||||
latest_version,
|
||||
} => match resolve_tracking_preference(requested_version, latest_version)? {
|
||||
Some(TrackingPreference::Requested) => {
|
||||
resolved
|
||||
.interactions
|
||||
.retain(|item| item.key != "tracking-preference");
|
||||
}
|
||||
Some(TrackingPreference::Latest) => {
|
||||
resolved = prefer_latest_tracking(resolved);
|
||||
}
|
||||
None => return Ok(None),
|
||||
},
|
||||
InteractionKind::SelectRegisteredApp { .. }
|
||||
| InteractionKind::SelectArtifact { .. } => {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(resolved))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum TrackingPreference {
|
||||
Requested,
|
||||
Latest,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PromptError {
|
||||
InvalidTrackingPreference(String),
|
||||
Dialoguer(dialoguer::Error),
|
||||
}
|
||||
|
||||
impl From<dialoguer::Error> for PromptError {
|
||||
fn from(value: dialoguer::Error) -> Self {
|
||||
Self::Dialoguer(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_tracking_preference(
|
||||
requested_version: &str,
|
||||
latest_version: &str,
|
||||
) -> Result<Option<TrackingPreference>, PromptError> {
|
||||
if let Ok(value) = env::var(TRACKING_PREFERENCE_ENV) {
|
||||
return match value.trim().to_ascii_lowercase().as_str() {
|
||||
"requested" | "current" => Ok(Some(TrackingPreference::Requested)),
|
||||
"latest" => Ok(Some(TrackingPreference::Latest)),
|
||||
other => Err(PromptError::InvalidTrackingPreference(other.to_owned())),
|
||||
};
|
||||
}
|
||||
|
||||
if !std::io::stdin().is_terminal() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let options = [
|
||||
format!("Keep tracking the requested release lineage ({requested_version})"),
|
||||
format!("Track the latest release after install ({latest_version})"),
|
||||
];
|
||||
let selection = Select::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Choose update tracking behavior")
|
||||
.items(options)
|
||||
.default(1)
|
||||
.interact()?;
|
||||
|
||||
Ok(Some(match selection {
|
||||
0 => TrackingPreference::Requested,
|
||||
_ => TrackingPreference::Latest,
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use aim_core::domain::source::SourceRef;
|
||||
use aim_core::app::add::AddPlan;
|
||||
|
||||
use crate::DispatchResult;
|
||||
|
||||
|
|
@ -8,8 +8,9 @@ pub fn render_update_summary(total: usize, selected: usize, failed: usize) -> St
|
|||
|
||||
pub fn render_dispatch_result(result: &DispatchResult) -> String {
|
||||
match result {
|
||||
DispatchResult::AddPlan(source) => render_add_plan(source),
|
||||
DispatchResult::Added(added) => render_added_app(added),
|
||||
DispatchResult::List(rows) => render_list(rows),
|
||||
DispatchResult::PendingAdd(plan) => render_pending_add(plan),
|
||||
DispatchResult::Removed(display_name) => format!("removed: {display_name}"),
|
||||
DispatchResult::UpdatePlan(plan) => {
|
||||
render_update_summary(plan.items.len(), plan.items.len(), 0)
|
||||
|
|
@ -18,11 +19,26 @@ pub fn render_dispatch_result(result: &DispatchResult) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_add_plan(source: &SourceRef) -> String {
|
||||
fn render_added_app(added: &crate::AddedApp) -> String {
|
||||
format!(
|
||||
"resolved source: {} {}",
|
||||
source.kind.as_str(),
|
||||
source.locator
|
||||
"tracked app: {} ({})\nsource: {} {}\nselected artifact: {} [{}]",
|
||||
added.record.display_name,
|
||||
added.record.stable_id,
|
||||
added.source.kind.as_str(),
|
||||
added.source.locator,
|
||||
added.selected_artifact.url,
|
||||
added.selected_artifact.selection_reason,
|
||||
)
|
||||
}
|
||||
|
||||
fn render_pending_add(plan: &AddPlan) -> String {
|
||||
let prompts = crate::ui::prompt::render_interactions(&plan.interactions);
|
||||
format!(
|
||||
"resolved source: {} {}\nselected artifact: {} [{}]\n{prompts}",
|
||||
plan.resolution.source.kind.as_str(),
|
||||
plan.resolution.source.locator,
|
||||
plan.selected_artifact.url,
|
||||
plan.selected_artifact.selection_reason,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue