implement custom uninstall commands

This commit is contained in:
Moritz Hölting 2025-04-07 13:17:26 +02:00
parent e9f2b9b91d
commit 7392887c12
8 changed files with 70 additions and 17 deletions

View File

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- use "return" command for conditionals instead of data storage when using supported pack format - use "return" command for conditionals instead of data storage when using supported pack format
- update latest datapack format to 61
### Removed ### Removed

View File

@ -20,12 +20,16 @@ serde = ["dep:serde"]
zip = ["dep:zip"] zip = ["dep:zip"]
[dependencies] [dependencies]
chksum-md5 = "0.0.0" chksum-md5 = "0.1.0"
getset = "0.1.2" getset = "0.1.5"
serde = { version = "1.0.197", optional = true, features = ["derive"] } serde = { version = "1.0.219", optional = true, features = ["derive"] }
serde_json = "1.0.114" serde_json = "1.0.140"
tracing = "0.1.40" tracing = "0.1.41"
zip = { version = "2.1.3", default-features = false, features = ["deflate", "time"], optional = true } zip = { version = "2.6.1", default-features = false, features = ["deflate", "time"], optional = true }
[dev-dependencies] [dev-dependencies]
tempfile = "3.13.0" tempfile = "3.19.1"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

View File

@ -0,0 +1,31 @@
use std::path::Path;
// import the prelude to get all the necessary structs
use shulkerbox::prelude::*;
fn main() {
let mut dp = Datapack::new("main", 20).with_supported_formats(16..=20);
// get the namespace "test"
let namespace = dp.namespace_mut("test");
// get the function "foo" of the namespace "test" and add some commands
let foo_function = namespace.function_mut("foo");
let ex = Execute::If(
Condition::from("entity A"),
Box::new(Execute::Run(Box::new("say A".into()))),
Some(Box::new(Execute::If(
Condition::from("entity B"),
Box::new(Execute::Run(Box::new("say B".into()))),
Some(Box::new(Execute::Run(Box::new("say C".into())))),
))),
);
foo_function.add_command(ex);
// compile the datapack
let v_folder = dp.compile(&CompileOptions::default());
// place the compiled datapack in the "./dist" folder
v_folder.place(Path::new("./dist")).unwrap();
}

View File

@ -234,7 +234,7 @@ impl Execute {
Self::If(_, next, el) => { Self::If(_, next, el) => {
pack_formats.start() >= &4 pack_formats.start() >= &4
&& next.validate(pack_formats) && next.validate(pack_formats)
&& el.as_deref().map_or(true, |el| el.validate(pack_formats)) && el.as_deref().is_none_or(|el| el.validate(pack_formats))
} }
Self::Summon(_, next) | Self::On(_, next) => { Self::Summon(_, next) | Self::On(_, next) => {
pack_formats.start() >= &12 && next.validate(pack_formats) pack_formats.start() >= &12 && next.validate(pack_formats)
@ -261,7 +261,7 @@ impl Execute {
Self::If(cond, then, el) => { Self::If(cond, then, el) => {
cond.contains_macro() cond.contains_macro()
|| then.contains_macro() || then.contains_macro()
|| el.as_deref().map_or(false, Self::contains_macro) || el.as_deref().is_some_and(Self::contains_macro)
} }
Self::Run(cmd) => cmd.contains_macro(), Self::Run(cmd) => cmd.contains_macro(),
Self::Runs(cmds) => cmds.iter().any(super::Command::contains_macro), Self::Runs(cmds) => cmds.iter().any(super::Command::contains_macro),

View File

@ -363,8 +363,8 @@ fn validate_raw_cmd(cmd: &str, pack_formats: &RangeInclusive<u8>) -> bool {
map map
}); });
cmd.split_ascii_whitespace().next().map_or(true, |cmd| { cmd.split_ascii_whitespace().next().is_none_or(|cmd| {
cmd_formats.get(cmd).map_or(true, |range| { cmd_formats.get(cmd).is_none_or(|range| {
let start_cmd = range.start(); let start_cmd = range.start();
let end_cmd = range.end(); let end_cmd = range.end();

View File

@ -27,11 +27,12 @@ pub struct Datapack {
namespaces: BTreeMap<String, Namespace>, namespaces: BTreeMap<String, Namespace>,
/// Scoreboard name -> (criteria, display name) /// Scoreboard name -> (criteria, display name)
scoreboards: BTreeMap<String, (Option<String>, Option<String>)>, scoreboards: BTreeMap<String, (Option<String>, Option<String>)>,
uninstall_commands: Vec<Command>,
custom_files: VFolder, custom_files: VFolder,
} }
impl Datapack { impl Datapack {
pub const LATEST_FORMAT: u8 = 48; pub const LATEST_FORMAT: u8 = 61;
/// Create a new Minecraft datapack. /// Create a new Minecraft datapack.
#[must_use] #[must_use]
@ -43,6 +44,7 @@ impl Datapack {
main_namespace_name: main_namespace_name.into(), main_namespace_name: main_namespace_name.into(),
namespaces: BTreeMap::new(), namespaces: BTreeMap::new(),
scoreboards: BTreeMap::new(), scoreboards: BTreeMap::new(),
uninstall_commands: Vec::new(),
custom_files: VFolder::new(), custom_files: VFolder::new(),
} }
} }
@ -70,6 +72,7 @@ impl Datapack {
/// # Errors /// # Errors
/// - If loading the directory fails /// - If loading the directory fails
#[cfg(feature = "fs_access")] #[cfg(feature = "fs_access")]
#[cfg_attr(docsrs, doc(cfg(feature = "fs_access")))]
pub fn with_template_folder<P>(self, path: P) -> std::io::Result<Self> pub fn with_template_folder<P>(self, path: P) -> std::io::Result<Self>
where where
P: AsRef<std::path::Path>, P: AsRef<std::path::Path>,
@ -135,6 +138,11 @@ impl Datapack {
&self.scoreboards &self.scoreboards
} }
/// Add commands to the uninstall function.
pub fn add_uninstall_commands(&mut self, commands: Vec<Command>) {
self.uninstall_commands.extend(commands);
}
/// Add a custom file to the datapack. /// Add a custom file to the datapack.
pub fn add_custom_file(&mut self, path: &str, file: VFile) { pub fn add_custom_file(&mut self, path: &str, file: VFile) {
self.custom_files.add_file(path, file); self.custom_files.add_file(path, file);
@ -163,7 +171,9 @@ impl Datapack {
.map(|(name, namespace)| (name.as_str(), Cow::Borrowed(namespace))) .map(|(name, namespace)| (name.as_str(), Cow::Borrowed(namespace)))
.collect::<BTreeMap<_, _>>(); .collect::<BTreeMap<_, _>>();
let mut uninstall_commands = options.uninstall_function.then(Vec::new); let mut uninstall_commands = options
.uninstall_function
.then_some(Cow::Borrowed(&self.uninstall_commands));
if !self.scoreboards.is_empty() { if !self.scoreboards.is_empty() {
let main_namespace = modified_namespaces let main_namespace = modified_namespaces
@ -185,6 +195,7 @@ impl Datapack {
if let Some(uninstall_commands) = uninstall_commands.as_mut() { if let Some(uninstall_commands) = uninstall_commands.as_mut() {
uninstall_commands uninstall_commands
.to_mut()
.push(Command::Raw(format!("scoreboard objectives remove {name}"))); .push(Command::Raw(format!("scoreboard objectives remove {name}")));
} }
} }
@ -213,7 +224,7 @@ impl Datapack {
let uninstall_function = main_namespace.to_mut().function_mut("uninstall"); let uninstall_function = main_namespace.to_mut().function_mut("uninstall");
uninstall_function uninstall_function
.get_commands_mut() .get_commands_mut()
.extend(uninstall_commands); .extend(uninstall_commands.into_owned());
} }
} }

View File

@ -10,6 +10,7 @@
)] )]
#![warn(clippy::all, clippy::pedantic, clippy::perf)] #![warn(clippy::all, clippy::pedantic, clippy::perf)]
#![allow(clippy::missing_panics_doc, clippy::missing_const_for_fn)] #![allow(clippy::missing_panics_doc, clippy::missing_const_for_fn)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod datapack; pub mod datapack;
pub mod util; pub mod util;

View File

@ -131,6 +131,7 @@ impl VFolder {
/// # Errors /// # Errors
/// - If the folder cannot be written /// - If the folder cannot be written
#[cfg(feature = "fs_access")] #[cfg(feature = "fs_access")]
#[cfg_attr(docsrs, doc(cfg(feature = "fs_access")))]
pub fn place<P>(&self, path: P) -> std::io::Result<()> pub fn place<P>(&self, path: P) -> std::io::Result<()>
where where
P: AsRef<std::path::Path>, P: AsRef<std::path::Path>,
@ -162,6 +163,7 @@ impl VFolder {
/// # Errors /// # Errors
/// - If the zip archive cannot be written /// - If the zip archive cannot be written
#[cfg(all(feature = "fs_access", feature = "zip"))] #[cfg(all(feature = "fs_access", feature = "zip"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "fs_access", feature = "zip"))))]
pub fn zip<P>(&self, path: P) -> std::io::Result<()> pub fn zip<P>(&self, path: P) -> std::io::Result<()>
where where
P: AsRef<std::path::Path>, P: AsRef<std::path::Path>,
@ -198,6 +200,7 @@ impl VFolder {
/// # Errors /// # Errors
/// - If the zip archive cannot be written /// - If the zip archive cannot be written
#[cfg(all(feature = "fs_access", feature = "zip"))] #[cfg(all(feature = "fs_access", feature = "zip"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "fs_access", feature = "zip"))))]
pub fn zip_with_comment<P, S>(&self, path: P, comment: S) -> std::io::Result<()> pub fn zip_with_comment<P, S>(&self, path: P, comment: S) -> std::io::Result<()>
where where
P: AsRef<std::path::Path>, P: AsRef<std::path::Path>,
@ -257,14 +260,14 @@ impl VFolder {
/// Recursively merge another folder into this folder. /// Recursively merge another folder into this folder.
/// Returns a list of paths that were replaced by other. /// Returns a list of paths that were replaced by other.
pub fn merge(&mut self, other: Self) -> Vec<String> { pub fn merge(&mut self, other: Self) -> Vec<String> {
self._merge(other, "") self.merge_(other, "")
} }
fn _merge(&mut self, other: Self, prefix: &str) -> Vec<String> { fn merge_(&mut self, other: Self, prefix: &str) -> Vec<String> {
let mut replaced = Vec::new(); let mut replaced = Vec::new();
for (name, folder) in other.folders { for (name, folder) in other.folders {
if let Some(existing_folder) = self.folders.get_mut(&name) { if let Some(existing_folder) = self.folders.get_mut(&name) {
let replaced_folder = existing_folder._merge(folder, &format!("{prefix}{name}/")); let replaced_folder = existing_folder.merge_(folder, &format!("{prefix}{name}/"));
replaced.extend(replaced_folder); replaced.extend(replaced_folder);
} else { } else {
self.folders.insert(name, folder); self.folders.insert(name, folder);
@ -282,6 +285,7 @@ impl VFolder {
} }
#[cfg(feature = "fs_access")] #[cfg(feature = "fs_access")]
#[cfg_attr(docsrs, doc(cfg(feature = "fs_access")))]
impl TryFrom<&std::path::Path> for VFolder { impl TryFrom<&std::path::Path> for VFolder {
type Error = std::io::Error; type Error = std::io::Error;
@ -385,6 +389,7 @@ impl From<&[u8]> for VFile {
} }
#[cfg(feature = "fs_access")] #[cfg(feature = "fs_access")]
#[cfg_attr(docsrs, doc(cfg(feature = "fs_access")))]
impl TryFrom<&std::path::Path> for VFile { impl TryFrom<&std::path::Path> for VFile {
type Error = std::io::Error; type Error = std::io::Error;