Ast now borrows from the deserializer where possible.

This commit is contained in:
Filipe Rodrigues 2022-09-24 22:36:40 +01:00
parent c71c8a7291
commit 31442ac356
6 changed files with 102 additions and 81 deletions

View File

@ -9,54 +9,67 @@ use {
/// Zbuild ast
#[derive(Clone, Debug)]
#[derive(serde::Deserialize)]
pub struct Ast {
pub struct Ast<'a> {
/// Aliases
#[serde(rename = "alias")]
#[serde(default)]
pub aliases: HashMap<String, Expr>,
#[serde(borrow)]
pub aliases: HashMap<String, Expr<'a>>,
/// Default target
#[serde(default)]
pub default: Vec<Target>,
#[serde(borrow)]
pub default: Vec<Target<'a>>,
/// Rules
pub rules: HashMap<String, Rule>,
#[serde(borrow)]
pub rules: HashMap<String, Rule<'a>>,
}
/// Output Item
#[derive(Clone, Debug)]
#[derive(serde::Deserialize)]
#[serde(untagged)]
pub enum OutItem {
pub enum OutItem<'a> {
/// File
File(Expr),
File(#[serde(borrow)] Expr<'a>),
/// Dependencies file
DepsFile { deps_file: Expr },
DepsFile {
#[serde(borrow)]
deps_file: Expr<'a>,
},
}
/// Dependency Item
#[derive(Clone, Debug)]
#[derive(serde::Deserialize)]
#[serde(untagged)]
pub enum DepItem {
pub enum DepItem<'a> {
/// File
File(Expr),
File(#[serde(borrow)] Expr<'a>),
/// Rule
Rule {
rule: Expr,
#[serde(borrow)]
rule: Expr<'a>,
#[serde(default)]
pats: HashMap<Expr, Expr>,
#[serde(borrow)]
pats: HashMap<Expr<'a>, Expr<'a>>,
},
/// Dependencies file
DepsFile { deps_file: Expr },
DepsFile {
#[serde(borrow)]
deps_file: Expr<'a>,
},
/// Static dependency
Static {
#[serde(rename = "static")]
item: StaticDepItem,
#[serde(borrow)]
item: StaticDepItem<'a>,
},
}
@ -64,38 +77,44 @@ pub enum DepItem {
#[derive(Clone, Debug)]
#[derive(serde::Deserialize)]
#[serde(untagged)]
pub enum StaticDepItem {
pub enum StaticDepItem<'a> {
/// File
File(Expr),
File(#[serde(borrow)] Expr<'a>),
/// Dependencies file
DepsFile { deps_file: Expr },
DepsFile {
#[serde(borrow)]
deps_file: Expr<'a>,
},
}
/// Target
#[derive(Clone, Debug)]
#[derive(serde::Deserialize)]
#[serde(untagged)]
pub enum Target {
pub enum Target<'a> {
/// File
File(Expr),
File(#[serde(borrow)] Expr<'a>),
/// Rule
Rule { rule: Expr },
Rule {
#[serde(borrow)]
rule: Expr<'a>,
},
}
/// Expression
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
pub struct Expr {
pub struct Expr<'a> {
/// Components
pub cmpts: Vec<ExprCmpt>,
pub cmpts: Vec<ExprCmpt<'a>>,
}
/// Expression component
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
pub enum ExprCmpt {
pub enum ExprCmpt<'a> {
/// String
String(String),
String(Cow<'a, str>),
/// Pattern
Pattern { name: String, ops: Vec<PatternOp> },
@ -118,18 +137,20 @@ pub enum AliasOp {
DirName,
}
impl<'de> serde::Deserialize<'de> for Expr {
impl<'a, 'de: 'a> serde::Deserialize<'de> for Expr<'a> {
#[allow(clippy::indexing_slicing, clippy::string_slice)] // We verify the indexes are correct
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
// Parse the string
let expr_str = Cow::<str>::deserialize(deserializer)?;
// TODO: Use `Cow<'a, str>`? We need to clone in case we to get an owned
// version, however, which complicates the code below
let expr_str = <&'a str>::deserialize(deserializer)?;
// Then parse all components
let mut cmpts = vec![];
let mut rest = &*expr_str;
let mut rest = expr_str;
loop {
// Try to find the next pattern / alias
match rest.find(['$', '^']) {
@ -137,7 +158,7 @@ impl<'de> serde::Deserialize<'de> for Expr {
Some(idx) => {
// Add the string until the pattern / alias, if it isn't empty
if !rest[..idx].is_empty() {
cmpts.push(ExprCmpt::String(rest[..idx].to_owned()));
cmpts.push(ExprCmpt::String(Cow::Borrowed(&rest[..idx])));
}
// Then check if it was an alias or pattern
@ -202,7 +223,7 @@ impl<'de> serde::Deserialize<'de> for Expr {
None => {
// Add the rest only if it isn't empty
if !rest.is_empty() {
cmpts.push(ExprCmpt::String(rest.to_owned()));
cmpts.push(ExprCmpt::String(Cow::Borrowed(rest)));
}
break;
},
@ -216,44 +237,49 @@ impl<'de> serde::Deserialize<'de> for Expr {
/// Rule
#[derive(Clone, Debug)]
#[derive(serde::Deserialize)]
pub struct Rule {
pub struct Rule<'a> {
/// Aliases
#[serde(default)]
pub alias: HashMap<String, Expr>,
#[serde(borrow)]
pub alias: HashMap<String, Expr<'a>>,
/// Output items
#[serde(default)]
pub out: Vec<OutItem>,
#[serde(borrow)]
pub out: Vec<OutItem<'a>>,
/// Dependencies
#[serde(default)]
pub deps: Vec<DepItem>,
#[serde(borrow)]
pub deps: Vec<DepItem<'a>>,
/// Execution
#[serde(default)]
pub exec: Exec,
#[serde(borrow)]
pub exec: Exec<'a>,
}
/// Execution
#[derive(Clone, Debug)]
#[derive(serde::Deserialize)]
#[serde(untagged)]
pub enum Exec {
pub enum Exec<'a> {
/// Only commands
OnlyCmds(Vec<Command>),
OnlyCmds(Vec<Command<'a>>),
/// Full
Full {
/// working directory
#[serde(default)]
cwd: Option<Expr>,
#[serde(borrow)]
cwd: Option<Expr<'a>>,
/// Commands
cmds: Vec<Command>,
cmds: Vec<Command<'a>>,
},
}
impl Default for Exec {
impl<'a> Default for Exec<'a> {
fn default() -> Self {
Self::OnlyCmds(vec![])
}
@ -264,7 +290,8 @@ impl Default for Exec {
#[derive(Clone, Debug)]
#[derive(serde::Deserialize)]
#[serde(transparent)]
pub struct Command {
pub struct Command<'a> {
/// All arguments
pub args: Vec<Expr>,
#[serde(borrow)]
pub args: Vec<Expr<'a>>,
}

View File

@ -25,7 +25,6 @@ use {
std::{
collections::HashMap,
mem,
path::Path,
time::{Duration, SystemTime},
},
tokio::{fs, process::Command, sync::Semaphore},
@ -263,14 +262,18 @@ impl Builder {
is_static,
is_dep_file: false,
is_output: false,
exists: fs_try_exists(file).await.map_err(AppError::check_file_exists(file))?,
exists: util::fs_try_exists(file)
.await
.map_err(AppError::check_file_exists(file))?,
}),
DepItem::DepsFile { ref file, is_static } => Ok(Dep::File {
file,
is_static,
is_dep_file: true,
is_output: false,
exists: fs_try_exists(file).await.map_err(AppError::check_file_exists(file))?,
exists: util::fs_try_exists(file)
.await
.map_err(AppError::check_file_exists(file))?,
}),
DepItem::Rule { ref name, ref pats } => Ok(Dep::Rule { name, pats }),
})
@ -289,7 +292,9 @@ impl Builder {
is_static: false,
is_dep_file: true,
is_output: true,
exists: fs_try_exists(file).await.map_err(AppError::check_file_exists(file))?,
exists: util::fs_try_exists(file)
.await
.map_err(AppError::check_file_exists(file))?,
})),
})
.collect::<FuturesUnordered<_>>()
@ -613,12 +618,3 @@ fn file_modified_time(metadata: std::fs::Metadata) -> SystemTime {
SystemTime::UNIX_EPOCH + unix_offset
}
/// Async `std::fs_try_exists`
async fn fs_try_exists(path: impl AsRef<Path> + Send) -> Result<bool, std::io::Error> {
match fs::metadata(path).await {
Ok(_) => Ok(true),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(err) => Err(err),
}
}

View File

@ -43,17 +43,6 @@ pub enum AppError {
err: io::Error,
},
/// Open file
#[error("Unable to open file {file_path:?}")]
OpenFile {
/// File we failed to open
file_path: PathBuf,
/// Underlying error
#[source]
err: io::Error,
},
/// Read file
#[error("Unable to read file {file_path:?}")]
ReadFile {
@ -339,13 +328,6 @@ impl AppError {
}
}
pub fn open_file(file_path: impl Into<PathBuf>) -> impl FnOnce(io::Error) -> Self {
move |err| Self::OpenFile {
file_path: file_path.into(),
err,
}
}
pub fn read_file(file_path: impl Into<PathBuf>) -> impl FnOnce(io::Error) -> Self {
move |err| Self::ReadFile {
file_path: file_path.into(),

View File

@ -80,7 +80,8 @@ use {
args::Args,
clap::StructOpt,
futures::{stream::FuturesUnordered, TryStreamExt},
std::{collections::HashMap, env, fs, path::PathBuf},
std::{collections::HashMap, env, path::PathBuf},
tokio::fs,
watcher::Watcher,
};
@ -99,18 +100,18 @@ async fn main() -> Result<(), anyhow::Error> {
// Find the zbuild location and change the current directory to it
let zbuild_path = match args.zbuild_path {
Some(path) => path,
None => self::find_zbuild()?,
None => self::find_zbuild().await?,
};
let zbuild_dir = zbuild_path.parent().expect("Zbuild path had no parent");
tracing::trace!(?zbuild_path, "Found zbuild path");
std::env::set_current_dir(zbuild_dir).map_err(AppError::set_current_dir(zbuild_dir))?;
// Parse the ast
let ast = {
let zbuild_file = fs::File::open(&zbuild_path).map_err(AppError::open_file(&zbuild_path))?;
serde_yaml::from_reader::<_, Ast>(zbuild_file).map_err(AppError::parse_yaml(&zbuild_path))?
};
tracing::trace!(target: "zbuild_ast", ?ast, "Parsed ast");
let zbuild_file = fs::read_to_string(&zbuild_path)
.await
.map_err(AppError::read_file(&zbuild_path))?;
let ast = serde_yaml::from_str::<Ast>(&zbuild_file).map_err(AppError::parse_yaml(&zbuild_path))?;
tracing::trace!(target: "zbuild_ast", "Parsed ast: {ast:#?}");
// Build the rules
let rules = Rules::new(ast);
@ -203,13 +204,16 @@ async fn main() -> Result<(), anyhow::Error> {
}
/// Finds the nearest zbuild file
fn find_zbuild() -> Result<PathBuf, AppError> {
async fn find_zbuild() -> Result<PathBuf, AppError> {
let cur_path = env::current_dir().map_err(AppError::get_current_dir())?;
let mut cur_path = cur_path.as_path();
loop {
let zbuild_path = cur_path.join("zbuild.yaml");
match fs::try_exists(&zbuild_path).map_err(AppError::check_file_exists(&zbuild_path))? {
match util::fs_try_exists(&zbuild_path)
.await
.map_err(AppError::check_file_exists(&zbuild_path))?
{
true => return Ok(zbuild_path),
false => match cur_path.parent() {
Some(parent) => cur_path = parent,

View File

@ -37,7 +37,7 @@ impl Expr {
.cmpts
.into_iter()
.map(|cmpt| match cmpt {
ast::ExprCmpt::String(s) => ExprCmpt::String(s),
ast::ExprCmpt::String(s) => ExprCmpt::String(s.into_owned()),
ast::ExprCmpt::Pattern { name, ops } => ExprCmpt::Pattern(Pattern {
name,
ops: ops

View File

@ -1,5 +1,8 @@
//! Utilities
// Imports
use {std::path::Path, tokio::fs};
/// Chains together any number of `IntoIterator`s
pub macro chain {
($lhs:expr, $rhs:expr $(,)?) => {
@ -10,3 +13,12 @@ pub macro chain {
::std::iter::Iterator::chain($lhs.into_iter(), $crate::util::chain!($rhs, $($rest),*))
},
}
/// Async `std::fs_try_exists`
pub async fn fs_try_exists(path: impl AsRef<Path> + Send) -> Result<bool, std::io::Error> {
match fs::metadata(path).await {
Ok(_) => Ok(true),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(err) => Err(err),
}
}