#![allow(dead_code)]
use cli_ui::styles::{paint, CYAN, DIM, OK};
use cli_ui::{bail, header, ok, phase, step, summary, CliCommand, CliOptions};
use std::path::PathBuf;
fn workspace_examples() -> Vec<String> {
let Ok(rd) = std::fs::read_dir("examples") else {
return Vec::new();
};
let mut v: Vec<String> = rd
.flatten()
.filter_map(|e| {
let p = e.path();
if p.is_file() && p.extension()?.to_str() == Some("rs") {
Some(p.file_stem()?.to_string_lossy().to_string())
} else {
None
}
})
.collect();
v.sort();
v
}
fn workspace_bins() -> Vec<String> {
let Ok(rd) = std::fs::read_dir("src/bin") else {
return Vec::new();
};
let mut v: Vec<String> = rd
.flatten()
.filter_map(|e| {
let p = e.path();
if p.is_file() && p.extension()?.to_str() == Some("rs") {
Some(p.file_stem()?.to_string_lossy().to_string())
} else {
None
}
})
.collect();
v.sort();
v
}
fn workspace_members() -> Vec<String> {
let Ok(src) = std::fs::read_to_string("Cargo.toml") else {
return Vec::new();
};
let mut members = Vec::new();
let mut in_members = false;
for line in src.lines() {
let t = line.trim();
if t == "members = [" || t.starts_with("members = [") {
in_members = true;
}
if in_members {
if t == "]" {
break;
}
if let Some(s) = t
.trim_matches(|c| c == '"' || c == ',' || c == '[' || c == ']')
.split('"')
.next()
{
let clean = s.trim().trim_matches('"').trim_matches(',').trim();
if !clean.is_empty() && !clean.starts_with('[') {
members.push(clean.to_string());
}
}
}
}
members
}
fn available_features() -> Vec<String> {
let Ok(src) = std::fs::read_to_string("Cargo.toml") else {
return Vec::new();
};
let mut features = Vec::new();
let mut in_features = false;
for line in src.lines() {
let t = line.trim();
if t == "[features]" {
in_features = true;
continue;
}
if in_features {
if t.starts_with('[') {
break;
}
if let Some(name) = t.split('=').next() {
let n = name.trim();
if !n.is_empty() && !n.starts_with('#') {
features.push(n.to_string());
}
}
}
}
features
}
fn known_profiles() -> Vec<String> {
vec![
"dev".into(),
"release".into(),
"test".into(),
"bench".into(),
]
}
fn known_targets() -> Vec<String> {
vec![
"x86_64-unknown-linux-gnu".into(),
"x86_64-unknown-linux-musl".into(),
"x86_64-apple-darwin".into(),
"aarch64-apple-darwin".into(),
"x86_64-pc-windows-msvc".into(),
"wasm32-unknown-unknown".into(),
"wasm32-wasi".into(),
"aarch64-unknown-linux-gnu".into(),
]
}
fn known_editions() -> Vec<String> {
vec!["2015".into(), "2018".into(), "2021".into(), "2024".into()]
}
fn known_vcs() -> Vec<String> {
vec![
"git".into(),
"hg".into(),
"pijul".into(),
"fossil".into(),
"none".into(),
]
}
#[derive(CliOptions)]
#[cli(about = "global flags for mini-cargo")]
struct Global {
#[arg(short = 'v', long = "verbose", negatable)]
verbose: bool,
#[arg(short = 'q', long = "quiet", negatable)]
quiet: bool,
#[arg(
long = "color",
default = "auto",
validate(one_of("auto", "always", "never"))
)]
color: String,
#[arg(long = "manifest-path", validate(is_file, ext("toml")))]
manifest_path: Option<PathBuf>,
#[arg(long = "target-dir", validate(is_dir))]
target_dir: Option<PathBuf>,
}
#[derive(CliOptions)]
#[cli(
name = "build",
about = "Compile the current package",
tagline = "all targets by default",
example = "cargo-mini build --release",
example = "cargo-mini build --target wasm32-unknown-unknown"
)]
struct BuildOpt {
#[arg(section = "Build", short = 'r', long = "release")]
release: bool,
#[arg(section = "Build", long = "example", complete = workspace_examples)]
example: Option<String>,
#[arg(section = "Build", long = "bin", complete = workspace_bins)]
bin: Option<String>,
#[arg(section = "Features", short = 'F', long = "features",
complete = available_features)]
features: Option<String>,
#[arg(section = "Features", long = "all-features")]
all_features: bool,
#[arg(section = "Features", long = "no-default-features")]
no_default_features: bool,
#[arg(section = "Target", long = "target", complete = known_targets)]
target: Option<String>,
#[arg(section = "Target", long = "profile", complete = known_profiles)]
profile: Option<String>,
#[arg(
section = "Performance",
short = 'j',
long = "jobs",
default = "4",
validate(range(1, 256))
)]
jobs: usize,
#[arg(section = "Output", long = "quiet", negatable)]
quiet: bool,
#[arg(section = "Output", long = "verbose", negatable)]
verbose: bool,
}
#[derive(CliOptions)]
#[cli(
name = "run",
about = "Run a binary or example of the local package",
example = "cargo-mini run --example mini_cargo",
example = "cargo-mini run --release -- --help"
)]
struct RunOpt {
#[arg(section = "Target", long = "example", complete = workspace_examples)]
example: Option<String>,
#[arg(section = "Target", long = "bin", complete = workspace_bins)]
bin: Option<String>,
#[arg(section = "Build", short = 'r', long = "release")]
release: bool,
#[arg(section = "Features", short = 'F', long = "features",
complete = available_features)]
features: Option<String>,
#[arg(section = "Build", long = "profile", complete = known_profiles)]
profile: Option<String>,
#[arg(section = "Build", long = "target", complete = known_targets)]
target: Option<String>,
#[arg(skip)]
_extra_args: Vec<String>,
}
#[derive(CliOptions)]
#[cli(
name = "add",
about = "Add dependencies to a Cargo.toml manifest file",
example = "cargo-mini add serde --features derive",
example = "cargo-mini add tokio --features full --no-default-features"
)]
struct AddOpt {
#[arg(positional)]
crate_name: String,
#[arg(section = "Dependency", short = 'F', long = "features")]
features: Option<String>,
#[arg(section = "Dependency", long = "dev")]
dev: bool,
#[arg(section = "Dependency", long = "build")]
build: bool,
#[arg(section = "Workspace", short = 'p', long = "package",
complete = workspace_members)]
package: Option<String>,
#[arg(section = "Misc", long = "dry-run")]
dry_run: bool,
#[arg(section = "Dependency", long = "no-default-features")]
no_default_features: bool,
}
#[derive(CliOptions)]
#[cli(
name = "remove",
about = "Remove dependencies from a Cargo.toml manifest file",
example = "cargo-mini remove serde"
)]
struct RemoveOpt {
#[arg(positional)]
crate_name: String,
#[arg(section = "Dependency", long = "dev")]
dev: bool,
#[arg(section = "Dependency", long = "build")]
build: bool,
#[arg(section = "Workspace", short = 'p', long = "package",
complete = workspace_members)]
package: Option<String>,
}
#[derive(CliOptions)]
#[cli(
name = "new",
about = "Create a new Cargo package",
example = "cargo-mini new my-project --lib",
example = "cargo-mini new my-cli --edition 2021 --vcs git"
)]
struct NewOpt {
#[arg(positional)]
path: PathBuf,
#[arg(section = "Template", long = "lib")]
lib: bool,
#[arg(section = "Template", long = "bin")]
bin: bool,
#[arg(
section = "Manifest",
long = "edition",
default = "2021",
validate(one_of("2015", "2018", "2021", "2024"))
)]
edition: String,
#[arg(
section = "Manifest",
long = "vcs",
validate(one_of("git", "hg", "pijul", "fossil", "none"))
)]
vcs: Option<String>,
#[arg(section = "Manifest", long = "name")]
name: Option<String>,
}
#[derive(CliOptions)]
#[cli(
name = "init",
about = "Create a new Cargo package in an existing directory",
example = "cargo-mini init",
example = "cargo-mini init my-dir --lib"
)]
struct InitOpt {
#[arg(positional)]
path: Option<PathBuf>,
#[arg(section = "Template", long = "lib")]
lib: bool,
#[arg(section = "Template", long = "bin")]
bin: bool,
#[arg(
section = "Manifest",
long = "edition",
default = "2021",
validate(one_of("2015", "2018", "2021", "2024"))
)]
edition: String,
#[arg(
section = "Manifest",
long = "vcs",
validate(one_of("git", "hg", "pijul", "fossil", "none"))
)]
vcs: Option<String>,
}
#[derive(CliOptions)]
#[cli(
name = "test",
about = "Execute all unit and integration tests and build examples",
example = "cargo-mini test --lib",
example = "cargo-mini test integration_test -- --nocapture"
)]
struct TestOpt {
#[arg(positional)]
filter: Option<String>,
#[arg(section = "Target", long = "lib")]
lib: bool,
#[arg(section = "Target", long = "example", complete = workspace_examples)]
example: Option<String>,
#[arg(section = "Build", long = "release")]
release: bool,
#[arg(section = "Features", short = 'F', long = "features",
complete = available_features)]
features: Option<String>,
#[arg(
section = "Output",
long = "test-threads",
default = "4",
validate(range(1, 256))
)]
test_threads: usize,
#[arg(section = "Output", long = "nocapture")]
nocapture: bool,
}
#[derive(CliOptions)]
#[cli(
name = "check",
about = "Check a local package and all of its dependencies for errors",
example = "cargo-mini check --all-targets"
)]
struct CheckOpt {
#[arg(section = "Target", long = "all-targets")]
all_targets: bool,
#[arg(section = "Features", short = 'F', long = "features",
complete = available_features)]
features: Option<String>,
#[arg(section = "Target", long = "target", complete = known_targets)]
target: Option<String>,
}
#[derive(CliOptions)]
#[cli(
name = "clean",
about = "Remove artifacts that cargo has generated in the past",
example = "cargo-mini clean --release"
)]
struct CleanOpt {
#[arg(section = "Target", long = "release")]
release: bool,
#[arg(section = "Target", long = "target", complete = known_targets)]
target: Option<String>,
#[arg(section = "Workspace", short = 'p', long = "package",
complete = workspace_members)]
package: Option<String>,
}
#[derive(CliOptions)]
#[cli(
name = "metadata",
about = "Output the resolved dependencies of the current package as JSON"
)]
struct MetadataOpt {
#[arg(
section = "Output",
long = "format-version",
default = "1",
validate(one_of("1"))
)]
format_version: u32,
#[arg(section = "Output", long = "no-deps")]
no_deps: bool,
}
#[derive(CliOptions)]
#[cli(
name = "update",
about = "Update dependencies as recorded in the lock file",
example = "cargo-mini update serde"
)]
struct UpdateOpt {
#[arg(positional)]
crate_name: Option<String>,
#[arg(section = "Misc", long = "dry-run")]
dry_run: bool,
#[arg(section = "Misc", long = "unstable-features")]
unstable: bool,
}
#[derive(CliOptions)]
#[cli(
name = "info",
about = "Display information about a package in the registry",
example = "cargo-mini info serde"
)]
struct InfoOpt {
#[arg(positional)]
crate_name: String,
#[arg(section = "Filter", long = "version")]
version: Option<String>,
}
#[derive(CliOptions)]
#[cli(
name = "import",
about = "Import a list of dependencies from a CSV manifest",
example = "cargo-mini import --file deps.csv"
)]
struct ImportOpt {
#[arg(
section = "Input",
long = "file",
validate(exists, is_file, ext("csv"))
)]
file: PathBuf,
#[arg(section = "Misc", long = "dry-run")]
dry_run: bool,
}
#[derive(CliCommand)]
#[cli(
name = "cargo-mini",
about = "The Rust package manager (emulator)",
tagline = "no real side effects — safe to tab-complete",
theme = "yellow",
global = Global,
example = "cargo-mini build --release",
example = "cargo-mini run --example mini_cargo",
example = "cargo-mini add tokio --features full",
)]
enum Cmd {
Build(BuildOpt),
#[cli(alias = "r")]
Run(RunOpt),
Add(AddOpt),
#[cli(alias = "rm")]
Remove(RemoveOpt),
New(NewOpt),
Init(InitOpt),
#[cli(alias = "t")]
Test(TestOpt),
Check(CheckOpt),
Clean(CleanOpt),
Metadata(MetadataOpt),
Update(UpdateOpt),
Info(InfoOpt),
Import(ImportOpt),
}
fn main() {
let cmd = match Cmd::parse() {
Ok(c) => c,
Err(e) => bail!("{}", e),
};
let global = Cmd::global();
header!(
"cargo-mini",
env!("CARGO_PKG_VERSION"),
"The Rust package manager (emulator)",
"no real side effects — safe to tab-complete",
badge = cli_ui::styles::theme_styles("yellow").0
);
if global.verbose {
phase!("verbose", "verbose output enabled");
}
match cmd {
Cmd::Build(opt) => cmd_build(&opt, global),
Cmd::Run(opt) => cmd_run(&opt, global),
Cmd::Add(opt) => cmd_add(&opt, global),
Cmd::Remove(opt) => cmd_remove(&opt, global),
Cmd::New(opt) => cmd_new(&opt, global),
Cmd::Init(opt) => cmd_init(&opt, global),
Cmd::Test(opt) => cmd_test(&opt, global),
Cmd::Check(opt) => cmd_check(&opt, global),
Cmd::Clean(opt) => cmd_clean(&opt, global),
Cmd::Metadata(opt) => cmd_metadata(&opt, global),
Cmd::Update(opt) => cmd_update(&opt, global),
Cmd::Info(opt) => cmd_info(&opt, global),
Cmd::Import(opt) => cmd_import(&opt, global),
}
}
fn cmd_build(opt: &BuildOpt, global: &Global) {
let mode = if opt.release { "release" } else { "dev" };
phase!("compile", "Compiling packages in {} mode", mode);
if let Some(ref e) = opt.example {
phase!("target", "example = {e}");
}
if let Some(ref b) = opt.bin {
phase!("target", "bin = {b}");
}
if let Some(ref t) = opt.target {
phase!("target", "target = {t}");
}
if let Some(ref f) = opt.features {
phase!("features", "{f}");
}
fake_compile(opt.jobs);
ok!("target/debug/mini_cargo");
summary! {
done: "Build finished",
"mode" => paint(OK, mode),
"jobs" => paint(DIM, &opt.jobs.to_string()),
"verbose" => paint(DIM, &global.verbose.to_string()),
}
}
fn cmd_run(opt: &RunOpt, global: &Global) {
let mode = if opt.release { "release" } else { "dev" };
phase!("compile", "Compiling in {} mode", mode);
if let Some(ref e) = opt.example {
phase!("run", "example = {e}");
}
if let Some(ref b) = opt.bin {
phase!("run", "bin = {b}");
}
if let Some(ref f) = opt.features {
phase!("features", "{f}");
}
fake_compile(4);
phase!("running", "Running `target/{}/...`", mode);
summary! {
done: "Run complete",
"mode" => paint(OK, mode),
"quiet" => paint(DIM, &global.quiet.to_string()),
}
}
fn cmd_add(opt: &AddOpt, _global: &Global) {
let kind = if opt.dev {
"dev-dependency"
} else if opt.build {
"build-dependency"
} else {
"dependency"
};
phase!("resolve", "Resolving {} `{}`", kind, opt.crate_name);
if let Some(ref f) = opt.features {
phase!("features", "enabling: {f}");
}
if opt.dry_run {
phase!("dry-run", "would write to Cargo.toml (dry run)");
} else {
phase!("update", "Updating Cargo.toml");
phase!("lock", "Updating Cargo.lock");
}
summary! {
done: "Dependency added",
"crate" => paint(CYAN, &opt.crate_name),
"kind" => paint(DIM, kind),
}
}
fn cmd_remove(opt: &RemoveOpt, _global: &Global) {
phase!("remove", "Removing `{}` from Cargo.toml", opt.crate_name);
summary! {
done: "Dependency removed",
"crate" => paint(CYAN, &opt.crate_name),
}
}
fn cmd_new(opt: &NewOpt, _global: &Global) {
let kind = if opt.lib { "library" } else { "binary" };
let name = opt
.name
.as_deref()
.or_else(|| opt.path.file_name()?.to_str())
.unwrap_or("my-package");
phase!("create", "Creating {} package `{}`", kind, name);
phase!("edition", "edition = {}", opt.edition);
if let Some(ref v) = opt.vcs {
phase!("vcs", "initializing {} repo", v);
}
ok!(opt.path.display());
summary! {
done: "Package created",
"name" => paint(CYAN, name),
"kind" => paint(DIM, kind),
"edition" => paint(DIM, &opt.edition),
}
}
fn cmd_init(opt: &InitOpt, _global: &Global) {
let dir = opt
.path
.as_deref()
.unwrap_or_else(|| std::path::Path::new("."));
let kind = if opt.lib { "library" } else { "binary" };
phase!(
"init",
"Initializing {} package in `{}`",
kind,
dir.display()
);
if let Some(ref v) = opt.vcs {
phase!("vcs", "initializing {} repo", v);
}
summary! {
done: "Package initialized",
"dir" => paint(CYAN, &dir.display().to_string()),
"edition" => paint(DIM, &opt.edition),
}
}
fn cmd_test(opt: &TestOpt, _global: &Global) {
let mode = if opt.release { "release" } else { "dev" };
phase!("compile", "Compiling test targets in {} mode", mode);
if let Some(ref f) = opt.filter {
phase!("filter", "running tests matching `{f}`");
}
if let Some(ref f) = opt.features {
phase!("features", "{f}");
}
fake_compile(4);
step!("running {} test thread(s)", opt.test_threads);
eprintln!();
eprintln!(
" {} test result: {}. {} passed; 0 failed",
paint(DIM, "│"),
paint(OK, "ok"),
paint(OK, "42"),
);
eprintln!();
summary! {
done: "Tests passed",
"mode" => paint(OK, mode),
"threads" => paint(DIM, &opt.test_threads.to_string()),
"passed" => paint(OK, "42"),
"failed" => paint(DIM, "0"),
}
}
fn cmd_check(opt: &CheckOpt, _global: &Global) {
phase!("check", "Checking package");
if opt.all_targets {
phase!("targets", "checking all targets");
}
if let Some(ref f) = opt.features {
phase!("features", "{f}");
}
if let Some(ref t) = opt.target {
phase!("target", "{t}");
}
fake_compile(4);
summary! { done: "Check passed" }
}
fn cmd_clean(opt: &CleanOpt, _global: &Global) {
let mode = if opt.release { "release" } else { "debug" };
phase!("clean", "Removing target/{}", mode);
if let Some(ref p) = opt.package {
phase!("package", "{p}");
}
summary! {
done: "Clean complete",
"mode" => paint(DIM, mode),
}
}
fn cmd_metadata(opt: &MetadataOpt, _global: &Global) {
phase!("metadata", "Resolving dependency graph");
if opt.no_deps {
phase!("filter", "excluding transitive dependencies");
}
eprintln!();
eprintln!(
" {} {{\"packages\":[...],\"workspace_root\":\"{}\"}}",
paint(DIM, "│"),
paint(DIM, "."),
);
eprintln!();
summary! {
done: "Metadata output",
"version" => paint(DIM, &opt.format_version.to_string()),
}
}
fn cmd_update(opt: &UpdateOpt, _global: &Global) {
if let Some(ref c) = opt.crate_name {
phase!("update", "Updating `{}`", c);
} else {
phase!("update", "Updating all dependencies");
}
if opt.dry_run {
phase!("dry-run", "would update Cargo.lock (dry run)");
}
summary! { done: "Lock file updated" }
}
fn cmd_info(opt: &InfoOpt, _global: &Global) {
phase!("fetch", "Fetching info for `{}`", opt.crate_name);
if let Some(ref v) = opt.version {
phase!("version", "showing v{v}");
}
eprintln!();
eprintln!(
" {} {} v1.0.0",
paint(DIM, "│"),
paint(CYAN, &opt.crate_name)
);
eprintln!(
" {} {}",
paint(DIM, "│"),
paint(DIM, "A well-known Rust crate (emulated)")
);
eprintln!(
" {} https://crates.io/crates/{}",
paint(DIM, "│"),
opt.crate_name
);
eprintln!();
summary! {
done: "Info retrieved",
"crate" => paint(CYAN, &opt.crate_name),
}
}
fn cmd_import(opt: &ImportOpt, _global: &Global) {
phase!("read", "Reading CSV manifest `{}`", opt.file.display());
if opt.dry_run {
phase!("dry-run", "would update Cargo.toml (dry run)");
} else {
phase!("update", "Updating Cargo.toml");
}
summary! {
done: "Import complete",
"file" => paint(CYAN, &opt.file.display().to_string()),
}
}
fn fake_compile(jobs: usize) {
let pkgs = ["proc-macro2", "quote", "syn", "serde", "cli-ui"];
step!("compiling {} packages ({} jobs)", pkgs.len(), jobs);
eprintln!();
for pkg in &pkgs {
eprintln!(
" {} {} {}",
paint(DIM, "│"),
paint(DIM, "Compiling"),
paint(CYAN, pkg),
);
}
eprintln!();
}