Added rustfmt.toml, formatted codebase
This commit is contained in:
7
rustfmt.toml
Normal file
7
rustfmt.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
max_width = 100
|
||||||
|
tab_spaces = 2
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
newline_style = "Unix"
|
||||||
|
|
||||||
|
wrap_comments = true
|
||||||
@@ -1,95 +1,103 @@
|
|||||||
use crate::{jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, read_logic, write_logic}};
|
use crate::{
|
||||||
|
jobs::JobBldr,
|
||||||
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
|
parse::{NdRule, Node},
|
||||||
|
prelude::*,
|
||||||
|
procio::{borrow_fd, IoStack},
|
||||||
|
state::{self, read_logic, write_logic},
|
||||||
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command {
|
||||||
unreachable!()
|
assignments: _,
|
||||||
};
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the environment variables
|
// Display the environment variables
|
||||||
let mut alias_output = read_logic(|l| {
|
let mut alias_output = read_logic(|l| {
|
||||||
l.aliases()
|
l.aliases()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ent| format!("{} = \"{}\"", ent.0, ent.1))
|
.map(|ent| format!("{} = \"{}\"", ent.0, ent.1))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
});
|
});
|
||||||
alias_output.sort(); // Sort them alphabetically
|
alias_output.sort(); // Sort them alphabetically
|
||||||
let mut alias_output = alias_output.join("\n"); // Join them with newlines
|
let mut alias_output = alias_output.join("\n"); // Join them with newlines
|
||||||
alias_output.push('\n'); // Push a final newline
|
alias_output.push('\n'); // Push a final newline
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
write(stdout, alias_output.as_bytes())?; // Write it
|
write(stdout, alias_output.as_bytes())?; // Write it
|
||||||
} else {
|
} else {
|
||||||
for (arg,span) in argv {
|
for (arg, span) in argv {
|
||||||
if arg == "command" || arg == "builtin" {
|
if arg == "command" || arg == "builtin" {
|
||||||
return Err(
|
return Err(ShErr::full(
|
||||||
ShErr::full(
|
ShErrKind::ExecFail,
|
||||||
ShErrKind::ExecFail,
|
format!("alias: Cannot assign alias to reserved name '{arg}'"),
|
||||||
format!("alias: Cannot assign alias to reserved name '{arg}'"),
|
span,
|
||||||
span
|
));
|
||||||
)
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some((name,body)) = arg.split_once('=') else {
|
let Some((name, body)) = arg.split_once('=') else {
|
||||||
return Err(
|
return Err(ShErr::full(
|
||||||
ShErr::full(
|
ShErrKind::SyntaxErr,
|
||||||
ShErrKind::SyntaxErr,
|
"alias: Expected an assignment in alias args",
|
||||||
"alias: Expected an assignment in alias args",
|
span,
|
||||||
span
|
));
|
||||||
)
|
};
|
||||||
)
|
write_logic(|l| l.insert_alias(name, body));
|
||||||
};
|
}
|
||||||
write_logic(|l| l.insert_alias(name, body));
|
}
|
||||||
}
|
io_frame.unwrap().restore()?;
|
||||||
}
|
state::set_status(0);
|
||||||
io_frame.unwrap().restore()?;
|
Ok(())
|
||||||
state::set_status(0);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command {
|
||||||
unreachable!()
|
assignments: _,
|
||||||
};
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
|
if argv.is_empty() {
|
||||||
|
// Display the environment variables
|
||||||
|
let mut alias_output = read_logic(|l| {
|
||||||
|
l.aliases()
|
||||||
|
.iter()
|
||||||
|
.map(|ent| format!("{} = \"{}\"", ent.0, ent.1))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
alias_output.sort(); // Sort them alphabetically
|
||||||
|
let mut alias_output = alias_output.join("\n"); // Join them with newlines
|
||||||
|
alias_output.push('\n'); // Push a final newline
|
||||||
|
|
||||||
if argv.is_empty() {
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
// Display the environment variables
|
write(stdout, alias_output.as_bytes())?; // Write it
|
||||||
let mut alias_output = read_logic(|l| {
|
} else {
|
||||||
l.aliases()
|
for (arg, span) in argv {
|
||||||
.iter()
|
flog!(DEBUG, arg);
|
||||||
.map(|ent| format!("{} = \"{}\"", ent.0, ent.1))
|
if read_logic(|l| l.get_alias(&arg)).is_none() {
|
||||||
.collect::<Vec<_>>()
|
return Err(ShErr::full(
|
||||||
});
|
ShErrKind::SyntaxErr,
|
||||||
alias_output.sort(); // Sort them alphabetically
|
format!("unalias: alias '{arg}' not found"),
|
||||||
let mut alias_output = alias_output.join("\n"); // Join them with newlines
|
span,
|
||||||
alias_output.push('\n'); // Push a final newline
|
));
|
||||||
|
};
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
write_logic(|l| l.remove_alias(&arg))
|
||||||
write(stdout, alias_output.as_bytes())?; // Write it
|
}
|
||||||
} else {
|
}
|
||||||
for (arg,span) in argv {
|
io_frame.unwrap().restore()?;
|
||||||
flog!(DEBUG, arg);
|
state::set_status(0);
|
||||||
if read_logic(|l| l.get_alias(&arg)).is_none() {
|
Ok(())
|
||||||
return Err(
|
|
||||||
ShErr::full(
|
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
format!("unalias: alias '{arg}' not found"),
|
|
||||||
span
|
|
||||||
)
|
|
||||||
)
|
|
||||||
};
|
|
||||||
write_logic(|l| l.remove_alias(&arg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
io_frame.unwrap().restore()?;
|
|
||||||
state::set_status(0);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,51 @@
|
|||||||
use crate::{jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, state::{self}};
|
use crate::{
|
||||||
|
jobs::JobBldr,
|
||||||
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
|
parse::{NdRule, Node},
|
||||||
|
prelude::*,
|
||||||
|
state::{self},
|
||||||
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let span = node.get_span();
|
let span = node.get_span();
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command {
|
||||||
unreachable!()
|
assignments: _,
|
||||||
};
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
let (argv,_) = setup_builtin(argv,job,None)?;
|
let (argv, _) = setup_builtin(argv, job, None)?;
|
||||||
|
|
||||||
let new_dir = if let Some((arg,_)) = argv.into_iter().next() {
|
let new_dir = if let Some((arg, _)) = argv.into_iter().next() {
|
||||||
PathBuf::from(arg)
|
PathBuf::from(arg)
|
||||||
} else {
|
} else {
|
||||||
PathBuf::from(env::var("HOME").unwrap())
|
PathBuf::from(env::var("HOME").unwrap())
|
||||||
};
|
};
|
||||||
|
|
||||||
if !new_dir.exists() {
|
if !new_dir.exists() {
|
||||||
return Err(
|
return Err(ShErr::full(
|
||||||
ShErr::full(
|
ShErrKind::ExecFail,
|
||||||
ShErrKind::ExecFail,
|
format!("cd: No such file or directory '{}'", new_dir.display()),
|
||||||
format!("cd: No such file or directory '{}'",new_dir.display()),
|
span,
|
||||||
span,
|
));
|
||||||
)
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !new_dir.is_dir() {
|
if !new_dir.is_dir() {
|
||||||
return Err(
|
return Err(ShErr::full(
|
||||||
ShErr::full(
|
ShErrKind::ExecFail,
|
||||||
ShErrKind::ExecFail,
|
format!("cd: Not a directory '{}'", new_dir.display()),
|
||||||
format!("cd: Not a directory '{}'",new_dir.display()),
|
span,
|
||||||
span,
|
));
|
||||||
)
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
env::set_current_dir(new_dir).unwrap();
|
env::set_current_dir(new_dir).unwrap();
|
||||||
let new_dir = env::current_dir().unwrap();
|
let new_dir = env::current_dir().unwrap();
|
||||||
env::set_var("PWD", new_dir);
|
env::set_var("PWD", new_dir);
|
||||||
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +1,90 @@
|
|||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use crate::{builtin::setup_builtin, getopt::{get_opts_from_tokens, Opt, OptSet}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state};
|
use crate::{
|
||||||
|
builtin::setup_builtin,
|
||||||
|
getopt::{get_opts_from_tokens, Opt, OptSet},
|
||||||
|
jobs::JobBldr,
|
||||||
|
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||||
|
parse::{NdRule, Node},
|
||||||
|
prelude::*,
|
||||||
|
procio::{borrow_fd, IoStack},
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
|
||||||
pub static ECHO_OPTS: LazyLock<OptSet> = LazyLock::new(|| {[
|
pub static ECHO_OPTS: LazyLock<OptSet> = LazyLock::new(|| {
|
||||||
Opt::Short('n'),
|
[
|
||||||
Opt::Short('E'),
|
Opt::Short('n'),
|
||||||
Opt::Short('e'),
|
Opt::Short('E'),
|
||||||
Opt::Short('r'),
|
Opt::Short('e'),
|
||||||
].into()});
|
Opt::Short('r'),
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
});
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub struct EchoFlags: u32 {
|
pub struct EchoFlags: u32 {
|
||||||
const NO_NEWLINE = 0b000001;
|
const NO_NEWLINE = 0b000001;
|
||||||
const USE_STDERR = 0b000010;
|
const USE_STDERR = 0b000010;
|
||||||
const USE_ESCAPE = 0b000100;
|
const USE_ESCAPE = 0b000100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command {
|
||||||
unreachable!()
|
assignments: _,
|
||||||
};
|
argv,
|
||||||
assert!(!argv.is_empty());
|
} = node.class
|
||||||
let (argv,opts) = get_opts_from_tokens(argv);
|
else {
|
||||||
let flags = get_echo_flags(opts).blame(blame)?;
|
unreachable!()
|
||||||
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
};
|
||||||
|
assert!(!argv.is_empty());
|
||||||
|
let (argv, opts) = get_opts_from_tokens(argv);
|
||||||
|
let flags = get_echo_flags(opts).blame(blame)?;
|
||||||
|
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
|
let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
|
||||||
borrow_fd(STDERR_FILENO)
|
borrow_fd(STDERR_FILENO)
|
||||||
} else {
|
} else {
|
||||||
borrow_fd(STDOUT_FILENO)
|
borrow_fd(STDOUT_FILENO)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut echo_output = argv.into_iter()
|
let mut echo_output = argv
|
||||||
.map(|a| a.0) // Extract the String from the tuple of (String,Span)
|
.into_iter()
|
||||||
.collect::<Vec<_>>()
|
.map(|a| a.0) // Extract the String from the tuple of (String,Span)
|
||||||
.join(" ");
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
if !flags.contains(EchoFlags::NO_NEWLINE) {
|
if !flags.contains(EchoFlags::NO_NEWLINE) {
|
||||||
echo_output.push('\n')
|
echo_output.push('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
write(output_channel, echo_output.as_bytes())?;
|
write(output_channel, echo_output.as_bytes())?;
|
||||||
|
|
||||||
io_frame.unwrap().restore()?;
|
io_frame.unwrap().restore()?;
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_echo_flags(mut opts: Vec<Opt>) -> ShResult<EchoFlags> {
|
pub fn get_echo_flags(mut opts: Vec<Opt>) -> ShResult<EchoFlags> {
|
||||||
let mut flags = EchoFlags::empty();
|
let mut flags = EchoFlags::empty();
|
||||||
|
|
||||||
while let Some(opt) = opts.pop() {
|
while let Some(opt) = opts.pop() {
|
||||||
if !ECHO_OPTS.contains(&opt) {
|
if !ECHO_OPTS.contains(&opt) {
|
||||||
return Err(
|
return Err(ShErr::simple(
|
||||||
ShErr::simple(
|
ShErrKind::ExecFail,
|
||||||
ShErrKind::ExecFail,
|
format!("echo: Unexpected flag '{opt}'"),
|
||||||
format!("echo: Unexpected flag '{opt}'"),
|
));
|
||||||
)
|
}
|
||||||
)
|
let Opt::Short(opt) = opt else { unreachable!() };
|
||||||
}
|
|
||||||
let Opt::Short(opt) = opt else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
|
||||||
|
|
||||||
match opt {
|
match opt {
|
||||||
'n' => flags |= EchoFlags::NO_NEWLINE,
|
'n' => flags |= EchoFlags::NO_NEWLINE,
|
||||||
'r' => flags |= EchoFlags::USE_STDERR,
|
'r' => flags |= EchoFlags::USE_STDERR,
|
||||||
'e' => flags |= EchoFlags::USE_ESCAPE,
|
'e' => flags |= EchoFlags::USE_ESCAPE,
|
||||||
_ => unreachable!()
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(flags)
|
Ok(flags)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,48 @@
|
|||||||
use crate::{jobs::JobBldr, libsh::error::ShResult, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, write_vars}};
|
use crate::{
|
||||||
|
jobs::JobBldr,
|
||||||
|
libsh::error::ShResult,
|
||||||
|
parse::{NdRule, Node},
|
||||||
|
prelude::*,
|
||||||
|
procio::{borrow_fd, IoStack},
|
||||||
|
state::{self, write_vars},
|
||||||
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command {
|
||||||
unreachable!()
|
assignments: _,
|
||||||
};
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the environment variables
|
// Display the environment variables
|
||||||
let mut env_output = env::vars()
|
let mut env_output = env::vars()
|
||||||
.map(|var| format!("{}={}",var.0,var.1)) // Get all of them, zip them into one string
|
.map(|var| format!("{}={}", var.0, var.1)) // Get all of them, zip them into one string
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
env_output.sort(); // Sort them alphabetically
|
env_output.sort(); // Sort them alphabetically
|
||||||
let mut env_output = env_output.join("\n"); // Join them with newlines
|
let mut env_output = env_output.join("\n"); // Join them with newlines
|
||||||
env_output.push('\n'); // Push a final newline
|
env_output.push('\n'); // Push a final newline
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
write(stdout, env_output.as_bytes())?; // Write it
|
write(stdout, env_output.as_bytes())?; // Write it
|
||||||
} else {
|
} else {
|
||||||
for (arg,_) in argv {
|
for (arg, _) in argv {
|
||||||
if let Some((var,val)) = arg.split_once('=') {
|
if let Some((var, val)) = arg.split_once('=') {
|
||||||
write_vars(|v| v.set_var(var, val, true)); // Export an assignment like 'foo=bar'
|
write_vars(|v| v.set_var(var, val, true)); // Export an assignment like
|
||||||
} else {
|
// 'foo=bar'
|
||||||
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if any
|
} else {
|
||||||
}
|
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if
|
||||||
}
|
// any
|
||||||
}
|
}
|
||||||
io_frame.unwrap().restore()?;
|
}
|
||||||
state::set_status(0);
|
}
|
||||||
Ok(())
|
io_frame.unwrap().restore()?;
|
||||||
|
state::set_status(0);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,46 @@
|
|||||||
use crate::{libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*};
|
use crate::{
|
||||||
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
|
parse::{execute::prepare_argv, NdRule, Node},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn flowctl(node: Node, kind: ShErrKind) -> ShResult<()> {
|
pub fn flowctl(node: Node, kind: ShErrKind) -> ShResult<()> {
|
||||||
use ShErrKind::*;
|
use ShErrKind::*;
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command {
|
||||||
unreachable!()
|
assignments: _,
|
||||||
};
|
argv,
|
||||||
let mut code = 0;
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let mut code = 0;
|
||||||
|
|
||||||
let mut argv = prepare_argv(argv)?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let cmd = argv.remove(0).0;
|
let cmd = argv.remove(0).0;
|
||||||
|
|
||||||
if !argv.is_empty() {
|
if !argv.is_empty() {
|
||||||
let (arg,span) = argv
|
let (arg, span) = argv.into_iter().next().unwrap();
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let Ok(status) = arg.parse::<i32>() else {
|
let Ok(status) = arg.parse::<i32>() else {
|
||||||
return Err(
|
return Err(ShErr::full(
|
||||||
ShErr::full(ShErrKind::SyntaxErr, format!("{cmd}: Expected a number"), span)
|
ShErrKind::SyntaxErr,
|
||||||
)
|
format!("{cmd}: Expected a number"),
|
||||||
};
|
span,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
code = status;
|
code = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
flog!(DEBUG,code);
|
flog!(DEBUG, code);
|
||||||
|
|
||||||
let kind = match kind {
|
let kind = match kind {
|
||||||
LoopContinue(_) => LoopContinue(code),
|
LoopContinue(_) => LoopContinue(code),
|
||||||
LoopBreak(_) => LoopBreak(code),
|
LoopBreak(_) => LoopBreak(code),
|
||||||
FuncReturn(_) => FuncReturn(code),
|
FuncReturn(_) => FuncReturn(code),
|
||||||
CleanExit(_) => CleanExit(code),
|
CleanExit(_) => CleanExit(code),
|
||||||
_ => unreachable!()
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Err(ShErr::simple(kind, ""))
|
Err(ShErr::simple(kind, ""))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,183 +1,182 @@
|
|||||||
use crate::{jobs::{JobBldr, JobCmdFlags, JobID}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{lex::Span, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, read_jobs, write_jobs}};
|
use crate::{
|
||||||
|
jobs::{JobBldr, JobCmdFlags, JobID},
|
||||||
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
|
parse::{lex::Span, NdRule, Node},
|
||||||
|
prelude::*,
|
||||||
|
procio::{borrow_fd, IoStack},
|
||||||
|
state::{self, read_jobs, write_jobs},
|
||||||
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub enum JobBehavior {
|
pub enum JobBehavior {
|
||||||
Foregound,
|
Foregound,
|
||||||
Background
|
Background,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShResult<()> {
|
pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let cmd = match behavior {
|
let cmd = match behavior {
|
||||||
JobBehavior::Foregound => "fg",
|
JobBehavior::Foregound => "fg",
|
||||||
JobBehavior::Background => "bg"
|
JobBehavior::Background => "bg",
|
||||||
};
|
};
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command {
|
||||||
unreachable!()
|
assignments: _,
|
||||||
};
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
let (argv,_) = setup_builtin(argv, job, None)?;
|
let (argv, _) = setup_builtin(argv, job, None)?;
|
||||||
let mut argv = argv.into_iter();
|
let mut argv = argv.into_iter();
|
||||||
|
|
||||||
if read_jobs(|j| j.get_fg().is_some()) {
|
if read_jobs(|j| j.get_fg().is_some()) {
|
||||||
return Err(
|
return Err(ShErr::full(
|
||||||
ShErr::full(
|
ShErrKind::InternalErr,
|
||||||
ShErrKind::InternalErr,
|
format!("Somehow called '{}' with an existing foreground job", cmd),
|
||||||
format!("Somehow called '{}' with an existing foreground job",cmd),
|
blame,
|
||||||
blame
|
));
|
||||||
)
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||||
id
|
id
|
||||||
} else {
|
} else {
|
||||||
return Err(
|
return Err(ShErr::full(ShErrKind::ExecFail, "No jobs found", blame));
|
||||||
ShErr::full(
|
};
|
||||||
ShErrKind::ExecFail,
|
|
||||||
"No jobs found",
|
|
||||||
blame
|
|
||||||
)
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let tabid = match argv.next() {
|
let tabid = match argv.next() {
|
||||||
Some((arg,blame)) => parse_job_id(&arg, blame)?,
|
Some((arg, blame)) => parse_job_id(&arg, blame)?,
|
||||||
None => curr_job_id
|
None => curr_job_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut job = write_jobs(|j| {
|
let mut job = write_jobs(|j| {
|
||||||
let id = JobID::TableID(tabid);
|
let id = JobID::TableID(tabid);
|
||||||
let query_result = j.query(id.clone());
|
let query_result = j.query(id.clone());
|
||||||
if query_result.is_some() {
|
if query_result.is_some() {
|
||||||
Ok(j.remove_job(id.clone()).unwrap())
|
Ok(j.remove_job(id.clone()).unwrap())
|
||||||
} else {
|
} else {
|
||||||
Err(
|
Err(ShErr::full(
|
||||||
ShErr::full(
|
ShErrKind::ExecFail,
|
||||||
ShErrKind::ExecFail,
|
format!("Job id `{}' not found", tabid),
|
||||||
format!("Job id `{}' not found", tabid),
|
blame,
|
||||||
blame
|
))
|
||||||
)
|
}
|
||||||
)
|
})?;
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
job.killpg(Signal::SIGCONT)?;
|
job.killpg(Signal::SIGCONT)?;
|
||||||
|
|
||||||
match behavior {
|
match behavior {
|
||||||
JobBehavior::Foregound => {
|
JobBehavior::Foregound => {
|
||||||
write_jobs(|j| j.new_fg(job))?;
|
write_jobs(|j| j.new_fg(job))?;
|
||||||
}
|
}
|
||||||
JobBehavior::Background => {
|
JobBehavior::Background => {
|
||||||
let job_order = read_jobs(|j| j.order().to_vec());
|
let job_order = read_jobs(|j| j.order().to_vec());
|
||||||
write(borrow_fd(1), job.display(&job_order, JobCmdFlags::PIDS).as_bytes())?;
|
write(
|
||||||
write_jobs(|j| j.insert_job(job, true))?;
|
borrow_fd(1),
|
||||||
}
|
job.display(&job_order, JobCmdFlags::PIDS).as_bytes(),
|
||||||
}
|
)?;
|
||||||
state::set_status(0);
|
write_jobs(|j| j.insert_job(job, true))?;
|
||||||
Ok(())
|
}
|
||||||
|
}
|
||||||
|
state::set_status(0);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
||||||
if arg.starts_with('%') {
|
if arg.starts_with('%') {
|
||||||
let arg = arg.strip_prefix('%').unwrap();
|
let arg = arg.strip_prefix('%').unwrap();
|
||||||
if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
||||||
Ok(arg.parse::<usize>().unwrap())
|
Ok(arg.parse::<usize>().unwrap())
|
||||||
} else {
|
} else {
|
||||||
let result = write_jobs(|j| {
|
let result = write_jobs(|j| {
|
||||||
let query_result = j.query(JobID::Command(arg.into()));
|
let query_result = j.query(JobID::Command(arg.into()));
|
||||||
query_result.map(|job| job.tabid().unwrap())
|
query_result.map(|job| job.tabid().unwrap())
|
||||||
});
|
});
|
||||||
match result {
|
match result {
|
||||||
Some(id) => Ok(id),
|
Some(id) => Ok(id),
|
||||||
None => Err(
|
None => Err(ShErr::full(
|
||||||
ShErr::full(
|
ShErrKind::InternalErr,
|
||||||
ShErrKind::InternalErr,
|
"Found a job but no table id in parse_job_id()",
|
||||||
"Found a job but no table id in parse_job_id()",
|
blame,
|
||||||
blame
|
)),
|
||||||
)
|
}
|
||||||
)
|
}
|
||||||
}
|
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
||||||
}
|
let result = write_jobs(|j| {
|
||||||
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
let pgid_query_result = j.query(JobID::Pgid(Pid::from_raw(arg.parse::<i32>().unwrap())));
|
||||||
let result = write_jobs(|j| {
|
if let Some(job) = pgid_query_result {
|
||||||
let pgid_query_result = j.query(JobID::Pgid(Pid::from_raw(arg.parse::<i32>().unwrap())));
|
return Some(job.tabid().unwrap());
|
||||||
if let Some(job) = pgid_query_result {
|
}
|
||||||
return Some(job.tabid().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
if arg.parse::<i32>().unwrap() > 0 {
|
if arg.parse::<i32>().unwrap() > 0 {
|
||||||
let table_id_query_result = j.query(JobID::TableID(arg.parse::<usize>().unwrap()));
|
let table_id_query_result = j.query(JobID::TableID(arg.parse::<usize>().unwrap()));
|
||||||
return table_id_query_result.map(|job| job.tabid().unwrap());
|
return table_id_query_result.map(|job| job.tabid().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
});
|
});
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Some(id) => Ok(id),
|
Some(id) => Ok(id),
|
||||||
None => Err(
|
None => Err(ShErr::full(
|
||||||
ShErr::full(
|
ShErrKind::InternalErr,
|
||||||
ShErrKind::InternalErr,
|
"Found a job but no table id in parse_job_id()",
|
||||||
"Found a job but no table id in parse_job_id()",
|
blame,
|
||||||
blame
|
)),
|
||||||
)
|
}
|
||||||
)
|
} else {
|
||||||
}
|
Err(ShErr::full(
|
||||||
} else {
|
ShErrKind::SyntaxErr,
|
||||||
Err(
|
format!("Invalid fd arg: {}", arg),
|
||||||
ShErr::full(
|
blame,
|
||||||
ShErrKind::SyntaxErr,
|
))
|
||||||
format!("Invalid fd arg: {}", arg),
|
}
|
||||||
blame
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command {
|
||||||
unreachable!()
|
assignments: _,
|
||||||
};
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let mut flags = JobCmdFlags::empty();
|
let mut flags = JobCmdFlags::empty();
|
||||||
for (arg,span) in argv {
|
for (arg, span) in argv {
|
||||||
let mut chars = arg.chars().peekable();
|
let mut chars = arg.chars().peekable();
|
||||||
if chars.peek().is_none_or(|ch| *ch != '-') {
|
if chars.peek().is_none_or(|ch| *ch != '-') {
|
||||||
return Err(
|
return Err(ShErr::full(
|
||||||
ShErr::full(
|
ShErrKind::SyntaxErr,
|
||||||
ShErrKind::SyntaxErr,
|
"Invalid flag in jobs call",
|
||||||
"Invalid flag in jobs call",
|
span,
|
||||||
span
|
));
|
||||||
)
|
}
|
||||||
)
|
chars.next();
|
||||||
}
|
for ch in chars {
|
||||||
chars.next();
|
let flag = match ch {
|
||||||
for ch in chars {
|
'l' => JobCmdFlags::LONG,
|
||||||
let flag = match ch {
|
'p' => JobCmdFlags::PIDS,
|
||||||
'l' => JobCmdFlags::LONG,
|
'n' => JobCmdFlags::NEW_ONLY,
|
||||||
'p' => JobCmdFlags::PIDS,
|
'r' => JobCmdFlags::RUNNING,
|
||||||
'n' => JobCmdFlags::NEW_ONLY,
|
's' => JobCmdFlags::STOPPED,
|
||||||
'r' => JobCmdFlags::RUNNING,
|
_ => {
|
||||||
's' => JobCmdFlags::STOPPED,
|
return Err(ShErr::full(
|
||||||
_ => return Err(
|
ShErrKind::SyntaxErr,
|
||||||
ShErr::full(
|
"Invalid flag in jobs call",
|
||||||
ShErrKind::SyntaxErr,
|
span,
|
||||||
"Invalid flag in jobs call",
|
))
|
||||||
span
|
}
|
||||||
)
|
};
|
||||||
)
|
flags |= flag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write_jobs(|j| j.print_jobs(flags))?;
|
||||||
|
io_frame.unwrap().restore()?;
|
||||||
|
state::set_status(0);
|
||||||
|
|
||||||
};
|
Ok(())
|
||||||
flags |= flag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write_jobs(|j| j.print_jobs(flags))?;
|
|
||||||
io_frame.unwrap().restore()?;
|
|
||||||
state::set_status(0);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,45 @@
|
|||||||
use nix::unistd::Pid;
|
use nix::unistd::Pid;
|
||||||
|
|
||||||
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{execute::prepare_argv, lex::{Span, Tk}, Redir}, procio::{IoFrame, IoStack}};
|
use crate::{
|
||||||
|
jobs::{ChildProc, JobBldr},
|
||||||
|
libsh::error::ShResult,
|
||||||
|
parse::{
|
||||||
|
execute::prepare_argv,
|
||||||
|
lex::{Span, Tk},
|
||||||
|
Redir,
|
||||||
|
},
|
||||||
|
procio::{IoFrame, IoStack},
|
||||||
|
};
|
||||||
|
|
||||||
pub mod echo;
|
|
||||||
pub mod cd;
|
|
||||||
pub mod export;
|
|
||||||
pub mod pwd;
|
|
||||||
pub mod source;
|
|
||||||
pub mod shift;
|
|
||||||
pub mod jobctl;
|
|
||||||
pub mod alias;
|
pub mod alias;
|
||||||
|
pub mod cd;
|
||||||
|
pub mod echo;
|
||||||
|
pub mod export;
|
||||||
pub mod flowctl;
|
pub mod flowctl;
|
||||||
pub mod zoltraak;
|
pub mod jobctl;
|
||||||
|
pub mod pwd;
|
||||||
|
pub mod shift;
|
||||||
pub mod shopt;
|
pub mod shopt;
|
||||||
pub mod test; // [[ ]] thing
|
pub mod source;
|
||||||
|
pub mod test;
|
||||||
|
pub mod zoltraak; // [[ ]] thing
|
||||||
|
|
||||||
pub const BUILTINS: [&str;19] = [
|
pub const BUILTINS: [&str; 19] = [
|
||||||
"echo",
|
"echo", "cd", "export", "pwd", "source", "shift", "jobs", "fg", "bg", "alias", "unalias",
|
||||||
"cd",
|
"return", "break", "continue", "exit", "zoltraak", "shopt", "builtin", "command",
|
||||||
"export",
|
|
||||||
"pwd",
|
|
||||||
"source",
|
|
||||||
"shift",
|
|
||||||
"jobs",
|
|
||||||
"fg",
|
|
||||||
"bg",
|
|
||||||
"alias",
|
|
||||||
"unalias",
|
|
||||||
"return",
|
|
||||||
"break",
|
|
||||||
"continue",
|
|
||||||
"exit",
|
|
||||||
"zoltraak",
|
|
||||||
"shopt",
|
|
||||||
"builtin",
|
|
||||||
"command",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Sets up a builtin command
|
/// Sets up a builtin command
|
||||||
///
|
///
|
||||||
/// Prepares a builtin for execution by processing arguments, setting up redirections, and registering the command as a child process in the given `JobBldr`
|
/// Prepares a builtin for execution by processing arguments, setting up
|
||||||
|
/// redirections, and registering the command as a child process in the given
|
||||||
|
/// `JobBldr`
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// * argv - The vector of raw argument tokens
|
/// * argv - The vector of raw argument tokens
|
||||||
/// * job - A mutable reference to a `JobBldr`
|
/// * job - A mutable reference to a `JobBldr`
|
||||||
/// * io_mode - An optional 2-tuple consisting of a mutable reference to an `IoStack` and a vector of `Redirs`
|
/// * io_mode - An optional 2-tuple consisting of a mutable reference to an
|
||||||
|
/// `IoStack` and a vector of `Redirs`
|
||||||
///
|
///
|
||||||
/// # Behavior
|
/// # Behavior
|
||||||
/// * Cleans, expands, and word splits the arg vector
|
/// * Cleans, expands, and word splits the arg vector
|
||||||
@@ -56,36 +51,39 @@ pub const BUILTINS: [&str;19] = [
|
|||||||
/// * The popped `IoFrame`, if any
|
/// * The popped `IoFrame`, if any
|
||||||
///
|
///
|
||||||
/// # Notes
|
/// # Notes
|
||||||
/// * If redirections are given to this function, the caller must call `IoFrame.restore()` on the returned `IoFrame`
|
/// * If redirections are given to this function, the caller must call
|
||||||
/// * If redirections are given, the second field of the resulting tuple will *always* be `Some()`
|
/// `IoFrame.restore()` on the returned `IoFrame`
|
||||||
|
/// * If redirections are given, the second field of the resulting tuple will
|
||||||
|
/// *always* be `Some()`
|
||||||
/// * If no redirections are given, the second field will *always* be `None`
|
/// * If no redirections are given, the second field will *always* be `None`
|
||||||
type SetupReturns = ShResult<(Vec<(String,Span)>, Option<IoFrame>)>;
|
type SetupReturns = ShResult<(Vec<(String, Span)>, Option<IoFrame>)>;
|
||||||
pub fn setup_builtin(
|
pub fn setup_builtin(
|
||||||
argv: Vec<Tk>,
|
argv: Vec<Tk>,
|
||||||
job: &mut JobBldr,
|
job: &mut JobBldr,
|
||||||
io_mode: Option<(&mut IoStack,Vec<Redir>)>,
|
io_mode: Option<(&mut IoStack, Vec<Redir>)>,
|
||||||
) -> SetupReturns {
|
) -> SetupReturns {
|
||||||
let mut argv: Vec<(String,Span)> = prepare_argv(argv)?;
|
let mut argv: Vec<(String, Span)> = prepare_argv(argv)?;
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let child_pgid = if let Some(pgid) = job.pgid() {
|
||||||
pgid
|
pgid
|
||||||
} else {
|
} else {
|
||||||
job.set_pgid(Pid::this());
|
job.set_pgid(Pid::this());
|
||||||
Pid::this()
|
Pid::this()
|
||||||
};
|
};
|
||||||
let cmd_name = argv.remove(0).0;
|
let cmd_name = argv.remove(0).0;
|
||||||
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
|
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
|
||||||
job.push_child(child);
|
job.push_child(child);
|
||||||
|
|
||||||
let io_frame = if let Some((io_stack,redirs)) = io_mode {
|
let io_frame = if let Some((io_stack, redirs)) = io_mode {
|
||||||
io_stack.append_to_frame(redirs);
|
io_stack.append_to_frame(redirs);
|
||||||
let mut io_frame = io_stack.pop_frame();
|
let mut io_frame = io_stack.pop_frame();
|
||||||
io_frame.redirect()?;
|
io_frame.redirect()?;
|
||||||
Some(io_frame)
|
Some(io_frame)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// We return the io_frame because the caller needs to also call io_frame.restore()
|
// We return the io_frame because the caller needs to also call
|
||||||
Ok((argv,io_frame))
|
// io_frame.restore()
|
||||||
|
Ok((argv, io_frame))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,32 @@
|
|||||||
use crate::{jobs::JobBldr, libsh::error::ShResult, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state};
|
use crate::{
|
||||||
|
jobs::JobBldr,
|
||||||
|
libsh::error::ShResult,
|
||||||
|
parse::{NdRule, Node},
|
||||||
|
prelude::*,
|
||||||
|
procio::{borrow_fd, IoStack},
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub fn pwd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn pwd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command {
|
||||||
unreachable!()
|
assignments: _,
|
||||||
};
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
let (_,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
let (_, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
|
||||||
let mut curr_dir = env::current_dir().unwrap().to_str().unwrap().to_string();
|
let mut curr_dir = env::current_dir().unwrap().to_str().unwrap().to_string();
|
||||||
curr_dir.push('\n');
|
curr_dir.push('\n');
|
||||||
write(stdout, curr_dir.as_bytes())?;
|
write(stdout, curr_dir.as_bytes())?;
|
||||||
|
|
||||||
io_frame.unwrap().restore().unwrap();
|
io_frame.unwrap().restore().unwrap();
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,37 @@
|
|||||||
use crate::{jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, state::{self, write_vars}};
|
use crate::{
|
||||||
|
jobs::JobBldr,
|
||||||
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
|
parse::{NdRule, Node},
|
||||||
|
state::{self, write_vars},
|
||||||
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command {
|
||||||
unreachable!()
|
assignments: _,
|
||||||
};
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
let (argv,_) = setup_builtin(argv, job, None)?;
|
let (argv, _) = setup_builtin(argv, job, None)?;
|
||||||
let mut argv = argv.into_iter();
|
let mut argv = argv.into_iter();
|
||||||
|
|
||||||
if let Some((arg,span)) = argv.next() {
|
if let Some((arg, span)) = argv.next() {
|
||||||
let Ok(count) = arg.parse::<usize>() else {
|
let Ok(count) = arg.parse::<usize>() else {
|
||||||
return Err(
|
return Err(ShErr::full(
|
||||||
ShErr::full(
|
ShErrKind::ExecFail,
|
||||||
ShErrKind::ExecFail,
|
"Expected a number in shift args",
|
||||||
"Expected a number in shift args",
|
span,
|
||||||
span
|
));
|
||||||
)
|
};
|
||||||
)
|
for _ in 0..count {
|
||||||
};
|
write_vars(|v| v.fpop_arg());
|
||||||
for _ in 0..count {
|
}
|
||||||
write_vars(|v| v.fpop_arg());
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,41 @@
|
|||||||
use crate::{jobs::JobBldr, libsh::error::{ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::write_shopts};
|
use crate::{
|
||||||
|
jobs::JobBldr,
|
||||||
|
libsh::error::{ShResult, ShResultExt},
|
||||||
|
parse::{NdRule, Node},
|
||||||
|
prelude::*,
|
||||||
|
procio::{borrow_fd, IoStack},
|
||||||
|
state::write_shopts,
|
||||||
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command {
|
||||||
unreachable!()
|
assignments: _,
|
||||||
};
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let mut io_frame = io_frame.unwrap();
|
let mut io_frame = io_frame.unwrap();
|
||||||
io_frame.redirect()?;
|
io_frame.redirect()?;
|
||||||
for (arg,span) in argv {
|
for (arg, span) in argv {
|
||||||
let Some(mut output) = write_shopts(|s| s.query(&arg)).blame(span)? else {
|
let Some(mut output) = write_shopts(|s| s.query(&arg)).blame(span)? else {
|
||||||
continue
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let output_channel = borrow_fd(STDOUT_FILENO);
|
let output_channel = borrow_fd(STDOUT_FILENO);
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
|
|
||||||
if let Err(e) = write(output_channel, output.as_bytes()) {
|
if let Err(e) = write(output_channel, output.as_bytes()) {
|
||||||
io_frame.restore()?;
|
io_frame.restore()?;
|
||||||
return Err(e.into())
|
return Err(e.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
io_frame.restore()?;
|
io_frame.restore()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,43 @@
|
|||||||
use crate::{jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, state::{self, source_file}};
|
use crate::{
|
||||||
|
jobs::JobBldr,
|
||||||
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
|
parse::{NdRule, Node},
|
||||||
|
prelude::*,
|
||||||
|
state::{self, source_file},
|
||||||
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command {
|
||||||
unreachable!()
|
assignments: _,
|
||||||
};
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
let (argv,_) = setup_builtin(argv, job, None)?;
|
let (argv, _) = setup_builtin(argv, job, None)?;
|
||||||
|
|
||||||
for (arg,span) in argv {
|
for (arg, span) in argv {
|
||||||
let path = PathBuf::from(arg);
|
let path = PathBuf::from(arg);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(
|
return Err(ShErr::full(
|
||||||
ShErr::full(
|
ShErrKind::ExecFail,
|
||||||
ShErrKind::ExecFail,
|
format!("source: File '{}' not found", path.display()),
|
||||||
format!("source: File '{}' not found",path.display()),
|
span,
|
||||||
span
|
));
|
||||||
)
|
}
|
||||||
);
|
if !path.is_file() {
|
||||||
}
|
return Err(ShErr::full(
|
||||||
if !path.is_file() {
|
ShErrKind::ExecFail,
|
||||||
return Err(
|
format!("source: Given path '{}' is not a file", path.display()),
|
||||||
ShErr::full(
|
span,
|
||||||
ShErrKind::ExecFail,
|
));
|
||||||
format!("source: Given path '{}' is not a file",path.display()),
|
}
|
||||||
span
|
source_file(path)?;
|
||||||
)
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
source_file(path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,328 +1,321 @@
|
|||||||
use std::{fs::metadata, path::PathBuf, str::FromStr};
|
use std::{fs::metadata, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
use nix::{sys::stat::{self, SFlag}, unistd::AccessFlags};
|
use nix::{
|
||||||
|
sys::stat::{self, SFlag},
|
||||||
|
unistd::AccessFlags,
|
||||||
|
};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::{libsh::error::{ShErr, ShErrKind, ShResult},prelude::*, parse::{ConjunctOp, NdRule, Node, TestCase, TEST_UNARY_OPS}};
|
use crate::{
|
||||||
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
|
parse::{ConjunctOp, NdRule, Node, TestCase, TEST_UNARY_OPS},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum UnaryOp {
|
pub enum UnaryOp {
|
||||||
Exists, // -e
|
Exists, // -e
|
||||||
Directory, // -d
|
Directory, // -d
|
||||||
File, // -f
|
File, // -f
|
||||||
Symlink, // -h or -L
|
Symlink, // -h or -L
|
||||||
Readable, // -r
|
Readable, // -r
|
||||||
Writable, // -w
|
Writable, // -w
|
||||||
Executable, // -x
|
Executable, // -x
|
||||||
NonEmpty, // -s
|
NonEmpty, // -s
|
||||||
NamedPipe, // -p
|
NamedPipe, // -p
|
||||||
Socket, // -S
|
Socket, // -S
|
||||||
BlockSpecial, // -b
|
BlockSpecial, // -b
|
||||||
CharSpecial, // -c
|
CharSpecial, // -c
|
||||||
Sticky, // -k
|
Sticky, // -k
|
||||||
UIDOwner, // -O
|
UIDOwner, // -O
|
||||||
GIDOwner, // -G
|
GIDOwner, // -G
|
||||||
ModifiedSinceStatusChange, // -N
|
ModifiedSinceStatusChange, // -N
|
||||||
SetUID, // -u
|
SetUID, // -u
|
||||||
SetGID, // -g
|
SetGID, // -g
|
||||||
Terminal, // -t
|
Terminal, // -t
|
||||||
NonNull, // -n
|
NonNull, // -n
|
||||||
Null, // -z
|
Null, // -z
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for UnaryOp {
|
impl FromStr for UnaryOp {
|
||||||
type Err = ShErr;
|
type Err = ShErr;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s {
|
match s {
|
||||||
"-e" => Ok(Self::Exists),
|
"-e" => Ok(Self::Exists),
|
||||||
"-d" => Ok(Self::Directory),
|
"-d" => Ok(Self::Directory),
|
||||||
"-f" => Ok(Self::File),
|
"-f" => Ok(Self::File),
|
||||||
"-h" | "-L" => Ok(Self::Symlink), // -h or -L
|
"-h" | "-L" => Ok(Self::Symlink), // -h or -L
|
||||||
"-r" => Ok(Self::Readable),
|
"-r" => Ok(Self::Readable),
|
||||||
"-w" => Ok(Self::Writable),
|
"-w" => Ok(Self::Writable),
|
||||||
"-x" => Ok(Self::Executable),
|
"-x" => Ok(Self::Executable),
|
||||||
"-s" => Ok(Self::NonEmpty),
|
"-s" => Ok(Self::NonEmpty),
|
||||||
"-p" => Ok(Self::NamedPipe),
|
"-p" => Ok(Self::NamedPipe),
|
||||||
"-S" => Ok(Self::Socket),
|
"-S" => Ok(Self::Socket),
|
||||||
"-b" => Ok(Self::BlockSpecial),
|
"-b" => Ok(Self::BlockSpecial),
|
||||||
"-c" => Ok(Self::CharSpecial),
|
"-c" => Ok(Self::CharSpecial),
|
||||||
"-k" => Ok(Self::Sticky),
|
"-k" => Ok(Self::Sticky),
|
||||||
"-O" => Ok(Self::UIDOwner),
|
"-O" => Ok(Self::UIDOwner),
|
||||||
"-G" => Ok(Self::GIDOwner),
|
"-G" => Ok(Self::GIDOwner),
|
||||||
"-N" => Ok(Self::ModifiedSinceStatusChange),
|
"-N" => Ok(Self::ModifiedSinceStatusChange),
|
||||||
"-u" => Ok(Self::SetUID),
|
"-u" => Ok(Self::SetUID),
|
||||||
"-g" => Ok(Self::SetGID),
|
"-g" => Ok(Self::SetGID),
|
||||||
"-t" => Ok(Self::Terminal),
|
"-t" => Ok(Self::Terminal),
|
||||||
"-n" => Ok(Self::NonNull),
|
"-n" => Ok(Self::NonNull),
|
||||||
"-z" => Ok(Self::Null),
|
"-z" => Ok(Self::Null),
|
||||||
_ => Err(ShErr::Simple { kind: ShErrKind::SyntaxErr, msg: "Invalid test operator".into(), notes: vec![] })
|
_ => Err(ShErr::Simple {
|
||||||
}
|
kind: ShErrKind::SyntaxErr,
|
||||||
}
|
msg: "Invalid test operator".into(),
|
||||||
|
notes: vec![],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TestOp {
|
pub enum TestOp {
|
||||||
Unary(UnaryOp),
|
Unary(UnaryOp),
|
||||||
StringEq, // ==
|
StringEq, // ==
|
||||||
StringNeq, // !=
|
StringNeq, // !=
|
||||||
IntEq, // -eq
|
IntEq, // -eq
|
||||||
IntNeq, // -ne
|
IntNeq, // -ne
|
||||||
IntGt, // -gt
|
IntGt, // -gt
|
||||||
IntLt, // -lt
|
IntLt, // -lt
|
||||||
IntGe, // -ge
|
IntGe, // -ge
|
||||||
IntLe, // -le
|
IntLe, // -le
|
||||||
RegexMatch, // =~
|
RegexMatch, // =~
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for TestOp {
|
impl FromStr for TestOp {
|
||||||
type Err = ShErr;
|
type Err = ShErr;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s {
|
match s {
|
||||||
"==" => Ok(Self::StringEq),
|
"==" => Ok(Self::StringEq),
|
||||||
"!=" => Ok(Self::StringNeq),
|
"!=" => Ok(Self::StringNeq),
|
||||||
"=~" => Ok(Self::RegexMatch),
|
"=~" => Ok(Self::RegexMatch),
|
||||||
"-eq" => Ok(Self::IntEq),
|
"-eq" => Ok(Self::IntEq),
|
||||||
"-ne" => Ok(Self::IntNeq),
|
"-ne" => Ok(Self::IntNeq),
|
||||||
"-gt" => Ok(Self::IntGt),
|
"-gt" => Ok(Self::IntGt),
|
||||||
"-lt" => Ok(Self::IntLt),
|
"-lt" => Ok(Self::IntLt),
|
||||||
"-ge" => Ok(Self::IntGe),
|
"-ge" => Ok(Self::IntGe),
|
||||||
"-le" => Ok(Self::IntLe),
|
"-le" => Ok(Self::IntLe),
|
||||||
_ if TEST_UNARY_OPS.contains(&s) => {
|
_ if TEST_UNARY_OPS.contains(&s) => Ok(Self::Unary(s.parse::<UnaryOp>()?)),
|
||||||
Ok(Self::Unary(s.parse::<UnaryOp>()?))
|
_ => Err(ShErr::Simple {
|
||||||
}
|
kind: ShErrKind::SyntaxErr,
|
||||||
_ => Err(ShErr::Simple { kind: ShErrKind::SyntaxErr, msg: "Invalid test operator".into(), notes: vec![] })
|
msg: "Invalid test operator".into(),
|
||||||
}
|
notes: vec![],
|
||||||
}
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_posix_classes(pat: &str) -> String {
|
fn replace_posix_classes(pat: &str) -> String {
|
||||||
pat.replace("[[:alnum:]]", r"[A-Za-z0-9]")
|
pat
|
||||||
.replace("[[:alpha:]]", r"[A-Za-z]")
|
.replace("[[:alnum:]]", r"[A-Za-z0-9]")
|
||||||
.replace("[[:blank:]]", r"[ \t]")
|
.replace("[[:alpha:]]", r"[A-Za-z]")
|
||||||
.replace("[[:cntrl:]]", r"[\x00-\x1F\x7F]")
|
.replace("[[:blank:]]", r"[ \t]")
|
||||||
.replace("[[:digit:]]", r"[0-9]")
|
.replace("[[:cntrl:]]", r"[\x00-\x1F\x7F]")
|
||||||
.replace("[[:graph:]]", r"[!-~]")
|
.replace("[[:digit:]]", r"[0-9]")
|
||||||
.replace("[[:lower:]]", r"[a-z]")
|
.replace("[[:graph:]]", r"[!-~]")
|
||||||
.replace("[[:print:]]", r"[\x20-\x7E]")
|
.replace("[[:lower:]]", r"[a-z]")
|
||||||
.replace("[[:space:]]", r"[ \t\r\n\x0B\x0C]") // vertical tab (\x0B), form feed (\x0C)
|
.replace("[[:print:]]", r"[\x20-\x7E]")
|
||||||
.replace("[[:upper:]]", r"[A-Z]")
|
.replace("[[:space:]]", r"[ \t\r\n\x0B\x0C]") // vertical tab (\x0B), form feed (\x0C)
|
||||||
.replace("[[:xdigit:]]", r"[0-9A-Fa-f]")
|
.replace("[[:upper:]]", r"[A-Z]")
|
||||||
|
.replace("[[:xdigit:]]", r"[0-9A-Fa-f]")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn double_bracket_test(node: Node) -> ShResult<bool> {
|
pub fn double_bracket_test(node: Node) -> ShResult<bool> {
|
||||||
let err_span = node.get_span();
|
let err_span = node.get_span();
|
||||||
let NdRule::Test { cases } = node.class else {
|
let NdRule::Test { cases } = node.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let mut last_result = false;
|
let mut last_result = false;
|
||||||
let mut conjunct_op: Option<ConjunctOp>;
|
let mut conjunct_op: Option<ConjunctOp>;
|
||||||
|
|
||||||
for case in cases {
|
for case in cases {
|
||||||
let result = match case {
|
let result = match case {
|
||||||
TestCase::Unary { operator, operand, conjunct } => {
|
TestCase::Unary {
|
||||||
let operand = operand.expand()?.get_words().join(" ");
|
operator,
|
||||||
conjunct_op = conjunct;
|
operand,
|
||||||
let TestOp::Unary(op) = TestOp::from_str(operator.as_str())? else {
|
conjunct,
|
||||||
return Err(
|
} => {
|
||||||
ShErr::Full { kind: ShErrKind::SyntaxErr, msg: "Invalid unary operator".into(), notes: vec![], span: err_span }
|
let operand = operand.expand()?.get_words().join(" ");
|
||||||
)
|
conjunct_op = conjunct;
|
||||||
};
|
let TestOp::Unary(op) = TestOp::from_str(operator.as_str())? else {
|
||||||
match op {
|
return Err(ShErr::Full {
|
||||||
UnaryOp::Exists => {
|
kind: ShErrKind::SyntaxErr,
|
||||||
let path = PathBuf::from(operand.as_str());
|
msg: "Invalid unary operator".into(),
|
||||||
path.exists()
|
notes: vec![],
|
||||||
}
|
span: err_span,
|
||||||
UnaryOp::Directory => {
|
});
|
||||||
let path = PathBuf::from(operand.as_str());
|
};
|
||||||
if path.exists() {
|
match op {
|
||||||
path.metadata()
|
UnaryOp::Exists => {
|
||||||
.unwrap()
|
let path = PathBuf::from(operand.as_str());
|
||||||
.is_dir()
|
path.exists()
|
||||||
} else {
|
}
|
||||||
false
|
UnaryOp::Directory => {
|
||||||
}
|
let path = PathBuf::from(operand.as_str());
|
||||||
}
|
if path.exists() {
|
||||||
UnaryOp::File => {
|
path.metadata().unwrap().is_dir()
|
||||||
let path = PathBuf::from(operand.as_str());
|
} else {
|
||||||
if path.exists() {
|
false
|
||||||
path.metadata()
|
}
|
||||||
.unwrap()
|
}
|
||||||
.is_file()
|
UnaryOp::File => {
|
||||||
} else {
|
let path = PathBuf::from(operand.as_str());
|
||||||
false
|
if path.exists() {
|
||||||
}
|
path.metadata().unwrap().is_file()
|
||||||
}
|
} else {
|
||||||
UnaryOp::Symlink => {
|
false
|
||||||
let path = PathBuf::from(operand.as_str());
|
}
|
||||||
if path.exists() {
|
}
|
||||||
path.metadata()
|
UnaryOp::Symlink => {
|
||||||
.unwrap()
|
let path = PathBuf::from(operand.as_str());
|
||||||
.file_type()
|
if path.exists() {
|
||||||
.is_symlink()
|
path.metadata().unwrap().file_type().is_symlink()
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UnaryOp::Readable => nix::unistd::access(operand.as_str(), AccessFlags::R_OK).is_ok(),
|
UnaryOp::Readable => nix::unistd::access(operand.as_str(), AccessFlags::R_OK).is_ok(),
|
||||||
UnaryOp::Writable => nix::unistd::access(operand.as_str(), AccessFlags::W_OK).is_ok(),
|
UnaryOp::Writable => nix::unistd::access(operand.as_str(), AccessFlags::W_OK).is_ok(),
|
||||||
UnaryOp::Executable => nix::unistd::access(operand.as_str(), AccessFlags::X_OK).is_ok(),
|
UnaryOp::Executable => nix::unistd::access(operand.as_str(), AccessFlags::X_OK).is_ok(),
|
||||||
UnaryOp::NonEmpty => {
|
UnaryOp::NonEmpty => match metadata(operand.as_str()) {
|
||||||
match metadata(operand.as_str()) {
|
Ok(meta) => meta.len() > 0,
|
||||||
Ok(meta) => meta.len() > 0,
|
Err(_) => false,
|
||||||
Err(_) => false
|
},
|
||||||
}
|
UnaryOp::NamedPipe => match stat::stat(operand.as_str()) {
|
||||||
}
|
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFIFO),
|
||||||
UnaryOp::NamedPipe => {
|
Err(_) => false,
|
||||||
match stat::stat(operand.as_str()) {
|
},
|
||||||
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFIFO),
|
UnaryOp::Socket => match stat::stat(operand.as_str()) {
|
||||||
Err(_) => false,
|
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFSOCK),
|
||||||
}
|
Err(_) => false,
|
||||||
}
|
},
|
||||||
UnaryOp::Socket => {
|
UnaryOp::BlockSpecial => match stat::stat(operand.as_str()) {
|
||||||
match stat::stat(operand.as_str()) {
|
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFBLK),
|
||||||
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFSOCK),
|
Err(_) => false,
|
||||||
Err(_) => false,
|
},
|
||||||
}
|
UnaryOp::CharSpecial => match stat::stat(operand.as_str()) {
|
||||||
}
|
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFCHR),
|
||||||
UnaryOp::BlockSpecial => {
|
Err(_) => false,
|
||||||
match stat::stat(operand.as_str()) {
|
},
|
||||||
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFBLK),
|
UnaryOp::Sticky => match stat::stat(operand.as_str()) {
|
||||||
Err(_) => false,
|
Ok(stat) => stat.st_mode & nix::libc::S_ISVTX != 0,
|
||||||
}
|
Err(_) => false,
|
||||||
}
|
},
|
||||||
UnaryOp::CharSpecial => {
|
UnaryOp::UIDOwner => match stat::stat(operand.as_str()) {
|
||||||
match stat::stat(operand.as_str()) {
|
Ok(stat) => stat.st_uid == nix::unistd::geteuid().as_raw(),
|
||||||
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFCHR),
|
Err(_) => false,
|
||||||
Err(_) => false,
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
UnaryOp::Sticky => {
|
|
||||||
match stat::stat(operand.as_str()) {
|
|
||||||
Ok(stat) => stat.st_mode & nix::libc::S_ISVTX != 0,
|
|
||||||
Err(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UnaryOp::UIDOwner => {
|
|
||||||
match stat::stat(operand.as_str()) {
|
|
||||||
Ok(stat) => stat.st_uid == nix::unistd::geteuid().as_raw(),
|
|
||||||
Err(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UnaryOp::GIDOwner => {
|
UnaryOp::GIDOwner => match stat::stat(operand.as_str()) {
|
||||||
match stat::stat(operand.as_str()) {
|
Ok(stat) => stat.st_gid == nix::unistd::getegid().as_raw(),
|
||||||
Ok(stat) => stat.st_gid == nix::unistd::getegid().as_raw(),
|
Err(_) => false,
|
||||||
Err(_) => false,
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UnaryOp::ModifiedSinceStatusChange => {
|
UnaryOp::ModifiedSinceStatusChange => match stat::stat(operand.as_str()) {
|
||||||
match stat::stat(operand.as_str()) {
|
Ok(stat) => stat.st_mtime > stat.st_ctime,
|
||||||
Ok(stat) => stat.st_mtime > stat.st_ctime,
|
Err(_) => false,
|
||||||
Err(_) => false,
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UnaryOp::SetUID => {
|
UnaryOp::SetUID => match stat::stat(operand.as_str()) {
|
||||||
match stat::stat(operand.as_str()) {
|
Ok(stat) => stat.st_mode & nix::libc::S_ISUID != 0,
|
||||||
Ok(stat) => stat.st_mode & nix::libc::S_ISUID != 0,
|
Err(_) => false,
|
||||||
Err(_) => false,
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UnaryOp::SetGID => {
|
UnaryOp::SetGID => match stat::stat(operand.as_str()) {
|
||||||
match stat::stat(operand.as_str()) {
|
Ok(stat) => stat.st_mode & nix::libc::S_ISGID != 0,
|
||||||
Ok(stat) => stat.st_mode & nix::libc::S_ISGID != 0,
|
Err(_) => false,
|
||||||
Err(_) => false,
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UnaryOp::Terminal => {
|
UnaryOp::Terminal => match operand.as_str().parse::<nix::libc::c_int>() {
|
||||||
match operand.as_str().parse::<nix::libc::c_int>() {
|
Ok(fd) => unsafe { nix::libc::isatty(fd) == 1 },
|
||||||
Ok(fd) => unsafe { nix::libc::isatty(fd) == 1 },
|
Err(_) => false,
|
||||||
Err(_) => false,
|
},
|
||||||
}
|
UnaryOp::NonNull => !operand.is_empty(),
|
||||||
}
|
UnaryOp::Null => operand.is_empty(),
|
||||||
UnaryOp::NonNull => !operand.is_empty(),
|
}
|
||||||
UnaryOp::Null => operand.is_empty(),
|
}
|
||||||
}
|
TestCase::Binary {
|
||||||
}
|
lhs,
|
||||||
TestCase::Binary { lhs, operator, rhs, conjunct } => {
|
operator,
|
||||||
let lhs = lhs.expand()?.get_words().join(" ");
|
rhs,
|
||||||
let rhs = rhs.expand()?.get_words().join(" ");
|
conjunct,
|
||||||
conjunct_op = conjunct;
|
} => {
|
||||||
let test_op = operator.as_str().parse::<TestOp>()?;
|
let lhs = lhs.expand()?.get_words().join(" ");
|
||||||
flog!(DEBUG, lhs);
|
let rhs = rhs.expand()?.get_words().join(" ");
|
||||||
flog!(DEBUG, rhs);
|
conjunct_op = conjunct;
|
||||||
flog!(DEBUG, test_op);
|
let test_op = operator.as_str().parse::<TestOp>()?;
|
||||||
match test_op {
|
flog!(DEBUG, lhs);
|
||||||
TestOp::Unary(_) => {
|
flog!(DEBUG, rhs);
|
||||||
return Err(
|
flog!(DEBUG, test_op);
|
||||||
ShErr::Full {
|
match test_op {
|
||||||
kind: ShErrKind::SyntaxErr,
|
TestOp::Unary(_) => {
|
||||||
msg: "Expected a binary operator in this test call; found a unary operator".into(),
|
return Err(ShErr::Full {
|
||||||
notes: vec![],
|
kind: ShErrKind::SyntaxErr,
|
||||||
span: err_span
|
msg: "Expected a binary operator in this test call; found a unary operator".into(),
|
||||||
}
|
notes: vec![],
|
||||||
)
|
span: err_span,
|
||||||
}
|
})
|
||||||
TestOp::StringEq => rhs.trim() == lhs.trim(),
|
}
|
||||||
TestOp::StringNeq => rhs.trim() != lhs.trim(),
|
TestOp::StringEq => rhs.trim() == lhs.trim(),
|
||||||
TestOp::IntNeq |
|
TestOp::StringNeq => rhs.trim() != lhs.trim(),
|
||||||
TestOp::IntGt |
|
TestOp::IntNeq
|
||||||
TestOp::IntLt |
|
| TestOp::IntGt
|
||||||
TestOp::IntGe |
|
| TestOp::IntLt
|
||||||
TestOp::IntLe |
|
| TestOp::IntGe
|
||||||
TestOp::IntEq => {
|
| TestOp::IntLe
|
||||||
let err = ShErr::Full {
|
| TestOp::IntEq => {
|
||||||
kind: ShErrKind::SyntaxErr,
|
let err = ShErr::Full {
|
||||||
msg: format!("Expected an integer with '{}' operator", operator.as_str()),
|
kind: ShErrKind::SyntaxErr,
|
||||||
notes: vec![],
|
msg: format!("Expected an integer with '{}' operator", operator.as_str()),
|
||||||
span: err_span.clone()
|
notes: vec![],
|
||||||
};
|
span: err_span.clone(),
|
||||||
let Ok(lhs) = lhs.trim().parse::<i32>() else {
|
};
|
||||||
return Err(err)
|
let Ok(lhs) = lhs.trim().parse::<i32>() else {
|
||||||
};
|
return Err(err);
|
||||||
let Ok(rhs) = rhs.trim().parse::<i32>() else {
|
};
|
||||||
return Err(err)
|
let Ok(rhs) = rhs.trim().parse::<i32>() else {
|
||||||
};
|
return Err(err);
|
||||||
match test_op {
|
};
|
||||||
TestOp::IntNeq => lhs != rhs,
|
match test_op {
|
||||||
TestOp::IntGt => lhs > rhs,
|
TestOp::IntNeq => lhs != rhs,
|
||||||
TestOp::IntLt => lhs < rhs,
|
TestOp::IntGt => lhs > rhs,
|
||||||
TestOp::IntGe => lhs >= rhs,
|
TestOp::IntLt => lhs < rhs,
|
||||||
TestOp::IntLe => lhs <= rhs,
|
TestOp::IntGe => lhs >= rhs,
|
||||||
TestOp::IntEq => lhs == rhs,
|
TestOp::IntLe => lhs <= rhs,
|
||||||
_ => unreachable!()
|
TestOp::IntEq => lhs == rhs,
|
||||||
}
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
TestOp::RegexMatch => {
|
}
|
||||||
// FIXME: Imagine doing all of this in every single iteration of a loop
|
TestOp::RegexMatch => {
|
||||||
let cleaned = replace_posix_classes(&rhs);
|
// FIXME: Imagine doing all of this in every single iteration of a loop
|
||||||
let regex = Regex::new(&cleaned).unwrap();
|
let cleaned = replace_posix_classes(&rhs);
|
||||||
regex.is_match(&lhs)
|
let regex = Regex::new(&cleaned).unwrap();
|
||||||
}
|
regex.is_match(&lhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
flog!(DEBUG, last_result);
|
};
|
||||||
|
flog!(DEBUG, last_result);
|
||||||
|
|
||||||
if let Some(op) = conjunct_op {
|
if let Some(op) = conjunct_op {
|
||||||
match op {
|
match op {
|
||||||
ConjunctOp::And if !last_result => {
|
ConjunctOp::And if !last_result => {
|
||||||
last_result = result;
|
last_result = result;
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
ConjunctOp::Or if last_result => {
|
ConjunctOp::Or if last_result => {
|
||||||
last_result = result;
|
last_result = result;
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
last_result = result;
|
last_result = result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flog!(DEBUG, last_result);
|
flog!(DEBUG, last_result);
|
||||||
Ok(last_result)
|
Ok(last_result)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,190 +1,191 @@
|
|||||||
use std::{os::unix::fs::OpenOptionsExt, sync::LazyLock};
|
use std::{os::unix::fs::OpenOptionsExt, sync::LazyLock};
|
||||||
|
|
||||||
use crate::{getopt::{get_opts_from_tokens, Opt, OptSet}, jobs::JobBldr, libsh::error::{Note, ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}};
|
use crate::{
|
||||||
|
getopt::{get_opts_from_tokens, Opt, OptSet},
|
||||||
|
jobs::JobBldr,
|
||||||
|
libsh::error::{Note, ShErr, ShErrKind, ShResult, ShResultExt},
|
||||||
|
parse::{NdRule, Node},
|
||||||
|
prelude::*,
|
||||||
|
procio::{borrow_fd, IoStack},
|
||||||
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub static ZOLTRAAK_OPTS: LazyLock<OptSet> = LazyLock::new(|| {
|
pub static ZOLTRAAK_OPTS: LazyLock<OptSet> = LazyLock::new(|| {
|
||||||
[
|
[
|
||||||
Opt::Long("dry-run".into()),
|
Opt::Long("dry-run".into()),
|
||||||
Opt::Long("confirm".into()),
|
Opt::Long("confirm".into()),
|
||||||
Opt::Long("no-preserve-root".into()),
|
Opt::Long("no-preserve-root".into()),
|
||||||
Opt::Short('r'),
|
Opt::Short('r'),
|
||||||
Opt::Short('f'),
|
Opt::Short('f'),
|
||||||
Opt::Short('v')
|
Opt::Short('v'),
|
||||||
].into()
|
]
|
||||||
|
.into()
|
||||||
});
|
});
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Clone,Copy,Debug,PartialEq,Eq)]
|
#[derive(Clone,Copy,Debug,PartialEq,Eq)]
|
||||||
struct ZoltFlags: u32 {
|
struct ZoltFlags: u32 {
|
||||||
const DRY = 0b000001;
|
const DRY = 0b000001;
|
||||||
const CONFIRM = 0b000010;
|
const CONFIRM = 0b000010;
|
||||||
const NO_PRESERVE_ROOT = 0b000100;
|
const NO_PRESERVE_ROOT = 0b000100;
|
||||||
const RECURSIVE = 0b001000;
|
const RECURSIVE = 0b001000;
|
||||||
const FORCE = 0b010000;
|
const FORCE = 0b010000;
|
||||||
const VERBOSE = 0b100000;
|
const VERBOSE = 0b100000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Annihilate a file
|
/// Annihilate a file
|
||||||
///
|
///
|
||||||
/// This command works similarly to 'rm', but behaves more destructively.
|
/// This command works similarly to 'rm', but behaves more destructively.
|
||||||
/// The file given as an argument is completely destroyed. The command works by shredding all of the data contained in the file, before truncating the length of the file to 0 to ensure that not even any metadata remains.
|
/// The file given as an argument is completely destroyed. The command works by
|
||||||
|
/// shredding all of the data contained in the file, before truncating the
|
||||||
|
/// length of the file to 0 to ensure that not even any metadata remains.
|
||||||
pub fn zoltraak(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn zoltraak(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command {
|
||||||
unreachable!()
|
assignments: _,
|
||||||
};
|
argv,
|
||||||
let mut flags = ZoltFlags::empty();
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let mut flags = ZoltFlags::empty();
|
||||||
|
|
||||||
let (argv,opts) = get_opts_from_tokens(argv);
|
let (argv, opts) = get_opts_from_tokens(argv);
|
||||||
|
|
||||||
for opt in opts {
|
for opt in opts {
|
||||||
if !ZOLTRAAK_OPTS.contains(&opt) {
|
if !ZOLTRAAK_OPTS.contains(&opt) {
|
||||||
return Err(
|
return Err(ShErr::simple(
|
||||||
ShErr::simple(
|
ShErrKind::SyntaxErr,
|
||||||
ShErrKind::SyntaxErr,
|
format!("zoltraak: unrecognized option '{opt}'"),
|
||||||
format!("zoltraak: unrecognized option '{opt}'")
|
));
|
||||||
)
|
}
|
||||||
)
|
match opt {
|
||||||
}
|
Opt::Long(flag) => match flag.as_str() {
|
||||||
match opt {
|
"no-preserve-root" => flags |= ZoltFlags::NO_PRESERVE_ROOT,
|
||||||
Opt::Long(flag) => {
|
"confirm" => flags |= ZoltFlags::CONFIRM,
|
||||||
match flag.as_str() {
|
"dry-run" => flags |= ZoltFlags::DRY,
|
||||||
"no-preserve-root" => flags |= ZoltFlags::NO_PRESERVE_ROOT,
|
_ => unreachable!(),
|
||||||
"confirm" => flags |= ZoltFlags::CONFIRM,
|
},
|
||||||
"dry-run" => flags |= ZoltFlags::DRY,
|
Opt::Short(flag) => match flag {
|
||||||
_ => unreachable!()
|
'r' => flags |= ZoltFlags::RECURSIVE,
|
||||||
}
|
'f' => flags |= ZoltFlags::FORCE,
|
||||||
}
|
'v' => flags |= ZoltFlags::VERBOSE,
|
||||||
Opt::Short(flag) => {
|
_ => unreachable!(),
|
||||||
match flag {
|
},
|
||||||
'r' => flags |= ZoltFlags::RECURSIVE,
|
}
|
||||||
'f' => flags |= ZoltFlags::FORCE,
|
}
|
||||||
'v' => flags |= ZoltFlags::VERBOSE,
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let mut io_frame = io_frame.unwrap();
|
let mut io_frame = io_frame.unwrap();
|
||||||
io_frame.redirect()?;
|
io_frame.redirect()?;
|
||||||
|
|
||||||
for (arg,span) in argv {
|
for (arg, span) in argv {
|
||||||
if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) {
|
if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) {
|
||||||
return Err(
|
return Err(
|
||||||
ShErr::simple(
|
ShErr::simple(
|
||||||
ShErrKind::ExecFail,
|
ShErrKind::ExecFail,
|
||||||
"zoltraak: Attempted to destroy root directory '/'"
|
"zoltraak: Attempted to destroy root directory '/'",
|
||||||
)
|
)
|
||||||
.with_note(
|
.with_note(
|
||||||
Note::new("If you really want to do this, you can use the --no-preserve-root flag")
|
Note::new("If you really want to do this, you can use the --no-preserve-root flag")
|
||||||
.with_sub_notes(vec![
|
.with_sub_notes(vec!["Example: 'zoltraak --no-preserve-root /'"]),
|
||||||
"Example: 'zoltraak --no-preserve-root /'"
|
),
|
||||||
])
|
);
|
||||||
)
|
}
|
||||||
)
|
if let Err(e) = annihilate(&arg, flags).blame(span) {
|
||||||
}
|
io_frame.restore()?;
|
||||||
if let Err(e) = annihilate(&arg, flags).blame(span) {
|
return Err(e);
|
||||||
io_frame.restore()?;
|
}
|
||||||
return Err(e);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
io_frame.restore()?;
|
io_frame.restore()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn annihilate(path: &str, flags: ZoltFlags) -> ShResult<()> {
|
fn annihilate(path: &str, flags: ZoltFlags) -> ShResult<()> {
|
||||||
let path_buf = PathBuf::from(path);
|
let path_buf = PathBuf::from(path);
|
||||||
let is_recursive = flags.contains(ZoltFlags::RECURSIVE);
|
let is_recursive = flags.contains(ZoltFlags::RECURSIVE);
|
||||||
let is_verbose = flags.contains(ZoltFlags::VERBOSE);
|
let is_verbose = flags.contains(ZoltFlags::VERBOSE);
|
||||||
|
|
||||||
const BLOCK_SIZE: u64 = 4096;
|
const BLOCK_SIZE: u64 = 4096;
|
||||||
|
|
||||||
if !path_buf.exists() {
|
if !path_buf.exists() {
|
||||||
return Err(
|
return Err(ShErr::simple(
|
||||||
ShErr::simple(
|
ShErrKind::ExecFail,
|
||||||
ShErrKind::ExecFail,
|
format!("zoltraak: File '{path}' not found"),
|
||||||
format!("zoltraak: File '{path}' not found")
|
));
|
||||||
)
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if path_buf.is_file() {
|
if path_buf.is_file() {
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.custom_flags(libc::O_DIRECT)
|
.custom_flags(libc::O_DIRECT)
|
||||||
.open(path_buf)?;
|
.open(path_buf)?;
|
||||||
|
|
||||||
let meta = file.metadata()?;
|
let meta = file.metadata()?;
|
||||||
let file_size = meta.len();
|
let file_size = meta.len();
|
||||||
let full_blocks = file_size / BLOCK_SIZE;
|
let full_blocks = file_size / BLOCK_SIZE;
|
||||||
let byte_remainder = file_size % BLOCK_SIZE;
|
let byte_remainder = file_size % BLOCK_SIZE;
|
||||||
|
|
||||||
let full_buf = vec![0; BLOCK_SIZE as usize];
|
let full_buf = vec![0; BLOCK_SIZE as usize];
|
||||||
let remainder_buf = vec![0; byte_remainder as usize];
|
let remainder_buf = vec![0; byte_remainder as usize];
|
||||||
|
|
||||||
for _ in 0..full_blocks {
|
for _ in 0..full_blocks {
|
||||||
file.write_all(&full_buf)?;
|
file.write_all(&full_buf)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if byte_remainder > 0 {
|
if byte_remainder > 0 {
|
||||||
file.write_all(&remainder_buf)?;
|
file.write_all(&remainder_buf)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
file.set_len(0)?;
|
file.set_len(0)?;
|
||||||
mem::drop(file);
|
mem::drop(file);
|
||||||
fs::remove_file(path)?;
|
fs::remove_file(path)?;
|
||||||
if is_verbose {
|
if is_verbose {
|
||||||
let stderr = borrow_fd(STDERR_FILENO);
|
let stderr = borrow_fd(STDERR_FILENO);
|
||||||
write(stderr, format!("shredded file '{path}'\n").as_bytes())?;
|
write(stderr, format!("shredded file '{path}'\n").as_bytes())?;
|
||||||
}
|
}
|
||||||
|
} else if path_buf.is_dir() {
|
||||||
|
if is_recursive {
|
||||||
|
annihilate_recursive(path, flags)?; // scary
|
||||||
|
} else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
format!("zoltraak: '{path}' is a directory"),
|
||||||
|
)
|
||||||
|
.with_note(
|
||||||
|
Note::new("Use the '-r' flag to recursively shred directories")
|
||||||
|
.with_sub_notes(vec!["Example: 'zoltraak -r directory'"]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if path_buf.is_dir() {
|
Ok(())
|
||||||
if is_recursive {
|
|
||||||
annihilate_recursive(path, flags)?; // scary
|
|
||||||
} else {
|
|
||||||
return Err(
|
|
||||||
ShErr::simple(
|
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("zoltraak: '{path}' is a directory")
|
|
||||||
)
|
|
||||||
.with_note(
|
|
||||||
Note::new("Use the '-r' flag to recursively shred directories")
|
|
||||||
.with_sub_notes(vec![
|
|
||||||
"Example: 'zoltraak -r directory'"
|
|
||||||
])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn annihilate_recursive(dir: &str, flags: ZoltFlags) -> ShResult<()> {
|
fn annihilate_recursive(dir: &str, flags: ZoltFlags) -> ShResult<()> {
|
||||||
let dir_path = PathBuf::from(dir);
|
let dir_path = PathBuf::from(dir);
|
||||||
let is_verbose = flags.contains(ZoltFlags::VERBOSE);
|
let is_verbose = flags.contains(ZoltFlags::VERBOSE);
|
||||||
|
|
||||||
for dir_entry in fs::read_dir(&dir_path)? {
|
for dir_entry in fs::read_dir(&dir_path)? {
|
||||||
let entry = dir_entry?.path();
|
let entry = dir_entry?.path();
|
||||||
let file = entry.to_str().unwrap();
|
let file = entry.to_str().unwrap();
|
||||||
|
|
||||||
if entry.is_file() {
|
if entry.is_file() {
|
||||||
annihilate(file, flags)?;
|
annihilate(file, flags)?;
|
||||||
} else if entry.is_dir() {
|
} else if entry.is_dir() {
|
||||||
annihilate_recursive(file, flags)?;
|
annihilate_recursive(file, flags)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fs::remove_dir(dir)?;
|
fs::remove_dir(dir)?;
|
||||||
if is_verbose {
|
if is_verbose {
|
||||||
let stderr = borrow_fd(STDERR_FILENO);
|
let stderr = borrow_fd(STDERR_FILENO);
|
||||||
write(stderr, format!("shredded directory '{dir}'\n").as_bytes())?;
|
write(stderr, format!("shredded directory '{dir}'\n").as_bytes())?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
2706
src/expand.rs
2706
src/expand.rs
File diff suppressed because it is too large
Load Diff
175
src/fern.rs
175
src/fern.rs
@@ -1,127 +1,130 @@
|
|||||||
#![allow(
|
#![allow(
|
||||||
clippy::derivable_impls,
|
clippy::derivable_impls,
|
||||||
clippy::tabs_in_doc_comments,
|
clippy::tabs_in_doc_comments,
|
||||||
clippy::while_let_on_iterator
|
clippy::while_let_on_iterator
|
||||||
)]
|
)]
|
||||||
pub mod prelude;
|
|
||||||
pub mod libsh;
|
|
||||||
pub mod prompt;
|
|
||||||
pub mod procio;
|
|
||||||
pub mod parse;
|
|
||||||
pub mod expand;
|
|
||||||
pub mod state;
|
|
||||||
pub mod builtin;
|
pub mod builtin;
|
||||||
pub mod jobs;
|
pub mod expand;
|
||||||
pub mod signal;
|
|
||||||
pub mod getopt;
|
pub mod getopt;
|
||||||
|
pub mod jobs;
|
||||||
|
pub mod libsh;
|
||||||
|
pub mod parse;
|
||||||
|
pub mod prelude;
|
||||||
|
pub mod procio;
|
||||||
|
pub mod prompt;
|
||||||
pub mod shopt;
|
pub mod shopt;
|
||||||
|
pub mod signal;
|
||||||
|
pub mod state;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
|
||||||
use crate::libsh::sys::{save_termios, set_termios};
|
use crate::libsh::sys::{save_termios, set_termios};
|
||||||
use crate::parse::execute::exec_input;
|
use crate::parse::execute::exec_input;
|
||||||
|
use crate::prelude::*;
|
||||||
use crate::signal::sig_setup;
|
use crate::signal::sig_setup;
|
||||||
use crate::state::source_rc;
|
use crate::state::source_rc;
|
||||||
use crate::prelude::*;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use shopt::FernEditMode;
|
use shopt::FernEditMode;
|
||||||
use state::{read_shopts, read_vars, write_shopts, write_vars};
|
use state::{read_vars, write_shopts, write_vars};
|
||||||
|
|
||||||
#[derive(Parser,Debug)]
|
#[derive(Parser, Debug)]
|
||||||
struct FernArgs {
|
struct FernArgs {
|
||||||
script: Option<String>,
|
script: Option<String>,
|
||||||
|
|
||||||
#[arg(trailing_var_arg = true)]
|
#[arg(trailing_var_arg = true)]
|
||||||
script_args: Vec<String>,
|
script_args: Vec<String>,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
version: bool
|
version: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force evaluation of lazily-initialized values early in shell startup.
|
/// Force evaluation of lazily-initialized values early in shell startup.
|
||||||
///
|
///
|
||||||
/// In particular, this ensures that the variable table is initialized, which populates
|
/// In particular, this ensures that the variable table is initialized, which
|
||||||
/// environment variables from the system. If this initialization is deferred too long,
|
/// populates environment variables from the system. If this initialization is
|
||||||
/// features like prompt expansion may fail due to missing environment variables.
|
/// deferred too long, features like prompt expansion may fail due to missing
|
||||||
|
/// environment variables.
|
||||||
///
|
///
|
||||||
/// This function triggers initialization by calling `read_vars` with a no-op closure,
|
/// This function triggers initialization by calling `read_vars` with a no-op
|
||||||
/// which forces access to the variable table and causes its `LazyLock` constructor to run.
|
/// closure, which forces access to the variable table and causes its `LazyLock`
|
||||||
|
/// constructor to run.
|
||||||
fn kickstart_lazy_evals() {
|
fn kickstart_lazy_evals() {
|
||||||
read_vars(|_| {});
|
read_vars(|_| {});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
kickstart_lazy_evals();
|
kickstart_lazy_evals();
|
||||||
let args = FernArgs::parse();
|
let args = FernArgs::parse();
|
||||||
if args.version {
|
if args.version {
|
||||||
println!("fern {}", env!("CARGO_PKG_VERSION"));
|
println!("fern {}", env!("CARGO_PKG_VERSION"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(path) = args.script {
|
if let Some(path) = args.script {
|
||||||
run_script(path, args.script_args);
|
run_script(path, args.script_args);
|
||||||
} else {
|
} else {
|
||||||
fern_interactive();
|
fern_interactive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) {
|
fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
eprintln!("fern: Failed to open input file: {}", path.display());
|
eprintln!("fern: Failed to open input file: {}", path.display());
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
let Ok(input) = fs::read_to_string(path) else {
|
let Ok(input) = fs::read_to_string(path) else {
|
||||||
eprintln!("fern: Failed to read input file: {}", path.display());
|
eprintln!("fern: Failed to read input file: {}", path.display());
|
||||||
exit(1);
|
exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
write_vars(|v| v.bpush_arg(path.to_string_lossy().to_string()));
|
write_vars(|v| v.bpush_arg(path.to_string_lossy().to_string()));
|
||||||
for arg in args {
|
for arg in args {
|
||||||
write_vars(|v| v.bpush_arg(arg))
|
write_vars(|v| v.bpush_arg(arg))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = exec_input(input,None) {
|
if let Err(e) = exec_input(input, None) {
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fern_interactive() {
|
fn fern_interactive() {
|
||||||
save_termios();
|
save_termios();
|
||||||
set_termios();
|
set_termios();
|
||||||
sig_setup();
|
sig_setup();
|
||||||
|
|
||||||
if let Err(e) = source_rc() {
|
if let Err(e) = source_rc() {
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut readline_err_count: u32 = 0;
|
let mut readline_err_count: u32 = 0;
|
||||||
|
|
||||||
loop { // Main loop
|
loop {
|
||||||
let edit_mode = write_shopts(|opt| opt.query("prompt.edit_mode"))
|
// Main loop
|
||||||
.unwrap()
|
let edit_mode = write_shopts(|opt| opt.query("prompt.edit_mode"))
|
||||||
.map(|mode| mode.parse::<FernEditMode>().unwrap_or_default())
|
.unwrap()
|
||||||
.unwrap();
|
.map(|mode| mode.parse::<FernEditMode>().unwrap_or_default())
|
||||||
let input = match prompt::readline(edit_mode) {
|
.unwrap();
|
||||||
Ok(line) => {
|
let input = match prompt::readline(edit_mode) {
|
||||||
readline_err_count = 0;
|
Ok(line) => {
|
||||||
line
|
readline_err_count = 0;
|
||||||
}
|
line
|
||||||
Err(e) => {
|
}
|
||||||
eprintln!("{e}");
|
Err(e) => {
|
||||||
readline_err_count += 1;
|
eprintln!("{e}");
|
||||||
if readline_err_count == 20 {
|
readline_err_count += 1;
|
||||||
eprintln!("reached maximum readline error count, exiting");
|
if readline_err_count == 20 {
|
||||||
break
|
eprintln!("reached maximum readline error count, exiting");
|
||||||
} else {
|
break;
|
||||||
continue
|
} else {
|
||||||
}
|
continue;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(e) = exec_input(input,None) {
|
if let Err(e) = exec_input(input, None) {
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
109
src/getopt.rs
109
src/getopt.rs
@@ -6,75 +6,74 @@ use crate::{parse::lex::Tk, prelude::*};
|
|||||||
|
|
||||||
pub type OptSet = Arc<[Opt]>;
|
pub type OptSet = Arc<[Opt]>;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
#[derive(Clone,PartialEq,Eq,Debug)]
|
|
||||||
pub enum Opt {
|
pub enum Opt {
|
||||||
Long(String),
|
Long(String),
|
||||||
Short(char)
|
Short(char),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Opt {
|
impl Opt {
|
||||||
pub fn parse(s: &str) -> Vec<Self> {
|
pub fn parse(s: &str) -> Vec<Self> {
|
||||||
let mut opts = vec![];
|
let mut opts = vec![];
|
||||||
|
|
||||||
if s.starts_with("--") {
|
if s.starts_with("--") {
|
||||||
opts.push(Opt::Long(s.trim_start_matches('-').to_string()))
|
opts.push(Opt::Long(s.trim_start_matches('-').to_string()))
|
||||||
} else if s.starts_with('-') {
|
} else if s.starts_with('-') {
|
||||||
let mut chars = s.trim_start_matches('-').chars();
|
let mut chars = s.trim_start_matches('-').chars();
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
opts.push(Self::Short(ch))
|
opts.push(Self::Short(ch))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
opts
|
opts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Opt {
|
impl Display for Opt {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Long(opt) => write!(f,"--{}",opt),
|
Self::Long(opt) => write!(f, "--{}", opt),
|
||||||
Self::Short(opt) => write!(f,"-{}",opt),
|
Self::Short(opt) => write!(f, "-{}", opt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_opts(words: Vec<String>) -> (Vec<String>,Vec<Opt>) {
|
pub fn get_opts(words: Vec<String>) -> (Vec<String>, Vec<Opt>) {
|
||||||
let mut words_iter = words.into_iter();
|
let mut words_iter = words.into_iter();
|
||||||
let mut opts = vec![];
|
let mut opts = vec![];
|
||||||
let mut non_opts = vec![];
|
let mut non_opts = vec![];
|
||||||
|
|
||||||
while let Some(word) = words_iter.next() {
|
while let Some(word) = words_iter.next() {
|
||||||
if &word == "--" {
|
if &word == "--" {
|
||||||
non_opts.extend(words_iter);
|
non_opts.extend(words_iter);
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
let parsed_opts = Opt::parse(&word);
|
let parsed_opts = Opt::parse(&word);
|
||||||
if parsed_opts.is_empty() {
|
if parsed_opts.is_empty() {
|
||||||
non_opts.push(word)
|
non_opts.push(word)
|
||||||
} else {
|
} else {
|
||||||
opts.extend(parsed_opts);
|
opts.extend(parsed_opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(non_opts,opts)
|
(non_opts, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_opts_from_tokens(tokens: Vec<Tk>) -> (Vec<Tk>, Vec<Opt>) {
|
pub fn get_opts_from_tokens(tokens: Vec<Tk>) -> (Vec<Tk>, Vec<Opt>) {
|
||||||
let mut tokens_iter = tokens.into_iter();
|
let mut tokens_iter = tokens.into_iter();
|
||||||
let mut opts = vec![];
|
let mut opts = vec![];
|
||||||
let mut non_opts = vec![];
|
let mut non_opts = vec![];
|
||||||
|
|
||||||
while let Some(token) = tokens_iter.next() {
|
while let Some(token) = tokens_iter.next() {
|
||||||
if &token.to_string() == "--" {
|
if &token.to_string() == "--" {
|
||||||
non_opts.extend(tokens_iter);
|
non_opts.extend(tokens_iter);
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
let parsed_opts = Opt::parse(&token.to_string());
|
let parsed_opts = Opt::parse(&token.to_string());
|
||||||
if parsed_opts.is_empty() {
|
if parsed_opts.is_empty() {
|
||||||
non_opts.push(token)
|
non_opts.push(token)
|
||||||
} else {
|
} else {
|
||||||
opts.extend(parsed_opts);
|
opts.extend(parsed_opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(non_opts,opts)
|
(non_opts, opts)
|
||||||
}
|
}
|
||||||
|
|||||||
1293
src/jobs.rs
1293
src/jobs.rs
File diff suppressed because it is too large
Load Diff
@@ -1,356 +1,439 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::term::{Style, Styled},
|
libsh::term::{Style, Styled},
|
||||||
parse::lex::Span,
|
parse::lex::Span,
|
||||||
prelude::*
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type ShResult<T> = Result<T,ShErr>;
|
pub type ShResult<T> = Result<T, ShErr>;
|
||||||
|
|
||||||
pub trait ShResultExt {
|
pub trait ShResultExt {
|
||||||
fn blame(self, span: Span) -> Self;
|
fn blame(self, span: Span) -> Self;
|
||||||
fn try_blame(self, span: Span) -> Self;
|
fn try_blame(self, span: Span) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ShResultExt for Result<T,ShErr> {
|
impl<T> ShResultExt for Result<T, ShErr> {
|
||||||
/// Blame a span for an error
|
/// Blame a span for an error
|
||||||
fn blame(self, new_span: Span) -> Self {
|
fn blame(self, new_span: Span) -> Self {
|
||||||
let Err(e) = self else {
|
let Err(e) = self else { return self };
|
||||||
return self
|
match e {
|
||||||
};
|
ShErr::Simple { kind, msg, notes }
|
||||||
match e {
|
| ShErr::Full {
|
||||||
ShErr::Simple { kind, msg, notes } |
|
kind,
|
||||||
ShErr::Full { kind, msg, notes, span: _ } => Err(ShErr::Full { kind: kind.clone(), msg: msg.clone(), notes: notes.clone(), span: new_span }),
|
msg,
|
||||||
}
|
notes,
|
||||||
}
|
span: _,
|
||||||
/// Blame a span if no blame has been assigned yet
|
} => Err(ShErr::Full {
|
||||||
fn try_blame(self, new_span: Span) -> Self {
|
kind: kind.clone(),
|
||||||
let Err(e) = &self else {
|
msg: msg.clone(),
|
||||||
return self
|
notes: notes.clone(),
|
||||||
};
|
span: new_span,
|
||||||
match e {
|
}),
|
||||||
ShErr::Simple { kind, msg, notes } => Err(ShErr::Full { kind: kind.clone(), msg: msg.clone(), notes: notes.clone(), span: new_span }),
|
}
|
||||||
ShErr::Full { kind: _, msg: _, span: _, notes: _ } => self
|
}
|
||||||
}
|
/// Blame a span if no blame has been assigned yet
|
||||||
}
|
fn try_blame(self, new_span: Span) -> Self {
|
||||||
|
let Err(e) = &self else { return self };
|
||||||
|
match e {
|
||||||
|
ShErr::Simple { kind, msg, notes } => Err(ShErr::Full {
|
||||||
|
kind: kind.clone(),
|
||||||
|
msg: msg.clone(),
|
||||||
|
notes: notes.clone(),
|
||||||
|
span: new_span,
|
||||||
|
}),
|
||||||
|
ShErr::Full {
|
||||||
|
kind: _,
|
||||||
|
msg: _,
|
||||||
|
span: _,
|
||||||
|
notes: _,
|
||||||
|
} => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Note {
|
pub struct Note {
|
||||||
main: String,
|
main: String,
|
||||||
sub_notes: Vec<Note>,
|
sub_notes: Vec<Note>,
|
||||||
depth: usize
|
depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Note {
|
impl Note {
|
||||||
pub fn new(main: impl Into<String>) -> Self {
|
pub fn new(main: impl Into<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
main: main.into(),
|
main: main.into(),
|
||||||
sub_notes: vec![],
|
sub_notes: vec![],
|
||||||
depth: 0
|
depth: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_sub_notes(self, new_sub_notes: Vec<impl Into<String>>) -> Self {
|
pub fn with_sub_notes(self, new_sub_notes: Vec<impl Into<String>>) -> Self {
|
||||||
let Self { main, mut sub_notes, depth } = self;
|
let Self {
|
||||||
for raw_note in new_sub_notes {
|
main,
|
||||||
let mut note = Note::new(raw_note);
|
mut sub_notes,
|
||||||
note.depth = self.depth + 1;
|
depth,
|
||||||
sub_notes.push(note);
|
} = self;
|
||||||
}
|
for raw_note in new_sub_notes {
|
||||||
Self { main, sub_notes, depth }
|
let mut note = Note::new(raw_note);
|
||||||
}
|
note.depth = self.depth + 1;
|
||||||
|
sub_notes.push(note);
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
main,
|
||||||
|
sub_notes,
|
||||||
|
depth,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Note {
|
impl Display for Note {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let note = "note".styled(Style::Green);
|
let note = "note".styled(Style::Green);
|
||||||
let main = &self.main;
|
let main = &self.main;
|
||||||
if self.depth == 0 {
|
if self.depth == 0 {
|
||||||
writeln!(f, "{note}: {main}")?;
|
writeln!(f, "{note}: {main}")?;
|
||||||
} else {
|
} else {
|
||||||
let bar_break = "-".styled(Style::Cyan | Style::Bold);
|
let bar_break = "-".styled(Style::Cyan | Style::Bold);
|
||||||
let indent = " ".repeat(self.depth);
|
let indent = " ".repeat(self.depth);
|
||||||
writeln!(f, " {indent}{bar_break} {main}")?;
|
writeln!(f, " {indent}{bar_break} {main}")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for sub_note in &self.sub_notes {
|
for sub_note in &self.sub_notes {
|
||||||
write!(f, "{sub_note}")?;
|
write!(f, "{sub_note}")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ShErr {
|
pub enum ShErr {
|
||||||
Simple { kind: ShErrKind, msg: String, notes: Vec<Note> },
|
Simple {
|
||||||
Full { kind: ShErrKind, msg: String, notes: Vec<Note>, span: Span }
|
kind: ShErrKind,
|
||||||
|
msg: String,
|
||||||
|
notes: Vec<Note>,
|
||||||
|
},
|
||||||
|
Full {
|
||||||
|
kind: ShErrKind,
|
||||||
|
msg: String,
|
||||||
|
notes: Vec<Note>,
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShErr {
|
impl ShErr {
|
||||||
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
||||||
let msg = msg.into();
|
let msg = msg.into();
|
||||||
Self::Simple { kind, msg, notes: vec![] }
|
Self::Simple {
|
||||||
}
|
kind,
|
||||||
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: Span) -> Self {
|
msg,
|
||||||
let msg = msg.into();
|
notes: vec![],
|
||||||
Self::Full { kind, msg, span, notes: vec![] }
|
}
|
||||||
}
|
}
|
||||||
pub fn unpack(self) -> (ShErrKind,String,Vec<Note>,Option<Span>) {
|
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: Span) -> Self {
|
||||||
match self {
|
let msg = msg.into();
|
||||||
ShErr::Simple { kind, msg, notes } => (kind,msg,notes,None),
|
Self::Full {
|
||||||
ShErr::Full { kind, msg, notes, span } => (kind,msg,notes,Some(span))
|
kind,
|
||||||
}
|
msg,
|
||||||
}
|
span,
|
||||||
pub fn with_note(self, note: Note) -> Self {
|
notes: vec![],
|
||||||
let (kind,msg,mut notes,span) = self.unpack();
|
}
|
||||||
notes.push(note);
|
}
|
||||||
if let Some(span) = span {
|
pub fn unpack(self) -> (ShErrKind, String, Vec<Note>, Option<Span>) {
|
||||||
Self::Full { kind, msg, notes, span }
|
match self {
|
||||||
} else {
|
ShErr::Simple { kind, msg, notes } => (kind, msg, notes, None),
|
||||||
Self::Simple { kind, msg, notes }
|
ShErr::Full {
|
||||||
}
|
kind,
|
||||||
}
|
msg,
|
||||||
pub fn with_span(sherr: ShErr, span: Span) -> Self {
|
notes,
|
||||||
let (kind,msg,notes,_) = sherr.unpack();
|
span,
|
||||||
Self::Full { kind, msg, notes, span }
|
} => (kind, msg, notes, Some(span)),
|
||||||
}
|
}
|
||||||
pub fn kind(&self) -> &ShErrKind {
|
}
|
||||||
match self {
|
pub fn with_note(self, note: Note) -> Self {
|
||||||
ShErr::Simple { kind, msg: _, notes: _ } |
|
let (kind, msg, mut notes, span) = self.unpack();
|
||||||
ShErr::Full { kind, msg: _, notes: _, span: _ } => kind
|
notes.push(note);
|
||||||
}
|
if let Some(span) = span {
|
||||||
}
|
Self::Full {
|
||||||
pub fn get_window(&self) -> Vec<(usize,String)> {
|
kind,
|
||||||
let ShErr::Full { kind: _, msg: _, notes: _, span } = self else {
|
msg,
|
||||||
unreachable!()
|
notes,
|
||||||
};
|
span,
|
||||||
let mut total_len: usize = 0;
|
}
|
||||||
let mut total_lines: usize = 1;
|
} else {
|
||||||
let mut lines = vec![];
|
Self::Simple { kind, msg, notes }
|
||||||
let mut cur_line = String::new();
|
}
|
||||||
|
}
|
||||||
|
pub fn with_span(sherr: ShErr, span: Span) -> Self {
|
||||||
|
let (kind, msg, notes, _) = sherr.unpack();
|
||||||
|
Self::Full {
|
||||||
|
kind,
|
||||||
|
msg,
|
||||||
|
notes,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn kind(&self) -> &ShErrKind {
|
||||||
|
match self {
|
||||||
|
ShErr::Simple {
|
||||||
|
kind,
|
||||||
|
msg: _,
|
||||||
|
notes: _,
|
||||||
|
}
|
||||||
|
| ShErr::Full {
|
||||||
|
kind,
|
||||||
|
msg: _,
|
||||||
|
notes: _,
|
||||||
|
span: _,
|
||||||
|
} => kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_window(&self) -> Vec<(usize, String)> {
|
||||||
|
let ShErr::Full {
|
||||||
|
kind: _,
|
||||||
|
msg: _,
|
||||||
|
notes: _,
|
||||||
|
span,
|
||||||
|
} = self
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let mut total_len: usize = 0;
|
||||||
|
let mut total_lines: usize = 1;
|
||||||
|
let mut lines = vec![];
|
||||||
|
let mut cur_line = String::new();
|
||||||
|
|
||||||
let src = span.get_source();
|
let src = span.get_source();
|
||||||
let mut chars = src.chars();
|
let mut chars = src.chars();
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
total_len += ch.len_utf8();
|
total_len += ch.len_utf8();
|
||||||
cur_line.push(ch);
|
cur_line.push(ch);
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
if total_len > span.start {
|
if total_len > span.start {
|
||||||
let line = (
|
let line = (total_lines, mem::take(&mut cur_line));
|
||||||
total_lines,
|
lines.push(line);
|
||||||
mem::take(&mut cur_line)
|
}
|
||||||
);
|
if total_len >= span.end {
|
||||||
lines.push(line);
|
break;
|
||||||
}
|
}
|
||||||
if total_len >= span.end {
|
total_lines += 1;
|
||||||
break
|
|
||||||
}
|
|
||||||
total_lines += 1;
|
|
||||||
|
|
||||||
cur_line.clear();
|
cur_line.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cur_line.is_empty() {
|
if !cur_line.is_empty() {
|
||||||
let line = (
|
let line = (total_lines, mem::take(&mut cur_line));
|
||||||
total_lines,
|
lines.push(line);
|
||||||
mem::take(&mut cur_line)
|
}
|
||||||
);
|
|
||||||
lines.push(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
pub fn get_line_col(&self) -> (usize,usize) {
|
pub fn get_line_col(&self) -> (usize, usize) {
|
||||||
let ShErr::Full { kind: _, msg: _, notes: _, span } = self else {
|
let ShErr::Full {
|
||||||
unreachable!()
|
kind: _,
|
||||||
};
|
msg: _,
|
||||||
|
notes: _,
|
||||||
|
span,
|
||||||
|
} = self
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
let mut lineno = 1;
|
let mut lineno = 1;
|
||||||
let mut colno = 1;
|
let mut colno = 1;
|
||||||
let src = span.get_source();
|
let src = span.get_source();
|
||||||
let mut chars = src.chars().enumerate();
|
let mut chars = src.chars().enumerate();
|
||||||
while let Some((pos,ch)) = chars.next() {
|
while let Some((pos, ch)) = chars.next() {
|
||||||
if pos >= span.start {
|
if pos >= span.start {
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
lineno += 1;
|
lineno += 1;
|
||||||
colno = 1;
|
colno = 1;
|
||||||
} else {
|
} else {
|
||||||
colno += 1;
|
colno += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(lineno,colno)
|
(lineno, colno)
|
||||||
}
|
}
|
||||||
pub fn get_indicator_lines(&self) -> Option<Vec<String>> {
|
pub fn get_indicator_lines(&self) -> Option<Vec<String>> {
|
||||||
match self {
|
match self {
|
||||||
ShErr::Simple { kind: _, msg: _, notes: _ } => None,
|
ShErr::Simple {
|
||||||
ShErr::Full { kind: _, msg: _, notes: _, span } => {
|
kind: _,
|
||||||
let text = span.as_str();
|
msg: _,
|
||||||
let lines = text.lines();
|
notes: _,
|
||||||
let mut indicator_lines = vec![];
|
} => None,
|
||||||
|
ShErr::Full {
|
||||||
|
kind: _,
|
||||||
|
msg: _,
|
||||||
|
notes: _,
|
||||||
|
span,
|
||||||
|
} => {
|
||||||
|
let text = span.as_str();
|
||||||
|
let lines = text.lines();
|
||||||
|
let mut indicator_lines = vec![];
|
||||||
|
|
||||||
for line in lines {
|
for line in lines {
|
||||||
let indicator_line = "^".repeat(line.trim().len()).styled(Style::Red | Style::Bold);
|
let indicator_line = "^"
|
||||||
indicator_lines.push(indicator_line);
|
.repeat(line.trim().len())
|
||||||
}
|
.styled(Style::Red | Style::Bold);
|
||||||
|
indicator_lines.push(indicator_line);
|
||||||
|
}
|
||||||
|
|
||||||
Some(indicator_lines)
|
Some(indicator_lines)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ShErr {
|
impl Display for ShErr {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Simple { msg, kind: _, notes } => {
|
Self::Simple {
|
||||||
let mut all_strings = vec![msg.to_string()];
|
msg,
|
||||||
let mut notes_fmt = vec![];
|
kind: _,
|
||||||
for note in notes {
|
notes,
|
||||||
let fmt = format!("{note}");
|
} => {
|
||||||
notes_fmt.push(fmt);
|
let mut all_strings = vec![msg.to_string()];
|
||||||
}
|
let mut notes_fmt = vec![];
|
||||||
all_strings.append(&mut notes_fmt);
|
for note in notes {
|
||||||
let mut output = all_strings.join("\n");
|
let fmt = format!("{note}");
|
||||||
output.push('\n');
|
notes_fmt.push(fmt);
|
||||||
|
}
|
||||||
|
all_strings.append(&mut notes_fmt);
|
||||||
|
let mut output = all_strings.join("\n");
|
||||||
|
output.push('\n');
|
||||||
|
|
||||||
writeln!(f, "{}", output)
|
writeln!(f, "{}", output)
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::Full { msg, kind, notes, span: _ } => {
|
Self::Full {
|
||||||
let window = self.get_window();
|
msg,
|
||||||
let mut indicator_lines = self.get_indicator_lines().unwrap().into_iter();
|
kind,
|
||||||
let mut lineno_pad_count = 0;
|
notes,
|
||||||
for (lineno,_) in window.clone() {
|
span: _,
|
||||||
if lineno.to_string().len() > lineno_pad_count {
|
} => {
|
||||||
lineno_pad_count = lineno.to_string().len() + 1
|
let window = self.get_window();
|
||||||
}
|
let mut indicator_lines = self.get_indicator_lines().unwrap().into_iter();
|
||||||
}
|
let mut lineno_pad_count = 0;
|
||||||
let padding = " ".repeat(lineno_pad_count);
|
for (lineno, _) in window.clone() {
|
||||||
writeln!(f)?;
|
if lineno.to_string().len() > lineno_pad_count {
|
||||||
|
lineno_pad_count = lineno.to_string().len() + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let padding = " ".repeat(lineno_pad_count);
|
||||||
|
writeln!(f)?;
|
||||||
|
|
||||||
|
let (line, col) = self.get_line_col();
|
||||||
|
let line_fmt = line.styled(Style::Cyan | Style::Bold);
|
||||||
|
let col_fmt = col.styled(Style::Cyan | Style::Bold);
|
||||||
|
let kind = kind.styled(Style::Red | Style::Bold);
|
||||||
|
let arrow = "->".styled(Style::Cyan | Style::Bold);
|
||||||
|
writeln!(f, "{kind} - {msg}",)?;
|
||||||
|
writeln!(f, "{padding}{arrow} [{line_fmt};{col_fmt}]",)?;
|
||||||
|
|
||||||
let (line,col) = self.get_line_col();
|
let bar = format!("{padding}|").styled(Style::Cyan | Style::Bold);
|
||||||
let line_fmt = line.styled(Style::Cyan | Style::Bold);
|
writeln!(f, "{bar}")?;
|
||||||
let col_fmt = col.styled(Style::Cyan | Style::Bold);
|
|
||||||
let kind = kind.styled(Style::Red | Style::Bold);
|
|
||||||
let arrow = "->".styled(Style::Cyan | Style::Bold);
|
|
||||||
writeln!(f,
|
|
||||||
"{kind} - {msg}",
|
|
||||||
)?;
|
|
||||||
writeln!(f,
|
|
||||||
"{padding}{arrow} [{line_fmt};{col_fmt}]",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let bar = format!("{padding}|").styled(Style::Cyan | Style::Bold);
|
let mut first_ind_ln = true;
|
||||||
writeln!(f,"{bar}")?;
|
for (lineno, line) in window {
|
||||||
|
let lineno = lineno.to_string();
|
||||||
|
let line = line.trim();
|
||||||
|
let mut prefix = format!("{padding}|");
|
||||||
|
prefix.replace_range(0..lineno.len(), &lineno);
|
||||||
|
prefix = prefix.styled(Style::Cyan | Style::Bold);
|
||||||
|
writeln!(f, "{prefix} {line}")?;
|
||||||
|
|
||||||
let mut first_ind_ln = true;
|
if let Some(ind_ln) = indicator_lines.next() {
|
||||||
for (lineno,line) in window {
|
if first_ind_ln {
|
||||||
let lineno = lineno.to_string();
|
let ind_ln_padding = " ".repeat(col);
|
||||||
let line = line.trim();
|
let ind_ln = format!("{ind_ln_padding}{ind_ln}");
|
||||||
let mut prefix = format!("{padding}|");
|
writeln!(f, "{bar}{ind_ln}")?;
|
||||||
prefix.replace_range(0..lineno.len(), &lineno);
|
first_ind_ln = false;
|
||||||
prefix = prefix.styled(Style::Cyan | Style::Bold);
|
} else {
|
||||||
writeln!(f,"{prefix} {line}")?;
|
writeln!(f, "{bar} {ind_ln}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ind_ln) = indicator_lines.next() {
|
write!(f, "{bar}")?;
|
||||||
if first_ind_ln {
|
|
||||||
let ind_ln_padding = " ".repeat(col);
|
|
||||||
let ind_ln = format!("{ind_ln_padding}{ind_ln}");
|
|
||||||
writeln!(f, "{bar}{ind_ln}")?;
|
|
||||||
first_ind_ln = false;
|
|
||||||
} else {
|
|
||||||
writeln!(f, "{bar} {ind_ln}")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f,"{bar}")?;
|
let bar_break = "-".styled(Style::Cyan | Style::Bold);
|
||||||
|
if !notes.is_empty() {
|
||||||
|
writeln!(f)?;
|
||||||
let bar_break = "-".styled(Style::Cyan | Style::Bold);
|
}
|
||||||
if !notes.is_empty() {
|
for note in notes {
|
||||||
writeln!(f)?;
|
write!(f, "{padding}{bar_break} {note}")?;
|
||||||
}
|
}
|
||||||
for note in notes {
|
Ok(())
|
||||||
|
}
|
||||||
write!(f,
|
}
|
||||||
"{padding}{bar_break} {note}"
|
}
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for ShErr {
|
impl From<std::io::Error> for ShErr {
|
||||||
fn from(_: std::io::Error) -> Self {
|
fn from(_: std::io::Error) -> Self {
|
||||||
let msg = std::io::Error::last_os_error();
|
let msg = std::io::Error::last_os_error();
|
||||||
ShErr::simple(ShErrKind::IoErr, msg.to_string())
|
ShErr::simple(ShErrKind::IoErr, msg.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::env::VarError> for ShErr {
|
impl From<std::env::VarError> for ShErr {
|
||||||
fn from(value: std::env::VarError) -> Self {
|
fn from(value: std::env::VarError) -> Self {
|
||||||
ShErr::simple(ShErrKind::InternalErr, value.to_string())
|
ShErr::simple(ShErrKind::InternalErr, value.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Errno> for ShErr {
|
impl From<Errno> for ShErr {
|
||||||
fn from(value: Errno) -> Self {
|
fn from(value: Errno) -> Self {
|
||||||
ShErr::simple(ShErrKind::Errno, value.to_string())
|
ShErr::simple(ShErrKind::Errno, value.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ShErrKind {
|
pub enum ShErrKind {
|
||||||
IoErr,
|
IoErr,
|
||||||
SyntaxErr,
|
SyntaxErr,
|
||||||
ParseErr,
|
ParseErr,
|
||||||
InternalErr,
|
InternalErr,
|
||||||
ExecFail,
|
ExecFail,
|
||||||
HistoryReadErr,
|
HistoryReadErr,
|
||||||
ResourceLimitExceeded,
|
ResourceLimitExceeded,
|
||||||
BadPermission,
|
BadPermission,
|
||||||
Errno,
|
Errno,
|
||||||
FileNotFound(String),
|
FileNotFound(String),
|
||||||
CmdNotFound(String),
|
CmdNotFound(String),
|
||||||
CleanExit(i32),
|
CleanExit(i32),
|
||||||
FuncReturn(i32),
|
FuncReturn(i32),
|
||||||
LoopContinue(i32),
|
LoopContinue(i32),
|
||||||
LoopBreak(i32),
|
LoopBreak(i32),
|
||||||
ReadlineErr,
|
ReadlineErr,
|
||||||
Null
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ShErrKind {
|
impl Display for ShErrKind {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let output = match self {
|
let output = match self {
|
||||||
Self::IoErr => "I/O Error",
|
Self::IoErr => "I/O Error",
|
||||||
Self::SyntaxErr => "Syntax Error",
|
Self::SyntaxErr => "Syntax Error",
|
||||||
Self::ParseErr => "Parse Error",
|
Self::ParseErr => "Parse Error",
|
||||||
Self::InternalErr => "Internal Error",
|
Self::InternalErr => "Internal Error",
|
||||||
Self::HistoryReadErr => "History Parse Error",
|
Self::HistoryReadErr => "History Parse Error",
|
||||||
Self::ExecFail => "Execution Failed",
|
Self::ExecFail => "Execution Failed",
|
||||||
Self::ResourceLimitExceeded => "Resource Limit Exceeded",
|
Self::ResourceLimitExceeded => "Resource Limit Exceeded",
|
||||||
Self::BadPermission => "Bad Permissions",
|
Self::BadPermission => "Bad Permissions",
|
||||||
Self::Errno => "ERRNO",
|
Self::Errno => "ERRNO",
|
||||||
Self::FileNotFound(file) => &format!("File not found: {file}"),
|
Self::FileNotFound(file) => &format!("File not found: {file}"),
|
||||||
Self::CmdNotFound(cmd) => &format!("Command not found: {cmd}"),
|
Self::CmdNotFound(cmd) => &format!("Command not found: {cmd}"),
|
||||||
Self::CleanExit(_) => "",
|
Self::CleanExit(_) => "",
|
||||||
Self::FuncReturn(_) => "",
|
Self::FuncReturn(_) => "",
|
||||||
Self::LoopContinue(_) => "",
|
Self::LoopContinue(_) => "",
|
||||||
Self::LoopBreak(_) => "",
|
Self::LoopBreak(_) => "",
|
||||||
Self::ReadlineErr => "Line Read Error",
|
Self::ReadlineErr => "Line Read Error",
|
||||||
Self::Null => "",
|
Self::Null => "",
|
||||||
};
|
};
|
||||||
write!(f,"{output}")
|
write!(f, "{output}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,55 +2,57 @@ use std::fmt::Display;
|
|||||||
|
|
||||||
use super::term::{Style, Styled};
|
use super::term::{Style, Styled};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq , Debug)]
|
#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Debug)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum FernLogLevel {
|
pub enum FernLogLevel {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
ERROR = 1,
|
ERROR = 1,
|
||||||
WARN = 2,
|
WARN = 2,
|
||||||
INFO = 3,
|
INFO = 3,
|
||||||
DEBUG = 4,
|
DEBUG = 4,
|
||||||
TRACE = 5
|
TRACE = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for FernLogLevel {
|
impl Display for FernLogLevel {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
use FernLogLevel::*;
|
use FernLogLevel::*;
|
||||||
match self {
|
match self {
|
||||||
ERROR => write!(f,"{}","ERROR".styled(Style::Red | Style::Bold)),
|
ERROR => write!(f, "{}", "ERROR".styled(Style::Red | Style::Bold)),
|
||||||
WARN => write!(f,"{}","WARN".styled(Style::Yellow | Style::Bold)),
|
WARN => write!(f, "{}", "WARN".styled(Style::Yellow | Style::Bold)),
|
||||||
INFO => write!(f,"{}","INFO".styled(Style::Green | Style::Bold)),
|
INFO => write!(f, "{}", "INFO".styled(Style::Green | Style::Bold)),
|
||||||
DEBUG => write!(f,"{}","DEBUG".styled(Style::Magenta | Style::Bold)),
|
DEBUG => write!(f, "{}", "DEBUG".styled(Style::Magenta | Style::Bold)),
|
||||||
TRACE => write!(f,"{}","TRACE".styled(Style::Blue | Style::Bold)),
|
TRACE => write!(f, "{}", "TRACE".styled(Style::Blue | Style::Bold)),
|
||||||
NONE => write!(f,"")
|
NONE => write!(f, ""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_level() -> FernLogLevel {
|
pub fn log_level() -> FernLogLevel {
|
||||||
use FernLogLevel::*;
|
use FernLogLevel::*;
|
||||||
let level = std::env::var("FERN_LOG_LEVEL").unwrap_or_default();
|
let level = std::env::var("FERN_LOG_LEVEL").unwrap_or_default();
|
||||||
match level.to_lowercase().as_str() {
|
match level.to_lowercase().as_str() {
|
||||||
"error" => ERROR,
|
"error" => ERROR,
|
||||||
"warn" => WARN,
|
"warn" => WARN,
|
||||||
"info" => INFO,
|
"info" => INFO,
|
||||||
"debug" => DEBUG,
|
"debug" => DEBUG,
|
||||||
"trace" => TRACE,
|
"trace" => TRACE,
|
||||||
_ => NONE
|
_ => NONE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A structured logging macro designed for `fern`.
|
/// A structured logging macro designed for `fern`.
|
||||||
///
|
///
|
||||||
/// `flog!` was implemented because `rustyline` uses `env_logger`, which clutters the debug output.
|
/// `flog!` was implemented because `rustyline` uses `env_logger`, which
|
||||||
/// This macro prints log messages in a structured format, including the log level, filename, and line number.
|
/// clutters the debug output. This macro prints log messages in a structured
|
||||||
|
/// format, including the log level, filename, and line number.
|
||||||
///
|
///
|
||||||
/// # Usage
|
/// # Usage
|
||||||
///
|
///
|
||||||
/// The macro supports three types of arguments:
|
/// The macro supports three types of arguments:
|
||||||
///
|
///
|
||||||
/// ## 1. **Formatted Messages**
|
/// ## 1. **Formatted Messages**
|
||||||
/// Similar to `println!` or `format!`, allows embedding values inside a formatted string.
|
/// Similar to `println!` or `format!`, allows embedding values inside a
|
||||||
|
/// formatted string.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// flog!(ERROR, "foo is {}", foo);
|
/// flog!(ERROR, "foo is {}", foo);
|
||||||
@@ -73,7 +75,8 @@ pub fn log_level() -> FernLogLevel {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## 3. **Expressions**
|
/// ## 3. **Expressions**
|
||||||
/// Logs the evaluated result of each given expression, displaying both the expression and its value.
|
/// Logs the evaluated result of each given expression, displaying both the
|
||||||
|
/// expression and its value.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// flog!(INFO, 1.min(2));
|
/// flog!(INFO, 1.min(2));
|
||||||
@@ -84,8 +87,10 @@ pub fn log_level() -> FernLogLevel {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Considerations
|
/// # Considerations
|
||||||
/// - This macro uses `eprintln!()` internally, so its formatting rules must be followed.
|
/// - This macro uses `eprintln!()` internally, so its formatting rules must be
|
||||||
/// - **Literals and formatted messages** require arguments that implement [`std::fmt::Display`].
|
/// followed.
|
||||||
|
/// - **Literals and formatted messages** require arguments that implement
|
||||||
|
/// [`std::fmt::Display`].
|
||||||
/// - **Expressions** require arguments that implement [`std::fmt::Debug`].
|
/// - **Expressions** require arguments that implement [`std::fmt::Debug`].
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! flog {
|
macro_rules! flog {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod term;
|
|
||||||
pub mod flog;
|
pub mod flog;
|
||||||
pub mod sys;
|
pub mod sys;
|
||||||
|
pub mod term;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|||||||
109
src/libsh/sys.rs
109
src/libsh/sys.rs
@@ -4,70 +4,91 @@ use crate::{prelude::*, state::write_jobs};
|
|||||||
///
|
///
|
||||||
/// The previous state of the terminal options.
|
/// The previous state of the terminal options.
|
||||||
///
|
///
|
||||||
/// This variable stores the terminal settings at the start of the program and restores them when the program exits.
|
/// This variable stores the terminal settings at the start of the program and
|
||||||
/// It is initialized exactly once at the start of the program and accessed exactly once at the end of the program.
|
/// restores them when the program exits. It is initialized exactly once at the
|
||||||
/// It will not be mutated or accessed under any other circumstances.
|
/// start of the program and accessed exactly once at the end of the program. It
|
||||||
|
/// will not be mutated or accessed under any other circumstances.
|
||||||
///
|
///
|
||||||
/// This ended up being necessary because wrapping Termios in a thread-safe way was unreasonably tricky.
|
/// This ended up being necessary because wrapping Termios in a thread-safe way
|
||||||
|
/// was unreasonably tricky.
|
||||||
///
|
///
|
||||||
/// The possible states of this variable are:
|
/// The possible states of this variable are:
|
||||||
/// - `None`: The terminal options have not been set yet (before initialization).
|
/// - `None`: The terminal options have not been set yet (before
|
||||||
/// - `Some(None)`: There were no terminal options to save (i.e., no terminal input detected).
|
/// initialization).
|
||||||
/// - `Some(Some(Termios))`: The terminal options (as `Termios`) have been saved.
|
/// - `Some(None)`: There were no terminal options to save (i.e., no terminal
|
||||||
|
/// input detected).
|
||||||
|
/// - `Some(Some(Termios))`: The terminal options (as `Termios`) have been
|
||||||
|
/// saved.
|
||||||
///
|
///
|
||||||
/// **Important:** This static variable is mutable and accessed via unsafe code. It is only safe to use because:
|
/// **Important:** This static variable is mutable and accessed via unsafe code.
|
||||||
/// - It is set once during program startup and accessed once during program exit.
|
/// It is only safe to use because:
|
||||||
|
/// - It is set once during program startup and accessed once during program
|
||||||
|
/// exit.
|
||||||
/// - It is not mutated or accessed after the initial setup and final read.
|
/// - It is not mutated or accessed after the initial setup and final read.
|
||||||
///
|
///
|
||||||
/// **Caution:** Future changes to this code should respect these constraints to ensure safety. Modifying or accessing this variable outside the defined lifecycle could lead to undefined behavior.
|
/// **Caution:** Future changes to this code should respect these constraints to
|
||||||
|
/// ensure safety. Modifying or accessing this variable outside the defined
|
||||||
|
/// lifecycle could lead to undefined behavior.
|
||||||
pub(crate) static mut SAVED_TERMIOS: Option<Option<Termios>> = None;
|
pub(crate) static mut SAVED_TERMIOS: Option<Option<Termios>> = None;
|
||||||
|
|
||||||
pub fn save_termios() {
|
pub fn save_termios() {
|
||||||
unsafe {
|
unsafe {
|
||||||
SAVED_TERMIOS = Some(if isatty(std::io::stdin().as_raw_fd()).unwrap() {
|
SAVED_TERMIOS = Some(if isatty(std::io::stdin().as_raw_fd()).unwrap() {
|
||||||
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
|
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
|
||||||
termios.local_flags &= !LocalFlags::ECHOCTL;
|
termios.local_flags &= !LocalFlags::ECHOCTL;
|
||||||
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, &termios).unwrap();
|
termios::tcsetattr(
|
||||||
Some(termios)
|
std::io::stdin(),
|
||||||
} else {
|
nix::sys::termios::SetArg::TCSANOW,
|
||||||
None
|
&termios,
|
||||||
});
|
)
|
||||||
}
|
.unwrap();
|
||||||
|
Some(termios)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[allow(static_mut_refs)]
|
#[allow(static_mut_refs)]
|
||||||
///Access the saved termios
|
///Access the saved termios
|
||||||
///
|
///
|
||||||
///# Safety
|
///# Safety
|
||||||
///This function is unsafe because it accesses a public mutable static value. This function should only ever be called after save_termios() has already been called.
|
///This function is unsafe because it accesses a public mutable static value.
|
||||||
|
/// This function should only ever be called after save_termios() has already
|
||||||
|
/// been called.
|
||||||
pub unsafe fn get_saved_termios() -> Option<Termios> {
|
pub unsafe fn get_saved_termios() -> Option<Termios> {
|
||||||
// SAVED_TERMIOS should *only ever* be set once and accessed once
|
// SAVED_TERMIOS should *only ever* be set once and accessed once
|
||||||
// Set at the start of the program, and accessed during the exit of the program to reset the termios.
|
// Set at the start of the program, and accessed during the exit of the program
|
||||||
// Do not use this variable anywhere else
|
// to reset the termios. Do not use this variable anywhere else
|
||||||
SAVED_TERMIOS.clone().flatten()
|
SAVED_TERMIOS.clone().flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set termios to not echo control characters, like ^Z for instance
|
/// Set termios to not echo control characters, like ^Z for instance
|
||||||
pub fn set_termios() {
|
pub fn set_termios() {
|
||||||
if isatty(std::io::stdin().as_raw_fd()).unwrap() {
|
if isatty(std::io::stdin().as_raw_fd()).unwrap() {
|
||||||
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
|
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
|
||||||
termios.local_flags &= !LocalFlags::ECHOCTL;
|
termios.local_flags &= !LocalFlags::ECHOCTL;
|
||||||
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, &termios).unwrap();
|
termios::tcsetattr(
|
||||||
}
|
std::io::stdin(),
|
||||||
|
nix::sys::termios::SetArg::TCSANOW,
|
||||||
|
&termios,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sh_quit(code: i32) -> ! {
|
pub fn sh_quit(code: i32) -> ! {
|
||||||
write_jobs(|j| {
|
write_jobs(|j| {
|
||||||
for job in j.jobs_mut().iter_mut().flatten() {
|
for job in j.jobs_mut().iter_mut().flatten() {
|
||||||
job.killpg(Signal::SIGTERM).ok();
|
job.killpg(Signal::SIGTERM).ok();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if let Some(termios) = unsafe { get_saved_termios() } {
|
if let Some(termios) = unsafe { get_saved_termios() } {
|
||||||
termios::tcsetattr(std::io::stdin(), termios::SetArg::TCSANOW, &termios).unwrap();
|
termios::tcsetattr(std::io::stdin(), termios::SetArg::TCSANOW, &termios).unwrap();
|
||||||
}
|
}
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
eprintln!("exit");
|
eprintln!("exit");
|
||||||
} else {
|
} else {
|
||||||
eprintln!("exit {code}");
|
eprintln!("exit {code}");
|
||||||
}
|
}
|
||||||
exit(code);
|
exit(code);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use std::{fmt::Display, ops::BitOr};
|
use std::{fmt::Display, ops::BitOr};
|
||||||
|
|
||||||
pub trait Styled: Sized + Display {
|
pub trait Styled: Sized + Display {
|
||||||
fn styled<S: Into<StyleSet>>(self, style: S) -> String {
|
fn styled<S: Into<StyleSet>>(self, style: S) -> String {
|
||||||
let styles: StyleSet = style.into();
|
let styles: StyleSet = style.into();
|
||||||
let reset = Style::Reset;
|
let reset = Style::Reset;
|
||||||
format!("{styles}{self}{reset}")
|
format!("{styles}{self}{reset}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Display> Styled for T {}
|
impl<T: Display> Styled for T {}
|
||||||
@@ -13,157 +13,157 @@ impl<T: Display> Styled for T {}
|
|||||||
/// Enum representing a single ANSI style
|
/// Enum representing a single ANSI style
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Style {
|
pub enum Style {
|
||||||
// Undoes all styles
|
// Undoes all styles
|
||||||
Reset,
|
Reset,
|
||||||
// Foreground Colors
|
// Foreground Colors
|
||||||
Black,
|
Black,
|
||||||
Red,
|
Red,
|
||||||
Green,
|
Green,
|
||||||
Yellow,
|
Yellow,
|
||||||
Blue,
|
Blue,
|
||||||
Magenta,
|
Magenta,
|
||||||
Cyan,
|
Cyan,
|
||||||
White,
|
White,
|
||||||
BrightBlack,
|
BrightBlack,
|
||||||
BrightRed,
|
BrightRed,
|
||||||
BrightGreen,
|
BrightGreen,
|
||||||
BrightYellow,
|
BrightYellow,
|
||||||
BrightBlue,
|
BrightBlue,
|
||||||
BrightMagenta,
|
BrightMagenta,
|
||||||
BrightCyan,
|
BrightCyan,
|
||||||
BrightWhite,
|
BrightWhite,
|
||||||
RGB(u8, u8, u8), // Custom foreground color
|
RGB(u8, u8, u8), // Custom foreground color
|
||||||
|
|
||||||
// Background Colors
|
// Background Colors
|
||||||
BgBlack,
|
BgBlack,
|
||||||
BgRed,
|
BgRed,
|
||||||
BgGreen,
|
BgGreen,
|
||||||
BgYellow,
|
BgYellow,
|
||||||
BgBlue,
|
BgBlue,
|
||||||
BgMagenta,
|
BgMagenta,
|
||||||
BgCyan,
|
BgCyan,
|
||||||
BgWhite,
|
BgWhite,
|
||||||
BgBrightBlack,
|
BgBrightBlack,
|
||||||
BgBrightRed,
|
BgBrightRed,
|
||||||
BgBrightGreen,
|
BgBrightGreen,
|
||||||
BgBrightYellow,
|
BgBrightYellow,
|
||||||
BgBrightBlue,
|
BgBrightBlue,
|
||||||
BgBrightMagenta,
|
BgBrightMagenta,
|
||||||
BgBrightCyan,
|
BgBrightCyan,
|
||||||
BgBrightWhite,
|
BgBrightWhite,
|
||||||
BgRGB(u8, u8, u8), // Custom background color
|
BgRGB(u8, u8, u8), // Custom background color
|
||||||
|
|
||||||
// Text Attributes
|
// Text Attributes
|
||||||
Bold,
|
Bold,
|
||||||
Dim,
|
Dim,
|
||||||
Italic,
|
Italic,
|
||||||
Underline,
|
Underline,
|
||||||
Strikethrough,
|
Strikethrough,
|
||||||
Reversed,
|
Reversed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Style {
|
impl Display for Style {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Style::Reset => write!(f, "\x1b[0m"),
|
Style::Reset => write!(f, "\x1b[0m"),
|
||||||
|
|
||||||
// Foreground colors
|
// Foreground colors
|
||||||
Style::Black => write!(f, "\x1b[30m"),
|
Style::Black => write!(f, "\x1b[30m"),
|
||||||
Style::Red => write!(f, "\x1b[31m"),
|
Style::Red => write!(f, "\x1b[31m"),
|
||||||
Style::Green => write!(f, "\x1b[32m"),
|
Style::Green => write!(f, "\x1b[32m"),
|
||||||
Style::Yellow => write!(f, "\x1b[33m"),
|
Style::Yellow => write!(f, "\x1b[33m"),
|
||||||
Style::Blue => write!(f, "\x1b[34m"),
|
Style::Blue => write!(f, "\x1b[34m"),
|
||||||
Style::Magenta => write!(f, "\x1b[35m"),
|
Style::Magenta => write!(f, "\x1b[35m"),
|
||||||
Style::Cyan => write!(f, "\x1b[36m"),
|
Style::Cyan => write!(f, "\x1b[36m"),
|
||||||
Style::White => write!(f, "\x1b[37m"),
|
Style::White => write!(f, "\x1b[37m"),
|
||||||
Style::BrightBlack => write!(f, "\x1b[90m"),
|
Style::BrightBlack => write!(f, "\x1b[90m"),
|
||||||
Style::BrightRed => write!(f, "\x1b[91m"),
|
Style::BrightRed => write!(f, "\x1b[91m"),
|
||||||
Style::BrightGreen => write!(f, "\x1b[92m"),
|
Style::BrightGreen => write!(f, "\x1b[92m"),
|
||||||
Style::BrightYellow => write!(f, "\x1b[93m"),
|
Style::BrightYellow => write!(f, "\x1b[93m"),
|
||||||
Style::BrightBlue => write!(f, "\x1b[94m"),
|
Style::BrightBlue => write!(f, "\x1b[94m"),
|
||||||
Style::BrightMagenta => write!(f, "\x1b[95m"),
|
Style::BrightMagenta => write!(f, "\x1b[95m"),
|
||||||
Style::BrightCyan => write!(f, "\x1b[96m"),
|
Style::BrightCyan => write!(f, "\x1b[96m"),
|
||||||
Style::BrightWhite => write!(f, "\x1b[97m"),
|
Style::BrightWhite => write!(f, "\x1b[97m"),
|
||||||
Style::RGB(r, g, b) => write!(f, "\x1b[38;2;{r};{g};{b}m"),
|
Style::RGB(r, g, b) => write!(f, "\x1b[38;2;{r};{g};{b}m"),
|
||||||
|
|
||||||
// Background colors
|
// Background colors
|
||||||
Style::BgBlack => write!(f, "\x1b[40m"),
|
Style::BgBlack => write!(f, "\x1b[40m"),
|
||||||
Style::BgRed => write!(f, "\x1b[41m"),
|
Style::BgRed => write!(f, "\x1b[41m"),
|
||||||
Style::BgGreen => write!(f, "\x1b[42m"),
|
Style::BgGreen => write!(f, "\x1b[42m"),
|
||||||
Style::BgYellow => write!(f, "\x1b[43m"),
|
Style::BgYellow => write!(f, "\x1b[43m"),
|
||||||
Style::BgBlue => write!(f, "\x1b[44m"),
|
Style::BgBlue => write!(f, "\x1b[44m"),
|
||||||
Style::BgMagenta => write!(f, "\x1b[45m"),
|
Style::BgMagenta => write!(f, "\x1b[45m"),
|
||||||
Style::BgCyan => write!(f, "\x1b[46m"),
|
Style::BgCyan => write!(f, "\x1b[46m"),
|
||||||
Style::BgWhite => write!(f, "\x1b[47m"),
|
Style::BgWhite => write!(f, "\x1b[47m"),
|
||||||
Style::BgBrightBlack => write!(f, "\x1b[100m"),
|
Style::BgBrightBlack => write!(f, "\x1b[100m"),
|
||||||
Style::BgBrightRed => write!(f, "\x1b[101m"),
|
Style::BgBrightRed => write!(f, "\x1b[101m"),
|
||||||
Style::BgBrightGreen => write!(f, "\x1b[102m"),
|
Style::BgBrightGreen => write!(f, "\x1b[102m"),
|
||||||
Style::BgBrightYellow => write!(f, "\x1b[103m"),
|
Style::BgBrightYellow => write!(f, "\x1b[103m"),
|
||||||
Style::BgBrightBlue => write!(f, "\x1b[104m"),
|
Style::BgBrightBlue => write!(f, "\x1b[104m"),
|
||||||
Style::BgBrightMagenta => write!(f, "\x1b[105m"),
|
Style::BgBrightMagenta => write!(f, "\x1b[105m"),
|
||||||
Style::BgBrightCyan => write!(f, "\x1b[106m"),
|
Style::BgBrightCyan => write!(f, "\x1b[106m"),
|
||||||
Style::BgBrightWhite => write!(f, "\x1b[107m"),
|
Style::BgBrightWhite => write!(f, "\x1b[107m"),
|
||||||
Style::BgRGB(r, g, b) => write!(f, "\x1b[48;2;{r};{g};{b}m"),
|
Style::BgRGB(r, g, b) => write!(f, "\x1b[48;2;{r};{g};{b}m"),
|
||||||
|
|
||||||
// Text attributes
|
// Text attributes
|
||||||
Style::Bold => write!(f, "\x1b[1m"),
|
Style::Bold => write!(f, "\x1b[1m"),
|
||||||
Style::Dim => write!(f, "\x1b[2m"), // New
|
Style::Dim => write!(f, "\x1b[2m"), // New
|
||||||
Style::Italic => write!(f, "\x1b[3m"),
|
Style::Italic => write!(f, "\x1b[3m"),
|
||||||
Style::Underline => write!(f, "\x1b[4m"),
|
Style::Underline => write!(f, "\x1b[4m"),
|
||||||
Style::Strikethrough => write!(f, "\x1b[9m"), // New
|
Style::Strikethrough => write!(f, "\x1b[9m"), // New
|
||||||
Style::Reversed => write!(f, "\x1b[7m"),
|
Style::Reversed => write!(f, "\x1b[7m"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Struct representing a **set** of styles
|
/// Struct representing a **set** of styles
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct StyleSet {
|
pub struct StyleSet {
|
||||||
styles: Vec<Style>,
|
styles: Vec<Style>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyleSet {
|
impl StyleSet {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { styles: vec![] }
|
Self { styles: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_style(mut self, style: Style) -> Self {
|
pub fn add_style(mut self, style: Style) -> Self {
|
||||||
if !self.styles.contains(&style) {
|
if !self.styles.contains(&style) {
|
||||||
self.styles.push(style);
|
self.styles.push(style);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for StyleSet {
|
impl Display for StyleSet {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
for style in &self.styles {
|
for style in &self.styles {
|
||||||
style.fmt(f)?
|
style.fmt(f)?
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allow OR (`|`) operator to combine multiple `Style` values into a `StyleSet`
|
/// Allow OR (`|`) operator to combine multiple `Style` values into a `StyleSet`
|
||||||
impl BitOr for Style {
|
impl BitOr for Style {
|
||||||
type Output = StyleSet;
|
type Output = StyleSet;
|
||||||
|
|
||||||
fn bitor(self, rhs: Self) -> Self::Output {
|
fn bitor(self, rhs: Self) -> Self::Output {
|
||||||
StyleSet::new().add_style(self).add_style(rhs)
|
StyleSet::new().add_style(self).add_style(rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allow OR (`|`) operator to combine `StyleSet` with `Style`
|
/// Allow OR (`|`) operator to combine `StyleSet` with `Style`
|
||||||
impl BitOr<Style> for StyleSet {
|
impl BitOr<Style> for StyleSet {
|
||||||
type Output = StyleSet;
|
type Output = StyleSet;
|
||||||
|
|
||||||
fn bitor(self, rhs: Style) -> Self::Output {
|
fn bitor(self, rhs: Style) -> Self::Output {
|
||||||
self.add_style(rhs)
|
self.add_style(rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Style> for StyleSet {
|
impl From<Style> for StyleSet {
|
||||||
fn from(style: Style) -> Self {
|
fn from(style: Style) -> Self {
|
||||||
StyleSet::new().add_style(style)
|
StyleSet::new().add_style(style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,107 +5,105 @@ use crate::parse::{Redir, RedirType};
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub trait VecDequeExt<T> {
|
pub trait VecDequeExt<T> {
|
||||||
fn to_vec(self) -> Vec<T>;
|
fn to_vec(self) -> Vec<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CharDequeUtils {
|
pub trait CharDequeUtils {
|
||||||
fn to_string(self) -> String;
|
fn to_string(self) -> String;
|
||||||
fn ends_with(&self, pat: &str) -> bool;
|
fn ends_with(&self, pat: &str) -> bool;
|
||||||
fn starts_with(&self, pat: &str) -> bool;
|
fn starts_with(&self, pat: &str) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TkVecUtils<Tk> {
|
pub trait TkVecUtils<Tk> {
|
||||||
fn get_span(&self) -> Option<Span>;
|
fn get_span(&self) -> Option<Span>;
|
||||||
fn debug_tokens(&self);
|
fn debug_tokens(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait RedirVecUtils<Redir> {
|
pub trait RedirVecUtils<Redir> {
|
||||||
/// Splits the vector of redirections into two vectors
|
/// Splits the vector of redirections into two vectors
|
||||||
///
|
///
|
||||||
/// One vector contains input redirs, the other contains output redirs
|
/// One vector contains input redirs, the other contains output redirs
|
||||||
fn split_by_channel(self) -> (Vec<Redir>,Vec<Redir>);
|
fn split_by_channel(self) -> (Vec<Redir>, Vec<Redir>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> VecDequeExt<T> for VecDeque<T> {
|
impl<T> VecDequeExt<T> for VecDeque<T> {
|
||||||
fn to_vec(self) -> Vec<T> {
|
fn to_vec(self) -> Vec<T> {
|
||||||
self.into_iter().collect::<Vec<T>>()
|
self.into_iter().collect::<Vec<T>>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CharDequeUtils for VecDeque<char> {
|
impl CharDequeUtils for VecDeque<char> {
|
||||||
fn to_string(mut self) -> String {
|
fn to_string(mut self) -> String {
|
||||||
let mut result = String::with_capacity(self.len());
|
let mut result = String::with_capacity(self.len());
|
||||||
while let Some(ch) = self.pop_front() {
|
while let Some(ch) = self.pop_front() {
|
||||||
result.push(ch);
|
result.push(ch);
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ends_with(&self, pat: &str) -> bool {
|
fn ends_with(&self, pat: &str) -> bool {
|
||||||
let pat_chars = pat.chars();
|
let pat_chars = pat.chars();
|
||||||
let self_len = self.len();
|
let self_len = self.len();
|
||||||
|
|
||||||
// If pattern is longer than self, return false
|
// If pattern is longer than self, return false
|
||||||
if pat_chars.clone().count() > self_len {
|
if pat_chars.clone().count() > self_len {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare from the back
|
// Compare from the back
|
||||||
self.iter().rev().zip(pat_chars.rev()).all(|(c1, c2)| c1 == &c2)
|
self
|
||||||
}
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.zip(pat_chars.rev())
|
||||||
|
.all(|(c1, c2)| c1 == &c2)
|
||||||
|
}
|
||||||
|
|
||||||
fn starts_with(&self, pat: &str) -> bool {
|
fn starts_with(&self, pat: &str) -> bool {
|
||||||
let pat_chars = pat.chars();
|
let pat_chars = pat.chars();
|
||||||
let self_len = self.len();
|
let self_len = self.len();
|
||||||
|
|
||||||
// If pattern is longer than self, return false
|
// If pattern is longer than self, return false
|
||||||
if pat_chars.clone().count() > self_len {
|
if pat_chars.clone().count() > self_len {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare from the front
|
// Compare from the front
|
||||||
self.iter().zip(pat_chars).all(|(c1, c2)| c1 == &c2)
|
self.iter().zip(pat_chars).all(|(c1, c2)| c1 == &c2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TkVecUtils<Tk> for Vec<Tk> {
|
impl TkVecUtils<Tk> for Vec<Tk> {
|
||||||
fn get_span(&self) -> Option<Span> {
|
fn get_span(&self) -> Option<Span> {
|
||||||
if let Some(first_tk) = self.first() {
|
if let Some(first_tk) = self.first() {
|
||||||
self.last().map(|last_tk| {
|
self
|
||||||
Span::new(
|
.last()
|
||||||
first_tk.span.start..last_tk.span.end,
|
.map(|last_tk| Span::new(first_tk.span.start..last_tk.span.end, first_tk.source()))
|
||||||
first_tk.source()
|
} else {
|
||||||
)
|
None
|
||||||
})
|
}
|
||||||
} else {
|
}
|
||||||
None
|
fn debug_tokens(&self) {
|
||||||
}
|
for token in self {
|
||||||
}
|
flog!(DEBUG, "token: {}", token)
|
||||||
fn debug_tokens(&self) {
|
}
|
||||||
for token in self {
|
}
|
||||||
flog!(DEBUG, "token: {}",token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RedirVecUtils<Redir> for Vec<Redir> {
|
impl RedirVecUtils<Redir> for Vec<Redir> {
|
||||||
fn split_by_channel(self) -> (Vec<Redir>,Vec<Redir>) {
|
fn split_by_channel(self) -> (Vec<Redir>, Vec<Redir>) {
|
||||||
let mut input = vec![];
|
let mut input = vec![];
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
for redir in self {
|
for redir in self {
|
||||||
match redir.class {
|
match redir.class {
|
||||||
RedirType::Input => input.push(redir),
|
RedirType::Input => input.push(redir),
|
||||||
RedirType::Pipe => {
|
RedirType::Pipe => match redir.io_mode.tgt_fd() {
|
||||||
match redir.io_mode.tgt_fd() {
|
STDIN_FILENO => input.push(redir),
|
||||||
STDIN_FILENO => input.push(redir),
|
STDOUT_FILENO | STDERR_FILENO => output.push(redir),
|
||||||
STDOUT_FILENO |
|
_ => unreachable!(),
|
||||||
STDERR_FILENO => output.push(redir),
|
},
|
||||||
_ => unreachable!()
|
_ => output.push(redir),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => output.push(redir)
|
(input, output)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
(input,output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
1243
src/parse/execute.rs
1243
src/parse/execute.rs
File diff suppressed because it is too large
Load Diff
1596
src/parse/lex.rs
1596
src/parse/lex.rs
File diff suppressed because it is too large
Load Diff
2810
src/parse/mod.rs
2810
src/parse/mod.rs
File diff suppressed because it is too large
Load Diff
@@ -1,46 +1,37 @@
|
|||||||
// Standard Library Common IO and FS Abstractions
|
// Standard Library Common IO and FS Abstractions
|
||||||
pub use std::io::{
|
|
||||||
self,
|
|
||||||
BufRead,
|
|
||||||
BufReader,
|
|
||||||
BufWriter,
|
|
||||||
Error,
|
|
||||||
ErrorKind,
|
|
||||||
Read,
|
|
||||||
Seek,
|
|
||||||
SeekFrom,
|
|
||||||
Write,
|
|
||||||
};
|
|
||||||
pub use std::fs::{ self, File, OpenOptions };
|
|
||||||
pub use std::path::{ Path, PathBuf };
|
|
||||||
pub use std::ffi::{ CStr, CString };
|
|
||||||
pub use std::process::exit;
|
|
||||||
pub use std::time::Instant;
|
|
||||||
pub use std::sync::Arc;
|
|
||||||
pub use std::mem;
|
|
||||||
pub use std::env;
|
pub use std::env;
|
||||||
|
pub use std::ffi::{CStr, CString};
|
||||||
pub use std::fmt;
|
pub use std::fmt;
|
||||||
|
pub use std::fs::{self, File, OpenOptions};
|
||||||
|
pub use std::io::{
|
||||||
|
self, BufRead, BufReader, BufWriter, Error, ErrorKind, Read, Seek, SeekFrom, Write,
|
||||||
|
};
|
||||||
|
pub use std::mem;
|
||||||
|
pub use std::path::{Path, PathBuf};
|
||||||
|
pub use std::process::exit;
|
||||||
|
pub use std::sync::Arc;
|
||||||
|
pub use std::time::Instant;
|
||||||
|
|
||||||
// Unix-specific IO abstractions
|
// Unix-specific IO abstractions
|
||||||
pub use std::os::unix::io::{ AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd, };
|
pub use std::os::unix::io::{AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
|
||||||
|
|
||||||
// Nix crate for POSIX APIs
|
// Nix crate for POSIX APIs
|
||||||
pub use nix::{
|
|
||||||
errno::Errno,
|
|
||||||
fcntl::{ open, OFlag },
|
|
||||||
sys::{
|
|
||||||
termios::{ self },
|
|
||||||
signal::{ self, signal, kill, killpg, pthread_sigmask, SigSet, SigmaskHow, SigHandler, Signal },
|
|
||||||
stat::Mode,
|
|
||||||
wait::{ waitpid, WaitPidFlag as WtFlag, WaitStatus as WtStat },
|
|
||||||
},
|
|
||||||
libc::{ self, STDIN_FILENO, STDERR_FILENO, STDOUT_FILENO },
|
|
||||||
unistd::{
|
|
||||||
dup, read, isatty, write, close, setpgid, dup2, getpgrp, getpgid,
|
|
||||||
execvpe, tcgetpgrp, tcsetpgrp, fork, pipe, Pid, ForkResult
|
|
||||||
},
|
|
||||||
};
|
|
||||||
pub use bitflags::bitflags;
|
pub use bitflags::bitflags;
|
||||||
|
pub use nix::{
|
||||||
|
errno::Errno,
|
||||||
|
fcntl::{open, OFlag},
|
||||||
|
libc::{self, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO},
|
||||||
|
sys::{
|
||||||
|
signal::{self, kill, killpg, pthread_sigmask, signal, SigHandler, SigSet, SigmaskHow, Signal},
|
||||||
|
stat::Mode,
|
||||||
|
termios::{self},
|
||||||
|
wait::{waitpid, WaitPidFlag as WtFlag, WaitStatus as WtStat},
|
||||||
|
},
|
||||||
|
unistd::{
|
||||||
|
close, dup, dup2, execvpe, fork, getpgid, getpgrp, isatty, pipe, read, setpgid, tcgetpgrp,
|
||||||
|
tcsetpgrp, write, ForkResult, Pid,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
pub use crate::flog;
|
pub use crate::flog;
|
||||||
pub use crate::libsh::flog::FernLogLevel::*;
|
pub use crate::libsh::flog::FernLogLevel::*;
|
||||||
|
|||||||
470
src/procio.rs
470
src/procio.rs
@@ -1,266 +1,312 @@
|
|||||||
use std::{fmt::Debug, ops::{Deref, DerefMut}};
|
use std::{
|
||||||
|
fmt::Debug,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::RedirVecUtils}, parse::{get_redir_file, Redir, RedirType}, prelude::*};
|
use crate::{
|
||||||
|
libsh::{
|
||||||
|
error::{ShErr, ShErrKind, ShResult},
|
||||||
|
utils::RedirVecUtils,
|
||||||
|
},
|
||||||
|
parse::{get_redir_file, Redir, RedirType},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
// Credit to fish-shell for many of the implementation ideas present in this module
|
// Credit to fish-shell for many of the implementation ideas present in this
|
||||||
// https://fishshell.com/
|
// module https://fishshell.com/
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum IoMode {
|
pub enum IoMode {
|
||||||
Fd { tgt_fd: RawFd, src_fd: Arc<OwnedFd> },
|
Fd {
|
||||||
File { tgt_fd: RawFd, path: PathBuf, mode: RedirType },
|
tgt_fd: RawFd,
|
||||||
Pipe { tgt_fd: RawFd, pipe: Arc<OwnedFd> },
|
src_fd: Arc<OwnedFd>,
|
||||||
Buffer { buf: String, pipe: Arc<OwnedFd> }
|
},
|
||||||
|
File {
|
||||||
|
tgt_fd: RawFd,
|
||||||
|
path: PathBuf,
|
||||||
|
mode: RedirType,
|
||||||
|
},
|
||||||
|
Pipe {
|
||||||
|
tgt_fd: RawFd,
|
||||||
|
pipe: Arc<OwnedFd>,
|
||||||
|
},
|
||||||
|
Buffer {
|
||||||
|
buf: String,
|
||||||
|
pipe: Arc<OwnedFd>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IoMode {
|
impl IoMode {
|
||||||
pub fn fd(tgt_fd: RawFd, src_fd: RawFd) -> Self {
|
pub fn fd(tgt_fd: RawFd, src_fd: RawFd) -> Self {
|
||||||
let src_fd = unsafe { OwnedFd::from_raw_fd(src_fd).into() };
|
let src_fd = unsafe { OwnedFd::from_raw_fd(src_fd).into() };
|
||||||
Self::Fd { tgt_fd, src_fd }
|
Self::Fd { tgt_fd, src_fd }
|
||||||
}
|
}
|
||||||
pub fn file(tgt_fd: RawFd, path: PathBuf, mode: RedirType) -> Self {
|
pub fn file(tgt_fd: RawFd, path: PathBuf, mode: RedirType) -> Self {
|
||||||
Self::File { tgt_fd, path, mode }
|
Self::File { tgt_fd, path, mode }
|
||||||
}
|
}
|
||||||
pub fn pipe(tgt_fd: RawFd, pipe: OwnedFd) -> Self {
|
pub fn pipe(tgt_fd: RawFd, pipe: OwnedFd) -> Self {
|
||||||
let pipe = pipe.into();
|
let pipe = pipe.into();
|
||||||
Self::Pipe { tgt_fd, pipe }
|
Self::Pipe { tgt_fd, pipe }
|
||||||
}
|
}
|
||||||
pub fn tgt_fd(&self) -> RawFd {
|
pub fn tgt_fd(&self) -> RawFd {
|
||||||
match self {
|
match self {
|
||||||
IoMode::Fd { tgt_fd, .. } |
|
IoMode::Fd { tgt_fd, .. } | IoMode::File { tgt_fd, .. } | IoMode::Pipe { tgt_fd, .. } => {
|
||||||
IoMode::File { tgt_fd, .. } |
|
*tgt_fd
|
||||||
IoMode::Pipe { tgt_fd, .. } => *tgt_fd,
|
}
|
||||||
_ => panic!()
|
_ => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn src_fd(&self) -> RawFd {
|
pub fn src_fd(&self) -> RawFd {
|
||||||
match self {
|
match self {
|
||||||
IoMode::Fd { tgt_fd: _, src_fd } => src_fd.as_raw_fd(),
|
IoMode::Fd { tgt_fd: _, src_fd } => src_fd.as_raw_fd(),
|
||||||
IoMode::File {..} => panic!("Attempted to obtain src_fd from file before opening"),
|
IoMode::File { .. } => panic!("Attempted to obtain src_fd from file before opening"),
|
||||||
IoMode::Pipe { tgt_fd: _, pipe } => pipe.as_raw_fd(),
|
IoMode::Pipe { tgt_fd: _, pipe } => pipe.as_raw_fd(),
|
||||||
_ => panic!()
|
_ => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn open_file(mut self) -> ShResult<Self> {
|
pub fn open_file(mut self) -> ShResult<Self> {
|
||||||
if let IoMode::File { tgt_fd, path, mode } = self {
|
if let IoMode::File { tgt_fd, path, mode } = self {
|
||||||
let file = get_redir_file(mode, path)?;
|
let file = get_redir_file(mode, path)?;
|
||||||
self = IoMode::Fd { tgt_fd, src_fd: Arc::new(OwnedFd::from(file)) }
|
self = IoMode::Fd {
|
||||||
}
|
tgt_fd,
|
||||||
Ok(self)
|
src_fd: Arc::new(OwnedFd::from(file)),
|
||||||
}
|
}
|
||||||
pub fn get_pipes() -> (Self,Self) {
|
}
|
||||||
let (rpipe,wpipe) = pipe().unwrap();
|
Ok(self)
|
||||||
(
|
}
|
||||||
Self::Pipe { tgt_fd: STDIN_FILENO, pipe: rpipe.into() },
|
pub fn get_pipes() -> (Self, Self) {
|
||||||
Self::Pipe { tgt_fd: STDOUT_FILENO, pipe: wpipe.into() }
|
let (rpipe, wpipe) = pipe().unwrap();
|
||||||
)
|
(
|
||||||
}
|
Self::Pipe {
|
||||||
|
tgt_fd: STDIN_FILENO,
|
||||||
|
pipe: rpipe.into(),
|
||||||
|
},
|
||||||
|
Self::Pipe {
|
||||||
|
tgt_fd: STDOUT_FILENO,
|
||||||
|
pipe: wpipe.into(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Read for IoMode {
|
impl Read for IoMode {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
let src_fd = self.src_fd();
|
let src_fd = self.src_fd();
|
||||||
Ok(read(src_fd, buf)?)
|
Ok(read(src_fd, buf)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IoBuf<R: Read> {
|
pub struct IoBuf<R: Read> {
|
||||||
buf: Vec<u8>,
|
buf: Vec<u8>,
|
||||||
reader: R,
|
reader: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> IoBuf<R> {
|
impl<R: Read> IoBuf<R> {
|
||||||
pub fn new(reader: R) -> Self {
|
pub fn new(reader: R) -> Self {
|
||||||
Self {
|
Self {
|
||||||
buf: Vec::new(),
|
buf: Vec::new(),
|
||||||
reader,
|
reader,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads exactly `size` bytes (or fewer if EOF) into the buffer
|
/// Reads exactly `size` bytes (or fewer if EOF) into the buffer
|
||||||
pub fn read_buffer(&mut self, size: usize) -> io::Result<()> {
|
pub fn read_buffer(&mut self, size: usize) -> io::Result<()> {
|
||||||
let mut temp_buf = vec![0; size]; // Temporary buffer
|
let mut temp_buf = vec![0; size]; // Temporary buffer
|
||||||
let bytes_read = self.reader.read(&mut temp_buf)?;
|
let bytes_read = self.reader.read(&mut temp_buf)?;
|
||||||
self.buf.extend_from_slice(&temp_buf[..bytes_read]); // Append only what was read
|
self.buf.extend_from_slice(&temp_buf[..bytes_read]); // Append only what was read
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Continuously reads until EOF
|
/// Continuously reads until EOF
|
||||||
pub fn fill_buffer(&mut self) -> io::Result<()> {
|
pub fn fill_buffer(&mut self) -> io::Result<()> {
|
||||||
let mut temp_buf = vec![0; 1024]; // Read in chunks
|
let mut temp_buf = vec![0; 1024]; // Read in chunks
|
||||||
loop {
|
loop {
|
||||||
flog!(DEBUG, "reading bytes");
|
flog!(DEBUG, "reading bytes");
|
||||||
let bytes_read = self.reader.read(&mut temp_buf)?;
|
let bytes_read = self.reader.read(&mut temp_buf)?;
|
||||||
flog!(DEBUG, bytes_read);
|
flog!(DEBUG, bytes_read);
|
||||||
if bytes_read == 0 {
|
if bytes_read == 0 {
|
||||||
break; // EOF reached
|
break; // EOF reached
|
||||||
}
|
}
|
||||||
self.buf.extend_from_slice(&temp_buf[..bytes_read]);
|
self.buf.extend_from_slice(&temp_buf[..bytes_read]);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get current buffer contents as a string (if valid UTF-8)
|
/// Get current buffer contents as a string (if valid UTF-8)
|
||||||
pub fn as_str(&self) -> ShResult<&str> {
|
pub fn as_str(&self) -> ShResult<&str> {
|
||||||
std::str::from_utf8(&self.buf).map_err(|_| {
|
std::str::from_utf8(&self.buf)
|
||||||
ShErr::simple(ShErrKind::InternalErr, "Invalid utf-8 in IoBuf")
|
.map_err(|_| ShErr::simple(ShErrKind::InternalErr, "Invalid utf-8 in IoBuf"))
|
||||||
})
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A struct wrapping three fildescs representing `stdin`, `stdout`, and `stderr` respectively
|
/// A struct wrapping three fildescs representing `stdin`, `stdout`, and
|
||||||
#[derive(Debug,Clone)]
|
/// `stderr` respectively
|
||||||
pub struct IoGroup(RawFd,RawFd,RawFd);
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct IoGroup(RawFd, RawFd, RawFd);
|
||||||
|
|
||||||
/// A single stack frame used with the IoStack
|
/// A single stack frame used with the IoStack
|
||||||
/// Each stack frame represents the redirections of a single command
|
/// Each stack frame represents the redirections of a single command
|
||||||
#[derive(Default,Clone,Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct IoFrame {
|
pub struct IoFrame {
|
||||||
redirs: Vec<Redir>,
|
redirs: Vec<Redir>,
|
||||||
saved_io: Option<IoGroup>,
|
saved_io: Option<IoGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'e> IoFrame {
|
impl<'e> IoFrame {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
pub fn from_redirs(redirs: Vec<Redir>) -> Self {
|
pub fn from_redirs(redirs: Vec<Redir>) -> Self {
|
||||||
Self { redirs, saved_io: None }
|
Self {
|
||||||
}
|
redirs,
|
||||||
pub fn from_redir(redir: Redir) -> Self {
|
saved_io: None,
|
||||||
Self { redirs: vec![redir], saved_io: None }
|
}
|
||||||
}
|
}
|
||||||
|
pub fn from_redir(redir: Redir) -> Self {
|
||||||
|
Self {
|
||||||
|
redirs: vec![redir],
|
||||||
|
saved_io: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Splits the frame into two frames
|
/// Splits the frame into two frames
|
||||||
///
|
///
|
||||||
/// One frame contains input redirections, the other contains output redirections
|
/// One frame contains input redirections, the other contains output
|
||||||
/// This is used in shell structures to route redirections either *to* the condition, or *from* the body
|
/// redirections This is used in shell structures to route redirections
|
||||||
/// The first field of the tuple contains input redirections (used for the condition)
|
/// either *to* the condition, or *from* the body The first field of the
|
||||||
/// The second field contains output redirections (used for the body)
|
/// tuple contains input redirections (used for the condition) The second
|
||||||
pub fn split_frame(self) -> (Self,Self) {
|
/// field contains output redirections (used for the body)
|
||||||
let Self { redirs, saved_io: _ } = self;
|
pub fn split_frame(self) -> (Self, Self) {
|
||||||
let (input_redirs,output_redirs) = redirs.split_by_channel();
|
let Self {
|
||||||
(
|
redirs,
|
||||||
Self::from_redirs(input_redirs),
|
saved_io: _,
|
||||||
Self::from_redirs(output_redirs)
|
} = self;
|
||||||
)
|
let (input_redirs, output_redirs) = redirs.split_by_channel();
|
||||||
}
|
(
|
||||||
pub fn save(&'e mut self) {
|
Self::from_redirs(input_redirs),
|
||||||
let saved_in = dup(STDIN_FILENO).unwrap();
|
Self::from_redirs(output_redirs),
|
||||||
let saved_out = dup(STDOUT_FILENO).unwrap();
|
)
|
||||||
let saved_err = dup(STDERR_FILENO).unwrap();
|
}
|
||||||
self.saved_io = Some(IoGroup(saved_in,saved_out,saved_err));
|
pub fn save(&'e mut self) {
|
||||||
}
|
let saved_in = dup(STDIN_FILENO).unwrap();
|
||||||
pub fn redirect(&mut self) -> ShResult<()> {
|
let saved_out = dup(STDOUT_FILENO).unwrap();
|
||||||
self.save();
|
let saved_err = dup(STDERR_FILENO).unwrap();
|
||||||
for redir in &mut self.redirs {
|
self.saved_io = Some(IoGroup(saved_in, saved_out, saved_err));
|
||||||
let io_mode = &mut redir.io_mode;
|
}
|
||||||
flog!(DEBUG, io_mode);
|
pub fn redirect(&mut self) -> ShResult<()> {
|
||||||
if let IoMode::File {..} = io_mode {
|
self.save();
|
||||||
*io_mode = io_mode.clone().open_file()?;
|
for redir in &mut self.redirs {
|
||||||
};
|
let io_mode = &mut redir.io_mode;
|
||||||
flog!(DEBUG, io_mode);
|
flog!(DEBUG, io_mode);
|
||||||
let tgt_fd = io_mode.tgt_fd();
|
if let IoMode::File { .. } = io_mode {
|
||||||
let src_fd = io_mode.src_fd();
|
*io_mode = io_mode.clone().open_file()?;
|
||||||
dup2(src_fd, tgt_fd)?;
|
};
|
||||||
}
|
flog!(DEBUG, io_mode);
|
||||||
Ok(())
|
let tgt_fd = io_mode.tgt_fd();
|
||||||
}
|
let src_fd = io_mode.src_fd();
|
||||||
pub fn restore(&mut self) -> ShResult<()> {
|
dup2(src_fd, tgt_fd)?;
|
||||||
if let Some(saved) = self.saved_io.take() {
|
}
|
||||||
dup2(saved.0, STDIN_FILENO)?;
|
Ok(())
|
||||||
close(saved.0)?;
|
}
|
||||||
dup2(saved.1, STDOUT_FILENO)?;
|
pub fn restore(&mut self) -> ShResult<()> {
|
||||||
close(saved.1)?;
|
if let Some(saved) = self.saved_io.take() {
|
||||||
dup2(saved.2, STDERR_FILENO)?;
|
dup2(saved.0, STDIN_FILENO)?;
|
||||||
close(saved.2)?;
|
close(saved.0)?;
|
||||||
}
|
dup2(saved.1, STDOUT_FILENO)?;
|
||||||
Ok(())
|
close(saved.1)?;
|
||||||
}
|
dup2(saved.2, STDERR_FILENO)?;
|
||||||
|
close(saved.2)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for IoFrame {
|
impl Deref for IoFrame {
|
||||||
type Target = Vec<Redir>;
|
type Target = Vec<Redir>;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.redirs
|
&self.redirs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for IoFrame {
|
impl DerefMut for IoFrame {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.redirs
|
&mut self.redirs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A stack that maintains the current state of I/O for commands
|
/// A stack that maintains the current state of I/O for commands
|
||||||
///
|
///
|
||||||
/// This struct maintains the current state of I/O for the `Dispatcher` struct
|
/// This struct maintains the current state of I/O for the `Dispatcher` struct
|
||||||
/// Each executed command requires an `IoFrame` in order to perform redirections.
|
/// Each executed command requires an `IoFrame` in order to perform
|
||||||
/// As nodes are walked through by the `Dispatcher`, it pushes new frames in certain contexts, and pops frames in others.
|
/// redirections. As nodes are walked through by the `Dispatcher`, it pushes new
|
||||||
/// Each command calls pop_frame() in order to get the current IoFrame in order to perform redirection
|
/// frames in certain contexts, and pops frames in others. Each command calls
|
||||||
#[derive(Debug,Default)]
|
/// pop_frame() in order to get the current IoFrame in order to perform
|
||||||
|
/// redirection
|
||||||
|
#[derive(Debug, Default)]
|
||||||
pub struct IoStack {
|
pub struct IoStack {
|
||||||
stack: Vec<IoFrame>,
|
stack: Vec<IoFrame>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IoStack {
|
impl IoStack {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
stack: vec![IoFrame::new()],
|
stack: vec![IoFrame::new()],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn curr_frame(&self) -> &IoFrame {
|
pub fn curr_frame(&self) -> &IoFrame {
|
||||||
self.stack.last().unwrap()
|
self.stack.last().unwrap()
|
||||||
}
|
}
|
||||||
pub fn curr_frame_mut(&mut self) -> &mut IoFrame {
|
pub fn curr_frame_mut(&mut self) -> &mut IoFrame {
|
||||||
self.stack.last_mut().unwrap()
|
self.stack.last_mut().unwrap()
|
||||||
}
|
}
|
||||||
pub fn push_to_frame(&mut self, redir: Redir) {
|
pub fn push_to_frame(&mut self, redir: Redir) {
|
||||||
self.curr_frame_mut().push(redir)
|
self.curr_frame_mut().push(redir)
|
||||||
}
|
}
|
||||||
pub fn append_to_frame(&mut self, mut other: Vec<Redir>) {
|
pub fn append_to_frame(&mut self, mut other: Vec<Redir>) {
|
||||||
self.curr_frame_mut().append(&mut other)
|
self.curr_frame_mut().append(&mut other)
|
||||||
}
|
}
|
||||||
/// Pop the current stack frame
|
/// Pop the current stack frame
|
||||||
/// This differs from using `pop()` because it always returns a stack frame
|
/// This differs from using `pop()` because it always returns a stack frame
|
||||||
/// If `self.pop()` would empty the `IoStack`, it instead uses `std::mem::take()` to take the last frame
|
/// If `self.pop()` would empty the `IoStack`, it instead uses
|
||||||
/// There will always be at least one frame in the `IoStack`.
|
/// `std::mem::take()` to take the last frame There will always be at least
|
||||||
pub fn pop_frame(&mut self) -> IoFrame {
|
/// one frame in the `IoStack`.
|
||||||
if self.stack.len() > 1 {
|
pub fn pop_frame(&mut self) -> IoFrame {
|
||||||
self.pop().unwrap()
|
if self.stack.len() > 1 {
|
||||||
} else {
|
self.pop().unwrap()
|
||||||
std::mem::take(self.curr_frame_mut())
|
} else {
|
||||||
}
|
std::mem::take(self.curr_frame_mut())
|
||||||
}
|
}
|
||||||
/// Push a new stack frame.
|
}
|
||||||
pub fn push_frame(&mut self, frame: IoFrame) {
|
/// Push a new stack frame.
|
||||||
self.push(frame)
|
pub fn push_frame(&mut self, frame: IoFrame) {
|
||||||
}
|
self.push(frame)
|
||||||
/// Flatten the `IoStack`
|
}
|
||||||
/// All of the current stack frames will be flattened into a single one
|
/// Flatten the `IoStack`
|
||||||
/// Not sure what use this will serve, but my gut said this was worthy of writing
|
/// All of the current stack frames will be flattened into a single one
|
||||||
pub fn flatten(&mut self) {
|
/// Not sure what use this will serve, but my gut said this was worthy of
|
||||||
let mut flat_frame = IoFrame::new();
|
/// writing
|
||||||
while let Some(mut frame) = self.pop() {
|
pub fn flatten(&mut self) {
|
||||||
flat_frame.append(&mut frame)
|
let mut flat_frame = IoFrame::new();
|
||||||
}
|
while let Some(mut frame) = self.pop() {
|
||||||
self.push(flat_frame);
|
flat_frame.append(&mut frame)
|
||||||
}
|
}
|
||||||
|
self.push(flat_frame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for IoStack {
|
impl Deref for IoStack {
|
||||||
type Target = Vec<IoFrame>;
|
type Target = Vec<IoFrame>;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.stack
|
&self.stack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for IoStack {
|
impl DerefMut for IoStack {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.stack
|
&mut self.stack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn borrow_fd<'f>(fd: i32) -> BorrowedFd<'f> {
|
pub fn borrow_fd<'f>(fd: i32) -> BorrowedFd<'f> {
|
||||||
unsafe { BorrowedFd::borrow_raw(fd) }
|
unsafe { BorrowedFd::borrow_raw(fd) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,36 @@
|
|||||||
pub mod readline;
|
|
||||||
pub mod highlight;
|
pub mod highlight;
|
||||||
|
pub mod readline;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use readline::{FernVi, Readline};
|
use readline::{FernVi, Readline};
|
||||||
|
|
||||||
use crate::{expand::expand_prompt, libsh::error::ShResult, prelude::*, shopt::FernEditMode, state::read_shopts};
|
use crate::{
|
||||||
|
expand::expand_prompt, libsh::error::ShResult, prelude::*, shopt::FernEditMode,
|
||||||
|
state::read_shopts,
|
||||||
|
};
|
||||||
|
|
||||||
/// Initialize the line editor
|
/// Initialize the line editor
|
||||||
fn get_prompt() -> ShResult<String> {
|
fn get_prompt() -> ShResult<String> {
|
||||||
let Ok(prompt) = env::var("PS1") else {
|
let Ok(prompt) = env::var("PS1") else {
|
||||||
// prompt expands to:
|
// prompt expands to:
|
||||||
//
|
//
|
||||||
// username@hostname
|
// username@hostname
|
||||||
// short/path/to/pwd/
|
// short/path/to/pwd/
|
||||||
// $ _
|
// $ _
|
||||||
let default = "\\n\\e[1;0m\\u\\e[1;36m@\\e[1;31m\\h\\n\\e[1;36m\\W\\e[1;32m/\\n\\e[1;32m\\$\\e[0m ";
|
let default =
|
||||||
return expand_prompt(default)
|
"\\n\\e[1;0m\\u\\e[1;36m@\\e[1;31m\\h\\n\\e[1;36m\\W\\e[1;32m/\\n\\e[1;32m\\$\\e[0m ";
|
||||||
};
|
return expand_prompt(default);
|
||||||
|
};
|
||||||
|
|
||||||
expand_prompt(&prompt)
|
expand_prompt(&prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn readline(edit_mode: FernEditMode) -> ShResult<String> {
|
pub fn readline(edit_mode: FernEditMode) -> ShResult<String> {
|
||||||
let prompt = get_prompt()?;
|
let prompt = get_prompt()?;
|
||||||
let mut reader: Box<dyn Readline> = match edit_mode {
|
let mut reader: Box<dyn Readline> = match edit_mode {
|
||||||
FernEditMode::Vi => Box::new(FernVi::new(Some(prompt))?),
|
FernEditMode::Vi => Box::new(FernVi::new(Some(prompt))?),
|
||||||
FernEditMode::Emacs => todo!()
|
FernEditMode::Emacs => todo!(),
|
||||||
};
|
};
|
||||||
reader.readline()
|
reader.readline()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,350 +1,393 @@
|
|||||||
use std::{env, fmt::{Write,Display}, fs::{self, OpenOptions}, io::Write as IoWrite, path::{Path, PathBuf}, str::FromStr, time::{Duration, SystemTime, UNIX_EPOCH}};
|
use std::{
|
||||||
|
env,
|
||||||
|
fmt::{Display, Write},
|
||||||
|
fs::{self, OpenOptions},
|
||||||
|
io::Write as IoWrite,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
str::FromStr,
|
||||||
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::libsh::error::{ShErr, ShErrKind, ShResult};
|
use crate::libsh::error::{ShErr, ShErrKind, ShResult};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use super::vicmd::Direction; // surprisingly useful
|
use super::vicmd::Direction; // surprisingly useful
|
||||||
|
|
||||||
#[derive(Default,Clone,Copy,Debug)]
|
#[derive(Default, Clone, Copy, Debug)]
|
||||||
pub enum SearchKind {
|
pub enum SearchKind {
|
||||||
Fuzzy,
|
Fuzzy,
|
||||||
#[default]
|
#[default]
|
||||||
Prefix
|
Prefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default,Clone,Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct SearchConstraint {
|
pub struct SearchConstraint {
|
||||||
kind: SearchKind,
|
kind: SearchKind,
|
||||||
term: String,
|
term: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SearchConstraint {
|
impl SearchConstraint {
|
||||||
pub fn new(kind: SearchKind, term: String) -> Self {
|
pub fn new(kind: SearchKind, term: String) -> Self {
|
||||||
Self { kind, term }
|
Self { kind, term }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HistEntry {
|
pub struct HistEntry {
|
||||||
id: u32,
|
id: u32,
|
||||||
timestamp: SystemTime,
|
timestamp: SystemTime,
|
||||||
command: String,
|
command: String,
|
||||||
new: bool
|
new: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HistEntry {
|
impl HistEntry {
|
||||||
pub fn id(&self) -> u32 {
|
pub fn id(&self) -> u32 {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
pub fn timestamp(&self) -> &SystemTime {
|
pub fn timestamp(&self) -> &SystemTime {
|
||||||
&self.timestamp
|
&self.timestamp
|
||||||
}
|
}
|
||||||
pub fn command(&self) -> &str {
|
pub fn command(&self) -> &str {
|
||||||
&self.command
|
&self.command
|
||||||
}
|
}
|
||||||
fn with_escaped_newlines(&self) -> String {
|
fn with_escaped_newlines(&self) -> String {
|
||||||
let mut escaped = String::new();
|
let mut escaped = String::new();
|
||||||
let mut chars = self.command.chars();
|
let mut chars = self.command.chars();
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
'\\' => {
|
'\\' => {
|
||||||
escaped.push(ch);
|
escaped.push(ch);
|
||||||
if let Some(ch) = chars.next() {
|
if let Some(ch) = chars.next() {
|
||||||
escaped.push(ch)
|
escaped.push(ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\n' => {
|
'\n' => {
|
||||||
escaped.push_str("\\\n");
|
escaped.push_str("\\\n");
|
||||||
}
|
}
|
||||||
_ => escaped.push(ch),
|
_ => escaped.push(ch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
escaped
|
escaped
|
||||||
}
|
}
|
||||||
pub fn is_new(&self) -> bool {
|
pub fn is_new(&self) -> bool {
|
||||||
self.new
|
self.new
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for HistEntry {
|
impl FromStr for HistEntry {
|
||||||
type Err = ShErr;
|
type Err = ShErr;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let err = Err(
|
let err = Err(ShErr::Simple {
|
||||||
ShErr::Simple { kind: ShErrKind::HistoryReadErr, msg: format!("Bad formatting on history entry '{s}'"), notes: vec![] }
|
kind: ShErrKind::HistoryReadErr,
|
||||||
);
|
msg: format!("Bad formatting on history entry '{s}'"),
|
||||||
|
notes: vec![],
|
||||||
|
});
|
||||||
|
|
||||||
//: 248972349;148;echo foo; echo bar
|
//: 248972349;148;echo foo; echo bar
|
||||||
let Some(cleaned) = s.strip_prefix(": ") else { return err };
|
let Some(cleaned) = s.strip_prefix(": ") else {
|
||||||
//248972349;148;echo foo; echo bar
|
return err;
|
||||||
let Some((timestamp,id_and_command)) = cleaned.split_once(';') else { return err };
|
};
|
||||||
//("248972349","148;echo foo; echo bar")
|
//248972349;148;echo foo; echo bar
|
||||||
let Some((id,command)) = id_and_command.split_once(';') else { return err };
|
let Some((timestamp, id_and_command)) = cleaned.split_once(';') else {
|
||||||
//("148","echo foo; echo bar")
|
return err;
|
||||||
let Ok(ts_seconds) = timestamp.parse::<u64>() else { return err };
|
};
|
||||||
let Ok(id) = id.parse::<u32>() else { return err };
|
//("248972349","148;echo foo; echo bar")
|
||||||
let timestamp = UNIX_EPOCH + Duration::from_secs(ts_seconds);
|
let Some((id, command)) = id_and_command.split_once(';') else {
|
||||||
let command = command.to_string();
|
return err;
|
||||||
Ok(Self { id, timestamp, command, new: false })
|
};
|
||||||
}
|
//("148","echo foo; echo bar")
|
||||||
|
let Ok(ts_seconds) = timestamp.parse::<u64>() else {
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
let Ok(id) = id.parse::<u32>() else {
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
let timestamp = UNIX_EPOCH + Duration::from_secs(ts_seconds);
|
||||||
|
let command = command.to_string();
|
||||||
|
Ok(Self {
|
||||||
|
id,
|
||||||
|
timestamp,
|
||||||
|
command,
|
||||||
|
new: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for HistEntry {
|
impl Display for HistEntry {
|
||||||
/// Similar to zsh's history format, but not entirely
|
/// Similar to zsh's history format, but not entirely
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let command = self.with_escaped_newlines();
|
let command = self.with_escaped_newlines();
|
||||||
let HistEntry { id, timestamp, command: _, new: _ } = self;
|
let HistEntry {
|
||||||
let timestamp = timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs();
|
id,
|
||||||
writeln!(f, ": {timestamp};{id};{command}")
|
timestamp,
|
||||||
}
|
command: _,
|
||||||
|
new: _,
|
||||||
|
} = self;
|
||||||
|
let timestamp = timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||||
|
writeln!(f, ": {timestamp};{id};{command}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HistEntries(Vec<HistEntry>);
|
pub struct HistEntries(Vec<HistEntry>);
|
||||||
|
|
||||||
|
|
||||||
impl FromStr for HistEntries {
|
impl FromStr for HistEntries {
|
||||||
type Err = ShErr;
|
type Err = ShErr;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let mut entries = vec![];
|
let mut entries = vec![];
|
||||||
|
|
||||||
let mut lines = s.lines().enumerate().peekable();
|
let mut lines = s.lines().enumerate().peekable();
|
||||||
let mut cur_line = String::new();
|
let mut cur_line = String::new();
|
||||||
|
|
||||||
while let Some((i,line)) = lines.next() {
|
while let Some((i, line)) = lines.next() {
|
||||||
if !line.starts_with(": ") {
|
if !line.starts_with(": ") {
|
||||||
return Err(
|
return Err(ShErr::Simple {
|
||||||
ShErr::Simple { kind: ShErrKind::HistoryReadErr, msg: format!("Bad formatting on line {i}"), notes: vec![] }
|
kind: ShErrKind::HistoryReadErr,
|
||||||
)
|
msg: format!("Bad formatting on line {i}"),
|
||||||
}
|
notes: vec![],
|
||||||
let mut chars = line.chars().peekable();
|
});
|
||||||
let mut feeding_lines = true;
|
}
|
||||||
while feeding_lines {
|
let mut chars = line.chars().peekable();
|
||||||
feeding_lines = false;
|
let mut feeding_lines = true;
|
||||||
while let Some(ch) = chars.next() {
|
while feeding_lines {
|
||||||
match ch {
|
feeding_lines = false;
|
||||||
'\\' => {
|
while let Some(ch) = chars.next() {
|
||||||
if let Some(esc_ch) = chars.next() {
|
match ch {
|
||||||
cur_line.push(esc_ch);
|
'\\' => {
|
||||||
} else {
|
if let Some(esc_ch) = chars.next() {
|
||||||
cur_line.push('\n');
|
cur_line.push(esc_ch);
|
||||||
feeding_lines = true;
|
} else {
|
||||||
}
|
cur_line.push('\n');
|
||||||
}
|
feeding_lines = true;
|
||||||
'\n' => {
|
}
|
||||||
break
|
}
|
||||||
}
|
'\n' => break,
|
||||||
_ => {
|
_ => {
|
||||||
cur_line.push(ch);
|
cur_line.push(ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if feeding_lines {
|
if feeding_lines {
|
||||||
let Some((_,line)) = lines.next() else {
|
let Some((_, line)) = lines.next() else {
|
||||||
return Err(
|
return Err(ShErr::Simple {
|
||||||
ShErr::Simple { kind: ShErrKind::HistoryReadErr, msg: format!("Bad formatting on line {i}"), notes: vec![] }
|
kind: ShErrKind::HistoryReadErr,
|
||||||
)
|
msg: format!("Bad formatting on line {i}"),
|
||||||
};
|
notes: vec![],
|
||||||
chars = line.chars().peekable();
|
});
|
||||||
}
|
};
|
||||||
}
|
chars = line.chars().peekable();
|
||||||
let entry = cur_line.parse::<HistEntry>()?;
|
}
|
||||||
entries.push(entry);
|
}
|
||||||
cur_line.clear();
|
let entry = cur_line.parse::<HistEntry>()?;
|
||||||
}
|
entries.push(entry);
|
||||||
|
cur_line.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self(entries))
|
||||||
Ok(Self(entries))
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_hist_file(path: &Path) -> ShResult<Vec<HistEntry>> {
|
fn read_hist_file(path: &Path) -> ShResult<Vec<HistEntry>> {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
fs::File::create(path)?;
|
fs::File::create(path)?;
|
||||||
}
|
}
|
||||||
let raw = fs::read_to_string(path)?;
|
let raw = fs::read_to_string(path)?;
|
||||||
Ok(raw.parse::<HistEntries>()?.0)
|
Ok(raw.parse::<HistEntries>()?.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct History {
|
pub struct History {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
entries: Vec<HistEntry>,
|
entries: Vec<HistEntry>,
|
||||||
search_mask: Vec<HistEntry>,
|
search_mask: Vec<HistEntry>,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
search_direction: Direction,
|
search_direction: Direction,
|
||||||
ignore_dups: bool,
|
ignore_dups: bool,
|
||||||
max_size: Option<u32>,
|
max_size: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl History {
|
impl History {
|
||||||
pub fn new() -> ShResult<Self> {
|
pub fn new() -> ShResult<Self> {
|
||||||
let path = PathBuf::from(env::var("FERNHIST").unwrap_or({
|
let path = PathBuf::from(env::var("FERNHIST").unwrap_or({
|
||||||
let home = env::var("HOME").unwrap();
|
let home = env::var("HOME").unwrap();
|
||||||
format!("{home}/.fern_history")
|
format!("{home}/.fern_history")
|
||||||
}));
|
}));
|
||||||
let mut entries = read_hist_file(&path)?;
|
let mut entries = read_hist_file(&path)?;
|
||||||
{
|
{
|
||||||
let id = entries.last().map(|ent| ent.id + 1).unwrap_or(0);
|
let id = entries.last().map(|ent| ent.id + 1).unwrap_or(0);
|
||||||
let timestamp = SystemTime::now();
|
let timestamp = SystemTime::now();
|
||||||
let command = "".into();
|
let command = "".into();
|
||||||
entries.push(HistEntry { id, timestamp, command, new: true })
|
entries.push(HistEntry {
|
||||||
}
|
id,
|
||||||
let search_mask = entries.clone();
|
timestamp,
|
||||||
let cursor = entries.len() - 1;
|
command,
|
||||||
let mut new = Self {
|
new: true,
|
||||||
path,
|
})
|
||||||
entries,
|
}
|
||||||
search_mask,
|
let search_mask = entries.clone();
|
||||||
cursor,
|
let cursor = entries.len() - 1;
|
||||||
search_direction: Direction::Backward,
|
let mut new = Self {
|
||||||
ignore_dups: true,
|
path,
|
||||||
max_size: None,
|
entries,
|
||||||
};
|
search_mask,
|
||||||
new.push_empty_entry(); // Current pending command
|
cursor,
|
||||||
Ok(new)
|
search_direction: Direction::Backward,
|
||||||
}
|
ignore_dups: true,
|
||||||
|
max_size: None,
|
||||||
|
};
|
||||||
|
new.push_empty_entry(); // Current pending command
|
||||||
|
Ok(new)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn entries(&self) -> &[HistEntry] {
|
pub fn entries(&self) -> &[HistEntry] {
|
||||||
&self.entries
|
&self.entries
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn masked_entries(&self) -> &[HistEntry] {
|
pub fn masked_entries(&self) -> &[HistEntry] {
|
||||||
&self.search_mask
|
&self.search_mask
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_empty_entry(&mut self) {
|
pub fn push_empty_entry(&mut self) {}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cursor_entry(&self) -> Option<&HistEntry> {
|
pub fn cursor_entry(&self) -> Option<&HistEntry> {
|
||||||
self.search_mask.get(self.cursor)
|
self.search_mask.get(self.cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_pending_cmd(&mut self, command: &str) {
|
pub fn update_pending_cmd(&mut self, command: &str) {
|
||||||
let Some(ent) = self.last_mut() else {
|
let Some(ent) = self.last_mut() else { return };
|
||||||
return
|
let cmd = command.to_string();
|
||||||
};
|
let constraint = SearchConstraint {
|
||||||
let cmd = command.to_string();
|
kind: SearchKind::Prefix,
|
||||||
let constraint = SearchConstraint { kind: SearchKind::Prefix, term: cmd.clone() };
|
term: cmd.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
ent.command = cmd;
|
||||||
|
self.constrain_entries(constraint);
|
||||||
|
}
|
||||||
|
|
||||||
ent.command = cmd;
|
pub fn last_mut(&mut self) -> Option<&mut HistEntry> {
|
||||||
self.constrain_entries(constraint);
|
self.entries.last_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_mut(&mut self) -> Option<&mut HistEntry> {
|
pub fn get_new_id(&self) -> u32 {
|
||||||
self.entries.last_mut()
|
let Some(ent) = self.entries.last() else {
|
||||||
}
|
return 0;
|
||||||
|
};
|
||||||
|
ent.id + 1
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_new_id(&self) -> u32 {
|
pub fn ignore_dups(&mut self, yn: bool) {
|
||||||
let Some(ent) = self.entries.last() else {
|
self.ignore_dups = yn
|
||||||
return 0
|
}
|
||||||
};
|
|
||||||
ent.id + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ignore_dups(&mut self, yn: bool) {
|
pub fn max_hist_size(&mut self, size: Option<u32>) {
|
||||||
self.ignore_dups = yn
|
self.max_size = size
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_hist_size(&mut self, size: Option<u32>) {
|
pub fn constrain_entries(&mut self, constraint: SearchConstraint) {
|
||||||
self.max_size = size
|
flog!(DEBUG, constraint);
|
||||||
}
|
let SearchConstraint { kind, term } = constraint;
|
||||||
|
match kind {
|
||||||
|
SearchKind::Prefix => {
|
||||||
|
if term.is_empty() {
|
||||||
|
self.search_mask = self.entries.clone();
|
||||||
|
} else {
|
||||||
|
let filtered = self
|
||||||
|
.entries
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|ent| ent.command().starts_with(&term));
|
||||||
|
|
||||||
pub fn constrain_entries(&mut self, constraint: SearchConstraint) {
|
self.search_mask = filtered.collect();
|
||||||
flog!(DEBUG,constraint);
|
}
|
||||||
let SearchConstraint { kind, term } = constraint;
|
self.cursor = self.search_mask.len().saturating_sub(1);
|
||||||
match kind {
|
}
|
||||||
SearchKind::Prefix => {
|
SearchKind::Fuzzy => todo!(),
|
||||||
if term.is_empty() {
|
}
|
||||||
self.search_mask = self.entries.clone();
|
}
|
||||||
} else {
|
|
||||||
let filtered = self.entries
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.filter(|ent| ent.command().starts_with(&term));
|
|
||||||
|
|
||||||
self.search_mask = filtered.collect();
|
pub fn hint_entry(&self) -> Option<&HistEntry> {
|
||||||
}
|
let second_to_last = self.search_mask.len().checked_sub(2)?;
|
||||||
self.cursor = self.search_mask.len().saturating_sub(1);
|
self.search_mask.get(second_to_last)
|
||||||
}
|
}
|
||||||
SearchKind::Fuzzy => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hint_entry(&self) -> Option<&HistEntry> {
|
pub fn get_hint(&self) -> Option<String> {
|
||||||
let second_to_last = self.search_mask.len().checked_sub(2)?;
|
if self
|
||||||
self.search_mask.get(second_to_last)
|
.cursor_entry()
|
||||||
}
|
.is_some_and(|ent| ent.is_new() && !ent.command().is_empty())
|
||||||
|
{
|
||||||
|
let entry = self.hint_entry()?;
|
||||||
|
let prefix = self.cursor_entry()?.command();
|
||||||
|
Some(entry.command().to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_hint(&self) -> Option<String> {
|
pub fn scroll(&mut self, offset: isize) -> Option<&HistEntry> {
|
||||||
if self.cursor_entry().is_some_and(|ent| ent.is_new() && !ent.command().is_empty()) {
|
let new_idx = self
|
||||||
let entry = self.hint_entry()?;
|
.cursor
|
||||||
let prefix = self.cursor_entry()?.command();
|
.saturating_add_signed(offset)
|
||||||
Some(entry.command().to_string())
|
.clamp(0, self.search_mask.len().saturating_sub(1));
|
||||||
} else {
|
let ent = self.search_mask.get(new_idx)?;
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scroll(&mut self, offset: isize) -> Option<&HistEntry> {
|
self.cursor = new_idx;
|
||||||
let new_idx = self.cursor
|
|
||||||
.saturating_add_signed(offset)
|
|
||||||
.clamp(0, self.search_mask.len().saturating_sub(1));
|
|
||||||
let ent = self.search_mask.get(new_idx)?;
|
|
||||||
|
|
||||||
self.cursor = new_idx;
|
Some(ent)
|
||||||
|
}
|
||||||
|
|
||||||
Some(ent)
|
pub fn push(&mut self, command: String) {
|
||||||
}
|
let timestamp = SystemTime::now();
|
||||||
|
let id = self.get_new_id();
|
||||||
|
if self.ignore_dups && self.is_dup(&command) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.entries.push(HistEntry {
|
||||||
|
id,
|
||||||
|
timestamp,
|
||||||
|
command,
|
||||||
|
new: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, command: String) {
|
pub fn is_dup(&self, other: &str) -> bool {
|
||||||
let timestamp = SystemTime::now();
|
let Some(ent) = self.entries.last() else {
|
||||||
let id = self.get_new_id();
|
return false;
|
||||||
if self.ignore_dups && self.is_dup(&command) {
|
};
|
||||||
return
|
let ent_cmd = &ent.command;
|
||||||
}
|
ent_cmd == other
|
||||||
self.entries.push(HistEntry { id, timestamp, command, new: true });
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_dup(&self, other: &str) -> bool {
|
pub fn save(&mut self) -> ShResult<()> {
|
||||||
let Some(ent) = self.entries.last() else {
|
let mut file = OpenOptions::new()
|
||||||
return false
|
.create(true)
|
||||||
};
|
.append(true)
|
||||||
let ent_cmd = &ent.command;
|
.open(&self.path)?;
|
||||||
ent_cmd == other
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save(&mut self) -> ShResult<()> {
|
let last_file_entry = self
|
||||||
let mut file = OpenOptions::new()
|
.entries
|
||||||
.create(true)
|
.iter()
|
||||||
.append(true)
|
.filter(|ent| !ent.new)
|
||||||
.open(&self.path)?;
|
.next_back()
|
||||||
|
.map(|ent| ent.command.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let last_file_entry = self.entries
|
let entries = self.entries.iter_mut().filter(|ent| {
|
||||||
.iter()
|
ent.new
|
||||||
.filter(|ent| !ent.new)
|
&& !ent.command.is_empty()
|
||||||
.next_back()
|
&& if self.ignore_dups {
|
||||||
.map(|ent| ent.command.clone())
|
ent.command() != last_file_entry
|
||||||
.unwrap_or_default();
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let entries = self.entries
|
let mut data = String::new();
|
||||||
.iter_mut()
|
for ent in entries {
|
||||||
.filter(|ent| {
|
ent.new = false;
|
||||||
ent.new &&
|
write!(data, "{ent}").unwrap();
|
||||||
!ent.command.is_empty() &&
|
}
|
||||||
if self.ignore_dups {
|
|
||||||
ent.command() != last_file_entry
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut data = String::new();
|
file.write_all(data.as_bytes())?;
|
||||||
for ent in entries {
|
|
||||||
ent.new = false;
|
|
||||||
write!(data, "{ent}").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
file.write_all(data.as_bytes())?;
|
Ok(())
|
||||||
|
}
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,140 +3,137 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||||||
|
|
||||||
// Credit to Rustyline for the design ideas in this module
|
// Credit to Rustyline for the design ideas in this module
|
||||||
// https://github.com/kkawakam/rustyline
|
// https://github.com/kkawakam/rustyline
|
||||||
#[derive(Clone,PartialEq,Eq,Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct KeyEvent(pub KeyCode, pub ModKeys);
|
pub struct KeyEvent(pub KeyCode, pub ModKeys);
|
||||||
|
|
||||||
|
|
||||||
impl KeyEvent {
|
impl KeyEvent {
|
||||||
pub fn new(ch: &str, mut mods: ModKeys) -> Self {
|
pub fn new(ch: &str, mut mods: ModKeys) -> Self {
|
||||||
use {KeyCode as K, KeyEvent as E, ModKeys as M};
|
use {KeyCode as K, KeyEvent as E, ModKeys as M};
|
||||||
|
|
||||||
let mut graphemes = ch.graphemes(true);
|
let mut graphemes = ch.graphemes(true);
|
||||||
|
|
||||||
let first = match graphemes.next() {
|
let first = match graphemes.next() {
|
||||||
Some(g) => g,
|
Some(g) => g,
|
||||||
None => return E(K::Null, mods),
|
None => return E(K::Null, mods),
|
||||||
};
|
};
|
||||||
|
|
||||||
// If more than one grapheme, it's not a single key event
|
// If more than one grapheme, it's not a single key event
|
||||||
if graphemes.next().is_some() {
|
if graphemes.next().is_some() {
|
||||||
return E(K::Null, mods); // Or panic, or wrap in Grapheme if desired
|
return E(K::Null, mods); // Or panic, or wrap in Grapheme if desired
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut chars = first.chars();
|
let mut chars = first.chars();
|
||||||
|
|
||||||
let single_char = chars.next();
|
let single_char = chars.next();
|
||||||
let is_single_char = chars.next().is_none();
|
let is_single_char = chars.next().is_none();
|
||||||
|
|
||||||
match single_char {
|
match single_char {
|
||||||
Some(c) if is_single_char && c.is_control() => {
|
Some(c) if is_single_char && c.is_control() => match c {
|
||||||
match c {
|
'\x00' => E(K::Char('@'), mods | M::CTRL),
|
||||||
'\x00' => E(K::Char('@'), mods | M::CTRL),
|
'\x01' => E(K::Char('A'), mods | M::CTRL),
|
||||||
'\x01' => E(K::Char('A'), mods | M::CTRL),
|
'\x02' => E(K::Char('B'), mods | M::CTRL),
|
||||||
'\x02' => E(K::Char('B'), mods | M::CTRL),
|
'\x03' => E(K::Char('C'), mods | M::CTRL),
|
||||||
'\x03' => E(K::Char('C'), mods | M::CTRL),
|
'\x04' => E(K::Char('D'), mods | M::CTRL),
|
||||||
'\x04' => E(K::Char('D'), mods | M::CTRL),
|
'\x05' => E(K::Char('E'), mods | M::CTRL),
|
||||||
'\x05' => E(K::Char('E'), mods | M::CTRL),
|
'\x06' => E(K::Char('F'), mods | M::CTRL),
|
||||||
'\x06' => E(K::Char('F'), mods | M::CTRL),
|
'\x07' => E(K::Char('G'), mods | M::CTRL),
|
||||||
'\x07' => E(K::Char('G'), mods | M::CTRL),
|
'\x08' => E(K::Backspace, mods),
|
||||||
'\x08' => E(K::Backspace, mods),
|
'\x09' => {
|
||||||
'\x09' => {
|
if mods.contains(M::SHIFT) {
|
||||||
if mods.contains(M::SHIFT) {
|
mods.remove(M::SHIFT);
|
||||||
mods.remove(M::SHIFT);
|
E(K::BackTab, mods)
|
||||||
E(K::BackTab, mods)
|
} else {
|
||||||
} else {
|
E(K::Tab, mods)
|
||||||
E(K::Tab, mods)
|
}
|
||||||
}
|
}
|
||||||
}
|
'\x0a' => E(K::Char('J'), mods | M::CTRL),
|
||||||
'\x0a' => E(K::Char('J'), mods | M::CTRL),
|
'\x0b' => E(K::Char('K'), mods | M::CTRL),
|
||||||
'\x0b' => E(K::Char('K'), mods | M::CTRL),
|
'\x0c' => E(K::Char('L'), mods | M::CTRL),
|
||||||
'\x0c' => E(K::Char('L'), mods | M::CTRL),
|
'\x0d' => E(K::Enter, mods),
|
||||||
'\x0d' => E(K::Enter, mods),
|
'\x0e' => E(K::Char('N'), mods | M::CTRL),
|
||||||
'\x0e' => E(K::Char('N'), mods | M::CTRL),
|
'\x0f' => E(K::Char('O'), mods | M::CTRL),
|
||||||
'\x0f' => E(K::Char('O'), mods | M::CTRL),
|
'\x10' => E(K::Char('P'), mods | M::CTRL),
|
||||||
'\x10' => E(K::Char('P'), mods | M::CTRL),
|
'\x11' => E(K::Char('Q'), mods | M::CTRL),
|
||||||
'\x11' => E(K::Char('Q'), mods | M::CTRL),
|
'\x12' => E(K::Char('R'), mods | M::CTRL),
|
||||||
'\x12' => E(K::Char('R'), mods | M::CTRL),
|
'\x13' => E(K::Char('S'), mods | M::CTRL),
|
||||||
'\x13' => E(K::Char('S'), mods | M::CTRL),
|
'\x14' => E(K::Char('T'), mods | M::CTRL),
|
||||||
'\x14' => E(K::Char('T'), mods | M::CTRL),
|
'\x15' => E(K::Char('U'), mods | M::CTRL),
|
||||||
'\x15' => E(K::Char('U'), mods | M::CTRL),
|
'\x16' => E(K::Char('V'), mods | M::CTRL),
|
||||||
'\x16' => E(K::Char('V'), mods | M::CTRL),
|
'\x17' => E(K::Char('W'), mods | M::CTRL),
|
||||||
'\x17' => E(K::Char('W'), mods | M::CTRL),
|
'\x18' => E(K::Char('X'), mods | M::CTRL),
|
||||||
'\x18' => E(K::Char('X'), mods | M::CTRL),
|
'\x19' => E(K::Char('Y'), mods | M::CTRL),
|
||||||
'\x19' => E(K::Char('Y'), mods | M::CTRL),
|
'\x1a' => E(K::Char('Z'), mods | M::CTRL),
|
||||||
'\x1a' => E(K::Char('Z'), mods | M::CTRL),
|
'\x1b' => E(K::Esc, mods),
|
||||||
'\x1b' => E(K::Esc, mods),
|
'\x1c' => E(K::Char('\\'), mods | M::CTRL),
|
||||||
'\x1c' => E(K::Char('\\'), mods | M::CTRL),
|
'\x1d' => E(K::Char(']'), mods | M::CTRL),
|
||||||
'\x1d' => E(K::Char(']'), mods | M::CTRL),
|
'\x1e' => E(K::Char('^'), mods | M::CTRL),
|
||||||
'\x1e' => E(K::Char('^'), mods | M::CTRL),
|
'\x1f' => E(K::Char('_'), mods | M::CTRL),
|
||||||
'\x1f' => E(K::Char('_'), mods | M::CTRL),
|
'\x7f' => E(K::Backspace, mods),
|
||||||
'\x7f' => E(K::Backspace, mods),
|
'\u{9b}' => E(K::Esc, mods | M::SHIFT),
|
||||||
'\u{9b}' => E(K::Esc, mods | M::SHIFT),
|
_ => E(K::Null, mods),
|
||||||
_ => E(K::Null, mods),
|
},
|
||||||
}
|
Some(c) if is_single_char => {
|
||||||
}
|
if !mods.is_empty() {
|
||||||
Some(c) if is_single_char => {
|
mods.remove(M::SHIFT);
|
||||||
if !mods.is_empty() {
|
}
|
||||||
mods.remove(M::SHIFT);
|
E(K::Char(c), mods)
|
||||||
}
|
}
|
||||||
E(K::Char(c), mods)
|
_ => {
|
||||||
}
|
// multi-char grapheme (emoji, accented, etc)
|
||||||
_ => {
|
if !mods.is_empty() {
|
||||||
// multi-char grapheme (emoji, accented, etc)
|
mods.remove(M::SHIFT);
|
||||||
if !mods.is_empty() {
|
}
|
||||||
mods.remove(M::SHIFT);
|
E(K::Grapheme(Arc::from(first)), mods)
|
||||||
}
|
}
|
||||||
E(K::Grapheme(Arc::from(first)), mods)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,PartialEq,Eq,Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum KeyCode {
|
pub enum KeyCode {
|
||||||
UnknownEscSeq,
|
UnknownEscSeq,
|
||||||
Backspace,
|
Backspace,
|
||||||
BackTab,
|
BackTab,
|
||||||
BracketedPasteStart,
|
BracketedPasteStart,
|
||||||
BracketedPasteEnd,
|
BracketedPasteEnd,
|
||||||
Char(char),
|
Char(char),
|
||||||
Grapheme(Arc<str>),
|
Grapheme(Arc<str>),
|
||||||
Delete,
|
Delete,
|
||||||
Down,
|
Down,
|
||||||
End,
|
End,
|
||||||
Enter,
|
Enter,
|
||||||
Esc,
|
Esc,
|
||||||
F(u8),
|
F(u8),
|
||||||
Home,
|
Home,
|
||||||
Insert,
|
Insert,
|
||||||
Left,
|
Left,
|
||||||
Null,
|
Null,
|
||||||
PageDown,
|
PageDown,
|
||||||
PageUp,
|
PageUp,
|
||||||
Right,
|
Right,
|
||||||
Tab,
|
Tab,
|
||||||
Up,
|
Up,
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
pub struct ModKeys: u8 {
|
pub struct ModKeys: u8 {
|
||||||
/// Control modifier
|
/// Control modifier
|
||||||
const CTRL = 1<<3;
|
const CTRL = 1<<3;
|
||||||
/// Escape or Alt modifier
|
/// Escape or Alt modifier
|
||||||
const ALT = 1<<2;
|
const ALT = 1<<2;
|
||||||
/// Shift modifier
|
/// Shift modifier
|
||||||
const SHIFT = 1<<1;
|
const SHIFT = 1<<1;
|
||||||
|
|
||||||
/// No modifier
|
/// No modifier
|
||||||
const NONE = 0;
|
const NONE = 0;
|
||||||
/// Ctrl + Shift
|
/// Ctrl + Shift
|
||||||
const CTRL_SHIFT = Self::CTRL.bits() | Self::SHIFT.bits();
|
const CTRL_SHIFT = Self::CTRL.bits() | Self::SHIFT.bits();
|
||||||
/// Alt + Shift
|
/// Alt + Shift
|
||||||
const ALT_SHIFT = Self::ALT.bits() | Self::SHIFT.bits();
|
const ALT_SHIFT = Self::ALT.bits() | Self::SHIFT.bits();
|
||||||
/// Ctrl + Alt
|
/// Ctrl + Alt
|
||||||
const CTRL_ALT = Self::CTRL.bits() | Self::ALT.bits();
|
const CTRL_ALT = Self::CTRL.bits() | Self::ALT.bits();
|
||||||
/// Ctrl + Alt + Shift
|
/// Ctrl + Alt + Shift
|
||||||
const CTRL_ALT_SHIFT = Self::CTRL.bits() | Self::ALT.bits() | Self::SHIFT.bits();
|
const CTRL_ALT_SHIFT = Self::CTRL.bits() | Self::ALT.bits() | Self::SHIFT.bits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,396 +6,377 @@ use term::{get_win_size, raw_mode, KeyReader, Layout, LineWriter, TermReader, Te
|
|||||||
use vicmd::{CmdFlags, Motion, MotionCmd, RegisterName, To, Verb, VerbCmd, ViCmd};
|
use vicmd::{CmdFlags, Motion, MotionCmd, RegisterName, To, Verb, VerbCmd, ViCmd};
|
||||||
use vimode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace, ViVisual};
|
use vimode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace, ViVisual};
|
||||||
|
|
||||||
use crate::libsh::{error::{ShErr, ShErrKind, ShResult}, sys::sh_quit, term::{Style, Styled}};
|
use crate::libsh::{
|
||||||
|
error::{ShErr, ShErrKind, ShResult},
|
||||||
|
sys::sh_quit,
|
||||||
|
term::{Style, Styled},
|
||||||
|
};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub mod term;
|
|
||||||
pub mod linebuf;
|
|
||||||
pub mod layout;
|
|
||||||
pub mod keys;
|
|
||||||
pub mod vicmd;
|
|
||||||
pub mod register;
|
|
||||||
pub mod vimode;
|
|
||||||
pub mod history;
|
pub mod history;
|
||||||
|
pub mod keys;
|
||||||
|
pub mod layout;
|
||||||
|
pub mod linebuf;
|
||||||
|
pub mod register;
|
||||||
|
pub mod term;
|
||||||
|
pub mod vicmd;
|
||||||
|
pub mod vimode;
|
||||||
|
|
||||||
// Very useful for testing
|
// Very useful for testing
|
||||||
const LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nCurabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra.";
|
const LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nCurabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra.";
|
||||||
|
|
||||||
pub trait Readline {
|
pub trait Readline {
|
||||||
fn readline(&mut self) -> ShResult<String>;
|
fn readline(&mut self) -> ShResult<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FernVi {
|
pub struct FernVi {
|
||||||
pub reader: Box<dyn KeyReader>,
|
pub reader: Box<dyn KeyReader>,
|
||||||
pub writer: Box<dyn LineWriter>,
|
pub writer: Box<dyn LineWriter>,
|
||||||
pub prompt: String,
|
pub prompt: String,
|
||||||
pub mode: Box<dyn ViMode>,
|
pub mode: Box<dyn ViMode>,
|
||||||
pub old_layout: Option<Layout>,
|
pub old_layout: Option<Layout>,
|
||||||
pub repeat_action: Option<CmdReplay>,
|
pub repeat_action: Option<CmdReplay>,
|
||||||
pub repeat_motion: Option<MotionCmd>,
|
pub repeat_motion: Option<MotionCmd>,
|
||||||
pub editor: LineBuf,
|
pub editor: LineBuf,
|
||||||
pub history: History
|
pub history: History,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Readline for FernVi {
|
impl Readline for FernVi {
|
||||||
fn readline(&mut self) -> ShResult<String> {
|
fn readline(&mut self) -> ShResult<String> {
|
||||||
let raw_mode_guard = raw_mode(); // Restores termios state on drop
|
let raw_mode_guard = raw_mode(); // Restores termios state on drop
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
raw_mode_guard.disable_for(|| self.print_line())?;
|
raw_mode_guard.disable_for(|| self.print_line())?;
|
||||||
|
|
||||||
let Some(key) = self.reader.read_key() else {
|
let Some(key) = self.reader.read_key() else {
|
||||||
raw_mode_guard.disable_for(|| self.writer.flush_write("\n"))?;
|
raw_mode_guard.disable_for(|| self.writer.flush_write("\n"))?;
|
||||||
std::mem::drop(raw_mode_guard);
|
std::mem::drop(raw_mode_guard);
|
||||||
return Err(ShErr::simple(ShErrKind::ReadlineErr, "EOF"))
|
return Err(ShErr::simple(ShErrKind::ReadlineErr, "EOF"));
|
||||||
};
|
};
|
||||||
flog!(DEBUG, key);
|
flog!(DEBUG, key);
|
||||||
|
|
||||||
if self.should_accept_hint(&key) {
|
if self.should_accept_hint(&key) {
|
||||||
self.editor.accept_hint();
|
self.editor.accept_hint();
|
||||||
self.history.update_pending_cmd(self.editor.as_str());
|
self.history.update_pending_cmd(self.editor.as_str());
|
||||||
self.print_line()?;
|
self.print_line()?;
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(mut cmd) = self.mode.handle_key(key) else {
|
let Some(mut cmd) = self.mode.handle_key(key) else {
|
||||||
flog!(DEBUG, "got none??");
|
flog!(DEBUG, "got none??");
|
||||||
continue
|
continue;
|
||||||
};
|
};
|
||||||
flog!(DEBUG,cmd);
|
flog!(DEBUG, cmd);
|
||||||
cmd.alter_line_motion_if_no_verb();
|
cmd.alter_line_motion_if_no_verb();
|
||||||
|
|
||||||
if self.should_grab_history(&cmd) {
|
if self.should_grab_history(&cmd) {
|
||||||
self.scroll_history(cmd);
|
self.scroll_history(cmd);
|
||||||
self.print_line()?;
|
self.print_line()?;
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.should_submit() {
|
if cmd.should_submit() {
|
||||||
raw_mode_guard.disable_for(|| self.writer.flush_write("\n"))?;
|
raw_mode_guard.disable_for(|| self.writer.flush_write("\n"))?;
|
||||||
std::mem::drop(raw_mode_guard);
|
std::mem::drop(raw_mode_guard);
|
||||||
return Ok(self.editor.take_buf())
|
return Ok(self.editor.take_buf());
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.verb().is_some_and(|v| v.1 == Verb::EndOfFile) {
|
if cmd.verb().is_some_and(|v| v.1 == Verb::EndOfFile) {
|
||||||
if self.editor.buffer.is_empty() {
|
if self.editor.buffer.is_empty() {
|
||||||
std::mem::drop(raw_mode_guard);
|
std::mem::drop(raw_mode_guard);
|
||||||
sh_quit(0);
|
sh_quit(0);
|
||||||
} else {
|
} else {
|
||||||
self.editor.buffer.clear();
|
self.editor.buffer.clear();
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flog!(DEBUG,cmd);
|
flog!(DEBUG, cmd);
|
||||||
|
|
||||||
let before = self.editor.buffer.clone();
|
let before = self.editor.buffer.clone();
|
||||||
self.exec_cmd(cmd)?;
|
self.exec_cmd(cmd)?;
|
||||||
let after = self.editor.as_str();
|
let after = self.editor.as_str();
|
||||||
|
|
||||||
if before != after {
|
if before != after {
|
||||||
self.history.update_pending_cmd(self.editor.as_str());
|
self.history.update_pending_cmd(self.editor.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
let hint = self.history.get_hint();
|
let hint = self.history.get_hint();
|
||||||
self.editor.set_hint(hint);
|
self.editor.set_hint(hint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FernVi {
|
impl FernVi {
|
||||||
pub fn new(prompt: Option<String>) -> ShResult<Self> {
|
pub fn new(prompt: Option<String>) -> ShResult<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
reader: Box::new(TermReader::new()),
|
reader: Box::new(TermReader::new()),
|
||||||
writer: Box::new(TermWriter::new(STDOUT_FILENO)),
|
writer: Box::new(TermWriter::new(STDOUT_FILENO)),
|
||||||
prompt: prompt.unwrap_or("$ ".styled(Style::Green)),
|
prompt: prompt.unwrap_or("$ ".styled(Style::Green)),
|
||||||
mode: Box::new(ViInsert::new()),
|
mode: Box::new(ViInsert::new()),
|
||||||
old_layout: None,
|
old_layout: None,
|
||||||
repeat_action: None,
|
repeat_action: None,
|
||||||
repeat_motion: None,
|
repeat_motion: None,
|
||||||
editor: LineBuf::new().with_initial(LOREM_IPSUM, 0),
|
editor: LineBuf::new().with_initial(LOREM_IPSUM, 0),
|
||||||
history: History::new()?
|
history: History::new()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_layout(&mut self) -> Layout {
|
pub fn get_layout(&mut self) -> Layout {
|
||||||
let line = self.editor.to_string();
|
let line = self.editor.to_string();
|
||||||
flog!(DEBUG,line);
|
flog!(DEBUG, line);
|
||||||
let to_cursor = self.editor.slice_to_cursor().unwrap_or_default();
|
let to_cursor = self.editor.slice_to_cursor().unwrap_or_default();
|
||||||
let (cols,_) = get_win_size(STDIN_FILENO);
|
let (cols, _) = get_win_size(STDIN_FILENO);
|
||||||
Layout::from_parts(
|
Layout::from_parts(/* tab_stop: */ 8, cols, &self.prompt, to_cursor, &line)
|
||||||
/*tab_stop:*/ 8,
|
}
|
||||||
cols,
|
pub fn scroll_history(&mut self, cmd: ViCmd) {
|
||||||
&self.prompt,
|
flog!(DEBUG, "scrolling");
|
||||||
to_cursor,
|
/*
|
||||||
&line
|
if self.history.cursor_entry().is_some_and(|ent| ent.is_new()) {
|
||||||
)
|
let constraint = SearchConstraint::new(SearchKind::Prefix, self.editor.to_string());
|
||||||
}
|
self.history.constrain_entries(constraint);
|
||||||
pub fn scroll_history(&mut self, cmd: ViCmd) {
|
}
|
||||||
flog!(DEBUG,"scrolling");
|
*/
|
||||||
/*
|
let count = &cmd.motion().unwrap().0;
|
||||||
if self.history.cursor_entry().is_some_and(|ent| ent.is_new()) {
|
let motion = &cmd.motion().unwrap().1;
|
||||||
let constraint = SearchConstraint::new(SearchKind::Prefix, self.editor.to_string());
|
flog!(DEBUG, count, motion);
|
||||||
self.history.constrain_entries(constraint);
|
flog!(DEBUG, self.history.masked_entries());
|
||||||
}
|
let entry = match motion {
|
||||||
*/
|
Motion::LineUpCharwise => {
|
||||||
let count = &cmd.motion().unwrap().0;
|
let Some(hist_entry) = self.history.scroll(-(*count as isize)) else {
|
||||||
let motion = &cmd.motion().unwrap().1;
|
return;
|
||||||
flog!(DEBUG,count,motion);
|
};
|
||||||
flog!(DEBUG,self.history.masked_entries());
|
flog!(DEBUG, "found entry");
|
||||||
let entry = match motion {
|
flog!(DEBUG, hist_entry.command());
|
||||||
Motion::LineUpCharwise => {
|
hist_entry
|
||||||
let Some(hist_entry) = self.history.scroll(-(*count as isize)) else {
|
}
|
||||||
return
|
Motion::LineDownCharwise => {
|
||||||
};
|
let Some(hist_entry) = self.history.scroll(*count as isize) else {
|
||||||
flog!(DEBUG,"found entry");
|
return;
|
||||||
flog!(DEBUG,hist_entry.command());
|
};
|
||||||
hist_entry
|
flog!(DEBUG, "found entry");
|
||||||
}
|
flog!(DEBUG, hist_entry.command());
|
||||||
Motion::LineDownCharwise => {
|
hist_entry
|
||||||
let Some(hist_entry) = self.history.scroll(*count as isize) else {
|
}
|
||||||
return
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
flog!(DEBUG,"found entry");
|
let col = self.editor.saved_col.unwrap_or(self.editor.cursor_col());
|
||||||
flog!(DEBUG,hist_entry.command());
|
let mut buf = LineBuf::new().with_initial(entry.command(), 0);
|
||||||
hist_entry
|
let line_end = buf.end_of_line();
|
||||||
}
|
if let Some(dest) = self.mode.hist_scroll_start_pos() {
|
||||||
_ => unreachable!()
|
match dest {
|
||||||
};
|
To::Start => { /* Already at 0 */ }
|
||||||
let col = self.editor.saved_col.unwrap_or(self.editor.cursor_col());
|
To::End => {
|
||||||
let mut buf = LineBuf::new().with_initial(entry.command(),0);
|
// History entries cannot be empty
|
||||||
let line_end = buf.end_of_line();
|
// So this subtraction is safe (maybe)
|
||||||
if let Some(dest) = self.mode.hist_scroll_start_pos() {
|
buf.cursor.add(line_end);
|
||||||
match dest {
|
}
|
||||||
To::Start => {
|
}
|
||||||
/* Already at 0 */
|
} else {
|
||||||
}
|
let target = (col).min(line_end);
|
||||||
To::End => {
|
buf.cursor.add(target);
|
||||||
// History entries cannot be empty
|
}
|
||||||
// So this subtraction is safe (maybe)
|
|
||||||
buf.cursor.add(line_end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let target = (col).min(line_end);
|
|
||||||
buf.cursor.add(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.editor = buf
|
self.editor = buf
|
||||||
}
|
}
|
||||||
pub fn should_accept_hint(&self, event: &KeyEvent) -> bool {
|
pub fn should_accept_hint(&self, event: &KeyEvent) -> bool {
|
||||||
flog!(DEBUG,self.editor.cursor_at_max());
|
flog!(DEBUG, self.editor.cursor_at_max());
|
||||||
flog!(DEBUG,self.editor.cursor);
|
flog!(DEBUG, self.editor.cursor);
|
||||||
if self.editor.cursor_at_max() && self.editor.has_hint() {
|
if self.editor.cursor_at_max() && self.editor.has_hint() {
|
||||||
match self.mode.report_mode() {
|
match self.mode.report_mode() {
|
||||||
ModeReport::Replace |
|
ModeReport::Replace | ModeReport::Insert => {
|
||||||
ModeReport::Insert => {
|
matches!(event, KeyEvent(KeyCode::Right, ModKeys::NONE))
|
||||||
matches!(
|
}
|
||||||
event,
|
ModeReport::Visual | ModeReport::Normal => {
|
||||||
KeyEvent(KeyCode::Right, ModKeys::NONE)
|
matches!(event, KeyEvent(KeyCode::Right, ModKeys::NONE))
|
||||||
)
|
|| (self.mode.pending_seq().unwrap(/* always Some on normal mode */).is_empty()
|
||||||
}
|
&& matches!(event, KeyEvent(KeyCode::Char('l'), ModKeys::NONE)))
|
||||||
ModeReport::Visual |
|
}
|
||||||
ModeReport::Normal => {
|
_ => unimplemented!(),
|
||||||
matches!(
|
}
|
||||||
event,
|
} else {
|
||||||
KeyEvent(KeyCode::Right, ModKeys::NONE)
|
false
|
||||||
) ||
|
}
|
||||||
(
|
}
|
||||||
self.mode.pending_seq().unwrap(/* always Some on normal mode */).is_empty() &&
|
|
||||||
matches!(
|
|
||||||
event,
|
|
||||||
KeyEvent(KeyCode::Char('l'), ModKeys::NONE)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => unimplemented!()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn should_grab_history(&mut self, cmd: &ViCmd) -> bool {
|
pub fn should_grab_history(&mut self, cmd: &ViCmd) -> bool {
|
||||||
cmd.verb().is_none() &&
|
cmd.verb().is_none()
|
||||||
(
|
&& (cmd
|
||||||
cmd.motion().is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineUpCharwise))) &&
|
.motion()
|
||||||
self.editor.start_of_line() == 0
|
.is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineUpCharwise)))
|
||||||
) ||
|
&& self.editor.start_of_line() == 0)
|
||||||
(
|
|| (cmd
|
||||||
cmd.motion().is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineDownCharwise))) &&
|
.motion()
|
||||||
self.editor.end_of_line() == self.editor.cursor_max() &&
|
.is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineDownCharwise)))
|
||||||
!self.history.cursor_entry().is_some_and(|ent| ent.is_new())
|
&& self.editor.end_of_line() == self.editor.cursor_max()
|
||||||
)
|
&& !self.history.cursor_entry().is_some_and(|ent| ent.is_new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_line(&mut self) -> ShResult<()> {
|
pub fn print_line(&mut self) -> ShResult<()> {
|
||||||
let new_layout = self.get_layout();
|
let new_layout = self.get_layout();
|
||||||
if let Some(layout) = self.old_layout.as_ref() {
|
if let Some(layout) = self.old_layout.as_ref() {
|
||||||
self.writer.clear_rows(layout)?;
|
self.writer.clear_rows(layout)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.writer.redraw(
|
self
|
||||||
&self.prompt,
|
.writer
|
||||||
&self.editor,
|
.redraw(&self.prompt, &self.editor, &new_layout)?;
|
||||||
&new_layout
|
|
||||||
)?;
|
|
||||||
|
|
||||||
self.writer.flush_write(&self.mode.cursor_style())?;
|
self.writer.flush_write(&self.mode.cursor_style())?;
|
||||||
|
|
||||||
self.old_layout = Some(new_layout);
|
self.old_layout = Some(new_layout);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec_cmd(&mut self, mut cmd: ViCmd) -> ShResult<()> {
|
pub fn exec_cmd(&mut self, mut cmd: ViCmd) -> ShResult<()> {
|
||||||
let mut selecting = false;
|
let mut selecting = false;
|
||||||
let mut is_insert_mode = false;
|
let mut is_insert_mode = false;
|
||||||
if cmd.is_mode_transition() {
|
if cmd.is_mode_transition() {
|
||||||
let count = cmd.verb_count();
|
let count = cmd.verb_count();
|
||||||
let mut mode: Box<dyn ViMode> = match cmd.verb().unwrap().1 {
|
let mut mode: Box<dyn ViMode> = match cmd.verb().unwrap().1 {
|
||||||
Verb::Change |
|
Verb::Change | Verb::InsertModeLineBreak(_) | Verb::InsertMode => {
|
||||||
Verb::InsertModeLineBreak(_) |
|
is_insert_mode = true;
|
||||||
Verb::InsertMode => {
|
Box::new(ViInsert::new().with_count(count as u16))
|
||||||
is_insert_mode = true;
|
}
|
||||||
Box::new(ViInsert::new().with_count(count as u16))
|
|
||||||
}
|
|
||||||
|
|
||||||
Verb::NormalMode => {
|
Verb::NormalMode => Box::new(ViNormal::new()),
|
||||||
Box::new(ViNormal::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
Verb::ReplaceMode => Box::new(ViReplace::new()),
|
Verb::ReplaceMode => Box::new(ViReplace::new()),
|
||||||
|
|
||||||
Verb::VisualModeSelectLast => {
|
Verb::VisualModeSelectLast => {
|
||||||
if self.mode.report_mode() != ModeReport::Visual {
|
if self.mode.report_mode() != ModeReport::Visual {
|
||||||
self.editor.start_selecting(SelectMode::Char(SelectAnchor::End));
|
self
|
||||||
}
|
.editor
|
||||||
let mut mode: Box<dyn ViMode> = Box::new(ViVisual::new());
|
.start_selecting(SelectMode::Char(SelectAnchor::End));
|
||||||
std::mem::swap(&mut mode, &mut self.mode);
|
}
|
||||||
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
let mut mode: Box<dyn ViMode> = Box::new(ViVisual::new());
|
||||||
|
std::mem::swap(&mut mode, &mut self.mode);
|
||||||
|
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
||||||
|
|
||||||
return self.editor.exec_cmd(cmd)
|
return self.editor.exec_cmd(cmd);
|
||||||
}
|
}
|
||||||
Verb::VisualMode => {
|
Verb::VisualMode => {
|
||||||
selecting = true;
|
selecting = true;
|
||||||
Box::new(ViVisual::new())
|
Box::new(ViVisual::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => unreachable!()
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
std::mem::swap(&mut mode, &mut self.mode);
|
std::mem::swap(&mut mode, &mut self.mode);
|
||||||
|
|
||||||
if mode.is_repeatable() {
|
if mode.is_repeatable() {
|
||||||
self.repeat_action = mode.as_replay();
|
self.repeat_action = mode.as_replay();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.editor.exec_cmd(cmd)?;
|
self.editor.exec_cmd(cmd)?;
|
||||||
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
||||||
|
|
||||||
if selecting {
|
if selecting {
|
||||||
self.editor.start_selecting(SelectMode::Char(SelectAnchor::End));
|
self
|
||||||
} else {
|
.editor
|
||||||
self.editor.stop_selecting();
|
.start_selecting(SelectMode::Char(SelectAnchor::End));
|
||||||
}
|
} else {
|
||||||
if is_insert_mode {
|
self.editor.stop_selecting();
|
||||||
self.editor.mark_insert_mode_start_pos();
|
}
|
||||||
} else {
|
if is_insert_mode {
|
||||||
self.editor.clear_insert_mode_start_pos();
|
self.editor.mark_insert_mode_start_pos();
|
||||||
}
|
} else {
|
||||||
return Ok(())
|
self.editor.clear_insert_mode_start_pos();
|
||||||
} else if cmd.is_cmd_repeat() {
|
}
|
||||||
let Some(replay) = self.repeat_action.clone() else {
|
return Ok(());
|
||||||
return Ok(())
|
} else if cmd.is_cmd_repeat() {
|
||||||
};
|
let Some(replay) = self.repeat_action.clone() else {
|
||||||
let ViCmd { verb, .. } = cmd;
|
return Ok(());
|
||||||
let VerbCmd(count,_) = verb.unwrap();
|
};
|
||||||
match replay {
|
let ViCmd { verb, .. } = cmd;
|
||||||
CmdReplay::ModeReplay { cmds, mut repeat } => {
|
let VerbCmd(count, _) = verb.unwrap();
|
||||||
if count > 1 {
|
match replay {
|
||||||
repeat = count as u16;
|
CmdReplay::ModeReplay { cmds, mut repeat } => {
|
||||||
}
|
if count > 1 {
|
||||||
for _ in 0..repeat {
|
repeat = count as u16;
|
||||||
let cmds = cmds.clone();
|
}
|
||||||
for cmd in cmds {
|
for _ in 0..repeat {
|
||||||
self.editor.exec_cmd(cmd)?
|
let cmds = cmds.clone();
|
||||||
}
|
for cmd in cmds {
|
||||||
}
|
self.editor.exec_cmd(cmd)?
|
||||||
}
|
}
|
||||||
CmdReplay::Single(mut cmd) => {
|
}
|
||||||
if count > 1 {
|
}
|
||||||
// Override the counts with the one passed to the '.' command
|
CmdReplay::Single(mut cmd) => {
|
||||||
if cmd.verb.is_some() {
|
if count > 1 {
|
||||||
if let Some(v_mut) = cmd.verb.as_mut() {
|
// Override the counts with the one passed to the '.' command
|
||||||
v_mut.0 = count
|
if cmd.verb.is_some() {
|
||||||
}
|
if let Some(v_mut) = cmd.verb.as_mut() {
|
||||||
if let Some(m_mut) = cmd.motion.as_mut() {
|
v_mut.0 = count
|
||||||
m_mut.0 = 1
|
}
|
||||||
}
|
if let Some(m_mut) = cmd.motion.as_mut() {
|
||||||
} else {
|
m_mut.0 = 1
|
||||||
return Ok(()) // it has to have a verb to be repeatable, something weird happened
|
}
|
||||||
}
|
} else {
|
||||||
}
|
return Ok(()); // it has to have a verb to be repeatable,
|
||||||
self.editor.exec_cmd(cmd)?;
|
// something weird happened
|
||||||
}
|
}
|
||||||
_ => unreachable!("motions should be handled in the other branch")
|
}
|
||||||
}
|
self.editor.exec_cmd(cmd)?;
|
||||||
return Ok(())
|
}
|
||||||
} else if cmd.is_motion_repeat() {
|
_ => unreachable!("motions should be handled in the other branch"),
|
||||||
match cmd.motion.as_ref().unwrap() {
|
}
|
||||||
MotionCmd(count,Motion::RepeatMotion) => {
|
return Ok(());
|
||||||
let Some(motion) = self.repeat_motion.clone() else {
|
} else if cmd.is_motion_repeat() {
|
||||||
return Ok(())
|
match cmd.motion.as_ref().unwrap() {
|
||||||
};
|
MotionCmd(count, Motion::RepeatMotion) => {
|
||||||
let repeat_cmd = ViCmd {
|
let Some(motion) = self.repeat_motion.clone() else {
|
||||||
register: RegisterName::default(),
|
return Ok(());
|
||||||
verb: None,
|
};
|
||||||
motion: Some(motion),
|
let repeat_cmd = ViCmd {
|
||||||
raw_seq: format!("{count};"),
|
register: RegisterName::default(),
|
||||||
flags: CmdFlags::empty()
|
verb: None,
|
||||||
};
|
motion: Some(motion),
|
||||||
return self.editor.exec_cmd(repeat_cmd);
|
raw_seq: format!("{count};"),
|
||||||
}
|
flags: CmdFlags::empty(),
|
||||||
MotionCmd(count,Motion::RepeatMotionRev) => {
|
};
|
||||||
let Some(motion) = self.repeat_motion.clone() else {
|
return self.editor.exec_cmd(repeat_cmd);
|
||||||
return Ok(())
|
}
|
||||||
};
|
MotionCmd(count, Motion::RepeatMotionRev) => {
|
||||||
let mut new_motion = motion.invert_char_motion();
|
let Some(motion) = self.repeat_motion.clone() else {
|
||||||
new_motion.0 = *count;
|
return Ok(());
|
||||||
let repeat_cmd = ViCmd {
|
};
|
||||||
register: RegisterName::default(),
|
let mut new_motion = motion.invert_char_motion();
|
||||||
verb: None,
|
new_motion.0 = *count;
|
||||||
motion: Some(new_motion),
|
let repeat_cmd = ViCmd {
|
||||||
raw_seq: format!("{count},"),
|
register: RegisterName::default(),
|
||||||
flags: CmdFlags::empty()
|
verb: None,
|
||||||
};
|
motion: Some(new_motion),
|
||||||
return self.editor.exec_cmd(repeat_cmd);
|
raw_seq: format!("{count},"),
|
||||||
}
|
flags: CmdFlags::empty(),
|
||||||
_ => unreachable!()
|
};
|
||||||
}
|
return self.editor.exec_cmd(repeat_cmd);
|
||||||
}
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if cmd.is_repeatable() {
|
if cmd.is_repeatable() {
|
||||||
if self.mode.report_mode() == ModeReport::Visual {
|
if self.mode.report_mode() == ModeReport::Visual {
|
||||||
// The motion is assigned in the line buffer execution, so we also have to assign it here
|
// The motion is assigned in the line buffer execution, so we also have to
|
||||||
// in order to be able to repeat it
|
// assign it here in order to be able to repeat it
|
||||||
let range = self.editor.select_range().unwrap();
|
let range = self.editor.select_range().unwrap();
|
||||||
cmd.motion = Some(MotionCmd(1,Motion::Range(range.0, range.1)))
|
cmd.motion = Some(MotionCmd(1, Motion::Range(range.0, range.1)))
|
||||||
}
|
}
|
||||||
self.repeat_action = Some(CmdReplay::Single(cmd.clone()));
|
self.repeat_action = Some(CmdReplay::Single(cmd.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.is_char_search() {
|
if cmd.is_char_search() {
|
||||||
self.repeat_motion = cmd.motion.clone()
|
self.repeat_motion = cmd.motion.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.editor.exec_cmd(cmd.clone())?;
|
self.editor.exec_cmd(cmd.clone())?;
|
||||||
|
|
||||||
if self.mode.report_mode() == ModeReport::Visual && cmd.verb().is_some_and(|v| v.1.is_edit()) {
|
if self.mode.report_mode() == ModeReport::Visual && cmd.verb().is_some_and(|v| v.1.is_edit()) {
|
||||||
self.editor.stop_selecting();
|
self.editor.stop_selecting();
|
||||||
let mut mode: Box<dyn ViMode> = Box::new(ViNormal::new());
|
let mut mode: Box<dyn ViMode> = Box::new(ViNormal::new());
|
||||||
std::mem::swap(&mut mode, &mut self.mode);
|
std::mem::swap(&mut mode, &mut self.mode);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,166 +3,170 @@ use std::sync::Mutex;
|
|||||||
pub static REGISTERS: Mutex<Registers> = Mutex::new(Registers::new());
|
pub static REGISTERS: Mutex<Registers> = Mutex::new(Registers::new());
|
||||||
|
|
||||||
pub fn read_register(ch: Option<char>) -> Option<String> {
|
pub fn read_register(ch: Option<char>) -> Option<String> {
|
||||||
let lock = REGISTERS.lock().unwrap();
|
let lock = REGISTERS.lock().unwrap();
|
||||||
lock.get_reg(ch).map(|r| r.buf().clone())
|
lock.get_reg(ch).map(|r| r.buf().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_register(ch: Option<char>, buf: String) {
|
pub fn write_register(ch: Option<char>, buf: String) {
|
||||||
let mut lock = REGISTERS.lock().unwrap();
|
let mut lock = REGISTERS.lock().unwrap();
|
||||||
if let Some(r) = lock.get_reg_mut(ch) { r.write(buf) }
|
if let Some(r) = lock.get_reg_mut(ch) {
|
||||||
|
r.write(buf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn append_register(ch: Option<char>, buf: String) {
|
pub fn append_register(ch: Option<char>, buf: String) {
|
||||||
let mut lock = REGISTERS.lock().unwrap();
|
let mut lock = REGISTERS.lock().unwrap();
|
||||||
if let Some(r) = lock.get_reg_mut(ch) { r.append(buf) }
|
if let Some(r) = lock.get_reg_mut(ch) {
|
||||||
|
r.append(buf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default,Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Registers {
|
pub struct Registers {
|
||||||
default: Register,
|
default: Register,
|
||||||
a: Register,
|
a: Register,
|
||||||
b: Register,
|
b: Register,
|
||||||
c: Register,
|
c: Register,
|
||||||
d: Register,
|
d: Register,
|
||||||
e: Register,
|
e: Register,
|
||||||
f: Register,
|
f: Register,
|
||||||
g: Register,
|
g: Register,
|
||||||
h: Register,
|
h: Register,
|
||||||
i: Register,
|
i: Register,
|
||||||
j: Register,
|
j: Register,
|
||||||
k: Register,
|
k: Register,
|
||||||
l: Register,
|
l: Register,
|
||||||
m: Register,
|
m: Register,
|
||||||
n: Register,
|
n: Register,
|
||||||
o: Register,
|
o: Register,
|
||||||
p: Register,
|
p: Register,
|
||||||
q: Register,
|
q: Register,
|
||||||
r: Register,
|
r: Register,
|
||||||
s: Register,
|
s: Register,
|
||||||
t: Register,
|
t: Register,
|
||||||
u: Register,
|
u: Register,
|
||||||
v: Register,
|
v: Register,
|
||||||
w: Register,
|
w: Register,
|
||||||
x: Register,
|
x: Register,
|
||||||
y: Register,
|
y: Register,
|
||||||
z: Register,
|
z: Register,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Registers {
|
impl Registers {
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
default: Register(String::new()),
|
default: Register(String::new()),
|
||||||
a: Register(String::new()),
|
a: Register(String::new()),
|
||||||
b: Register(String::new()),
|
b: Register(String::new()),
|
||||||
c: Register(String::new()),
|
c: Register(String::new()),
|
||||||
d: Register(String::new()),
|
d: Register(String::new()),
|
||||||
e: Register(String::new()),
|
e: Register(String::new()),
|
||||||
f: Register(String::new()),
|
f: Register(String::new()),
|
||||||
g: Register(String::new()),
|
g: Register(String::new()),
|
||||||
h: Register(String::new()),
|
h: Register(String::new()),
|
||||||
i: Register(String::new()),
|
i: Register(String::new()),
|
||||||
j: Register(String::new()),
|
j: Register(String::new()),
|
||||||
k: Register(String::new()),
|
k: Register(String::new()),
|
||||||
l: Register(String::new()),
|
l: Register(String::new()),
|
||||||
m: Register(String::new()),
|
m: Register(String::new()),
|
||||||
n: Register(String::new()),
|
n: Register(String::new()),
|
||||||
o: Register(String::new()),
|
o: Register(String::new()),
|
||||||
p: Register(String::new()),
|
p: Register(String::new()),
|
||||||
q: Register(String::new()),
|
q: Register(String::new()),
|
||||||
r: Register(String::new()),
|
r: Register(String::new()),
|
||||||
s: Register(String::new()),
|
s: Register(String::new()),
|
||||||
t: Register(String::new()),
|
t: Register(String::new()),
|
||||||
u: Register(String::new()),
|
u: Register(String::new()),
|
||||||
v: Register(String::new()),
|
v: Register(String::new()),
|
||||||
w: Register(String::new()),
|
w: Register(String::new()),
|
||||||
x: Register(String::new()),
|
x: Register(String::new()),
|
||||||
y: Register(String::new()),
|
y: Register(String::new()),
|
||||||
z: Register(String::new()),
|
z: Register(String::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_reg(&self, ch: Option<char>) -> Option<&Register> {
|
pub fn get_reg(&self, ch: Option<char>) -> Option<&Register> {
|
||||||
let Some(ch) = ch else {
|
let Some(ch) = ch else {
|
||||||
return Some(&self.default)
|
return Some(&self.default);
|
||||||
};
|
};
|
||||||
match ch {
|
match ch {
|
||||||
'a' => Some(&self.a),
|
'a' => Some(&self.a),
|
||||||
'b' => Some(&self.b),
|
'b' => Some(&self.b),
|
||||||
'c' => Some(&self.c),
|
'c' => Some(&self.c),
|
||||||
'd' => Some(&self.d),
|
'd' => Some(&self.d),
|
||||||
'e' => Some(&self.e),
|
'e' => Some(&self.e),
|
||||||
'f' => Some(&self.f),
|
'f' => Some(&self.f),
|
||||||
'g' => Some(&self.g),
|
'g' => Some(&self.g),
|
||||||
'h' => Some(&self.h),
|
'h' => Some(&self.h),
|
||||||
'i' => Some(&self.i),
|
'i' => Some(&self.i),
|
||||||
'j' => Some(&self.j),
|
'j' => Some(&self.j),
|
||||||
'k' => Some(&self.k),
|
'k' => Some(&self.k),
|
||||||
'l' => Some(&self.l),
|
'l' => Some(&self.l),
|
||||||
'm' => Some(&self.m),
|
'm' => Some(&self.m),
|
||||||
'n' => Some(&self.n),
|
'n' => Some(&self.n),
|
||||||
'o' => Some(&self.o),
|
'o' => Some(&self.o),
|
||||||
'p' => Some(&self.p),
|
'p' => Some(&self.p),
|
||||||
'q' => Some(&self.q),
|
'q' => Some(&self.q),
|
||||||
'r' => Some(&self.r),
|
'r' => Some(&self.r),
|
||||||
's' => Some(&self.s),
|
's' => Some(&self.s),
|
||||||
't' => Some(&self.t),
|
't' => Some(&self.t),
|
||||||
'u' => Some(&self.u),
|
'u' => Some(&self.u),
|
||||||
'v' => Some(&self.v),
|
'v' => Some(&self.v),
|
||||||
'w' => Some(&self.w),
|
'w' => Some(&self.w),
|
||||||
'x' => Some(&self.x),
|
'x' => Some(&self.x),
|
||||||
'y' => Some(&self.y),
|
'y' => Some(&self.y),
|
||||||
'z' => Some(&self.z),
|
'z' => Some(&self.z),
|
||||||
_ => None
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_reg_mut(&mut self, ch: Option<char>) -> Option<&mut Register> {
|
pub fn get_reg_mut(&mut self, ch: Option<char>) -> Option<&mut Register> {
|
||||||
let Some(ch) = ch else {
|
let Some(ch) = ch else {
|
||||||
return Some(&mut self.default)
|
return Some(&mut self.default);
|
||||||
};
|
};
|
||||||
match ch {
|
match ch {
|
||||||
'a' => Some(&mut self.a),
|
'a' => Some(&mut self.a),
|
||||||
'b' => Some(&mut self.b),
|
'b' => Some(&mut self.b),
|
||||||
'c' => Some(&mut self.c),
|
'c' => Some(&mut self.c),
|
||||||
'd' => Some(&mut self.d),
|
'd' => Some(&mut self.d),
|
||||||
'e' => Some(&mut self.e),
|
'e' => Some(&mut self.e),
|
||||||
'f' => Some(&mut self.f),
|
'f' => Some(&mut self.f),
|
||||||
'g' => Some(&mut self.g),
|
'g' => Some(&mut self.g),
|
||||||
'h' => Some(&mut self.h),
|
'h' => Some(&mut self.h),
|
||||||
'i' => Some(&mut self.i),
|
'i' => Some(&mut self.i),
|
||||||
'j' => Some(&mut self.j),
|
'j' => Some(&mut self.j),
|
||||||
'k' => Some(&mut self.k),
|
'k' => Some(&mut self.k),
|
||||||
'l' => Some(&mut self.l),
|
'l' => Some(&mut self.l),
|
||||||
'm' => Some(&mut self.m),
|
'm' => Some(&mut self.m),
|
||||||
'n' => Some(&mut self.n),
|
'n' => Some(&mut self.n),
|
||||||
'o' => Some(&mut self.o),
|
'o' => Some(&mut self.o),
|
||||||
'p' => Some(&mut self.p),
|
'p' => Some(&mut self.p),
|
||||||
'q' => Some(&mut self.q),
|
'q' => Some(&mut self.q),
|
||||||
'r' => Some(&mut self.r),
|
'r' => Some(&mut self.r),
|
||||||
's' => Some(&mut self.s),
|
's' => Some(&mut self.s),
|
||||||
't' => Some(&mut self.t),
|
't' => Some(&mut self.t),
|
||||||
'u' => Some(&mut self.u),
|
'u' => Some(&mut self.u),
|
||||||
'v' => Some(&mut self.v),
|
'v' => Some(&mut self.v),
|
||||||
'w' => Some(&mut self.w),
|
'w' => Some(&mut self.w),
|
||||||
'x' => Some(&mut self.x),
|
'x' => Some(&mut self.x),
|
||||||
'y' => Some(&mut self.y),
|
'y' => Some(&mut self.y),
|
||||||
'z' => Some(&mut self.z),
|
'z' => Some(&mut self.z),
|
||||||
_ => None
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Default,Debug)]
|
#[derive(Clone, Default, Debug)]
|
||||||
pub struct Register(String);
|
pub struct Register(String);
|
||||||
impl Register {
|
impl Register {
|
||||||
pub fn buf(&self) -> &String {
|
pub fn buf(&self) -> &String {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
pub fn write(&mut self, buf: String) {
|
pub fn write(&mut self, buf: String) {
|
||||||
self.0 = buf
|
self.0 = buf
|
||||||
}
|
}
|
||||||
pub fn append(&mut self, buf: String) {
|
pub fn append(&mut self, buf: String) {
|
||||||
self.0.push_str(&buf)
|
self.0.push_str(&buf)
|
||||||
}
|
}
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.0.clear()
|
self.0.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,438 +2,458 @@ use bitflags::bitflags;
|
|||||||
|
|
||||||
use super::register::{append_register, read_register, write_register};
|
use super::register::{append_register, read_register, write_register};
|
||||||
|
|
||||||
//TODO: write tests that take edit results and cursor positions from actual neovim edits and test them against the behavior of this editor
|
//TODO: write tests that take edit results and cursor positions from actual
|
||||||
|
// neovim edits and test them against the behavior of this editor
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct RegisterName {
|
pub struct RegisterName {
|
||||||
name: Option<char>,
|
name: Option<char>,
|
||||||
count: usize,
|
count: usize,
|
||||||
append: bool
|
append: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegisterName {
|
impl RegisterName {
|
||||||
pub fn new(name: Option<char>, count: Option<usize>) -> Self {
|
pub fn new(name: Option<char>, count: Option<usize>) -> Self {
|
||||||
let Some(ch) = name else {
|
let Some(ch) = name else {
|
||||||
return Self::default()
|
return Self::default();
|
||||||
};
|
};
|
||||||
|
|
||||||
let append = ch.is_uppercase();
|
let append = ch.is_uppercase();
|
||||||
let name = ch.to_ascii_lowercase();
|
let name = ch.to_ascii_lowercase();
|
||||||
Self {
|
Self {
|
||||||
name: Some(name),
|
name: Some(name),
|
||||||
count: count.unwrap_or(1),
|
count: count.unwrap_or(1),
|
||||||
append
|
append,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn name(&self) -> Option<char> {
|
pub fn name(&self) -> Option<char> {
|
||||||
self.name
|
self.name
|
||||||
}
|
}
|
||||||
pub fn is_append(&self) -> bool {
|
pub fn is_append(&self) -> bool {
|
||||||
self.append
|
self.append
|
||||||
}
|
}
|
||||||
pub fn count(&self) -> usize {
|
pub fn count(&self) -> usize {
|
||||||
self.count
|
self.count
|
||||||
}
|
}
|
||||||
pub fn write_to_register(&self, buf: String) {
|
pub fn write_to_register(&self, buf: String) {
|
||||||
if self.append {
|
if self.append {
|
||||||
append_register(self.name, buf);
|
append_register(self.name, buf);
|
||||||
} else {
|
} else {
|
||||||
write_register(self.name, buf);
|
write_register(self.name, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn read_from_register(&self) -> Option<String> {
|
pub fn read_from_register(&self) -> Option<String> {
|
||||||
read_register(self.name)
|
read_register(self.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RegisterName {
|
impl Default for RegisterName {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: None,
|
name: None,
|
||||||
count: 1,
|
count: 1,
|
||||||
append: false
|
append: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct CmdFlags: u32 {
|
pub struct CmdFlags: u32 {
|
||||||
const VISUAL = 1<<0;
|
const VISUAL = 1<<0;
|
||||||
const VISUAL_LINE = 1<<1;
|
const VISUAL_LINE = 1<<1;
|
||||||
const VISUAL_BLOCK = 1<<2;
|
const VISUAL_BLOCK = 1<<2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Default,Debug)]
|
#[derive(Clone, Default, Debug)]
|
||||||
pub struct ViCmd {
|
pub struct ViCmd {
|
||||||
pub register: RegisterName,
|
pub register: RegisterName,
|
||||||
pub verb: Option<VerbCmd>,
|
pub verb: Option<VerbCmd>,
|
||||||
pub motion: Option<MotionCmd>,
|
pub motion: Option<MotionCmd>,
|
||||||
pub raw_seq: String,
|
pub raw_seq: String,
|
||||||
pub flags: CmdFlags,
|
pub flags: CmdFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViCmd {
|
impl ViCmd {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
pub fn set_motion(&mut self, motion: MotionCmd) {
|
pub fn set_motion(&mut self, motion: MotionCmd) {
|
||||||
self.motion = Some(motion)
|
self.motion = Some(motion)
|
||||||
}
|
}
|
||||||
pub fn set_verb(&mut self, verb: VerbCmd) {
|
pub fn set_verb(&mut self, verb: VerbCmd) {
|
||||||
self.verb = Some(verb)
|
self.verb = Some(verb)
|
||||||
}
|
}
|
||||||
pub fn verb(&self) -> Option<&VerbCmd> {
|
pub fn verb(&self) -> Option<&VerbCmd> {
|
||||||
self.verb.as_ref()
|
self.verb.as_ref()
|
||||||
}
|
}
|
||||||
pub fn motion(&self) -> Option<&MotionCmd> {
|
pub fn motion(&self) -> Option<&MotionCmd> {
|
||||||
self.motion.as_ref()
|
self.motion.as_ref()
|
||||||
}
|
}
|
||||||
pub fn verb_count(&self) -> usize {
|
pub fn verb_count(&self) -> usize {
|
||||||
self.verb.as_ref().map(|v| v.0).unwrap_or(1)
|
self.verb.as_ref().map(|v| v.0).unwrap_or(1)
|
||||||
}
|
}
|
||||||
pub fn motion_count(&self) -> usize {
|
pub fn motion_count(&self) -> usize {
|
||||||
self.motion.as_ref().map(|m| m.0).unwrap_or(1)
|
self.motion.as_ref().map(|m| m.0).unwrap_or(1)
|
||||||
}
|
}
|
||||||
pub fn normalize_counts(&mut self) {
|
pub fn normalize_counts(&mut self) {
|
||||||
let Some(verb) = self.verb.as_mut() else { return };
|
let Some(verb) = self.verb.as_mut() else {
|
||||||
let Some(motion) = self.motion.as_mut() else { return };
|
return;
|
||||||
let VerbCmd(v_count, _) = verb;
|
};
|
||||||
let MotionCmd(m_count, _) = motion;
|
let Some(motion) = self.motion.as_mut() else {
|
||||||
let product = *v_count * *m_count;
|
return;
|
||||||
verb.0 = 1;
|
};
|
||||||
motion.0 = product;
|
let VerbCmd(v_count, _) = verb;
|
||||||
}
|
let MotionCmd(m_count, _) = motion;
|
||||||
pub fn is_repeatable(&self) -> bool {
|
let product = *v_count * *m_count;
|
||||||
self.verb.as_ref().is_some_and(|v| v.1.is_repeatable())
|
verb.0 = 1;
|
||||||
}
|
motion.0 = product;
|
||||||
pub fn is_cmd_repeat(&self) -> bool {
|
}
|
||||||
self.verb.as_ref().is_some_and(|v| matches!(v.1,Verb::RepeatLast))
|
pub fn is_repeatable(&self) -> bool {
|
||||||
}
|
self.verb.as_ref().is_some_and(|v| v.1.is_repeatable())
|
||||||
pub fn is_motion_repeat(&self) -> bool {
|
}
|
||||||
self.motion.as_ref().is_some_and(|m| matches!(m.1,Motion::RepeatMotion | Motion::RepeatMotionRev))
|
pub fn is_cmd_repeat(&self) -> bool {
|
||||||
}
|
self
|
||||||
pub fn is_char_search(&self) -> bool {
|
.verb
|
||||||
self.motion.as_ref().is_some_and(|m| matches!(m.1, Motion::CharSearch(..)))
|
.as_ref()
|
||||||
}
|
.is_some_and(|v| matches!(v.1, Verb::RepeatLast))
|
||||||
pub fn should_submit(&self) -> bool {
|
}
|
||||||
self.verb.as_ref().is_some_and(|v| matches!(v.1, Verb::AcceptLineOrNewline))
|
pub fn is_motion_repeat(&self) -> bool {
|
||||||
}
|
self
|
||||||
pub fn is_undo_op(&self) -> bool {
|
.motion
|
||||||
self.verb.as_ref().is_some_and(|v| matches!(v.1, Verb::Undo | Verb::Redo))
|
.as_ref()
|
||||||
}
|
.is_some_and(|m| matches!(m.1, Motion::RepeatMotion | Motion::RepeatMotionRev))
|
||||||
pub fn is_inplace_edit(&self) -> bool {
|
}
|
||||||
self.verb.as_ref().is_some_and(|v| matches!(v.1, Verb::ReplaceCharInplace(_,_) | Verb::ToggleCaseInplace(_))) &&
|
pub fn is_char_search(&self) -> bool {
|
||||||
self.motion.is_none()
|
self
|
||||||
}
|
.motion
|
||||||
pub fn is_line_motion(&self) -> bool {
|
.as_ref()
|
||||||
self.motion.as_ref().is_some_and(|m| {
|
.is_some_and(|m| matches!(m.1, Motion::CharSearch(..)))
|
||||||
matches!(m.1,
|
}
|
||||||
Motion::LineUp |
|
pub fn should_submit(&self) -> bool {
|
||||||
Motion::LineDown |
|
self
|
||||||
Motion::LineUpCharwise |
|
.verb
|
||||||
Motion::LineDownCharwise
|
.as_ref()
|
||||||
)
|
.is_some_and(|v| matches!(v.1, Verb::AcceptLineOrNewline))
|
||||||
})
|
}
|
||||||
}
|
pub fn is_undo_op(&self) -> bool {
|
||||||
/// If a ViCmd has a linewise motion, but no verb, we change it to charwise
|
self
|
||||||
pub fn alter_line_motion_if_no_verb(&mut self) {
|
.verb
|
||||||
if self.is_line_motion() && self.verb.is_none() {
|
.as_ref()
|
||||||
if let Some(motion) = self.motion.as_mut() {
|
.is_some_and(|v| matches!(v.1, Verb::Undo | Verb::Redo))
|
||||||
match motion.1 {
|
}
|
||||||
Motion::LineUp => motion.1 = Motion::LineUpCharwise,
|
pub fn is_inplace_edit(&self) -> bool {
|
||||||
Motion::LineDown => motion.1 = Motion::LineDownCharwise,
|
self.verb.as_ref().is_some_and(|v| {
|
||||||
_ => unreachable!()
|
matches!(
|
||||||
}
|
v.1,
|
||||||
}
|
Verb::ReplaceCharInplace(_, _) | Verb::ToggleCaseInplace(_)
|
||||||
}
|
)
|
||||||
}
|
}) && self.motion.is_none()
|
||||||
pub fn is_mode_transition(&self) -> bool {
|
}
|
||||||
self.verb.as_ref().is_some_and(|v| {
|
pub fn is_line_motion(&self) -> bool {
|
||||||
matches!(v.1,
|
self.motion.as_ref().is_some_and(|m| {
|
||||||
Verb::Change |
|
matches!(
|
||||||
Verb::InsertMode |
|
m.1,
|
||||||
Verb::InsertModeLineBreak(_) |
|
Motion::LineUp | Motion::LineDown | Motion::LineUpCharwise | Motion::LineDownCharwise
|
||||||
Verb::NormalMode |
|
)
|
||||||
Verb::VisualModeSelectLast |
|
})
|
||||||
Verb::VisualMode |
|
}
|
||||||
Verb::ReplaceMode
|
/// If a ViCmd has a linewise motion, but no verb, we change it to charwise
|
||||||
)
|
pub fn alter_line_motion_if_no_verb(&mut self) {
|
||||||
})
|
if self.is_line_motion() && self.verb.is_none() {
|
||||||
}
|
if let Some(motion) = self.motion.as_mut() {
|
||||||
|
match motion.1 {
|
||||||
|
Motion::LineUp => motion.1 = Motion::LineUpCharwise,
|
||||||
|
Motion::LineDown => motion.1 = Motion::LineDownCharwise,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn is_mode_transition(&self) -> bool {
|
||||||
|
self.verb.as_ref().is_some_and(|v| {
|
||||||
|
matches!(
|
||||||
|
v.1,
|
||||||
|
Verb::Change
|
||||||
|
| Verb::InsertMode
|
||||||
|
| Verb::InsertModeLineBreak(_)
|
||||||
|
| Verb::NormalMode
|
||||||
|
| Verb::VisualModeSelectLast
|
||||||
|
| Verb::VisualMode
|
||||||
|
| Verb::ReplaceMode
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct VerbCmd(pub usize,pub Verb);
|
pub struct VerbCmd(pub usize, pub Verb);
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MotionCmd(pub usize,pub Motion);
|
pub struct MotionCmd(pub usize, pub Motion);
|
||||||
|
|
||||||
impl MotionCmd {
|
impl MotionCmd {
|
||||||
pub fn invert_char_motion(self) -> Self {
|
pub fn invert_char_motion(self) -> Self {
|
||||||
let MotionCmd(count,Motion::CharSearch(dir, dest, ch)) = self else {
|
let MotionCmd(count, Motion::CharSearch(dir, dest, ch)) = self else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let new_dir = match dir {
|
let new_dir = match dir {
|
||||||
Direction::Forward => Direction::Backward,
|
Direction::Forward => Direction::Backward,
|
||||||
Direction::Backward => Direction::Forward,
|
Direction::Backward => Direction::Forward,
|
||||||
};
|
};
|
||||||
MotionCmd(count,Motion::CharSearch(new_dir, dest, ch))
|
MotionCmd(count, Motion::CharSearch(new_dir, dest, ch))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Verb {
|
pub enum Verb {
|
||||||
Delete,
|
Delete,
|
||||||
Change,
|
Change,
|
||||||
Yank,
|
Yank,
|
||||||
Rot13, // lol
|
Rot13, // lol
|
||||||
ReplaceChar(char), // char to replace with, number of chars to replace
|
ReplaceChar(char), // char to replace with, number of chars to replace
|
||||||
ReplaceCharInplace(char,u16), // char to replace with, number of chars to replace
|
ReplaceCharInplace(char, u16), // char to replace with, number of chars to replace
|
||||||
ToggleCaseInplace(u16), // Number of chars to toggle
|
ToggleCaseInplace(u16), // Number of chars to toggle
|
||||||
ToggleCaseRange,
|
ToggleCaseRange,
|
||||||
ToLower,
|
ToLower,
|
||||||
ToUpper,
|
ToUpper,
|
||||||
Complete,
|
Complete,
|
||||||
CompleteBackward,
|
CompleteBackward,
|
||||||
Undo,
|
Undo,
|
||||||
Redo,
|
Redo,
|
||||||
RepeatLast,
|
RepeatLast,
|
||||||
Put(Anchor),
|
Put(Anchor),
|
||||||
ReplaceMode,
|
ReplaceMode,
|
||||||
InsertMode,
|
InsertMode,
|
||||||
InsertModeLineBreak(Anchor),
|
InsertModeLineBreak(Anchor),
|
||||||
NormalMode,
|
NormalMode,
|
||||||
VisualMode,
|
VisualMode,
|
||||||
VisualModeLine,
|
VisualModeLine,
|
||||||
VisualModeBlock, // dont even know if im going to implement this
|
VisualModeBlock, // dont even know if im going to implement this
|
||||||
VisualModeSelectLast,
|
VisualModeSelectLast,
|
||||||
SwapVisualAnchor,
|
SwapVisualAnchor,
|
||||||
JoinLines,
|
JoinLines,
|
||||||
InsertChar(char),
|
InsertChar(char),
|
||||||
Insert(String),
|
Insert(String),
|
||||||
Indent,
|
Indent,
|
||||||
Dedent,
|
Dedent,
|
||||||
Equalize,
|
Equalize,
|
||||||
AcceptLineOrNewline,
|
AcceptLineOrNewline,
|
||||||
EndOfFile
|
EndOfFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Verb {
|
impl Verb {
|
||||||
pub fn is_repeatable(&self) -> bool {
|
pub fn is_repeatable(&self) -> bool {
|
||||||
matches!(self,
|
matches!(
|
||||||
Self::Delete |
|
self,
|
||||||
Self::Change |
|
Self::Delete
|
||||||
Self::ReplaceChar(_) |
|
| Self::Change
|
||||||
Self::ReplaceCharInplace(_,_) |
|
| Self::ReplaceChar(_)
|
||||||
Self::ToLower |
|
| Self::ReplaceCharInplace(_, _)
|
||||||
Self::ToUpper |
|
| Self::ToLower
|
||||||
Self::ToggleCaseRange |
|
| Self::ToUpper
|
||||||
Self::ToggleCaseInplace(_) |
|
| Self::ToggleCaseRange
|
||||||
Self::Put(_) |
|
| Self::ToggleCaseInplace(_)
|
||||||
Self::ReplaceMode |
|
| Self::Put(_)
|
||||||
Self::InsertModeLineBreak(_) |
|
| Self::ReplaceMode
|
||||||
Self::JoinLines |
|
| Self::InsertModeLineBreak(_)
|
||||||
Self::InsertChar(_) |
|
| Self::JoinLines
|
||||||
Self::Insert(_) |
|
| Self::InsertChar(_)
|
||||||
Self::Indent |
|
| Self::Insert(_)
|
||||||
Self::Dedent |
|
| Self::Indent
|
||||||
Self::Equalize
|
| Self::Dedent
|
||||||
)
|
| Self::Equalize
|
||||||
}
|
)
|
||||||
pub fn is_edit(&self) -> bool {
|
}
|
||||||
matches!(self,
|
pub fn is_edit(&self) -> bool {
|
||||||
Self::Delete |
|
matches!(
|
||||||
Self::Change |
|
self,
|
||||||
Self::ReplaceChar(_) |
|
Self::Delete
|
||||||
Self::ReplaceCharInplace(_,_) |
|
| Self::Change
|
||||||
Self::ToggleCaseRange |
|
| Self::ReplaceChar(_)
|
||||||
Self::ToggleCaseInplace(_) |
|
| Self::ReplaceCharInplace(_, _)
|
||||||
Self::ToLower |
|
| Self::ToggleCaseRange
|
||||||
Self::ToUpper |
|
| Self::ToggleCaseInplace(_)
|
||||||
Self::RepeatLast |
|
| Self::ToLower
|
||||||
Self::Put(_) |
|
| Self::ToUpper
|
||||||
Self::ReplaceMode |
|
| Self::RepeatLast
|
||||||
Self::InsertModeLineBreak(_) |
|
| Self::Put(_)
|
||||||
Self::JoinLines |
|
| Self::ReplaceMode
|
||||||
Self::InsertChar(_) |
|
| Self::InsertModeLineBreak(_)
|
||||||
Self::Insert(_) |
|
| Self::JoinLines
|
||||||
Self::Rot13 |
|
| Self::InsertChar(_)
|
||||||
Self::EndOfFile
|
| Self::Insert(_)
|
||||||
)
|
| Self::Rot13
|
||||||
}
|
| Self::EndOfFile
|
||||||
pub fn is_char_insert(&self) -> bool {
|
)
|
||||||
matches!(self,
|
}
|
||||||
Self::Change |
|
pub fn is_char_insert(&self) -> bool {
|
||||||
Self::InsertChar(_) |
|
matches!(
|
||||||
Self::ReplaceChar(_) |
|
self,
|
||||||
Self::ReplaceCharInplace(_,_)
|
Self::Change | Self::InsertChar(_) | Self::ReplaceChar(_) | Self::ReplaceCharInplace(_, _)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum Motion {
|
pub enum Motion {
|
||||||
WholeLine,
|
WholeLine,
|
||||||
TextObj(TextObj),
|
TextObj(TextObj),
|
||||||
EndOfLastWord,
|
EndOfLastWord,
|
||||||
BeginningOfFirstWord,
|
BeginningOfFirstWord,
|
||||||
BeginningOfLine,
|
BeginningOfLine,
|
||||||
EndOfLine,
|
EndOfLine,
|
||||||
WordMotion(To,Word,Direction),
|
WordMotion(To, Word, Direction),
|
||||||
CharSearch(Direction,Dest,char),
|
CharSearch(Direction, Dest, char),
|
||||||
BackwardChar,
|
BackwardChar,
|
||||||
ForwardChar,
|
ForwardChar,
|
||||||
BackwardCharForced, // These two variants can cross line boundaries
|
BackwardCharForced, // These two variants can cross line boundaries
|
||||||
ForwardCharForced,
|
ForwardCharForced,
|
||||||
LineUp,
|
LineUp,
|
||||||
LineUpCharwise,
|
LineUpCharwise,
|
||||||
ScreenLineUp,
|
ScreenLineUp,
|
||||||
ScreenLineUpCharwise,
|
ScreenLineUpCharwise,
|
||||||
LineDown,
|
LineDown,
|
||||||
LineDownCharwise,
|
LineDownCharwise,
|
||||||
ScreenLineDown,
|
ScreenLineDown,
|
||||||
ScreenLineDownCharwise,
|
ScreenLineDownCharwise,
|
||||||
BeginningOfScreenLine,
|
BeginningOfScreenLine,
|
||||||
FirstGraphicalOnScreenLine,
|
FirstGraphicalOnScreenLine,
|
||||||
HalfOfScreen,
|
HalfOfScreen,
|
||||||
HalfOfScreenLineText,
|
HalfOfScreenLineText,
|
||||||
WholeBuffer,
|
WholeBuffer,
|
||||||
BeginningOfBuffer,
|
BeginningOfBuffer,
|
||||||
EndOfBuffer,
|
EndOfBuffer,
|
||||||
ToColumn,
|
ToColumn,
|
||||||
ToDelimMatch,
|
ToDelimMatch,
|
||||||
ToBrace(Direction),
|
ToBrace(Direction),
|
||||||
ToBracket(Direction),
|
ToBracket(Direction),
|
||||||
ToParen(Direction),
|
ToParen(Direction),
|
||||||
Range(usize,usize),
|
Range(usize, usize),
|
||||||
RepeatMotion,
|
RepeatMotion,
|
||||||
RepeatMotionRev,
|
RepeatMotionRev,
|
||||||
Null
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy,PartialEq,Eq,Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
pub enum MotionBehavior {
|
pub enum MotionBehavior {
|
||||||
Exclusive,
|
Exclusive,
|
||||||
Inclusive,
|
Inclusive,
|
||||||
Linewise
|
Linewise,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Motion {
|
impl Motion {
|
||||||
pub fn behavior(&self) -> MotionBehavior {
|
pub fn behavior(&self) -> MotionBehavior {
|
||||||
if self.is_linewise() {
|
if self.is_linewise() {
|
||||||
MotionBehavior::Linewise
|
MotionBehavior::Linewise
|
||||||
} else if self.is_exclusive() {
|
} else if self.is_exclusive() {
|
||||||
MotionBehavior::Exclusive
|
MotionBehavior::Exclusive
|
||||||
} else {
|
} else {
|
||||||
MotionBehavior::Inclusive
|
MotionBehavior::Inclusive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_exclusive(&self) -> bool {
|
pub fn is_exclusive(&self) -> bool {
|
||||||
matches!(&self,
|
matches!(
|
||||||
Self::BeginningOfLine |
|
&self,
|
||||||
Self::BeginningOfFirstWord |
|
Self::BeginningOfLine
|
||||||
Self::BeginningOfScreenLine |
|
| Self::BeginningOfFirstWord
|
||||||
Self::FirstGraphicalOnScreenLine |
|
| Self::BeginningOfScreenLine
|
||||||
Self::LineDownCharwise |
|
| Self::FirstGraphicalOnScreenLine
|
||||||
Self::LineUpCharwise |
|
| Self::LineDownCharwise
|
||||||
Self::ScreenLineUpCharwise |
|
| Self::LineUpCharwise
|
||||||
Self::ScreenLineDownCharwise |
|
| Self::ScreenLineUpCharwise
|
||||||
Self::ToColumn |
|
| Self::ScreenLineDownCharwise
|
||||||
Self::TextObj(TextObj::Sentence(_)) |
|
| Self::ToColumn
|
||||||
Self::TextObj(TextObj::Paragraph(_)) |
|
| Self::TextObj(TextObj::Sentence(_))
|
||||||
Self::CharSearch(Direction::Backward, _, _) |
|
| Self::TextObj(TextObj::Paragraph(_))
|
||||||
Self::WordMotion(To::Start,_,_) |
|
| Self::CharSearch(Direction::Backward, _, _)
|
||||||
Self::ToBrace(_) |
|
| Self::WordMotion(To::Start, _, _)
|
||||||
Self::ToBracket(_) |
|
| Self::ToBrace(_)
|
||||||
Self::ToParen(_) |
|
| Self::ToBracket(_)
|
||||||
Self::ScreenLineDown |
|
| Self::ToParen(_)
|
||||||
Self::ScreenLineUp |
|
| Self::ScreenLineDown
|
||||||
Self::Range(_, _)
|
| Self::ScreenLineUp
|
||||||
)
|
| Self::Range(_, _)
|
||||||
}
|
)
|
||||||
pub fn is_linewise(&self) -> bool {
|
}
|
||||||
matches!(self,
|
pub fn is_linewise(&self) -> bool {
|
||||||
Self::WholeLine |
|
matches!(
|
||||||
Self::LineUp |
|
self,
|
||||||
Self::LineDown |
|
Self::WholeLine | Self::LineUp | Self::LineDown | Self::ScreenLineDown | Self::ScreenLineUp
|
||||||
Self::ScreenLineDown |
|
)
|
||||||
Self::ScreenLineUp
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum Anchor {
|
pub enum Anchor {
|
||||||
After,
|
After,
|
||||||
Before
|
Before,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum TextObj {
|
pub enum TextObj {
|
||||||
/// `iw`, `aw` — inner word, around word
|
/// `iw`, `aw` — inner word, around word
|
||||||
Word(Word, Bound),
|
Word(Word, Bound),
|
||||||
|
|
||||||
/// `)`, `(` — forward, backward
|
/// `)`, `(` — forward, backward
|
||||||
Sentence(Direction),
|
Sentence(Direction),
|
||||||
|
|
||||||
/// `}`, `{` — forward, backward
|
/// `}`, `{` — forward, backward
|
||||||
Paragraph(Direction),
|
Paragraph(Direction),
|
||||||
|
|
||||||
WholeSentence(Bound),
|
WholeSentence(Bound),
|
||||||
WholeParagraph(Bound),
|
WholeParagraph(Bound),
|
||||||
|
|
||||||
/// `i"`, `a"` — inner/around double quotes
|
/// `i"`, `a"` — inner/around double quotes
|
||||||
DoubleQuote(Bound),
|
DoubleQuote(Bound),
|
||||||
/// `i'`, `a'`
|
/// `i'`, `a'`
|
||||||
SingleQuote(Bound),
|
SingleQuote(Bound),
|
||||||
/// `i\``, `a\``
|
/// `i\``, `a\``
|
||||||
BacktickQuote(Bound),
|
BacktickQuote(Bound),
|
||||||
|
|
||||||
/// `i)`, `a)` — round parens
|
/// `i)`, `a)` — round parens
|
||||||
Paren(Bound),
|
Paren(Bound),
|
||||||
/// `i]`, `a]`
|
/// `i]`, `a]`
|
||||||
Bracket(Bound),
|
Bracket(Bound),
|
||||||
/// `i}`, `a}`
|
/// `i}`, `a}`
|
||||||
Brace(Bound),
|
Brace(Bound),
|
||||||
/// `i<`, `a<`
|
/// `i<`, `a<`
|
||||||
Angle(Bound),
|
Angle(Bound),
|
||||||
|
|
||||||
/// `it`, `at` — HTML/XML tags
|
/// `it`, `at` — HTML/XML tags
|
||||||
Tag(Bound),
|
Tag(Bound),
|
||||||
|
|
||||||
/// Custom user-defined objects maybe?
|
/// Custom user-defined objects maybe?
|
||||||
Custom(char),
|
Custom(char),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum Word {
|
pub enum Word {
|
||||||
Big,
|
Big,
|
||||||
Normal
|
Normal,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub enum Bound {
|
pub enum Bound {
|
||||||
Inside,
|
Inside,
|
||||||
Around
|
Around,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
#[default]
|
#[default]
|
||||||
Forward,
|
Forward,
|
||||||
Backward
|
Backward,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub enum Dest {
|
pub enum Dest {
|
||||||
On,
|
On,
|
||||||
Before,
|
Before,
|
||||||
After
|
After,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub enum To {
|
pub enum To {
|
||||||
Start,
|
Start,
|
||||||
End
|
End,
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
1027
src/shopt.rs
1027
src/shopt.rs
File diff suppressed because it is too large
Load Diff
255
src/signal.rs
255
src/signal.rs
@@ -1,161 +1,168 @@
|
|||||||
use crate::{jobs::{take_term, JobCmdFlags, JobID}, libsh::{error::ShResult, sys::sh_quit}, prelude::*, state::{read_jobs, write_jobs}};
|
use crate::{
|
||||||
|
jobs::{take_term, JobCmdFlags, JobID},
|
||||||
|
libsh::{error::ShResult, sys::sh_quit},
|
||||||
|
prelude::*,
|
||||||
|
state::{read_jobs, write_jobs},
|
||||||
|
};
|
||||||
|
|
||||||
pub fn sig_setup() {
|
pub fn sig_setup() {
|
||||||
unsafe {
|
unsafe {
|
||||||
signal(Signal::SIGCHLD, SigHandler::Handler(handle_sigchld)).unwrap();
|
signal(Signal::SIGCHLD, SigHandler::Handler(handle_sigchld)).unwrap();
|
||||||
signal(Signal::SIGQUIT, SigHandler::Handler(handle_sigquit)).unwrap();
|
signal(Signal::SIGQUIT, SigHandler::Handler(handle_sigquit)).unwrap();
|
||||||
signal(Signal::SIGTSTP, SigHandler::Handler(handle_sigtstp)).unwrap();
|
signal(Signal::SIGTSTP, SigHandler::Handler(handle_sigtstp)).unwrap();
|
||||||
signal(Signal::SIGHUP, SigHandler::Handler(handle_sighup)).unwrap();
|
signal(Signal::SIGHUP, SigHandler::Handler(handle_sighup)).unwrap();
|
||||||
signal(Signal::SIGINT, SigHandler::Handler(handle_sigint)).unwrap();
|
signal(Signal::SIGINT, SigHandler::Handler(handle_sigint)).unwrap();
|
||||||
signal(Signal::SIGTTIN, SigHandler::SigIgn).unwrap();
|
signal(Signal::SIGTTIN, SigHandler::SigIgn).unwrap();
|
||||||
signal(Signal::SIGTTOU, SigHandler::SigIgn).unwrap();
|
signal(Signal::SIGTTOU, SigHandler::SigIgn).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
extern "C" fn handle_sighup(_: libc::c_int) {
|
extern "C" fn handle_sighup(_: libc::c_int) {
|
||||||
write_jobs(|j| {
|
write_jobs(|j| {
|
||||||
for job in j.jobs_mut().iter_mut().flatten() {
|
for job in j.jobs_mut().iter_mut().flatten() {
|
||||||
job.killpg(Signal::SIGTERM).ok();
|
job.killpg(Signal::SIGTERM).ok();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn handle_sigtstp(_: libc::c_int) {
|
extern "C" fn handle_sigtstp(_: libc::c_int) {
|
||||||
write_jobs(|j| {
|
write_jobs(|j| {
|
||||||
if let Some(job) = j.get_fg_mut() {
|
if let Some(job) = j.get_fg_mut() {
|
||||||
job.killpg(Signal::SIGTSTP).ok();
|
job.killpg(Signal::SIGTSTP).ok();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn handle_sigint(_: libc::c_int) {
|
extern "C" fn handle_sigint(_: libc::c_int) {
|
||||||
write_jobs(|j| {
|
write_jobs(|j| {
|
||||||
if let Some(job) = j.get_fg_mut() {
|
if let Some(job) = j.get_fg_mut() {
|
||||||
job.killpg(Signal::SIGINT).ok();
|
job.killpg(Signal::SIGINT).ok();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "C" fn ignore_sigchld(_: libc::c_int) {
|
pub extern "C" fn ignore_sigchld(_: libc::c_int) {
|
||||||
/*
|
/*
|
||||||
Do nothing
|
Do nothing
|
||||||
|
|
||||||
This function exists because using SIGIGN to ignore SIGCHLD
|
This function exists because using SIGIGN to ignore SIGCHLD
|
||||||
will cause the kernel to automatically reap the child process, which is not what we want.
|
will cause the kernel to automatically reap the child process, which is not what we want.
|
||||||
This handler will leave the signaling process as a zombie, allowing us
|
This handler will leave the signaling process as a zombie, allowing us
|
||||||
to handle it somewhere else.
|
to handle it somewhere else.
|
||||||
|
|
||||||
This handler is used when we want to handle SIGCHLD explicitly,
|
This handler is used when we want to handle SIGCHLD explicitly,
|
||||||
like in the case of handling foreground jobs
|
like in the case of handling foreground jobs
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn handle_sigquit(_: libc::c_int) {
|
extern "C" fn handle_sigquit(_: libc::c_int) {
|
||||||
sh_quit(0)
|
sh_quit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "C" fn handle_sigchld(_: libc::c_int) {
|
pub extern "C" fn handle_sigchld(_: libc::c_int) {
|
||||||
let flags = WtFlag::WNOHANG | WtFlag::WSTOPPED;
|
let flags = WtFlag::WNOHANG | WtFlag::WSTOPPED;
|
||||||
while let Ok(status) = waitpid(None, Some(flags)) {
|
while let Ok(status) = waitpid(None, Some(flags)) {
|
||||||
if let Err(e) = match status {
|
if let Err(e) = match status {
|
||||||
WtStat::Exited(pid, _) => child_exited(pid, status),
|
WtStat::Exited(pid, _) => child_exited(pid, status),
|
||||||
WtStat::Signaled(pid, signal, _) => child_signaled(pid, signal),
|
WtStat::Signaled(pid, signal, _) => child_signaled(pid, signal),
|
||||||
WtStat::Stopped(pid, signal) => child_stopped(pid, signal),
|
WtStat::Stopped(pid, signal) => child_stopped(pid, signal),
|
||||||
WtStat::Continued(pid) => child_continued(pid),
|
WtStat::Continued(pid) => child_continued(pid),
|
||||||
WtStat::StillAlive => break,
|
WtStat::StillAlive => break,
|
||||||
_ => unimplemented!()
|
_ => unimplemented!(),
|
||||||
} {
|
} {
|
||||||
eprintln!("{}",e)
|
eprintln!("{}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn child_signaled(pid: Pid, sig: Signal) -> ShResult<()> {
|
pub fn child_signaled(pid: Pid, sig: Signal) -> ShResult<()> {
|
||||||
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
||||||
write_jobs(|j| {
|
write_jobs(|j| {
|
||||||
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
||||||
let child = job.children_mut().iter_mut().find(|chld| pid == chld.pid()).unwrap();
|
let child = job
|
||||||
let stat = WtStat::Signaled(pid, sig, false);
|
.children_mut()
|
||||||
child.set_stat(stat);
|
.iter_mut()
|
||||||
}
|
.find(|chld| pid == chld.pid())
|
||||||
});
|
.unwrap();
|
||||||
if sig == Signal::SIGINT {
|
let stat = WtStat::Signaled(pid, sig, false);
|
||||||
take_term().unwrap()
|
child.set_stat(stat);
|
||||||
}
|
}
|
||||||
Ok(())
|
});
|
||||||
|
if sig == Signal::SIGINT {
|
||||||
|
take_term().unwrap()
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn child_stopped(pid: Pid, sig: Signal) -> ShResult<()> {
|
pub fn child_stopped(pid: Pid, sig: Signal) -> ShResult<()> {
|
||||||
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
||||||
write_jobs(|j| {
|
write_jobs(|j| {
|
||||||
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
||||||
let child = job.children_mut().iter_mut().find(|chld| pid == chld.pid()).unwrap();
|
let child = job
|
||||||
let status = WtStat::Stopped(pid, sig);
|
.children_mut()
|
||||||
child.set_stat(status);
|
.iter_mut()
|
||||||
} else if j.get_fg_mut().is_some_and(|fg| fg.pgid() == pgid) {
|
.find(|chld| pid == chld.pid())
|
||||||
j.fg_to_bg(WtStat::Stopped(pid, sig)).unwrap();
|
.unwrap();
|
||||||
}
|
let status = WtStat::Stopped(pid, sig);
|
||||||
});
|
child.set_stat(status);
|
||||||
take_term()?;
|
} else if j.get_fg_mut().is_some_and(|fg| fg.pgid() == pgid) {
|
||||||
Ok(())
|
j.fg_to_bg(WtStat::Stopped(pid, sig)).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
take_term()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn child_continued(pid: Pid) -> ShResult<()> {
|
pub fn child_continued(pid: Pid) -> ShResult<()> {
|
||||||
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
||||||
write_jobs(|j| {
|
write_jobs(|j| {
|
||||||
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
||||||
job.killpg(Signal::SIGCONT).ok();
|
job.killpg(Signal::SIGCONT).ok();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
|
pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
|
||||||
/*
|
/*
|
||||||
* Here we are going to get metadata on the exited process by querying the job table with the pid.
|
* Here we are going to get metadata on the exited process by querying the
|
||||||
* Then if the discovered job is the fg task, return terminal control to rsh
|
* job table with the pid. Then if the discovered job is the fg task,
|
||||||
* If it is not the fg task, print the display info for the job in the job table
|
* return terminal control to rsh If it is not the fg task, print the
|
||||||
* We can reasonably assume that if it is not a foreground job, then it exists in the job table
|
* display info for the job in the job table We can reasonably assume that
|
||||||
* If this assumption is incorrect, the code has gone wrong somewhere.
|
* if it is not a foreground job, then it exists in the job table
|
||||||
*/
|
* If this assumption is incorrect, the code has gone wrong somewhere.
|
||||||
write_jobs(|j| j.close_job_fds(pid));
|
*/
|
||||||
if let Some((
|
write_jobs(|j| j.close_job_fds(pid));
|
||||||
pgid,
|
if let Some((pgid, is_fg, is_finished)) = write_jobs(|j| {
|
||||||
is_fg,
|
let fg_pgid = j.get_fg().map(|job| job.pgid());
|
||||||
is_finished
|
if let Some(job) = j.query_mut(JobID::Pid(pid)) {
|
||||||
)) = write_jobs(|j| {
|
let pgid = job.pgid();
|
||||||
let fg_pgid = j.get_fg().map(|job| job.pgid());
|
let is_fg = fg_pgid.is_some_and(|fg| fg == pgid);
|
||||||
if let Some(job) = j.query_mut(JobID::Pid(pid)) {
|
job.update_by_id(JobID::Pid(pid), status).unwrap();
|
||||||
let pgid = job.pgid();
|
let is_finished = !job.running();
|
||||||
let is_fg = fg_pgid.is_some_and(|fg| fg == pgid);
|
|
||||||
job.update_by_id(JobID::Pid(pid), status).unwrap();
|
|
||||||
let is_finished = !job.running();
|
|
||||||
|
|
||||||
|
if let Some(child) = job.children_mut().iter_mut().find(|chld| pid == chld.pid()) {
|
||||||
|
child.set_stat(status);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(child) = job.children_mut().iter_mut().find(|chld| pid == chld.pid()) {
|
Some((pgid, is_fg, is_finished))
|
||||||
child.set_stat(status);
|
} else {
|
||||||
}
|
None
|
||||||
|
}
|
||||||
Some((pgid, is_fg, is_finished))
|
}) {
|
||||||
} else {
|
if is_finished {
|
||||||
None
|
if is_fg {
|
||||||
}
|
take_term()?;
|
||||||
}) {
|
} else {
|
||||||
|
println!();
|
||||||
if is_finished {
|
let job_order = read_jobs(|j| j.order().to_vec());
|
||||||
if is_fg {
|
let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned());
|
||||||
take_term()?;
|
if let Some(job) = result {
|
||||||
} else {
|
println!("{}", job.display(&job_order, JobCmdFlags::PIDS))
|
||||||
println!();
|
}
|
||||||
let job_order = read_jobs(|j| j.order().to_vec());
|
}
|
||||||
let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned());
|
}
|
||||||
if let Some(job) = result {
|
}
|
||||||
println!("{}",job.display(&job_order,JobCmdFlags::PIDS))
|
Ok(())
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
703
src/state.rs
703
src/state.rs
@@ -1,8 +1,23 @@
|
|||||||
use std::{collections::{HashMap, VecDeque}, ops::Deref, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}, time::Duration};
|
use std::{
|
||||||
|
collections::{HashMap, VecDeque},
|
||||||
|
ops::Deref,
|
||||||
|
sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use nix::unistd::{gethostname, getppid, User};
|
use nix::unistd::{gethostname, getppid, User};
|
||||||
|
|
||||||
use crate::{exec_input, jobs::JobTab, libsh::{error::{ShErr, ShErrKind, ShResult}, utils::VecDequeExt}, parse::{ConjunctNode, NdRule, Node, ParsedSrc}, prelude::*, shopt::ShOpts};
|
use crate::{
|
||||||
|
exec_input,
|
||||||
|
jobs::JobTab,
|
||||||
|
libsh::{
|
||||||
|
error::{ShErr, ShErrKind, ShResult},
|
||||||
|
utils::VecDequeExt,
|
||||||
|
},
|
||||||
|
parse::{ConjunctNode, NdRule, Node, ParsedSrc},
|
||||||
|
prelude::*,
|
||||||
|
shopt::ShOpts,
|
||||||
|
};
|
||||||
|
|
||||||
pub static JOB_TABLE: LazyLock<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(JobTab::new()));
|
pub static JOB_TABLE: LazyLock<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(JobTab::new()));
|
||||||
|
|
||||||
@@ -16,438 +31,458 @@ pub static SHOPTS: LazyLock<RwLock<ShOpts>> = LazyLock::new(|| RwLock::new(ShOpt
|
|||||||
|
|
||||||
/// A shell function
|
/// A shell function
|
||||||
///
|
///
|
||||||
/// Consists of the BraceGrp Node and the stored ParsedSrc that the node refers to
|
/// Consists of the BraceGrp Node and the stored ParsedSrc that the node refers
|
||||||
/// The Node must be stored with the ParsedSrc because the tokens of the node contain an Arc<String>
|
/// to The Node must be stored with the ParsedSrc because the tokens of the node
|
||||||
/// Which refers to the String held in ParsedSrc
|
/// contain an Arc<String> Which refers to the String held in ParsedSrc
|
||||||
///
|
///
|
||||||
/// Can be dereferenced to pull out the wrapped Node
|
/// Can be dereferenced to pull out the wrapped Node
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ShFunc(Node);
|
pub struct ShFunc(Node);
|
||||||
|
|
||||||
impl ShFunc {
|
impl ShFunc {
|
||||||
pub fn new(mut src: ParsedSrc) -> Self {
|
pub fn new(mut src: ParsedSrc) -> Self {
|
||||||
let body = Self::extract_brc_grp_hack(src.extract_nodes());
|
let body = Self::extract_brc_grp_hack(src.extract_nodes());
|
||||||
Self(body)
|
Self(body)
|
||||||
}
|
}
|
||||||
fn extract_brc_grp_hack(mut tree: Vec<Node>) -> Node {
|
fn extract_brc_grp_hack(mut tree: Vec<Node>) -> Node {
|
||||||
// FIXME: find a better way to do this
|
// FIXME: find a better way to do this
|
||||||
let conjunction = tree.pop().unwrap();
|
let conjunction = tree.pop().unwrap();
|
||||||
let NdRule::Conjunction { mut elements } = conjunction.class else {
|
let NdRule::Conjunction { mut elements } = conjunction.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let conjunct_node = elements.pop().unwrap();
|
let conjunct_node = elements.pop().unwrap();
|
||||||
let ConjunctNode { cmd, operator: _ } = conjunct_node;
|
let ConjunctNode { cmd, operator: _ } = conjunct_node;
|
||||||
*cmd
|
*cmd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for ShFunc {
|
impl Deref for ShFunc {
|
||||||
type Target = Node;
|
type Target = Node;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The logic table for the shell
|
/// The logic table for the shell
|
||||||
///
|
///
|
||||||
/// Contains aliases and functions
|
/// Contains aliases and functions
|
||||||
#[derive(Default,Clone,Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct LogTab {
|
pub struct LogTab {
|
||||||
functions: HashMap<String,ShFunc>,
|
functions: HashMap<String, ShFunc>,
|
||||||
aliases: HashMap<String,String>
|
aliases: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogTab {
|
impl LogTab {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
pub fn insert_func(&mut self, name: &str, src: ShFunc) {
|
pub fn insert_func(&mut self, name: &str, src: ShFunc) {
|
||||||
self.functions.insert(name.into(), src);
|
self.functions.insert(name.into(), src);
|
||||||
}
|
}
|
||||||
pub fn get_func(&self, name: &str) -> Option<ShFunc> {
|
pub fn get_func(&self, name: &str) -> Option<ShFunc> {
|
||||||
self.functions.get(name).cloned()
|
self.functions.get(name).cloned()
|
||||||
}
|
}
|
||||||
pub fn funcs(&self) -> &HashMap<String,ShFunc> {
|
pub fn funcs(&self) -> &HashMap<String, ShFunc> {
|
||||||
&self.functions
|
&self.functions
|
||||||
}
|
}
|
||||||
pub fn aliases(&self) -> &HashMap<String,String> {
|
pub fn aliases(&self) -> &HashMap<String, String> {
|
||||||
&self.aliases
|
&self.aliases
|
||||||
}
|
}
|
||||||
pub fn insert_alias(&mut self, name: &str, body: &str) {
|
pub fn insert_alias(&mut self, name: &str, body: &str) {
|
||||||
self.aliases.insert(name.into(), body.into());
|
self.aliases.insert(name.into(), body.into());
|
||||||
}
|
}
|
||||||
pub fn get_alias(&self, name: &str) -> Option<String> {
|
pub fn get_alias(&self, name: &str) -> Option<String> {
|
||||||
self.aliases.get(name).cloned()
|
self.aliases.get(name).cloned()
|
||||||
}
|
}
|
||||||
pub fn remove_alias(&mut self, name: &str) {
|
pub fn remove_alias(&mut self, name: &str) {
|
||||||
flog!(DEBUG, self.aliases);
|
flog!(DEBUG, self.aliases);
|
||||||
flog!(DEBUG, name);
|
flog!(DEBUG, name);
|
||||||
self.aliases.remove(name);
|
self.aliases.remove(name);
|
||||||
flog!(DEBUG, self.aliases);
|
flog!(DEBUG, self.aliases);
|
||||||
}
|
}
|
||||||
pub fn clear_aliases(&mut self) {
|
pub fn clear_aliases(&mut self) {
|
||||||
self.aliases.clear()
|
self.aliases.clear()
|
||||||
}
|
}
|
||||||
pub fn clear_functions(&mut self) {
|
pub fn clear_functions(&mut self) {
|
||||||
self.functions.clear()
|
self.functions.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Var {
|
pub struct Var {
|
||||||
export: bool,
|
export: bool,
|
||||||
value: String
|
value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Var {
|
impl Var {
|
||||||
pub fn new(value: String) -> Self {
|
pub fn new(value: String) -> Self {
|
||||||
Self { export: false, value }
|
Self {
|
||||||
}
|
export: false,
|
||||||
pub fn mark_for_export(&mut self) {
|
value,
|
||||||
self.export = true;
|
}
|
||||||
}
|
}
|
||||||
|
pub fn mark_for_export(&mut self) {
|
||||||
|
self.export = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Var {
|
impl Deref for Var {
|
||||||
type Target = String;
|
type Target = String;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.value
|
&self.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default,Clone,Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct VarTab {
|
pub struct VarTab {
|
||||||
vars: HashMap<String,Var>,
|
vars: HashMap<String, Var>,
|
||||||
params: HashMap<String,String>,
|
params: HashMap<String, String>,
|
||||||
sh_argv: VecDeque<String>, // Using a VecDeque makes the implementation of `shift` straightforward
|
sh_argv: VecDeque<String>, /* Using a VecDeque makes the implementation of `shift`
|
||||||
|
* straightforward */
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VarTab {
|
impl VarTab {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let vars = HashMap::new();
|
let vars = HashMap::new();
|
||||||
let params = Self::init_params();
|
let params = Self::init_params();
|
||||||
Self::init_env();
|
Self::init_env();
|
||||||
let mut var_tab = Self { vars, params, sh_argv: VecDeque::new() };
|
let mut var_tab = Self {
|
||||||
var_tab.init_sh_argv();
|
vars,
|
||||||
var_tab
|
params,
|
||||||
}
|
sh_argv: VecDeque::new(),
|
||||||
fn init_params() -> HashMap<String, String> {
|
};
|
||||||
let mut params = HashMap::new();
|
var_tab.init_sh_argv();
|
||||||
params.insert("?".into(), "0".into()); // Last command exit status
|
var_tab
|
||||||
params.insert("#".into(), "0".into()); // Number of positional parameters
|
}
|
||||||
params.insert("0".into(), std::env::current_exe().unwrap().to_str().unwrap().to_string()); // Name of the shell
|
fn init_params() -> HashMap<String, String> {
|
||||||
params.insert("$".into(), Pid::this().to_string()); // PID of the shell
|
let mut params = HashMap::new();
|
||||||
params.insert("!".into(), "".into()); // PID of the last background job (if any)
|
params.insert("?".into(), "0".into()); // Last command exit status
|
||||||
params
|
params.insert("#".into(), "0".into()); // Number of positional parameters
|
||||||
}
|
params.insert(
|
||||||
fn init_env() {
|
"0".into(),
|
||||||
let pathbuf_to_string = |pb: Result<PathBuf, std::io::Error>| pb.unwrap_or_default().to_string_lossy().to_string();
|
std::env::current_exe()
|
||||||
// First, inherit any env vars from the parent process
|
.unwrap()
|
||||||
let term = {
|
.to_str()
|
||||||
if isatty(1).unwrap() {
|
.unwrap()
|
||||||
if let Ok(term) = std::env::var("TERM") {
|
.to_string(),
|
||||||
term
|
); // Name of the shell
|
||||||
} else {
|
params.insert("$".into(), Pid::this().to_string()); // PID of the shell
|
||||||
"linux".to_string()
|
params.insert("!".into(), "".into()); // PID of the last background job (if any)
|
||||||
}
|
params
|
||||||
} else {
|
}
|
||||||
"xterm-256color".to_string()
|
fn init_env() {
|
||||||
}
|
let pathbuf_to_string =
|
||||||
};
|
|pb: Result<PathBuf, std::io::Error>| pb.unwrap_or_default().to_string_lossy().to_string();
|
||||||
let home;
|
// First, inherit any env vars from the parent process
|
||||||
let username;
|
let term = {
|
||||||
let uid;
|
if isatty(1).unwrap() {
|
||||||
if let Some(user) = User::from_uid(nix::unistd::Uid::current()).ok().flatten() {
|
if let Ok(term) = std::env::var("TERM") {
|
||||||
home = user.dir;
|
term
|
||||||
username = user.name;
|
} else {
|
||||||
uid = user.uid;
|
"linux".to_string()
|
||||||
} else {
|
}
|
||||||
home = PathBuf::new();
|
} else {
|
||||||
username = "unknown".into();
|
"xterm-256color".to_string()
|
||||||
uid = 0.into();
|
}
|
||||||
}
|
};
|
||||||
let home = pathbuf_to_string(Ok(home));
|
let home;
|
||||||
let hostname = gethostname().map(|hname| hname.to_string_lossy().to_string()).unwrap_or_default();
|
let username;
|
||||||
|
let uid;
|
||||||
|
if let Some(user) = User::from_uid(nix::unistd::Uid::current()).ok().flatten() {
|
||||||
|
home = user.dir;
|
||||||
|
username = user.name;
|
||||||
|
uid = user.uid;
|
||||||
|
} else {
|
||||||
|
home = PathBuf::new();
|
||||||
|
username = "unknown".into();
|
||||||
|
uid = 0.into();
|
||||||
|
}
|
||||||
|
let home = pathbuf_to_string(Ok(home));
|
||||||
|
let hostname = gethostname()
|
||||||
|
.map(|hname| hname.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
env::set_var("IFS", " \t\n");
|
env::set_var("IFS", " \t\n");
|
||||||
env::set_var("HOST", hostname.clone());
|
env::set_var("HOST", hostname.clone());
|
||||||
env::set_var("UID", uid.to_string());
|
env::set_var("UID", uid.to_string());
|
||||||
env::set_var("PPID", getppid().to_string());
|
env::set_var("PPID", getppid().to_string());
|
||||||
env::set_var("TMPDIR", "/tmp");
|
env::set_var("TMPDIR", "/tmp");
|
||||||
env::set_var("TERM", term);
|
env::set_var("TERM", term);
|
||||||
env::set_var("LANG", "en_US.UTF-8");
|
env::set_var("LANG", "en_US.UTF-8");
|
||||||
env::set_var("USER", username.clone());
|
env::set_var("USER", username.clone());
|
||||||
env::set_var("LOGNAME", username);
|
env::set_var("LOGNAME", username);
|
||||||
env::set_var("PWD", pathbuf_to_string(std::env::current_dir()));
|
env::set_var("PWD", pathbuf_to_string(std::env::current_dir()));
|
||||||
env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir()));
|
env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir()));
|
||||||
env::set_var("HOME", home.clone());
|
env::set_var("HOME", home.clone());
|
||||||
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
||||||
env::set_var("FERN_HIST",format!("{}/.fernhist",home));
|
env::set_var("FERN_HIST", format!("{}/.fernhist", home));
|
||||||
env::set_var("FERN_RC",format!("{}/.fernrc",home));
|
env::set_var("FERN_RC", format!("{}/.fernrc", home));
|
||||||
}
|
}
|
||||||
pub fn init_sh_argv(&mut self) {
|
pub fn init_sh_argv(&mut self) {
|
||||||
for arg in env::args() {
|
for arg in env::args() {
|
||||||
self.bpush_arg(arg);
|
self.bpush_arg(arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn update_exports(&mut self) {
|
pub fn update_exports(&mut self) {
|
||||||
for var_name in self.vars.keys() {
|
for var_name in self.vars.keys() {
|
||||||
let var = self.vars.get(var_name).unwrap();
|
let var = self.vars.get(var_name).unwrap();
|
||||||
if var.export {
|
if var.export {
|
||||||
env::set_var(var_name, &var.value);
|
env::set_var(var_name, &var.value);
|
||||||
} else {
|
} else {
|
||||||
env::set_var(var_name, "");
|
env::set_var(var_name, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn sh_argv(&self) -> &VecDeque<String> {
|
pub fn sh_argv(&self) -> &VecDeque<String> {
|
||||||
&self.sh_argv
|
&self.sh_argv
|
||||||
}
|
}
|
||||||
pub fn sh_argv_mut(&mut self) -> &mut VecDeque<String> {
|
pub fn sh_argv_mut(&mut self) -> &mut VecDeque<String> {
|
||||||
&mut self.sh_argv
|
&mut self.sh_argv
|
||||||
}
|
}
|
||||||
pub fn clear_args(&mut self) {
|
pub fn clear_args(&mut self) {
|
||||||
self.sh_argv.clear();
|
self.sh_argv.clear();
|
||||||
// Push the current exe again
|
// Push the current exe again
|
||||||
// This makes sure that $0 is always the current shell, no matter what
|
// This makes sure that $0 is always the current shell, no matter what
|
||||||
// It also updates the arg parameters '@' and '#' as well
|
// It also updates the arg parameters '@' and '#' as well
|
||||||
self.bpush_arg(env::current_exe().unwrap().to_str().unwrap().to_string());
|
self.bpush_arg(env::current_exe().unwrap().to_str().unwrap().to_string());
|
||||||
}
|
}
|
||||||
fn update_arg_params(&mut self) {
|
fn update_arg_params(&mut self) {
|
||||||
self.set_param("@", &self.sh_argv.clone().to_vec()[1..].join(" "));
|
self.set_param("@", &self.sh_argv.clone().to_vec()[1..].join(" "));
|
||||||
self.set_param("#", &(self.sh_argv.len() - 1).to_string());
|
self.set_param("#", &(self.sh_argv.len() - 1).to_string());
|
||||||
}
|
}
|
||||||
/// Push an arg to the front of the arg deque
|
/// Push an arg to the front of the arg deque
|
||||||
pub fn fpush_arg(&mut self, arg: String) {
|
pub fn fpush_arg(&mut self, arg: String) {
|
||||||
self.sh_argv.push_front(arg);
|
self.sh_argv.push_front(arg);
|
||||||
self.update_arg_params();
|
self.update_arg_params();
|
||||||
}
|
}
|
||||||
/// Push an arg to the back of the arg deque
|
/// Push an arg to the back of the arg deque
|
||||||
pub fn bpush_arg(&mut self, arg: String) {
|
pub fn bpush_arg(&mut self, arg: String) {
|
||||||
self.sh_argv.push_back(arg);
|
self.sh_argv.push_back(arg);
|
||||||
self.update_arg_params();
|
self.update_arg_params();
|
||||||
}
|
}
|
||||||
/// Pop an arg from the front of the arg deque
|
/// Pop an arg from the front of the arg deque
|
||||||
pub fn fpop_arg(&mut self) -> Option<String> {
|
pub fn fpop_arg(&mut self) -> Option<String> {
|
||||||
let arg = self.sh_argv.pop_front();
|
let arg = self.sh_argv.pop_front();
|
||||||
self.update_arg_params();
|
self.update_arg_params();
|
||||||
arg
|
arg
|
||||||
}
|
}
|
||||||
/// Pop an arg from the back of the arg deque
|
/// Pop an arg from the back of the arg deque
|
||||||
pub fn bpop_arg(&mut self) -> Option<String> {
|
pub fn bpop_arg(&mut self) -> Option<String> {
|
||||||
let arg = self.sh_argv.pop_back();
|
let arg = self.sh_argv.pop_back();
|
||||||
self.update_arg_params();
|
self.update_arg_params();
|
||||||
arg
|
arg
|
||||||
}
|
}
|
||||||
pub fn vars(&self) -> &HashMap<String,Var> {
|
pub fn vars(&self) -> &HashMap<String, Var> {
|
||||||
&self.vars
|
&self.vars
|
||||||
}
|
}
|
||||||
pub fn vars_mut(&mut self) -> &mut HashMap<String,Var> {
|
pub fn vars_mut(&mut self) -> &mut HashMap<String, Var> {
|
||||||
&mut self.vars
|
&mut self.vars
|
||||||
}
|
}
|
||||||
pub fn params(&self) -> &HashMap<String,String> {
|
pub fn params(&self) -> &HashMap<String, String> {
|
||||||
&self.params
|
&self.params
|
||||||
}
|
}
|
||||||
pub fn params_mut(&mut self) -> &mut HashMap<String,String> {
|
pub fn params_mut(&mut self) -> &mut HashMap<String, String> {
|
||||||
&mut self.params
|
&mut self.params
|
||||||
}
|
}
|
||||||
pub fn export_var(&mut self, var_name: &str) {
|
pub fn export_var(&mut self, var_name: &str) {
|
||||||
if let Some(var) = self.vars.get_mut(var_name) {
|
if let Some(var) = self.vars.get_mut(var_name) {
|
||||||
var.mark_for_export();
|
var.mark_for_export();
|
||||||
env::set_var(var_name, &var.value);
|
env::set_var(var_name, &var.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_var(&self, var: &str) -> String {
|
pub fn get_var(&self, var: &str) -> String {
|
||||||
if var.chars().count() == 1 ||
|
if var.chars().count() == 1 || var.parse::<usize>().is_ok() {
|
||||||
var.parse::<usize>().is_ok() {
|
let param = self.get_param(var);
|
||||||
let param = self.get_param(var);
|
if !param.is_empty() {
|
||||||
if !param.is_empty() {
|
return param;
|
||||||
return param
|
}
|
||||||
}
|
}
|
||||||
}
|
if let Some(var) = self.vars.get(var).map(|s| s.to_string()) {
|
||||||
if let Some(var) = self.vars.get(var).map(|s| s.to_string()) {
|
var
|
||||||
var
|
} else {
|
||||||
} else {
|
std::env::var(var).unwrap_or_default()
|
||||||
std::env::var(var).unwrap_or_default()
|
}
|
||||||
}
|
}
|
||||||
}
|
pub fn set_var(&mut self, var_name: &str, val: &str, export: bool) {
|
||||||
pub fn set_var(&mut self, var_name: &str, val: &str, export: bool) {
|
if let Some(var) = self.vars.get_mut(var_name) {
|
||||||
if let Some(var) = self.vars.get_mut(var_name) {
|
var.value = val.to_string();
|
||||||
var.value = val.to_string();
|
if var.export {
|
||||||
if var.export {
|
env::set_var(var_name, val);
|
||||||
env::set_var(var_name, val);
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
let mut var = Var::new(val.to_string());
|
||||||
let mut var = Var::new(val.to_string());
|
if export {
|
||||||
if export {
|
var.mark_for_export();
|
||||||
var.mark_for_export();
|
env::set_var(var_name, &*var);
|
||||||
env::set_var(var_name, &*var);
|
}
|
||||||
}
|
self.vars.insert(var_name.to_string(), var);
|
||||||
self.vars.insert(var_name.to_string(), var);
|
}
|
||||||
}
|
}
|
||||||
}
|
pub fn var_exists(&self, var_name: &str) -> bool {
|
||||||
pub fn var_exists(&self, var_name: &str) -> bool {
|
if var_name.parse::<usize>().is_ok() {
|
||||||
if var_name.parse::<usize>().is_ok() {
|
return self.params.contains_key(var_name);
|
||||||
return self.params.contains_key(var_name);
|
}
|
||||||
}
|
self.vars.contains_key(var_name) || (var_name.len() == 1 && self.params.contains_key(var_name))
|
||||||
self.vars.contains_key(var_name) ||
|
}
|
||||||
(
|
pub fn set_param(&mut self, param: &str, val: &str) {
|
||||||
var_name.len() == 1 &&
|
self.params.insert(param.to_string(), val.to_string());
|
||||||
self.params.contains_key(var_name)
|
}
|
||||||
)
|
pub fn get_param(&self, param: &str) -> String {
|
||||||
}
|
if param.parse::<usize>().is_ok() {
|
||||||
pub fn set_param(&mut self, param: &str, val: &str) {
|
let argv_idx = param.to_string().parse::<usize>().unwrap();
|
||||||
self.params.insert(param.to_string(),val.to_string());
|
let arg = self
|
||||||
}
|
.sh_argv
|
||||||
pub fn get_param(&self, param: &str) -> String {
|
.get(argv_idx)
|
||||||
if param.parse::<usize>().is_ok() {
|
.map(|s| s.to_string())
|
||||||
let argv_idx = param
|
.unwrap_or_default();
|
||||||
.to_string()
|
arg
|
||||||
.parse::<usize>()
|
} else if param == "?" {
|
||||||
.unwrap();
|
self
|
||||||
let arg = self.sh_argv.get(argv_idx).map(|s| s.to_string()).unwrap_or_default();
|
.params
|
||||||
arg
|
.get(param)
|
||||||
} else if param == "?" {
|
.map(|s| s.to_string())
|
||||||
self.params.get(param).map(|s| s.to_string()).unwrap_or("0".into())
|
.unwrap_or("0".into())
|
||||||
} else {
|
} else {
|
||||||
self.params.get(param).map(|s| s.to_string()).unwrap_or_default()
|
self
|
||||||
}
|
.params
|
||||||
}
|
.get(param)
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A table of metadata for the shell
|
/// A table of metadata for the shell
|
||||||
#[derive(Default,Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct MetaTab {
|
pub struct MetaTab {
|
||||||
runtime_start: Option<Instant>
|
runtime_start: Option<Instant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetaTab {
|
impl MetaTab {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
pub fn start_timer(&mut self) {
|
pub fn start_timer(&mut self) {
|
||||||
self.runtime_start = Some(Instant::now());
|
self.runtime_start = Some(Instant::now());
|
||||||
}
|
}
|
||||||
pub fn stop_timer(&mut self) -> Option<Duration> {
|
pub fn stop_timer(&mut self) -> Option<Duration> {
|
||||||
self.runtime_start
|
self
|
||||||
.take() // runtime_start returns to None
|
.runtime_start
|
||||||
.map(|start| start.elapsed()) // return the duration, if any
|
.take() // runtime_start returns to None
|
||||||
}
|
.map(|start| start.elapsed()) // return the duration, if any
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read from the job table
|
/// Read from the job table
|
||||||
pub fn read_jobs<T, F: FnOnce(RwLockReadGuard<JobTab>) -> T>(f: F) -> T {
|
pub fn read_jobs<T, F: FnOnce(RwLockReadGuard<JobTab>) -> T>(f: F) -> T {
|
||||||
let lock = JOB_TABLE.read().unwrap();
|
let lock = JOB_TABLE.read().unwrap();
|
||||||
f(lock)
|
f(lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write to the job table
|
/// Write to the job table
|
||||||
pub fn write_jobs<T, F: FnOnce(&mut RwLockWriteGuard<JobTab>) -> T>(f: F) -> T {
|
pub fn write_jobs<T, F: FnOnce(&mut RwLockWriteGuard<JobTab>) -> T>(f: F) -> T {
|
||||||
let lock = &mut JOB_TABLE.write().unwrap();
|
let lock = &mut JOB_TABLE.write().unwrap();
|
||||||
f(lock)
|
f(lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read from the variable table
|
/// Read from the variable table
|
||||||
pub fn read_vars<T, F: FnOnce(RwLockReadGuard<VarTab>) -> T>(f: F) -> T {
|
pub fn read_vars<T, F: FnOnce(RwLockReadGuard<VarTab>) -> T>(f: F) -> T {
|
||||||
let lock = VAR_TABLE.read().unwrap();
|
let lock = VAR_TABLE.read().unwrap();
|
||||||
f(lock)
|
f(lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write to the variable table
|
/// Write to the variable table
|
||||||
pub fn write_vars<T, F: FnOnce(&mut RwLockWriteGuard<VarTab>) -> T>(f: F) -> T {
|
pub fn write_vars<T, F: FnOnce(&mut RwLockWriteGuard<VarTab>) -> T>(f: F) -> T {
|
||||||
let lock = &mut VAR_TABLE.write().unwrap();
|
let lock = &mut VAR_TABLE.write().unwrap();
|
||||||
f(lock)
|
f(lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_meta<T, F: FnOnce(RwLockReadGuard<MetaTab>) -> T>(f: F) -> T {
|
pub fn read_meta<T, F: FnOnce(RwLockReadGuard<MetaTab>) -> T>(f: F) -> T {
|
||||||
let lock = META_TABLE.read().unwrap();
|
let lock = META_TABLE.read().unwrap();
|
||||||
f(lock)
|
f(lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write to the variable table
|
/// Write to the variable table
|
||||||
pub fn write_meta<T, F: FnOnce(&mut RwLockWriteGuard<MetaTab>) -> T>(f: F) -> T {
|
pub fn write_meta<T, F: FnOnce(&mut RwLockWriteGuard<MetaTab>) -> T>(f: F) -> T {
|
||||||
let lock = &mut META_TABLE.write().unwrap();
|
let lock = &mut META_TABLE.write().unwrap();
|
||||||
f(lock)
|
f(lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read from the logic table
|
/// Read from the logic table
|
||||||
pub fn read_logic<T, F: FnOnce(RwLockReadGuard<LogTab>) -> T>(f: F) -> T {
|
pub fn read_logic<T, F: FnOnce(RwLockReadGuard<LogTab>) -> T>(f: F) -> T {
|
||||||
let lock = LOGIC_TABLE.read().unwrap();
|
let lock = LOGIC_TABLE.read().unwrap();
|
||||||
f(lock)
|
f(lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write to the logic table
|
/// Write to the logic table
|
||||||
pub fn write_logic<T, F: FnOnce(&mut RwLockWriteGuard<LogTab>) -> T>(f: F) -> T {
|
pub fn write_logic<T, F: FnOnce(&mut RwLockWriteGuard<LogTab>) -> T>(f: F) -> T {
|
||||||
let lock = &mut LOGIC_TABLE.write().unwrap();
|
let lock = &mut LOGIC_TABLE.write().unwrap();
|
||||||
f(lock)
|
f(lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_shopts<T, F: FnOnce(RwLockReadGuard<ShOpts>) -> T>(f: F) -> T {
|
pub fn read_shopts<T, F: FnOnce(RwLockReadGuard<ShOpts>) -> T>(f: F) -> T {
|
||||||
let lock = SHOPTS.read().unwrap();
|
let lock = SHOPTS.read().unwrap();
|
||||||
f(lock)
|
f(lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_shopts<T, F: FnOnce(&mut RwLockWriteGuard<ShOpts>) -> T>(f: F) -> T {
|
pub fn write_shopts<T, F: FnOnce(&mut RwLockWriteGuard<ShOpts>) -> T>(f: F) -> T {
|
||||||
let lock = &mut SHOPTS.write().unwrap();
|
let lock = &mut SHOPTS.write().unwrap();
|
||||||
f(lock)
|
f(lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function is used internally and ideally never sees user input
|
/// This function is used internally and ideally never sees user input
|
||||||
///
|
///
|
||||||
/// It will panic if you give it an invalid path.
|
/// It will panic if you give it an invalid path.
|
||||||
pub fn get_shopt(path: &str) -> String {
|
pub fn get_shopt(path: &str) -> String {
|
||||||
read_shopts(|s| s.get(path)).unwrap().unwrap()
|
read_shopts(|s| s.get(path)).unwrap().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_status() -> i32 {
|
pub fn get_status() -> i32 {
|
||||||
read_vars(|v| v.get_param("?")).parse::<i32>().unwrap()
|
read_vars(|v| v.get_param("?")).parse::<i32>().unwrap()
|
||||||
}
|
}
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn set_status(code: i32) {
|
pub fn set_status(code: i32) {
|
||||||
write_vars(|v| v.set_param("?", &code.to_string()))
|
write_vars(|v| v.set_param("?", &code.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save the current state of the logic and variable table, and the working directory path
|
/// Save the current state of the logic and variable table, and the working
|
||||||
|
/// directory path
|
||||||
pub fn get_snapshots() -> (LogTab, VarTab, String) {
|
pub fn get_snapshots() -> (LogTab, VarTab, String) {
|
||||||
(
|
(
|
||||||
read_logic(|l| l.clone()),
|
read_logic(|l| l.clone()),
|
||||||
read_vars(|v| v.clone()),
|
read_vars(|v| v.clone()),
|
||||||
env::var("PWD").unwrap_or_default()
|
env::var("PWD").unwrap_or_default(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore_snapshot(snapshot: (LogTab, VarTab, String)) {
|
pub fn restore_snapshot(snapshot: (LogTab, VarTab, String)) {
|
||||||
write_logic(|l| **l = snapshot.0);
|
write_logic(|l| **l = snapshot.0);
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
**v = snapshot.1;
|
**v = snapshot.1;
|
||||||
v.update_exports();
|
v.update_exports();
|
||||||
});
|
});
|
||||||
env::set_current_dir(&snapshot.2).unwrap();
|
env::set_current_dir(&snapshot.2).unwrap();
|
||||||
env::set_var("PWD", &snapshot.2);
|
env::set_var("PWD", &snapshot.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_rc() -> ShResult<()> {
|
pub fn source_rc() -> ShResult<()> {
|
||||||
let path = if let Ok(path) = env::var("FERN_RC") {
|
let path = if let Ok(path) = env::var("FERN_RC") {
|
||||||
PathBuf::from(&path)
|
PathBuf::from(&path)
|
||||||
} else {
|
} else {
|
||||||
let home = env::var("HOME").unwrap();
|
let home = env::var("HOME").unwrap();
|
||||||
PathBuf::from(format!("{home}/.fernrc"))
|
PathBuf::from(format!("{home}/.fernrc"))
|
||||||
};
|
};
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(
|
return Err(ShErr::simple(ShErrKind::InternalErr, ".fernrc not found"));
|
||||||
ShErr::simple(ShErrKind::InternalErr, ".fernrc not found")
|
}
|
||||||
)
|
source_file(path)
|
||||||
}
|
|
||||||
source_file(path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_file(path: PathBuf) -> ShResult<()> {
|
pub fn source_file(path: PathBuf) -> ShResult<()> {
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new().read(true).open(path)?;
|
||||||
.read(true)
|
|
||||||
.open(path)?;
|
|
||||||
|
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
file.read_to_string(&mut buf)?;
|
file.read_to_string(&mut buf)?;
|
||||||
exec_input(buf,None)?;
|
exec_input(buf, None)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,173 +2,175 @@ use super::*;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cmd_not_found() {
|
fn cmd_not_found() {
|
||||||
let input = "foo";
|
let input = "foo";
|
||||||
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty()).next().unwrap().unwrap();
|
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
||||||
let err = ShErr::full(ShErrKind::CmdNotFound("foo".into()), "", token.span);
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let err = ShErr::full(ShErrKind::CmdNotFound("foo".into()), "", token.span);
|
||||||
|
|
||||||
let err_fmt = format!("{err}");
|
let err_fmt = format!("{err}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unclosed_subsh() {
|
fn unclosed_subsh() {
|
||||||
let input = "(foo";
|
let input = "(foo";
|
||||||
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty()).nth(1).unwrap();
|
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
||||||
let Err(err) = token else {
|
.nth(1)
|
||||||
panic!("{:?}",token);
|
.unwrap();
|
||||||
};
|
let Err(err) = token else {
|
||||||
|
panic!("{:?}", token);
|
||||||
|
};
|
||||||
|
|
||||||
let err_fmt = format!("{err}");
|
let err_fmt = format!("{err}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unclosed_dquote() {
|
fn unclosed_dquote() {
|
||||||
let input = "\"foo bar";
|
let input = "\"foo bar";
|
||||||
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty()).nth(1).unwrap();
|
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
||||||
let Err(err) = token else {
|
.nth(1)
|
||||||
panic!();
|
.unwrap();
|
||||||
};
|
let Err(err) = token else {
|
||||||
|
panic!();
|
||||||
|
};
|
||||||
|
|
||||||
let err_fmt = format!("{err}");
|
let err_fmt = format!("{err}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unclosed_squote() {
|
fn unclosed_squote() {
|
||||||
let input = "'foo bar";
|
let input = "'foo bar";
|
||||||
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty()).nth(1).unwrap();
|
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
||||||
let Err(err) = token else {
|
.nth(1)
|
||||||
panic!();
|
.unwrap();
|
||||||
};
|
let Err(err) = token else {
|
||||||
|
panic!();
|
||||||
|
};
|
||||||
|
|
||||||
let err_fmt = format!("{err}");
|
let err_fmt = format!("{err}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unclosed_brc_grp() {
|
fn unclosed_brc_grp() {
|
||||||
let input = "{ foo bar";
|
let input = "{ foo bar";
|
||||||
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let node = ParseStream::new(tokens).next().unwrap();
|
let node = ParseStream::new(tokens).next().unwrap();
|
||||||
let Err(err) = node else {
|
let Err(err) = node else {
|
||||||
panic!();
|
panic!();
|
||||||
};
|
};
|
||||||
|
|
||||||
let err_fmt = format!("{err}");
|
let err_fmt = format!("{err}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn if_no_fi() {
|
fn if_no_fi() {
|
||||||
let input = "if foo; then bar;";
|
let input = "if foo; then bar;";
|
||||||
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let node = ParseStream::new(tokens).next().unwrap();
|
let node = ParseStream::new(tokens).next().unwrap();
|
||||||
let Err(e) = node else { panic!() };
|
let Err(e) = node else { panic!() };
|
||||||
|
|
||||||
let err_fmt = format!("{e}");
|
let err_fmt = format!("{e}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn if_no_then() {
|
fn if_no_then() {
|
||||||
let input = "if foo; bar; fi";
|
let input = "if foo; bar; fi";
|
||||||
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let node = ParseStream::new(tokens).next().unwrap();
|
let node = ParseStream::new(tokens).next().unwrap();
|
||||||
let Err(e) = node else { panic!() };
|
let Err(e) = node else { panic!() };
|
||||||
|
|
||||||
let err_fmt = format!("{e}");
|
let err_fmt = format!("{e}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn loop_no_done() {
|
fn loop_no_done() {
|
||||||
let input = "while true; do echo foo;";
|
let input = "while true; do echo foo;";
|
||||||
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let node = ParseStream::new(tokens).next().unwrap();
|
let node = ParseStream::new(tokens).next().unwrap();
|
||||||
let Err(e) = node else { panic!() };
|
let Err(e) = node else { panic!() };
|
||||||
|
|
||||||
let err_fmt = format!("{e}");
|
let err_fmt = format!("{e}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn loop_no_do() {
|
fn loop_no_do() {
|
||||||
let input = "while true; echo foo; done";
|
let input = "while true; echo foo; done";
|
||||||
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let node = ParseStream::new(tokens).next().unwrap();
|
let node = ParseStream::new(tokens).next().unwrap();
|
||||||
let Err(e) = node else { panic!() };
|
let Err(e) = node else { panic!() };
|
||||||
|
|
||||||
let err_fmt = format!("{e}");
|
let err_fmt = format!("{e}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn case_no_esac() {
|
fn case_no_esac() {
|
||||||
let input = "case foo in foo) bar;; bar) foo;;";
|
let input = "case foo in foo) bar;; bar) foo;;";
|
||||||
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let node = ParseStream::new(tokens).next().unwrap();
|
let node = ParseStream::new(tokens).next().unwrap();
|
||||||
let Err(e) = node else { panic!() };
|
let Err(e) = node else { panic!() };
|
||||||
|
|
||||||
let err_fmt = format!("{e}");
|
let err_fmt = format!("{e}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn case_no_in() {
|
fn case_no_in() {
|
||||||
let input = "case foo foo) bar;; bar) foo;; esac";
|
let input = "case foo foo) bar;; bar) foo;; esac";
|
||||||
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let node = ParseStream::new(tokens).next().unwrap();
|
let node = ParseStream::new(tokens).next().unwrap();
|
||||||
let Err(e) = node else { panic!() };
|
let Err(e) = node else { panic!() };
|
||||||
|
|
||||||
let err_fmt = format!("{e}");
|
let err_fmt = format!("{e}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn error_with_notes() {
|
fn error_with_notes() {
|
||||||
let err = ShErr::simple(ShErrKind::ExecFail, "Execution failed")
|
let err = ShErr::simple(ShErrKind::ExecFail, "Execution failed")
|
||||||
.with_note(Note::new("Execution failed for this reason"))
|
.with_note(Note::new("Execution failed for this reason"))
|
||||||
.with_note(Note::new("Here is how to fix it: blah blah blah"));
|
.with_note(Note::new("Here is how to fix it: blah blah blah"));
|
||||||
|
|
||||||
let err_fmt = format!("{err}");
|
let err_fmt = format!("{err}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn error_with_notes_and_sub_notes() {
|
fn error_with_notes_and_sub_notes() {
|
||||||
let err = ShErr::simple(ShErrKind::ExecFail, "Execution failed")
|
let err = ShErr::simple(ShErrKind::ExecFail, "Execution failed")
|
||||||
.with_note(Note::new("Execution failed for this reason"))
|
.with_note(Note::new("Execution failed for this reason"))
|
||||||
.with_note(
|
.with_note(Note::new("Here is how to fix it:").with_sub_notes(vec!["blah", "blah", "blah"]));
|
||||||
Note::new("Here is how to fix it:")
|
|
||||||
.with_sub_notes(vec![
|
|
||||||
"blah",
|
|
||||||
"blah",
|
|
||||||
"blah"
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
let err_fmt = format!("{err}");
|
let err_fmt = format!("{err}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,297 +6,300 @@ use super::*;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_expansion() {
|
fn simple_expansion() {
|
||||||
let varsub = "$foo";
|
let varsub = "$foo";
|
||||||
write_vars(|v| v.set_var("foo", "this is the value of the variable", false));
|
write_vars(|v| v.set_var("foo", "this is the value of the variable", false));
|
||||||
|
|
||||||
let mut tokens: Vec<Tk> = LexStream::new(Arc::new(varsub.to_string()), LexFlags::empty())
|
let mut tokens: Vec<Tk> = LexStream::new(Arc::new(varsub.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.filter(|tk| !matches!(tk.class, TkRule::EOI | TkRule::SOI))
|
.filter(|tk| !matches!(tk.class, TkRule::EOI | TkRule::SOI))
|
||||||
.collect();
|
.collect();
|
||||||
let var_tk = tokens.pop().unwrap();
|
let var_tk = tokens.pop().unwrap();
|
||||||
|
|
||||||
let exp_tk = var_tk.expand().unwrap();
|
let exp_tk = var_tk.expand().unwrap();
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
insta::assert_debug_snapshot!(exp_tk.get_words())
|
insta::assert_debug_snapshot!(exp_tk.get_words())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unescape_string() {
|
fn unescape_string() {
|
||||||
let string = "echo $foo \\$bar";
|
let string = "echo $foo \\$bar";
|
||||||
let unescaped = unescape_str(string);
|
let unescaped = unescape_str(string);
|
||||||
|
|
||||||
insta::assert_snapshot!(unescaped)
|
insta::assert_snapshot!(unescaped)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expand_alias_simple() {
|
fn expand_alias_simple() {
|
||||||
write_logic(|l| {
|
write_logic(|l| {
|
||||||
l.insert_alias("foo", "echo foo");
|
l.insert_alias("foo", "echo foo");
|
||||||
let input = String::from("foo");
|
let input = String::from("foo");
|
||||||
|
|
||||||
let result = expand_aliases(input, HashSet::new(), l);
|
let result = expand_aliases(input, HashSet::new(), l);
|
||||||
assert_eq!(result.as_str(),"echo foo");
|
assert_eq!(result.as_str(), "echo foo");
|
||||||
l.clear_aliases();
|
l.clear_aliases();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expand_alias_in_if() {
|
fn expand_alias_in_if() {
|
||||||
write_logic(|l| {
|
write_logic(|l| {
|
||||||
l.insert_alias("foo", "echo foo");
|
l.insert_alias("foo", "echo foo");
|
||||||
let input = String::from("if foo; then echo bar; fi");
|
let input = String::from("if foo; then echo bar; fi");
|
||||||
|
|
||||||
let result = expand_aliases(input, HashSet::new(), l);
|
let result = expand_aliases(input, HashSet::new(), l);
|
||||||
assert_eq!(result.as_str(),"if echo foo; then echo bar; fi");
|
assert_eq!(result.as_str(), "if echo foo; then echo bar; fi");
|
||||||
l.clear_aliases();
|
l.clear_aliases();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expand_alias_multiline() {
|
fn expand_alias_multiline() {
|
||||||
write_logic(|l| {
|
write_logic(|l| {
|
||||||
l.insert_alias("foo", "echo foo");
|
l.insert_alias("foo", "echo foo");
|
||||||
l.insert_alias("bar", "echo bar");
|
l.insert_alias("bar", "echo bar");
|
||||||
let input = String::from("
|
let input = String::from(
|
||||||
|
"
|
||||||
foo
|
foo
|
||||||
if true; then
|
if true; then
|
||||||
bar
|
bar
|
||||||
fi
|
fi
|
||||||
");
|
",
|
||||||
let expected = String::from("
|
);
|
||||||
|
let expected = String::from(
|
||||||
|
"
|
||||||
echo foo
|
echo foo
|
||||||
if true; then
|
if true; then
|
||||||
echo bar
|
echo bar
|
||||||
fi
|
fi
|
||||||
");
|
",
|
||||||
|
);
|
||||||
|
|
||||||
let result = expand_aliases(input, HashSet::new(), l);
|
let result = expand_aliases(input, HashSet::new(), l);
|
||||||
assert_eq!(result,expected)
|
assert_eq!(result, expected)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expand_multiple_aliases() {
|
fn expand_multiple_aliases() {
|
||||||
write_logic(|l| {
|
write_logic(|l| {
|
||||||
l.insert_alias("foo", "echo foo");
|
l.insert_alias("foo", "echo foo");
|
||||||
l.insert_alias("bar", "echo bar");
|
l.insert_alias("bar", "echo bar");
|
||||||
l.insert_alias("biz", "echo biz");
|
l.insert_alias("biz", "echo biz");
|
||||||
let input = String::from("foo; bar; biz");
|
let input = String::from("foo; bar; biz");
|
||||||
|
|
||||||
let result = expand_aliases(input, HashSet::new(), l);
|
let result = expand_aliases(input, HashSet::new(), l);
|
||||||
assert_eq!(result.as_str(),"echo foo; echo bar; echo biz");
|
assert_eq!(result.as_str(), "echo foo; echo bar; echo biz");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn alias_in_arg_position() {
|
fn alias_in_arg_position() {
|
||||||
write_logic(|l| {
|
write_logic(|l| {
|
||||||
l.insert_alias("foo", "echo foo");
|
l.insert_alias("foo", "echo foo");
|
||||||
let input = String::from("echo foo");
|
let input = String::from("echo foo");
|
||||||
|
|
||||||
let result = expand_aliases(input.clone(), HashSet::new(), l);
|
let result = expand_aliases(input.clone(), HashSet::new(), l);
|
||||||
assert_eq!(input,result);
|
assert_eq!(input, result);
|
||||||
l.clear_aliases();
|
l.clear_aliases();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expand_recursive_alias() {
|
fn expand_recursive_alias() {
|
||||||
write_logic(|l| {
|
write_logic(|l| {
|
||||||
l.insert_alias("foo", "echo foo");
|
l.insert_alias("foo", "echo foo");
|
||||||
l.insert_alias("bar", "foo bar");
|
l.insert_alias("bar", "foo bar");
|
||||||
|
|
||||||
let input = String::from("bar");
|
let input = String::from("bar");
|
||||||
let result = expand_aliases(input, HashSet::new(), l);
|
let result = expand_aliases(input, HashSet::new(), l);
|
||||||
assert_eq!(result.as_str(),"echo foo bar");
|
assert_eq!(result.as_str(), "echo foo bar");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_infinite_recursive_alias() {
|
fn test_infinite_recursive_alias() {
|
||||||
write_logic(|l| {
|
write_logic(|l| {
|
||||||
l.insert_alias("foo", "foo bar");
|
l.insert_alias("foo", "foo bar");
|
||||||
|
|
||||||
let input = String::from("foo");
|
|
||||||
let result = expand_aliases(input, HashSet::new(), l);
|
|
||||||
assert_eq!(result.as_str(),"foo bar");
|
|
||||||
l.clear_aliases();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
let input = String::from("foo");
|
||||||
|
let result = expand_aliases(input, HashSet::new(), l);
|
||||||
|
assert_eq!(result.as_str(), "foo bar");
|
||||||
|
l.clear_aliases();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_defaultunsetornull() {
|
fn param_expansion_defaultunsetornull() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
v.set_var("set_var", "value", false);
|
v.set_var("set_var", "value", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("unset:-default").unwrap();
|
let result = perform_param_expansion("unset:-default").unwrap();
|
||||||
assert_eq!(result, "default");
|
assert_eq!(result, "default");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_defaultunset() {
|
fn param_expansion_defaultunset() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
v.set_var("set_var", "value", false);
|
v.set_var("set_var", "value", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("unset-default").unwrap();
|
let result = perform_param_expansion("unset-default").unwrap();
|
||||||
assert_eq!(result, "default");
|
assert_eq!(result, "default");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_setdefaultunsetornull() {
|
fn param_expansion_setdefaultunsetornull() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
v.set_var("set_var", "value", false);
|
v.set_var("set_var", "value", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("unset:=assigned").unwrap();
|
let result = perform_param_expansion("unset:=assigned").unwrap();
|
||||||
assert_eq!(result, "assigned");
|
assert_eq!(result, "assigned");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_setdefaultunset() {
|
fn param_expansion_setdefaultunset() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
v.set_var("set_var", "value", false);
|
v.set_var("set_var", "value", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("unset=assigned").unwrap();
|
let result = perform_param_expansion("unset=assigned").unwrap();
|
||||||
assert_eq!(result, "assigned");
|
assert_eq!(result, "assigned");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_altsetnotnull() {
|
fn param_expansion_altsetnotnull() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
v.set_var("set_var", "value", false);
|
v.set_var("set_var", "value", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("set_var:+alt").unwrap();
|
let result = perform_param_expansion("set_var:+alt").unwrap();
|
||||||
assert_eq!(result, "alt");
|
assert_eq!(result, "alt");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_altnotnull() {
|
fn param_expansion_altnotnull() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
v.set_var("set_var", "value", false);
|
v.set_var("set_var", "value", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("set_var+alt").unwrap();
|
let result = perform_param_expansion("set_var+alt").unwrap();
|
||||||
assert_eq!(result, "alt");
|
assert_eq!(result, "alt");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_len() {
|
fn param_expansion_len() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("#foo").unwrap();
|
let result = perform_param_expansion("#foo").unwrap();
|
||||||
assert_eq!(result, "3");
|
assert_eq!(result, "3");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_substr() {
|
fn param_expansion_substr() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("foo:1").unwrap();
|
let result = perform_param_expansion("foo:1").unwrap();
|
||||||
assert_eq!(result, "oo");
|
assert_eq!(result, "oo");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_substrlen() {
|
fn param_expansion_substrlen() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("foo:0:2").unwrap();
|
let result = perform_param_expansion("foo:0:2").unwrap();
|
||||||
assert_eq!(result, "fo");
|
assert_eq!(result, "fo");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_remshortestprefix() {
|
fn param_expansion_remshortestprefix() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("foo#f*").unwrap();
|
let result = perform_param_expansion("foo#f*").unwrap();
|
||||||
assert_eq!(result, "oo");
|
assert_eq!(result, "oo");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_remlongestprefix() {
|
fn param_expansion_remlongestprefix() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("foo##f*").unwrap();
|
let result = perform_param_expansion("foo##f*").unwrap();
|
||||||
assert_eq!(result, "");
|
assert_eq!(result, "");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_remshortestsuffix() {
|
fn param_expansion_remshortestsuffix() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("foo%*o").unwrap();
|
let result = perform_param_expansion("foo%*o").unwrap();
|
||||||
assert_eq!(result, "fo");
|
assert_eq!(result, "fo");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_remlongestsuffix() {
|
fn param_expansion_remlongestsuffix() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("foo%%*o").unwrap();
|
let result = perform_param_expansion("foo%%*o").unwrap();
|
||||||
assert_eq!(result, "");
|
assert_eq!(result, "");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_replacefirstmatch() {
|
fn param_expansion_replacefirstmatch() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("foo/foo/X").unwrap();
|
let result = perform_param_expansion("foo/foo/X").unwrap();
|
||||||
assert_eq!(result, "X");
|
assert_eq!(result, "X");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_replaceallmatches() {
|
fn param_expansion_replaceallmatches() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("foo//o/X").unwrap();
|
let result = perform_param_expansion("foo//o/X").unwrap();
|
||||||
assert_eq!(result, "fXX");
|
assert_eq!(result, "fXX");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_replaceprefix() {
|
fn param_expansion_replaceprefix() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("foo/#f/X").unwrap();
|
let result = perform_param_expansion("foo/#f/X").unwrap();
|
||||||
assert_eq!(result, "Xoo");
|
assert_eq!(result, "Xoo");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_expansion_replacesuffix() {
|
fn param_expansion_replacesuffix() {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.set_var("foo", "foo", false);
|
v.set_var("foo", "foo", false);
|
||||||
});
|
});
|
||||||
let result = perform_param_expansion("foo/%o/X").unwrap();
|
let result = perform_param_expansion("foo/%o/X").unwrap();
|
||||||
assert_eq!(result, "foX");
|
assert_eq!(result, "foX");
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,32 +6,44 @@ use super::super::*;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn getopt_from_argv() {
|
fn getopt_from_argv() {
|
||||||
let node = get_nodes("echo -n -e foo", |node| matches!(node.class, NdRule::Command {..}))
|
let node = get_nodes("echo -n -e foo", |node| {
|
||||||
.pop()
|
matches!(node.class, NdRule::Command { .. })
|
||||||
.unwrap();
|
})
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
.pop()
|
||||||
panic!()
|
.unwrap();
|
||||||
};
|
let NdRule::Command {
|
||||||
|
assignments: _,
|
||||||
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
|
||||||
let (words,opts) = get_opts_from_tokens(argv);
|
let (words, opts) = get_opts_from_tokens(argv);
|
||||||
insta::assert_debug_snapshot!(words);
|
insta::assert_debug_snapshot!(words);
|
||||||
insta::assert_debug_snapshot!(opts)
|
insta::assert_debug_snapshot!(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn getopt_simple() {
|
fn getopt_simple() {
|
||||||
let raw = "echo -n foo".split_whitespace().map(|s| s.to_string()).collect::<Vec<_>>();
|
let raw = "echo -n foo"
|
||||||
|
.split_whitespace()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let (words,opts) = get_opts(raw);
|
let (words, opts) = get_opts(raw);
|
||||||
insta::assert_debug_snapshot!(words);
|
insta::assert_debug_snapshot!(words);
|
||||||
insta::assert_debug_snapshot!(opts);
|
insta::assert_debug_snapshot!(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn getopt_multiple_short() {
|
fn getopt_multiple_short() {
|
||||||
let raw = "echo -nre foo".split_whitespace().map(|s| s.to_string()).collect::<Vec<_>>();
|
let raw = "echo -nre foo"
|
||||||
|
.split_whitespace()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let (words,opts) = get_opts(raw);
|
let (words, opts) = get_opts(raw);
|
||||||
insta::assert_debug_snapshot!(words);
|
insta::assert_debug_snapshot!(words);
|
||||||
insta::assert_debug_snapshot!(opts);
|
insta::assert_debug_snapshot!(opts);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +1,52 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
fn lex_simple() {
|
fn lex_simple() {
|
||||||
let input = "echo hello world";
|
let input = "echo hello world";
|
||||||
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(tokens)
|
insta::assert_debug_snapshot!(tokens)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn lex_redir() {
|
fn lex_redir() {
|
||||||
let input = "echo foo > bar.txt";
|
let input = "echo foo > bar.txt";
|
||||||
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(tokens)
|
insta::assert_debug_snapshot!(tokens)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn lex_redir_fds() {
|
fn lex_redir_fds() {
|
||||||
let input = "echo foo 1>&2";
|
let input = "echo foo 1>&2";
|
||||||
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(tokens)
|
insta::assert_debug_snapshot!(tokens)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn lex_quote_str() {
|
fn lex_quote_str() {
|
||||||
let input = "echo \"foo bar\" biz baz";
|
let input = "echo \"foo bar\" biz baz";
|
||||||
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(tokens)
|
insta::assert_debug_snapshot!(tokens)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn lex_with_keywords() {
|
fn lex_with_keywords() {
|
||||||
let input = "if true; then echo foo; fi";
|
let input = "if true; then echo foo; fi";
|
||||||
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(tokens)
|
insta::assert_debug_snapshot!(tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lex_multiline() {
|
fn lex_multiline() {
|
||||||
let input = "echo hello world\necho foo bar\necho boo biz";
|
let input = "echo hello world\necho foo bar\necho boo biz";
|
||||||
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(tokens)
|
insta::assert_debug_snapshot!(tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lex_case() {
|
fn lex_case() {
|
||||||
let input = "case $foo in foo) bar;; bar) foo;; biz) baz;; esac";
|
let input = "case $foo in foo) bar;; bar) foo;; biz) baz;; esac";
|
||||||
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
let tokens: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty()).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(tokens)
|
insta::assert_debug_snapshot!(tokens)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,41 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::libsh::error::{
|
use crate::expand::{expand_aliases, unescape_str};
|
||||||
Note, ShErr, ShErrKind
|
use crate::libsh::error::{Note, ShErr, ShErrKind};
|
||||||
};
|
|
||||||
use crate::parse::{
|
use crate::parse::{
|
||||||
node_operation, Node, NdRule, ParseStream,
|
lex::{LexFlags, LexStream, Tk, TkRule},
|
||||||
lex::{
|
node_operation, NdRule, Node, ParseStream,
|
||||||
Tk, TkRule, LexFlags, LexStream
|
|
||||||
}
|
|
||||||
};
|
|
||||||
use crate::expand::{
|
|
||||||
expand_aliases, unescape_str
|
|
||||||
};
|
|
||||||
use crate::state::{
|
|
||||||
write_logic, write_vars
|
|
||||||
};
|
};
|
||||||
|
use crate::state::{write_logic, write_vars};
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
pub mod expand;
|
||||||
|
pub mod getopt;
|
||||||
|
pub mod highlight;
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod expand;
|
|
||||||
pub mod term;
|
|
||||||
pub mod error;
|
|
||||||
pub mod getopt;
|
|
||||||
pub mod script;
|
|
||||||
pub mod highlight;
|
|
||||||
pub mod readline;
|
pub mod readline;
|
||||||
|
pub mod script;
|
||||||
|
pub mod term;
|
||||||
|
|
||||||
/// Unsafe to use outside of tests
|
/// Unsafe to use outside of tests
|
||||||
pub fn get_nodes<F1>(input: &str, filter: F1) -> Vec<Node>
|
pub fn get_nodes<F1>(input: &str, filter: F1) -> Vec<Node>
|
||||||
where
|
where
|
||||||
F1: Fn(&Node) -> bool
|
F1: Fn(&Node) -> bool,
|
||||||
{
|
{
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
let tokens = LexStream::new(Arc::new(input.into()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let mut parsed_nodes = ParseStream::new(tokens)
|
let mut parsed_nodes = ParseStream::new(tokens)
|
||||||
.map(|nd| nd.unwrap())
|
.map(|nd| nd.unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for node in parsed_nodes.iter_mut() {
|
for node in parsed_nodes.iter_mut() {
|
||||||
node_operation(node,
|
node_operation(node, &filter, &mut |node: &mut Node| {
|
||||||
&filter,
|
nodes.push(node.clone())
|
||||||
&mut |node: &mut Node| nodes.push(node.clone())
|
});
|
||||||
);
|
}
|
||||||
}
|
nodes
|
||||||
nodes
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,95 +2,95 @@ use super::*;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_simple() {
|
fn parse_simple() {
|
||||||
let input = "echo hello world";
|
let input = "echo hello world";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_pipeline() {
|
fn parse_pipeline() {
|
||||||
let input = "echo foo | sed s/foo/bar";
|
let input = "echo foo | sed s/foo/bar";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_conjunction() {
|
fn parse_conjunction() {
|
||||||
let input = "echo foo && echo bar";
|
let input = "echo foo && echo bar";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_conjunction_and_pipeline() {
|
fn parse_conjunction_and_pipeline() {
|
||||||
let input = "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/";
|
let input = "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_multiline() {
|
fn parse_multiline() {
|
||||||
let input = "
|
let input = "
|
||||||
echo hello world
|
echo hello world
|
||||||
echo foo bar
|
echo foo bar
|
||||||
echo boo biz";
|
echo boo biz";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_if_simple() {
|
fn parse_if_simple() {
|
||||||
let input = "if foo; then echo bar; fi";
|
let input = "if foo; then echo bar; fi";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_if_with_elif() {
|
fn parse_if_with_elif() {
|
||||||
let input = "if foo; then echo bar; elif bar; then echo foo; fi";
|
let input = "if foo; then echo bar; elif bar; then echo foo; fi";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_if_multiple_elif() {
|
fn parse_if_multiple_elif() {
|
||||||
let input = "if foo; then echo bar; elif bar; then echo foo; elif biz; then echo baz; fi";
|
let input = "if foo; then echo bar; elif bar; then echo foo; elif biz; then echo baz; fi";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_if_multiline() {
|
fn parse_if_multiline() {
|
||||||
let input = "
|
let input = "
|
||||||
if foo; then
|
if foo; then
|
||||||
echo bar
|
echo bar
|
||||||
elif bar; then
|
elif bar; then
|
||||||
@@ -98,59 +98,59 @@ elif bar; then
|
|||||||
elif biz; then
|
elif biz; then
|
||||||
echo baz
|
echo baz
|
||||||
fi";
|
fi";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_loop_simple() {
|
fn parse_loop_simple() {
|
||||||
let input = "while foo; do bar; done";
|
let input = "while foo; do bar; done";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_loop_until() {
|
fn parse_loop_until() {
|
||||||
let input = "until foo; do bar; done";
|
let input = "until foo; do bar; done";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_loop_multiline() {
|
fn parse_loop_multiline() {
|
||||||
let input = "
|
let input = "
|
||||||
until foo; do
|
until foo; do
|
||||||
bar
|
bar
|
||||||
done";
|
done";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_case_simple() {
|
fn parse_case_simple() {
|
||||||
let input = "case foo in foo) bar;; bar) foo;; biz) baz;; esac";
|
let input = "case foo in foo) bar;; bar) foo;; biz) baz;; esac";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_case_multiline() {
|
fn parse_case_multiline() {
|
||||||
let input = "case foo in
|
let input = "case foo in
|
||||||
foo) bar
|
foo) bar
|
||||||
;;
|
;;
|
||||||
bar) foo
|
bar) foo
|
||||||
@@ -158,16 +158,16 @@ fn parse_case_multiline() {
|
|||||||
biz) baz
|
biz) baz
|
||||||
;;
|
;;
|
||||||
esac";
|
esac";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_case_nested() {
|
fn parse_case_nested() {
|
||||||
let input = "case foo in
|
let input = "case foo in
|
||||||
foo)
|
foo)
|
||||||
if true; then
|
if true; then
|
||||||
while true; do
|
while true; do
|
||||||
@@ -194,41 +194,41 @@ fn parse_case_nested() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac";
|
esac";
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_cursed() {
|
fn parse_cursed() {
|
||||||
let input = "if if if if case foo in foo) if true; then true; fi;; esac; then case foo in foo) until true; do true; done;; esac; fi; then until if case foo in foo) true;; esac; then if true; then true; fi; fi; do until until true; do true; done; do case foo in foo) true;; esac; done; done; fi; then until until case foo in foo) true;; esac; do if true; then true; fi; done; do until true; do true; done; done; fi; then until case foo in foo) case foo in foo) true;; esac;; esac; do if if true; then true; fi; then until true; do true; done; fi; done; elif until until case foo in foo) true;; esac; do if true; then true; fi; done; do case foo in foo) until true; do true; done;; esac; done; then case foo in foo) if case foo in foo) true;; esac; then if true; then true; fi; fi;; esac; else case foo in foo) until until true; do true; done; do case foo in foo) true;; esac; done;; esac; fi";
|
let input = "if if if if case foo in foo) if true; then true; fi;; esac; then case foo in foo) until true; do true; done;; esac; fi; then until if case foo in foo) true;; esac; then if true; then true; fi; fi; do until until true; do true; done; do case foo in foo) true;; esac; done; done; fi; then until until case foo in foo) true;; esac; do if true; then true; fi; done; do until true; do true; done; done; fi; then until case foo in foo) case foo in foo) true;; esac;; esac; do if if true; then true; fi; then until true; do true; done; fi; done; elif until until case foo in foo) true;; esac; do if true; then true; fi; done; do case foo in foo) until true; do true; done;; esac; done; then case foo in foo) if case foo in foo) true;; esac; then if true; then true; fi; fi;; esac; else case foo in foo) until until true; do true; done; do case foo in foo) true;; esac; done;; esac; fi";
|
||||||
|
|
||||||
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Arc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
// 15,000 line snapshot file btw
|
// 15,000 line snapshot file btw
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_node_operation() {
|
fn test_node_operation() {
|
||||||
let input = String::from("echo hello world; echo foo bar");
|
let input = String::from("echo hello world; echo foo bar");
|
||||||
let mut check_nodes = vec![];
|
let mut check_nodes = vec![];
|
||||||
let tokens: Vec<Tk> = LexStream::new(input.into(), LexFlags::empty())
|
let tokens: Vec<Tk> = LexStream::new(input.into(), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let nodes = ParseStream::new(tokens)
|
let nodes = ParseStream::new(tokens).map(|nd| nd.unwrap());
|
||||||
.map(|nd| nd.unwrap());
|
|
||||||
|
|
||||||
for mut node in nodes {
|
for mut node in nodes {
|
||||||
node_operation(&mut node,
|
node_operation(
|
||||||
&|node: &Node| matches!(node.class, NdRule::Command {..}),
|
&mut node,
|
||||||
&mut |node: &mut Node| check_nodes.push(node.clone()),
|
&|node: &Node| matches!(node.class, NdRule::Command { .. }),
|
||||||
);
|
&mut |node: &mut Node| check_nodes.push(node.clone()),
|
||||||
}
|
);
|
||||||
insta::assert_debug_snapshot!(check_nodes)
|
}
|
||||||
|
insta::assert_debug_snapshot!(check_nodes)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,49 +4,48 @@ use pretty_assertions::assert_eq;
|
|||||||
|
|
||||||
use super::super::*;
|
use super::super::*;
|
||||||
fn get_script_output(name: &str, args: &[&str]) -> Output {
|
fn get_script_output(name: &str, args: &[&str]) -> Output {
|
||||||
// Resolve the path to the fern binary.
|
// Resolve the path to the fern binary.
|
||||||
// Do not question me.
|
// Do not question me.
|
||||||
let mut fern_path = env::current_exe()
|
let mut fern_path = env::current_exe().expect("Failed to get test executable"); // The path to the test executable
|
||||||
.expect("Failed to get test executable"); // The path to the test executable
|
fern_path.pop(); // Hocus pocus
|
||||||
fern_path.pop(); // Hocus pocus
|
fern_path.pop();
|
||||||
fern_path.pop();
|
fern_path.push("fern"); // Abra Kadabra
|
||||||
fern_path.push("fern"); // Abra Kadabra
|
|
||||||
|
|
||||||
if !fern_path.is_file() {
|
if !fern_path.is_file() {
|
||||||
fern_path.pop();
|
fern_path.pop();
|
||||||
fern_path.pop();
|
fern_path.pop();
|
||||||
fern_path.push("release");
|
fern_path.push("release");
|
||||||
fern_path.push("fern");
|
fern_path.push("fern");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !fern_path.is_file() {
|
if !fern_path.is_file() {
|
||||||
panic!("where the hell is the binary")
|
panic!("where the hell is the binary")
|
||||||
}
|
}
|
||||||
|
|
||||||
process::Command::new(fern_path) // Alakazam
|
process::Command::new(fern_path) // Alakazam
|
||||||
.arg(name)
|
.arg(name)
|
||||||
.args(args)
|
.args(args)
|
||||||
.output()
|
.output()
|
||||||
.expect("Failed to run script")
|
.expect("Failed to run script")
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn script_hello_world() {
|
fn script_hello_world() {
|
||||||
let output = get_script_output("./test_scripts/hello.sh", &[]);
|
let output = get_script_output("./test_scripts/hello.sh", &[]);
|
||||||
assert!(output.status.success());
|
assert!(output.status.success());
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
assert_eq!(stdout.trim(), "Hello, World!")
|
assert_eq!(stdout.trim(), "Hello, World!")
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn script_cmdsub() {
|
fn script_cmdsub() {
|
||||||
let output = get_script_output("./test_scripts/cmdsub.sh", &[]);
|
let output = get_script_output("./test_scripts/cmdsub.sh", &[]);
|
||||||
assert!(output.status.success());
|
assert!(output.status.success());
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
assert_eq!(stdout.trim(), "foo Hello bar")
|
assert_eq!(stdout.trim(), "foo Hello bar")
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn script_multiline() {
|
fn script_multiline() {
|
||||||
let output = get_script_output("./test_scripts/multiline.sh", &[]);
|
let output = get_script_output("./test_scripts/multiline.sh", &[]);
|
||||||
assert!(output.status.success());
|
assert!(output.status.success());
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
assert_eq!(stdout.trim(), "foo\nbar\nbiz\nbuzz")
|
assert_eq!(stdout.trim(), "foo\nbar\nbiz\nbuzz")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,39 +3,41 @@ use libsh::term::{Style, StyleSet, Styled};
|
|||||||
use super::super::*;
|
use super::super::*;
|
||||||
#[test]
|
#[test]
|
||||||
fn styled_simple() {
|
fn styled_simple() {
|
||||||
let input = "hello world";
|
let input = "hello world";
|
||||||
let styled = input.styled(Style::Green);
|
let styled = input.styled(Style::Green);
|
||||||
|
|
||||||
insta::assert_snapshot!(styled)
|
insta::assert_snapshot!(styled)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn styled_multiple() {
|
fn styled_multiple() {
|
||||||
let input = "styled text";
|
let input = "styled text";
|
||||||
let styled = input.styled(Style::Red | Style::Bold | Style::Underline);
|
let styled = input.styled(Style::Red | Style::Bold | Style::Underline);
|
||||||
insta::assert_snapshot!(styled);
|
insta::assert_snapshot!(styled);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn styled_rgb() {
|
fn styled_rgb() {
|
||||||
let input = "RGB styled text";
|
let input = "RGB styled text";
|
||||||
let styled = input.styled(Style::RGB(255, 99, 71)); // Tomato color
|
let styled = input.styled(Style::RGB(255, 99, 71)); // Tomato color
|
||||||
insta::assert_snapshot!(styled);
|
insta::assert_snapshot!(styled);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn styled_background() {
|
fn styled_background() {
|
||||||
let input = "text with background";
|
let input = "text with background";
|
||||||
let styled = input.styled(Style::BgBlue | Style::Bold);
|
let styled = input.styled(Style::BgBlue | Style::Bold);
|
||||||
insta::assert_snapshot!(styled);
|
insta::assert_snapshot!(styled);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn styled_set() {
|
fn styled_set() {
|
||||||
let input = "multi-style text";
|
let input = "multi-style text";
|
||||||
let style_set = StyleSet::new().add_style(Style::Magenta).add_style(Style::Italic);
|
let style_set = StyleSet::new()
|
||||||
let styled = input.styled(style_set);
|
.add_style(Style::Magenta)
|
||||||
insta::assert_snapshot!(styled);
|
.add_style(Style::Italic);
|
||||||
|
let styled = input.styled(style_set);
|
||||||
|
insta::assert_snapshot!(styled);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn styled_reset() {
|
fn styled_reset() {
|
||||||
let input = "reset test";
|
let input = "reset test";
|
||||||
let styled = input.styled(Style::Bold | Style::Reset);
|
let styled = input.styled(Style::Bold | Style::Reset);
|
||||||
insta::assert_snapshot!(styled);
|
insta::assert_snapshot!(styled);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user