use std::io::IsTerminal;
use console::style;
pub(crate) const ACCENT: u8 = 173;
pub(crate) const BRAND: u8 = 215;
pub(crate) fn brand(s: &str) -> String {
style(s).color256(BRAND).bold().to_string()
}
pub(crate) fn title(s: &str) -> String {
style(s).color256(ACCENT).bold().to_string()
}
pub(crate) fn heading(s: &str) -> String {
style(s).color256(ACCENT).bold().to_string()
}
pub(crate) fn hint(s: &str) -> String {
style(s).dim().to_string()
}
pub(crate) fn command(s: &str) -> String {
style(s).bold().to_string()
}
pub(crate) fn path(s: &str) -> String {
style(s).cyan().to_string()
}
pub(crate) fn url(s: &str) -> String {
style(s).cyan().underlined().to_string()
}
pub(crate) fn value(s: &str) -> String {
style(s).bold().to_string()
}
pub(crate) fn accent(s: &str) -> String {
style(s).color256(ACCENT).to_string()
}
pub(crate) fn check() -> String {
style("✓").green().to_string()
}
pub(crate) fn warn(s: &str) -> String {
style(s).yellow().to_string()
}
pub(crate) fn arrow() -> String {
style("›").color256(ACCENT).to_string()
}
pub(crate) fn divider() -> String {
style("──────────────────────────────────────────")
.dim()
.to_string()
}
pub(crate) fn step(n: usize, total: usize, title: &str) -> String {
format!(
"{} {}",
accent(&format!("Step {n} of {total} ·")),
value(title)
)
}
pub(crate) fn confirm(value_text: &str) -> String {
format!(" {} {}", check(), value(value_text))
}
pub(crate) fn welcome_banner() -> String {
let row =
|cmd: &str, desc: &str| format!(" {} {}", command(&format!("{cmd:<30}")), hint(desc));
let mut out: Vec<String> = Vec::new();
out.push(String::new());
out.push(format!(
" {} {}",
brand("RustIO"),
hint("· the admin for your Rust app")
));
out.push(String::new());
out.push(format!(
" {}",
hint("Describe your data as Rust structs and get a complete admin —")
));
out.push(format!(
" {}",
hint("list, create, edit, search, delete — with login, roles, recovery,")
));
out.push(format!(
" {}",
hint("and an audit trail already wired in.")
));
out.push(String::new());
out.push(format!(" {}", heading("Getting started")));
out.push(row(
"rustio-admin new <project>",
"create a project (guided)",
));
out.push(row(
"rustio-admin startapp <model>",
"add a model — table + admin page",
));
out.push(row(
"rustio-admin migrate apply",
"apply database migrations",
));
out.push(row("rustio-admin user create", "create a login"));
out.push(format!(
" {} {} {}",
command(&format!("{:<30}", "cargo run")),
hint("launch →"),
url("http://127.0.0.1:8000/admin")
));
out.push(String::new());
out.push(format!(" {}", heading("When you need more")));
out.push(row("rustio-admin ai", "govern an external AI assistant"));
out.push(row(
"rustio-admin memory",
"record the \"why\" behind the project",
));
out.push(row("rustio-admin builder", "build from recorded changes"));
out.push(String::new());
out.push(format!(" {}", heading("Help")));
out.push(row("rustio-admin doctor", "is my environment ready?"));
out.push(row("rustio-admin docs", "where the documentation lives"));
out.push(row(
"rustio-admin <command> --help",
"full, example-rich help for any command",
));
out.push(String::new());
out.push(format!(
" {}",
hint("The complete command list follows below.")
));
out.push(format!(" {}", divider()));
out.join("\n")
}
pub(crate) fn is_interactive() -> bool {
std::io::stdout().is_terminal() && std::env::var_os("CI").is_none()
}
pub(crate) fn next_step(what: &str, steps: &[(String, String)]) {
println!();
println!(" {}", heading(&format!("Next — {what}:")));
println!();
for (cmd, annot) in steps {
println!(" {}", command(cmd));
if !annot.is_empty() {
println!(" {annot}");
}
}
}