Use crude Nix parsing for parsing the flake path

This commit is contained in:
notgne2 2020-11-29 11:51:52 -07:00
parent a19af74789
commit fa4c0a86cd
No known key found for this signature in database
GPG Key ID: BB661E172B42A7F8
4 changed files with 160 additions and 55 deletions

56
Cargo.lock generated
View File

@ -44,6 +44,15 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
[[package]]
name = "cbitset"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29b6ad25ae296159fb0da12b970b2fe179b234584d7cd294c891e2bbb284466b"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "0.1.10" version = "0.1.10"
@ -93,6 +102,7 @@ dependencies = [
"log", "log",
"merge", "merge",
"pretty_env_logger", "pretty_env_logger",
"rnix",
"serde", "serde",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
@ -562,6 +572,34 @@ version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
[[package]]
name = "rnix"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbbea4c714e5bbf462fa4316ddf45875d8f0e28e5db81050b5f9ce99746c6863"
dependencies = [
"cbitset",
"rowan",
]
[[package]]
name = "rowan"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ea7cadf87a9d8432e85cb4eb86bd2e765ace60c24ef86e79084dcae5d1c5a19"
dependencies = [
"rustc-hash",
"smol_str",
"text_unit",
"thin-dst",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.5" version = "1.0.5"
@ -612,6 +650,12 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]]
name = "smol_str"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ca0f7ce3a29234210f0f4f0b56f8be2e722488b95cb522077943212da3b32eb"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.3.15" version = "0.3.15"
@ -650,6 +694,12 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "text_unit"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20431e104bfecc1a40872578dbc390e10290a0e9c35fffe3ce6f73c15a9dbfc2"
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.12.1" version = "0.12.1"
@ -659,6 +709,12 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "thin-dst"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c46be180f1af9673ebb27bc1235396f61ef6965b3fe0dbb2e624deb604f0e"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.21" version = "1.0.21"

View File

@ -26,6 +26,7 @@ fork = "0.1"
thiserror = "1.0" thiserror = "1.0"
toml = "0.5" toml = "0.5"
yn = "0.1" yn = "0.1"
rnix = "0.8"
[[bin]] [[bin]]

View File

@ -338,7 +338,7 @@ async fn run_deploy(
extra_build_args: &[String], extra_build_args: &[String],
) -> Result<(), RunDeployError> { ) -> Result<(), RunDeployError> {
let to_deploy: Vec<((&str, &utils::data::Node), (&str, &utils::data::Profile))> = let to_deploy: Vec<((&str, &utils::data::Node), (&str, &utils::data::Profile))> =
match (deploy_flake.node, deploy_flake.profile) { match (&deploy_flake.node, &deploy_flake.profile) {
(Some(node_name), Some(profile_name)) => { (Some(node_name), Some(profile_name)) => {
let node = match data.nodes.get(node_name) { let node = match data.nodes.get(node_name) {
Some(x) => x, Some(x) => x,
@ -379,7 +379,7 @@ async fn run_deploy(
profiles_list profiles_list
.into_iter() .into_iter()
.map(|x| ((node_name, node), x)) .map(|x| ((node_name.as_str(), node), x))
.collect() .collect()
} }
(None, None) => { (None, None) => {
@ -424,7 +424,7 @@ async fn run_deploy(
let mut parts: Vec<(utils::DeployData, utils::DeployDefs)> = Vec::new(); let mut parts: Vec<(utils::DeployData, utils::DeployDefs)> = Vec::new();
for ((node_name, node), (profile_name, profile)) in &to_deploy { for ((node_name, node), (profile_name, profile)) in to_deploy {
let deploy_data = utils::make_deploy_data( let deploy_data = utils::make_deploy_data(
&data.generic_settings, &data.generic_settings,
node, node,
@ -478,6 +478,8 @@ enum RunError {
CheckDeploymentError(#[from] CheckDeploymentError), CheckDeploymentError(#[from] CheckDeploymentError),
#[error("Failed to evaluate deployment data: {0}")] #[error("Failed to evaluate deployment data: {0}")]
GetDeploymentDataError(#[from] GetDeploymentDataError), GetDeploymentDataError(#[from] GetDeploymentDataError),
#[error("Error parsing flake: {0}")]
ParseFlakeError(#[from] utils::ParseFlakeError),
#[error("{0}")] #[error("{0}")]
RunDeployError(#[from] RunDeployError), RunDeployError(#[from] RunDeployError),
} }
@ -491,7 +493,7 @@ async fn run() -> Result<(), RunError> {
let opts: Opts = Opts::parse(); let opts: Opts = Opts::parse();
let deploy_flake = utils::parse_flake(opts.flake.as_str()); let deploy_flake = utils::parse_flake(opts.flake.as_str())?;
let cmd_overrides = utils::CmdOverrides { let cmd_overrides = utils::CmdOverrides {
ssh_user: opts.ssh_user, ssh_user: opts.ssh_user,

View File

@ -2,6 +2,8 @@
// //
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use rnix::{types::*, NodeOrToken, SyntaxKind::*, SyntaxNode};
use std::path::PathBuf; use std::path::PathBuf;
use merge::Merge; use merge::Merge;
@ -36,66 +38,86 @@ pub struct CmdOverrides {
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub struct DeployFlake<'a> { pub struct DeployFlake<'a> {
pub repo: &'a str, pub repo: &'a str,
pub node: Option<&'a str>, pub node: Option<String>,
pub profile: Option<&'a str>, pub profile: Option<String>,
} }
pub fn parse_flake(flake: &str) -> DeployFlake { #[derive(Error, Debug)]
pub enum ParseFlakeError {
#[error("The given path was too long, did you mean to put something in quotes?")]
PathTooLong,
#[error("Unrecognized node or token encountered")]
Unrecognized,
}
pub fn parse_flake(flake: &str) -> Result<DeployFlake, ParseFlakeError> {
let flake_fragment_start = flake.find('#'); let flake_fragment_start = flake.find('#');
let (repo, maybe_fragment) = match flake_fragment_start { let (repo, maybe_fragment) = match flake_fragment_start {
Some(s) => (&flake[..s], Some(&flake[s + 1..])), Some(s) => (&flake[..s], Some(&flake[s + 1..])),
None => (flake, None), None => (flake, None),
}; };
let (node, profile) = match maybe_fragment { let mut node: Option<String> = None;
Some(fragment) => { let mut profile: Option<String> = None;
let fragment_profile_start = fragment.rfind('.');
match fragment_profile_start { if let Some(fragment) = maybe_fragment {
Some(s) => ( let ast = rnix::parse(fragment);
Some(&fragment[..s]),
// Ignore the trailing `.` let first_child = match ast.root().node().first_child() {
(if (s + 1) == fragment.len() { Some(x) => x,
None None => {
} else { return Ok(DeployFlake {
Some(&fragment[s + 1..]) repo,
}), node: None,
), profile: None,
None => (Some(fragment), None), })
}
};
let mut node_over = false;
for entry in first_child.children_with_tokens() {
let x: Option<String> = match (entry.kind(), node_over) {
(TOKEN_DOT, false) => {
node_over = true;
None
}
(TOKEN_DOT, true) => {
return Err(ParseFlakeError::PathTooLong);
}
(NODE_IDENT, _) => Some(entry.into_node().unwrap().text().to_string()),
(TOKEN_IDENT, _) => Some(entry.into_token().unwrap().text().to_string()),
(NODE_STRING, _) => {
let c = entry
.into_node()
.unwrap()
.children_with_tokens()
.nth(1)
.unwrap();
Some(c.into_token().unwrap().text().to_string())
}
_ => return Err(ParseFlakeError::Unrecognized),
};
if !node_over {
node = x;
} else {
profile = x;
} }
} }
None => (None, None), }
};
DeployFlake { Ok(DeployFlake {
repo, repo,
node, node,
profile, profile,
} })
} }
#[test] #[test]
fn test_parse_flake() { fn test_parse_flake() {
assert_eq!( assert_eq!(
parse_flake("../deploy/examples/system#example"), parse_flake("../deploy/examples/system").unwrap(),
DeployFlake {
repo: "../deploy/examples/system",
node: Some("example"),
profile: None
}
);
assert_eq!(
parse_flake("../deploy/examples/system#example.system"),
DeployFlake {
repo: "../deploy/examples/system",
node: Some("example"),
profile: Some("system")
}
);
assert_eq!(
parse_flake("../deploy/examples/system"),
DeployFlake { DeployFlake {
repo: "../deploy/examples/system", repo: "../deploy/examples/system",
node: None, node: None,
@ -103,33 +125,57 @@ fn test_parse_flake() {
} }
); );
// Trailing `.` should be ignored
assert_eq!( assert_eq!(
parse_flake("../deploy/examples/system#example."), parse_flake("../deploy/examples/system#").unwrap(),
DeployFlake { DeployFlake {
repo: "../deploy/examples/system", repo: "../deploy/examples/system",
node: Some("example"), node: None,
profile: None,
}
);
assert_eq!(
parse_flake("../deploy/examples/system#computer.\"something.nix\"").unwrap(),
DeployFlake {
repo: "../deploy/examples/system",
node: Some("computer".to_string()),
profile: Some("something.nix".to_string()),
}
);
assert_eq!(
parse_flake("../deploy/examples/system#\"example.com\".system").unwrap(),
DeployFlake {
repo: "../deploy/examples/system",
node: Some("example.com".to_string()),
profile: Some("system".to_string()),
}
);
assert_eq!(
parse_flake("../deploy/examples/system#example").unwrap(),
DeployFlake {
repo: "../deploy/examples/system",
node: Some("example".to_string()),
profile: None profile: None
} }
); );
// The last `.` should be used for splitting
assert_eq!( assert_eq!(
parse_flake("../deploy/examples/system#example.com.system"), parse_flake("../deploy/examples/system#example.system").unwrap(),
DeployFlake { DeployFlake {
repo: "../deploy/examples/system", repo: "../deploy/examples/system",
node: Some("example.com"), node: Some("example".to_string()),
profile: Some("system") profile: Some("system".to_string())
} }
); );
// The last `.` should be used for splitting, _and_ trailing `.` should be ignored
assert_eq!( assert_eq!(
parse_flake("../deploy/examples/system#example.com."), parse_flake("../deploy/examples/system").unwrap(),
DeployFlake { DeployFlake {
repo: "../deploy/examples/system", repo: "../deploy/examples/system",
node: Some("example.com"), node: None,
profile: None profile: None,
} }
); );
} }