use clap::{Parser, Subcommand};
use std::path::PathBuf;
use crate::commands;
use crate::ui::{self, ColorMode};
#[cfg(feature = "verification")]
const LARGO_VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (full)");
#[cfg(not(feature = "verification"))]
const LARGO_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Parser)]
#[command(name = "largo")]
#[command(about = "The LOGOS build tool", long_about = None)]
#[command(version = LARGO_VERSION)]
#[command(arg_required_else_help = true)]
#[command(styles = ui::CLAP_STYLES)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
#[arg(long, short, global = true)]
pub quiet: bool,
#[arg(long, short, global = true, action = clap::ArgAction::Count)]
pub verbose: u8,
#[arg(long, global = true, value_enum, default_value = "auto", value_name = "WHEN")]
pub color: ColorMode,
}
#[derive(Subcommand)]
pub enum Commands {
#[command(after_help = "Examples:\n largo new hello\n cd hello\n largo run")]
New {
name: String,
},
#[command(after_help = "Examples:\n mkdir app && cd app\n largo init\n largo init --name my_app")]
Init {
#[arg(long)]
name: Option<String>,
},
#[command(after_help = "Examples:\n largo build\n largo build --release\n largo build --emit wasm\n largo build --lib --target aarch64-unknown-linux-gnu")]
Build {
#[arg(long, short)]
release: bool,
#[arg(long)]
verify: bool,
#[arg(long)]
license: Option<String>,
#[arg(long)]
lib: bool,
#[arg(long)]
target: Option<String>,
#[arg(long)]
native_functions: bool,
#[arg(long)]
emit: Option<String>,
},
#[command(after_help = "Examples:\n largo verify --license sub_xxxxx\n LOGOS_LICENSE=sub_xxxxx largo verify")]
Verify {
#[arg(long)]
license: Option<String>,
},
#[command(after_help = "Examples:\n largo run\n largo run --release\n largo run --interpret\n largo run --emit wasm\n largo run -- input.txt --program-flag")]
Run {
#[arg(long, short)]
release: bool,
#[arg(long, short)]
interpret: bool,
#[arg(long, conflicts_with = "interpret")]
emit: Option<String>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
#[command(after_help = "Examples:\n largo check\n largo check --quiet")]
Check {
#[arg(long)]
deep: bool,
},
#[command(after_help = "Examples:\n largo opts src/main.lg\n largo opts src/main.lg --json")]
Opts {
file: PathBuf,
#[arg(long)]
json: bool,
},
#[command(after_help = "Examples:\n largo publish --dry-run\n largo publish\n largo publish --allow-dirty")]
Publish {
#[arg(long)]
registry: Option<String>,
#[arg(long)]
dry_run: bool,
#[arg(long)]
allow_dirty: bool,
},
#[command(after_help = "Examples:\n largo login\n largo login --token lgr_xxxxx")]
Login {
#[arg(long)]
registry: Option<String>,
#[arg(long)]
token: Option<String>,
},
#[command(after_help = "Examples:\n largo logout\n largo logout --registry https://registry.example.com")]
Logout {
#[arg(long)]
registry: Option<String>,
},
#[command(after_help = "Examples:\n largo doctor\n largo doctor --registry https://registry.example.com")]
Doctor {
#[arg(long)]
registry: Option<String>,
},
#[command(after_help = "Examples:\n largo repl\n largo repl --logic\n largo repl --logic --format latex\n largo repl --load session.lg\n printf 'Let x be 5.\\nShow x.\\n' | largo repl")]
Repl {
#[arg(long)]
logic: bool,
#[arg(long, value_enum)]
format: Option<crate::commands::logic::LogicFormat>,
#[arg(long)]
load: Option<PathBuf>,
},
#[command(after_help = "Examples:\n largo sat instance.cnf\n largo sat instance.cnf --proof refutation.drat\n largo sat instance.cnf --stats")]
Sat {
file: PathBuf,
#[arg(long)]
proof: Option<PathBuf>,
#[arg(long)]
stats: bool,
},
#[command(after_help = "Examples:\n largo prove # prove the project entry\n largo prove geometry.lg\n largo prove socrates.lg --trace\n largo prove tarski.lg --json")]
Prove {
file: Option<PathBuf>,
#[arg(long)]
trace: bool,
#[arg(long, conflicts_with = "trace")]
json: bool,
},
#[command(after_help = "Examples:\n largo logic \"Every woman loves a man.\"\n largo logic \"Every woman loves a man.\" --all-readings\n largo logic \"It might rain.\" --format kripke\n echo \"Socrates is mortal.\" | largo logic\n printf 'A farmer owns a donkey.\\nHe feeds it.' | largo logic --discourse")]
Logic {
sentence: Option<String>,
#[arg(long, short, conflicts_with = "sentence")]
file: Option<PathBuf>,
#[arg(long, value_enum, default_value = "unicode")]
format: crate::commands::logic::LogicFormat,
#[arg(long, conflicts_with = "discourse")]
all_readings: bool,
#[arg(long)]
pragmatic: bool,
#[arg(long)]
discourse: bool,
},
#[command(after_help = "Examples:\n largo doc\n largo doc --out book")]
Doc {
#[arg(long)]
out: Option<PathBuf>,
},
#[command(after_help = "Examples:\n largo add math_utils\n largo add math_utils@1.2\n largo add logos:std\n largo add local_lib --path ../local_lib\n largo add remote --git https://example.com/remote.git")]
Add {
spec: String,
#[arg(long, conflicts_with = "git")]
path: Option<String>,
#[arg(long, conflicts_with = "path")]
git: Option<String>,
},
#[command(after_help = "Examples:\n largo remove math_utils")]
Remove {
name: String,
},
#[command(after_help = "Examples:\n largo fmt\n largo fmt src/main.lg\n largo fmt --check # CI gate, writes nothing")]
Fmt {
paths: Vec<PathBuf>,
#[arg(long)]
check: bool,
},
#[command(after_help = "Examples:\n largo emit rust\n largo emit rust -o generated.rs\n largo emit c standalone.lg\n largo emit wasm\n largo emit wasm-linked -o dist/app.wasm")]
Emit {
#[arg(value_enum)]
target: crate::commands::emit::EmitTarget,
file: Option<PathBuf>,
#[arg(long, short)]
output: Option<PathBuf>,
},
#[command(after_help = "Examples:\n largo clean\n largo clean --all")]
Clean {
#[arg(long)]
all: bool,
},
#[command(after_help = "Examples:\n largo completions bash > ~/.local/share/bash-completion/completions/largo\n largo completions zsh > ~/.zfunc/_largo\n largo completions fish > ~/.config/fish/completions/largo.fish")]
Completions {
#[arg(value_enum)]
shell: clap_complete::Shell,
},
#[command(hide = true)]
Test {
#[arg(trailing_var_arg = true, allow_hyphen_values = true, hide = true)]
args: Vec<String>,
},
}
pub fn run_cli() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
ui::init(cli.color, cli.quiet, cli.verbose);
match cli.command {
Commands::New { name } => commands::new::cmd_new(&name),
Commands::Init { name } => commands::new::cmd_init(name.as_deref()),
Commands::Build { release, verify, license, lib, target, native_functions, emit } => {
commands::build::cmd_build(release, verify, license, lib, target, native_functions, emit)
}
Commands::Run { emit: Some(e), args, .. } if e == "wasm" => commands::run::cmd_run_wasm(&args, false),
Commands::Run { emit: Some(e), args, .. } if e == "wasm-linked" => commands::run::cmd_run_wasm(&args, true),
Commands::Run { emit: Some(e), .. } => {
Err(format!("unknown --emit target '{e}' (expected 'wasm' or 'wasm-linked')").into())
}
Commands::Run { interpret, args, .. } if interpret => commands::run::cmd_run_interpret(&args),
Commands::Run { release, args, .. } => commands::run::cmd_run(release, &args),
Commands::Check { deep } => commands::check::cmd_check(deep),
Commands::Opts { file, json } => commands::opts::cmd_opts(&file, json),
Commands::Verify { license } => commands::verify::cmd_verify(license),
Commands::Publish { registry, dry_run, allow_dirty } => {
commands::publish::cmd_publish(registry.as_deref(), dry_run, allow_dirty)
}
Commands::Login { registry, token } => commands::publish::cmd_login(registry.as_deref(), token),
Commands::Logout { registry } => commands::publish::cmd_logout(registry.as_deref()),
Commands::Doctor { registry } => commands::doctor::cmd_doctor(registry),
Commands::Repl { logic, format, load } => crate::repl::cmd_repl(logic, format, load),
Commands::Sat { file, proof, stats } => commands::sat::cmd_sat(file, proof, stats),
Commands::Prove { file, trace, json } => commands::prove::cmd_prove(file, trace, json),
Commands::Logic { sentence, file, format, all_readings, pragmatic, discourse } => {
commands::logic::cmd_logic(sentence, file, format, all_readings, pragmatic, discourse)
}
Commands::Doc { out } => commands::doc::cmd_doc(out),
Commands::Add { spec, path, git } => commands::deps::cmd_add(spec, path, git),
Commands::Remove { name } => commands::deps::cmd_remove(name),
Commands::Fmt { paths, check } => commands::fmt::cmd_fmt(paths, check),
Commands::Emit { target, file, output } => commands::emit::cmd_emit(target, file, output),
Commands::Clean { all } => commands::clean::cmd_clean(all),
Commands::Completions { shell } => commands::completions::cmd_completions(shell),
Commands::Test { .. } => Err(ui::CliError::with_hint(
"`largo test` is reserved for the LOGOS test framework (coming in a future release)",
"run `largo check` to validate your project today",
)
.exit_code(ui::EXIT_USAGE)
.into()),
}
}