use corundum_framework_package::cli_path::{self, CliSpec, CliStatus};
use crate::tr;
pub struct CliRow {
spec: CliSpec,
status_of: fn(&CliSpec) -> CliStatus,
status: Option<CliStatus>,
on_path: std::sync::Arc<std::sync::Mutex<Option<bool>>>,
asked: bool,
error: Option<String>,
#[cfg(test)]
shown: Option<&'static str>,
#[cfg(test)]
noted: bool,
}
#[cfg(unix)]
#[derive(Clone, Copy)]
enum Act {
Install,
Remove,
}
impl CliRow {
pub fn new(spec: CliSpec) -> CliRow {
CliRow::with_status(spec, cli_path::status)
}
pub(crate) fn with_status(spec: CliSpec, status_of: fn(&CliSpec) -> CliStatus) -> CliRow {
CliRow {
spec,
status_of,
status: None,
on_path: Default::default(),
asked: false,
error: None,
#[cfg(test)]
shown: None,
#[cfg(test)]
noted: false,
}
}
pub fn ui(&mut self, ui: &mut egui::Ui) {
if self.status.is_none() {
self.status = Some((self.status_of)(&self.spec));
}
if !self.asked {
self.asked = true;
#[cfg(target_os = "linux")]
self.ask_login_path(ui.ctx());
}
let status = self.status.unwrap_or(CliStatus::Unsupported);
#[cfg(test)]
{
self.shown = None;
self.noted = false;
}
if status == CliStatus::Unsupported {
return;
}
#[cfg(unix)]
let mut clicked = None;
ui.horizontal(|ui| {
ui.label(tr!(ui, "corundum-cli-row", command = self.spec.command));
#[cfg(unix)]
{
let button = match status {
CliStatus::Missing => Some((Act::Install, "corundum-cli-install")),
CliStatus::Broken => Some((Act::Install, "corundum-cli-repair")),
CliStatus::Installed => Some((Act::Remove, "corundum-cli-remove")),
CliStatus::Taken | CliStatus::Unsupported => None,
};
if let Some((act, id)) = button {
#[cfg(test)]
{
self.shown = Some(id);
}
if ui.button(tr!(ui, id)).clicked() {
clicked = Some(act);
}
}
}
#[cfg(windows)]
{
let id = match status {
CliStatus::Installed => "corundum-cli-status-installed",
CliStatus::Taken => "corundum-cli-taken",
_ => "corundum-cli-status-missing",
};
ui.label(tr!(ui, id, command = self.spec.command));
}
if status == CliStatus::Taken {
#[cfg(test)]
{
self.shown = Some("corundum-cli-taken");
}
#[cfg(unix)]
ui.label(tr!(ui, "corundum-cli-taken", command = self.spec.command));
}
});
let off_path = *self.on_path.lock().unwrap_or_else(|e| e.into_inner()) == Some(false);
if off_path && status != CliStatus::Taken {
#[cfg(test)]
{
self.noted = true;
}
ui.colored_label(
ui.visuals().warn_fg_color,
tr!(ui, "corundum-cli-not-on-path"),
);
}
#[cfg(unix)]
if let Some(act) = clicked {
self.act(act);
}
if let Some(error) = &self.error {
ui.colored_label(
ui.visuals().error_fg_color,
tr!(ui, "corundum-cli-failed", error = error.as_str()),
);
}
}
#[cfg(target_os = "linux")]
fn ask_login_path(&self, ctx: &egui::Context) {
let Some(home) = std::env::var_os("HOME") else {
return;
};
let on_path = std::sync::Arc::clone(&self.on_path);
let ctx = ctx.clone();
let spawned = std::thread::Builder::new()
.name("login PATH".to_owned())
.spawn(move || {
let answer =
cli_path::on_login_path(&std::path::Path::new(&home).join(".local/bin"));
*on_path.lock().unwrap_or_else(|e| e.into_inner()) = answer;
ctx.request_repaint();
});
if let Err(err) = spawned {
tracing::warn!("cannot ask the login shell for its PATH: {err}");
}
}
#[cfg(unix)]
fn act(&mut self, act: Act) {
let done = match act {
Act::Install => cli_path::install(&self.spec),
Act::Remove => cli_path::uninstall(&self.spec),
};
self.error = done.err().map(|err| err.to_string());
self.status = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::render;
const SPEC: CliSpec = CliSpec { command: "demo" };
fn shown(status_of: fn(&CliSpec) -> CliStatus) -> Option<&'static str> {
let mut row = CliRow::with_status(SPEC, status_of);
render([600.0, 400.0], |ui| row.ui(ui));
row.shown
}
#[cfg(unix)]
#[test]
fn a_missing_command_offers_install() {
assert_eq!(shown(|_| CliStatus::Missing), Some("corundum-cli-install"));
}
#[cfg(unix)]
#[test]
fn an_installed_command_offers_remove() {
assert_eq!(shown(|_| CliStatus::Installed), Some("corundum-cli-remove"));
}
#[cfg(unix)]
#[test]
fn a_broken_link_offers_repair() {
assert_eq!(shown(|_| CliStatus::Broken), Some("corundum-cli-repair"));
}
#[cfg(unix)]
#[test]
fn a_taken_name_says_so_and_offers_no_install() {
assert_eq!(shown(|_| CliStatus::Taken), Some("corundum-cli-taken"));
}
#[test]
fn the_path_note_waits_for_the_shell_s_answer() {
let mut row = CliRow::with_status(SPEC, |_| CliStatus::Missing);
row.asked = true;
assert!(!note_shown(&mut row));
*row.on_path.lock().unwrap() = Some(true);
assert!(!note_shown(&mut row));
*row.on_path.lock().unwrap() = Some(false);
assert!(note_shown(&mut row));
}
fn note_shown(row: &mut CliRow) -> bool {
render([600.0, 400.0], |ui| row.ui(ui));
row.noted
}
#[test]
fn outside_a_package_the_row_is_not_drawn() {
assert_eq!(shown(|_| CliStatus::Unsupported), None);
}
}