summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2025-07-31 20:45:23 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2025-07-31 20:45:23 +0200
commit34297979bb9e4eb76d55ff1a89072865bb525d13 (patch)
tree0eb6217c9a604ab731c385e7de2ecd12b02a9468
parent9e41cdfb69532c51be5a241c975cec023b8d858b (diff)
Detect all built-in commands.
-rw-r--r--src/main.rs270
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))),
+ }
+}