crates/laymesh-cli/src/main.rs 1use laymesh_core::engine::compile_file;2use std::{env, path::Path, process};3mod export;4mod preview;5 6fn usage(status: i32) -> ! {7 let help = "用法:\n laymesh validate <file.lay>\n laymesh inspect <file.lay> --json\n laymesh render <file.lay> -o <output.svg|pdf|png|jpg|tif|webp|bmp|gif|ico|pnm|pbm|pgm|ppm|pam|tga>\n [--dpi <number>] [--quality <1–100 (JPEG) / 0–100 (WebP)>]\n [--compression <fast|default|best (PNG) / none|lzw|deflate|packbits (TIFF)>]\n [--background <#RRGGBB>] [--webp-lossless <true|false>]\n [--webp-method <0–6>] [--webp-alpha-quality <0–100>] [--webp-near-lossless <0–100>]\n laymesh lsp --stdio\n laymesh preview --stdio\n所有命令支持 --warnings show|hide(默认读取 LAYMESH_WARNINGS)\n几何默认 mm;字号与线宽默认 pt;样式表使用 .lcss";8 if status == 0 {9 println!("{help}")10 } else {11 eprintln!("{help}")12 };13 process::exit(status)14}15fn run() -> Result<(), Box<dyn std::error::Error>> {16 let args: Vec<String> = env::args().skip(1).collect();17 if args == ["--version"] || args == ["-V"] {18 println!("laymesh {}", env!("CARGO_PKG_VERSION"));19 return Ok(());20 }21 if args == ["--help"] || args == ["-h"] {22 usage(0)23 }24 if args == ["lsp", "--stdio"] {25 laymesh_language::lsp::serve()?;26 return Ok(());27 }28 if args == ["preview", "--stdio"] {29 preview::serve()?;30 return Ok(());31 }32 if args.len() < 2 || args.iter().any(|s| s == "--help" || s == "-h") {33 usage(2)34 }35 let (command, file) = (&args[0], &args[1]);36 if !["validate", "render", "inspect"].contains(&command.as_str()) || !file.ends_with(".lay") {37 usage(2)38 }39 let mut warning_mode = env::var("LAYMESH_WARNINGS").unwrap_or("show".into());40 let mut rest = Vec::new();41 let mut i = 2;42 while i < args.len() {43 if args[i] == "--warnings" {44 i += 1;45 warning_mode = args.get(i).ok_or("--warnings requires show|hide")?.clone()46 } else {47 rest.push(args[i].as_str())48 }49 i += 1;50 }51 if !["show", "hide"].contains(&warning_mode.as_str()) {52 return Err("--warnings / LAYMESH_WARNINGS 须为 show 或 hide".into());53 }54 if command == "inspect" {55 if rest != ["--json"] {56 usage(2)57 };58 println!(59 "{}",60 serde_json::to_string_pretty(&laymesh_language::inspect::inspect_scene(61 &compile_file(file)?62 ))?63 );64 return Ok(());65 }66 if command == "validate" {67 if !rest.is_empty() {68 usage(2)69 };70 let scene = compile_file(file)?;71 if warning_mode == "show" {72 for w in &scene.warnings {73 eprintln!("{w}")74 }75 }76 println!(77 "有效:{file}({} × {} mm,{} 个顶层实例)",78 scene.width,79 scene.height,80 scene.nodes.len()81 );82 return Ok(());83 }84 let mut output = None;85 let mut options = laymesh_render::ExportOptions::default();86 i = 0;87 while i < rest.len() {88 match rest[i] {89 "-o" | "--output" => {90 i += 1;91 output = rest.get(i).copied()92 }93 flag @ ("--dpi"94 | "--quality"95 | "--compression"96 | "--background"97 | "--webp-lossless"98 | "--webp-method"99 | "--webp-alpha-quality"100 | "--webp-near-lossless") => {101 i += 1;102 let value = rest.get(i).unwrap_or_else(|| usage(2));103 match flag {104 "--dpi" => options.dpi = Some(value.parse().unwrap_or_else(|_| usage(2))),105 "--quality" => {106 options.quality = Some(value.parse().unwrap_or_else(|_| usage(2)))107 }108 "--compression" => options.compression = Some(value.to_string()),109 "--background" => options.background = Some(value.to_string()),110 "--webp-lossless" => {111 options.webp_lossless = Some(value.parse().unwrap_or_else(|_| usage(2)))112 }113 "--webp-method" => {114 options.webp_method = Some(value.parse().unwrap_or_else(|_| usage(2)))115 }116 "--webp-alpha-quality" => {117 options.webp_alpha_quality =118 Some(value.parse().unwrap_or_else(|_| usage(2)))119 }120 "--webp-near-lossless" => {121 options.webp_near_lossless =122 Some(value.parse().unwrap_or_else(|_| usage(2)))123 }124 _ => unreachable!(),125 }126 }127 _ => usage(2),128 }129 i += 1130 }131 let Some(output) = output else { usage(2) };132 let format = Path::new(output)133 .extension()134 .and_then(|s| s.to_str())135 .unwrap_or("")136 .to_ascii_lowercase();137 if let Err(error) = options.validate(&format) {138 eprintln!("{error}");139 usage(2)140 }141 let scene = compile_file(file)?;142 if warning_mode == "show" {143 for w in &scene.warnings {144 eprintln!("{w}")145 }146 }147 let data = laymesh_render::render_export(&scene, &format, &options)?;148 export::write_atomic(Path::new(output), &data)?;149 println!("已导出:{output}");150 Ok(())151}152fn main() {153 if let Err(error) = run() {154 eprintln!("{error}");155 process::exit(1)156 }157}