Skip to content

Commit 7156d41

Browse files
committed
Include disc name in generate manifest
1 parent ff95768 commit 7156d41

File tree

5 files changed

+167
-21
lines changed

5 files changed

+167
-21
lines changed

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@ path = "src/lib.rs"
1212
name = "dfs"
1313
path = "src/main.rs"
1414

15+
[profile.release]
16+
lto = true
17+
opt-level = 'z'
18+
1519
[dependencies]
1620
ascii = ">= 1.0"
1721
xml-rs = "0.8.4"
1822
gumdrop = "0.8"
1923
arrayvec = "0.7.1"
24+
enum-utils = "0.1.2"

src/dfs/disc.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use crate::support::*;
99

1010
/// What a DFS-supporting OS would do with a [`Disc`](./struct.Disc.html)
1111
/// found in the drive during a Shift-BREAK.
12-
#[derive(Debug, PartialEq, Clone, Copy)]
12+
#[derive(Debug, PartialEq, Clone, Copy, enum_utils::FromStr)]
13+
#[enumeration(case_insensitive)]
1314
#[repr(u8)]
1415
pub enum BootOption {
1516
None = 0,
@@ -57,6 +58,8 @@ pub type DiscName = AsciiName<12>;
5758
pub struct Disc<'d> {
5859
_data: PhantomData<&'d [u8]>,
5960

61+
// TODO: hold tracks count
62+
6063
name: DiscName,
6164
boot_option: BootOption,
6265
cycle: BCD,
@@ -77,10 +80,11 @@ impl<'d> Disc<'d> {
7780
}
7881

7982
pub fn name(&self) -> &AsciiStr { self.name.as_ascii_str() }
80-
pub fn set_name(&mut self, new_name: &AsciiPrintingStr) -> Result<(), DFSError> {
81-
self.name = DiscName::try_from(new_name.as_ascii_str().as_slice())
82-
.map_err(|e| DFSError::InputTooLarge(e.position()))?;
83-
Ok(())
83+
pub fn set_name(&mut self, new_name: &AsciiPrintingStr) -> Result<(), AsciiNameError> {
84+
match AsciiName::try_from(new_name) {
85+
Ok(n) => { self.name = n; Ok(()) },
86+
Err(e) => Err(e),
87+
}
8488
}
8589

8690
pub fn boot_option(&self) -> BootOption { self.boot_option }

src/dfs/file.rs

+7
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ impl<'d> File<'d> {
4444
self.name.name.as_ascii_str()
4545
}
4646

47+
pub fn set_name(&mut self, new_name: &AsciiPrintingStr) -> Result<(), AsciiNameError> {
48+
match AsciiName::<7>::try_from(new_name) {
49+
Ok(n) => { self.name.name = n; Ok(()) },
50+
Err(e) => Err(e),
51+
}
52+
}
53+
4754
pub fn load_addr(&self) -> u32 { self.load_addr }
4855
pub fn exec_addr(&self) -> u32 { self.exec_addr }
4956
pub fn is_locked(&self) -> bool { self.is_locked }

src/main.rs

+131-16
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
use dfsdisc::dfs;
2+
use dfsdisc::support::*;
23

4+
use std::borrow::Cow;
35
use std::io;
46
use std::io::Read;
57
use std::ffi::{OsStr,OsString};
68
use std::fs::File;
9+
use std::path::{Path,PathBuf};
10+
use std::str::FromStr;
711

812
use gumdrop::Options;
913

14+
const XML_NAMESPACE: &'static str = "http://pearfalse.com/schemas/2021/dfs-manifest";
15+
1016
#[derive(Debug, Options)]
1117
struct CliArgs {
1218
#[options(help = "print this")]
@@ -21,7 +27,7 @@ enum Subcommand {
2127
#[options(help = "dump the contents of a disc image")]
2228
Probe(ScProbe),
2329
#[options(help = "build a disc image from source files and a manifest")]
24-
Build(ScBuild),
30+
Pack(ScPack),
2531
#[options(help = "unpack a disc image into separate files (and a manifest)")]
2632
Unpack(ScUnpack),
2733
}
@@ -36,7 +42,7 @@ struct ScProbe {
3642
}
3743

3844
#[derive(Debug, Options)]
39-
struct ScBuild {
45+
struct ScPack {
4046
#[options()]
4147
help: bool,
4248

@@ -64,10 +70,7 @@ fn main() {
6470
let r = match args.command {
6571
Some(Subcommand::Probe(ref probe)) => sc_probe(&*probe.image_file),
6672
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()),
7174
None => {
7275
eprintln!("{}", args.self_usage());
7376
std::process::exit(1);
@@ -84,6 +87,12 @@ enum CliError {
8487
InputTooLarge,
8588
Io(io::Error),
8689
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) }
8796
}
8897

8998
impl From<io::Error> for CliError {
@@ -92,6 +101,28 @@ impl From<io::Error> for CliError {
92101
}
93102
}
94103

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+
95126

96127
fn read_image(path: &OsStr) -> Result<Vec<u8>, CliError> {
97128
let mut data = Vec::new();
@@ -129,8 +160,7 @@ fn sc_probe(image_path: &OsStr) -> Result<(), CliError> {
129160
Ok(())
130161
}
131162

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 {
134164
use std::fs;
135165
use std::io::Write;
136166
use ascii::{AsciiChar,AsciiStr};
@@ -144,20 +174,19 @@ fn sc_unpack(image_path: &OsStr, target: &OsStr) -> Result<(), CliError> {
144174
const SEPARATOR: AsciiChar = AsciiChar::Slash;
145175
let root_namespace = Namespace({
146176
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));
148178
map
149179
});
150180

151181
fs::DirBuilder::new()
152182
.recursive(true)
153183
.create(target)
154-
.map_err(CliError::Io)?;
184+
?;
155185

156186
std::env::set_current_dir(target)?;
157187

158188
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)?;
161190

162191
let dirs: std::collections::HashSet<dfsdisc::support::AsciiPrintingChar>
163192
= disc.files().map(|f| f.dir()).collect();
@@ -175,7 +204,7 @@ fn sc_unpack(image_path: &OsStr, target: &OsStr) -> Result<(), CliError> {
175204

176205
fs::File::create(<&AsciiStr>::from(&*file_path_buf).as_str())
177206
.and_then(|mut f| f.write_all(file.content()))
178-
.map_err(CliError::Io)?;
207+
?;
179208
}
180209

181210
// create manifest file
@@ -185,7 +214,7 @@ fn sc_unpack(image_path: &OsStr, target: &OsStr) -> Result<(), CliError> {
185214
perform_indent: true,
186215
pad_self_closing: false,
187216
.. Default::default()
188-
})).map_err(CliError::Io)?;
217+
}))?;
189218

190219
// begin manifest
191220
match (|| {
@@ -198,6 +227,7 @@ fn sc_unpack(image_path: &OsStr, target: &OsStr) -> Result<(), CliError> {
198227
// <dfsdisc>
199228
let attr_cycle = format!("{}", disc.cycle().into_u8());
200229
let start_attrs = [
230+
Attribute::new(XmlName::local("name"), disc.name().as_str()),
201231
// hardcoding to 100KiB 40T DFS for now. TODO fix this, obviously
202232
Attribute::new(XmlName::local("sides"), "1"),
203233
Attribute::new(XmlName::local("tracks"), "40"),
@@ -255,8 +285,8 @@ fn sc_unpack(image_path: &OsStr, target: &OsStr) -> Result<(), CliError> {
255285
Err(_e) => panic!("Unexpected XML error: {:?}", _e),
256286
};
257287

258-
manifest.into_inner().write_all(b"\n")
259-
.map_err(CliError::Io)
288+
manifest.into_inner().write_all(b"\n")?;
289+
Ok(())
260290
}
261291

262292
trait FileHeuristics {
@@ -276,3 +306,88 @@ impl FileHeuristics for [u8] {
276306
self.len() >= 2 && [self[0], self[1]] == [0xd, 0x0]
277307
}
278308
}
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+
}

src/support.rs

+15
Original file line numberDiff line numberDiff line change
@@ -190,13 +190,28 @@ impl From<AsciiPrintingChar> for AsciiChar {
190190
}
191191
}
192192

193+
impl ascii::ToAsciiChar for AsciiPrintingChar {
194+
unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { self.0 }
195+
196+
fn to_ascii_char(self) -> Result<AsciiChar, ascii::ToAsciiCharError> { Ok(self.0) }
197+
}
198+
193199
pub type AsciiPrintingStr = [AsciiPrintingChar];
194200

195201
pub trait AsciiPrintingSlice {
202+
fn try_from_str(src: &str) -> Result<&AsciiPrintingStr, AsciiPrintingCharError>;
196203
fn as_ascii_str(&self) -> &AsciiStr;
197204
}
198205

199206
impl AsciiPrintingSlice for AsciiPrintingStr {
207+
fn try_from_str(src: &str) -> Result<&AsciiPrintingStr, AsciiPrintingCharError> {
208+
for &ch in src.as_bytes().iter() {
209+
AsciiPrintingChar::from(ch)?;
210+
}
211+
212+
Ok(unsafe { &*(src as *const str as *const [AsciiPrintingChar]) })
213+
}
214+
200215
fn as_ascii_str(&self) -> &AsciiStr {
201216
unsafe { &*(self as *const AsciiPrintingStr as *const AsciiStr) }
202217
}

0 commit comments

Comments
 (0)