Преглед на файлове

aml_tester: Add positional file arguments, in-order parsing and shared namespace

Ron Williams преди 2 години
родител
ревизия
0b3a35d725
променени са 1 файла, в които са добавени 134 реда и са изтрити 64 реда
  1. 134 64
      aml_tester/src/main.rs

+ 134 - 64
aml_tester/src/main.rs

@@ -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;