implemented command
refactor command to a new crate
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,2 @@
|
|||||||
/target
|
**/target
|
||||||
.idea
|
.idea
|
||||||
|
|||||||
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -374,15 +374,24 @@ dependencies = [
|
|||||||
"clap_mangen",
|
"clap_mangen",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
"magma-command",
|
||||||
"pest",
|
"pest",
|
||||||
"pest_derive",
|
"pest_derive",
|
||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "magma-command"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"color-eyre",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.6"
|
version = "2.7.6"
|
||||||
|
|||||||
15
Cargo.toml
15
Cargo.toml
@@ -3,15 +3,24 @@ name = "magma"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [ "magma-command" ]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.54", features = ["cargo", "derive"] }
|
clap = { version = "4.5.54", features = ["cargo", "derive"] }
|
||||||
clap_mangen = "0.2.31"
|
clap_mangen = "0.2.31"
|
||||||
pest = { version = "2.8.5", features = ["pretty-print"] }
|
pest = { version = "2.8.5", features = ["pretty-print"] }
|
||||||
pest_derive = { version = "2.8.5", features = ["grammar-extras"] }
|
pest_derive = { version = "2.8.5", features = ["grammar-extras"] }
|
||||||
color-eyre = "0.6.5"
|
|
||||||
tokio = { version = "1.49.0", features = ["full"] }
|
tokio = { version = "1.49.0", features = ["full"] }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
|
||||||
toml = "0.9.11"
|
toml = "0.9.11"
|
||||||
semver = { version = "1.0.27", features = ["serde"] }
|
semver = { version = "1.0.27", features = ["serde"] }
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
serde_json = "1.0.149"
|
|
||||||
|
magma-command = { path = "./magma-command" }
|
||||||
|
|
||||||
|
color-eyre.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
color-eyre = "0.6.5"
|
||||||
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
@@ -8141,7 +8141,7 @@
|
|||||||
"type": "literal",
|
"type": "literal",
|
||||||
"name": "run",
|
"name": "run",
|
||||||
"executable": false,
|
"executable": false,
|
||||||
"redirects": [],
|
"redirects": [""],
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
7
magma-command/Cargo.lock
generated
Normal file
7
magma-command/Cargo.lock
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "magma-command"
|
||||||
|
version = "0.1.0"
|
||||||
10
magma-command/Cargo.toml
Normal file
10
magma-command/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "magma-command"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde_json = "1.0.149"
|
||||||
|
|
||||||
|
serde.workspace = true
|
||||||
|
color-eyre.workspace = true
|
||||||
48
magma-command/src/error.rs
Normal file
48
magma-command/src/error.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum CommandError {
|
||||||
|
EmptyCommand,
|
||||||
|
IncompleteCommand {
|
||||||
|
path: String,
|
||||||
|
suggestions: Vec<String>,
|
||||||
|
},
|
||||||
|
TooManyArguments {
|
||||||
|
path: String,
|
||||||
|
},
|
||||||
|
InvalidArgument {
|
||||||
|
path: String,
|
||||||
|
argument: String,
|
||||||
|
expected: Vec<String>,
|
||||||
|
},
|
||||||
|
InvalidRedirect {
|
||||||
|
from: String,
|
||||||
|
to: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for CommandError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::EmptyCommand => write!(f, "Empty command"),
|
||||||
|
Self::IncompleteCommand { path, suggestions } => {
|
||||||
|
write!(f, "Incomplete command: {}\nExpected one of: {}", path, suggestions.join(", "))
|
||||||
|
}
|
||||||
|
Self::TooManyArguments { path } => {
|
||||||
|
write!(f, "Too many arguments for command: {}", path)
|
||||||
|
}
|
||||||
|
Self::InvalidArgument { path, argument, expected } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Invalid argument '{}' for command: {}\nExpected one of: {}",
|
||||||
|
argument,
|
||||||
|
path,
|
||||||
|
expected.join(", ")
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Self::InvalidRedirect { from, to } => {
|
||||||
|
write!(f, "Invalid redirect from '{}' to '{}'", from, to.join(", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for CommandError {}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
|
pub mod types;
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
use crate::types::mc_command::{CommandNode, CommandTree, ParserType};
|
use types::{CommandNode, CommandTree, ParserType, StringKind};
|
||||||
|
use error::CommandError;
|
||||||
|
|
||||||
pub struct MinecraftCommandValidator {
|
pub struct MinecraftCommandValidator {
|
||||||
root: CommandNode,
|
root: CommandNode,
|
||||||
@@ -65,15 +68,14 @@ impl MinecraftCommandValidator {
|
|||||||
tokens: &[String],
|
tokens: &[String],
|
||||||
node: &CommandNode,
|
node: &CommandNode,
|
||||||
index: usize,
|
index: usize,
|
||||||
path: Vec<String>,
|
mut path: Vec<String>,
|
||||||
) -> Result<ValidationResult, CommandError> {
|
) -> Result<ValidationResult, CommandError> {
|
||||||
// If we've consumed all tokens
|
|
||||||
if index >= tokens.len() {
|
if index >= tokens.len() {
|
||||||
return if node.is_executable() {
|
return if node.is_executable() {
|
||||||
Ok(ValidationResult {
|
Ok(ValidationResult {
|
||||||
valid: true,
|
valid: true,
|
||||||
path,
|
path,
|
||||||
suggestions: vec![],
|
suggestions: self.get_suggestions_from_node(node),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(CommandError::IncompleteCommand {
|
Err(CommandError::IncompleteCommand {
|
||||||
@@ -83,60 +85,80 @@ impl MinecraftCommandValidator {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let token = &tokens[index];
|
let current_token = &tokens[index];
|
||||||
let children = node.children();
|
let children = node.children();
|
||||||
|
|
||||||
if children.is_empty() {
|
if children.is_empty() && node.redirects().is_empty() {
|
||||||
if node.is_executable() {
|
return if node.is_executable() {
|
||||||
return Err(CommandError::TooManyArguments {
|
Err(CommandError::TooManyArguments {
|
||||||
path: path.join(" "),
|
path: path.join(" "),
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
return Err(CommandError::IncompleteCommand {
|
Err(CommandError::IncompleteCommand {
|
||||||
path: path.join(" "),
|
path: path.join(" "),
|
||||||
suggestions: vec![],
|
suggestions: self.get_suggestions_from_node(node),
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try literal match first
|
if !node.redirects().is_empty() {
|
||||||
for child in children {
|
let nodes = self.get_redirects(&node.redirects());
|
||||||
if let CommandNode::Literal { name, .. } = child {
|
for node in nodes {
|
||||||
if name == token {
|
if let Ok(result) = self.validate_tokens(tokens, node, index, path.clone()) {
|
||||||
let mut new_path = path.clone();
|
return Ok(result);
|
||||||
new_path.push(token.clone());
|
|
||||||
return self.validate_tokens(tokens, child, index + 1, new_path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Err(CommandError::InvalidRedirect {
|
||||||
|
from: path.join(" "),
|
||||||
|
to: node.redirects().to_vec(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try argument parsers
|
|
||||||
for child in children {
|
for child in children {
|
||||||
if let CommandNode::Argument { name, parser, .. } = child {
|
if let CommandNode::Literal { name, .. } = child && name == current_token {
|
||||||
let parser_type = ParserType::from_parser_info(parser);
|
path.push(current_token.clone());
|
||||||
|
return self.validate_tokens(tokens, child, index + 1, path);
|
||||||
// Check if this is a greedy parser (includes Message, Component, and greedy strings)
|
|
||||||
if self.is_greedy_parser(&parser_type) {
|
|
||||||
// Greedy parser consumes all remaining tokens
|
|
||||||
let remaining = tokens[index..].join(" ");
|
|
||||||
if self.validate_argument(&remaining, &parser_type).is_ok() {
|
|
||||||
let mut new_path = path.clone();
|
|
||||||
new_path.push(format!("<{}>", name));
|
|
||||||
// Jump to the end since greedy consumed everything
|
|
||||||
return self.validate_tokens(tokens, child, tokens.len(), new_path);
|
|
||||||
}
|
|
||||||
} else if self.validate_argument(token, &parser_type).is_ok() {
|
|
||||||
let mut new_path = path.clone();
|
|
||||||
new_path.push(format!("<{}>", name));
|
|
||||||
return self.validate_tokens(tokens, child, index + 1, new_path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.parse_args(node, tokens, index, path)
|
||||||
// No match found
|
}
|
||||||
|
|
||||||
|
/// an empty string value will redirect to all children
|
||||||
|
fn get_redirects(&self, name: &[String]) -> Vec<&CommandNode> {
|
||||||
|
self.root.children().iter()
|
||||||
|
.filter(
|
||||||
|
|child|
|
||||||
|
name.contains(child.name()) || name.contains(&String::from(""))
|
||||||
|
)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_args(
|
||||||
|
&self,
|
||||||
|
node: &CommandNode,
|
||||||
|
tokens: &[String],
|
||||||
|
index: usize,
|
||||||
|
mut path: Vec<String>
|
||||||
|
) -> Result<ValidationResult, CommandError> {
|
||||||
|
let token = &tokens[index];
|
||||||
|
for child in node.children() {
|
||||||
|
let CommandNode::Argument { name, parser, .. } = child else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let parser_type = ParserType::from_parser_info(parser);
|
||||||
|
if self.is_greedy_parser(&parser_type)
|
||||||
|
&& self.validate_argument(&tokens[index..].join(" "), &parser_type).is_ok()
|
||||||
|
{
|
||||||
|
path.push(format!("<{}>", name));
|
||||||
|
return self.validate_tokens(tokens, child, tokens.len(), path);
|
||||||
|
} else if self.validate_argument(token, &parser_type).is_ok() {
|
||||||
|
path.push(format!("<{}>", name));
|
||||||
|
return self.validate_tokens(tokens, child, index + 1, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(CommandError::InvalidArgument {
|
Err(CommandError::InvalidArgument {
|
||||||
path: path.join(" "),
|
path: path.join(" "),
|
||||||
argument: token.clone(),
|
argument: token.to_string(),
|
||||||
expected: self.get_suggestions_from_node(node),
|
expected: self.get_suggestions_from_node(node),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -144,7 +166,7 @@ impl MinecraftCommandValidator {
|
|||||||
fn is_greedy_parser(&self, parser_type: &ParserType) -> bool {
|
fn is_greedy_parser(&self, parser_type: &ParserType) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
parser_type,
|
parser_type,
|
||||||
ParserType::String { kind: crate::types::mc_command::StringKind::GreedyPhrase }
|
ParserType::String { kind: StringKind::GreedyPhrase }
|
||||||
| ParserType::Message
|
| ParserType::Message
|
||||||
| ParserType::Component
|
| ParserType::Component
|
||||||
)
|
)
|
||||||
@@ -161,43 +183,35 @@ impl MinecraftCommandValidator {
|
|||||||
}
|
}
|
||||||
ParserType::Integer { min, max } => {
|
ParserType::Integer { min, max } => {
|
||||||
let num: i32 = value.parse().map_err(|_| "Invalid integer")?;
|
let num: i32 = value.parse().map_err(|_| "Invalid integer")?;
|
||||||
if let Some(min) = min {
|
if let Some(min) = min && (num as f64) < *min {
|
||||||
if (num as f64) < *min {
|
return Err(format!("Value must be >= {}", min));
|
||||||
return Err(format!("Value must be >= {}", min));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some(max) = max {
|
if let Some(max) = max && (num as f64) > *max {
|
||||||
if (num as f64) > *max {
|
return Err(format!("Value must be <= {}", max));
|
||||||
return Err(format!("Value must be <= {}", max));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
ParserType::Float { min, max } | ParserType::Double { min, max } => {
|
ParserType::Float { min, max } | ParserType::Double { min, max } => {
|
||||||
let num: f64 = value.parse().map_err(|_| "Invalid number")?;
|
let num: f64 = value.parse().map_err(|_| "Invalid number")?;
|
||||||
if let Some(min) = min {
|
if let Some(min) = min && num < *min {
|
||||||
if num < *min {
|
return Err(format!("Value must be >= {}", min));
|
||||||
return Err(format!("Value must be >= {}", min));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some(max) = max {
|
if let Some(max) = max && num > *max {
|
||||||
if num > *max {
|
return Err(format!("Value must be <= {}", max));
|
||||||
return Err(format!("Value must be <= {}", max));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
ParserType::String { .. } => Ok(()),
|
|
||||||
ParserType::Entity { .. } => {
|
ParserType::Entity { .. } => {
|
||||||
if value.starts_with('@') || value.starts_with('"') {
|
//TODO: have to check amount
|
||||||
|
if value.starts_with('@') {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err("Invalid entity selector".to_string())
|
Err("Invalid entity selector".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Message and Component parsers accept any text
|
ParserType::String { .. } => Ok(()),
|
||||||
ParserType::Message | ParserType::Component => Ok(()),
|
ParserType::Message | ParserType::Component => Ok(()),
|
||||||
_ => todo!(),
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,46 +239,4 @@ pub struct ValidationResult {
|
|||||||
pub valid: bool,
|
pub valid: bool,
|
||||||
pub path: Vec<String>,
|
pub path: Vec<String>,
|
||||||
pub suggestions: Vec<String>,
|
pub suggestions: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum CommandError {
|
|
||||||
EmptyCommand,
|
|
||||||
IncompleteCommand {
|
|
||||||
path: String,
|
|
||||||
suggestions: Vec<String>,
|
|
||||||
},
|
|
||||||
TooManyArguments {
|
|
||||||
path: String,
|
|
||||||
},
|
|
||||||
InvalidArgument {
|
|
||||||
path: String,
|
|
||||||
argument: String,
|
|
||||||
expected: Vec<String>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for CommandError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::EmptyCommand => write!(f, "Empty command"),
|
|
||||||
Self::IncompleteCommand { path, suggestions } => {
|
|
||||||
write!(f, "Incomplete command: {}\nExpected one of: {}", path, suggestions.join(", "))
|
|
||||||
}
|
|
||||||
Self::TooManyArguments { path } => {
|
|
||||||
write!(f, "Too many arguments for command: {}", path)
|
|
||||||
}
|
|
||||||
Self::InvalidArgument { path, argument, expected } => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Invalid argument '{}' for command: {}\nExpected one of: {}",
|
|
||||||
argument,
|
|
||||||
path,
|
|
||||||
expected.join(", ")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for CommandError {}
|
|
||||||
75
magma-command/src/types/enums.rs
Normal file
75
magma-command/src/types/enums.rs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ParserType {
|
||||||
|
Bool,
|
||||||
|
Double { min: Option<f64>, max: Option<f64> },
|
||||||
|
Float { min: Option<f64>, max: Option<f64> },
|
||||||
|
Integer { min: Option<f64>, max: Option<f64> },
|
||||||
|
Long { min: Option<f64>, max: Option<f64> },
|
||||||
|
String { kind: StringKind },
|
||||||
|
Entity { amount: EntityAmount, entity_type: EntityType },
|
||||||
|
ScoreHolder { amount: ScoreHolderAmount },
|
||||||
|
GameProfile,
|
||||||
|
BlockPos,
|
||||||
|
ColumnPos,
|
||||||
|
Vec3,
|
||||||
|
Vec2,
|
||||||
|
BlockState,
|
||||||
|
BlockPredicate,
|
||||||
|
ItemStack,
|
||||||
|
ItemPredicate,
|
||||||
|
Color,
|
||||||
|
Component,
|
||||||
|
Message,
|
||||||
|
Nbt,
|
||||||
|
NbtTag,
|
||||||
|
NbtPath,
|
||||||
|
Objective,
|
||||||
|
ObjectiveCriteria,
|
||||||
|
Operation,
|
||||||
|
Particle,
|
||||||
|
Angle,
|
||||||
|
Rotation,
|
||||||
|
ScoreboardSlot,
|
||||||
|
Swizzle,
|
||||||
|
Team,
|
||||||
|
ItemSlot,
|
||||||
|
ResourceLocation { registry: Option<String> },
|
||||||
|
Function,
|
||||||
|
EntityAnchor,
|
||||||
|
IntRange,
|
||||||
|
FloatRange,
|
||||||
|
Dimension,
|
||||||
|
Gamemode,
|
||||||
|
Time,
|
||||||
|
ResourceOrTag { registry: Option<String> },
|
||||||
|
Resource { registry: Option<String> },
|
||||||
|
TemplateMirror,
|
||||||
|
TemplateRotation,
|
||||||
|
Uuid,
|
||||||
|
Unknown(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum StringKind {
|
||||||
|
SingleWord,
|
||||||
|
QuotablePhrase,
|
||||||
|
GreedyPhrase,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum EntityAmount {
|
||||||
|
Single,
|
||||||
|
Multiple,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum EntityType {
|
||||||
|
Players,
|
||||||
|
Entities,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ScoreHolderAmount {
|
||||||
|
Single,
|
||||||
|
Multiple,
|
||||||
|
}
|
||||||
86
magma-command/src/types/mod.rs
Normal file
86
magma-command/src/types/mod.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
mod enums;
|
||||||
|
mod parser;
|
||||||
|
|
||||||
|
pub use enums::*;
|
||||||
|
pub use parser::*;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct CommandTree {
|
||||||
|
pub root: CommandNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(tag = "type", rename_all = "lowercase")]
|
||||||
|
pub enum CommandNode {
|
||||||
|
Root {
|
||||||
|
name: String,
|
||||||
|
#[serde(default)]
|
||||||
|
executable: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
redirects: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
children: Vec<CommandNode>,
|
||||||
|
},
|
||||||
|
Literal {
|
||||||
|
name: String,
|
||||||
|
#[serde(default)]
|
||||||
|
executable: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
redirects: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
children: Vec<CommandNode>,
|
||||||
|
},
|
||||||
|
Argument {
|
||||||
|
name: String,
|
||||||
|
#[serde(default)]
|
||||||
|
executable: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
redirects: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
children: Vec<CommandNode>,
|
||||||
|
parser: ParserInfo,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandNode {
|
||||||
|
pub fn name(&self) -> &String {
|
||||||
|
match self {
|
||||||
|
CommandNode::Root { name, .. } => name,
|
||||||
|
CommandNode::Literal { name, .. } => name,
|
||||||
|
CommandNode::Argument { name, .. } => name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_executable(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
CommandNode::Root { executable, .. } => *executable,
|
||||||
|
CommandNode::Literal { executable, .. } => *executable,
|
||||||
|
CommandNode::Argument { executable, .. } => *executable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn children(&self) -> &[CommandNode] {
|
||||||
|
match self {
|
||||||
|
CommandNode::Root { children, .. } => children,
|
||||||
|
CommandNode::Literal { children, .. } => children,
|
||||||
|
CommandNode::Argument { children, .. } => children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redirects(&self) -> &[String] {
|
||||||
|
match self {
|
||||||
|
CommandNode::Root { redirects, .. } => redirects,
|
||||||
|
CommandNode::Literal { redirects, .. } => redirects,
|
||||||
|
CommandNode::Argument { redirects, .. } => redirects,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parser(&self) -> Option<&ParserInfo> {
|
||||||
|
match self {
|
||||||
|
CommandNode::Argument { parser, .. } => Some(parser),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
67
magma-command/src/types/parser.rs
Normal file
67
magma-command/src/types/parser.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::types::{EntityAmount, EntityType, ParserType, ScoreHolderAmount, StringKind};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct ParserInfo {
|
||||||
|
pub parser: String,
|
||||||
|
pub modifier: Option<serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParserType {
|
||||||
|
pub fn from_parser_info(info: &ParserInfo) -> Self {
|
||||||
|
match info.parser.as_str() {
|
||||||
|
"brigadier:bool" => ParserType::Bool,
|
||||||
|
"brigadier:double" => ParserType::Double { min: None, max: None },
|
||||||
|
"brigadier:float" => ParserType::Float { min: None, max: None },
|
||||||
|
"brigadier:integer" => ParserType::Integer { min: None, max: None },
|
||||||
|
"brigadier:long" => ParserType::Long { min: None, max: None },
|
||||||
|
"brigadier:string" => ParserType::String { kind: StringKind::SingleWord },
|
||||||
|
"minecraft:entity" => ParserType::Entity {
|
||||||
|
amount: EntityAmount::Multiple,
|
||||||
|
entity_type: EntityType::Entities,
|
||||||
|
},
|
||||||
|
"minecraft:score_holder" => ParserType::ScoreHolder {
|
||||||
|
amount: ScoreHolderAmount::Multiple,
|
||||||
|
},
|
||||||
|
"minecraft:game_profile" => ParserType::GameProfile,
|
||||||
|
"minecraft:block_pos" => ParserType::BlockPos,
|
||||||
|
"minecraft:column_pos" => ParserType::ColumnPos,
|
||||||
|
"minecraft:vec3" => ParserType::Vec3,
|
||||||
|
"minecraft:vec2" => ParserType::Vec2,
|
||||||
|
"minecraft:block_state" => ParserType::BlockState,
|
||||||
|
"minecraft:block_predicate" => ParserType::BlockPredicate,
|
||||||
|
"minecraft:item_stack" => ParserType::ItemStack,
|
||||||
|
"minecraft:item_predicate" => ParserType::ItemPredicate,
|
||||||
|
"minecraft:color" => ParserType::Color,
|
||||||
|
"minecraft:component" => ParserType::Component,
|
||||||
|
"minecraft:message" => ParserType::Message,
|
||||||
|
"minecraft:nbt_compound_tag" => ParserType::Nbt,
|
||||||
|
"minecraft:nbt_tag" => ParserType::NbtTag,
|
||||||
|
"minecraft:nbt_path" => ParserType::NbtPath,
|
||||||
|
"minecraft:objective" => ParserType::Objective,
|
||||||
|
"minecraft:objective_criteria" => ParserType::ObjectiveCriteria,
|
||||||
|
"minecraft:operation" => ParserType::Operation,
|
||||||
|
"minecraft:particle" => ParserType::Particle,
|
||||||
|
"minecraft:angle" => ParserType::Angle,
|
||||||
|
"minecraft:rotation" => ParserType::Rotation,
|
||||||
|
"minecraft:scoreboard_slot" => ParserType::ScoreboardSlot,
|
||||||
|
"minecraft:swizzle" => ParserType::Swizzle,
|
||||||
|
"minecraft:team" => ParserType::Team,
|
||||||
|
"minecraft:item_slot" => ParserType::ItemSlot,
|
||||||
|
"minecraft:resource_location" => ParserType::ResourceLocation { registry: None },
|
||||||
|
"minecraft:function" => ParserType::Function,
|
||||||
|
"minecraft:entity_anchor" => ParserType::EntityAnchor,
|
||||||
|
"minecraft:int_range" => ParserType::IntRange,
|
||||||
|
"minecraft:float_range" => ParserType::FloatRange,
|
||||||
|
"minecraft:dimension" => ParserType::Dimension,
|
||||||
|
"minecraft:gamemode" => ParserType::Gamemode,
|
||||||
|
"minecraft:time" => ParserType::Time,
|
||||||
|
"minecraft:resource_or_tag" => ParserType::ResourceOrTag { registry: None },
|
||||||
|
"minecraft:resource" => ParserType::Resource { registry: None },
|
||||||
|
"minecraft:template_mirror" => ParserType::TemplateMirror,
|
||||||
|
"minecraft:template_rotation" => ParserType::TemplateRotation,
|
||||||
|
"minecraft:uuid" => ParserType::Uuid,
|
||||||
|
_ => ParserType::Unknown(info.parser.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use pest::iterators::{Pair, Pairs};
|
use pest::iterators::{Pair, Pairs};
|
||||||
use pest::Parser;
|
use pest::Parser;
|
||||||
use crate::helpers::MinecraftCommandValidator;
|
use magma_command::MinecraftCommandValidator;
|
||||||
use crate::parser::{MagmaParser, Rule};
|
use crate::parser::{MagmaParser, Rule};
|
||||||
use crate::types::{MagmaProjectConfig, McFunctionFile};
|
use crate::types::{MagmaProjectConfig, McFunctionFile};
|
||||||
|
|
||||||
@@ -36,15 +36,17 @@ impl MagmaCompiler {
|
|||||||
Rule::functionExpression => {
|
Rule::functionExpression => {
|
||||||
let mut pairs = pair.into_inner();
|
let mut pairs = pair.into_inner();
|
||||||
let identifier = pairs.next().unwrap();
|
let identifier = pairs.next().unwrap();
|
||||||
|
|
||||||
let args = if let Some(rule) = pairs.peek()
|
let args = if let Some(rule) = pairs.peek()
|
||||||
&& rule.as_rule() == Rule::functionArgs
|
&& rule.as_rule() == Rule::functionArgs
|
||||||
{
|
{ pairs.next() }
|
||||||
Some(pairs.next().unwrap())
|
else { None };
|
||||||
} else { None };
|
|
||||||
let statements = if let Some(rule) = pairs.peek()
|
let statements = if let Some(rule) = pairs.peek()
|
||||||
&& rule.as_rule() == Rule::block
|
&& rule.as_rule() == Rule::block
|
||||||
{ unbox_rule(pairs.next().unwrap()) }
|
{ expand_next_rule(pairs) }
|
||||||
else { None };
|
else { None };
|
||||||
|
|
||||||
self.parse_function(None, identifier, args, statements)?;
|
self.parse_function(None, identifier, args, statements)?;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -64,25 +66,33 @@ impl MagmaCompiler {
|
|||||||
return Ok(file);
|
return Ok(file);
|
||||||
};
|
};
|
||||||
for statement in statements {
|
for statement in statements {
|
||||||
let Some(statement) = self.parse_statement(statement)? else {
|
let statements = self.parse_statement(statement)?;
|
||||||
continue;
|
file.add_lines(statements);
|
||||||
};
|
|
||||||
file.add_line(statement);
|
|
||||||
}
|
}
|
||||||
println!("{}", file.lines().join("\n"));
|
println!("{}", file.lines().join("\n"));
|
||||||
|
println!();
|
||||||
Ok(file)
|
Ok(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_statement(&self, statement: Pair<Rule>) -> color_eyre::Result<Option<String>> {
|
fn parse_statement(&self, statement: Pair<Rule>) -> color_eyre::Result<Vec<String>> {
|
||||||
|
let statement = statement.into_inner().next().unwrap();
|
||||||
|
let mut lines = Vec::new();
|
||||||
match statement.as_rule() {
|
match statement.as_rule() {
|
||||||
Rule::commandLine => {
|
Rule::commandLine => {
|
||||||
let command = statement.into_inner().next().unwrap();
|
let command = statement.into_inner().next().unwrap();
|
||||||
Ok(Some(self.parse_command(command)?))
|
lines.push(self.parse_command(command)?);
|
||||||
|
},
|
||||||
|
Rule::commandBlock => {
|
||||||
|
let commands = statement.into_inner();
|
||||||
|
for command in commands {
|
||||||
|
lines.push(self.parse_command(command)?);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
Ok(None)
|
println!("{:?}", statement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_command(&self, command: Pair<Rule>) -> color_eyre::Result<String> {
|
fn parse_command(&self, command: Pair<Rule>) -> color_eyre::Result<String> {
|
||||||
@@ -93,7 +103,7 @@ impl MagmaCompiler {
|
|||||||
let string = unbox_string(primitive);
|
let string = unbox_string(primitive);
|
||||||
primitives.push(string);
|
primitives.push(string);
|
||||||
}
|
}
|
||||||
_ => primitives.push(primitive.as_str().to_string())
|
_ => { primitives.push(primitive.as_str().to_string())}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let unboxed_command = primitives.join(" ");
|
let unboxed_command = primitives.join(" ");
|
||||||
@@ -102,9 +112,8 @@ impl MagmaCompiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unbox_rule(rule: Pair<Rule>) -> Option<Pairs<Rule>> {
|
fn expand_next_rule(mut rule: Pairs<Rule>) -> Option<Pairs<Rule>> {
|
||||||
let mut block = rule.into_inner();
|
if let Some(inner) = rule.next() {
|
||||||
if let Some(inner) = block.next() {
|
|
||||||
Some(inner.into_inner())
|
Some(inner.into_inner())
|
||||||
} else { None }
|
} else { None }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ commandLine = { "command" ~ command }
|
|||||||
commandBlock = { "command" ~ "{" ~ command* ~ "}" }
|
commandBlock = { "command" ~ "{" ~ command* ~ "}" }
|
||||||
|
|
||||||
command = { commandName ~ mcArg+ ~ ";" }
|
command = { commandName ~ mcArg+ ~ ";" }
|
||||||
commandName = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
|
commandName = @{ ASCII_ALPHA+ }
|
||||||
mcArg = _{ nbtBlock | string | mcPrimitive }
|
mcArg = _{ nbtBlock | string | mcPrimitive }
|
||||||
nbtBlock = { "{" ~ (nbtBlock | string | !("}" | "{") ~ ANY)* ~ "}" }
|
nbtBlock = { "{" ~ (nbtBlock | string | !("}" | "{") ~ ANY)* ~ "}" }
|
||||||
mcPrimitive = @{ (!(";" | "{" | "}" | "\"" | WHITESPACE) ~ ANY)+ }
|
mcPrimitive = @{ (!(";" | "{" | "}" | "\"" | WHITESPACE) ~ ANY)+ }
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
mod command_validator;
|
|
||||||
|
|
||||||
pub use command_validator::*;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub const FILE_EXTENSION: &str = "mg";
|
pub const FILE_EXTENSION: &str = "mg";
|
||||||
|
|||||||
@@ -1,221 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct CommandTree {
|
|
||||||
pub root: CommandNode,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
#[serde(tag = "type", rename_all = "lowercase")]
|
|
||||||
pub enum CommandNode {
|
|
||||||
Root {
|
|
||||||
name: String,
|
|
||||||
#[serde(default)]
|
|
||||||
executable: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
redirects: Vec<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
children: Vec<CommandNode>,
|
|
||||||
},
|
|
||||||
Literal {
|
|
||||||
name: String,
|
|
||||||
#[serde(default)]
|
|
||||||
executable: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
redirects: Vec<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
children: Vec<CommandNode>,
|
|
||||||
},
|
|
||||||
Argument {
|
|
||||||
name: String,
|
|
||||||
#[serde(default)]
|
|
||||||
executable: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
redirects: Vec<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
children: Vec<CommandNode>,
|
|
||||||
parser: ParserInfo,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct ParserInfo {
|
|
||||||
pub parser: String,
|
|
||||||
pub modifier: Option<serde_json::Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommandNode {
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
CommandNode::Root { name, .. } => name,
|
|
||||||
CommandNode::Literal { name, .. } => name,
|
|
||||||
CommandNode::Argument { name, .. } => name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_executable(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
CommandNode::Root { executable, .. } => *executable,
|
|
||||||
CommandNode::Literal { executable, .. } => *executable,
|
|
||||||
CommandNode::Argument { executable, .. } => *executable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn children(&self) -> &[CommandNode] {
|
|
||||||
match self {
|
|
||||||
CommandNode::Root { children, .. } => children,
|
|
||||||
CommandNode::Literal { children, .. } => children,
|
|
||||||
CommandNode::Argument { children, .. } => children,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redirects(&self) -> &[String] {
|
|
||||||
match self {
|
|
||||||
CommandNode::Root { redirects, .. } => redirects,
|
|
||||||
CommandNode::Literal { redirects, .. } => redirects,
|
|
||||||
CommandNode::Argument { redirects, .. } => redirects,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parser(&self) -> Option<&ParserInfo> {
|
|
||||||
match self {
|
|
||||||
CommandNode::Argument { parser, .. } => Some(parser),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ParserType {
|
|
||||||
Bool,
|
|
||||||
Double { min: Option<f64>, max: Option<f64> },
|
|
||||||
Float { min: Option<f64>, max: Option<f64> },
|
|
||||||
Integer { min: Option<f64>, max: Option<f64> },
|
|
||||||
Long { min: Option<f64>, max: Option<f64> },
|
|
||||||
String { kind: StringKind },
|
|
||||||
Entity { amount: EntityAmount, entity_type: EntityType },
|
|
||||||
ScoreHolder { amount: ScoreHolderAmount },
|
|
||||||
GameProfile,
|
|
||||||
BlockPos,
|
|
||||||
ColumnPos,
|
|
||||||
Vec3,
|
|
||||||
Vec2,
|
|
||||||
BlockState,
|
|
||||||
BlockPredicate,
|
|
||||||
ItemStack,
|
|
||||||
ItemPredicate,
|
|
||||||
Color,
|
|
||||||
Component,
|
|
||||||
Message,
|
|
||||||
Nbt,
|
|
||||||
NbtTag,
|
|
||||||
NbtPath,
|
|
||||||
Objective,
|
|
||||||
ObjectiveCriteria,
|
|
||||||
Operation,
|
|
||||||
Particle,
|
|
||||||
Angle,
|
|
||||||
Rotation,
|
|
||||||
ScoreboardSlot,
|
|
||||||
Swizzle,
|
|
||||||
Team,
|
|
||||||
ItemSlot,
|
|
||||||
ResourceLocation { registry: Option<String> },
|
|
||||||
Function,
|
|
||||||
EntityAnchor,
|
|
||||||
IntRange,
|
|
||||||
FloatRange,
|
|
||||||
Dimension,
|
|
||||||
Gamemode,
|
|
||||||
Time,
|
|
||||||
ResourceOrTag { registry: Option<String> },
|
|
||||||
Resource { registry: Option<String> },
|
|
||||||
TemplateMirror,
|
|
||||||
TemplateRotation,
|
|
||||||
Uuid,
|
|
||||||
Unknown(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum StringKind {
|
|
||||||
SingleWord,
|
|
||||||
QuotablePhrase,
|
|
||||||
GreedyPhrase,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum EntityAmount {
|
|
||||||
Single,
|
|
||||||
Multiple,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum EntityType {
|
|
||||||
Players,
|
|
||||||
Entities,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum ScoreHolderAmount {
|
|
||||||
Single,
|
|
||||||
Multiple,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ParserType {
|
|
||||||
pub fn from_parser_info(info: &ParserInfo) -> Self {
|
|
||||||
match info.parser.as_str() {
|
|
||||||
"brigadier:bool" => ParserType::Bool,
|
|
||||||
"brigadier:double" => ParserType::Double { min: None, max: None },
|
|
||||||
"brigadier:float" => ParserType::Float { min: None, max: None },
|
|
||||||
"brigadier:integer" => ParserType::Integer { min: None, max: None },
|
|
||||||
"brigadier:long" => ParserType::Long { min: None, max: None },
|
|
||||||
"brigadier:string" => ParserType::String { kind: StringKind::SingleWord },
|
|
||||||
"minecraft:entity" => ParserType::Entity {
|
|
||||||
amount: EntityAmount::Multiple,
|
|
||||||
entity_type: EntityType::Entities,
|
|
||||||
},
|
|
||||||
"minecraft:score_holder" => ParserType::ScoreHolder {
|
|
||||||
amount: ScoreHolderAmount::Multiple,
|
|
||||||
},
|
|
||||||
"minecraft:game_profile" => ParserType::GameProfile,
|
|
||||||
"minecraft:block_pos" => ParserType::BlockPos,
|
|
||||||
"minecraft:column_pos" => ParserType::ColumnPos,
|
|
||||||
"minecraft:vec3" => ParserType::Vec3,
|
|
||||||
"minecraft:vec2" => ParserType::Vec2,
|
|
||||||
"minecraft:block_state" => ParserType::BlockState,
|
|
||||||
"minecraft:block_predicate" => ParserType::BlockPredicate,
|
|
||||||
"minecraft:item_stack" => ParserType::ItemStack,
|
|
||||||
"minecraft:item_predicate" => ParserType::ItemPredicate,
|
|
||||||
"minecraft:color" => ParserType::Color,
|
|
||||||
"minecraft:component" => ParserType::Component,
|
|
||||||
"minecraft:message" => ParserType::Message,
|
|
||||||
"minecraft:nbt_compound_tag" => ParserType::Nbt,
|
|
||||||
"minecraft:nbt_tag" => ParserType::NbtTag,
|
|
||||||
"minecraft:nbt_path" => ParserType::NbtPath,
|
|
||||||
"minecraft:objective" => ParserType::Objective,
|
|
||||||
"minecraft:objective_criteria" => ParserType::ObjectiveCriteria,
|
|
||||||
"minecraft:operation" => ParserType::Operation,
|
|
||||||
"minecraft:particle" => ParserType::Particle,
|
|
||||||
"minecraft:angle" => ParserType::Angle,
|
|
||||||
"minecraft:rotation" => ParserType::Rotation,
|
|
||||||
"minecraft:scoreboard_slot" => ParserType::ScoreboardSlot,
|
|
||||||
"minecraft:swizzle" => ParserType::Swizzle,
|
|
||||||
"minecraft:team" => ParserType::Team,
|
|
||||||
"minecraft:item_slot" => ParserType::ItemSlot,
|
|
||||||
"minecraft:resource_location" => ParserType::ResourceLocation { registry: None },
|
|
||||||
"minecraft:function" => ParserType::Function,
|
|
||||||
"minecraft:entity_anchor" => ParserType::EntityAnchor,
|
|
||||||
"minecraft:int_range" => ParserType::IntRange,
|
|
||||||
"minecraft:float_range" => ParserType::FloatRange,
|
|
||||||
"minecraft:dimension" => ParserType::Dimension,
|
|
||||||
"minecraft:gamemode" => ParserType::Gamemode,
|
|
||||||
"minecraft:time" => ParserType::Time,
|
|
||||||
"minecraft:resource_or_tag" => ParserType::ResourceOrTag { registry: None },
|
|
||||||
"minecraft:resource" => ParserType::Resource { registry: None },
|
|
||||||
"minecraft:template_mirror" => ParserType::TemplateMirror,
|
|
||||||
"minecraft:template_rotation" => ParserType::TemplateRotation,
|
|
||||||
"minecraft:uuid" => ParserType::Uuid,
|
|
||||||
_ => ParserType::Unknown(info.parser.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,10 @@ impl McFunctionFile {
|
|||||||
self.content.push(line);
|
self.content.push(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_lines(&mut self, lines: Vec<String>) {
|
||||||
|
self.content.extend(lines);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn lines(&self) -> &Vec<String> {
|
pub fn lines(&self) -> &Vec<String> {
|
||||||
&self.content
|
&self.content
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
mod magma_project_config;
|
mod magma_project_config;
|
||||||
mod mcfunction_file;
|
mod mcfunction_file;
|
||||||
pub(crate) mod mc_command;
|
|
||||||
|
|
||||||
pub use magma_project_config::*;
|
pub use magma_project_config::*;
|
||||||
pub(crate) use mcfunction_file::*;
|
pub(crate) use mcfunction_file::*;
|
||||||
Reference in New Issue
Block a user