use crate::repl::{
explain_bash, format_lint_results, format_parse_error, lint_bash,
loader::{format_functions, load_script, LoadResult},
parse_bash, purify_bash,
variables::expand_variables,
ReplMode, ReplState,
};
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq)]
pub enum ModeCommandResult {
ShowCurrent { mode: ReplMode, description: String },
Switched { mode: ReplMode, description: String },
InvalidMode(String),
InvalidUsage,
}
impl ModeCommandResult {
pub fn format(&self) -> String {
match self {
Self::ShowCurrent { mode, description } => {
let mut output = format!("Current mode: {} - {}\n\n", mode, description);
output.push_str("Available modes:\n");
output.push_str(" normal - Execute bash commands directly\n");
output.push_str(" purify - Show purified version of bash commands\n");
output.push_str(" lint - Show linting results for bash commands\n");
output.push_str(" debug - Debug bash commands with step-by-step execution\n");
output.push_str(" explain - Explain bash constructs and syntax\n\n");
output.push_str("Usage: :mode <mode_name>");
output
}
Self::Switched { mode, description } => {
format!("Switched to {} mode - {}", mode, description)
}
Self::InvalidMode(err) => format!("Error: {}", err),
Self::InvalidUsage => {
"Usage: :mode [<mode_name>]\nValid modes: normal, purify, lint, debug, explain"
.to_string()
}
}
}
}
#[derive(Debug, Clone)]
pub enum ParseCommandResult {
Success {
statement_count: usize,
parse_time_ms: u64,
ast_debug: String,
},
Error(String),
MissingInput,
}
impl ParseCommandResult {
pub fn format(&self) -> String {
match self {
Self::Success {
statement_count,
parse_time_ms,
ast_debug,
} => {
let mut output = String::new();
output.push_str("✓ Parse successful!\n");
output.push_str(&format!("Statements: {}\n", statement_count));
output.push_str(&format!("Parse time: {}ms\n\n", parse_time_ms));
output.push_str("AST:\n");
output.push_str(ast_debug);
output
}
Self::Error(e) => format!("✗ {}", e),
Self::MissingInput => {
"Usage: :parse <bash_code>\nExample: :parse echo hello".to_string()
}
}
}
}
#[derive(Debug, Clone)]
pub enum PurifyCommandResult {
Success(String),
Error(String),
MissingInput,
}
impl PurifyCommandResult {
pub fn format(&self) -> String {
match self {
Self::Success(result) => format!("✓ Purification successful!\n{}", result),
Self::Error(e) => format!("✗ Purification error: {}", e),
Self::MissingInput => {
"Usage: :purify <bash_code>\nExample: :purify mkdir /tmp/test".to_string()
}
}
}
}
#[derive(Debug, Clone)]
pub enum LintCommandResult {
Success(String),
Error(String),
MissingInput,
}
impl LintCommandResult {
pub fn format(&self) -> String {
match self {
Self::Success(result) => result.clone(),
Self::Error(e) => format!("✗ Lint error: {}", e),
Self::MissingInput => {
"Usage: :lint <bash_code>\nExample: :lint cat file.txt | grep pattern".to_string()
}
}
}
}
#[derive(Debug, Clone)]
pub enum LoadCommandResult {
Success {
path: PathBuf,
function_count: usize,
formatted: String,
},
Error(String),
MissingInput,
}
impl LoadCommandResult {
pub fn format(&self) -> String {
match self {
Self::Success { formatted, .. } => formatted.clone(),
Self::Error(e) => e.clone(),
Self::MissingInput => {
"Usage: :load <file>\nExample: :load examples/functions.sh".to_string()
}
}
}
}
#[derive(Debug, Clone)]
pub enum ModeProcessResult {
Executed(String),
Purified(String),
Linted(String),
Debug(String),
Explained(String),
NoExplanation(String),
Error(String),
}
impl ModeProcessResult {
pub fn format(&self) -> String {
match self {
Self::Executed(output) => output.clone(),
Self::Purified(result) => format!("✓ Purified:\n{}", result),
Self::Linted(result) => result.clone(),
Self::Debug(line) => format!(
"Debug mode: {}\n(Note: Debug mode not yet implemented)",
line
),
Self::Explained(explanation) => explanation.clone(),
Self::NoExplanation(line) => {
format!(
"No explanation available for: {}\nTry parameter expansions (${{var:-default}}), control flow (for, if, while), or redirections (>, <, |)",
line
)
}
Self::Error(e) => e.clone(),
}
}
}
#[derive(Debug, Clone)]
pub enum HistoryResult {
Entries(Vec<String>),
Empty,
}
impl HistoryResult {
pub fn format(&self) -> String {
match self {
Self::Empty => "No commands in history".to_string(),
Self::Entries(history) => {
let mut output = format!("Command History ({} commands):\n", history.len());
for (i, cmd) in history.iter().enumerate() {
output.push_str(&format!(" {} {}\n", i + 1, cmd));
}
output.trim_end().to_string()
}
}
}
}
#[derive(Debug, Clone)]
pub enum VarsResult {
Variables(Vec<(String, String)>),
Empty,
}
impl VarsResult {
pub fn format(&self) -> String {
match self {
Self::Empty => "No session variables set".to_string(),
Self::Variables(vars) => {
let mut output = format!("Session Variables ({} variables):\n", vars.len());
for (name, value) in vars {
output.push_str(&format!(" {} = {}\n", name, value));
}
output.trim_end().to_string()
}
}
}
}
#[derive(Debug, Clone)]
pub struct FunctionsResult(pub String);
impl FunctionsResult {
pub fn format(&self) -> String {
self.0.clone()
}
}
#[derive(Debug, Clone)]
pub enum ReloadResult {
Success {
path: PathBuf,
function_count: usize,
},
Error(String),
NoScript,
}
impl ReloadResult {
pub fn format(&self) -> String {
match self {
Self::Success {
path,
function_count,
} => format!(
"✓ Reloaded: {} ({} functions)",
path.display(),
function_count
),
Self::Error(e) => e.clone(),
Self::NoScript => "No script to reload. Use :load <file> first.".to_string(),
}
}
}
pub fn process_mode_command(
line: &str,
state: &ReplState,
) -> (ModeCommandResult, Option<ReplMode>) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() == 1 {
(
ModeCommandResult::ShowCurrent {
mode: state.mode(),
description: state.mode().description().to_string(),
},
None,
)
} else if parts.len() == 2 {
if let Some(mode_name) = parts.get(1) {
match mode_name.parse::<ReplMode>() {
Ok(mode) => (
ModeCommandResult::Switched {
mode,
description: mode.description().to_string(),
},
Some(mode),
),
Err(err) => (ModeCommandResult::InvalidMode(err.to_string()), None),
}
} else {
(ModeCommandResult::InvalidUsage, None)
}
} else {
(ModeCommandResult::InvalidUsage, None)
}
}
pub fn process_parse_command(line: &str) -> ParseCommandResult {
let parts: Vec<&str> = line.splitn(2, ' ').collect();
if parts.len() == 1 {
return ParseCommandResult::MissingInput;
}
let bash_code = parts.get(1).unwrap_or(&"");
match parse_bash(bash_code) {
Ok(ast) => {
let mut ast_debug = String::new();
for (i, stmt) in ast.statements.iter().enumerate() {
ast_debug.push_str(&format!(" [{}] {:?}\n", i, stmt));
}
ParseCommandResult::Success {
statement_count: ast.statements.len(),
parse_time_ms: ast.metadata.parse_time_ms,
ast_debug,
}
}
Err(e) => ParseCommandResult::Error(format_parse_error(&e)),
}
}
include!("logic_process.rs");