mirror of
https://github.com/watchexec/watchexec.git
synced 2024-11-16 17:18:30 +01:00
Start writing config file parser
This commit is contained in:
parent
f41299d7f9
commit
0ed758595f
4 changed files with 325 additions and 4 deletions
42
Cargo.lock
generated
42
Cargo.lock
generated
|
@ -965,6 +965,17 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kdl"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "114071e31456ec827056ca691d141f8e96327d9d9a29140da2e6fba9a5f17b83"
|
||||
dependencies = [
|
||||
"nom 7.1.0",
|
||||
"phf",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.4"
|
||||
|
@ -1418,7 +1429,9 @@ version = "0.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_shared",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1441,6 +1454,20 @@ dependencies = [
|
|||
"rand 0.7.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.8.0"
|
||||
|
@ -1553,6 +1580,12 @@ dependencies = [
|
|||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.36"
|
||||
|
@ -1833,18 +1866,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.133"
|
||||
version = "1.0.134"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
|
||||
checksum = "96b3c34c1690edf8174f5b289a336ab03f568a4460d8c6df75f2f3a692b3bc6a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.133"
|
||||
version = "1.0.134"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
|
||||
checksum = "784ed1fbfa13fe191077537b0d70ec8ad1e903cfe04831da608aa36457cb653d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2583,6 +2616,7 @@ dependencies = [
|
|||
"embed-resource",
|
||||
"futures",
|
||||
"insta",
|
||||
"kdl",
|
||||
"miette",
|
||||
"mimalloc",
|
||||
"notify-rust",
|
||||
|
|
|
@ -24,6 +24,7 @@ path = "src/main.rs"
|
|||
console-subscriber = { version = "0.1.0", optional = true }
|
||||
dunce = "1.0.2"
|
||||
futures = "0.3.17"
|
||||
kdl = "3.0.0"
|
||||
miette = { version = "3.2.0", features = ["fancy"] }
|
||||
notify-rust = "4.5.2"
|
||||
tracing = "0.1.26"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
pub mod file;
|
||||
mod init;
|
||||
mod runtime;
|
||||
|
||||
pub use file::Config as File;
|
||||
pub use init::init;
|
||||
pub use runtime::runtime;
|
||||
|
|
284
cli/src/config/file.rs
Normal file
284
cli/src/config/file.rs
Normal file
|
@ -0,0 +1,284 @@
|
|||
use kdl::{parse_document, KdlNode, KdlValue};
|
||||
use miette::{IntoDiagnostic, Report, Result};
|
||||
use watchexec::command::Shell;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Config {
|
||||
pub commands: Vec<Command>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn parse(input: &str) -> Result<Config> {
|
||||
let kdl = parse_document(input).into_diagnostic()?;
|
||||
let mut config = Config::default();
|
||||
|
||||
for root in kdl {
|
||||
match root.name.as_str() {
|
||||
"command" => config.commands.push(Command::parse(root)?),
|
||||
otherwise => todo!("Root: {:?}", otherwise),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Command {
|
||||
pub name: String,
|
||||
pub run: Option<Run>,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
fn parse(node: KdlNode) -> Result<Self> {
|
||||
let name = node
|
||||
.values
|
||||
.first()
|
||||
.ok_or_else(|| Report::msg("Command has no name"))
|
||||
.and_then(|name| match name {
|
||||
KdlValue::String(s) => Ok(s.to_owned()),
|
||||
otherwise => Err(Report::msg("Command name is not a string")
|
||||
.wrap_err(format!("{:?}", otherwise))),
|
||||
})?;
|
||||
|
||||
let mut runs = node
|
||||
.children
|
||||
.iter()
|
||||
.filter(|node| node.name == "run")
|
||||
.map(Run::parse)
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
if runs.len() > 1 {
|
||||
return Err(Report::msg("Command has multiple runs"));
|
||||
}
|
||||
|
||||
Ok(Command {
|
||||
name,
|
||||
run: runs.pop(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Run {
|
||||
pub shell: Shell,
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
impl Run {
|
||||
fn parse(node: &KdlNode) -> Result<Self> {
|
||||
let args = node
|
||||
.values
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(n, v)| match v {
|
||||
KdlValue::String(s) => Ok(s.to_owned()),
|
||||
otherwise => Err(Report::msg(format!("Run argument {n} is not a string"))
|
||||
.wrap_err(format!("{otherwise:?}"))),
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()
|
||||
.and_then(|run| {
|
||||
if run.is_empty() {
|
||||
Err(Report::msg("Run has no arguments"))
|
||||
} else {
|
||||
Ok(run)
|
||||
}
|
||||
})?;
|
||||
|
||||
let shell = node
|
||||
.properties
|
||||
.get("shell")
|
||||
.map(|shell| match shell {
|
||||
KdlValue::String(s) => Ok(s.to_owned()),
|
||||
otherwise => {
|
||||
Err(Report::msg("Run shell is not a string").wrap_err(format!("{otherwise:?}")))
|
||||
}
|
||||
})
|
||||
.transpose()?
|
||||
.map(|shell| match shell.as_str() {
|
||||
"powershell" | "pwsh" => Shell::Powershell,
|
||||
"none" => Shell::None,
|
||||
#[cfg(windows)]
|
||||
"cmd" => Shell::Cmd,
|
||||
unix => Shell::Unix(unix.to_owned()),
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if args.len() > 1 && shell != Shell::None {
|
||||
Err(Report::msg("Run has more than one argument and a shell"))
|
||||
} else {
|
||||
Ok(Run { shell, args })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn empty_command() {
|
||||
let config = Config::parse(r#"command "empty""#).unwrap();
|
||||
assert_eq!(config.commands.len(), 1);
|
||||
assert_eq!(config.commands[0].name, "empty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_command_with_braces() {
|
||||
let config = Config::parse(r#"command "empty" {}"#).unwrap();
|
||||
assert_eq!(config.commands.len(), 1);
|
||||
assert_eq!(config.commands[0].name, "empty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_with_run_one() {
|
||||
let config = Config::parse(
|
||||
r#"command "running" {
|
||||
run "echo hello-world"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(config.commands.len(), 1);
|
||||
assert_eq!(config.commands[0].name, "running");
|
||||
assert_eq!(
|
||||
config.commands[0].run,
|
||||
Some(Run {
|
||||
args: vec!["echo hello-world".to_owned()],
|
||||
shell: Shell::default()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_with_run_two() {
|
||||
let config = Config::parse(
|
||||
r#"command "running" {
|
||||
run "echo" "hello-world"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(config.commands.len(), 1);
|
||||
assert_eq!(config.commands[0].name, "running");
|
||||
assert_eq!(
|
||||
config.commands[0].run,
|
||||
Some(Run {
|
||||
args: vec!["echo".to_owned(), "hello-world".to_owned()],
|
||||
shell: Shell::default()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_with_no_run() {
|
||||
let config = Config::parse(r#"command "running" {}"#).unwrap();
|
||||
assert_eq!(config.commands.len(), 1);
|
||||
assert_eq!(config.commands[0].name, "running");
|
||||
assert_eq!(config.commands[0].run, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn command_with_empty_run() {
|
||||
Config::parse(
|
||||
r#"command "running" {
|
||||
run
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_with_default_shell() {
|
||||
let config = Config::parse(
|
||||
r#"command "running" {
|
||||
run "echo hello-world"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
config.commands[0].run.as_ref().unwrap().shell,
|
||||
Shell::default()
|
||||
);
|
||||
assert_eq!(
|
||||
config.commands[0].run.as_ref().unwrap().shell,
|
||||
Shell::None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_with_explicit_shell() {
|
||||
let config = Config::parse(
|
||||
r#"command "running" {
|
||||
run shell="bash" "echo hello-world"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
config.commands[0].run.as_ref().unwrap().shell,
|
||||
Shell::Unix("bash".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_with_powershell() {
|
||||
let config = Config::parse(
|
||||
r#"command "running" {
|
||||
run shell="powershell" "echo hello-world"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
config.commands[0].run.as_ref().unwrap().shell,
|
||||
Shell::Powershell
|
||||
);
|
||||
|
||||
let config = Config::parse(
|
||||
r#"command "running" {
|
||||
run shell="pwsh" "echo hello-world"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
config.commands[0].run.as_ref().unwrap().shell,
|
||||
Shell::Powershell
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn run_with_cmd_unix() {
|
||||
let config = Config::parse(
|
||||
r#"command "running" {
|
||||
run shell="cmd" "echo hello-world"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
config.commands[0].run.as_ref().unwrap().shell,
|
||||
Shell::Unix("cmd".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn run_with_cmd_windows() {
|
||||
let config = Config::parse(
|
||||
r#"command "running" {
|
||||
run shell="cmd" "echo hello-world"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(config.commands[0].run.as_ref().unwrap().shell, Shell::Cmd);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn multi_arg_run_with_shell() {
|
||||
Config::parse(
|
||||
r#"command "running" {
|
||||
run shell="bash" "echo" "hello-world"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue