Start writing config file parser
This commit is contained in:
parent
f41299d7f9
commit
0ed758595f
|
@ -965,6 +965,17 @@ dependencies = [
|
||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "kqueue"
|
name = "kqueue"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -1418,7 +1429,9 @@ version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"phf_macros",
|
||||||
"phf_shared",
|
"phf_shared",
|
||||||
|
"proc-macro-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1441,6 +1454,20 @@ dependencies = [
|
||||||
"rand 0.7.3",
|
"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]]
|
[[package]]
|
||||||
name = "phf_shared"
|
name = "phf_shared"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -1553,6 +1580,12 @@ dependencies = [
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-hack"
|
||||||
|
version = "0.5.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.36"
|
version = "1.0.36"
|
||||||
|
@ -1833,18 +1866,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.133"
|
version = "1.0.134"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
|
checksum = "96b3c34c1690edf8174f5b289a336ab03f568a4460d8c6df75f2f3a692b3bc6a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.133"
|
version = "1.0.134"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
|
checksum = "784ed1fbfa13fe191077537b0d70ec8ad1e903cfe04831da608aa36457cb653d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2583,6 +2616,7 @@ dependencies = [
|
||||||
"embed-resource",
|
"embed-resource",
|
||||||
"futures",
|
"futures",
|
||||||
"insta",
|
"insta",
|
||||||
|
"kdl",
|
||||||
"miette",
|
"miette",
|
||||||
"mimalloc",
|
"mimalloc",
|
||||||
"notify-rust",
|
"notify-rust",
|
||||||
|
|
|
@ -24,6 +24,7 @@ path = "src/main.rs"
|
||||||
console-subscriber = { version = "0.1.0", optional = true }
|
console-subscriber = { version = "0.1.0", optional = true }
|
||||||
dunce = "1.0.2"
|
dunce = "1.0.2"
|
||||||
futures = "0.3.17"
|
futures = "0.3.17"
|
||||||
|
kdl = "3.0.0"
|
||||||
miette = { version = "3.2.0", features = ["fancy"] }
|
miette = { version = "3.2.0", features = ["fancy"] }
|
||||||
notify-rust = "4.5.2"
|
notify-rust = "4.5.2"
|
||||||
tracing = "0.1.26"
|
tracing = "0.1.26"
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
pub mod file;
|
||||||
mod init;
|
mod init;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
|
|
||||||
|
pub use file::Config as File;
|
||||||
pub use init::init;
|
pub use init::init;
|
||||||
pub use runtime::runtime;
|
pub use runtime::runtime;
|
||||||
|
|
|
@ -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 New Issue