From fae795b54cbd4fe13adf40a55117680ebcbe5cb7 Mon Sep 17 00:00:00 2001 From: Jheison Martinez Bolivar Date: Wed, 3 Jun 2026 17:19:07 -0500 Subject: [PATCH 1/3] refactor: collapse workspace back into a single-binary cadforge crate Drops the cadforge-cli / cadforge-view workspace split and ships the CLI as src/main.rs in the root cadforge package again. The in-process cadforge::viewer is used directly, so no separate viewer binary is needed for 'cadforge view'. Adds homepage, repository, and an 'exclude' for the leftover crates/ tree in package metadata. --- Cargo.toml | 10 +-- README.md | 2 +- src/main.rs | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml index dd9852f..2a9dbcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,18 @@ -[workspace] -members = ["crates/cadforge-cli", "crates/cadforge-view"] -resolver = "2" - [package] name = "cadforge" version = "0.1.0-beta.1" edition = "2021" description = "Architecture as Code — deterministic geometry engine for reproducible architectural design" license = "MIT" +homepage = "https://github.com/UniverLab/cadforge" +repository = "https://github.com/UniverLab/cadforge" publish = true +exclude = ["crates/**"] [dependencies] -dxf = "0.6.1" anyhow = "1.0" +clap = { version = "4.6", features = ["derive"] } +dxf = "0.6.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" toml = "0.8" diff --git a/README.md b/README.md index 7e722fa..1bf094b 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ cadforge is an **Architecture as Code** CLI tool and Rust library for declarativ | `cadforge watch` | Auto-rebuild on file changes | | `cadforge import ` | Import DXF into `.cf` layers + `project.toml` | | `cadforge import --layer ` | Import only one DXF layer | -| `cadforge view` | Open the dedicated `cadforge-view` viewer | +| `cadforge view` | Open the project in the configured viewer | | `cadforge view --layer ` | Open only one layer in the viewer | | `cadforge config set ` | Set global defaults (`author`, `units`) | | `cadforge config show` | Show global defaults | diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..cfd0516 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,204 @@ +use anyhow::{bail, Result}; +use cadforge::compiler::{check_project, compile_project, list_layers}; +use cadforge::config::{config_set, config_show}; +use cadforge::fmt::format_project; +use cadforge::importer::import_dxf; +use cadforge::preview::generate_preview; +use cadforge::scaffold::{create_project, init_project}; +use cadforge::viewer::view_project; +use cadforge::watch::watch_project; +use clap::{Parser, Subcommand}; +use std::path::PathBuf; + +#[derive(Parser)] +#[command( + name = "cadforge", + version, + about = "Architecture as Code — declarative geometry → DXF" +)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Create a new CADforge project + New { + /// Project name (creates a directory with this name) + name: String, + }, + /// Initialize CADforge in the current directory + Init, + /// Compile project (.cf files) → DXF output + Build { + /// Project directory (defaults to current dir) + #[arg(short, long)] + path: Option, + /// Compile only a specific layer + #[arg(short, long)] + layer: Option, + /// Output file path (defaults to output.dxf in project dir) + #[arg(short, long)] + output: Option, + /// Validate constraints and geometry without generating DXF + #[arg(long)] + check: bool, + }, + /// Validate project without generating DXF + Check { + /// Project directory (defaults to current dir) + #[arg(short, long)] + path: Option, + }, + /// List project layers with status + Layers { + /// Project directory (defaults to current dir) + #[arg(short, long)] + path: Option, + }, + /// Generate PNG preview + metadata JSON for AI agents + Preview { + /// Project directory (defaults to current dir) + #[arg(short, long)] + path: Option, + /// Image width in pixels + #[arg(short, long, default_value = "2048")] + width: u32, + /// Image height in pixels + #[arg(short, long, default_value = "1536")] + height: u32, + /// Render only a specific layer + #[arg(short, long)] + layer: Option, + }, + /// Format .cf files (sort keys, normalize whitespace) + Fmt { + /// Project directory (defaults to current dir) + #[arg(short, long)] + path: Option, + /// Check formatting without modifying files + #[arg(long)] + check: bool, + }, + /// Watch project files and auto-rebuild on changes + Watch { + /// Project directory (defaults to current dir) + #[arg(short, long)] + path: Option, + }, + /// Import a DXF file into CADforge project files + Import { + /// Input DXF file + input: PathBuf, + /// Output directory for generated project (defaults to current dir) + #[arg(short, long)] + output: Option, + /// Import only one DXF layer + #[arg(short, long)] + layer: Option, + }, + /// Open project output in external viewer + View { + /// Project directory (defaults to current dir) + #[arg(short, long)] + path: Option, + /// View only one layer + #[arg(short, long)] + layer: Option, + }, + /// Global cadforge configuration + Config { + #[command(subcommand)] + command: ConfigCommands, + }, +} + +#[derive(Subcommand)] +enum ConfigCommands { + /// Set global default value + Set { + /// Config key (author | units) + key: String, + /// Value to store + value: String, + }, + /// Show global configuration + Show, +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + + match cli.command { + Commands::New { name } => create_project(&name, &PathBuf::from(".")), + Commands::Init => init_project(&PathBuf::from(".")), + Commands::Build { + path, + layer, + output, + check, + } => { + let dir = resolve_project_dir(path)?; + if check { + check_project(&dir)?; + Ok(()) + } else { + compile_project(&dir, layer.as_deref(), output.as_deref()) + } + } + Commands::Check { path } => { + let dir = resolve_project_dir(path)?; + check_project(&dir)?; + Ok(()) + } + Commands::Layers { path } => { + let dir = resolve_project_dir(path)?; + list_layers(&dir) + } + Commands::Preview { + path, + width, + height, + layer, + } => { + let dir = resolve_project_dir(path)?; + generate_preview(&dir, width, height, layer.as_deref()) + } + Commands::Fmt { path, check } => { + let dir = resolve_project_dir(path)?; + format_project(&dir, check) + } + Commands::Watch { path } => { + let dir = resolve_project_dir(path)?; + watch_project(&dir) + } + Commands::Import { + input, + output, + layer, + } => { + let out_dir = output.unwrap_or_else(|| PathBuf::from(".")); + import_dxf(&input, &out_dir, layer.as_deref()) + } + Commands::View { path, layer } => { + let dir = resolve_project_dir(path)?; + view_project(&dir, layer.as_deref()) + } + Commands::Config { command } => match command { + ConfigCommands::Set { key, value } => config_set(&key, &value), + ConfigCommands::Show => config_show(), + }, + } +} + +fn resolve_project_dir(path: Option) -> Result { + let dir = path.unwrap_or_else(|| PathBuf::from(".")); + if !dir.join("project.toml").exists() { + bail!( + "No project.toml found in '{}'. Run `cadforge new` to create a project.", + dir.display() + ); + } + Ok(dir) +} From f04579691cc39db3bd202e8cb1777831bd438e62 Mon Sep 17 00:00:00 2001 From: Jheison Martinez Bolivar Date: Thu, 4 Jun 2026 07:55:17 -0500 Subject: [PATCH 2/3] chore(release): bump version to 0.1.0-beta.2 --- Cargo.toml | 2 +- crates/cadforge-cli/Cargo.toml | 2 +- crates/cadforge-view/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2a9dbcf..3a5e78a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cadforge" -version = "0.1.0-beta.1" +version = "0.1.0-beta.2" edition = "2021" description = "Architecture as Code — deterministic geometry engine for reproducible architectural design" license = "MIT" diff --git a/crates/cadforge-cli/Cargo.toml b/crates/cadforge-cli/Cargo.toml index 4419455..810f0f1 100644 --- a/crates/cadforge-cli/Cargo.toml +++ b/crates/cadforge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cadforge-cli" -version = "0.1.0-beta.1" +version = "0.1.0-beta.2" edition = "2021" description = "CLI binary for cadforge" license = "MIT" diff --git a/crates/cadforge-view/Cargo.toml b/crates/cadforge-view/Cargo.toml index 8ccbee4..8b51948 100644 --- a/crates/cadforge-view/Cargo.toml +++ b/crates/cadforge-view/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cadforge-view" -version = "0.1.0-beta.1" +version = "0.1.0-beta.2" edition = "2021" description = "Vector viewer for cadforge projects" license = "MIT" From 31dd30e886408542ba9d4fb40d65bd26c69df196 Mon Sep 17 00:00:00 2001 From: Jheison Martinez Bolivar Date: Thu, 4 Jun 2026 07:56:44 -0500 Subject: [PATCH 3/3] chore: drop orphan cadforge-cli and cadforge-view crates Removes the crates/ tree that was left behind when the workspace was collapsed back into a single-binary cadforge crate. The two sub-crates are no longer referenced by any [workspace] members or path deps. --- crates/cadforge-cli/Cargo.toml | 17 --- crates/cadforge-cli/src/main.rs | 204 ------------------------------- crates/cadforge-view/Cargo.toml | 20 --- crates/cadforge-view/src/lib.rs | 11 -- crates/cadforge-view/src/main.rs | 28 ----- 5 files changed, 280 deletions(-) delete mode 100644 crates/cadforge-cli/Cargo.toml delete mode 100644 crates/cadforge-cli/src/main.rs delete mode 100644 crates/cadforge-view/Cargo.toml delete mode 100644 crates/cadforge-view/src/lib.rs delete mode 100644 crates/cadforge-view/src/main.rs diff --git a/crates/cadforge-cli/Cargo.toml b/crates/cadforge-cli/Cargo.toml deleted file mode 100644 index 810f0f1..0000000 --- a/crates/cadforge-cli/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "cadforge-cli" -version = "0.1.0-beta.2" -edition = "2021" -description = "CLI binary for cadforge" -license = "MIT" -publish = true - -[[bin]] -name = "cadforge" -path = "src/main.rs" - -[dependencies] -anyhow = "1.0" -clap = { version = "4.6", features = ["derive"] } -cadforge = { path = "../.." } -cadforge-view = { path = "../cadforge-view" } diff --git a/crates/cadforge-cli/src/main.rs b/crates/cadforge-cli/src/main.rs deleted file mode 100644 index f21420a..0000000 --- a/crates/cadforge-cli/src/main.rs +++ /dev/null @@ -1,204 +0,0 @@ -use anyhow::{bail, Result}; -use cadforge::compiler::{check_project, compile_project, list_layers}; -use cadforge::config::{config_set, config_show}; -use cadforge::fmt::format_project; -use cadforge::importer::import_dxf; -use cadforge::preview::generate_preview; -use cadforge::scaffold::{create_project, init_project}; -use cadforge::watch::watch_project; -use cadforge_view::run_viewer; -use clap::{Parser, Subcommand}; -use std::path::PathBuf; - -#[derive(Parser)] -#[command( - name = "cadforge", - version, - about = "Architecture as Code — declarative geometry → DXF" -)] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand)] -enum Commands { - /// Create a new CADforge project - New { - /// Project name (creates a directory with this name) - name: String, - }, - /// Initialize CADforge in the current directory - Init, - /// Compile project (.cf files) → DXF output - Build { - /// Project directory (defaults to current dir) - #[arg(short, long)] - path: Option, - /// Compile only a specific layer - #[arg(short, long)] - layer: Option, - /// Output file path (defaults to output.dxf in project dir) - #[arg(short, long)] - output: Option, - /// Validate constraints and geometry without generating DXF - #[arg(long)] - check: bool, - }, - /// Validate project without generating DXF - Check { - /// Project directory (defaults to current dir) - #[arg(short, long)] - path: Option, - }, - /// List project layers with status - Layers { - /// Project directory (defaults to current dir) - #[arg(short, long)] - path: Option, - }, - /// Generate PNG preview + metadata JSON for AI agents - Preview { - /// Project directory (defaults to current dir) - #[arg(short, long)] - path: Option, - /// Image width in pixels - #[arg(short, long, default_value = "2048")] - width: u32, - /// Image height in pixels - #[arg(short, long, default_value = "1536")] - height: u32, - /// Render only a specific layer - #[arg(short, long)] - layer: Option, - }, - /// Format .cf files (sort keys, normalize whitespace) - Fmt { - /// Project directory (defaults to current dir) - #[arg(short, long)] - path: Option, - /// Check formatting without modifying files - #[arg(long)] - check: bool, - }, - /// Watch project files and auto-rebuild on changes - Watch { - /// Project directory (defaults to current dir) - #[arg(short, long)] - path: Option, - }, - /// Import a DXF file into CADforge project files - Import { - /// Input DXF file - input: PathBuf, - /// Output directory for generated project (defaults to current dir) - #[arg(short, long)] - output: Option, - /// Import only one DXF layer - #[arg(short, long)] - layer: Option, - }, - /// Open project output in external viewer - View { - /// Project directory (defaults to current dir) - #[arg(short, long)] - path: Option, - /// View only one layer - #[arg(short, long)] - layer: Option, - }, - /// Global cadforge configuration - Config { - #[command(subcommand)] - command: ConfigCommands, - }, -} - -#[derive(Subcommand)] -enum ConfigCommands { - /// Set global default value - Set { - /// Config key (author | units) - key: String, - /// Value to store - value: String, - }, - /// Show global configuration - Show, -} - -fn main() -> Result<()> { - let cli = Cli::parse(); - - match cli.command { - Commands::New { name } => create_project(&name, &PathBuf::from(".")), - Commands::Init => init_project(&PathBuf::from(".")), - Commands::Build { - path, - layer, - output, - check, - } => { - let dir = resolve_project_dir(path)?; - if check { - check_project(&dir)?; - Ok(()) - } else { - compile_project(&dir, layer.as_deref(), output.as_deref()) - } - } - Commands::Check { path } => { - let dir = resolve_project_dir(path)?; - check_project(&dir)?; - Ok(()) - } - Commands::Layers { path } => { - let dir = resolve_project_dir(path)?; - list_layers(&dir) - } - Commands::Preview { - path, - width, - height, - layer, - } => { - let dir = resolve_project_dir(path)?; - generate_preview(&dir, width, height, layer.as_deref()) - } - Commands::Fmt { path, check } => { - let dir = resolve_project_dir(path)?; - format_project(&dir, check) - } - Commands::Watch { path } => { - let dir = resolve_project_dir(path)?; - watch_project(&dir) - } - Commands::Import { - input, - output, - layer, - } => { - let out_dir = output.unwrap_or_else(|| PathBuf::from(".")); - import_dxf(&input, &out_dir, layer.as_deref()) - } - Commands::View { path, layer } => { - let dir = resolve_project_dir(path)?; - run_viewer(&dir, layer.as_deref()) - } - Commands::Config { command } => match command { - ConfigCommands::Set { key, value } => config_set(&key, &value), - ConfigCommands::Show => config_show(), - }, - } -} - -fn resolve_project_dir(path: Option) -> Result { - let dir = path.unwrap_or_else(|| PathBuf::from(".")); - if !dir.join("project.toml").exists() { - bail!( - "No project.toml found in '{}'. Run `cadforge new` to create a project.", - dir.display() - ); - } - Ok(dir) -} diff --git a/crates/cadforge-view/Cargo.toml b/crates/cadforge-view/Cargo.toml deleted file mode 100644 index 8b51948..0000000 --- a/crates/cadforge-view/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "cadforge-view" -version = "0.1.0-beta.2" -edition = "2021" -description = "Vector viewer for cadforge projects" -license = "MIT" -publish = true - -[[bin]] -name = "cadforge-view" -path = "src/main.rs" - -[dependencies] -anyhow = "1.0" -arboard = "3.4" -bytemuck = { version = "1.16", features = ["derive"] } -cadforge = { path = "../.." } -pollster = "0.3" -wgpu = "0.20" -winit = "0.30" diff --git a/crates/cadforge-view/src/lib.rs b/crates/cadforge-view/src/lib.rs deleted file mode 100644 index 256dda6..0000000 --- a/crates/cadforge-view/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! cadforge-view — vector viewer for cadforge projects. -//! -//! Stub crate; the wgpu-backed renderer lands in a follow-up commit. - -use anyhow::Result; -use std::path::Path; - -pub fn run_viewer(project_dir: &Path, layer: Option<&str>) -> Result<()> { - let _ = (project_dir, layer); - Ok(()) -} diff --git a/crates/cadforge-view/src/main.rs b/crates/cadforge-view/src/main.rs deleted file mode 100644 index 9bd7b95..0000000 --- a/crates/cadforge-view/src/main.rs +++ /dev/null @@ -1,28 +0,0 @@ -use anyhow::Result; -use cadforge_view::run_viewer; -use std::env; -use std::path::PathBuf; - -fn main() -> Result<()> { - let args: Vec = env::args().skip(1).collect(); - let mut path: Option = None; - let mut layer: Option = None; - - let mut iter = args.into_iter(); - while let Some(arg) = iter.next() { - match arg.as_str() { - "--path" | "-p" => path = iter.next().map(PathBuf::from), - "--layer" | "-l" => layer = iter.next(), - other if other.starts_with("--path=") => { - path = Some(PathBuf::from(&other["--path=".len()..])); - } - other if other.starts_with("--layer=") => { - layer = Some(other["--layer=".len()..].to_string()); - } - _ => {} - } - } - - let dir = path.unwrap_or_else(|| PathBuf::from(".")); - run_viewer(&dir, layer.as_deref()) -}