diff --git a/Cargo.lock b/Cargo.lock index ea3b7ad8a..a37c10e2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,6 +346,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + [[package]] name = "bumpalo" version = "3.11.1" @@ -1145,7 +1154,9 @@ dependencies = [ "fj-operations", "fj-viewer", "fj-window", + "ignore", "serde", + "tar", "tracing-subscriber", ] @@ -1498,6 +1509,19 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +[[package]] +name = "globset" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + [[package]] name = "glow" version = "0.11.2" @@ -1777,6 +1801,24 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -3514,6 +3556,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", +] + [[package]] name = "tempfile" version = "3.3.0" diff --git a/crates/fj-app/Cargo.toml b/crates/fj-app/Cargo.toml index c008b8fcf..b3ca70905 100644 --- a/crates/fj-app/Cargo.toml +++ b/crates/fj-app/Cargo.toml @@ -10,6 +10,13 @@ license.workspace = true keywords.workspace = true categories.workspace = true +[build-dependencies.ignore] +version = "0.4.18" +default-features = false + +[build-dependencies.tar] +version = "0.4.35" +default-features = false [dependencies] anyhow = "1.0.66" @@ -38,3 +45,7 @@ features = ["derive"] [dependencies.tracing-subscriber] version = "0.3.16" features = ["env-filter", "fmt"] + +[dependencies.tar] +version = "0.4.35" +default-features = false diff --git a/crates/fj-app/build.rs b/crates/fj-app/build.rs new file mode 100644 index 000000000..deb34d250 --- /dev/null +++ b/crates/fj-app/build.rs @@ -0,0 +1,43 @@ +use ignore::WalkBuilder; +use std::{collections::HashSet, env, ffi::OsStr, fs::File, path::Path}; + +static NEW_MODEL_TEMPLATE: &str = "star"; +static EXTRA_IGNORED_FILES: &[&str] = &["star.png", "README.md"]; + +fn main() { + create_new_model_tar(); +} + +fn create_new_model_tar() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let file = File::create(Path::new(&out_dir).join("new_model.tar")).unwrap(); + let mut tar_builder = tar::Builder::new(file); + + let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap(); + let fornjot_root_path = Path::new(&manifest_dir) + .ancestors() + .nth(2) + .expect("Failed to get 'fornjot_root_path' path"); + + let new_model_path = + fornjot_root_path.join("models").join(NEW_MODEL_TEMPLATE); + + let extra_ignored_files = EXTRA_IGNORED_FILES + .iter() + .map(OsStr::new) + .collect::>(); + + for entry in WalkBuilder::new(&new_model_path).hidden(false).build() { + let path = entry.unwrap().into_path(); + if path.is_dir() + || extra_ignored_files.contains(&path.file_name().unwrap()) + { + continue; + } + let tar_path = path.strip_prefix(&new_model_path).unwrap(); + tar_builder + .append_file(tar_path, &mut File::open(&path).unwrap()) + .unwrap(); + } + tar_builder.finish().unwrap(); +} diff --git a/crates/fj-app/src/args.rs b/crates/fj-app/src/args.rs index 8bccf7758..ecaaeff36 100644 --- a/crates/fj-app/src/args.rs +++ b/crates/fj-app/src/args.rs @@ -12,8 +12,12 @@ pub struct Args { /// The model to open pub model: Option, + /// Create a new model with this name + #[arg(short, long, value_name = "MODEL_NAME")] + pub new: Option, + /// Export model to this path - #[arg(short, long)] + #[arg(short, long, value_name = "PATH")] pub export: Option, /// Parameters for the model, each in the form `key=value` diff --git a/crates/fj-app/src/main.rs b/crates/fj-app/src/main.rs index e8ab235a5..6f7cc4587 100644 --- a/crates/fj-app/src/main.rs +++ b/crates/fj-app/src/main.rs @@ -14,6 +14,7 @@ mod args; mod config; +mod model_crate; mod path; use anyhow::{anyhow, Context}; @@ -49,6 +50,10 @@ fn main() -> anyhow::Result<()> { tolerance: args.tolerance, }; + if let Some(model_name) = args.new { + return model_crate::create(&model_name); + } + let model = model_path.map(|m| m.load_model(parameters)).transpose()?; if let Some(export_path) = args.export { diff --git a/crates/fj-app/src/model_crate.rs b/crates/fj-app/src/model_crate.rs new file mode 100644 index 000000000..2c699eb2a --- /dev/null +++ b/crates/fj-app/src/model_crate.rs @@ -0,0 +1,48 @@ +use std::{fs, path::Path}; +use tar::Archive; + +static NEW_MODEL_TEMPLATE: &str = "star"; + +static NEW_MODEL_TAR: &[u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/new_model.tar")); + +pub fn create(model_name: &str) -> anyhow::Result<()> { + let path = Path::new(model_name); + Archive::new(NEW_MODEL_TAR).unpack(path)?; + postprocess_model_files(path, model_name)?; + println!("Model '{model_name}' created"); + Ok(()) +} + +fn postprocess_model_files( + path: &Path, + model_name: &str, +) -> anyhow::Result<()> { + replace_in_file( + &path.join("Cargo.toml"), + [ + ( + format!("name = \"{NEW_MODEL_TEMPLATE}\""), + format!("name = \"{model_name}\""), + ), + ( + r#"path = "../../crates/fj""#.to_owned(), + ["version = \"", fj::version::VERSION_PKG, "\""].concat(), + ), + ], + )?; + fs::write(path.join("README.md"), format!("# {model_name}\n"))?; + Ok(()) +} + +fn replace_in_file( + path: &Path, + replacements: impl IntoIterator, +) -> anyhow::Result<()> { + let mut content = fs::read_to_string(path)?; + for (from, to) in replacements { + content = content.replace(&from, &to); + } + fs::write(path, content)?; + Ok(()) +}