implemented 'getopts' builtin
This commit is contained in:
231
src/builtin/getopts.rs
Normal file
231
src/builtin/getopts.rs
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use ariadne::Fmt;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
getopt::{Opt, OptSpec}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color}, parse::{NdRule, Node, execute::prepare_argv, lex::Span}, state::{self, VarFlags, VarKind, read_meta, read_vars, write_meta, write_vars}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum OptMatch {
|
||||||
|
NoMatch,
|
||||||
|
IsMatch,
|
||||||
|
WantsArg
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GetOptsSpec {
|
||||||
|
silent_err: bool,
|
||||||
|
opt_specs: Vec<OptSpec>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetOptsSpec {
|
||||||
|
pub fn matches(&self, ch: char) -> OptMatch {
|
||||||
|
for spec in &self.opt_specs {
|
||||||
|
let OptSpec { opt, takes_arg } = spec;
|
||||||
|
match opt {
|
||||||
|
Opt::Short(opt_ch) if ch == *opt_ch => {
|
||||||
|
if *takes_arg {
|
||||||
|
return OptMatch::WantsArg
|
||||||
|
} else {
|
||||||
|
return OptMatch::IsMatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { continue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OptMatch::NoMatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for GetOptsSpec {
|
||||||
|
type Err = ShErr;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut s = s;
|
||||||
|
let mut opt_specs = vec![];
|
||||||
|
let mut silent_err = false;
|
||||||
|
if s.starts_with(':') {
|
||||||
|
silent_err = true;
|
||||||
|
s = &s[1..];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut chars = s.chars().peekable();
|
||||||
|
while let Some(ch) = chars.peek() {
|
||||||
|
match ch {
|
||||||
|
ch if ch.is_alphanumeric() => {
|
||||||
|
let opt = Opt::Short(*ch);
|
||||||
|
chars.next();
|
||||||
|
let takes_arg = chars.peek() == Some(&':');
|
||||||
|
if takes_arg {
|
||||||
|
chars.next();
|
||||||
|
}
|
||||||
|
opt_specs.push(OptSpec { opt, takes_arg })
|
||||||
|
}
|
||||||
|
_ => return Err(ShErr::simple(
|
||||||
|
ShErrKind::ParseErr,
|
||||||
|
format!("unexpected character '{}'", ch.fg(next_color()))
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(GetOptsSpec { silent_err, opt_specs })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance_optind(opt_index: usize, amount: usize) -> ShResult<()> {
|
||||||
|
write_vars(|v| v.set_var("OPTIND", VarKind::Str((opt_index + amount).to_string()), VarFlags::NONE))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getopts_inner(opts_spec: &GetOptsSpec, opt_var: &str, argv: &[String], blame: Span) -> ShResult<()> {
|
||||||
|
let opt_index = read_vars(|v| v.get_var("OPTIND").parse::<usize>().unwrap_or(1));
|
||||||
|
// OPTIND is 1-based
|
||||||
|
let arr_idx = opt_index.saturating_sub(1);
|
||||||
|
|
||||||
|
let Some(arg) = argv.get(arr_idx) else {
|
||||||
|
state::set_status(1);
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
// "--" stops option processing
|
||||||
|
if arg.as_str() == "--" {
|
||||||
|
advance_optind(opt_index, 1)?;
|
||||||
|
write_meta(|m| m.reset_getopts_char_offset());
|
||||||
|
state::set_status(1);
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not an option — done
|
||||||
|
let Some(opt_str) = arg.strip_prefix('-') else {
|
||||||
|
state::set_status(1);
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bare "-" is not an option
|
||||||
|
if opt_str.is_empty() {
|
||||||
|
state::set_status(1);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let char_idx = read_meta(|m| m.getopts_char_offset());
|
||||||
|
let Some(ch) = opt_str.chars().nth(char_idx) else {
|
||||||
|
// Ran out of chars in this arg (shouldn't normally happen),
|
||||||
|
// advance to next arg and signal done for this call
|
||||||
|
write_meta(|m| m.reset_getopts_char_offset());
|
||||||
|
advance_optind(opt_index, 1)?;
|
||||||
|
state::set_status(1);
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let last_char_in_arg = char_idx >= opt_str.len() - 1;
|
||||||
|
|
||||||
|
// Advance past this character: either move to next char in this
|
||||||
|
// arg, or reset offset and bump OPTIND to the next arg.
|
||||||
|
let advance_one_char = |last: bool| -> ShResult<()> {
|
||||||
|
if last {
|
||||||
|
write_meta(|m| m.reset_getopts_char_offset());
|
||||||
|
advance_optind(opt_index, 1)?;
|
||||||
|
} else {
|
||||||
|
write_meta(|m| m.inc_getopts_char_offset());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
match opts_spec.matches(ch) {
|
||||||
|
OptMatch::NoMatch => {
|
||||||
|
advance_one_char(last_char_in_arg)?;
|
||||||
|
if opts_spec.silent_err {
|
||||||
|
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
||||||
|
write_vars(|v| v.set_var("OPTARG", VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||||
|
} else {
|
||||||
|
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
||||||
|
ShErr::at(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
blame.clone(),
|
||||||
|
format!("illegal option '-{}'", ch.fg(next_color()))
|
||||||
|
).print_error();
|
||||||
|
}
|
||||||
|
state::set_status(0);
|
||||||
|
}
|
||||||
|
OptMatch::IsMatch => {
|
||||||
|
advance_one_char(last_char_in_arg)?;
|
||||||
|
write_vars(|v| v.set_var(opt_var, VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||||
|
state::set_status(0);
|
||||||
|
}
|
||||||
|
OptMatch::WantsArg => {
|
||||||
|
write_meta(|m| m.reset_getopts_char_offset());
|
||||||
|
|
||||||
|
if !last_char_in_arg {
|
||||||
|
// Remaining chars in this arg are the argument: -bVALUE
|
||||||
|
let optarg: String = opt_str.chars().skip(char_idx + 1).collect();
|
||||||
|
write_vars(|v| v.set_var("OPTARG", VarKind::Str(optarg), VarFlags::NONE))?;
|
||||||
|
advance_optind(opt_index, 1)?;
|
||||||
|
} else if let Some(next_arg) = argv.get(arr_idx + 1) {
|
||||||
|
// Next arg is the argument
|
||||||
|
write_vars(|v| v.set_var("OPTARG", VarKind::Str(next_arg.clone()), VarFlags::NONE))?;
|
||||||
|
// Skip both the option arg and its value
|
||||||
|
advance_optind(opt_index, 2)?;
|
||||||
|
} else {
|
||||||
|
// Missing required argument
|
||||||
|
if opts_spec.silent_err {
|
||||||
|
write_vars(|v| v.set_var(opt_var, VarKind::Str(":".into()), VarFlags::NONE))?;
|
||||||
|
write_vars(|v| v.set_var("OPTARG", VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||||
|
} else {
|
||||||
|
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
||||||
|
ShErr::at(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
blame.clone(),
|
||||||
|
format!("option '-{}' requires an argument", ch.fg(next_color()))
|
||||||
|
).print_error();
|
||||||
|
}
|
||||||
|
advance_optind(opt_index, 1)?;
|
||||||
|
state::set_status(0);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
write_vars(|v| v.set_var(opt_var, VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||||
|
state::set_status(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getopts(node: Node) -> ShResult<()> {
|
||||||
|
let span = node.get_span().clone();
|
||||||
|
let NdRule::Command {
|
||||||
|
assignments: _,
|
||||||
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut argv = prepare_argv(argv)?;
|
||||||
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
let mut args = argv.into_iter();
|
||||||
|
|
||||||
|
let Some(arg_string) = args.next() else {
|
||||||
|
return Err(ShErr::at(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
span,
|
||||||
|
"getopts: missing option spec"
|
||||||
|
))
|
||||||
|
};
|
||||||
|
let Some(opt_var) = args.next() else {
|
||||||
|
return Err(ShErr::at(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
span,
|
||||||
|
"getopts: missing variable name"
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
let opts_spec = GetOptsSpec::from_str(&arg_string.0)
|
||||||
|
.promote_err(arg_string.1.clone())?;
|
||||||
|
|
||||||
|
let explicit_args: Vec<String> = args.map(|s| s.0).collect();
|
||||||
|
|
||||||
|
if !explicit_args.is_empty() {
|
||||||
|
getopts_inner(&opts_spec, &opt_var.0, &explicit_args, span)
|
||||||
|
} else {
|
||||||
|
let pos_params: Vec<String> = read_vars(|v| v.sh_argv().iter().skip(1).cloned().collect());
|
||||||
|
getopts_inner(&opts_spec, &opt_var.0, &pos_params, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,12 +24,14 @@ pub mod zoltraak;
|
|||||||
pub mod map;
|
pub mod map;
|
||||||
pub mod arrops;
|
pub mod arrops;
|
||||||
pub mod intro;
|
pub mod intro;
|
||||||
|
pub mod getopts;
|
||||||
|
|
||||||
pub const BUILTINS: [&str; 43] = [
|
pub const BUILTINS: [&str; 44] = [
|
||||||
"echo", "cd", "read", "export", "local", "pwd", "source", "shift", "jobs", "fg", "bg", "disown",
|
"echo", "cd", "read", "export", "local", "pwd", "source", "shift", "jobs", "fg", "bg", "disown",
|
||||||
"alias", "unalias", "return", "break", "continue", "exit", "zoltraak", "shopt", "builtin",
|
"alias", "unalias", "return", "break", "continue", "exit", "zoltraak", "shopt", "builtin",
|
||||||
"command", "trap", "pushd", "popd", "dirs", "exec", "eval", "true", "false", ":", "readonly",
|
"command", "trap", "pushd", "popd", "dirs", "exec", "eval", "true", "false", ":", "readonly",
|
||||||
"unset", "complete", "compgen", "map", "pop", "fpop", "push", "fpush", "rotate", "wait", "type"
|
"unset", "complete", "compgen", "map", "pop", "fpop", "push", "fpush", "rotate", "wait", "type",
|
||||||
|
"getopts"
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn true_builtin() -> ShResult<()> {
|
pub fn true_builtin() -> ShResult<()> {
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ use std::collections::HashSet;
|
|||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::str::{Chars, FromStr};
|
use std::str::{Chars, FromStr};
|
||||||
|
|
||||||
|
use ariadne::Fmt;
|
||||||
use glob::Pattern;
|
use glob::Pattern;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::libsh::error::{ShErr, ShErrKind, ShResult};
|
use crate::libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color};
|
||||||
use crate::parse::execute::exec_input;
|
use crate::parse::execute::exec_input;
|
||||||
use crate::parse::lex::{LexFlags, LexStream, QuoteState, Tk, TkFlags, TkRule, is_hard_sep};
|
use crate::parse::lex::{LexFlags, LexStream, QuoteState, Tk, TkFlags, TkRule, is_hard_sep};
|
||||||
use crate::parse::{Redir, RedirType};
|
use crate::parse::{Redir, RedirType};
|
||||||
@@ -24,7 +25,9 @@ impl Tk {
|
|||||||
pub fn expand(self) -> ShResult<Self> {
|
pub fn expand(self) -> ShResult<Self> {
|
||||||
let flags = self.flags;
|
let flags = self.flags;
|
||||||
let span = self.span.clone();
|
let span = self.span.clone();
|
||||||
let exp = Expander::new(self)?.expand()?;
|
let exp = Expander::new(self)?
|
||||||
|
.expand()
|
||||||
|
.promote_err(span.clone())?;
|
||||||
let class = TkRule::Expanded { exp };
|
let class = TkRule::Expanded { exp };
|
||||||
Ok(Self { class, span, flags })
|
Ok(Self { class, span, flags })
|
||||||
}
|
}
|
||||||
@@ -646,10 +649,11 @@ enum ArithTk {
|
|||||||
Op(ArithOp),
|
Op(ArithOp),
|
||||||
LParen,
|
LParen,
|
||||||
RParen,
|
RParen,
|
||||||
|
Var(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArithTk {
|
impl ArithTk {
|
||||||
pub fn tokenize(raw: &str) -> ShResult<Vec<Self>> {
|
pub fn tokenize(raw: &str) -> ShResult<Option<Vec<Self>>> {
|
||||||
let mut tokens = Vec::new();
|
let mut tokens = Vec::new();
|
||||||
let mut chars = raw.chars().peekable();
|
let mut chars = raw.chars().peekable();
|
||||||
|
|
||||||
@@ -687,16 +691,28 @@ impl ArithTk {
|
|||||||
tokens.push(Self::RParen);
|
tokens.push(Self::RParen);
|
||||||
chars.next();
|
chars.next();
|
||||||
}
|
}
|
||||||
|
_ if ch.is_alphabetic() || ch == '_' => {
|
||||||
|
chars.next();
|
||||||
|
let mut var_name = ch.to_string();
|
||||||
|
while let Some(ch) = chars.peek() {
|
||||||
|
match ch {
|
||||||
|
_ if ch.is_alphabetic() || *ch == '_' => {
|
||||||
|
var_name.push(*ch);
|
||||||
|
chars.next();
|
||||||
|
}
|
||||||
|
_ => break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.push(Self::Var(var_name));
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShErr::simple(
|
return Ok(None);
|
||||||
ShErrKind::ParseErr,
|
|
||||||
"Invalid character in arithmetic substitution",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(tokens)
|
Ok(Some(tokens))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_rpn(tokens: Vec<ArithTk>) -> ShResult<Vec<ArithTk>> {
|
fn to_rpn(tokens: Vec<ArithTk>) -> ShResult<Vec<ArithTk>> {
|
||||||
@@ -733,6 +749,22 @@ impl ArithTk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ArithTk::Var(var) => {
|
||||||
|
let Some(val) = read_vars(|v| v.try_get_var(&var)) else {
|
||||||
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::NotFound,
|
||||||
|
format!("Undefined variable in arithmetic expression: '{}'",var.fg(next_color())),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let Ok(num) = val.parse::<f64>() else {
|
||||||
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::ParseErr,
|
||||||
|
format!("Variable '{}' does not contain a number", var.fg(next_color())),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
output.push(ArithTk::Num(num));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -812,14 +844,16 @@ impl FromStr for ArithOp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_arithmetic(raw: &str) -> ShResult<String> {
|
pub fn expand_arithmetic(raw: &str) -> ShResult<Option<String>> {
|
||||||
let body = raw.strip_prefix('(').unwrap().strip_suffix(')').unwrap(); // Unwraps are safe here, we already checked for the parens
|
let body = raw.strip_prefix('(').unwrap().strip_suffix(')').unwrap(); // Unwraps are safe here, we already checked for the parens
|
||||||
let unescaped = unescape_math(body);
|
let unescaped = unescape_math(body);
|
||||||
let expanded = expand_raw(&mut unescaped.chars().peekable())?;
|
let expanded = expand_raw(&mut unescaped.chars().peekable())?;
|
||||||
let tokens = ArithTk::tokenize(&expanded)?;
|
let Some(tokens) = ArithTk::tokenize(&expanded)? else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
let rpn = ArithTk::to_rpn(tokens)?;
|
let rpn = ArithTk::to_rpn(tokens)?;
|
||||||
let result = ArithTk::eval_rpn(rpn)?;
|
let result = ArithTk::eval_rpn(rpn)?;
|
||||||
Ok(result.to_string())
|
Ok(Some(result.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_proc_sub(raw: &str, is_input: bool) -> ShResult<String> {
|
pub fn expand_proc_sub(raw: &str, is_input: bool) -> ShResult<String> {
|
||||||
@@ -874,7 +908,7 @@ pub fn expand_proc_sub(raw: &str, is_input: bool) -> ShResult<String> {
|
|||||||
pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
|
pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
|
||||||
if raw.starts_with('(')
|
if raw.starts_with('(')
|
||||||
&& raw.ends_with(')')
|
&& raw.ends_with(')')
|
||||||
&& let Ok(output) = expand_arithmetic(raw)
|
&& let Some(output) = expand_arithmetic(raw)?
|
||||||
{
|
{
|
||||||
return Ok(output); // It's actually an arithmetic sub
|
return Ok(output); // It's actually an arithmetic sub
|
||||||
}
|
}
|
||||||
@@ -1583,11 +1617,19 @@ pub fn glob_to_regex(glob: &str, anchored: bool) -> Regex {
|
|||||||
if anchored {
|
if anchored {
|
||||||
regex.push('^');
|
regex.push('^');
|
||||||
}
|
}
|
||||||
for ch in glob.chars() {
|
let mut chars = glob.chars();
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
|
'\\' => {
|
||||||
|
// Shell escape: next char is literal
|
||||||
|
if let Some(esc) = chars.next() {
|
||||||
|
regex.push('\\');
|
||||||
|
regex.push(esc);
|
||||||
|
}
|
||||||
|
}
|
||||||
'*' => regex.push_str(".*"),
|
'*' => regex.push_str(".*"),
|
||||||
'?' => regex.push('.'),
|
'?' => regex.push('.'),
|
||||||
'.' | '+' | '(' | ')' | '|' | '^' | '$' | '[' | ']' | '{' | '}' | '\\' => {
|
'.' | '+' | '(' | ')' | '|' | '^' | '$' | '[' | ']' | '{' | '}' => {
|
||||||
regex.push('\\');
|
regex.push('\\');
|
||||||
regex.push(ch);
|
regex.push(ch);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ pub fn clear_color() {
|
|||||||
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;
|
||||||
|
fn promote_err(self, span: Span) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ShResultExt for Result<T, ShErr> {
|
impl<T> ShResultExt for Result<T, ShErr> {
|
||||||
@@ -97,6 +98,9 @@ impl<T> ShResultExt for Result<T, ShErr> {
|
|||||||
fn try_blame(self, new_span: Span) -> Self {
|
fn try_blame(self, new_span: Span) -> Self {
|
||||||
self.map_err(|e| e.try_blame(new_span))
|
self.map_err(|e| e.try_blame(new_span))
|
||||||
}
|
}
|
||||||
|
fn promote_err(self, span: Span) -> Self {
|
||||||
|
self.map_err(|e| e.promote(span))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -175,6 +179,12 @@ impl ShErr {
|
|||||||
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
||||||
Self { kind, src_span: None, labels: vec![], sources: vec![], notes: vec![msg.into()], io_guards: vec![] }
|
Self { kind, src_span: None, labels: vec![], sources: vec![], notes: vec![msg.into()], io_guards: vec![] }
|
||||||
}
|
}
|
||||||
|
pub fn promote(mut self, span: Span) -> Self {
|
||||||
|
if let Some(note) = self.notes.pop() {
|
||||||
|
self = self.labeled(span, note)
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
pub fn with_redirs(mut self, guard: RedirGuard) -> Self {
|
pub fn with_redirs(mut self, guard: RedirGuard) -> Self {
|
||||||
self.io_guards.push(guard);
|
self.io_guards.push(guard);
|
||||||
self
|
self
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use ariadne::Fmt;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::{
|
builtin::{
|
||||||
alias::{alias, unalias}, arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate}, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, intro, jobctl::{self, JobBehavior, continue_job, disown, jobs}, map, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak
|
alias::{alias, unalias}, arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate}, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, getopts::getopts, intro, jobctl::{self, JobBehavior, continue_job, disown, jobs}, map, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak
|
||||||
},
|
},
|
||||||
expand::{expand_aliases, glob_to_regex},
|
expand::{expand_aliases, glob_to_regex},
|
||||||
jobs::{ChildProc, JobStack, attach_tty, dispatch_job},
|
jobs::{ChildProc, JobStack, attach_tty, dispatch_job},
|
||||||
@@ -818,6 +818,7 @@ impl Dispatcher {
|
|||||||
"rotate" => arr_rotate(cmd),
|
"rotate" => arr_rotate(cmd),
|
||||||
"wait" => jobctl::wait(cmd),
|
"wait" => jobctl::wait(cmd),
|
||||||
"type" => intro::type_builtin(cmd),
|
"type" => intro::type_builtin(cmd),
|
||||||
|
"getopts" => getopts(cmd),
|
||||||
"true" | ":" => {
|
"true" | ":" => {
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -937,7 +938,6 @@ impl Dispatcher {
|
|||||||
match unsafe { fork()? } {
|
match unsafe { fork()? } {
|
||||||
ForkResult::Child => {
|
ForkResult::Child => {
|
||||||
let _ = setpgid(Pid::from_raw(0), existing_pgid.unwrap_or(Pid::from_raw(0)));
|
let _ = setpgid(Pid::from_raw(0), existing_pgid.unwrap_or(Pid::from_raw(0)));
|
||||||
crate::signal::reset_signals();
|
|
||||||
f(self);
|
f(self);
|
||||||
exit(state::get_status())
|
exit(state::get_status())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1061,11 +1061,13 @@ pub fn lookahead(pat: &str, mut chars: Chars) -> Option<usize> {
|
|||||||
pub fn case_pat_lookahead(mut chars: Peekable<Chars>) -> Option<usize> {
|
pub fn case_pat_lookahead(mut chars: Peekable<Chars>) -> Option<usize> {
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
pos += 1;
|
pos += ch.len_utf8();
|
||||||
match ch {
|
match ch {
|
||||||
_ if is_hard_sep(ch) => return None,
|
_ if is_hard_sep(ch) => return None,
|
||||||
'\\' => {
|
'\\' => {
|
||||||
chars.next();
|
if let Some(esc) = chars.next() {
|
||||||
|
pos += esc.len_utf8();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
')' => return Some(pos),
|
')' => return Some(pos),
|
||||||
'(' => return None,
|
'(' => return None,
|
||||||
|
|||||||
@@ -2897,7 +2897,7 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
Verb::Equalize => todo!(),
|
Verb::Equalize => todo!(),
|
||||||
Verb::InsertModeLineBreak(anchor) => {
|
Verb::InsertModeLineBreak(anchor) => {
|
||||||
let (mut start, end) = self.this_line();
|
let (mut start, end) = self.this_line_exclusive();
|
||||||
let auto_indent = read_shopts(|o| o.prompt.auto_indent);
|
let auto_indent = read_shopts(|o| o.prompt.auto_indent);
|
||||||
if start == 0 && end == self.cursor.max {
|
if start == 0 && end == self.cursor.max {
|
||||||
match anchor {
|
match anchor {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::libsh::sys::TTY_FILENO;
|
|||||||
use crate::parse::lex::{LexStream, QuoteState};
|
use crate::parse::lex::{LexStream, QuoteState};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::readline::term::{Pos, calc_str_width};
|
use crate::readline::term::{Pos, calc_str_width};
|
||||||
use crate::state::read_shopts;
|
use crate::state::{ShellParam, read_shopts};
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::error::ShResult,
|
libsh::error::ShResult,
|
||||||
parse::lex::{self, LexFlags, Tk, TkFlags, TkRule},
|
parse::lex::{self, LexFlags, Tk, TkFlags, TkRule},
|
||||||
@@ -987,8 +987,7 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
let mut insertions: Vec<(usize, Marker)> = vec![];
|
let mut insertions: Vec<(usize, Marker)> = vec![];
|
||||||
|
|
||||||
if token.class != TkRule::Str
|
if token.class != TkRule::Str
|
||||||
&& let Some(marker) = marker_for(&token.class)
|
&& let Some(marker) = marker_for(&token.class) {
|
||||||
{
|
|
||||||
insertions.push((token.span.range().end, markers::RESET));
|
insertions.push((token.span.range().end, markers::RESET));
|
||||||
insertions.push((token.span.range().start, marker));
|
insertions.push((token.span.range().start, marker));
|
||||||
return insertions;
|
return insertions;
|
||||||
@@ -1082,6 +1081,7 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
|| *br_ch == '='
|
|| *br_ch == '='
|
||||||
|| *br_ch == '/' // parameter expansion symbols
|
|| *br_ch == '/' // parameter expansion symbols
|
||||||
|| *br_ch == '?'
|
|| *br_ch == '?'
|
||||||
|
|| *br_ch == '$' // we're in some expansion like $foo$bar or ${foo$bar}
|
||||||
{
|
{
|
||||||
token_chars.next();
|
token_chars.next();
|
||||||
} else if *br_ch == '}' {
|
} else if *br_ch == '}' {
|
||||||
@@ -1100,7 +1100,9 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
let mut end_pos = dollar_pos + 1;
|
let mut end_pos = dollar_pos + 1;
|
||||||
// consume the var name
|
// consume the var name
|
||||||
while let Some((cur_i, var_ch)) = token_chars.peek() {
|
while let Some((cur_i, var_ch)) = token_chars.peek() {
|
||||||
if var_ch.is_ascii_alphanumeric() || *var_ch == '_' {
|
if var_ch.is_ascii_alphanumeric()
|
||||||
|
|| ShellParam::from_char(var_ch).is_some()
|
||||||
|
|| *var_ch == '_' {
|
||||||
end_pos = *cur_i + 1;
|
end_pos = *cur_i + 1;
|
||||||
token_chars.next();
|
token_chars.next();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
30
src/state.rs
30
src/state.rs
@@ -78,6 +78,19 @@ impl ShellParam {
|
|||||||
Self::Status | Self::ShPid | Self::LastJob | Self::ShellName
|
Self::Status | Self::ShPid | Self::LastJob | Self::ShellName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_char(c: &char) -> Option<Self> {
|
||||||
|
match c {
|
||||||
|
'?' => Some(Self::Status),
|
||||||
|
'$' => Some(Self::ShPid),
|
||||||
|
'!' => Some(Self::LastJob),
|
||||||
|
'0' => Some(Self::ShellName),
|
||||||
|
'@' => Some(Self::AllArgs),
|
||||||
|
'*' => Some(Self::AllArgsStr),
|
||||||
|
'#' => Some(Self::ArgCount),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ShellParam {
|
impl Display for ShellParam {
|
||||||
@@ -165,6 +178,9 @@ impl ScopeStack {
|
|||||||
pub fn cur_scope_mut(&mut self) -> &mut VarTab {
|
pub fn cur_scope_mut(&mut self) -> &mut VarTab {
|
||||||
self.scopes.last_mut().unwrap()
|
self.scopes.last_mut().unwrap()
|
||||||
}
|
}
|
||||||
|
pub fn sh_argv(&self) -> &VecDeque<String> {
|
||||||
|
self.cur_scope().sh_argv()
|
||||||
|
}
|
||||||
pub fn unset_var(&mut self, var_name: &str) -> ShResult<()> {
|
pub fn unset_var(&mut self, var_name: &str) -> ShResult<()> {
|
||||||
for scope in self.scopes.iter_mut().rev() {
|
for scope in self.scopes.iter_mut().rev() {
|
||||||
if scope.var_exists(var_name) {
|
if scope.var_exists(var_name) {
|
||||||
@@ -1075,6 +1091,8 @@ pub struct MetaTab {
|
|||||||
|
|
||||||
// pushd/popd stack
|
// pushd/popd stack
|
||||||
dir_stack: VecDeque<PathBuf>,
|
dir_stack: VecDeque<PathBuf>,
|
||||||
|
// getopts char offset for opts like -abc
|
||||||
|
getopts_offset: usize,
|
||||||
|
|
||||||
old_path: Option<String>,
|
old_path: Option<String>,
|
||||||
old_pwd: Option<String>,
|
old_pwd: Option<String>,
|
||||||
@@ -1083,6 +1101,7 @@ pub struct MetaTab {
|
|||||||
cwd_cache: HashSet<String>,
|
cwd_cache: HashSet<String>,
|
||||||
// programmable completion specs
|
// programmable completion specs
|
||||||
comp_specs: HashMap<String, Box<dyn CompSpec>>,
|
comp_specs: HashMap<String, Box<dyn CompSpec>>,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetaTab {
|
impl MetaTab {
|
||||||
@@ -1092,6 +1111,17 @@ impl MetaTab {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn getopts_char_offset(&self) -> usize {
|
||||||
|
self.getopts_offset
|
||||||
|
}
|
||||||
|
pub fn inc_getopts_char_offset(&mut self) -> usize {
|
||||||
|
let offset = self.getopts_offset;
|
||||||
|
self.getopts_offset += 1;
|
||||||
|
offset
|
||||||
|
}
|
||||||
|
pub fn reset_getopts_char_offset(&mut self) {
|
||||||
|
self.getopts_offset = 0;
|
||||||
|
}
|
||||||
pub fn get_builtin_comp_specs() -> HashMap<String, Box<dyn CompSpec>> {
|
pub fn get_builtin_comp_specs() -> HashMap<String, Box<dyn CompSpec>> {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user