diff --git a/Cargo.lock b/Cargo.lock index b0be165..2cb92a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,7 @@ version = "0.1.0" dependencies = [ "nom", "regex", + "thiserror", ] [[package]] @@ -41,6 +42,24 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + [[package]] name = "regex" version = "1.10.5" @@ -69,3 +88,40 @@ name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "syn" +version = "2.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/Cargo.toml b/Cargo.toml index f03daf2..e075a72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" [dependencies] nom = "7.1.3" regex = "1.10.5" +thiserror = "1.0.61" diff --git a/src/main.rs b/src/main.rs index ceef1e3..083ba8d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,34 @@ -use std::{ - collections::{HashMap, HashSet}, - env::args, - path::PathBuf, -}; +use std::env::args; -use ast::{Parse as _, Term}; use makefile::{IDGen, Makefile}; -use nom::error::convert_error; +use nom::error::VerboseError; +use thiserror::Error; mod ast; mod makefile; mod parser; +#[derive(Error, Debug)] +pub enum Error { + #[error("IO error: {0}")] + IO(#[from] std::io::Error), + + #[error("Parsing error:\n{0}")] + ParseErr(String), + + #[error("{0}")] + PathErr(String), +} + +impl Error { + pub fn from_nom(source: &str, err: nom::error::VerboseError<&str>) -> Self { + let str = nom::error::convert_error(source, err); + Self::ParseErr(str) + } +} + fn main() { + // TODO: clap arg parser let path = args().nth(1).unwrap_or_else(|| { eprintln!("Usage: {} ", args().next().unwrap()); std::process::exit(1); @@ -20,7 +36,13 @@ fn main() { eprintln!("Starting at {}", path); - let (makefiles, externals) = Makefile::walk_from(path); + let (makefiles, externals) = match Makefile::walk_from(path) { + Ok(v) => v, + Err(err) => { + eprintln!("Error walking makefile:\n{}", err); + std::process::exit(1); + } + }; let mut id = IDGen::new("cluster_"); println!("digraph G {{\n\tranksep=3"); diff --git a/src/makefile.rs b/src/makefile.rs index c2cdcdb..536c436 100644 --- a/src/makefile.rs +++ b/src/makefile.rs @@ -8,7 +8,7 @@ use regex::Regex; use crate::{ ast::{self, Parse as _}, - parser, + parser, Error, }; type ID = String; @@ -17,7 +17,7 @@ type Variables = HashMap; macro_rules! regex { ($re:literal $(,)?) => {{ static RE: OnceLock = OnceLock::new(); - RE.get_or_init(|| regex::Regex::new($re).unwrap()) + RE.get_or_init(|| regex::Regex::new($re).expect("Invalid regex!")) }}; } @@ -76,7 +76,9 @@ impl Makefile { .find(|(_, t)| t.name == name) .map(|(id, _)| id) } - pub fn walk_from(path: impl AsRef) -> (Vec, HashSet>) { + pub fn walk_from( + path: impl AsRef, + ) -> Result<(Vec, HashSet>), crate::Error> { let path = path.as_ref().to_path_buf(); let mut out = Vec::new(); let mut idgen = IDGen::new("task"); @@ -86,26 +88,32 @@ impl Makefile { while let Some(path) = paths.pop_front() { eprintln!("Parsing {}", path.display()); let mut exts = HashSet::new(); - let data = std::fs::read_to_string(&path).unwrap(); - let terms = parser::Makefile::parse(&data).unwrap(); + let data = std::fs::read_to_string(&path)?; + let terms = parser::Makefile::parse(&data).map_err(|e| Error::from_nom(&data, e))?; let m = Makefile::from_terms(&mut idgen, &mut exts, path, terms); - let exts = exts.iter().map(|e| { - e.clone().map_path(|p| { - let p = m.resolve_makefile(p); - if !(paths.contains(&p) || out.iter().any(|m: &Makefile| m.file == p)) { - paths.push_back(p.clone()); + let exts = exts.iter().filter_map(|e| { + let path = &e.path; + let path = match m.resolve_makefile(path) { + Ok(p) => p, + Err(err) => { + eprintln!("Couldn't resolve makefile: {}, {}", path.0, err); + return None; } - p - }) + }; + if !(paths.contains(&path) || out.iter().any(|m: &Makefile| m.file == path)) { + paths.push_back(path.clone()); + } + + Some(e.clone().map_path(|_| path)) }); external.extend(exts); out.push(m); } - (out, external) + Ok((out, external)) } - pub fn resolve_vars(&self, str: VarStr) -> String { + pub fn resolve_vars(&self, str: &VarStr) -> String { let re_var = regex!(r"\$\{([^}]+)\}"); let out = re_var .replace_all(&str.0, |v: ®ex::Captures| { @@ -115,20 +123,27 @@ impl Makefile { .into_owned(); out } - pub fn resolve_makefile(&self, path: VarStr) -> PathBuf { + pub fn resolve_makefile(&self, path: &VarStr) -> Result { let path = self .file .parent() - .expect(format!("invalid path: {}", self.file.to_string_lossy()).as_str()) + .ok_or(Error::PathErr(format!( + "Makefile path has no parent: {}", + self.file.display() + )))? .join(self.resolve_vars(path)); - let mut path = path - .canonicalize() - .expect(format!("could not canonicalize path: {}", path.to_string_lossy()).as_str()); + let mut path = path.canonicalize().map_err(|err| { + Error::PathErr(format!( + "Couldn't canonicalize {},\n{}", + path.display(), + err + )) + })?; if path.is_dir() { path.push("Makefile"); } - path + Ok(path) } pub fn from_terms( id: &mut IDGen, diff --git a/src/parser.rs b/src/parser.rs index 0948ab8..46d1345 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -11,7 +11,7 @@ use nom::{ use crate::ast::{self, Task, Term, Variable}; -type ParseErr<'a> = VerboseError<&'a str>; +pub type ParseErr<'a> = VerboseError<&'a str>; type ParseResult<'a, O> = nom::IResult<&'a str, O, ParseErr<'a>>; fn hspace0<'a>(tab: bool) -> impl Parser<&'a str, (), ParseErr<'a>> {