diff --git a/Cargo.lock b/Cargo.lock index 0fa0f31..545160b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,12 +91,6 @@ dependencies = [ "libc", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" version = "0.2.129" @@ -109,7 +103,6 @@ version = "0.1.6" dependencies = [ "clap", "const_format", - "lazy_static", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index fd46122..74f6543 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ categories = ["command-line-utilities", "development-tools", "development-tools: [dependencies] clap = {version = "4", features = ["derive"]} const_format = "0.2" -lazy_static = "1" [[bin]] name = "omake" diff --git a/src/bin/main/_main.rs b/src/bin/main/_main.rs index ba42081..b72ab94 100644 --- a/src/bin/main/_main.rs +++ b/src/bin/main/_main.rs @@ -13,7 +13,10 @@ use clap::Parser; use args::Args; -use omake::{Context, DefaultLogger, Env, Logger, Makefile}; +use omake::context::Context; +use omake::logger::{DefaultLogger, Logger}; +use omake::makefile::Makefile; +use omake::vars::Env; /// An ordered list of filenames used to search for a makefile. const MAKEFILE_SEARCH: [&str; 6] = [ @@ -73,8 +76,8 @@ fn find_makefile() -> Option { } /// Print an error message and exit with code 2. -fn exit_with(msg: impl AsRef, logger: &DefaultLogger, context: Option<&Context>) -> ! { - logger.error(msg, context); +fn exit_with(logger: &DefaultLogger, msg: impl AsRef, context: Option) -> ! { + logger.error(msg, context.as_ref()); std::process::exit(2) } @@ -93,7 +96,7 @@ fn main() { } else { // Remember the current directory to return to. let cwd = env::current_dir() - .unwrap_or_else(|e| exit_with(format!("Failed to get cwd ({}).", e), &logger, None)); + .unwrap_or_else(|e| exit_with(&logger, format!("Failed to get cwd ({}).", e), None)); // Change to the specified directory. let dir = args @@ -102,14 +105,14 @@ fn main() { .fold(PathBuf::new(), |dir, d| dir.join(d)); logger.info(format!("Chdir to `{}`.", dir.display()), None); env::set_current_dir(&dir) - .unwrap_or_else(|e| exit_with(format!("Chdir failed: {}.", e), &logger, None)); + .unwrap_or_else(|e| exit_with(&logger, format!("Chdir failed: {}.", e), None)); Some(cwd) }; // Determine the makefile to read. let makefile_fn = match args.file { - None => find_makefile().unwrap_or_else(|| exit_with("No makefile found.", &logger, None)), + None => find_makefile().unwrap_or_else(|| exit_with(&logger, "No makefile found.", None)), Some(ref file) => PathBuf::from(file), }; @@ -132,19 +135,19 @@ fn main() { Box::new(DefaultLogger {}), env::vars().collect::().into(), ) { - Err(e) => exit_with(e.msg, &logger, Some(&e.context)), + Err(e) => exit_with(&logger, e.msg, Some(e.context)), Ok(m) => m, }; // Execute the makefile. if let Err(e) = makefile.execute(args.targets) { - exit_with(e.msg, &logger, Some(&e.context)); + exit_with(&logger, e.msg, Some(e.context)); } // Go back to the original directory, if we changed directory previously. if let Some(cwd) = original_dir { logger.info(format!("Chdir back to `{}`.", cwd.display()), None); env::set_current_dir(&cwd) - .unwrap_or_else(|e| exit_with(format!("Chdir failed: {}.", e), &logger, None)); + .unwrap_or_else(|e| exit_with(&logger, format!("Chdir failed: {}.", e), None)); } } diff --git a/src/bin/main/args.rs b/src/bin/main/args.rs index ca882dd..d8d6eba 100644 --- a/src/bin/main/args.rs +++ b/src/bin/main/args.rs @@ -4,9 +4,9 @@ use clap::Parser; use const_format::formatcp; -use omake::Opts; +use omake::makefile::Opts; -/// Represents the `clap`-based arguments provided by this binary. +/// The `clap`-based arguments provided by this binary. #[derive(Clone, Debug, Parser)] #[clap( name = "make (oxidized)", diff --git a/src/lib/_lib.rs b/src/lib/_lib.rs index d4e685c..8226ed4 100644 --- a/src/lib/_lib.rs +++ b/src/lib/_lib.rs @@ -1,16 +1,11 @@ //! # omake (Oxidized Make) //! -//! This is the library component of `omake`, generally oriented towards the main binary of this -//! crate, but should be designed to be used by other applications. +//! This is the library component of the `omake` project, responsible for parsing and executing +//! makefiles. -mod context; -mod error; -mod expand; -mod logger; -mod makefile; -mod vars; - -pub use context::Context; -pub use logger::{DefaultLogger, Logger, ERROR, INFO, WARN}; -pub use makefile::{Makefile, Opts}; -pub use vars::Env; +pub mod context; +pub mod error; +pub mod expand; +pub mod logger; +pub mod makefile; +pub mod vars; diff --git a/src/lib/context.rs b/src/lib/context.rs index 8bc1ef4..fdb1c1c 100644 --- a/src/lib/context.rs +++ b/src/lib/context.rs @@ -1,9 +1,8 @@ -//! Simple implementation of a `Context` struct designed to track parsing/execution location. +//! Tracking mechanism for makefiles; generally useful for errors, logging, etc. use std::path::PathBuf; -/// Represents parsing/execution context, specifically, which file and where in the file something -/// is happening. +/// Track a file path, content, and location within the file (line/column). #[derive(Clone, Debug)] pub struct Context { pub path: Option, diff --git a/src/lib/error.rs b/src/lib/error.rs index f48940b..1e07912 100644 --- a/src/lib/error.rs +++ b/src/lib/error.rs @@ -1,10 +1,11 @@ +//! Error type for the parser/executor. + use std::error::Error; use std::fmt; use crate::context::Context; -use crate::logger::{DefaultLogger, Logger, ERROR}; -/// Represents a generic error in a makefile, including context. +/// An error in the parsing or execution of a makefile. #[derive(Debug)] pub struct MakeError { pub msg: String, @@ -22,12 +23,9 @@ impl MakeError { impl Error for MakeError {} +/// Not really used, but needed so `MakeError` can implement `Error`. impl fmt::Display for MakeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - DefaultLogger {}.format_log(&self.msg, ERROR, Some(&self.context)) - ) + write!(f, "{e:?}", e = &self) } } diff --git a/src/lib/expand.rs b/src/lib/expand.rs index 0252ba7..dd5589c 100644 --- a/src/lib/expand.rs +++ b/src/lib/expand.rs @@ -1,17 +1,17 @@ +//! Implementation of variable expansion. + use crate::vars::Vars; -/// Represents a frame on the stack inside the `expand` function. This is used for tracking the -/// previous buffer when expanding potentially nested expressions (i.e., either `$()` or `${}`). -/// Single variable expansions (e.g., `$X`) are handled inline without creating a frame since they -/// cannot possibly have nested expressions. +/// A stack frame used to track the previous buffer when expanding potentially nested expressions +/// (i.e., either `$()` or `${}`). Single variable expansions (e.g., `$X`) are handled inline +/// without creating a frame since they cannot possibly have nested expressions. struct Frame { pub previous_buffer: String, - /// Track which character opened this stack frame (should be parenthesis or brace). + /// Which character opened this stack frame (parenthesis or brace)? pub opening_delimiter: char, } -/// The primary public interface for running variable expansion on an input string, given a -/// collection of `vars`. +/// Run variable expansion on an input string, given a collection of `vars`. /// /// The goal here is to be `O(n)`. This works by iterating over the input string and storing plain /// characters into a buffer until we hit either: diff --git a/src/lib/logger.rs b/src/lib/logger.rs index 03a9226..a16e912 100644 --- a/src/lib/logger.rs +++ b/src/lib/logger.rs @@ -1,3 +1,5 @@ +//! Generic logging facility with a default implementation. + use crate::context::Context; pub const INFO: &str = "INFO"; @@ -6,30 +8,28 @@ pub const ERROR: &str = "ERROR"; const MAX_SEVERITY_LENGTH: usize = 5; -#[derive(Clone, Debug)] -pub struct DefaultLogger {} - -pub trait Logger: Clone + std::fmt::Debug { +/// Generic trait any logger must implement. +pub trait Logger { /// Write the message somewhere. fn write(&self, msg: String); /// Log an `INFO` message. fn info(&self, msg: impl AsRef, context: Option<&Context>) { - self.write(self.format_log(msg, INFO, context)); + self.write(self.format_log(INFO, msg, context)); } /// Log a `WARN` message. fn warn(&self, msg: impl AsRef, context: Option<&Context>) { - self.write(self.format_log(msg, WARN, context)); + self.write(self.format_log(WARN, msg, context)); } /// Log an `ERROR` message. fn error(&self, msg: impl AsRef, context: Option<&Context>) { - self.write(self.format_log(msg, ERROR, context)); + self.write(self.format_log(ERROR, msg, context)); } /// Formatter for all log messages. - fn format_log(&self, msg: impl AsRef, level: &str, context: Option<&Context>) -> String { + fn format_log(&self, level: &str, msg: impl AsRef, context: Option<&Context>) -> String { // Format log level and context label/line. let level_display = format!("{:0width$}", level, width = MAX_SEVERITY_LENGTH); let context_label = context @@ -56,6 +56,9 @@ pub trait Logger: Clone + std::fmt::Debug { } } +/// Uses the default implementation and outputs to `stderr`. +pub struct DefaultLogger {} + /// By default, print to `stderr`. impl Logger for DefaultLogger { fn write(&self, msg: String) { diff --git a/src/lib/makefile.rs b/src/lib/makefile.rs index 3cbeb81..8872533 100644 --- a/src/lib/makefile.rs +++ b/src/lib/makefile.rs @@ -1,3 +1,5 @@ +//! Primary public interface for parsing/executing a makefile. + mod opts; mod rule_map; @@ -29,7 +31,7 @@ const COMMENT_INDICATOR: char = '#'; // breaks: Vec, // } -/// The internal representation of a makefile. +/// Represents a makefile. #[derive(Debug)] pub struct Makefile { pub opts: Opts, diff --git a/src/lib/vars.rs b/src/lib/vars.rs index b3cf34a..fac472b 100644 --- a/src/lib/vars.rs +++ b/src/lib/vars.rs @@ -1,61 +1,56 @@ -//! A wrapper for a `HashMap` for storing the environment variables during makefile parsing. +//! A wrapper for a [`HashMap`] storing the environment variables during makefile parsing. //! //! The only interesting behavior here is that for some special keys we have default values which //! should be "resettable" by setting the value to blank, and that calling `get` on a key that -//! doesn't exist should return an empty `Var`. To support these behaviors without polluting the -//! underlying `HashMap` with lots of duplicate data, the `Vars` struct contains fields for those -//! heap-allocated "constant" objects. Since we always return a reference to a `Var`, this is quite -//! efficient. +//! doesn't exist should return an empty [`Var`]. To support these behaviors without polluting the +//! underlying `HashMap` with lots of duplicate data, the [`Vars`] struct contains fields for those +//! heap-allocated "constant" objects. Since we always return a reference to a [`Var`], this is +//! quite efficient. use std::collections::HashMap; -use lazy_static::lazy_static; - -const BAD_VARIABLE_CHARS: [char; 3] = [':', '#', '=']; -const DEFAULT_SUFFIXES: [&str; 13] = [ +pub const BAD_VARIABLE_CHARS: [char; 3] = [':', '#', '=']; +pub const DEFAULT_SUFFIXES: [&str; 13] = [ ".C", ".F", ".S", ".c", ".cc", ".cpp", ".def", ".f", ".m", ".mod", ".p", ".r", ".s", ]; -/// List of variables where setting the value to blank means to reset it to the default value. -const BLANK_MEANS_DEFAULT_VARS: [&str; 1] = [".RECIPEPREFIX"]; - -lazy_static! { - /// Variables which are set in a non-recursive context by default, and can be overridden by the - /// environment. `SHELL` is not included, since it cannot be overridden by the environment, - /// unless explicitly directed to by `-e`. - static ref DEFAULT_VARS: HashMap = HashMap::from( - [ - (".RECIPEPREFIX", "\t"), - (".SHELLFLAGS", "-c"), - ("AR", "ar"), - ("ARFLAGS", "rv"), - ("AS", "as"), - ("CC", "cc"), - ("CO", "co"), - ("CTANGLE", "ctangle"), - ("CWEAVE", "cweave"), - ("CXX", "c++"), - ("FC", "f77"), - ("GET", "get"), - ("LD", "ld"), - ("LEX", "lex"), - ("LINT", "lint"), - ("M2C", "m2c"), - ("OBJC", "cc"), - ("PC", "pc"), - ("RM", "rm -f"), - ("TANGLE", "tangle"), - ("TEX", "tex"), - ("WEAVE", "weave"), - ("YACC", "yacc"), - ].map(|(k, v)| (k.to_string(), v.to_string())) - ); -} +/// List of variables where setting the value to blank means to reset it to the default value. All +/// of these values MUST exist in [`DEFAULT_VARS`]. +pub const BLANK_MEANS_DEFAULT_VARS: [&str; 1] = [".RECIPEPREFIX"]; + +/// Variables which are set in a non-recursive context by default, and can be overridden by the +/// environment. `SHELL` is not included, since it cannot be overridden by the environment, +/// unless explicitly directed to by `-e`. +pub const DEFAULT_VARS: [(&str, &str); 23] = [ + (".RECIPEPREFIX", "\t"), + (".SHELLFLAGS", "-c"), + ("AR", "ar"), + ("ARFLAGS", "rv"), + ("AS", "as"), + ("CC", "cc"), + ("CO", "co"), + ("CTANGLE", "ctangle"), + ("CWEAVE", "cweave"), + ("CXX", "c++"), + ("FC", "f77"), + ("GET", "get"), + ("LD", "ld"), + ("LEX", "lex"), + ("LINT", "lint"), + ("M2C", "m2c"), + ("OBJC", "cc"), + ("PC", "pc"), + ("RM", "rm -f"), + ("TANGLE", "tangle"), + ("TEX", "tex"), + ("WEAVE", "weave"), + ("YACC", "yacc"), +]; /// Variables which are set in a recursive context by default, and can be overridden by the /// environment. #[rustfmt::skip] -const DEFAULT_RECURSIVE_VARS: [(&str, &str); 36] = [ +pub const DEFAULT_RECURSIVE_VARS: [(&str, &str); 36] = [ ("OUTPUT_OPTION", "-o $@"), // Compiler definitions. @@ -101,13 +96,14 @@ const DEFAULT_RECURSIVE_VARS: [(&str, &str); 36] = [ /// Represents the "raw" environment coming from the OS. pub type Env = HashMap; +/// A single variable, with a value and a flag indicating whether it is recursive. #[derive(Debug)] pub struct Var { pub value: String, pub recursive: bool, } -/// This wraps a `HashMap` and a default value, providing an easy way to get variables, handling +/// Wrap a [`HashMap`] and a default `blank` value, providing an easy way to get variables, handling /// special and automatic variables properly. #[derive(Debug)] pub struct Vars { @@ -116,6 +112,10 @@ pub struct Vars { /// Variable to return when a variable is not found. This is allocated during initialization to /// prevent multiple blank allocations in the map and lifetime tracking. blank: Var, + + /// Stashing a map of [`DEFAULT_VARS`] here to make lookup fast since we sometimes need to + /// revert a value back to the default. + default_vars: HashMap, } impl Vars { @@ -128,11 +128,13 @@ impl Vars { value: "".to_string(), recursive: false, }, + default_vars: HashMap::new(), }; // Set default vars. - for (k, v) in DEFAULT_VARS.iter() { + for (k, v) in DEFAULT_VARS { vars.set(k, v, false).unwrap(); + vars.default_vars.insert(k.to_string(), v.to_string()); } // Set default recursive vars. @@ -186,7 +188,7 @@ impl Vars { } if BLANK_MEANS_DEFAULT_VARS.contains(&&k[..]) && v.is_empty() { - v = DEFAULT_VARS.get(&k).unwrap().to_string(); + v = self.default_vars.get(&k).unwrap().to_string(); } self.map.insert(