implement watch command

This commit is contained in:
Moritz Hölting 2024-06-12 22:25:09 +02:00
parent 055206f7c8
commit cc6cf18406
8 changed files with 158 additions and 18 deletions

View File

@ -17,9 +17,10 @@ name = "shulkerscript"
path = "src/main.rs"
[features]
default = ["zip", "lua"]
default = ["zip", "lua", "watch"]
lang-debug = []
lua = ["shulkerscript/lua"]
watch = ["dep:notify-debouncer-mini", "dep:ctrlc"]
zip = ["shulkerbox/zip"]
[dependencies]
@ -34,4 +35,5 @@ git2 = { version = "0.18.3", 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 }

View File

@ -57,6 +57,16 @@ Options:
Environment variables:
- `DATAPACK_DIR` The output directory [default: `./dist`]
### Watch for changes
```bash
shulkerscript watch [OPTIONS] [SUBCOMMAND]
```
Where [SUBCOMMAND] is either `build` or `package` [default: `build`]
Options:
- `--no-initial` Do not run the command initially
- `--debounce-time <DEBOUNCE_TIME>` The time to wait in ms after the last change before running the command [default: `2000`]
## Contributing
Pull requests are welcome. For major changes, please open an issue first

View File

@ -24,6 +24,9 @@ pub enum Command {
#[cfg(feature = "zip")]
/// Build and package the project.
Package(subcommands::PackageArgs),
#[cfg(feature = "watch")]
/// Watch for changes and execute command.
Watch(subcommands::WatchArgs),
#[cfg(feature = "lang-debug")]
/// Build the project and dump the intermediate state.
LangDebug(subcommands::LangDebugArgs),
@ -31,12 +34,20 @@ pub enum Command {
impl Args {
pub fn run(&self) -> Result<()> {
match &self.cmd {
Command::Init(args) => subcommands::init(self.verbose, args)?,
Command::Build(args) => subcommands::build(self.verbose, args)?,
Command::Clean(args) => subcommands::clean(self.verbose, args)?,
self.cmd.run(self.verbose)
}
}
impl Command {
pub fn run(&self, verbose: bool) -> Result<()> {
match self {
Command::Init(args) => subcommands::init(verbose, args)?,
Command::Build(args) => subcommands::build(verbose, args)?,
Command::Clean(args) => subcommands::clean(verbose, args)?,
#[cfg(feature = "zip")]
Command::Package(args) => subcommands::package(self.verbose, args)?,
Command::Package(args) => subcommands::package(verbose, args)?,
#[cfg(feature = "watch")]
Command::Watch(args) => subcommands::watch(verbose, args)?,
#[cfg(feature = "lang-debug")]
Command::LangDebug(args) => subcommands::lang_debug(args)?,
}

View File

@ -20,26 +20,26 @@ use crate::{
pub struct InitArgs {
/// The path of the folder to initialize in.
#[clap(default_value = ".")]
path: PathBuf,
pub path: PathBuf,
/// The name of the project.
#[clap(short, long)]
name: Option<String>,
pub name: Option<String>,
/// The description of the project.
#[clap(short, long)]
description: Option<String>,
pub description: Option<String>,
/// The pack format version.
#[clap(short, long)]
pack_format: Option<u8>,
pub pack_format: Option<u8>,
/// Force initialization even if the directory is not empty.
#[clap(short, long)]
force: bool,
pub force: bool,
/// The version control system to initialize.
#[clap(long, default_value = "git")]
vcs: VersionControlSystem,
pub vcs: VersionControlSystem,
}
#[derive(Debug, Clone, Copy, Default, ValueEnum)]
enum VersionControlSystem {
pub enum VersionControlSystem {
#[default]
Git,
None,

View File

@ -7,13 +7,13 @@ use std::path::PathBuf;
pub struct LangDebugArgs {
/// The path of the project to compile.
#[clap(default_value = ".")]
path: PathBuf,
pub path: PathBuf,
/// The state to dump.
#[clap(short, long, default_value = "ast")]
dump: DumpState,
pub dump: DumpState,
/// Pretty-print the output.
#[clap(short, long)]
pretty: bool,
pub pretty: bool,
}
#[derive(ValueEnum, Debug, Clone, Copy, Default)]

View File

@ -12,6 +12,11 @@ mod package;
#[cfg(feature = "zip")]
pub use package::{package, PackageArgs};
#[cfg(feature = "watch")]
mod watch;
#[cfg(feature = "watch")]
pub use watch::{watch, WatchArgs};
#[cfg(feature = "lang-debug")]
mod lang_debug;
#[cfg(feature = "lang-debug")]

View File

@ -14,7 +14,7 @@ use super::BuildArgs;
#[derive(Debug, clap::Args, Clone)]
pub struct PackageArgs {
#[clap(flatten)]
build_args: BuildArgs,
pub build_args: BuildArgs,
}
pub fn package(_verbose: bool, args: &PackageArgs) -> Result<()> {

112
src/subcommands/watch.rs Normal file
View File

@ -0,0 +1,112 @@
use std::{
path::{Path, PathBuf},
thread,
time::Duration,
};
use clap::Subcommand;
use notify_debouncer_mini::{new_debouncer, notify::*, DebounceEventResult};
use super::BuildArgs;
use crate::{
cli::Command,
error::Result,
terminal_output::{print_error, print_info},
};
#[derive(Debug, clap::Args, Clone)]
pub struct WatchArgs {
/// Do not run the command when starting, only after changes are detected.
#[clap(short, long)]
no_inital: bool,
/// The time to wait in ms before running the command after changes are detected.
#[clap(short, long, default_value = "2000")]
debounce_time: u64,
/// The command to run when changes are detected.
#[command(subcommand)]
cmd: Option<WatchSubcommand>,
}
#[derive(Debug, Clone, Subcommand)]
pub enum WatchSubcommand {
/// Build the project.
Build(BuildArgs),
#[cfg(feature = "zip")]
/// Build and package the project.
Package(super::PackageArgs),
}
impl From<WatchSubcommand> for Command {
fn from(value: WatchSubcommand) -> Self {
match value {
WatchSubcommand::Build(args) => Command::Build(args),
#[cfg(feature = "zip")]
WatchSubcommand::Package(args) => Command::Package(args),
}
}
}
pub fn watch(verbose: bool, args: &WatchArgs) -> Result<()> {
let cmd = Command::from(args.cmd.to_owned().unwrap_or_else(|| {
WatchSubcommand::Build(BuildArgs {
path: PathBuf::from("."),
output: None,
assets: None,
})
}));
let project_path = match &args.cmd {
Some(WatchSubcommand::Build(args)) => args.path.as_path(),
#[cfg(feature = "zip")]
Some(WatchSubcommand::Package(args)) => args.build_args.path.as_path(),
None => Path::new("."),
};
#[allow(clippy::collapsible_if)]
if !args.no_inital {
if cmd.run(verbose).is_err() {
print_error("Command failed to run initially");
}
}
ctrlc::set_handler(move || {
print_info("Stopping watcher...");
std::process::exit(0);
})
.expect("Error setting Ctrl-C handler");
let mut debouncer = new_debouncer(
Duration::from_millis(args.debounce_time),
move |res: DebounceEventResult| {
if res.is_ok() {
if cmd.run(verbose).is_err() {
print_error("Command failed to run");
}
} else {
std::process::exit(1);
}
},
)
.expect("Failed to initialize watcher");
let watcher = debouncer.watcher();
watcher
.watch(project_path.join("src").as_path(), RecursiveMode::Recursive)
.expect("Failed to watch project src");
watcher
.watch(
project_path.join("pack.png").as_path(),
RecursiveMode::NonRecursive,
)
.expect("Failed to watch project pack.png");
watcher
.watch(
project_path.join("pack.toml").as_path(),
RecursiveMode::NonRecursive,
)
.expect("Failed to watch project pack.toml");
loop {
thread::sleep(Duration::from_secs(60));
}
}