Add misses init command with binary and library scaffold modes

`misses init [--lib] [dir]` scaffolds a new proc-macro crate that uses
misses as its build toolchain. Two modes are supported:

Binary mode (default):
  The .mrs source files live in src/macros/ and are compiled to
  $OUT_DIR at build time via a generated build.rs. The generated
  Rust code is never committed — users must have misses installed
  to build from source.

Library mode (--lib):
  The generated code is committed to generated/ so downstream users
  of the crate need no misses install at all. The .mrs source and
  build.rs are still present for the library author, who can
  regenerate via `cargo build --features misses-source`. The
  misses-source feature makes misses an optional build-dependency
  and gates the regeneration path in build.rs with #[cfg(feature)].

The command also teaches `main.rs` a proper subcommand dispatch so
that `misses <file.mrs>` (compile) and `misses init` are cleanly
separated.

https://claude.ai/code/session_01D2g6zGJsBeUEzxBb7AvFDt
This commit is contained in:
Claude
2026-03-08 09:30:14 +00:00
parent 0ac3a473d4
commit 3be88ec21f
2 changed files with 262 additions and 12 deletions
+40 -12
View File
@@ -1,17 +1,35 @@
mod init;
use std::path::PathBuf;
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!("Usage: misses <input.mrs> [output.rs]");
std::process::exit(1);
}
match args.get(1).map(String::as_str) {
Some("init") => {
let is_lib = args.iter().any(|a| a == "--lib");
let dir = args
.iter()
.skip(2)
.find(|a| !a.starts_with('-'))
.map(String::as_str)
.unwrap_or(".");
init::run(dir, is_lib);
}
Some(_) => compile(&args),
None => {
print_usage();
std::process::exit(1);
}
}
}
fn compile(args: &[String]) {
let input_path = PathBuf::from(&args[1]);
let src = match std::fs::read_to_string(&input_path) {
Ok(s) => s,
Err(e) => {
eprintln!("Error reading {:?}: {}", input_path, e);
eprintln!("error reading {}: {}", input_path.display(), e);
std::process::exit(1);
}
};
@@ -19,7 +37,7 @@ fn main() {
let ast = match misses::parse(&src) {
Ok(a) => a,
Err(e) => {
eprintln!("Parse error: {}", e);
eprintln!("parse error: {}", e);
std::process::exit(1);
}
};
@@ -28,14 +46,24 @@ fn main() {
if args.len() >= 3 {
let output_path = PathBuf::from(&args[2]);
match std::fs::write(&output_path, &generated) {
Ok(_) => {}
Err(e) => {
eprintln!("Error writing {:?}: {}", output_path, e);
std::process::exit(1);
}
if let Err(e) = std::fs::write(&output_path, &generated) {
eprintln!("error writing {}: {}", output_path.display(), e);
std::process::exit(1);
}
} else {
print!("{}", generated);
}
}
fn print_usage() {
eprintln!("Usage:");
eprintln!(" misses <input.mrs> [output.rs] Compile a .mrs file to Rust");
eprintln!(" misses init [--lib] [dir] Scaffold a new misses project");
eprintln!();
eprintln!("Modes for `misses init`:");
eprintln!(" (default) Binary — .mrs sources are compiled at build time via build.rs;");
eprintln!(" generated code lives in \\$OUT_DIR and is never committed.");
eprintln!(" --lib Library — generated code is committed to generated/ so");
eprintln!(" downstream users need no misses install. Rebuild with");
eprintln!(" `cargo build --features misses-source`.");
}