diff --git a/Cargo.lock b/Cargo.lock index a251de9..ce13303 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -937,6 +937,7 @@ dependencies = [ "futures-util", "glob", "luajit2-sys", + "minijinja", "nanorand", "notify", "oodle", @@ -948,7 +949,6 @@ dependencies = [ "serde", "serde_sjson", "shlex", - "string_template", "tempfile", "tokio", "tokio-stream", @@ -3307,15 +3307,6 @@ dependencies = [ "float-cmp", ] -[[package]] -name = "string_template" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6f2c6b2c3fa950895c9aeb0c3cb9271d7eb580662af9af2b711b593f446320" -dependencies = [ - "regex", -] - [[package]] name = "strip-ansi-escapes" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index f38ac22..62d1fd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ exclude = ["lib/color-eyre"] [workspace.dependencies] zip = { version = "2.1.3", default-features = false, features = ["deflate", "bzip2", "zstd", "time"] } +minijinja = { version = "2.0.1", default-features = false } [patch.crates-io] color-eyre = { path = "lib/color-eyre" } diff --git a/crates/dtmm/Cargo.toml b/crates/dtmm/Cargo.toml index f947fc6..b7bee49 100644 --- a/crates/dtmm/Cargo.toml +++ b/crates/dtmm/Cargo.toml @@ -27,7 +27,7 @@ futures = "0.3.25" interprocess = "2.1.0" lazy_static = "1.4.0" luajit2-sys = { path = "../../lib/luajit2-sys", version = "*" } -minijinja = { version = "2.0.1", default-features = false } +minijinja = { workspace = true } nexusmods = { path = "../../lib/nexusmods", version = "*" } oodle = { path = "../../lib/oodle", version = "*" } open = "5.0.1" diff --git a/crates/dtmt/Cargo.toml b/crates/dtmt/Cargo.toml index d836a50..e2e3fb9 100644 --- a/crates/dtmt/Cargo.toml +++ b/crates/dtmt/Cargo.toml @@ -20,7 +20,7 @@ promptly = "0.3.1" sdk = { path = "../../lib/sdk", version = "*" } serde_sjson = { path = "../../lib/serde_sjson", version = "*" } serde = { version = "1.0.147", features = ["derive"] } -string_template = "0.2.1" +minijinja = { workspace = true } tokio-stream = { version = "0.1.11", features = ["fs", "io-util"] } tokio = { version = "1.21.2", features = ["rt-multi-thread", "fs", "process", "macros", "tracing", "io-util", "io-std"] } tracing-error = "0.2.0" diff --git a/crates/dtmt/src/cmd/new.rs b/crates/dtmt/src/cmd/new.rs index eb6a4f9..571b0cb 100644 --- a/crates/dtmt/src/cmd/new.rs +++ b/crates/dtmt/src/cmd/new.rs @@ -1,18 +1,30 @@ -use std::collections::HashMap; use std::path::PathBuf; use clap::{Arg, ArgMatches, Command}; use color_eyre::eyre::{self, Context, Result}; use color_eyre::Help; use futures::{StreamExt, TryStreamExt}; -use string_template::Template; +use minijinja::Environment; use tokio::fs::{self, DirBuilder}; const TEMPLATES: [(&str, &str); 5] = [ ( "dtmt.cfg", - r#"id = "{{id}}" + r#"// +// This is your mod's main configuration file. It tells DTMT how to build the mod, +// and DTMM what to display to your users. +// Certain files have been pre-filled by the template, the ones commented out (`//`) +// are optional. +// +// A unique identifier (preferably lower case, alphanumeric) +id = "{{id}}" +// The display name that your users will see. +// This doesn't have to be unique, but you still want to avoid being confused with other +// mods. name = "{{name}}" +// It's good practice to increase this number whenever you publish changes. +// It's up to you if you use SemVer or something simpler like `1970-12-24`. It should sort and +// compare well, though. version = "0.1.0" // author = "" @@ -32,16 +44,25 @@ categories = [ // A list of mod IDs that this mod depends on. You can find // those IDs by downloading the mod and extracting their `dtmt.cfg`. +// To make your fellow modders' lives easier, publish your own mods' IDs +// somewhere visible, such as the Nexusmods page. depends = [ DMF ] +// The primary resources that serve as the entry point to your +// mod's code. Unless for very specific use cases, the generated +// values shouldn't be changed. resources = { init = "scripts/mods/{{id}}/init" data = "scripts/mods/{{id}}/data" localization = "scripts/mods/{{id}}/localization" } +// The list of packages, or bundles, to build. +// Each one corresponds to a package definition in the named folder. +// For mods that contain only code and/or a few small assets, a single +// package will suffice. packages = [ "packages/mods/{{id}}" ] @@ -59,7 +80,6 @@ packages = [ r#"local mod = get_mod("{{id}}") -- Your mod code goes here. --- https://vmf-docs.verminti.de "#, ), ( @@ -137,34 +157,45 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> tracing::debug!(root = %root.display(), name, id); - let mut data = HashMap::new(); - data.insert("name", name.as_str()); - data.insert("id", id.as_str()); + let render_ctx = minijinja::context!(name => name.as_str(), id => id.as_str()); + let env = Environment::new(); let templates = TEMPLATES .iter() .map(|(path_tmpl, content_tmpl)| { - let path = Template::new(path_tmpl).render(&data); - let content = Template::new(content_tmpl).render(&data); - - (root.join(path), content) + env.render_str(path_tmpl, &render_ctx) + .wrap_err_with(|| format!("Failed to render template: {}", path_tmpl)) + .and_then(|path| { + env.render_named_str(&path, content_tmpl, &render_ctx) + .wrap_err_with(|| format!("Failed to render template '{}'", &path)) + .map(|content| (root.join(path), content)) + }) }) - .map(|(path, content)| async move { - let dir = path - .parent() - .ok_or_else(|| eyre::eyre!("invalid root path"))?; + .map(|res| async move { + match res { + Ok((path, content)) => { + let dir = path + .parent() + .ok_or_else(|| eyre::eyre!("invalid root path"))?; - DirBuilder::new() - .recursive(true) - .create(&dir) - .await - .wrap_err_with(|| format!("Failed to create directory {}", dir.display()))?; + DirBuilder::new() + .recursive(true) + .create(&dir) + .await + .wrap_err_with(|| { + format!("Failed to create directory {}", dir.display()) + })?; - tracing::trace!("Writing file {}", path.display()); + tracing::trace!("Writing file {}", path.display()); - fs::write(&path, content.as_bytes()) - .await - .wrap_err_with(|| format!("Failed to write content to path {}", path.display())) + fs::write(&path, content.as_bytes()) + .await + .wrap_err_with(|| { + format!("Failed to write content to path {}", path.display()) + }) + } + Err(e) => Err(e), + } }); futures::stream::iter(templates)