use crate::command_options::{
OptionError, OptionErrorKind, OptionSpec, ParseMode, ParsedOptions, WrapperOccurrence,
parse_options,
};
use crate::command_options_query::{WrapperFlags, split_wrapper_flags};
use crate::command_stash_table::{
STASH_APPLY_TABLE, STASH_DROP_TABLE, STASH_EMPTY_TABLE, STASH_EXPORT_TABLE,
STASH_KEEP_UNKNOWN_MODE, STASH_PLAIN_MODE, STASH_POP_TABLE, STASH_PUSH_TABLE, STASH_SAVE_TABLE,
STASH_SHOW_TABLE, STASH_STOP_MODE, STASH_STORE_TABLE,
};
use crate::escape_hatch::WORKTREE_ENFORCEMENT_ESCAPE_HATCH;
use std::ffi::OsString;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StashSubcommand {
Apply,
Clear,
Drop,
Pop,
Branch,
List,
Show,
Store,
Create,
Push,
Export,
Import,
Save,
AssumedPush,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StashRegion {
pub subcommand: StashSubcommand,
pub wrapper: WrapperFlags,
}
const SUBCOMMAND_WORDS: &[(&str, StashSubcommand)] = &[
("apply", StashSubcommand::Apply),
("clear", StashSubcommand::Clear),
("drop", StashSubcommand::Drop),
("pop", StashSubcommand::Pop),
("branch", StashSubcommand::Branch),
("list", StashSubcommand::List),
("show", StashSubcommand::Show),
("store", StashSubcommand::Store),
("create", StashSubcommand::Create),
("push", StashSubcommand::Push),
("export", StashSubcommand::Export),
("import", StashSubcommand::Import),
("save", StashSubcommand::Save),
];
fn subcommand_of(word: &[u8]) -> Option<StashSubcommand> {
for (name, subcommand) in SUBCOMMAND_WORDS {
if name.as_bytes() == word {
return Some(*subcommand);
}
}
return None;
}
fn grammar_of(subcommand: StashSubcommand) -> (&'static [OptionSpec], ParseMode) {
return match subcommand {
StashSubcommand::Apply => (STASH_APPLY_TABLE, STASH_PLAIN_MODE),
StashSubcommand::Clear => (STASH_EMPTY_TABLE, STASH_STOP_MODE),
StashSubcommand::Drop => (STASH_DROP_TABLE, STASH_PLAIN_MODE),
StashSubcommand::Pop => (STASH_POP_TABLE, STASH_PLAIN_MODE),
StashSubcommand::Branch => (STASH_EMPTY_TABLE, STASH_PLAIN_MODE),
StashSubcommand::List => (STASH_EMPTY_TABLE, STASH_KEEP_UNKNOWN_MODE),
StashSubcommand::Show => (STASH_SHOW_TABLE, STASH_KEEP_UNKNOWN_MODE),
StashSubcommand::Store => (STASH_STORE_TABLE, STASH_KEEP_UNKNOWN_MODE),
StashSubcommand::Create => (STASH_EMPTY_TABLE, STASH_STOP_MODE),
StashSubcommand::Push => (STASH_PUSH_TABLE, STASH_PLAIN_MODE),
StashSubcommand::Export => (STASH_EXPORT_TABLE, STASH_PLAIN_MODE),
StashSubcommand::Import => (STASH_EMPTY_TABLE, STASH_PLAIN_MODE),
StashSubcommand::Save => (STASH_SAVE_TABLE, STASH_PLAIN_MODE),
StashSubcommand::AssumedPush => (STASH_PUSH_TABLE, STASH_STOP_MODE),
};
}
fn check_top_level_token(token: &[u8], index: usize) -> Result<(), OptionError> {
if token.len() < 4 || token[0] != b'-' || token[1] == b'-' {
return Ok(());
}
let cluster: &[u8] = &token[1..];
for (name, _) in SUBCOMMAND_WORDS {
if name.as_bytes().starts_with(cluster) {
return Err(OptionError {
kind: OptionErrorKind::SingleDashLongOption,
token: index,
});
}
}
return Ok(());
}
pub fn parse_stash_region(
region: &[OsString],
wrapper_flags: &[&[u8]],
) -> Result<StashRegion, OptionError> {
let mut flags: Vec<&[u8]> = Vec::<&[u8]>::with_capacity(wrapper_flags.len() + 1);
flags.push(WORKTREE_ENFORCEMENT_ESCAPE_HATCH.as_bytes());
for flag in wrapper_flags {
flags.push(*flag);
}
let mut wrapper: Vec<WrapperOccurrence> = Vec::<WrapperOccurrence>::new();
let mut first: usize = 0;
while first < region.len() {
let token: &[u8] = region[first].as_encoded_bytes();
let mut flag: usize = 0;
while flag < flags.len() && flags[flag] != token {
flag += 1;
}
if flag == flags.len() {
break;
}
wrapper.push(WrapperOccurrence { flag, token: first });
first += 1;
}
let mut subcommand: StashSubcommand = StashSubcommand::AssumedPush;
let mut start: usize = first;
if first < region.len() {
let word: &[u8] = region[first].as_encoded_bytes();
if let Some(selected) = subcommand_of(word) {
subcommand = selected;
start = first + 1;
} else {
check_top_level_token(word, first)?;
}
}
if subcommand != StashSubcommand::Create {
let (table, mode): (&[OptionSpec], ParseMode) = grammar_of(subcommand);
let parsed: ParsedOptions =
match parse_options(®ion[start..], table, mode, flags.as_slice()) {
Ok(accepted) => accepted,
Err(error) => {
return Err(OptionError {
kind: error.kind,
token: error.token + start,
});
}
};
for occurrence in &parsed.wrapper {
wrapper.push(WrapperOccurrence {
flag: occurrence.flag,
token: occurrence.token + start,
});
}
}
return Ok(StashRegion {
subcommand,
wrapper: split_wrapper_flags(wrapper.as_slice()),
});
}
pub fn stash_changes_state(subcommand: StashSubcommand) -> bool {
return !matches!(
subcommand,
StashSubcommand::List | StashSubcommand::Show | StashSubcommand::Create
);
}
#[cfg(test)]
#[path = "command_stash_tests.rs"]
mod tests;