Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 1 addition & 24 deletions crates/mensung-client/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,15 @@
//! 2 bad command-line usage; 70 an internal or database error, the same
//! convention as the Unix EX_SOFTWARE sysexit.

use std::io::IsTerminal as _;
use std::process::ExitCode;

use crossterm::style::Stylize;
use mensung_core::{check_interactions, lookup_drug, CoreError, LookupOutcome};
use mensung_db::{Database, DbError, DrugRecord};
use mensung_domain::{DrugId, Severity};

use crate::style::{styled_out as styled, Tone};
use crate::DISCLAIMER;

#[derive(Clone, Copy)]
enum Tone {
Danger,
Warning,
Ok,
Dim,
Bold,
}

fn styled(text: &str, tone: Tone) -> String {
if !std::io::stdout().is_terminal() {
return text.to_string();
}
match tone {
Tone::Danger => text.red().bold().to_string(),
Tone::Warning => text.yellow().to_string(),
Tone::Ok => text.green().to_string(),
Tone::Dim => text.dark_grey().to_string(),
Tone::Bold => text.bold().to_string(),
}
}

fn severity_tone(severity: Severity) -> Tone {
match severity {
Severity::Contraindicated | Severity::HighRisk => Tone::Danger,
Expand Down
64 changes: 54 additions & 10 deletions crates/mensung-client/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use std::io::{IsTerminal, Write};
use std::path::{Path, PathBuf};

use crate::style::{styled_err as styled, Tone};

pub(crate) fn database_path() -> PathBuf {
let dir = std::env::var("MENSUNG_DATA_DIR")
.map(PathBuf::from)
Expand All @@ -42,7 +44,13 @@ pub(crate) fn load_or_install(path: &Path) -> Result<Vec<u8>, String> {
.map_err(|err| format!("Fatal: could not read {}: {err}", path.display()));
}

eprintln!("No medication database found at {}.", path.display());
eprintln!(
"{}",
styled(
&format!("No medication database found at {}.", path.display()),
Tone::Warning
)
);
eprintln!(
"You can place a compiled medical_database.men there yourself, or let mensung install a dataset now."
);
Expand Down Expand Up @@ -76,7 +84,13 @@ fn should_install() -> bool {
}

fn confirm_interactively() -> bool {
eprint!("Would you like to install the dataset now? [y/N] ");
eprint!(
"{} ",
styled(
"Would you like to install the dataset now? [y/N]",
Tone::Bold
)
);
let _ = std::io::stderr().flush();

let mut answer = String::new();
Expand All @@ -87,18 +101,36 @@ fn confirm_interactively() -> bool {
}

fn install(path: &Path) -> Result<Vec<u8>, String> {
eprintln!("Downloading MenSung's enriched dataset (DDInter + RxNorm + WHO ATC + PubChem + openFDA)...");
eprintln!(
"{}",
styled(
"Downloading MenSung's enriched dataset (DDInter + RxNorm + WHO ATC + PubChem + openFDA)...",
Tone::Bold
)
);

match crate::dataset_download::fetch() {
Ok(bytes) => {
write_database(path, &bytes)?;
eprintln!("Installed the enriched dataset to {}.", path.display());
eprintln!(
"{}",
styled(
&format!("Installed the enriched dataset to {}.", path.display()),
Tone::Ok
)
);
return Ok(bytes);
}
Err(err) => {
eprintln!(
"Could not download the enriched dataset ({err}); falling back to a \
DDInter-only build..."
"{}",
styled(
&format!(
"Could not download the enriched dataset ({err}); falling back to a \
DDInter-only build..."
),
Tone::Warning
)
);
}
}
Expand All @@ -107,7 +139,13 @@ fn install(path: &Path) -> Result<Vec<u8>, String> {
}

fn install_ddinter_only(path: &Path) -> Result<Vec<u8>, String> {
eprintln!("Downloading DDInter's dataset (CC BY-NC-SA 4.0, see MEDICAL_DATA_POLICY.md)...");
eprintln!(
"{}",
styled(
"Downloading DDInter's dataset (CC BY-NC-SA 4.0, see MEDICAL_DATA_POLICY.md)...",
Tone::Bold
)
);

let download_dir = std::env::temp_dir().join("mensung-ddinter-download");
let (drugs, interactions) = mensung_builder::download_and_import_ddinter(&download_dir)
Expand All @@ -120,9 +158,15 @@ fn install_ddinter_only(path: &Path) -> Result<Vec<u8>, String> {
write_database(path, &bytes)?;

eprintln!(
"Installed {} interactions to {}.",
report.interactions,
path.display()
"{}",
styled(
&format!(
"Installed {} interactions to {}.",
report.interactions,
path.display()
),
Tone::Ok
)
);

Ok(bytes)
Expand Down
7 changes: 6 additions & 1 deletion crates/mensung-client/src/dataset_download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

use std::io::{IsTerminal as _, Read as _, Write as _};

use crate::style::{styled_err as styled, Tone};

const RELEASE_TAG_URL: &str =
"https://api.github.com/repos/Etoile-Bleu/MenSung/releases/tags/medical-database";
const ASSET_NAME: &str = "medical_database.men";
Expand Down Expand Up @@ -117,7 +119,10 @@ fn download_asset(url: &str) -> Result<Vec<u8>, DatasetDownloadError> {
}
bytes.extend_from_slice(&chunk[..read]);
if show_progress {
eprint!("\r{}", render_progress(bytes.len() as u64, total));
eprint!(
"\r{}",
styled(&render_progress(bytes.len() as u64, total), Tone::Ok)
);
let _ = std::io::stderr().flush();
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/mensung-client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
mod cli;
mod data;
mod dataset_download;
mod style;
mod tui;
mod update;

Expand Down
40 changes: 40 additions & 0 deletions crates/mensung-client/src/style.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! Terminal color helpers shared by the CLI's interaction/info output
//! (`cli.rs`) and the dataset installer's progress messages (`data.rs`,
//! `dataset_download.rs`), so both present the same red/yellow/green
//! severity convention the TUI already uses. Colors are only ever applied
//! when the destination stream is a real terminal, so piped or redirected
//! output stays plain text.

use std::io::IsTerminal as _;

use crossterm::style::Stylize;

#[derive(Clone, Copy)]
pub(crate) enum Tone {
Danger,
Warning,
Ok,
Dim,
Bold,
}

pub(crate) fn styled_out(text: &str, tone: Tone) -> String {
styled(text, tone, std::io::stdout().is_terminal())
}

pub(crate) fn styled_err(text: &str, tone: Tone) -> String {
styled(text, tone, std::io::stderr().is_terminal())
}

fn styled(text: &str, tone: Tone, is_terminal: bool) -> String {
if !is_terminal {
return text.to_string();
}
match tone {
Tone::Danger => text.red().bold().to_string(),
Tone::Warning => text.yellow().to_string(),
Tone::Ok => text.green().to_string(),
Tone::Dim => text.dark_grey().to_string(),
Tone::Bold => text.bold().to_string(),
}
}
Loading