diff options
author | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2025-07-31 20:45:23 +0200 |
---|---|---|
committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2025-07-31 20:45:23 +0200 |
commit | 34297979bb9e4eb76d55ff1a89072865bb525d13 (patch) | |
tree | 0eb6217c9a604ab731c385e7de2ecd12b02a9468 | |
parent | 9e41cdfb69532c51be5a241c975cec023b8d858b (diff) |
Detect all built-in commands.
-rw-r--r-- | src/main.rs | 270 |
1 files changed, 269 insertions, 1 deletions
diff --git a/src/main.rs b/src/main.rs index 61f6663..744217a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -418,6 +418,10 @@ impl Command { use BuiltinCommand::*; use Command::*; + // Implement COMMAND.COM-style command line parsing. + // See https://en.wikipedia.org/wiki/Command.com#Command_line_syntax + // and https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts for details. + // let whitespace = Regex::new(r"\s+")?; let mut split_input = whitespace.splitn(input, 2); let Some(name) = split_input.next() else { @@ -441,7 +445,271 @@ impl Command { "EXIT" => Ok(Builtin(Exit)), - _ => Err(eyre::eyre!("parse not implemented for {:?}", input)), + "MORE" => Ok(Builtin(More)), + + "VERIFY" => Ok(Builtin(Verify)), + + // File-oriented commands + "COPY" => { + let parts: Vec<&str> = args.split_whitespace().collect(); + if parts.len() >= 2 { + Ok(Builtin(Copy { + from: parse_filespec(parts[0])?, + to: parse_filespec(parts[1])?, + })) + } else { + Err(eyre::eyre!("COPY requires source and destination")) + } + }, + + "DELTREE" => Ok(Builtin(Deltree { + path: PathBuf::from(args.trim()), + })), + + "DIR" => Ok(Builtin(Dir { + path: if args.trim().is_empty() { + PathBuf::from(".") + } else { + PathBuf::from(args.trim()) + }, + })), + + "FC" => Ok(Builtin(Fc)), + + "FIND" => Ok(Builtin(Find)), + + "MKDIR" | "MD" => Ok(Builtin(Mkdir { + path: PathBuf::from(args.trim()), + })), + + "MOVE" => Ok(Builtin(Move)), + + "DEL" | "ERASE" => Ok(Builtin(Remove { + path: PathBuf::from(args.trim()), + })), + + "REN" | "RENAME" => { + let parts: Vec<&str> = args.split_whitespace().collect(); + if parts.len() >= 2 { + Ok(Builtin(Rename { + from: parse_filespec(parts[0])?, + to: parse_filespec(parts[1])?, + })) + } else { + Err(eyre::eyre!("RENAME requires source and destination")) + } + }, + + "REPLACE" => Ok(Builtin(Replace)), + + "RMDIR" | "RD" => Ok(Builtin(Rmdir { + path: PathBuf::from(args.trim()), + })), + + "SORT" => Ok(Builtin(Sort)), + + "TREE" => Ok(Builtin(Tree { + path: if args.trim().is_empty() { + PathBuf::from(".") + } else { + PathBuf::from(args.trim()) + }, + })), + + "TYPE" => Ok(Builtin(Type { + file: parse_filespec(args.trim())?, + })), + + "XCOPY" => { + let parts: Vec<&str> = args.split_whitespace().collect(); + let recursive = parts.contains(&"/S") || parts.contains(&"/s"); + if parts.len() >= 2 { + Ok(Builtin(Xcopy { + from: parse_filespec(parts[0])?, + to: parse_filespec(parts[1])?, + recursive, + })) + } else { + Err(eyre::eyre!("XCOPY requires source and destination")) + } + }, + + // Shell-oriented commands + "APPEND" => Ok(Builtin(Append)), + + "CD" | "CHDIR" => Ok(Builtin(Chdir { + path: if args.trim().is_empty() { + std::env::current_dir()? + } else { + PathBuf::from(args.trim()) + }, + })), + + "PATH" => { + if args.trim().is_empty() { + Ok(Builtin(PathGet)) + } else { + Ok(Builtin(PathSet { + value: args.to_string(), + })) + } + }, + + "PROMPT" => { + if args.trim().is_empty() { + Ok(Builtin(PromptGet)) + } else { + Ok(Builtin(PromptSet { + message: args.to_string(), + })) + } + }, + + "SET" => { + if args.trim().is_empty() { + Err(eyre::eyre!("SET requires variable name")) + } else if let Some(eq_pos) = args.find('=') { + let name = args[..eq_pos].trim().to_string(); + let value = args[eq_pos + 1..].trim().to_string(); + Ok(Builtin(Set { name, value })) + } else { + Err(eyre::eyre!("SET requires variable=value format")) + } + }, + + "SETVER" => Ok(Builtin(Setver)), + + "VER" => Ok(Builtin(Ver)), + + // Utilities + "DATE" => Ok(Builtin(Date)), + + "TIME" => Ok(Builtin(Time)), + + // Dummies + "FASTOPEN" => Ok(Builtin(Fastopen)), + + "SMARTDRV" => Ok(Builtin(Smartdrv)), + + "SIZER" => Ok(Builtin(Sizer)), + + // For later + "ASSIGN" => Ok(Builtin(Assign)), + + "ATTRIB" => Ok(Builtin(Attrib)), + + "CHKDSK" => Ok(Builtin(Chkdsk)), + + "DOSKEY" => Ok(Builtin(Doskey)), + + "DOSSHELL" => Ok(Builtin(Dosshell)), + + "EDIT" => Ok(Builtin(Edit)), + + "FASTHELP" => Ok(Builtin(Fasthelp)), + + "HELP" => Ok(Builtin(Help)), + + "JOIN" => Ok(Builtin(Join)), + + "MEM" => Ok(Builtin(Mem)), + + "POWER" => Ok(Builtin(Power)), + + "SUBST" => Ok(Builtin(Subst)), + + "TRUENAME" => Ok(Builtin(Truename)), + + // For much later, if ever + "BREAK" => Ok(Builtin(Break)), + + "CHCP" => Ok(Builtin(Chcp)), + + "CTTY" => Ok(Builtin(Ctty)), + + "DEFRAG" => Ok(Builtin(Defrag)), + + "DISKCOPY" => Ok(Builtin(Diskcopy)), + + "EMM386" => Ok(Builtin(Emm386)), + + "FDISK" => Ok(Builtin(Fdisk)), + + "FORMAT" => Ok(Builtin(Format)), + + "INTERLNK" => Ok(Builtin(Interlnk)), + + "KEYB" => Ok(Builtin(Keyb)), + + "LABEL" => Ok(Builtin(Label)), + + "MODE" => Ok(Builtin(Mode)), + + "MSAV" => Ok(Builtin(Msav)), + + "MSBACKUP" => Ok(Builtin(Msbackup)), + + "MSCDEX" => Ok(Builtin(Mscdex)), + + "MSD" => Ok(Builtin(Msd)), + + "PRINT" => Ok(Builtin(Print)), + + "QBASIC" => Ok(Builtin(Qbasic)), + + "RESTORE" => Ok(Builtin(Restore)), + + "SCANDISK" => Ok(Builtin(Scandisk)), + + "SHARE" => Ok(Builtin(Share)), + + "SYS" => Ok(Builtin(Sys)), + + "UNDELETE" => Ok(Builtin(Undelete)), + + "UNFORMAT" => Ok(Builtin(Unformat)), + + "VOL" => Ok(Builtin(Vol)), + + "VSAFE" => Ok(Builtin(Vsafe)), + + // Scripting + "CALL" => Ok(Builtin(Call)), + + "CHOICE" => Ok(Builtin(Choice)), + + "FOR" => Ok(Builtin(For)), + + "GOTO" => Ok(Builtin(Goto)), + + "IF" => Ok(Builtin(If)), + + "PAUSE" => Ok(Builtin(Pause)), + + "REM" => Ok(Builtin(Rem { + message: args.to_string(), + })), + + "SHIFT" => Ok(Builtin(Shift)), + + _ if name.len() == 2 && name.ends_with(":") => Ok(Empty), + + _ => { + //let parts: Vec<&str> = args.split_whitespace().collect(); + } + + //Err(eyre::eyre!("parse not implemented for {:?}", input)), } } } + +fn parse_filespec(spec: &str) -> Result<FileSpec> { + match spec.to_uppercase().as_str() { + "CON" => Ok(FileSpec::Con), + "LPT1" => Ok(FileSpec::Lpt1), + "LPT2" => Ok(FileSpec::Lpt2), + "LPT3" => Ok(FileSpec::Lpt3), + "PRN" => Ok(FileSpec::Prn), + _ => Ok(FileSpec::Path(PathBuf::from(spec))), + } +} |