diff --git a/Cargo.toml b/Cargo.toml index 3c2eee1..a017fcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,11 +29,10 @@ colored = "2.1.0" serde = { version = "1.0.197", features = ["derive"] } thiserror = "1.0.58" toml = "0.8.12" -shulkerscript = { git = "https://github.com/moritz-hoelting/shulkerscript-lang", features = ["shulkerbox"], default-features = false, rev = "dd79541ae914140df4141fe90f71e74fb961f3a3" } -shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", default-features = false, rev = "e31f9f904a5f5905e912907d988c189ffc8cf313" } +shulkerscript = { git = "https://github.com/moritz-hoelting/shulkerscript-lang.git", features = ["shulkerbox"], default-features = false, rev = "a0a27cda96e1922b019b216961c39f7ef7991d22" } +shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox.git", default-features = false, rev = "a2d20dab8ea97bbd873edafb23afaad34292457f" } git2 = { version = "0.19.0", default-features = false } path-absolutize = "3.1.1" -color-eyre = "0.6.3" dotenvy = "0.15.7" notify-debouncer-mini = { version = "0.4.1", default-features = false, optional = true } ctrlc = { version = "3.4.4", optional = true } @@ -42,3 +41,5 @@ tracing-subscriber = "0.3.18" # waiting for pull request to be merged inquire = { git = "https://github.com/moritz-hoelting/rust-inquire.git", branch = "main", package = "inquire" } camino = "1.1.7" +human-panic = "2.0.0" +anyhow = "1.0.86" diff --git a/README.md b/README.md index d755ba7..e8f2cef 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Where [PATH] is the path of the project folder to build [default: `.`] Options: - `--assets ` The path to the assets directory [default: `./assets`] - `--output ` The output directory, overrides the `DATAPACK_DIR` environment variable +- `--no-validate` Do not validate the output to be compatible with the pack format - `--zip` Package the output into a zip file Environment variables: diff --git a/src/cli.rs b/src/cli.rs index d3cffca..acc3602 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,7 @@ use crate::subcommands::{self, BuildArgs, CleanArgs, InitArgs}; +use anyhow::Result; use clap::{Parser, Subcommand, ValueEnum}; -use color_eyre::eyre::Result; use tracing::Level; use tracing_subscriber::FmtSubscriber; @@ -54,7 +54,7 @@ pub enum TracingLevel { impl Args { pub fn run(&self) -> Result<()> { if let Some(level) = self.trace { - setup_tracing(level); + setup_tracing(level)?; } self.cmd.run() @@ -89,7 +89,7 @@ impl From for Level { } } -fn setup_tracing(level: TracingLevel) { +fn setup_tracing(level: TracingLevel) -> Result<()> { // a builder for `FmtSubscriber`. let subscriber = FmtSubscriber::builder() // all spans/events with a level higher than TRACE (e.g, debug, info, warn, etc.) @@ -98,7 +98,9 @@ fn setup_tracing(level: TracingLevel) { // completes the builder. .finish(); - tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); + tracing::subscriber::set_global_default(subscriber)?; + + Ok(()) } #[cfg(test)] diff --git a/src/error.rs b/src/error.rs index 187ba07..c9a6abb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,6 +13,8 @@ pub enum Error { InvalidPackPathError(PathBuf), #[error("An error occured because the feature {0} is not enabled.")] FeatureNotEnabledError(String), + #[error("An error occured because the pack version does not support a used feature")] + IncompatiblePackVersionError, } #[allow(dead_code)] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..add9d0f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +pub mod cli; +pub mod config; +pub mod error; +pub mod subcommands; +pub mod terminal_output; +pub mod util; diff --git a/src/main.rs b/src/main.rs index 73df006..f1edc5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,14 @@ -mod cli; -mod config; -mod error; -mod subcommands; -mod terminal_output; -mod util; - use std::process::ExitCode; use clap::Parser; -use cli::Args; + +use shulkerscript_cli::{cli::Args, terminal_output::print_info}; fn main() -> ExitCode { - color_eyre::install().unwrap(); - let _ = dotenvy::dotenv(); + human_panic::setup_panic!(); + if dotenvy::dotenv().is_ok() { + print_info("Using environment variables from .env file"); + } let args = Args::parse(); diff --git a/src/subcommands/build.rs b/src/subcommands/build.rs index bdaa391..f306c39 100644 --- a/src/subcommands/build.rs +++ b/src/subcommands/build.rs @@ -1,6 +1,10 @@ -use color_eyre::eyre::{Report, Result}; +use anyhow::Result; use path_absolutize::Absolutize; -use shulkerbox::virtual_fs::{VFile, VFolder}; +use shulkerbox::{ + util::compile::CompileOptions, + virtual_fs::{VFile, VFolder}, +}; +use shulkerscript::base::FsProvider; use crate::{ config::ProjectConfig, @@ -32,25 +36,15 @@ pub struct BuildArgs { /// Package the project to a zip file. #[arg(short, long)] pub zip: bool, -} - -impl Default for BuildArgs { - fn default() -> Self { - Self { - path: PathBuf::from("."), - output: None, - assets: None, - zip: false, - } - } + /// Skip validating the project for pack format compatibility. + #[arg(long)] + pub no_validate: bool, } pub fn build(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(), - ))); + return Err(Error::FeatureNotEnabledError("zip".to_string()).into()); } let path = args.path.as_path(); @@ -76,7 +70,21 @@ pub fn build(args: &BuildArgs) -> Result<()> { .join("src"), )?; - let mut compiled = shulkerscript::compile(&script_paths)?; + let datapack = shulkerscript::transpile( + &FsProvider::default(), + project_config.pack.pack_format, + &script_paths, + )?; + + if !args.no_validate && !datapack.validate() { + print_warning(format!( + "The datapack is not compatible with the specified pack format: {}", + project_config.pack.pack_format + )); + return Err(Error::IncompatiblePackVersionError.into()); + } + + let mut compiled = datapack.compile(&CompileOptions::default()); let icon_path = toml_path.parent().unwrap().join("pack.png"); @@ -120,7 +128,13 @@ pub fn build(args: &BuildArgs) -> Result<()> { #[cfg(feature = "zip")] if args.zip { - output.zip(&dist_path)?; + output.zip_with_comment( + &dist_path, + format!( + "{} - v{}", + &project_config.pack.description, &project_config.pack.version + ), + )?; } else { output.place(&dist_path)?; } diff --git a/src/subcommands/clean.rs b/src/subcommands/clean.rs index 012516d..5cd7cf2 100644 --- a/src/subcommands/clean.rs +++ b/src/subcommands/clean.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, path::PathBuf}; -use color_eyre::eyre::Result; +use anyhow::Result; use path_absolutize::Absolutize; use crate::terminal_output::{print_error, print_info, print_success}; diff --git a/src/subcommands/init.rs b/src/subcommands/init.rs index 528ca49..bce0b0a 100644 --- a/src/subcommands/init.rs +++ b/src/subcommands/init.rs @@ -5,8 +5,8 @@ use std::{ path::{Path, PathBuf}, }; +use anyhow::Result; use clap::ValueEnum; -use color_eyre::eyre::Result; use git2::{ IndexAddOption as GitIndexAddOption, Repository as GitRepository, Signature as GitSignature, }; @@ -143,28 +143,36 @@ fn initialize_interactive(args: &InitArgs) -> Result<()> { if !path.exists() { if force { fs::create_dir_all(path)?; - } else if let Ok(true) = - inquire::Confirm::new("The specified path does not exist. Do you want to create it?") - .with_default(true) - .prompt() - { - fs::create_dir_all(path)?; } else { - print_info(ABORT_MSG); - return Ok(()); + match inquire::Confirm::new( + "The specified path does not exist. Do you want to create it?", + ) + .with_default(true) + .prompt() + { + Ok(true) => fs::create_dir_all(path)?, + Ok(false) | Err(_) => { + print_info(ABORT_MSG); + return Err(inquire::InquireError::OperationCanceled.into()); + } + } } } else if !path.is_dir() { print_error("The specified path is not a directory."); Err(Error::NotDirectoryError(path.to_path_buf()))? } else if !force && path.read_dir()?.next().is_some() { - if let Ok(false) = - inquire::Confirm::new("The specified directory is not empty. Do you want to continue?") - .with_default(false) - .with_help_message("This may overwrite existing files in the directory.") - .prompt() + match inquire::Confirm::new( + "The specified directory is not empty. Do you want to continue?", + ) + .with_default(false) + .with_help_message("This may overwrite existing files in the directory.") + .prompt() { - print_info(ABORT_MSG); - return Ok(()); + Ok(false) | Err(_) => { + print_info(ABORT_MSG); + return Err(inquire::InquireError::OperationCanceled.into()); + } + Ok(true) => {} } } @@ -195,7 +203,7 @@ fn initialize_interactive(args: &InitArgs) -> Result<()> { if interrupted { print_info(ABORT_MSG); - return Ok(()); + return Err(inquire::InquireError::OperationCanceled.into()); } let description = description.map(Cow::Borrowed).or_else(|| { @@ -213,7 +221,7 @@ fn initialize_interactive(args: &InitArgs) -> Result<()> { if interrupted { print_info(ABORT_MSG); - return Ok(()); + return Err(inquire::InquireError::OperationCanceled.into()); } let pack_format = pack_format.or_else(|| { @@ -231,7 +239,7 @@ fn initialize_interactive(args: &InitArgs) -> Result<()> { if interrupted { print_info(ABORT_MSG); - return Ok(()); + return Err(inquire::InquireError::OperationCanceled.into()); } let vcs = args.vcs.unwrap_or_else(|| { @@ -252,7 +260,7 @@ fn initialize_interactive(args: &InitArgs) -> Result<()> { if interrupted { print_info(ABORT_MSG); - return Ok(()); + return Err(inquire::InquireError::OperationCanceled.into()); } let icon_path = args.icon_path.as_deref().map(Cow::Borrowed).or_else(|| { @@ -277,7 +285,7 @@ fn initialize_interactive(args: &InitArgs) -> Result<()> { if interrupted { print_info(ABORT_MSG); - return Ok(()); + return Err(inquire::InquireError::OperationCanceled.into()); } print_info("Initializing a new Shulkerscript project..."); diff --git a/src/subcommands/lang_debug.rs b/src/subcommands/lang_debug.rs index c7105e1..b8bd260 100644 --- a/src/subcommands/lang_debug.rs +++ b/src/subcommands/lang_debug.rs @@ -1,8 +1,11 @@ use clap::ValueEnum; -use color_eyre::eyre::Result; +use anyhow::Result; +use shulkerscript::base::FsProvider; use std::path::PathBuf; +use crate::config::PackConfig; + #[derive(Debug, clap::Args, Clone)] pub struct LangDebugArgs { /// The path of the project to compile. @@ -27,9 +30,10 @@ pub enum DumpState { } pub fn lang_debug(args: &LangDebugArgs) -> Result<()> { + let file_provider = FsProvider::default(); match args.dump { DumpState::Tokens => { - let tokens = shulkerscript::tokenize(&args.path)?; + let tokens = shulkerscript::tokenize(&file_provider, &args.path)?; if args.pretty { println!("{:#?}", tokens); } else { @@ -37,7 +41,7 @@ pub fn lang_debug(args: &LangDebugArgs) -> Result<()> { } } DumpState::Ast => { - let ast = shulkerscript::parse(&args.path)?; + let ast = shulkerscript::parse(&file_provider, &args.path)?; if args.pretty { println!("{:#?}", ast); } else { @@ -46,7 +50,11 @@ pub fn lang_debug(args: &LangDebugArgs) -> Result<()> { } DumpState::Datapack => { let program_paths = super::build::get_script_paths(&args.path.join("src"))?; - let datapack = shulkerscript::transpile(&program_paths)?; + let datapack = shulkerscript::transpile( + &file_provider, + PackConfig::DEFAULT_PACK_FORMAT, + &program_paths, + )?; if args.pretty { println!("{:#?}", datapack); } else {