Basic implementation for datapack including functions and tags
This commit is contained in:
parent
78a3d8f520
commit
2560588a17
|
@ -5,9 +5,8 @@ edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
|
||||||
serde = ["dep:serde"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0.197", features = ["derive"], optional = true }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
zip = "0.6.6"
|
serde_json = "1.0.114"
|
||||||
|
zip = { version = "0.6.6", default-features = false, features = ["deflate", "time"] }
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
//! Represents a command that can be included in a function.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::util::compile::{CompileOptions, MutCompilerState, MutFunctionCompilerState};
|
||||||
|
|
||||||
|
/// Represents a command that can be included in a function.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum Command {
|
||||||
|
/// A command that is already formatted as a string.
|
||||||
|
Raw(String),
|
||||||
|
/// Message to be printed only in debug mode
|
||||||
|
Debug(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
/// Create a new raw command.
|
||||||
|
pub fn raw(command: &str) -> Self {
|
||||||
|
Self::Raw(command.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile the command into a string.
|
||||||
|
pub fn compile(
|
||||||
|
&self,
|
||||||
|
options: &CompileOptions,
|
||||||
|
global_state: &MutCompilerState,
|
||||||
|
function_state: &MutFunctionCompilerState,
|
||||||
|
) -> String {
|
||||||
|
let _ = options;
|
||||||
|
let _ = global_state;
|
||||||
|
let _ = function_state;
|
||||||
|
match self {
|
||||||
|
Self::Raw(command) => command.clone(),
|
||||||
|
Self::Debug(message) => compile_debug(message, options),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Command {
|
||||||
|
fn from(command: &str) -> Self {
|
||||||
|
Self::raw(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_debug(message: &str, option: &CompileOptions) -> String {
|
||||||
|
if option.debug {
|
||||||
|
format!(
|
||||||
|
r#"tellraw @a [{{"text":"[","color":"dark_blue"}},{{"text":"DEBUG","color":"dark_green","hoverEvent":{{"action":"show_text","value":[{{"text":"Debug message generated by Shulkerbox"}},{{"text":"\nSet debug message to 'false' to disable"}}]}}}},{{"text":"]","color":"dark_blue"}},{{"text":" {}","color":"black"}}]"#,
|
||||||
|
message
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
//! Function struct and implementation
|
||||||
|
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
util::compile::{CompileOptions, FunctionCompilerState, MutCompilerState},
|
||||||
|
virtual_fs::VFile,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::command::Command;
|
||||||
|
|
||||||
|
/// Function that can be called by a command
|
||||||
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
|
pub struct Function {
|
||||||
|
commands: Vec<Command>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Function {
|
||||||
|
/// Create a new function.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a command to the function.
|
||||||
|
pub fn add_command(&mut self, command: Command) {
|
||||||
|
self.commands.push(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the commands of the function.
|
||||||
|
pub fn get_commands(&self) -> &Vec<Command> {
|
||||||
|
&self.commands
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile the function into a virtual file.
|
||||||
|
pub fn compile(&self, options: &CompileOptions, state: &MutCompilerState) -> VFile {
|
||||||
|
let function_state = Mutex::new(FunctionCompilerState::default());
|
||||||
|
|
||||||
|
let content = self
|
||||||
|
.commands
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.compile(options, state, &function_state))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n");
|
||||||
|
VFile::Text(content)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,156 @@
|
||||||
|
//! Datapack module for creating and managing Minecraft datapacks.
|
||||||
|
|
||||||
|
mod command;
|
||||||
|
mod function;
|
||||||
|
mod namespace;
|
||||||
|
pub mod tag;
|
||||||
|
pub use command::Command;
|
||||||
|
pub use function::Function;
|
||||||
|
pub use namespace::Namespace;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use std::{collections::HashMap, ops::RangeInclusive, path::Path, sync::Mutex};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
util::compile::{CompileOptions, CompilerState, MutCompilerState},
|
||||||
|
virtual_fs::{VFile, VFolder},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A Minecraft datapack.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Datapack {
|
||||||
|
// TODO: Support filter and overlays
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
pack_format: u8,
|
||||||
|
supported_formats: Option<RangeInclusive<u8>>,
|
||||||
|
namespaces: HashMap<String, Namespace>,
|
||||||
|
tick: Vec<String>,
|
||||||
|
load: Vec<String>,
|
||||||
|
custom_files: VFolder,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Datapack {
|
||||||
|
/// Create a new Minecraft datapack.
|
||||||
|
pub fn new(name: &str, pack_format: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.to_string(),
|
||||||
|
description: String::from("A Minecraft datapack created with shulkerbox"),
|
||||||
|
pack_format,
|
||||||
|
supported_formats: None,
|
||||||
|
namespaces: HashMap::new(),
|
||||||
|
tick: Vec::new(),
|
||||||
|
load: Vec::new(),
|
||||||
|
custom_files: VFolder::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the description of the datapack.
|
||||||
|
pub fn with_description(self, description: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
description: description.to_string(),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the supported pack formats of the datapack.
|
||||||
|
pub fn with_supported_formats(self, supported_formats: RangeInclusive<u8>) -> Self {
|
||||||
|
Self {
|
||||||
|
supported_formats: Some(supported_formats),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the custom files of the datapack.
|
||||||
|
pub fn with_template_folder(self, path: &Path) -> std::io::Result<Self> {
|
||||||
|
let mut template = VFolder::try_from(path)?;
|
||||||
|
template.merge(self.custom_files);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
custom_files: template,
|
||||||
|
..self
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a namespace to the datapack.
|
||||||
|
pub fn add_namespace(&mut self, namespace: Namespace) {
|
||||||
|
if !namespace.get_main_function().get_commands().is_empty() {
|
||||||
|
self.add_tick(&format!("{}:main", namespace.get_name()));
|
||||||
|
}
|
||||||
|
self.namespaces
|
||||||
|
.insert(namespace.get_name().to_string(), namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a function to the tick function list.
|
||||||
|
pub fn add_tick(&mut self, function: &str) {
|
||||||
|
self.tick.push(function.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a function to the load function list.
|
||||||
|
pub fn add_load(&mut self, function: &str) {
|
||||||
|
self.load.push(function.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a custom file to the datapack.
|
||||||
|
pub fn add_custom_file(&mut self, path: &str, file: VFile) {
|
||||||
|
self.custom_files.add_file(path, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile the pack into a virtual folder.
|
||||||
|
pub fn compile(&self, options: &CompileOptions) -> VFolder {
|
||||||
|
let compiler_state = Mutex::new(CompilerState::default());
|
||||||
|
|
||||||
|
let mut root_folder = self.custom_files.clone();
|
||||||
|
let mcmeta = generate_mcmeta(self, options, &compiler_state);
|
||||||
|
root_folder.add_file("pack.mcmeta", mcmeta);
|
||||||
|
let mut data_folder = VFolder::new();
|
||||||
|
|
||||||
|
// Compile namespaces
|
||||||
|
for (name, namespace) in &self.namespaces {
|
||||||
|
let namespace_folder = namespace.compile(options, &compiler_state);
|
||||||
|
data_folder.add_existing_folder(name, namespace_folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile tick and load tag
|
||||||
|
if !self.tick.is_empty() {
|
||||||
|
let mut tick_tag = tag::Tag::new(tag::TagType::Functions, false);
|
||||||
|
for function in &self.tick {
|
||||||
|
tick_tag.add_value(tag::TagValue::Simple(function.to_owned()));
|
||||||
|
}
|
||||||
|
data_folder.add_file(
|
||||||
|
"minecraft/tags/functions/tick.json",
|
||||||
|
tick_tag.compile_no_state(options).1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !self.load.is_empty() {
|
||||||
|
let mut load_tag = tag::Tag::new(tag::TagType::Functions, false);
|
||||||
|
for function in &self.tick {
|
||||||
|
load_tag.add_value(tag::TagValue::Simple(function.to_owned()));
|
||||||
|
}
|
||||||
|
data_folder.add_file(
|
||||||
|
"minecraft/tags/functions/load.json",
|
||||||
|
load_tag.compile_no_state(options).1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
root_folder.add_existing_folder("data", data_folder);
|
||||||
|
root_folder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_mcmeta(dp: &Datapack, _options: &CompileOptions, _state: &MutCompilerState) -> VFile {
|
||||||
|
let mut content = serde_json::json!({
|
||||||
|
"pack": {
|
||||||
|
"description": dp.description,
|
||||||
|
"pack_format": dp.pack_format
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(supported_formats) = &dp.supported_formats {
|
||||||
|
content["pack"]["supported_formats"] = serde_json::json!({
|
||||||
|
"min_inclusive": *supported_formats.start(),
|
||||||
|
"max_inclusive": *supported_formats.end()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
VFile::Text(content.to_string())
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
//! Namespace of a datapack
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
util::compile::{CompileOptions, MutCompilerState},
|
||||||
|
virtual_fs::VFolder,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{function::Function, tag::Tag};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Namespace of a datapack
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Namespace {
|
||||||
|
name: String,
|
||||||
|
functions: HashMap<String, Function>,
|
||||||
|
main_function: Function,
|
||||||
|
tags: HashMap<String, Tag>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Namespace {
|
||||||
|
/// Create a new namespace.
|
||||||
|
pub fn new(name: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.to_string(),
|
||||||
|
functions: HashMap::new(),
|
||||||
|
main_function: Function::default(),
|
||||||
|
tags: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the name of the namespace.
|
||||||
|
pub fn get_name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the main function of the namespace.
|
||||||
|
pub fn get_main_function(&self) -> &Function {
|
||||||
|
&self.main_function
|
||||||
|
}
|
||||||
|
/// Get the main function of the namespace mutably.
|
||||||
|
pub fn get_main_function_mut(&mut self) -> &mut Function {
|
||||||
|
&mut self.main_function
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the functions of the namespace.
|
||||||
|
pub fn get_functions(&self) -> &HashMap<String, Function> {
|
||||||
|
&self.functions
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the tags of the namespace.
|
||||||
|
pub fn get_tags(&self) -> &HashMap<String, Tag> {
|
||||||
|
&self.tags
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a function to the namespace.
|
||||||
|
pub fn add_function(&mut self, name: &str, function: Function) {
|
||||||
|
self.functions.insert(name.to_string(), function);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a tag to the namespace.
|
||||||
|
pub fn add_tag(&mut self, name: &str, tag: Tag) {
|
||||||
|
self.tags.insert(name.to_string(), tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile the namespace into a virtual folder.
|
||||||
|
pub fn compile(&self, options: &CompileOptions, state: &MutCompilerState) -> VFolder {
|
||||||
|
let mut root_folder = VFolder::new();
|
||||||
|
|
||||||
|
// Compile functions
|
||||||
|
for (path, function) in &self.functions {
|
||||||
|
root_folder.add_file(
|
||||||
|
&format!("functions/{}.mcfunction", path),
|
||||||
|
function.compile(options, state),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !self.main_function.get_commands().is_empty() {
|
||||||
|
root_folder.add_file(
|
||||||
|
"functions/main.mcfunction",
|
||||||
|
self.main_function.compile(options, state),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile tags
|
||||||
|
for (path, tag) in &self.tags {
|
||||||
|
let (tag_type, vfile) = tag.compile(options, state);
|
||||||
|
root_folder.add_file(&format!("tags/{}/{}.json", tag_type, path), vfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
root_folder
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
//! A tag for various types.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
util::compile::{CompileOptions, MutCompilerState},
|
||||||
|
virtual_fs::VFile,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A tag for various types.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Tag {
|
||||||
|
r#type: TagType,
|
||||||
|
replace: bool,
|
||||||
|
values: Vec<TagValue>,
|
||||||
|
}
|
||||||
|
impl Tag {
|
||||||
|
/// Create a new tag.
|
||||||
|
pub fn new(r#type: TagType, replace: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
r#type,
|
||||||
|
replace,
|
||||||
|
values: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a value to the tag.
|
||||||
|
pub fn add_value(&mut self, value: TagValue) {
|
||||||
|
self.values.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile the tag into a virtual file without state
|
||||||
|
pub fn compile_no_state(&self, _options: &CompileOptions) -> (String, VFile) {
|
||||||
|
let json = serde_json::json!({
|
||||||
|
"replace": self.replace,
|
||||||
|
"values": self.values.iter().map(TagValue::compile).collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
let type_str = self.r#type.to_string();
|
||||||
|
let vfile = VFile::Text(serde_json::to_string(&json).expect("Failed to serialize tag"));
|
||||||
|
|
||||||
|
(type_str, vfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile the tag into a virtual file.
|
||||||
|
pub fn compile(&self, options: &CompileOptions, _state: &MutCompilerState) -> (String, VFile) {
|
||||||
|
self.compile_no_state(options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type of a tag.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum TagType {
|
||||||
|
/// A tag for blocks.
|
||||||
|
Blocks,
|
||||||
|
/// A tag for fluids.
|
||||||
|
Fluids,
|
||||||
|
/// A tag for items.
|
||||||
|
Items,
|
||||||
|
/// A tag for entities.
|
||||||
|
Entities,
|
||||||
|
/// A tag for game events.
|
||||||
|
GameEvents,
|
||||||
|
/// A tag for functions.
|
||||||
|
Functions,
|
||||||
|
/// A custom tag
|
||||||
|
/// `Others(<registry path>)` => `data/<namespace>/tags/<registry path>`
|
||||||
|
Others(String),
|
||||||
|
}
|
||||||
|
impl ToString for TagType {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Blocks => "blocks".to_string(),
|
||||||
|
Self::Fluids => "fluids".to_string(),
|
||||||
|
Self::Items => "items".to_string(),
|
||||||
|
Self::Entities => "entity_types".to_string(),
|
||||||
|
Self::GameEvents => "game_events".to_string(),
|
||||||
|
Self::Functions => "functions".to_string(),
|
||||||
|
Self::Others(path) => path.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The value of a tag.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum TagValue {
|
||||||
|
/// A simple value, either a resource location or an id of another tag.
|
||||||
|
Simple(String),
|
||||||
|
/// An advanced value, with an id (same as above) and whether the loading of the tag should fail when entry is not found.
|
||||||
|
Advanced {
|
||||||
|
/// The id of the tag.
|
||||||
|
id: String,
|
||||||
|
/// Whether the loading of the tag should fail when the entry is not found.
|
||||||
|
required: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
impl From<&str> for TagValue {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Self::Simple(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TagValue {
|
||||||
|
/// Compile the tag value into a JSON value.
|
||||||
|
pub fn compile(&self) -> serde_json::Value {
|
||||||
|
match self {
|
||||||
|
Self::Simple(value) => serde_json::Value::String(value.clone()),
|
||||||
|
Self::Advanced { id, required } => {
|
||||||
|
let mut map = serde_json::Map::new();
|
||||||
|
map.insert("id".to_string(), serde_json::Value::String(id.clone()));
|
||||||
|
map.insert("required".to_string(), serde_json::Value::Bool(*required));
|
||||||
|
serde_json::Value::Object(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,4 +10,6 @@
|
||||||
)]
|
)]
|
||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
|
|
||||||
|
pub mod datapack;
|
||||||
|
pub mod util;
|
||||||
pub mod virtual_fs;
|
pub mod virtual_fs;
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
//! Compile options for the compiler.
|
||||||
|
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Compile options for the compiler.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CompileOptions {
|
||||||
|
/// Whether to compile in debug mode.
|
||||||
|
pub debug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CompileOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { debug: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// State of the compiler that can change during compilation.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct CompilerState {}
|
||||||
|
/// Mutex for the compiler state.
|
||||||
|
pub type MutCompilerState = Mutex<CompilerState>;
|
||||||
|
|
||||||
|
/// State of the compiler for each function that can change during compilation.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct FunctionCompilerState {}
|
||||||
|
/// Mutex for the function compiler state.
|
||||||
|
pub type MutFunctionCompilerState = Mutex<FunctionCompilerState>;
|
|
@ -0,0 +1,3 @@
|
||||||
|
//! Utility functions for the Shulkerbox project.
|
||||||
|
|
||||||
|
pub mod compile;
|
|
@ -7,11 +7,11 @@ use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use zip::ZipWriter;
|
use zip::ZipWriter;
|
||||||
|
|
||||||
/// Folder representation in virtual file system
|
/// Folder representation in virtual file system
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct VFolder {
|
pub struct VFolder {
|
||||||
folders: HashMap<String, VFolder>,
|
folders: HashMap<String, VFolder>,
|
||||||
files: HashMap<String, VFile>,
|
files: HashMap<String, VFile>,
|
||||||
|
@ -185,6 +185,20 @@ impl VFolder {
|
||||||
|
|
||||||
files
|
files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recursively merge another folder into this folder.
|
||||||
|
pub fn merge(&mut self, other: Self) {
|
||||||
|
for (name, folder) in other.folders {
|
||||||
|
if let Some(existing_folder) = self.folders.get_mut(&name) {
|
||||||
|
existing_folder.merge(folder);
|
||||||
|
} else {
|
||||||
|
self.folders.insert(name, folder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (name, file) in other.files {
|
||||||
|
self.files.insert(name, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&Path> for VFolder {
|
impl TryFrom<&Path> for VFolder {
|
||||||
|
@ -223,8 +237,7 @@ impl TryFrom<&Path> for VFolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// File representation in virtual file system
|
/// File representation in virtual file system
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub enum VFile {
|
pub enum VFile {
|
||||||
/// Text file
|
/// Text file
|
||||||
Text(String),
|
Text(String),
|
||||||
|
|
Loading…
Reference in New Issue