|
@@ -10,16 +10,25 @@
|
|
|
*/
|
|
|
|
|
|
use aml::{AmlContext, DebugVerbosity};
|
|
|
-use clap::{Arg, ArgAction};
|
|
|
+use clap::{Arg, ArgGroup, ArgAction};
|
|
|
use std::{
|
|
|
ffi::OsStr,
|
|
|
fs::{self, File},
|
|
|
io::{Read, Write},
|
|
|
- ops::Not,
|
|
|
- path::Path,
|
|
|
+ path::{Path, PathBuf},
|
|
|
process::Command,
|
|
|
+ collections::HashSet,
|
|
|
};
|
|
|
|
|
|
+enum CompilationOutcome {
|
|
|
+ Ignored,
|
|
|
+ IsAml(PathBuf),
|
|
|
+ Newer(PathBuf),
|
|
|
+ NotCompiled(PathBuf),
|
|
|
+ Failed(PathBuf),
|
|
|
+ Succeeded(PathBuf),
|
|
|
+}
|
|
|
+
|
|
|
fn main() -> std::io::Result<()> {
|
|
|
log::set_logger(&Logger).unwrap();
|
|
|
log::set_max_level(log::LevelFilter::Trace);
|
|
@@ -28,36 +37,102 @@ fn main() -> std::io::Result<()> {
|
|
|
.version("v0.1.0")
|
|
|
.author("Isaac Woods")
|
|
|
.about("Compiles and tests ASL files")
|
|
|
- .arg(Arg::new("path").short('p').long("path").required(true).action(ArgAction::Set).value_name("DIR"))
|
|
|
- .arg(Arg::new("no_compile").long("no-compile").action(ArgAction::SetTrue))
|
|
|
+ .arg(Arg::new("no_compile").long("no-compile").action(ArgAction::SetTrue).help("Don't compile asl to aml"))
|
|
|
+ .arg(Arg::new("reset").long("reset").action(ArgAction::SetTrue).help("Clear namespace after each file"))
|
|
|
+ .arg(Arg::new("path").short('p').long("path").required(false).action(ArgAction::Set).value_name("DIR"))
|
|
|
+ .arg(Arg::new("files").action(ArgAction::Append).value_name("FILE.{asl,aml}"))
|
|
|
+ .group(ArgGroup::new("files_list").args(["path", "files"]).required(true))
|
|
|
.get_matches();
|
|
|
|
|
|
- let dir_path = Path::new(matches.get_one::<String>("path").unwrap());
|
|
|
- println!("Running tests in directory: {:?}", dir_path);
|
|
|
+ // Get an initial list of files - may not work correctly on non-UTF8 OsString
|
|
|
+ let files: Vec<String> = if matches.contains_id("path") {
|
|
|
+ let dir_path = Path::new(matches.get_one::<String>("path").unwrap());
|
|
|
+ println!("Running tests in directory: {:?}", dir_path);
|
|
|
+ fs::read_dir(dir_path)?.filter_map(| entry | if entry.is_ok() {
|
|
|
+ Some(entry.unwrap().path().to_string_lossy().to_string())
|
|
|
+ } else {
|
|
|
+ None
|
|
|
+ }).collect()
|
|
|
+ } else {
|
|
|
+ matches.get_many::<String>("files").unwrap_or_default().map(| name | name.to_string()).collect()
|
|
|
+ };
|
|
|
+
|
|
|
+ // Make sure all files exist, propagate error if it occurs
|
|
|
+ files.iter().fold(Ok(()), | result: std::io::Result<()>, file | {
|
|
|
+ let path = Path::new(file);
|
|
|
+ if !path.is_file() {
|
|
|
+ println!("Not a regular file: {}", file);
|
|
|
+ // Get the io error if there is one
|
|
|
+ path.metadata()?;
|
|
|
+ }
|
|
|
+ result
|
|
|
+ })?;
|
|
|
+
|
|
|
+ // Make sure we have the ability to compile ASL -> AML, if user wants it
|
|
|
+ let user_wants_compile = !matches.get_flag("no_compile");
|
|
|
+ let can_compile = user_wants_compile &&
|
|
|
+ // Test if `iasl` is installed, so we can give a good error later if it's not
|
|
|
+ match Command::new("iasl").arg("-v").status() {
|
|
|
+ Ok(exit_status) if exit_status.success() => true,
|
|
|
+ Ok(exit_status) => {
|
|
|
+ panic!("`iasl` exited with unsuccessful status: {:?}", exit_status);
|
|
|
+ },
|
|
|
+ Err(_) => false,
|
|
|
+ };
|
|
|
|
|
|
- if !matches.get_flag("no_compile") {
|
|
|
- let (passed, failed) = compile_asl_files(dir_path)?;
|
|
|
- println!("Compiled {} ASL files: {} passed, {} failed.", passed + failed, passed, failed);
|
|
|
+ let compiled_files: Vec<CompilationOutcome> = files.iter().map(| name | resolve_and_compile(name, can_compile).unwrap()).collect();
|
|
|
+
|
|
|
+ // Check if compilation should have happened but did not
|
|
|
+ if user_wants_compile && compiled_files.iter().any(| outcome | matches!(outcome, CompilationOutcome::NotCompiled(_))) {
|
|
|
+ panic!("`iasl` is not installed, but we want to compile some ASL files! Pass --no-compile, or install `iasl`");
|
|
|
+ }
|
|
|
+ // Report compilation results
|
|
|
+ if user_wants_compile {
|
|
|
+ let (passed, failed) = compiled_files.iter()
|
|
|
+ .fold((0, 0), | (passed, failed), outcome | match outcome {
|
|
|
+ CompilationOutcome::Succeeded(_) => (passed + 1, failed),
|
|
|
+ CompilationOutcome::Failed(_) => (passed, failed + 1),
|
|
|
+ _ => (passed, failed),
|
|
|
+ });
|
|
|
+ if passed + failed > 0 {
|
|
|
+ println!("Compiled {} ASL files: {} passed, {} failed.", passed + failed, passed, failed);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- /*
|
|
|
- * Now, we find all the AML files in the directory, and try to compile them with the `aml`
|
|
|
- * parser.
|
|
|
- */
|
|
|
- let aml_files = fs::read_dir(dir_path)?
|
|
|
- .filter(|entry| entry.is_ok() && entry.as_ref().unwrap().path().extension() == Some(OsStr::new("aml")))
|
|
|
- .map(Result::unwrap);
|
|
|
+ // Make a list of the files we have processed, and skip them if we see them again
|
|
|
+ let mut dedup_list: HashSet<PathBuf> = HashSet::new();
|
|
|
+
|
|
|
+ // Filter down to the final list of AML files
|
|
|
+ let aml_files = compiled_files.iter()
|
|
|
+ .filter_map(| outcome | match outcome {
|
|
|
+ CompilationOutcome::IsAml(path) => Some(path.clone()),
|
|
|
+ CompilationOutcome::Newer(path) => Some(path.clone()),
|
|
|
+ CompilationOutcome::Succeeded(path) => Some(path.clone()),
|
|
|
+ CompilationOutcome::Ignored | CompilationOutcome::Failed(_) | CompilationOutcome::NotCompiled(_) => None,
|
|
|
+ })
|
|
|
+ .filter(| path | if dedup_list.contains(path) {
|
|
|
+ false
|
|
|
+ } else {
|
|
|
+ dedup_list.insert(path.clone());
|
|
|
+ true
|
|
|
+ });
|
|
|
+
|
|
|
+ let user_wants_reset = matches.get_flag("reset");
|
|
|
+ let mut context = AmlContext::new(Box::new(Handler), DebugVerbosity::None);
|
|
|
|
|
|
let (passed, failed) = aml_files.fold((0, 0), |(passed, failed), file_entry| {
|
|
|
- print!("Testing AML file: {:?}... ", file_entry.path());
|
|
|
+ print!("Testing AML file: {:?}... ", file_entry);
|
|
|
std::io::stdout().flush().unwrap();
|
|
|
|
|
|
- let mut file = File::open(file_entry.path()).unwrap();
|
|
|
+ let mut file = File::open(file_entry).unwrap();
|
|
|
let mut contents = Vec::new();
|
|
|
file.read_to_end(&mut contents).unwrap();
|
|
|
|
|
|
const AML_TABLE_HEADER_LENGTH: usize = 36;
|
|
|
- let mut context = AmlContext::new(Box::new(Handler), DebugVerbosity::None);
|
|
|
+
|
|
|
+ if user_wants_reset {
|
|
|
+ context = AmlContext::new(Box::new(Handler), DebugVerbosity::None);
|
|
|
+ }
|
|
|
|
|
|
match context.parse_table(&contents[AML_TABLE_HEADER_LENGTH..]) {
|
|
|
Ok(()) => {
|
|
@@ -78,58 +153,53 @@ fn main() -> std::io::Result<()> {
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
-fn compile_asl_files(dir_path: &Path) -> std::io::Result<(u32, u32)> {
|
|
|
- let mut asl_files = fs::read_dir(dir_path)?
|
|
|
- .filter(|entry| entry.is_ok() && entry.as_ref().unwrap().path().extension() == Some(OsStr::new("asl")))
|
|
|
- .map(Result::unwrap)
|
|
|
- .peekable();
|
|
|
+/// Determine what to do with this file - ignore, compile and parse, or just parse.
|
|
|
+/// If ".aml" does not exist, or if ".asl" is newer, compiles the file.
|
|
|
+/// If the ".aml" file is newer, indicate it is ready to parse.
|
|
|
+fn resolve_and_compile(name: &str, can_compile: bool) -> std::io::Result<CompilationOutcome> {
|
|
|
+ let path = PathBuf::from(name);
|
|
|
|
|
|
- if !asl_files.peek().is_none() {
|
|
|
- // Test if `iasl` is installed, so we can give a good error if it's not
|
|
|
- match Command::new("iasl").arg("-v").status() {
|
|
|
- Ok(exit_status) => if exit_status.success().not() {
|
|
|
- panic!("`iasl` exited with unsuccessfull status: {:?}", exit_status);
|
|
|
- },
|
|
|
- Err(_) => panic!("`iasl` is not installed, but we want to compile some ASL files! Pass --no-compile, or install `iasl`"),
|
|
|
- }
|
|
|
+ // If this file is aml and it exists, it's ready for parsing
|
|
|
+ // metadata() will error if the file does not exist
|
|
|
+ if path.extension() == Some(OsStr::new("aml")) && path.metadata()?.is_file() {
|
|
|
+ return Ok(CompilationOutcome::IsAml(path));
|
|
|
}
|
|
|
|
|
|
- let mut passed = 0;
|
|
|
- let mut failed = 0;
|
|
|
-
|
|
|
- for file in asl_files {
|
|
|
- let aml_path = file.path().with_extension(OsStr::new("aml"));
|
|
|
-
|
|
|
- /*
|
|
|
- * Check if an AML path with a matching last-modified date exists. If it
|
|
|
- * does, we don't need to compile the ASL file again.
|
|
|
- */
|
|
|
- if aml_path.is_file() {
|
|
|
- let asl_last_modified = file.metadata()?.modified()?;
|
|
|
- let aml_last_modified = aml_path.metadata()?.modified()?;
|
|
|
-
|
|
|
- if asl_last_modified <= aml_last_modified {
|
|
|
- continue;
|
|
|
- }
|
|
|
- }
|
|
|
+ // If this file is not asl, it's not interesting. Error if the file does not exist.
|
|
|
+ if path.extension() != Some(OsStr::new("asl")) || !path.metadata()?.is_file() {
|
|
|
+ return Ok(CompilationOutcome::Ignored);
|
|
|
+ }
|
|
|
|
|
|
- // Compile the ASL file using `iasl`
|
|
|
- println!("Compiling file: {}", file.path().to_str().unwrap());
|
|
|
- let output = Command::new("iasl").arg(file.path()).output()?;
|
|
|
+ let aml_path = path.with_extension("aml");
|
|
|
|
|
|
- if output.status.success() {
|
|
|
- passed += 1;
|
|
|
- } else {
|
|
|
- failed += 1;
|
|
|
- println!(
|
|
|
- "Failed to compile ASL file: {}. Output from iasl:\n {}",
|
|
|
- file.path().to_str().unwrap(),
|
|
|
- String::from_utf8_lossy(&output.stderr)
|
|
|
- );
|
|
|
+ if aml_path.is_file() {
|
|
|
+ let asl_last_modified = path.metadata()?.modified()?;
|
|
|
+ let aml_last_modified = aml_path.metadata()?.modified()?;
|
|
|
+ // If the aml is more recent than the asl, use the existing aml
|
|
|
+ // Otherwise continue to compilation
|
|
|
+ if asl_last_modified <= aml_last_modified {
|
|
|
+ return Ok(CompilationOutcome::Newer(aml_path))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- Ok((passed, failed))
|
|
|
+ if !can_compile {
|
|
|
+ return Ok(CompilationOutcome::NotCompiled(path));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Compile the ASL file using `iasl`
|
|
|
+ println!("Compiling file: {}", name);
|
|
|
+ let output = Command::new("iasl").arg(name).output()?;
|
|
|
+
|
|
|
+ if !output.status.success() {
|
|
|
+ println!(
|
|
|
+ "Failed to compile ASL file: {}. Output from iasl:\n {}",
|
|
|
+ name,
|
|
|
+ String::from_utf8_lossy(&output.stderr)
|
|
|
+ );
|
|
|
+ Ok(CompilationOutcome::Failed(path))
|
|
|
+ } else {
|
|
|
+ Ok(CompilationOutcome::Succeeded(aml_path))
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
struct Logger;
|