10
10
*/
11
11
12
12
use aml:: { AmlContext , DebugVerbosity } ;
13
- use clap:: { Arg , ArgAction } ;
13
+ use clap:: { Arg , ArgGroup , ArgAction } ;
14
14
use std:: {
15
15
ffi:: OsStr ,
16
16
fs:: { self , File } ,
17
17
io:: { Read , Write } ,
18
- ops:: Not ,
19
- path:: Path ,
18
+ path:: { Path , PathBuf } ,
20
19
process:: Command ,
20
+ collections:: HashSet ,
21
21
} ;
22
22
23
+ enum CompilationOutcome {
24
+ Ignored ,
25
+ IsAml ( PathBuf ) ,
26
+ Newer ( PathBuf ) ,
27
+ NotCompiled ( PathBuf ) ,
28
+ Failed ( PathBuf ) ,
29
+ Succeeded ( PathBuf ) ,
30
+ }
31
+
23
32
fn main ( ) -> std:: io:: Result < ( ) > {
24
33
log:: set_logger ( & Logger ) . unwrap ( ) ;
25
34
log:: set_max_level ( log:: LevelFilter :: Trace ) ;
@@ -28,36 +37,102 @@ fn main() -> std::io::Result<()> {
28
37
. version ( "v0.1.0" )
29
38
. author ( "Isaac Woods" )
30
39
. about ( "Compiles and tests ASL files" )
31
- . arg ( Arg :: new ( "path" ) . short ( 'p' ) . long ( "path" ) . required ( true ) . action ( ArgAction :: Set ) . value_name ( "DIR" ) )
32
- . arg ( Arg :: new ( "no_compile" ) . long ( "no-compile" ) . action ( ArgAction :: SetTrue ) )
40
+ . arg ( Arg :: new ( "no_compile" ) . long ( "no-compile" ) . action ( ArgAction :: SetTrue ) . help ( "Don't compile asl to aml" ) )
41
+ . arg ( Arg :: new ( "reset" ) . long ( "reset" ) . action ( ArgAction :: SetTrue ) . help ( "Clear namespace after each file" ) )
42
+ . arg ( Arg :: new ( "path" ) . short ( 'p' ) . long ( "path" ) . required ( false ) . action ( ArgAction :: Set ) . value_name ( "DIR" ) )
43
+ . arg ( Arg :: new ( "files" ) . action ( ArgAction :: Append ) . value_name ( "FILE.{asl,aml}" ) )
44
+ . group ( ArgGroup :: new ( "files_list" ) . args ( [ "path" , "files" ] ) . required ( true ) )
33
45
. get_matches ( ) ;
34
46
35
- let dir_path = Path :: new ( matches. get_one :: < String > ( "path" ) . unwrap ( ) ) ;
36
- println ! ( "Running tests in directory: {:?}" , dir_path) ;
47
+ // Get an initial list of files - may not work correctly on non-UTF8 OsString
48
+ let files: Vec < String > = if matches. contains_id ( "path" ) {
49
+ let dir_path = Path :: new ( matches. get_one :: < String > ( "path" ) . unwrap ( ) ) ;
50
+ println ! ( "Running tests in directory: {:?}" , dir_path) ;
51
+ fs:: read_dir ( dir_path) ?. filter_map ( | entry | if entry. is_ok ( ) {
52
+ Some ( entry. unwrap ( ) . path ( ) . to_string_lossy ( ) . to_string ( ) )
53
+ } else {
54
+ None
55
+ } ) . collect ( )
56
+ } else {
57
+ matches. get_many :: < String > ( "files" ) . unwrap_or_default ( ) . map ( | name | name. to_string ( ) ) . collect ( )
58
+ } ;
59
+
60
+ // Make sure all files exist, propagate error if it occurs
61
+ files. iter ( ) . fold ( Ok ( ( ) ) , | result : std:: io:: Result < ( ) > , file | {
62
+ let path = Path :: new ( file) ;
63
+ if !path. is_file ( ) {
64
+ println ! ( "Not a regular file: {}" , file) ;
65
+ // Get the io error if there is one
66
+ path. metadata ( ) ?;
67
+ }
68
+ result
69
+ } ) ?;
70
+
71
+ // Make sure we have the ability to compile ASL -> AML, if user wants it
72
+ let user_wants_compile = !matches. get_flag ( "no_compile" ) ;
73
+ let can_compile = user_wants_compile &&
74
+ // Test if `iasl` is installed, so we can give a good error later if it's not
75
+ match Command :: new ( "iasl" ) . arg ( "-v" ) . status ( ) {
76
+ Ok ( exit_status) if exit_status. success ( ) => true ,
77
+ Ok ( exit_status) => {
78
+ panic ! ( "`iasl` exited with unsuccessful status: {:?}" , exit_status) ;
79
+ } ,
80
+ Err ( _) => false ,
81
+ } ;
37
82
38
- if !matches. get_flag ( "no_compile" ) {
39
- let ( passed, failed) = compile_asl_files ( dir_path) ?;
40
- println ! ( "Compiled {} ASL files: {} passed, {} failed." , passed + failed, passed, failed) ;
83
+ let compiled_files: Vec < CompilationOutcome > = files. iter ( ) . map ( | name | resolve_and_compile ( name, can_compile) . unwrap ( ) ) . collect ( ) ;
84
+
85
+ // Check if compilation should have happened but did not
86
+ if user_wants_compile && compiled_files. iter ( ) . any ( | outcome | matches ! ( outcome, CompilationOutcome :: NotCompiled ( _) ) ) {
87
+ panic ! ( "`iasl` is not installed, but we want to compile some ASL files! Pass --no-compile, or install `iasl`" ) ;
88
+ }
89
+ // Report compilation results
90
+ if user_wants_compile {
91
+ let ( passed, failed) = compiled_files. iter ( )
92
+ . fold ( ( 0 , 0 ) , | ( passed, failed) , outcome | match outcome {
93
+ CompilationOutcome :: Succeeded ( _) => ( passed + 1 , failed) ,
94
+ CompilationOutcome :: Failed ( _) => ( passed, failed + 1 ) ,
95
+ _ => ( passed, failed) ,
96
+ } ) ;
97
+ if passed + failed > 0 {
98
+ println ! ( "Compiled {} ASL files: {} passed, {} failed." , passed + failed, passed, failed) ;
99
+ }
41
100
}
42
101
43
- /*
44
- * Now, we find all the AML files in the directory, and try to compile them with the `aml`
45
- * parser.
46
- */
47
- let aml_files = fs:: read_dir ( dir_path) ?
48
- . filter ( |entry| entry. is_ok ( ) && entry. as_ref ( ) . unwrap ( ) . path ( ) . extension ( ) == Some ( OsStr :: new ( "aml" ) ) )
49
- . map ( Result :: unwrap) ;
102
+ // Make a list of the files we have processed, and skip them if we see them again
103
+ let mut dedup_list: HashSet < PathBuf > = HashSet :: new ( ) ;
104
+
105
+ // Filter down to the final list of AML files
106
+ let aml_files = compiled_files. iter ( )
107
+ . filter_map ( | outcome | match outcome {
108
+ CompilationOutcome :: IsAml ( path) => Some ( path. clone ( ) ) ,
109
+ CompilationOutcome :: Newer ( path) => Some ( path. clone ( ) ) ,
110
+ CompilationOutcome :: Succeeded ( path) => Some ( path. clone ( ) ) ,
111
+ CompilationOutcome :: Ignored | CompilationOutcome :: Failed ( _) | CompilationOutcome :: NotCompiled ( _) => None ,
112
+ } )
113
+ . filter ( | path | if dedup_list. contains ( path) {
114
+ false
115
+ } else {
116
+ dedup_list. insert ( path. clone ( ) ) ;
117
+ true
118
+ } ) ;
119
+
120
+ let user_wants_reset = matches. get_flag ( "reset" ) ;
121
+ let mut context = AmlContext :: new ( Box :: new ( Handler ) , DebugVerbosity :: None ) ;
50
122
51
123
let ( passed, failed) = aml_files. fold ( ( 0 , 0 ) , |( passed, failed) , file_entry| {
52
- print ! ( "Testing AML file: {:?}... " , file_entry. path ( ) ) ;
124
+ print ! ( "Testing AML file: {:?}... " , file_entry) ;
53
125
std:: io:: stdout ( ) . flush ( ) . unwrap ( ) ;
54
126
55
- let mut file = File :: open ( file_entry. path ( ) ) . unwrap ( ) ;
127
+ let mut file = File :: open ( file_entry) . unwrap ( ) ;
56
128
let mut contents = Vec :: new ( ) ;
57
129
file. read_to_end ( & mut contents) . unwrap ( ) ;
58
130
59
131
const AML_TABLE_HEADER_LENGTH : usize = 36 ;
60
- let mut context = AmlContext :: new ( Box :: new ( Handler ) , DebugVerbosity :: None ) ;
132
+
133
+ if user_wants_reset {
134
+ context = AmlContext :: new ( Box :: new ( Handler ) , DebugVerbosity :: None ) ;
135
+ }
61
136
62
137
match context. parse_table ( & contents[ AML_TABLE_HEADER_LENGTH ..] ) {
63
138
Ok ( ( ) ) => {
@@ -78,58 +153,53 @@ fn main() -> std::io::Result<()> {
78
153
Ok ( ( ) )
79
154
}
80
155
81
- fn compile_asl_files ( dir_path : & Path ) -> std :: io :: Result < ( u32 , u32 ) > {
82
- let mut asl_files = fs :: read_dir ( dir_path ) ?
83
- . filter ( |entry| entry . is_ok ( ) && entry . as_ref ( ) . unwrap ( ) . path ( ) . extension ( ) == Some ( OsStr :: new ( "asl" ) ) )
84
- . map ( Result :: unwrap )
85
- . peekable ( ) ;
156
+ /// Determine what to do with this file - ignore, compile and parse, or just parse.
157
+ /// If ".aml" does not exist, or if ".asl" is newer, compiles the file.
158
+ /// If the ".aml" file is newer, indicate it is ready to parse.
159
+ fn resolve_and_compile ( name : & str , can_compile : bool ) -> std :: io :: Result < CompilationOutcome > {
160
+ let path = PathBuf :: from ( name ) ;
86
161
87
- if !asl_files. peek ( ) . is_none ( ) {
88
- // Test if `iasl` is installed, so we can give a good error if it's not
89
- match Command :: new ( "iasl" ) . arg ( "-v" ) . status ( ) {
90
- Ok ( exit_status) => if exit_status. success ( ) . not ( ) {
91
- panic ! ( "`iasl` exited with unsuccessfull status: {:?}" , exit_status) ;
92
- } ,
93
- Err ( _) => panic ! ( "`iasl` is not installed, but we want to compile some ASL files! Pass --no-compile, or install `iasl`" ) ,
94
- }
162
+ // If this file is aml and it exists, it's ready for parsing
163
+ // metadata() will error if the file does not exist
164
+ if path. extension ( ) == Some ( OsStr :: new ( "aml" ) ) && path. metadata ( ) ?. is_file ( ) {
165
+ return Ok ( CompilationOutcome :: IsAml ( path) ) ;
95
166
}
96
167
97
- let mut passed = 0 ;
98
- let mut failed = 0 ;
99
-
100
- for file in asl_files {
101
- let aml_path = file. path ( ) . with_extension ( OsStr :: new ( "aml" ) ) ;
102
-
103
- /*
104
- * Check if an AML path with a matching last-modified date exists. If it
105
- * does, we don't need to compile the ASL file again.
106
- */
107
- if aml_path. is_file ( ) {
108
- let asl_last_modified = file. metadata ( ) ?. modified ( ) ?;
109
- let aml_last_modified = aml_path. metadata ( ) ?. modified ( ) ?;
110
-
111
- if asl_last_modified <= aml_last_modified {
112
- continue ;
113
- }
114
- }
168
+ // If this file is not asl, it's not interesting. Error if the file does not exist.
169
+ if path. extension ( ) != Some ( OsStr :: new ( "asl" ) ) || !path. metadata ( ) ?. is_file ( ) {
170
+ return Ok ( CompilationOutcome :: Ignored ) ;
171
+ }
115
172
116
- // Compile the ASL file using `iasl`
117
- println ! ( "Compiling file: {}" , file. path( ) . to_str( ) . unwrap( ) ) ;
118
- let output = Command :: new ( "iasl" ) . arg ( file. path ( ) ) . output ( ) ?;
173
+ let aml_path = path. with_extension ( "aml" ) ;
119
174
120
- if output. status . success ( ) {
121
- passed += 1 ;
122
- } else {
123
- failed += 1 ;
124
- println ! (
125
- "Failed to compile ASL file: {}. Output from iasl:\n {}" ,
126
- file. path( ) . to_str( ) . unwrap( ) ,
127
- String :: from_utf8_lossy( & output. stderr)
128
- ) ;
175
+ if aml_path. is_file ( ) {
176
+ let asl_last_modified = path. metadata ( ) ?. modified ( ) ?;
177
+ let aml_last_modified = aml_path. metadata ( ) ?. modified ( ) ?;
178
+ // If the aml is more recent than the asl, use the existing aml
179
+ // Otherwise continue to compilation
180
+ if asl_last_modified <= aml_last_modified {
181
+ return Ok ( CompilationOutcome :: Newer ( aml_path) )
129
182
}
130
183
}
131
184
132
- Ok ( ( passed, failed) )
185
+ if !can_compile {
186
+ return Ok ( CompilationOutcome :: NotCompiled ( path) ) ;
187
+ }
188
+
189
+ // Compile the ASL file using `iasl`
190
+ println ! ( "Compiling file: {}" , name) ;
191
+ let output = Command :: new ( "iasl" ) . arg ( name) . output ( ) ?;
192
+
193
+ if !output. status . success ( ) {
194
+ println ! (
195
+ "Failed to compile ASL file: {}. Output from iasl:\n {}" ,
196
+ name,
197
+ String :: from_utf8_lossy( & output. stderr)
198
+ ) ;
199
+ Ok ( CompilationOutcome :: Failed ( path) )
200
+ } else {
201
+ Ok ( CompilationOutcome :: Succeeded ( aml_path) )
202
+ }
133
203
}
134
204
135
205
struct Logger ;
0 commit comments