use std::path::PathBuf;
use crate::AddressRange;
use super::parse::parse_option;
#[derive(Debug, Default)]
pub struct Args {
pub input_file: Option<PathBuf>,
pub output_file: Option<PathBuf>,
pub ini_file: Option<PathBuf>,
pub error_log: Option<PathBuf>,
pub silent: bool,
pub write_version: bool,
pub import_i16: Option<PathBuf>,
pub import_binary: Option<ImportParam>,
pub import_hex_ascii: Option<ImportParam>,
pub remap: Option<RemapParams>,
pub s08_map: bool,
pub s12_map: bool,
pub s12x_map: bool,
pub fill_ranges: Vec<AddressRange>,
pub fill_pattern: Vec<u8>,
pub fill_pattern_set: bool,
pub cut_ranges: Vec<AddressRange>,
pub merge_opaque: Vec<MergeParam>,
pub merge_transparent: Vec<MergeParam>,
pub address_range: Vec<AddressRange>,
pub address_range_empty_output: bool,
pub log_file: Option<PathBuf>,
pub fill_all: bool,
pub postbuild: Option<PathBuf>,
pub align_address: Option<u32>,
pub align_length: bool,
pub align_fill: u8,
pub align_erase: Option<u32>,
pub checksum: Option<ChecksumParams>,
pub checksum_multi: Vec<ChecksumParams>,
pub data_processing: Option<DataProcessingParams>,
pub signature_verify: Option<SignatureVerifyParams>,
pub split_block_size: Option<u32>,
pub swap_word: bool,
pub swap_long: bool,
pub dspic_expand: Vec<DspicOp>,
pub dspic_shrink: Vec<DspicOp>,
pub dspic_clear_ghost: Vec<AddressRange>,
pub output_format: Option<OutputFormat>,
pub bytes_per_line: Option<u8>,
}
#[derive(Debug, Clone)]
pub struct MergeParam {
pub file: PathBuf,
pub offset: Option<i64>,
pub range: Option<AddressRange>,
}
#[derive(Debug, Clone)]
pub struct ImportParam {
pub file: PathBuf,
pub offset: u32,
}
#[derive(Debug, Clone)]
pub struct RemapParams {
pub start: u32,
pub end: u32,
pub linear: u32,
pub size: u32,
pub inc: u32,
}
#[derive(Debug, Clone)]
pub struct ChecksumParams {
pub algorithm: u8,
pub target: ChecksumTarget,
pub little_endian: bool,
pub range: Option<AddressRange>,
pub forced_range: Option<ForcedRange>,
pub exclude_ranges: Vec<AddressRange>,
}
#[derive(Debug, Clone)]
pub struct ForcedRange {
pub range: AddressRange,
pub pattern: Vec<u8>,
}
#[derive(Debug, Clone)]
pub enum ChecksumTarget {
None,
Address(u32),
Append,
Begin,
Prepend,
OverwriteEnd,
File(PathBuf),
}
#[derive(Debug, Clone)]
pub struct DataProcessingParams {
pub method: u8,
pub placement: Option<ChecksumTarget>,
pub key_info: String,
pub output_file: Option<PathBuf>,
}
#[derive(Debug, Clone)]
pub struct SignatureVerifyParams {
pub method: u8,
pub key_info: String,
pub signature_info: String,
}
#[derive(Debug, Clone)]
pub struct DspicOp {
pub range: AddressRange,
pub target: Option<u32>,
}
#[derive(Debug, Clone)]
pub enum OutputFormat {
IntelHex {
record_type: Option<u8>,
}, SRecord {
record_type: Option<u8>,
}, Binary, HexAscii {
line_length: Option<u32>,
separator: Option<String>,
}, CCode, FordIntelHex, Porsche, SeparateBinary, }
#[derive(Debug)]
pub enum ParseArgError {
MissingInputFile,
InvalidOption(String),
InvalidRange(String),
InvalidNumber(String),
DuplicateOutputFormat,
MissingValue(String),
}
impl std::fmt::Display for ParseArgError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::MissingInputFile => write!(f, "missing input file"),
Self::InvalidOption(s) => write!(f, "invalid option: {s}"),
Self::InvalidRange(s) => write!(f, "invalid range: {s}"),
Self::InvalidNumber(s) => write!(f, "invalid number: {s}"),
Self::DuplicateOutputFormat => write!(f, "multiple output formats specified"),
Self::MissingValue(s) => write!(f, "missing value for {s}"),
}
}
}
impl std::error::Error for ParseArgError {}
impl Args {
pub fn parse() -> Result<Self, ParseArgError> {
Self::parse_from(std::env::args().skip(1).collect())
}
pub fn parse_from(args: Vec<String>) -> Result<Self, ParseArgError> {
Self::parse_from_with(args, |arg| {
let path = std::path::Path::new(arg);
arg.starts_with('/') && path.is_absolute() && path.exists()
})
}
pub fn parse_from_with<F>(
args: Vec<String>,
is_existing_abs_path: F,
) -> Result<Self, ParseArgError>
where
F: Fn(&str) -> bool,
{
let mut result = Args {
fill_pattern: vec![0xFF],
fill_pattern_set: false,
align_fill: 0xFF,
..Default::default()
};
let mut args_iter = args.iter().peekable();
let mut force_positional = false;
let is_existing_abs_path = &is_existing_abs_path;
while let Some(arg) = args_iter.next() {
if arg == "--" {
force_positional = true;
continue;
}
if arg.eq_ignore_ascii_case("-o") {
let next = args_iter
.next()
.ok_or(ParseArgError::MissingValue("-o".into()))?;
result.output_file = Some(PathBuf::from(next));
continue;
}
if force_positional {
if result.input_file.is_none() {
result.input_file = Some(PathBuf::from(arg));
continue;
}
return Err(ParseArgError::InvalidOption(arg.clone()));
}
if let Some(opt) = arg.strip_prefix('/').or_else(|| arg.strip_prefix('-')) {
match parse_option(&mut result, opt) {
Ok(()) => {}
Err(ParseArgError::InvalidOption(_)) => {
if result.input_file.is_none() && is_existing_abs_path(arg) {
result.input_file = Some(PathBuf::from(arg));
} else {
return Err(ParseArgError::InvalidOption(arg.clone()));
}
}
Err(e) => return Err(e),
}
} else if result.input_file.is_none() {
result.input_file = Some(PathBuf::from(arg));
} else {
return Err(ParseArgError::InvalidOption(arg.clone()));
}
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::Args;
use std::path::PathBuf;
#[test]
fn test_parse_double_dash_forces_positional() {
let args = vec!["--".to_string(), "/tmp/input.hex".to_string()];
let parsed = Args::parse_from(args).unwrap();
assert_eq!(parsed.input_file, Some(PathBuf::from("/tmp/input.hex")));
}
#[test]
fn test_parse_absolute_path_existing_file() {
let dir = std::env::temp_dir();
let path = dir.join("hexy_parse_input_test.bin");
std::fs::write(&path, [0xAA]).unwrap();
let args = vec![path.to_string_lossy().to_string()];
let parsed = Args::parse_from(args).unwrap();
assert_eq!(parsed.input_file, Some(path.clone()));
let _ = std::fs::remove_file(path);
}
#[test]
fn test_parse_positional_windows_path() {
let args = vec![r"C:\temp\input.hex".to_owned()];
let parsed = Args::parse_from_with(args, |_| false).unwrap();
assert_eq!(parsed.input_file, Some(PathBuf::from(r"C:\temp\input.hex")));
}
#[test]
fn test_parse_dash_o_with_windows_path() {
let args = vec![
"-o".to_owned(),
r"C:\temp\out.hex".to_owned(),
"input.hex".to_owned(),
];
let parsed = Args::parse_from_with(args, |_| false).unwrap();
assert_eq!(parsed.output_file, Some(PathBuf::from(r"C:\temp\out.hex")));
assert_eq!(parsed.input_file, Some(PathBuf::from("input.hex")));
}
#[test]
fn test_parse_dash_prefix_case_and_equal_separator() -> Result<(), Box<dyn std::error::Error>> {
let args = vec![
"-afcd".to_owned(),
"/aD=0x10".to_owned(),
"input.hex".to_owned(),
];
let parsed = Args::parse_from_with(args, |_| false)?;
assert_eq!(parsed.align_fill, 0xCD);
assert_eq!(parsed.align_address, Some(0x10));
assert_eq!(parsed.input_file, Some(PathBuf::from("input.hex")));
Ok(())
}
}