1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
use crate::elements::{get_sample_content, is_valid_element_type, ELEMENT_TYPES};
use anyhow::{Context, Result};
use console::style;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select};
use std::fs;
use std::path::PathBuf;
pub fn run(command: &crate::cli::Commands) -> Result<()> {
if let crate::cli::Commands::Add {
element,
path,
namespace,
name,
force,
} = command
{
let theme = &ColorfulTheme::default();
// prompt for element_type if not provided
let element_type = if let Some(et) = element {
et.clone()
} else {
// list of available element types
let element_names: Vec<&str> = ELEMENT_TYPES.iter().map(|(name, _)| *name).collect();
let selection = Select::with_theme(theme)
.with_prompt("Select element type to add")
.items(&element_names)
.interact()?;
element_names[selection].to_string()
};
// validate element type
if !is_valid_element_type(&element_type) {
let valid_types = ELEMENT_TYPES
.iter()
.map(|(name, _)| *name)
.collect::<Vec<_>>()
.join(", ");
anyhow::bail!("Invalid element type. Supported types are: {}", valid_types);
}
// **Move flags_used computation before unwrapping `name`**
let flags_used = element.is_some() || name.is_some();
// prompt for name if not provided
let name = if let Some(n) = name {
n.clone()
} else {
Input::with_theme(theme)
.with_prompt("Enter name for the new file")
.interact_text()
.context("Failed to get file name")?
};
let root_dir = if let Some(p) = path {
PathBuf::from(p)
} else {
std::env::current_dir()?
};
// verify it's a datapack directory
if !root_dir.join("pack.mcmeta").exists() {
anyhow::bail!("Not a datapack directory (pack.mcmeta not found)");
}
// get or select namespace
let namespace = if let Some(ns) = namespace {
ns.clone()
} else {
// look for existing namespaces
let data_dir = root_dir.join("data");
let mut namespaces: Vec<String> = if data_dir.exists() {
fs::read_dir(&data_dir)?
.filter_map(|entry| {
entry.ok().and_then(|e| {
if e.file_type().ok()?.is_dir() {
Some(e.file_name().to_string_lossy().to_string())
} else {
None
}
})
})
.collect()
} else {
Vec::new()
};
// if flags are used without specifying a namespace, exclude 'minecraft' from consideration
if flags_used {
namespaces.retain(|ns| ns != "minecraft");
}
if namespaces.is_empty() {
// if no namespaces are present, prompt for namespace (including 'minecraft' if flags are used)
if flags_used {
let mut all_namespaces = Vec::new();
if data_dir.exists() {
all_namespaces = fs::read_dir(&data_dir)?
.filter_map(|entry| {
entry.ok().and_then(|e| {
if e.file_type().ok()?.is_dir() {
Some(e.file_name().to_string_lossy().to_string())
} else {
None
}
})
})
.collect();
}
// include 'minecraft' in the prompt options only if flags are used
let selection = Select::with_theme(theme)
.with_prompt("Select namespace to add the element to")
.items(&all_namespaces)
.interact()?;
all_namespaces[selection].clone()
} else {
// non-flagged command behavior
Input::with_theme(theme)
.with_prompt("Enter namespace name")
.interact_text()?
}
} else if namespaces.len() == 1 {
// only one non-minecraft namespace exists, use it
namespaces[0].clone()
} else {
if flags_used {
// multiple namespaces exist, prompt user excluding 'minecraft'
let selection = Select::with_theme(theme)
.with_prompt("Select namespace to add the element to")
.items(&namespaces)
.interact()?;
namespaces[selection].clone()
} else {
// multiple namespaces exist, include 'minecraft' in the prompt options
let mut prompt_namespaces = namespaces.clone();
prompt_namespaces.push("minecraft".to_string());
let selection = Select::with_theme(theme)
.with_prompt("Select namespace to add the element to")
.items(&prompt_namespaces)
.interact()?;
prompt_namespaces[selection].clone()
}
}
};
let (_, extension) = ELEMENT_TYPES
.iter()
.find(|(name, _)| *name == element_type)
.unwrap();
let data_type_dir = root_dir.join("data").join(&namespace).join(&element_type);
let file_path = data_type_dir.join(format!("{}{}", name, extension));
// create the parent directories if they don't exist
if let Some(parent_dir) = file_path.parent() {
fs::create_dir_all(parent_dir)?;
}
// check if the file already exists
if file_path.exists() && !*force {
let confirm = Confirm::with_theme(theme)
.with_prompt(format!(
"File '{}' already exists. Overwrite?",
file_path.display()
))
.default(false)
.interact()?;
if !confirm {
println!(
"{} Skipped creating '{}'",
style("⚠️").yellow(),
file_path.display()
);
return Ok(());
}
}
fs::write(&file_path, get_sample_content(&element_type))?;
println!(
"\n{} Created {} '{}'",
style("✓").green(),
style(&element_type).cyan(),
style(
file_path
.strip_prefix(&root_dir.join("data").join(&namespace).join(&element_type))?
.display()
)
.white()
);
}
Ok(())
}