pub mod mkdsk;
pub mod put;
pub mod get;
pub mod get_img;
pub mod put_img;
pub mod stat;
pub mod modify;
pub mod langx;
pub mod completions;
pub mod ezcopy;
use std::str::FromStr;
use std::io::Read;
use crate::img::tracks::DiskFormat;
use crate::img::{Track,Sector};
use crate::DYNERR;
pub fn get_fmt(cmd: &clap::ArgMatches) -> Result<Option<DiskFormat>,Box<dyn std::error::Error>> {
match cmd.get_one::<String>("pro") {
Some(path_or_json) => {
match json::parse(path_or_json) {
Ok(_) => Ok(Some(DiskFormat::from_json(path_or_json)?)),
Err(_) => {
let json_str = std::fs::read_to_string(path_or_json)?;
Ok(Some(DiskFormat::from_json(&json_str)?))
}
}
},
None => Ok(None)
}
}
#[derive(thiserror::Error,Debug)]
pub enum CommandError {
#[error("Item type is not yet supported")]
UnsupportedItemType,
#[error("Item type is unknown")]
UnknownItemType,
#[error("Command could not be interpreted")]
InvalidCommand,
#[error("One of the parameters was out of range")]
OutOfRange,
#[error("Input source is not supported")]
UnsupportedFormat,
#[error("Input source could not be interpreted")]
UnknownFormat,
#[error("File not found")]
FileNotFound,
#[error("Key not found")]
KeyNotFound,
}
#[derive(PartialEq,Clone,Copy)]
pub enum ItemType {
FileImage,
Automatic,
AppleSingle,
Raw,
Binary,
Text,
Records,
ApplesoftText,
IntegerText,
MerlinText,
ApplesoftTokens,
IntegerTokens,
MerlinTokens,
ApplesoftVars,
IntegerVars,
Block,
Track,
Sector,
RawTrack,
System,
Metadata,
}
impl FromStr for ItemType {
type Err = CommandError;
fn from_str(s: &str) -> Result<Self,Self::Err> {
match s {
"any" => Ok(Self::FileImage),
"auto" => Ok(Self::Automatic),
"as" => Ok(Self::AppleSingle),
"raw" => Ok(Self::Raw),
"bin" => Ok(Self::Binary),
"txt" => Ok(Self::Text),
"rec" => Ok(Self::Records),
"atxt" => Ok(Self::ApplesoftText),
"itxt" => Ok(Self::IntegerText),
"mtxt" => Ok(Self::MerlinText),
"atok" => Ok(Self::ApplesoftTokens),
"itok" => Ok(Self::IntegerTokens),
"mtok" => Ok(Self::MerlinTokens),
"avar" => Ok(Self::ApplesoftVars),
"ivar" => Ok(Self::IntegerVars),
"block" => Ok(Self::Block),
"track" => Ok(Self::Track),
"raw_track" => Ok(Self::RawTrack),
"sec" => Ok(Self::Sector),
"sys" => Ok(Self::System),
"meta" => Ok(Self::Metadata),
_ => Err(CommandError::UnknownItemType)
}
}
}
const SEC_MESS: &str =
"sector specification should be <cyl>,<head>,<sec>` or a range";
const CYL_MESS: &str =
"cylinder specification should be a postive integer or quarter-decimal (e.g. 17.25)";
const TRK_MESS: &str =
"track specification should be `<cyl>,<head>` or a range";
fn parse_quarter_decimal(qdec: &str,head: usize,steps_per_cyl: usize) -> Result<Track,DYNERR> {
let cf: Vec<&str> = qdec.split('.').collect();
if cf.len() < 1 || cf.len() > 2 {
log::error!("{}",CYL_MESS);
return Err(Box::new(CommandError::InvalidCommand))
}
let coarse = usize::from_str(cf[0])?;
let mut fine = 0;
if cf.len() == 2 {
fine = match cf[1] {
"0" | "00" => 0,
"25" => 1,
"5" | "50" => 2,
"75" => 3,
_ => {
log::error!("{}",CYL_MESS);
return Err(Box::new(CommandError::InvalidCommand))
}
};
}
match (steps_per_cyl,fine) {
(1,0) => Ok(Track::CH((coarse,head))),
(2,f) if f==0 || f==2 => Ok(Track::Motor((coarse*2 + f/2,head))),
(4,f) => Ok(Track::Motor((coarse*4+f,head))),
_ => {
log::error!("fractional track is incompatible with this image");
Err(Box::new(CommandError::InvalidCommand))
}
}
}
fn parse_range(range: &str) -> Result<[usize;2],DYNERR> {
let mut ans = [0,1];
let mut lims = range.split("..");
for j in 0..2 {
match (j,lims.next()) {
(0,Some(lim)) => {
ans[0] = usize::from_str(lim)?;
},
(1,Some(lim)) => {
ans[1] = usize::from_str(lim)?;
if ans[1] <= ans[0] {
log::error!("end was <= start");
return Err(Box::new(CommandError::InvalidCommand));
}
},
(1,None) => {
ans[1] = ans[0] + 1;
},
_ => panic!("unexpected pattern parsing sector request")
}
}
if lims.next().is_some() {
log::error!("range specification should be in form `<beg>[..<end>]`");
return Err(Box::new(CommandError::InvalidCommand));
}
Ok(ans)
}
fn parse_track_range(range: &str,steps_per_cyl: usize) -> Result<[Track;2],DYNERR> {
let mut ans = [Track::Num(0),Track::Num(0)];
let mut lims = range.split("..");
for j in 0..2 {
match (j,lims.next()) {
(0,Some(lim)) => {
ans[0] = parse_quarter_decimal(lim,0,steps_per_cyl)?;
},
(1,Some(lim)) => {
ans[1] = parse_quarter_decimal(lim,0,steps_per_cyl)?;
match ans[0].partial_cmp(&ans[1]) {
Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) => {
log::error!("end was <= start");
return Err(Box::new(CommandError::InvalidCommand));
},
None => {
log::error!("start and end must both be integers or both be quarter decimals");
return Err(Box::new(CommandError::InvalidCommand));
},
_ => {}
}
},
(1,None) => {
ans[1] = match ans[0] {
Track::Motor((m,h)) => Track::Motor((m+1,h)),
Track::CH((c,h)) => Track::CH((c+1,h)),
_ => return Err(Box::new(CommandError::InvalidCommand))
};
},
_ => panic!("unexpected pattern parsing sector request")
}
}
if lims.next().is_some() {
log::error!("range specification should be in form `<beg>[..<end>]`");
return Err(Box::new(CommandError::InvalidCommand));
}
Ok(ans)
}
fn parse_sector_request(farg: &str,steps_per_cyl: usize) -> Result<Vec<(Track,Sector)>,DYNERR> {
let mut ans: Vec<(Track,Sector)> = Vec::new();
let mut contiguous_areas = farg.split(",,");
while let Some(contig) = contiguous_areas.next() {
let mut ranges = contig.split(',');
let trk_rng = match ranges.next() {
Some(range) => parse_track_range(range,steps_per_cyl)?,
None => {
log::error!("{}",SEC_MESS);
return Err(Box::new(CommandError::InvalidCommand));
}
};
let head_rng = match ranges.next() {
Some(range) => parse_range(range)?,
None => {
log::error!("{}",SEC_MESS);
return Err(Box::new(CommandError::InvalidCommand));
}
};
let sec_rng = match ranges.next() {
Some(range) => parse_range(range)?,
None => {
log::error!("{}",SEC_MESS);
return Err(Box::new(CommandError::InvalidCommand));
}
};
if ranges.next().is_some() {
log::error!("{}",SEC_MESS);
return Err(Box::new(CommandError::InvalidCommand));
}
let mut cyl = trk_rng[0].clone();
while cyl < trk_rng[1] {
for head in head_rng[0]..head_rng[1] {
for sec in sec_rng[0]..sec_rng[1] {
match cyl {
Track::CH((c,_)) => ans.push((Track::CH((c,head)),Sector::Num(sec))),
Track::Motor((m,_)) => ans.push((Track::Motor((m,head)),Sector::Num(sec))),
_ => panic!("unexpected track spec")
};
if ans.len()>4*(u16::MAX as usize) {
log::error!("sector request has too many sectors");
return Err(Box::new(CommandError::InvalidCommand));
}
}
}
cyl.jump(1,None,steps_per_cyl)?;
}
}
Ok(ans)
}
fn to_explicit(explicit_arg: &str,ts_list: &mut Vec<(Track,Sector)>) -> crate::STDRESULT {
let xaddr = explicit_arg.split(",").collect::<Vec<&str>>();
if xaddr.len() != ts_list.len() {
log::error!("there were {} sector addresses and {} sectors",xaddr.len(),ts_list.len());
return Err(Box::new(CommandError::InvalidCommand));
}
for i in 0..xaddr.len() {
ts_list[i].1.to_explicit(xaddr[i])?;
}
Ok(())
}
fn parse_track_request(farg: &str,steps_per_cyl: usize) -> Result<Vec<Track>,DYNERR> {
let mut ans: Vec<Track> = Vec::new();
let mut contiguous_areas = farg.split(",,");
while let Some(contig) = contiguous_areas.next() {
let mut ranges = contig.split(',');
let trk_rng = match ranges.next() {
Some(range) => parse_track_range(range,steps_per_cyl)?,
None => {
log::error!("{}",TRK_MESS);
return Err(Box::new(CommandError::InvalidCommand));
}
};
let head_rng = match ranges.next() {
Some(range) => parse_range(range)?,
None => {
log::error!("{}",TRK_MESS);
return Err(Box::new(CommandError::InvalidCommand));
}
};
if ranges.next().is_some() {
log::error!("{}",TRK_MESS);
return Err(Box::new(CommandError::InvalidCommand));
}
let mut cyl = trk_rng[0].clone();
while cyl < trk_rng[1] {
for head in head_rng[0]..head_rng[1] {
match cyl {
Track::CH((c,_)) => ans.push(Track::CH((c,head))),
Track::Motor((m,_)) => ans.push(Track::Motor((m,head))),
_ => panic!("unexpected track spec")
};
if ans.len()>4*(u16::MAX as usize) {
log::error!("track request has too many tracks");
return Err(Box::new(CommandError::InvalidCommand));
}
}
cyl.jump(1,None,steps_per_cyl)?;
}
}
Ok(ans)
}
fn request_one_track(farg: &str,steps_per_cyl: usize) -> Result<Track,DYNERR> {
let v = parse_track_request(farg,steps_per_cyl)?;
if v.len() != 1 {
log::error!("expected exactly one track but got {}",v.len());
return Err(Box::new(CommandError::InvalidCommand));
}
Ok(v[0].clone())
}
fn parse_block_request(farg: &str) -> Result<Vec<usize>,DYNERR> {
let mut ans: Vec<usize> = Vec::new();
let mut contiguous_areas = farg.split(",,");
while let Some(contig) = contiguous_areas.next() {
if contig.contains(",") {
log::error!("unexpected single comma in block request");
return Err(Box::new(CommandError::InvalidCommand));
}
let rng = parse_range(contig)?;
for b in rng[0]..rng[1] {
ans.push(b);
if ans.len()>4*(u16::MAX as usize) {
log::error!("block request has too many blocks");
return Err(Box::new(CommandError::InvalidCommand));
}
}
}
Ok(ans)
}
fn get_json_list_from_stdin() -> Result<json::JsonValue,DYNERR> {
let mut raw_list = Vec::new();
std::io::stdin().read_to_end(&mut raw_list)?;
let json_list = match json::parse(&String::from_utf8(raw_list)?) {
Ok(s) => s,
Err(_) => {
log::error!("input to mget was not valid JSON");
return Err(Box::new(CommandError::InvalidCommand));
}
};
if !json_list.is_array() {
log::error!("input to mget was not a JSON list");
return Err(Box::new(CommandError::InvalidCommand));
}
Ok(json_list)
}
#[test]
fn test_parse_sec_req() {
let unwrap_ts_keys = |keys: Vec<(Track,Sector)>| -> Vec<[usize;3]> {
let mut ans = Vec::new();
for k in keys {
match k {
(Track::CH((c,h)),Sector::Num(s)) => ans.push([c,h,s]),
_ => panic!("unhandled test scenario")
}
}
ans
};
let single = "2,0,3";
let contig = "2..4,0,3..5";
let non_contig = "2..4,0,3..5,,32..34,0,0..2";
let single_list = unwrap_ts_keys(parse_sector_request(single,1).expect("could not parse"));
assert_eq!(single_list,vec![[2,0,3]]);
let contig_list = unwrap_ts_keys(parse_sector_request(contig,1).expect("could not parse"));
assert_eq!(contig_list,vec![[2,0,3],[2,0,4],[3,0,3],[3,0,4]]);
let non_contig_list = unwrap_ts_keys(parse_sector_request(non_contig,1).expect("could not parse"));
assert_eq!(non_contig_list,vec![[2,0,3],[2,0,4],[3,0,3],[3,0,4],[32,0,0],[32,0,1],[33,0,0],[33,0,1]]);
}
#[test]
fn test_parse_flux_req() {
let unwrap_keys = |keys: Vec<Track>| -> Vec<[usize;2]> {
let mut ans = Vec::new();
for k in keys {
match k {
Track::CH((c,h)) => ans.push([c,h]),
_ => panic!("unhandled test scenario")
}
}
ans
};
let single = "2,0";
let contig = "2..4,0";
let non_contig = "2..4,0,,32..34,0";
let single_list = unwrap_keys(parse_track_request(single,1).expect("could not parse"));
assert_eq!(single_list,vec![[2,0]]);
let contig_list = unwrap_keys(parse_track_request(contig,1).expect("could not parse"));
assert_eq!(contig_list,vec![[2,0],[3,0]]);
let non_contig_list = unwrap_keys(parse_track_request(non_contig,1).expect("could not parse"));
assert_eq!(non_contig_list,vec![[2,0],[3,0],[32,0],[33,0]]);
}
#[test]
fn test_parse_block_req() {
let single = "1";
let contig = "1..4";
let non_contig = "1..4,,6,,8..10";
let single_list = parse_block_request(single).expect("could not parse");
assert_eq!(single_list,vec![1]);
let contig_list = parse_block_request(contig).expect("could not parse");
assert_eq!(contig_list,vec![1,2,3]);
let non_contig_list = parse_block_request(non_contig).expect("could not parse");
assert_eq!(non_contig_list,vec![1,2,3,6,8,9]);
}