210 lines
6.2 KiB
Rust
210 lines
6.2 KiB
Rust
use color_eyre::eyre::{Report, Result};
|
|
use path_absolutize::Absolutize;
|
|
use shulkerbox::virtual_fs::{VFile, VFolder};
|
|
|
|
use crate::{
|
|
config::ProjectConfig,
|
|
error::Error,
|
|
terminal_output::{print_error, print_info, print_success, print_warning},
|
|
};
|
|
use std::{
|
|
borrow::Cow,
|
|
fs,
|
|
path::{Path, PathBuf},
|
|
};
|
|
|
|
#[derive(Debug, clap::Args, Clone)]
|
|
pub struct BuildArgs {
|
|
/// The path of the project to build.
|
|
#[clap(default_value = ".")]
|
|
pub path: PathBuf,
|
|
/// The path of the directory to place the compiled datapack.
|
|
#[clap(short, long, env = "DATAPACK_DIR")]
|
|
pub output: Option<PathBuf>,
|
|
/// The path of a folder which files and subfolders will be copied to the root of the datapack.
|
|
/// Overrides the `assets` field in the pack.toml file.
|
|
#[clap(short, long)]
|
|
pub assets: Option<PathBuf>,
|
|
/// Whether to package the project to a zip file.
|
|
#[clap(short, long)]
|
|
pub zip: bool,
|
|
}
|
|
|
|
impl Default for BuildArgs {
|
|
fn default() -> Self {
|
|
Self {
|
|
path: PathBuf::from("."),
|
|
output: None,
|
|
assets: None,
|
|
zip: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn build(_verbose: bool, args: &BuildArgs) -> Result<()> {
|
|
if args.zip && !cfg!(feature = "zip") {
|
|
print_error("The zip feature is not enabled. Please install with the `zip` feature enabled to use the `--zip` option.");
|
|
return Err(Report::from(Error::FeatureNotEnabledError(
|
|
"zip".to_string(),
|
|
)));
|
|
}
|
|
|
|
let path = args.path.as_path();
|
|
let dist_path = args
|
|
.output
|
|
.as_ref()
|
|
.map(Cow::Borrowed)
|
|
.unwrap_or_else(|| Cow::Owned(path.join("dist")));
|
|
|
|
let and_package_msg = if args.zip { " and packaging" } else { "" };
|
|
|
|
print_info(format!(
|
|
"Building{and_package_msg} project at {}",
|
|
path.absolutize()?.display()
|
|
));
|
|
|
|
let (project_config, toml_path) = get_pack_config(path)?;
|
|
|
|
let script_paths = get_script_paths(
|
|
&toml_path
|
|
.parent()
|
|
.ok_or(Error::InvalidPackPathError(path.to_path_buf()))?
|
|
.join("src"),
|
|
)?;
|
|
|
|
let mut compiled = shulkerscript::compile(&script_paths)?;
|
|
|
|
let icon_path = toml_path.parent().unwrap().join("pack.png");
|
|
|
|
if icon_path.is_file() {
|
|
if let Ok(icon_data) = fs::read(icon_path) {
|
|
compiled.add_file("pack.png", VFile::Binary(icon_data));
|
|
}
|
|
}
|
|
|
|
let assets_path = args.assets.clone().or(project_config
|
|
.compiler
|
|
.as_ref()
|
|
.and_then(|c| c.assets.as_ref().map(|p| path.join(p))));
|
|
|
|
let output = if let Some(assets_path) = assets_path {
|
|
let assets = VFolder::try_from(assets_path.as_path());
|
|
if assets.is_err() {
|
|
print_error(format!(
|
|
"The specified assets path does not exist: {}",
|
|
assets_path.display()
|
|
));
|
|
}
|
|
let mut assets = assets?;
|
|
let replaced = assets.merge(compiled);
|
|
|
|
for replaced in replaced {
|
|
print_warning(format!(
|
|
"Template file {} was replaced by a file in the compiled datapack",
|
|
replaced
|
|
));
|
|
}
|
|
|
|
assets
|
|
} else {
|
|
compiled
|
|
};
|
|
|
|
let dist_extension = if args.zip { ".zip" } else { "" };
|
|
|
|
let dist_path = dist_path.join(project_config.pack.name + dist_extension);
|
|
|
|
#[cfg(feature = "zip")]
|
|
if args.zip {
|
|
output.zip(&dist_path)?;
|
|
} else {
|
|
output.place(&dist_path)?;
|
|
}
|
|
|
|
#[cfg(not(feature = "zip"))]
|
|
output.place(&dist_path)?;
|
|
|
|
print_success(format!(
|
|
"Finished building{and_package_msg} project to {}",
|
|
dist_path.absolutize_from(path)?.display()
|
|
));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Recursively get all script paths in a directory.
|
|
pub(super) fn get_script_paths(path: &Path) -> std::io::Result<Vec<(String, PathBuf)>> {
|
|
_get_script_paths(path, "")
|
|
}
|
|
|
|
fn _get_script_paths(path: &Path, prefix: &str) -> std::io::Result<Vec<(String, PathBuf)>> {
|
|
if path.exists() && path.is_dir() {
|
|
let contents = path.read_dir()?;
|
|
|
|
let mut paths = Vec::new();
|
|
|
|
for entry in contents {
|
|
let path = entry?.path();
|
|
if path.is_dir() {
|
|
let prefix = path
|
|
.absolutize()?
|
|
.file_name()
|
|
.unwrap()
|
|
.to_str()
|
|
.expect("Invalid folder name")
|
|
.to_string()
|
|
+ "/";
|
|
paths.extend(_get_script_paths(&path, &prefix)?);
|
|
} else if path.extension().unwrap_or_default() == "shu" {
|
|
paths.push((
|
|
prefix.to_string()
|
|
+ path
|
|
.file_stem()
|
|
.expect("ShulkerScript files are not allowed to have empty names")
|
|
.to_str()
|
|
.expect("Invalid characters in filename"),
|
|
path,
|
|
));
|
|
}
|
|
}
|
|
|
|
Ok(paths)
|
|
} else {
|
|
Ok(Vec::new())
|
|
}
|
|
}
|
|
|
|
/// Get the pack config and config path from a project path.
|
|
///
|
|
/// # Errors
|
|
/// - If the specified path does not exist.
|
|
/// - If the specified directory does not contain a pack.toml file.
|
|
pub(super) fn get_pack_config(path: &Path) -> Result<(ProjectConfig, PathBuf)> {
|
|
let toml_path = if !path.exists() {
|
|
print_error("The specified path does not exist.");
|
|
return Err(Error::PathNotFoundError(path.to_path_buf()))?;
|
|
} else if path.is_dir() {
|
|
let toml_path = path.join("pack.toml");
|
|
if !toml_path.exists() {
|
|
print_error("The specified directory does not contain a pack.toml file.");
|
|
Err(Error::InvalidPackPathError(path.to_path_buf()))?;
|
|
}
|
|
toml_path
|
|
} else if path.is_file()
|
|
&& path
|
|
.file_name()
|
|
.ok_or(Error::InvalidPackPathError(path.to_path_buf()))?
|
|
== "pack.toml"
|
|
{
|
|
path.to_path_buf()
|
|
} else {
|
|
print_error("The specified path is neither a directory nor a pack.toml file.");
|
|
return Err(Error::InvalidPackPathError(path.to_path_buf()))?;
|
|
};
|
|
|
|
let toml_content = fs::read_to_string(&toml_path)?;
|
|
let project_config = toml::from_str::<ProjectConfig>(&toml_content)?;
|
|
|
|
Ok((project_config, toml_path))
|
|
}
|