feat: Implement command to create mod project from template

This commit is contained in:
Lucas Schwiderski 2023-01-07 21:53:13 +01:00
parent f61fab4257
commit 3b7abe02bf
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
5 changed files with 351 additions and 12 deletions

View file

@ -6,6 +6,7 @@
- show status after adding dictionary entries
- implement building mod bundles
- implement command to create mod project from template
== [v0.2.0] - 2022-12-28

179
Cargo.lock generated
View file

@ -29,6 +29,15 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -185,6 +194,17 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "clipboard-win"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4ab1b92798304eedc095b53942963240037c0516452cb11aeba709d420b2219"
dependencies = [
"error-code",
"str-buf",
"winapi",
]
[[package]]
name = "color-eyre"
version = "0.6.2"
@ -313,6 +333,16 @@ dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
@ -324,6 +354,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "dtmt"
version = "0.2.0"
@ -338,9 +379,11 @@ dependencies = [
"libloading",
"nanorand",
"pin-project-lite",
"promptly",
"sdk",
"serde",
"serde_sjson",
"string_template",
"tempfile",
"tokio",
"tokio-stream",
@ -350,6 +393,12 @@ dependencies = [
"zip",
]
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "errno"
version = "0.2.8"
@ -371,6 +420,16 @@ dependencies = [
"libc",
]
[[package]]
name = "error-code"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
dependencies = [
"libc",
"str-buf",
]
[[package]]
name = "eyre"
version = "0.6.8"
@ -390,6 +449,17 @@ dependencies = [
"instant",
]
[[package]]
name = "fd-lock"
version = "3.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb21c69b9fea5e15dbc1049e4b77145dd0ba1c84019c488102de0dc4ea4b0a27"
dependencies = [
"cfg-if",
"rustix",
"windows-sys",
]
[[package]]
name = "flate2"
version = "1.0.25"
@ -650,6 +720,15 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -683,6 +762,28 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
[[package]]
name = "nibble_vec"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"smallvec",
]
[[package]]
name = "nix"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "nom"
version = "7.1.2"
@ -837,6 +938,15 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "promptly"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9acbc6c5a5b029fe58342f58445acb00ccfe24624e538894bc2f04ce112980ba"
dependencies = [
"rustyline",
]
[[package]]
name = "quote"
version = "1.0.23"
@ -846,6 +956,16 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]]
name = "rand_core"
version = "0.6.4"
@ -878,6 +998,8 @@ version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
@ -925,12 +1047,42 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "rustyline"
version = "9.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db7826789c0e25614b03e5a54a0717a86f9ff6e6e5247f92b369472869320039"
dependencies = [
"bitflags",
"cfg-if",
"clipboard-win",
"dirs-next",
"fd-lock",
"libc",
"log",
"memchr",
"nix",
"radix_trie",
"scopeguard",
"smallvec",
"unicode-segmentation",
"unicode-width",
"utf8parse",
"winapi",
]
[[package]]
name = "ryu"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "sdk"
version = "0.2.0"
@ -1037,6 +1189,21 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "str-buf"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
[[package]]
name = "string_template"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f2c6b2c3fa950895c9aeb0c3cb9271d7eb580662af9af2b711b593f446320"
dependencies = [
"regex",
]
[[package]]
name = "strsim"
version = "0.10.0"
@ -1282,12 +1449,24 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-segmentation"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "utf8parse"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "valuable"
version = "0.1.0"

View file

@ -23,6 +23,8 @@ tracing-error = "0.2.0"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
confy = "0.5.1"
zip = "0.6.3"
string_template = "0.2.1"
promptly = "0.3.1"
[dev-dependencies]
tempfile = "3.3.0"

View file

@ -1,19 +1,176 @@
use clap::{Arg, ArgMatches, Command};
use color_eyre::eyre::Result;
use std::collections::HashMap;
use std::path::PathBuf;
pub(crate) fn _command_definition() -> Command {
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 tokio::fs::{self, DirBuilder};
const TEMPLATES: [(&str, &str); 6] = [
(
"dtmt.cfg",
r#"name = "{{name}}"
description = "An elaborate description of my cool game mod!"
version = "0.1.0"
packages = [
"packages/{{name}}"
]
depends = [
"dmf"
]
"#,
),
(
"{{name}}.mod",
r#"return {
run = function()
fassert(rawget(_G, "new_mod"), "`{{title}}` encountered an error loading the Darktide Mod Framework.")
new_mod("{{name}}", {
mod_script = "scripts/mods/{{name}}/{{name}}",
mod_data = "scripts/mods/{{name}}/{{name}}_data",
mod_localization = "scripts/mods/{{name}}/{{name}}_localization",
})
end,
packages = {},
}"#,
),
(
"packages/{{name}}.package",
r#"lua = [
"scripts/mods/{{name}}/*"
]
"#,
),
(
"scripts/mods/{{name}}/{{name}}.lua",
r#"local mod = get_mod("{{name}}")
-- Your mod code goes here.
-- https://vmf-docs.verminti.de
"#,
),
(
"scripts/mods/{{name}}/{{name}}_data.lua",
r#"local mod = get_mod("{{name}}")
return {
name = "{{title}}",
description = mod:localize("mod_description"),
is_togglable = true,
}"#,
),
(
"scripts/mods/{{name}}/{{name}}_localization.lua",
r#"return {
mod_description = {
en = "An elaborate description of my cool game mod!",
},
}"#,
),
];
pub(crate) fn command_definition() -> Command {
Command::new("new")
.about("Create a new project")
.arg(Arg::new("name").help(
"The name of the new project. Will default to the name of the project's directory",
))
.arg(Arg::new("directory").required(true).help(
"The directory where to initialize the new project. This directory must be empty\
or must not exist. If `.` is given, the current directory will be used.",
.arg(
Arg::new("title")
.long("title")
.help("The display name of the new mod."),
)
.arg(Arg::new("root").help(
"The directory where to initialize the new project. This directory must be empty \
or must not exist. If omitted or `.` is given, the current directory \
will be used.",
))
}
#[tracing::instrument(skip_all)]
pub(crate) async fn run(_ctx: sdk::Context, _matches: &ArgMatches) -> Result<()> {
unimplemented!()
pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
let root = if let Some(dir) = matches.get_one::<String>("root") {
if dir == "." {
std::env::current_dir()
.wrap_err("the current working dir is invalid")
.with_suggestion(|| "Change to a different directory.")?
} else {
PathBuf::from(dir)
}
} else {
let prompt = "The mod directory";
match std::env::current_dir() {
Ok(default) => promptly::prompt_default(prompt, default)?,
Err(_) => promptly::prompt(prompt)?,
}
};
let title = if let Some(title) = matches.get_one::<String>("title") {
title.clone()
} else {
promptly::prompt("The mod display name")?
};
let name = {
let default = title
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() {
c.to_ascii_lowercase()
} else {
'_'
}
})
.collect::<String>();
promptly::prompt_default("The mod identifier name", default)?
};
tracing::debug!(root = %root.display());
tracing::debug!(title, name);
let mut data = HashMap::new();
data.insert("name", name.as_str());
data.insert("title", title.as_str());
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)
})
.map(|(path, content)| async move {
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()))?;
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()))
});
futures::stream::iter(templates)
.buffer_unordered(10)
.try_fold((), |_, _| async { Ok(()) })
.await?;
tracing::info!(
"Created {} files for mod '{}' in '{}'.",
TEMPLATES.len(),
title,
root.display()
);
Ok(())
}

View file

@ -64,7 +64,7 @@ async fn main() -> Result<()> {
.subcommand(cmd::bundle::command_definition())
.subcommand(cmd::dictionary::command_definition())
.subcommand(cmd::murmur::command_definition())
// .subcommand(cmd::new::command_definition())
.subcommand(cmd::new::command_definition())
// .subcommand(cmd::watch::command_definition())
.get_matches();