Various edge case fixes for prompt expansion and command substitution
This commit is contained in:
@@ -803,7 +803,7 @@ pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
|
|||||||
flog!(DEBUG, "filling buffer");
|
flog!(DEBUG, "filling buffer");
|
||||||
io_buf.fill_buffer()?;
|
io_buf.fill_buffer()?;
|
||||||
flog!(DEBUG, "done");
|
flog!(DEBUG, "done");
|
||||||
Ok(io_buf.as_str()?.trim().to_string())
|
Ok(io_buf.as_str()?.trim_end().to_string())
|
||||||
}
|
}
|
||||||
_ => Err(ShErr::simple(ShErrKind::InternalErr, "Command sub failed")),
|
_ => Err(ShErr::simple(ShErrKind::InternalErr, "Command sub failed")),
|
||||||
}
|
}
|
||||||
@@ -1615,7 +1615,7 @@ pub fn format_cmd_runtime(dur: std::time::Duration) -> String {
|
|||||||
let string = format!("{}s", seconds);
|
let string = format!("{}s", seconds);
|
||||||
result.push(string);
|
result.push(string);
|
||||||
}
|
}
|
||||||
if millis > 0 {
|
if result.is_empty() && millis > 0 {
|
||||||
let string = format!("{}ms", millis);
|
let string = format!("{}ms", millis);
|
||||||
result.push(string);
|
result.push(string);
|
||||||
}
|
}
|
||||||
@@ -1780,13 +1780,13 @@ pub fn expand_prompt(raw: &str) -> ShResult<String> {
|
|||||||
PromptTk::Text(txt) => result.push_str(&txt),
|
PromptTk::Text(txt) => result.push_str(&txt),
|
||||||
PromptTk::AnsiSeq(params) => result.push_str(¶ms),
|
PromptTk::AnsiSeq(params) => result.push_str(¶ms),
|
||||||
PromptTk::RuntimeMillis => {
|
PromptTk::RuntimeMillis => {
|
||||||
if let Some(runtime) = write_meta(|m| m.stop_timer()) {
|
if let Some(runtime) = write_meta(|m| m.get_time()) {
|
||||||
let runtime_millis = runtime.as_millis().to_string();
|
let runtime_millis = runtime.as_millis().to_string();
|
||||||
result.push_str(&runtime_millis);
|
result.push_str(&runtime_millis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PromptTk::RuntimeFormatted => {
|
PromptTk::RuntimeFormatted => {
|
||||||
if let Some(runtime) = write_meta(|m| m.stop_timer()) {
|
if let Some(runtime) = write_meta(|m| m.get_time()) {
|
||||||
let runtime_fmt = format_cmd_runtime(runtime);
|
let runtime_fmt = format_cmd_runtime(runtime);
|
||||||
result.push_str(&runtime_fmt);
|
result.push_str(&runtime_fmt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ use crate::libsh::sys::TermiosGuard;
|
|||||||
use crate::parse::execute::exec_input;
|
use crate::parse::execute::exec_input;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::signal::{QUIT_CODE, check_signals, sig_setup, signals_pending};
|
use crate::signal::{QUIT_CODE, check_signals, sig_setup, signals_pending};
|
||||||
use crate::state::source_rc;
|
use crate::state::{source_rc, write_meta};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use shopt::FernEditMode;
|
use shopt::FernEditMode;
|
||||||
use state::{read_vars, write_shopts, write_vars};
|
use state::{read_vars, write_shopts, write_vars};
|
||||||
@@ -178,6 +178,7 @@ fn fern_interactive() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
write_meta(|m| m.start_timer());
|
||||||
if let Err(e) = exec_input(input, None) {
|
if let Err(e) = exec_input(input, None) {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
ShErrKind::CleanExit(code) => {
|
ShErrKind::CleanExit(code) => {
|
||||||
@@ -189,5 +190,6 @@ fn fern_interactive() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
write_meta(|m| m.stop_timer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ impl ExecArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec_input(input: String, io_stack: Option<IoStack>) -> ShResult<()> {
|
pub fn exec_input(input: String, io_stack: Option<IoStack>) -> ShResult<()> {
|
||||||
write_meta(|m| m.start_timer());
|
|
||||||
let log_tab = read_logic(|l| l.clone());
|
let log_tab = read_logic(|l| l.clone());
|
||||||
let input = expand_aliases(input, HashSet::new(), &log_tab);
|
let input = expand_aliases(input, HashSet::new(), &log_tab);
|
||||||
let mut parser = ParsedSrc::new(Arc::new(input));
|
let mut parser = ParsedSrc::new(Arc::new(input));
|
||||||
@@ -471,6 +470,7 @@ impl Dispatcher {
|
|||||||
cond_frame.extend(in_redirs); // Condition gets input redirs
|
cond_frame.extend(in_redirs); // Condition gets input redirs
|
||||||
body_frame.extend(out_redirs); // Body gets output redirs
|
body_frame.extend(out_redirs); // Body gets output redirs
|
||||||
|
|
||||||
|
let mut matched = false;
|
||||||
for node in cond_nodes {
|
for node in cond_nodes {
|
||||||
let CondNode { cond, body } = node;
|
let CondNode { cond, body } = node;
|
||||||
self.io_stack.push(cond_frame.clone());
|
self.io_stack.push(cond_frame.clone());
|
||||||
@@ -482,16 +482,18 @@ impl Dispatcher {
|
|||||||
|
|
||||||
match state::get_status() {
|
match state::get_status() {
|
||||||
0 => {
|
0 => {
|
||||||
|
matched = true;
|
||||||
for body_node in body {
|
for body_node in body {
|
||||||
self.io_stack.push(body_frame.clone());
|
self.io_stack.push(body_frame.clone());
|
||||||
self.dispatch_node(body_node)?;
|
self.dispatch_node(body_node)?;
|
||||||
}
|
}
|
||||||
|
break; // Don't check remaining elif conditions
|
||||||
}
|
}
|
||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !else_block.is_empty() {
|
if !matched && !else_block.is_empty() {
|
||||||
for node in else_block {
|
for node in else_block {
|
||||||
self.io_stack.push(body_frame.clone());
|
self.io_stack.push(body_frame.clone());
|
||||||
self.dispatch_node(node)?;
|
self.dispatch_node(node)?;
|
||||||
|
|||||||
@@ -19,7 +19,11 @@ use crate::{
|
|||||||
pub enum IoMode {
|
pub enum IoMode {
|
||||||
Fd {
|
Fd {
|
||||||
tgt_fd: RawFd,
|
tgt_fd: RawFd,
|
||||||
src_fd: Arc<OwnedFd>,
|
src_fd: RawFd, // Just the fd number - dup2 will handle it at execution time
|
||||||
|
},
|
||||||
|
OpenedFile {
|
||||||
|
tgt_fd: RawFd,
|
||||||
|
file: Arc<OwnedFd>, // Owns the opened file descriptor
|
||||||
},
|
},
|
||||||
File {
|
File {
|
||||||
tgt_fd: RawFd,
|
tgt_fd: RawFd,
|
||||||
@@ -38,7 +42,7 @@ pub enum IoMode {
|
|||||||
|
|
||||||
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() };
|
// Just store the fd number - dup2 will use it directly at execution time
|
||||||
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 {
|
||||||
@@ -50,26 +54,28 @@ impl IoMode {
|
|||||||
}
|
}
|
||||||
pub fn tgt_fd(&self) -> RawFd {
|
pub fn tgt_fd(&self) -> RawFd {
|
||||||
match self {
|
match self {
|
||||||
IoMode::Fd { tgt_fd, .. } | IoMode::File { tgt_fd, .. } | IoMode::Pipe { tgt_fd, .. } => {
|
IoMode::Fd { tgt_fd, .. }
|
||||||
*tgt_fd
|
| IoMode::OpenedFile { tgt_fd, .. }
|
||||||
}
|
| IoMode::File { 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 { src_fd, .. } => *src_fd,
|
||||||
|
IoMode::OpenedFile { file, .. } => file.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 { 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 {
|
self = IoMode::OpenedFile {
|
||||||
tgt_fd,
|
tgt_fd,
|
||||||
src_fd: Arc::new(OwnedFd::from(file)),
|
file: Arc::new(OwnedFd::from(file)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(self)
|
Ok(self)
|
||||||
|
|||||||
33
src/state.rs
33
src/state.rs
@@ -57,6 +57,15 @@ pub enum ShellParam {
|
|||||||
ArgCount
|
ArgCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ShellParam {
|
||||||
|
pub fn is_global(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Self::Status | Self::ShPid | Self::LastJob | Self::ShellName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for ShellParam {
|
impl Display for ShellParam {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@@ -158,6 +167,9 @@ impl ScopeStack {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Ok(param) = var_name.parse::<ShellParam>() {
|
||||||
|
return self.global_params.contains_key(¶m.to_string());
|
||||||
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
pub fn flatten_vars(&self) -> HashMap<String, Var> {
|
pub fn flatten_vars(&self) -> HashMap<String, Var> {
|
||||||
@@ -187,6 +199,9 @@ impl ScopeStack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_var(&self, var_name: &str) -> String {
|
pub fn get_var(&self, var_name: &str) -> String {
|
||||||
|
if let Ok(param) = var_name.parse::<ShellParam>() {
|
||||||
|
return self.get_param(param);
|
||||||
|
}
|
||||||
for scope in self.scopes.iter().rev() {
|
for scope in self.scopes.iter().rev() {
|
||||||
if scope.var_exists(var_name) {
|
if scope.var_exists(var_name) {
|
||||||
return scope.get_var(var_name);
|
return scope.get_var(var_name);
|
||||||
@@ -196,6 +211,9 @@ impl ScopeStack {
|
|||||||
std::env::var(var_name).unwrap_or_default()
|
std::env::var(var_name).unwrap_or_default()
|
||||||
}
|
}
|
||||||
pub fn get_param(&self, param: ShellParam) -> String {
|
pub fn get_param(&self, param: ShellParam) -> String {
|
||||||
|
if param.is_global() && let Some(val) = self.global_params.get(¶m.to_string()) {
|
||||||
|
return val.clone();
|
||||||
|
}
|
||||||
for scope in self.scopes.iter().rev() {
|
for scope in self.scopes.iter().rev() {
|
||||||
let val = scope.get_param(param);
|
let val = scope.get_param(param);
|
||||||
if !val.is_empty() {
|
if !val.is_empty() {
|
||||||
@@ -637,6 +655,7 @@ impl VarTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn var_exists(&self, var_name: &str) -> bool {
|
pub fn var_exists(&self, var_name: &str) -> bool {
|
||||||
|
flog!(DEBUG, "checking existence of {}",var_name);
|
||||||
if let Ok(param) = var_name.parse::<ShellParam>() {
|
if let Ok(param) = var_name.parse::<ShellParam>() {
|
||||||
return self.params.contains_key(¶m);
|
return self.params.contains_key(¶m);
|
||||||
}
|
}
|
||||||
@@ -674,6 +693,7 @@ impl VarTab {
|
|||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct MetaTab {
|
pub struct MetaTab {
|
||||||
runtime_start: Option<Instant>,
|
runtime_start: Option<Instant>,
|
||||||
|
runtime_stop: Option<Instant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetaTab {
|
impl MetaTab {
|
||||||
@@ -683,11 +703,16 @@ impl MetaTab {
|
|||||||
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) {
|
||||||
self
|
self.runtime_stop = Some(Instant::now());
|
||||||
.runtime_start
|
|
||||||
.map(|start| start.elapsed()) // return the duration, if any
|
|
||||||
}
|
}
|
||||||
|
pub fn get_time(&self) -> Option<Duration> {
|
||||||
|
if let (Some(start), Some(stop)) = (self.runtime_start, self.runtime_stop) {
|
||||||
|
Some(stop.duration_since(start))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read from the job table
|
/// Read from the job table
|
||||||
|
|||||||
Reference in New Issue
Block a user