1
1
use dfsdisc:: dfs;
2
+ use dfsdisc:: support:: * ;
2
3
4
+ use std:: borrow:: Cow ;
3
5
use std:: io;
4
6
use std:: io:: Read ;
5
7
use std:: ffi:: { OsStr , OsString } ;
6
8
use std:: fs:: File ;
9
+ use std:: path:: { Path , PathBuf } ;
10
+ use std:: str:: FromStr ;
7
11
8
12
use gumdrop:: Options ;
9
13
14
+ const XML_NAMESPACE : & ' static str = "http://pearfalse.com/schemas/2021/dfs-manifest" ;
15
+
10
16
#[ derive( Debug , Options ) ]
11
17
struct CliArgs {
12
18
#[ options( help = "print this" ) ]
@@ -21,7 +27,7 @@ enum Subcommand {
21
27
#[ options( help = "dump the contents of a disc image" ) ]
22
28
Probe ( ScProbe ) ,
23
29
#[ options( help = "build a disc image from source files and a manifest" ) ]
24
- Build ( ScBuild ) ,
30
+ Pack ( ScPack ) ,
25
31
#[ options( help = "unpack a disc image into separate files (and a manifest)" ) ]
26
32
Unpack ( ScUnpack ) ,
27
33
}
@@ -36,7 +42,7 @@ struct ScProbe {
36
42
}
37
43
38
44
#[ derive( Debug , Options ) ]
39
- struct ScBuild {
45
+ struct ScPack {
40
46
#[ options( ) ]
41
47
help : bool ,
42
48
@@ -64,10 +70,7 @@ fn main() {
64
70
let r = match args. command {
65
71
Some ( Subcommand :: Probe ( ref probe) ) => sc_probe ( & * probe. image_file ) ,
66
72
Some ( Subcommand :: Unpack ( ref unpack) ) => sc_unpack ( & * unpack. image_file , & * unpack. output ) ,
67
- Some ( Subcommand :: Build ( _) ) => {
68
- eprintln ! ( "not implemented, sorry" ) ;
69
- Ok ( ( ) )
70
- } ,
73
+ Some ( Subcommand :: Pack ( ref pack) ) => sc_pack ( pack. manifest . as_ref ( ) , pack. output_file . as_ref ( ) ) ,
71
74
None => {
72
75
eprintln ! ( "{}" , args. self_usage( ) ) ;
73
76
std:: process:: exit ( 1 ) ;
@@ -84,6 +87,12 @@ enum CliError {
84
87
InputTooLarge ,
85
88
Io ( io:: Error ) ,
86
89
BadImage ( dfs:: DFSError ) ,
90
+ XmlParseError ( xml:: reader:: Error ) ,
91
+ XmlDfsError ( Cow < ' static , str > ) ,
92
+ }
93
+
94
+ impl < O > From < CliError > for Result < O , CliError > {
95
+ fn from ( src : CliError ) -> Self { Err ( src) }
87
96
}
88
97
89
98
impl From < io:: Error > for CliError {
@@ -92,6 +101,28 @@ impl From<io::Error> for CliError {
92
101
}
93
102
}
94
103
104
+ impl From < dfs:: DFSError > for CliError {
105
+ fn from ( src : dfs:: DFSError ) -> Self {
106
+ Self :: BadImage ( src)
107
+ }
108
+ }
109
+
110
+ impl From < xml:: reader:: Error > for CliError {
111
+ fn from ( src : xml:: reader:: Error ) -> Self {
112
+ Self :: XmlParseError ( src)
113
+ }
114
+ }
115
+
116
+
117
+ type CliResult = Result < ( ) , CliError > ;
118
+
119
+
120
+ macro_rules! warn {
121
+ ( $format: literal $( , $arg: expr) * ) => {
122
+ eprintln!( concat!( "warning: " , $format) $( , & ( $arg) ) * )
123
+ } ;
124
+ }
125
+
95
126
96
127
fn read_image ( path : & OsStr ) -> Result < Vec < u8 > , CliError > {
97
128
let mut data = Vec :: new ( ) ;
@@ -129,8 +160,7 @@ fn sc_probe(image_path: &OsStr) -> Result<(), CliError> {
129
160
Ok ( ( ) )
130
161
}
131
162
132
- fn sc_unpack ( image_path : & OsStr , target : & OsStr ) -> Result < ( ) , CliError > {
133
- use std:: borrow:: Cow ;
163
+ fn sc_unpack ( image_path : & OsStr , target : & OsStr ) -> CliResult {
134
164
use std:: fs;
135
165
use std:: io:: Write ;
136
166
use ascii:: { AsciiChar , AsciiStr } ;
@@ -144,20 +174,19 @@ fn sc_unpack(image_path: &OsStr, target: &OsStr) -> Result<(), CliError> {
144
174
const SEPARATOR : AsciiChar = AsciiChar :: Slash ;
145
175
let root_namespace = Namespace ( {
146
176
let mut map = std:: collections:: BTreeMap :: new ( ) ;
147
- map. insert ( String :: from ( xml:: namespace:: NS_NO_PREFIX ) , String :: from ( "http://pearfalse.com/schemas/2021/dfs-manifest" ) ) ;
177
+ map. insert ( String :: from ( xml:: namespace:: NS_NO_PREFIX ) , String :: from ( XML_NAMESPACE ) ) ;
148
178
map
149
179
} ) ;
150
180
151
181
fs:: DirBuilder :: new ( )
152
182
. recursive ( true )
153
183
. create ( target)
154
- . map_err ( CliError :: Io ) ?;
184
+ ?;
155
185
156
186
std:: env:: set_current_dir ( target) ?;
157
187
158
188
let image_data = read_image ( image_path) ?;
159
- let disc = dfs:: Disc :: from_bytes ( & image_data)
160
- . map_err ( CliError :: BadImage ) ?;
189
+ let disc = dfs:: Disc :: from_bytes ( & image_data) ?;
161
190
162
191
let dirs: std:: collections:: HashSet < dfsdisc:: support:: AsciiPrintingChar >
163
192
= disc. files ( ) . map ( |f| f. dir ( ) ) . collect ( ) ;
@@ -175,7 +204,7 @@ fn sc_unpack(image_path: &OsStr, target: &OsStr) -> Result<(), CliError> {
175
204
176
205
fs:: File :: create ( <& AsciiStr >:: from ( & * file_path_buf) . as_str ( ) )
177
206
. and_then ( |mut f| f. write_all ( file. content ( ) ) )
178
- . map_err ( CliError :: Io ) ?;
207
+ ?;
179
208
}
180
209
181
210
// create manifest file
@@ -185,7 +214,7 @@ fn sc_unpack(image_path: &OsStr, target: &OsStr) -> Result<(), CliError> {
185
214
perform_indent : true ,
186
215
pad_self_closing : false ,
187
216
.. Default :: default ( )
188
- } ) ) . map_err ( CliError :: Io ) ?;
217
+ } ) ) ?;
189
218
190
219
// begin manifest
191
220
match ( || {
@@ -198,6 +227,7 @@ fn sc_unpack(image_path: &OsStr, target: &OsStr) -> Result<(), CliError> {
198
227
// <dfsdisc>
199
228
let attr_cycle = format ! ( "{}" , disc. cycle( ) . into_u8( ) ) ;
200
229
let start_attrs = [
230
+ Attribute :: new ( XmlName :: local ( "name" ) , disc. name ( ) . as_str ( ) ) ,
201
231
// hardcoding to 100KiB 40T DFS for now. TODO fix this, obviously
202
232
Attribute :: new ( XmlName :: local ( "sides" ) , "1" ) ,
203
233
Attribute :: new ( XmlName :: local ( "tracks" ) , "40" ) ,
@@ -255,8 +285,8 @@ fn sc_unpack(image_path: &OsStr, target: &OsStr) -> Result<(), CliError> {
255
285
Err ( _e) => panic ! ( "Unexpected XML error: {:?}" , _e) ,
256
286
} ;
257
287
258
- manifest. into_inner ( ) . write_all ( b"\n " )
259
- . map_err ( CliError :: Io )
288
+ manifest. into_inner ( ) . write_all ( b"\n " ) ? ;
289
+ Ok ( ( ) )
260
290
}
261
291
262
292
trait FileHeuristics {
@@ -276,3 +306,88 @@ impl FileHeuristics for [u8] {
276
306
self . len ( ) >= 2 && [ self [ 0 ] , self [ 1 ] ] == [ 0xd , 0x0 ]
277
307
}
278
308
}
309
+
310
+
311
+ fn sc_pack ( manifest_path : & Path , image_path : & Path ) -> CliResult {
312
+ use xml:: reader:: XmlEvent ;
313
+
314
+ fn dfs_error ( s : & ' static str ) -> CliError {
315
+ CliError :: XmlDfsError ( Cow :: Borrowed ( s) )
316
+ }
317
+
318
+ let root = std:: fs:: canonicalize ( manifest_path)
319
+ . map_err ( CliError :: Io ) ?;
320
+
321
+ // open and parse manifest file
322
+ let mut reader = File :: open ( & * root)
323
+ . map ( xml:: EventReader :: new) ?;
324
+
325
+ // CD to path folder
326
+ std:: env:: set_current_dir ( root. parent ( ) . unwrap ( ) ) ?;
327
+
328
+ // load files
329
+
330
+ // - attempt to get root element
331
+ match reader. next ( ) ? {
332
+ XmlEvent :: StartDocument { version : _, encoding : _, standalone : _ } => { } ,
333
+ _ => return Err ( CliError :: XmlDfsError ( Cow :: Borrowed ( "expected XML document start" ) ) ) ,
334
+ } ;
335
+ let mut dfs_image = match reader. next ( ) ? {
336
+ XmlEvent :: StartElement { name : _, ref attributes, namespace : _} => {
337
+ let xmlns = attributes. local_attr ( "xmlns" ) ;
338
+ match xmlns. map ( |attr| attr. value . as_str ( ) ) {
339
+ Some ( XML_NAMESPACE ) => { } ,
340
+ Some ( _other) => warn ! ( "document has unexpected XML namespace; wanted '{}'" , XML_NAMESPACE ) ,
341
+ None => warn ! ( "document has no XML namespace; expected '{}'" , XML_NAMESPACE ) ,
342
+ } ;
343
+
344
+ let mut disc = dfs:: Disc :: new ( ) ;
345
+
346
+ if let Some ( name) = attributes. local_attr ( "name" ) {
347
+ let ap_name = AsciiPrintingStr :: try_from_str ( name. value . as_str ( ) )
348
+ . map_err ( |_| dfs_error ( "invalid disc name" ) ) ?;
349
+ disc. set_name ( ap_name) . map_err ( |e| CliError :: XmlDfsError ( Cow :: Owned (
350
+ format ! ( "disc name has non-printing or non-ASCII character at position {}" , e. position( ) )
351
+ ) ) ) ?;
352
+ }
353
+
354
+ if let Some ( cycle) = attributes. local_attr ( "cycle" ) {
355
+ * disc. cycle_mut ( ) = u8:: from_str ( cycle. value . as_str ( ) ) . ok ( )
356
+ . and_then ( |r#u8| BCD :: from_hex ( r#u8) . ok ( ) )
357
+ . ok_or_else ( || dfs_error ( "incorrect cycle count; not valid 2-digit BCD" ) ) ?;
358
+ }
359
+
360
+ if let Some ( boot_option) = attributes. local_attr ( "boot" ) {
361
+ match dfs:: BootOption :: from_str ( boot_option. value . as_str ( ) ) {
362
+ Ok ( bo) => * disc. boot_option_mut ( ) = bo,
363
+ Err ( _) => return Err ( dfs_error ( "invalid boot option" ) )
364
+ } ;
365
+ }
366
+
367
+ Ok ( disc)
368
+ } ,
369
+ _ => Err ( dfs_error ( "missing <dfsdisc> start element" ) ) ,
370
+ } ?;
371
+
372
+ // attempt to combine into disc image
373
+
374
+
375
+ // write it out to target
376
+
377
+ Ok ( ( ) )
378
+ }
379
+
380
+ trait AttributesExt {
381
+ type Attr ;
382
+
383
+ fn local_attr ( & self , local_name : & str ) -> Option < & Self :: Attr > ;
384
+ }
385
+
386
+ impl AttributesExt for [ xml:: attribute:: OwnedAttribute ] {
387
+ type Attr = xml:: attribute:: OwnedAttribute ;
388
+
389
+ fn local_attr ( & self , local_name : & str ) -> Option < & Self :: Attr > {
390
+ let target = xml:: name:: Name :: local ( local_name) ;
391
+ self . iter ( ) . find ( move |attr| attr. name . borrow ( ) == target)
392
+ }
393
+ }
0 commit comments