use kernel::consts_policy::DEFAULT_AT;
use sprawling::assembly::SystemClock;
use sprawling::firstrun;
use super::calling::call;
use super::city::{Entrance, init, resume, serve, up, up_at, use_folder};
use super::data::{adopt, enrol, export, fork, install, replay, restore, status};
use super::grammar::{Arguments, Invocation, parse};
use super::verbs::{self, Verb};
use super::{CLIENT_COMPLETE, CLIENT_FILES};
use std::process::ExitCode;
pub(super) fn client_summary() -> String {
if CLIENT_COMPLETE {
let total: usize = CLIENT_FILES.iter().map(|f| f.gz.len()).sum();
format!(
"embedded, {} file(s), {total} gzipped byte(s)",
CLIENT_FILES.len()
)
} else {
"page shell only - run `just build-web`, then rebuild this binary".to_owned()
}
}
pub(super) fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
match parse(&args) {
Ok(Invocation::FirstScreen) => first_screen(),
Ok(Invocation::Overview) => {
println!("{}", verbs::overview());
ExitCode::SUCCESS
}
Ok(Invocation::Help(verb)) => {
if let Some(row) = verbs::row(verb) {
println!("{}", verbs::help(row));
}
ExitCode::SUCCESS
}
Ok(Invocation::Version) => status(&args),
Ok(Invocation::Run(verb, read)) => run(verb, &read, &args),
Err(refused) => {
if let Some(result) = runtime::tools::run_container_guard(&args) {
return match result {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprint!(
"{}",
super::refusal::written(&error, super::refusal::Form::Human)
);
ExitCode::FAILURE
}
};
}
eprintln!("sprawling: {refused}");
ExitCode::from(2)
}
}
}
fn run(verb: Verb, read: &Arguments, args: &[String]) -> ExitCode {
let nth = |n| read.positional(n);
match verb {
Verb::Status => status(args),
Verb::PrivacyElevatedWrite => {
match sprawling::privacy::cli::elevated_write(nth(1).map(String::as_str)) {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprint!(
"{}",
super::refusal::written(&error, super::refusal::Form::Human)
);
ExitCode::FAILURE
}
}
}
Verb::PrivacyStatus => answered(sprawling::privacy::cli::status(), args),
Verb::PrivacyInspect => answered(
sprawling::privacy::cli::inspect(SystemClock, nth(1).map(String::as_str)),
args,
),
Verb::PrivacyApply => answered(
sprawling::privacy::cli::apply(
SystemClock,
nth(1).map(String::as_str),
nth(2).map(String::as_str),
),
args,
),
Verb::PrivacyRestore => answered(
sprawling::privacy::cli::restore(
SystemClock,
nth(1).map(String::as_str),
nth(2).map(String::as_str),
),
args,
),
Verb::PrivacyRestoreAll => answered(
sprawling::privacy::cli::restore_all(SystemClock, |line| println!("{line}"))
.map(|()| String::new()),
args,
),
Verb::PrivacyReconcile => answered(
sprawling::privacy::cli::reconcile(SystemClock, nth(1).map(String::as_str)),
args,
),
Verb::Replay => replay(nth(1)),
Verb::Whose => super::whose::verb(read),
Verb::Check => super::check::verb(nth(1)),
Verb::View => super::view::verb(read),
Verb::PlaybackExport => super::playback::export(read),
Verb::PlaybackCheck => super::playback::check(read),
Verb::Init => init(read),
Verb::Up => up(read, args),
Verb::Install => install(args),
Verb::Doctor => sprawling::doctor::verb(args, default_city_location),
Verb::Desktop => super::desktop::verb(nth(1).map(String::as_str)),
Verb::Call => call(args).into(),
Verb::Dispatch => super::dispatch::verb(read),
Verb::Gauge => super::gauge::verb(read),
Verb::Enrol => enrol(read),
Verb::Serve => serve(nth(1), nth(2), args),
Verb::Export => export(nth(1), nth(2)),
Verb::Restore => restore(nth(1), nth(2)),
Verb::Resume => resume(nth(1)),
Verb::Fork => fork(args),
Verb::Adopt => adopt(nth(1), nth(2)),
}
}
fn answered(result: Result<String, kernel::AxError>, args: &[String]) -> ExitCode {
match result {
Ok(answer) => {
if !answer.is_empty() {
println!("{answer}");
}
ExitCode::SUCCESS
}
Err(error) => {
eprint!(
"{}",
super::refusal::written(&error, super::refusal::Form::of(args))
);
ExitCode::FAILURE
}
}
}
pub(super) fn first_screen() -> ExitCode {
let city = default_city_location();
if city::has_history(&city).is_ok_and(|history| history == city::History::Present) {
let code = up_at(&city, DEFAULT_AT, &[], Entrance::Bare);
hold();
return code;
}
let answered = firstrun::ask(&city, &mut std::io::stdin().lock(), &mut std::io::stdout());
let code = match answered {
Ok(firstrun::FirstScreen::Start(city)) => up_at(&city, DEFAULT_AT, &[], Entrance::Bare),
Ok(firstrun::FirstScreen::Use(folder)) => use_folder(&folder),
Ok(firstrun::FirstScreen::Quit) => {
println!("{}", verbs::overview());
ExitCode::from(2)
}
Err(err) => {
eprintln!("could not read the answer: {err}");
ExitCode::FAILURE
}
};
hold();
code
}
fn hold() {
println!("\n Press Enter to close.");
let mut ignored = String::new();
drop(std::io::stdin().read_line(&mut ignored));
}
pub(super) fn default_city_location() -> std::path::PathBuf {
let home = accounting::home::Home::detect().ok();
match std::env::current_exe()
.ok()
.and_then(|exe| exe.parent().map(std::path::Path::to_path_buf))
{
Some(beside) => {
let takes_a_city = firstrun::writability(&beside);
firstrun::default_city(&beside, home.as_ref(), takes_a_city)
}
None => firstrun::default_city(
std::path::Path::new("."),
home.as_ref(),
firstrun::BesideBinary::Writable,
),
}
}
pub(super) fn flag_value(args: &[String], flag: &str) -> Option<String> {
let mut pairs = args.windows(2);
pairs.find_map(|pair| match pair {
[name, value] if name == flag => Some(value.clone()),
_ => None,
})
}
pub(super) fn log_floor(args: &[String]) -> Result<Option<runtime::diagnostics::Level>, String> {
let Some(asked) = flag_value(args, "--log") else {
return Ok(Some(runtime::diagnostics::Level::DEFAULT));
};
if asked == "off" {
return Ok(None);
}
runtime::diagnostics::Level::parse(&asked)
.map(Some)
.ok_or(asked)
}
pub(super) fn log_levels() -> String {
format!("use --log with one of {}", level_names())
}
pub(super) fn level_names() -> String {
let names: Vec<&str> = runtime::diagnostics::Level::ALL
.into_iter()
.map(|level| level.as_str())
.collect();
format!("{}, or off", names.join(", "))
}