added 'map', 'pop', 'push', 'fpop', 'fpush', and 'rotate' builtins
This commit is contained in:
35
Cargo.lock
generated
35
Cargo.lock
generated
@@ -236,6 +236,12 @@ version = "1.70.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jiff"
|
name = "jiff"
|
||||||
version = "0.2.20"
|
version = "0.2.20"
|
||||||
@@ -399,6 +405,15 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "serde_core"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -419,6 +434,19 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.149"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shed"
|
name = "shed"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -432,6 +460,7 @@ dependencies = [
|
|||||||
"nix",
|
"nix",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"regex",
|
"regex",
|
||||||
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
@@ -616,3 +645,9 @@ name = "yansi"
|
|||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ glob = "0.3.2"
|
|||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
nix = { version = "0.29.0", features = ["uio", "term", "user", "hostname", "fs", "default", "signal", "process", "event", "ioctl", "poll"] }
|
nix = { version = "0.29.0", features = ["uio", "term", "user", "hostname", "fs", "default", "signal", "process", "event", "ioctl", "poll"] }
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
|
serde_json = "1.0.149"
|
||||||
unicode-segmentation = "1.12.0"
|
unicode-segmentation = "1.12.0"
|
||||||
unicode-width = "0.2.0"
|
unicode-width = "0.2.0"
|
||||||
vte = "0.15"
|
vte = "0.15"
|
||||||
|
|||||||
188
src/builtin/arrops.rs
Normal file
188
src/builtin/arrops.rs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
use crate::{
|
||||||
|
getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, procio::{IoStack, borrow_fd}, state::{self, VarFlags, VarKind, write_vars}
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::setup_builtin;
|
||||||
|
|
||||||
|
fn arr_op_optspec() -> Vec<OptSpec> {
|
||||||
|
vec![
|
||||||
|
OptSpec {
|
||||||
|
opt: Opt::Short('c'),
|
||||||
|
takes_arg: true
|
||||||
|
},
|
||||||
|
OptSpec {
|
||||||
|
opt: Opt::Short('r'),
|
||||||
|
takes_arg: false
|
||||||
|
},
|
||||||
|
OptSpec {
|
||||||
|
opt: Opt::Short('v'),
|
||||||
|
takes_arg: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ArrOpOpts {
|
||||||
|
count: usize,
|
||||||
|
reverse: bool,
|
||||||
|
var: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ArrOpOpts {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
count: 1,
|
||||||
|
reverse: false,
|
||||||
|
var: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum End { Front, Back }
|
||||||
|
|
||||||
|
fn arr_pop_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End) -> ShResult<()> {
|
||||||
|
let NdRule::Command {
|
||||||
|
assignments: _,
|
||||||
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||||
|
let arr_op_opts = get_arr_op_opts(opts)?;
|
||||||
|
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
let mut status = 0;
|
||||||
|
|
||||||
|
for (arg,_) in argv {
|
||||||
|
for _ in 0..arr_op_opts.count {
|
||||||
|
let pop = |arr: &mut std::collections::VecDeque<String>| match end {
|
||||||
|
End::Front => arr.pop_front(),
|
||||||
|
End::Back => arr.pop_back(),
|
||||||
|
};
|
||||||
|
let Some(popped) = write_vars(|v| v.get_arr_mut(&arg).ok().and_then(pop)) else {
|
||||||
|
status = 1;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
status = 0;
|
||||||
|
|
||||||
|
if let Some(ref var) = arr_op_opts.var {
|
||||||
|
write_vars(|v| v.set_var(var, VarKind::Str(popped), VarFlags::NONE))?;
|
||||||
|
} else {
|
||||||
|
write(stdout, popped.as_bytes())?;
|
||||||
|
write(stdout, b"\n")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state::set_status(status);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arr_push_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End) -> ShResult<()> {
|
||||||
|
let NdRule::Command {
|
||||||
|
assignments: _,
|
||||||
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||||
|
let _arr_op_opts = get_arr_op_opts(opts)?;
|
||||||
|
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
|
let mut argv = argv.into_iter();
|
||||||
|
let Some((name, _)) = argv.next() else {
|
||||||
|
return Err(ShErr::simple(ShErrKind::ExecFail, "push: missing array name".to_string()));
|
||||||
|
};
|
||||||
|
|
||||||
|
for (val, _) in argv {
|
||||||
|
let push_val = val.clone();
|
||||||
|
if let Err(e) = write_vars(|v| v.get_arr_mut(&name).map(|arr| match end {
|
||||||
|
End::Front => arr.push_front(push_val),
|
||||||
|
End::Back => arr.push_back(push_val),
|
||||||
|
})) {
|
||||||
|
state::set_status(1);
|
||||||
|
return Err(e);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
state::set_status(0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arr_pop(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
|
arr_pop_inner(node, io_stack, job, End::Back)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arr_fpop(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
|
arr_pop_inner(node, io_stack, job, End::Front)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arr_push(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
|
arr_push_inner(node, io_stack, job, End::Back)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arr_fpush(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
|
arr_push_inner(node, io_stack, job, End::Front)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arr_rotate(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
|
let NdRule::Command {
|
||||||
|
assignments: _,
|
||||||
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||||
|
let arr_op_opts = get_arr_op_opts(opts)?;
|
||||||
|
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
|
for (arg, _) in argv {
|
||||||
|
write_vars(|v| -> ShResult<()> {
|
||||||
|
let arr = v.get_arr_mut(&arg)?;
|
||||||
|
if arr_op_opts.reverse {
|
||||||
|
arr.rotate_right(arr_op_opts.count.min(arr.len()));
|
||||||
|
} else {
|
||||||
|
arr.rotate_left(arr_op_opts.count.min(arr.len()));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
state::set_status(0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_arr_op_opts(opts: Vec<Opt>) -> ShResult<ArrOpOpts> {
|
||||||
|
let mut arr_op_opts = ArrOpOpts::default();
|
||||||
|
for opt in opts {
|
||||||
|
match opt {
|
||||||
|
Opt::ShortWithArg('c', count) => {
|
||||||
|
arr_op_opts.count = count.parse::<usize>().map_err(|_| {
|
||||||
|
ShErr::simple(ShErrKind::ParseErr, format!("invalid count: {}", count))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Opt::Short('c') => {
|
||||||
|
return Err(ShErr::simple(ShErrKind::ParseErr, "missing count for -c".to_string()));
|
||||||
|
}
|
||||||
|
Opt::Short('r') => {
|
||||||
|
arr_op_opts.reverse = true;
|
||||||
|
}
|
||||||
|
Opt::ShortWithArg('v', var) => {
|
||||||
|
arr_op_opts.var = Some(var);
|
||||||
|
}
|
||||||
|
Opt::Short('v') => {
|
||||||
|
return Err(ShErr::simple(ShErrKind::ParseErr, "missing variable name for -v".to_string()));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ShErr::simple(ShErrKind::ParseErr, format!("invalid option: {}", opt)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(arr_op_opts)
|
||||||
|
}
|
||||||
369
src/builtin/map.rs
Normal file
369
src/builtin/map.rs
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use bitflags::bitflags;
|
||||||
|
use nix::{libc::STDOUT_FILENO, unistd::write};
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node, lex::{split_all_unescaped, split_at_unescaped}}, procio::{IoStack, borrow_fd}, state::{self, read_vars, write_vars}
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum MapNode {
|
||||||
|
Leaf(String),
|
||||||
|
Array(Vec<MapNode>),
|
||||||
|
Branch(HashMap<String, MapNode>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MapNode {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Branch(HashMap::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MapNode> for serde_json::Value {
|
||||||
|
fn from(val: MapNode) -> Self {
|
||||||
|
match val {
|
||||||
|
MapNode::Branch(map) => {
|
||||||
|
let val_map = map.into_iter()
|
||||||
|
.map(|(k,v)| {
|
||||||
|
(k,v.into())
|
||||||
|
})
|
||||||
|
.collect::<Map<String,Value>>();
|
||||||
|
|
||||||
|
Value::Object(val_map)
|
||||||
|
}
|
||||||
|
MapNode::Array(nodes) => {
|
||||||
|
let arr = nodes
|
||||||
|
.into_iter()
|
||||||
|
.map(|node| node.into())
|
||||||
|
.collect();
|
||||||
|
Value::Array(arr)
|
||||||
|
}
|
||||||
|
MapNode::Leaf(leaf) => {
|
||||||
|
Value::String(leaf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Value> for MapNode {
|
||||||
|
fn from(value: Value) -> Self {
|
||||||
|
match value {
|
||||||
|
Value::Object(map) => {
|
||||||
|
let node_map = map.into_iter()
|
||||||
|
.map(|(k,v)| {
|
||||||
|
(k, v.into())
|
||||||
|
})
|
||||||
|
.collect::<HashMap<String, MapNode>>();
|
||||||
|
|
||||||
|
MapNode::Branch(node_map)
|
||||||
|
}
|
||||||
|
Value::Array(arr) => {
|
||||||
|
let nodes = arr
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| v.into())
|
||||||
|
.collect();
|
||||||
|
MapNode::Array(nodes)
|
||||||
|
}
|
||||||
|
Value::String(s) => MapNode::Leaf(s),
|
||||||
|
v => MapNode::Leaf(v.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MapNode {
|
||||||
|
fn get(&self, path: &[String]) -> Option<&MapNode> {
|
||||||
|
match path {
|
||||||
|
[] => Some(self),
|
||||||
|
[key, rest @ ..] => match self {
|
||||||
|
MapNode::Leaf(_) => None,
|
||||||
|
MapNode::Array(map_nodes) => {
|
||||||
|
let idx: usize = key.parse().ok()?;
|
||||||
|
map_nodes.get(idx)?.get(rest)
|
||||||
|
}
|
||||||
|
MapNode::Branch(map) => map.get(key)?.get(rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&mut self, path: &[String], value: MapNode) {
|
||||||
|
match path {
|
||||||
|
[] => *self = value,
|
||||||
|
[key, rest @ ..] => {
|
||||||
|
if matches!(self, MapNode::Leaf(_)) {
|
||||||
|
// promote leaf to branch if we still have path left to traverse
|
||||||
|
*self = Self::default();
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
MapNode::Branch(map) => {
|
||||||
|
let child = map
|
||||||
|
.entry(key.to_string())
|
||||||
|
.or_insert_with(Self::default);
|
||||||
|
child.set(rest, value);
|
||||||
|
}
|
||||||
|
MapNode::Array(map_nodes) => {
|
||||||
|
let idx: usize = key.parse().expect("expected array index");
|
||||||
|
if idx >= map_nodes.len() {
|
||||||
|
map_nodes.resize(idx + 1, Self::default());
|
||||||
|
}
|
||||||
|
map_nodes[idx].set(rest, value);
|
||||||
|
}
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, path: &[String]) -> Option<MapNode> {
|
||||||
|
match path {
|
||||||
|
[] => None,
|
||||||
|
[key] => match self {
|
||||||
|
MapNode::Branch(map) => map.remove(key),
|
||||||
|
MapNode::Array(nodes) => {
|
||||||
|
let idx: usize = key.parse().ok()?;
|
||||||
|
if idx >= nodes.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(nodes.remove(idx))
|
||||||
|
}
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
[key, rest @ ..] => match self {
|
||||||
|
MapNode::Branch(map) => map.get_mut(key)?.remove(rest),
|
||||||
|
MapNode::Array(nodes) => {
|
||||||
|
let idx: usize = key.parse().ok()?;
|
||||||
|
if idx >= nodes.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
nodes[idx].remove(rest)
|
||||||
|
}
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keys(&self) -> Vec<String> {
|
||||||
|
match self {
|
||||||
|
MapNode::Branch(map) => map.keys().map(|k| k.to_string()).collect(),
|
||||||
|
MapNode::Array(nodes) => nodes.iter().filter_map(|n| n.display(false, false).ok()).collect(),
|
||||||
|
MapNode::Leaf(s) => vec![s.clone()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display(&self, json: bool, pretty: bool) -> ShResult<String> {
|
||||||
|
if json || matches!(self, MapNode::Branch(_)) {
|
||||||
|
let val: Value = self.clone().into();
|
||||||
|
if pretty {
|
||||||
|
match serde_json::to_string_pretty(&val) {
|
||||||
|
Ok(s) => Ok(s),
|
||||||
|
Err(e) => Err(ShErr::simple(
|
||||||
|
ShErrKind::InternalErr,
|
||||||
|
format!("failed to serialize map: {e}")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match serde_json::to_string(&val) {
|
||||||
|
Ok(s) => Ok(s),
|
||||||
|
Err(e) => Err(ShErr::simple(
|
||||||
|
ShErrKind::InternalErr,
|
||||||
|
format!("failed to serialize map: {e}")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match self {
|
||||||
|
MapNode::Leaf(leaf) => Ok(leaf.clone()),
|
||||||
|
MapNode::Array(nodes) => {
|
||||||
|
let mut s = String::new();
|
||||||
|
for node in nodes {
|
||||||
|
let display = node.display(json, pretty)?;
|
||||||
|
if matches!(node, MapNode::Branch(_)) {
|
||||||
|
s.push_str(&format!("'{}'", display));
|
||||||
|
} else {
|
||||||
|
s.push_str(&node.display(json, pretty)?);
|
||||||
|
}
|
||||||
|
s.push('\n');
|
||||||
|
}
|
||||||
|
Ok(s.trim_end_matches('\n').to_string())
|
||||||
|
}
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use super::setup_builtin;
|
||||||
|
|
||||||
|
fn map_opts_spec() -> [OptSpec; 5] {
|
||||||
|
[
|
||||||
|
OptSpec {
|
||||||
|
opt: Opt::Short('r'),
|
||||||
|
takes_arg: false
|
||||||
|
},
|
||||||
|
OptSpec {
|
||||||
|
opt: Opt::Short('j'),
|
||||||
|
takes_arg: false
|
||||||
|
},
|
||||||
|
OptSpec {
|
||||||
|
opt: Opt::Short('k'),
|
||||||
|
takes_arg: false
|
||||||
|
},
|
||||||
|
OptSpec {
|
||||||
|
opt: Opt::Long("pretty".into()),
|
||||||
|
takes_arg: false
|
||||||
|
},
|
||||||
|
OptSpec {
|
||||||
|
opt: Opt::Short('l'),
|
||||||
|
takes_arg: false
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct MapOpts {
|
||||||
|
flags: MapFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct MapFlags: u32 {
|
||||||
|
const REMOVE = 0b000001;
|
||||||
|
const KEYS = 0b000010;
|
||||||
|
const JSON = 0b000100;
|
||||||
|
const LOCAL = 0b001000;
|
||||||
|
const PRETTY = 0b010000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
|
let NdRule::Command {
|
||||||
|
assignments: _,
|
||||||
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (argv, opts) = get_opts_from_tokens(argv, &map_opts_spec())?;
|
||||||
|
let map_opts = get_map_opts(opts);
|
||||||
|
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
|
for (arg,_) in argv {
|
||||||
|
if let Some((lhs,rhs)) = split_at_unescaped(&arg, "=") {
|
||||||
|
let path = split_all_unescaped(&lhs, ".");
|
||||||
|
let Some(name) = path.first() else {
|
||||||
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::InternalErr,
|
||||||
|
format!("invalid map path: {}", lhs)
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_json = map_opts.flags.contains(MapFlags::JSON);
|
||||||
|
let found = write_vars(|v| {
|
||||||
|
if let Some(map) = v.get_map_mut(name) {
|
||||||
|
if is_json {
|
||||||
|
if let Ok(parsed) = serde_json::from_str::<Value>(&rhs) {
|
||||||
|
map.set(&path[1..], parsed.into());
|
||||||
|
} else {
|
||||||
|
map.set(&path[1..], MapNode::Leaf(rhs.clone()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
map.set(&path[1..], MapNode::Leaf(rhs.clone()));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
let mut new = MapNode::default();
|
||||||
|
if is_json && let Ok(parsed) = serde_json::from_str::<Value>(&rhs) {
|
||||||
|
let node: MapNode = parsed.into();
|
||||||
|
new.set(&path[1..], node);
|
||||||
|
} else {
|
||||||
|
new.set(&path[1..], MapNode::Leaf(rhs));
|
||||||
|
}
|
||||||
|
write_vars(|v| v.set_map(name, new, map_opts.flags.contains(MapFlags::LOCAL)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let path = split_all_unescaped(&arg, ".");
|
||||||
|
let Some(name) = path.first() else {
|
||||||
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::InternalErr,
|
||||||
|
format!("invalid map path: {}", &arg)
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
if map_opts.flags.contains(MapFlags::REMOVE) {
|
||||||
|
write_vars(|v| {
|
||||||
|
if path.len() == 1 {
|
||||||
|
v.remove_map(name);
|
||||||
|
} else {
|
||||||
|
let Some(map) = v.get_map_mut(name) else {
|
||||||
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
format!("map not found: {}", name)
|
||||||
|
));
|
||||||
|
};
|
||||||
|
map.remove(&path[1..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = map_opts.flags.contains(MapFlags::JSON);
|
||||||
|
let pretty = map_opts.flags.contains(MapFlags::PRETTY);
|
||||||
|
let keys = map_opts.flags.contains(MapFlags::KEYS);
|
||||||
|
let has_map = read_vars(|v| v.get_map(name).is_some());
|
||||||
|
if !has_map {
|
||||||
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
format!("map not found: {}", name)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let Some(output) = read_vars(|v| {
|
||||||
|
v.get_map(name)
|
||||||
|
.and_then(|map| map.get(&path[1..])
|
||||||
|
.and_then(|n| {
|
||||||
|
if keys {
|
||||||
|
Some(n.keys().join(" "))
|
||||||
|
} else {
|
||||||
|
n.display(json, pretty).ok()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}) else {
|
||||||
|
state::set_status(1);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
write(stdout, output.as_bytes())?;
|
||||||
|
write(stdout, b"\n")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state::set_status(0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_map_opts(opts: Vec<Opt>) -> MapOpts {
|
||||||
|
let mut map_opts = MapOpts {
|
||||||
|
flags: MapFlags::empty()
|
||||||
|
};
|
||||||
|
|
||||||
|
for opt in opts {
|
||||||
|
match opt {
|
||||||
|
Opt::Short('r') => map_opts.flags |= MapFlags::REMOVE,
|
||||||
|
Opt::Short('j') => map_opts.flags |= MapFlags::JSON,
|
||||||
|
Opt::Short('k') => map_opts.flags |= MapFlags::KEYS,
|
||||||
|
Opt::Short('l') => map_opts.flags |= MapFlags::LOCAL,
|
||||||
|
Opt::Long(ref s) if s == "pretty" => map_opts.flags |= MapFlags::PRETTY,
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map_opts
|
||||||
|
}
|
||||||
@@ -30,12 +30,14 @@ pub mod test; // [[ ]] thing
|
|||||||
pub mod trap;
|
pub mod trap;
|
||||||
pub mod varcmds;
|
pub mod varcmds;
|
||||||
pub mod zoltraak;
|
pub mod zoltraak;
|
||||||
|
pub mod map;
|
||||||
|
pub mod arrops;
|
||||||
|
|
||||||
pub const BUILTINS: [&str; 35] = [
|
pub const BUILTINS: [&str; 41] = [
|
||||||
"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",
|
"unset", "complete", "compgen", "map", "pop", "fpop", "push", "fpush", "rotate"
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Sets up a builtin command
|
/// Sets up a builtin command
|
||||||
|
|||||||
@@ -5,23 +5,7 @@ use std::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::{
|
builtin::{
|
||||||
alias::{alias, unalias},
|
alias::{alias, unalias}, arrops::{arr_pop, arr_fpop, arr_push, arr_fpush, arr_rotate}, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, jobctl::{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
|
||||||
cd::cd,
|
|
||||||
complete::{compgen_builtin, complete_builtin},
|
|
||||||
dirstack::{dirs, popd, pushd},
|
|
||||||
echo::echo,
|
|
||||||
eval, exec,
|
|
||||||
flowctl::flowctl,
|
|
||||||
jobctl::{JobBehavior, continue_job, disown, jobs},
|
|
||||||
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, dispatch_job},
|
jobs::{ChildProc, JobStack, dispatch_job},
|
||||||
@@ -817,6 +801,12 @@ impl Dispatcher {
|
|||||||
"unset" => unset(cmd, io_stack_mut, curr_job_mut),
|
"unset" => unset(cmd, io_stack_mut, curr_job_mut),
|
||||||
"complete" => complete_builtin(cmd, io_stack_mut, curr_job_mut),
|
"complete" => complete_builtin(cmd, io_stack_mut, curr_job_mut),
|
||||||
"compgen" => compgen_builtin(cmd, io_stack_mut, curr_job_mut),
|
"compgen" => compgen_builtin(cmd, io_stack_mut, curr_job_mut),
|
||||||
|
"map" => map::map(cmd, io_stack_mut, curr_job_mut),
|
||||||
|
"pop" => arr_pop(cmd, io_stack_mut, curr_job_mut),
|
||||||
|
"fpop" => arr_fpop(cmd, io_stack_mut, curr_job_mut),
|
||||||
|
"push" => arr_push(cmd, io_stack_mut, curr_job_mut),
|
||||||
|
"fpush" => arr_fpush(cmd, io_stack_mut, curr_job_mut),
|
||||||
|
"rotate" => arr_rotate(cmd, io_stack_mut, curr_job_mut),
|
||||||
"true" | ":" => {
|
"true" | ":" => {
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -912,6 +912,35 @@ pub fn ends_with_unescaped(slice: &str, pat: &str) -> bool {
|
|||||||
slice.ends_with(pat) && !pos_is_escaped(slice, slice.len() - pat.len())
|
slice.ends_with(pat) && !pos_is_escaped(slice, slice.len() - pat.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn split_all_unescaped(slice: &str, pat: &str) -> Vec<String> {
|
||||||
|
let mut cursor = 0;
|
||||||
|
let mut splits = vec![];
|
||||||
|
while let Some(split) = split_at_unescaped(&slice[cursor..], pat) {
|
||||||
|
cursor += split.0.len() + pat.len();
|
||||||
|
splits.push(split.0);
|
||||||
|
}
|
||||||
|
if let Some(remaining) = slice.get(cursor..) {
|
||||||
|
splits.push(remaining.to_string());
|
||||||
|
}
|
||||||
|
splits
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_at_unescaped(slice: &str, pat: &str) -> Option<(String,String)> {
|
||||||
|
let mut window_start = 0;
|
||||||
|
let mut window_end = pat.len();
|
||||||
|
if window_end > slice.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
while window_end <= slice.len() {
|
||||||
|
if &slice[window_start..window_end] == pat && !pos_is_escaped(slice, window_start) {
|
||||||
|
return Some((slice[..window_start].to_string(), slice[window_end..].to_string()));
|
||||||
|
}
|
||||||
|
window_start += 1;
|
||||||
|
window_end += 1;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pos_is_escaped(slice: &str, pos: usize) -> bool {
|
pub fn pos_is_escaped(slice: &str, pos: usize) -> bool {
|
||||||
let bytes = slice.as_bytes();
|
let bytes = slice.as_bytes();
|
||||||
let mut escaped = false;
|
let mut escaped = false;
|
||||||
|
|||||||
@@ -955,6 +955,7 @@ impl ParseStream {
|
|||||||
};
|
};
|
||||||
case_blocks.push(case_node);
|
case_blocks.push(case_node);
|
||||||
|
|
||||||
|
self.catch_separator(&mut node_tks);
|
||||||
if self.check_keyword("esac") {
|
if self.check_keyword("esac") {
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
self.assert_separator(&mut node_tks)?;
|
self.assert_separator(&mut node_tks)?;
|
||||||
@@ -1057,6 +1058,7 @@ impl ParseStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.catch_separator(&mut node_tks);
|
||||||
if !self.check_keyword("fi") || !self.next_tk_is_some() {
|
if !self.check_keyword("fi") || !self.next_tk_is_some() {
|
||||||
self.panic_mode(&mut node_tks);
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
@@ -1139,6 +1141,7 @@ impl ParseStream {
|
|||||||
body.push(node)
|
body.push(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.catch_separator(&mut node_tks);
|
||||||
if !self.check_keyword("done") || !self.next_tk_is_some() {
|
if !self.check_keyword("done") || !self.next_tk_is_some() {
|
||||||
self.panic_mode(&mut node_tks);
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
@@ -1210,6 +1213,7 @@ impl ParseStream {
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.catch_separator(&mut node_tks);
|
||||||
if !self.check_keyword("done") || !self.next_tk_is_some() {
|
if !self.check_keyword("done") || !self.next_tk_is_some() {
|
||||||
self.panic_mode(&mut node_tks);
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
|
|||||||
@@ -1947,13 +1947,23 @@ impl LineBuf {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let mut level: usize = 0;
|
let mut level: usize = 0;
|
||||||
|
let mut last_keyword: Option<String> = None;
|
||||||
for tk in tokens {
|
for tk in tokens {
|
||||||
if tk.flags.contains(TkFlags::KEYWORD) {
|
if tk.flags.contains(TkFlags::KEYWORD) {
|
||||||
match tk.as_str() {
|
match tk.as_str() {
|
||||||
"then" | "do" | "in" => level += 1,
|
"in" => {
|
||||||
|
if last_keyword.as_deref() == Some("case") {
|
||||||
|
level += 1;
|
||||||
|
} else {
|
||||||
|
// 'in' is also used in for loops, but we already increment level on 'do' for those
|
||||||
|
// so we just skip it here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"then" | "do" => level += 1,
|
||||||
"done" | "fi" | "esac" => level = level.saturating_sub(1),
|
"done" | "fi" | "esac" => level = level.saturating_sub(1),
|
||||||
_ => { /* Continue */ }
|
_ => { /* Continue */ }
|
||||||
}
|
}
|
||||||
|
last_keyword = Some(tk.to_string());
|
||||||
} else if tk.class == TkRule::BraceGrpStart {
|
} else if tk.class == TkRule::BraceGrpStart {
|
||||||
level += 1;
|
level += 1;
|
||||||
} else if tk.class == TkRule::BraceGrpEnd {
|
} else if tk.class == TkRule::BraceGrpEnd {
|
||||||
|
|||||||
110
src/state.rs
110
src/state.rs
@@ -11,7 +11,7 @@ use std::{
|
|||||||
use nix::unistd::{User, gethostname, getppid};
|
use nix::unistd::{User, gethostname, getppid};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::{BUILTINS, trap::TrapTarget},
|
builtin::{BUILTINS, map::MapNode, trap::TrapTarget},
|
||||||
exec_input,
|
exec_input,
|
||||||
jobs::JobTab,
|
jobs::JobTab,
|
||||||
libsh::{
|
libsh::{
|
||||||
@@ -257,11 +257,7 @@ impl ScopeStack {
|
|||||||
{
|
{
|
||||||
match var.kind() {
|
match var.kind() {
|
||||||
VarKind::Arr(items) => {
|
VarKind::Arr(items) => {
|
||||||
let mut item_vec = items.clone().into_iter().collect::<Vec<(usize, String)>>();
|
return Ok(items.iter().cloned().collect());
|
||||||
|
|
||||||
item_vec.sort_by_key(|(idx, _)| *idx); // sort by index
|
|
||||||
|
|
||||||
return Ok(item_vec.into_iter().map(|(_, s)| s).collect());
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
@@ -277,6 +273,25 @@ impl ScopeStack {
|
|||||||
format!("Variable '{}' not found", var_name),
|
format!("Variable '{}' not found", var_name),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
pub fn get_arr_mut(&mut self, var_name: &str) -> ShResult<&mut VecDeque<String>> {
|
||||||
|
for scope in self.scopes.iter_mut().rev() {
|
||||||
|
if scope.var_exists(var_name)
|
||||||
|
&& let Some(var) = scope.vars_mut().get_mut(var_name)
|
||||||
|
{
|
||||||
|
match var.kind_mut() {
|
||||||
|
VarKind::Arr(items) => return Ok(items),
|
||||||
|
_ => return Err(ShErr::simple(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
format!("Variable '{}' is not an array", var_name),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ShErr::simple(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
format!("Variable '{}' not found", var_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
pub fn index_var(&self, var_name: &str, idx: ArrIndex) -> ShResult<String> {
|
pub fn index_var(&self, var_name: &str, idx: ArrIndex) -> ShResult<String> {
|
||||||
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)
|
||||||
@@ -304,7 +319,7 @@ impl ScopeStack {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(item) = items.get(&idx) {
|
if let Some(item) = items.get(idx) {
|
||||||
return Ok(item.clone());
|
return Ok(item.clone());
|
||||||
} else {
|
} else {
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
@@ -324,6 +339,38 @@ impl ScopeStack {
|
|||||||
}
|
}
|
||||||
Ok("".into())
|
Ok("".into())
|
||||||
}
|
}
|
||||||
|
pub fn remove_map(&mut self, map_name: &str) -> Option<MapNode> {
|
||||||
|
for scope in self.scopes.iter_mut().rev() {
|
||||||
|
if scope.get_map(map_name).is_some() {
|
||||||
|
return scope.remove_map(map_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
pub fn get_map(&self, map_name: &str) -> Option<&MapNode> {
|
||||||
|
for scope in self.scopes.iter().rev() {
|
||||||
|
if let Some(map) = scope.get_map(map_name) {
|
||||||
|
return Some(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
pub fn get_map_mut(&mut self, map_name: &str) -> Option<&mut MapNode> {
|
||||||
|
for scope in self.scopes.iter_mut().rev() {
|
||||||
|
if let Some(map) = scope.get_map_mut(map_name) {
|
||||||
|
return Some(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
pub fn set_map(&mut self, map_name: &str, map: MapNode, local: bool) {
|
||||||
|
if local
|
||||||
|
&& let Some(scope) = self.scopes.last_mut() {
|
||||||
|
scope.set_map(map_name, map);
|
||||||
|
} else if let Some(scope) = self.scopes.first_mut() {
|
||||||
|
scope.set_map(map_name, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn try_get_var(&self, var_name: &str) -> Option<String> {
|
pub fn try_get_var(&self, var_name: &str) -> Option<String> {
|
||||||
// This version of get_var() is mainly used internally
|
// This version of get_var() is mainly used internally
|
||||||
// so that we have access to Option methods
|
// so that we have access to Option methods
|
||||||
@@ -588,18 +635,11 @@ impl FromStr for ArrIndex {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hashmap_to_vec(map: HashMap<usize, String>) -> Vec<String> {
|
|
||||||
let mut items = map.into_iter().collect::<Vec<(usize, String)>>();
|
|
||||||
items.sort_by_key(|(idx, _)| *idx);
|
|
||||||
|
|
||||||
items.into_iter().map(|(_, i)| i).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum VarKind {
|
pub enum VarKind {
|
||||||
Str(String),
|
Str(String),
|
||||||
Int(i32),
|
Int(i32),
|
||||||
Arr(HashMap<usize, String>),
|
Arr(VecDeque<String>),
|
||||||
AssocArr(Vec<(String, String)>),
|
AssocArr(Vec<(String, String)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,7 +654,7 @@ impl VarKind {
|
|||||||
}
|
}
|
||||||
let raw = raw[1..raw.len() - 1].to_string();
|
let raw = raw[1..raw.len() - 1].to_string();
|
||||||
|
|
||||||
let tokens: HashMap<usize, String> = LexStream::new(Arc::new(raw), LexFlags::empty())
|
let tokens: VecDeque<String> = LexStream::new(Arc::new(raw), LexFlags::empty())
|
||||||
.map(|tk| tk.and_then(|tk| tk.expand()).map(|tk| tk.get_words()))
|
.map(|tk| tk.and_then(|tk| tk.expand()).map(|tk| tk.get_words()))
|
||||||
.try_fold(vec![], |mut acc, wrds| {
|
.try_fold(vec![], |mut acc, wrds| {
|
||||||
match wrds {
|
match wrds {
|
||||||
@@ -624,16 +664,13 @@ impl VarKind {
|
|||||||
Ok(acc)
|
Ok(acc)
|
||||||
})?
|
})?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(Self::Arr(tokens))
|
Ok(Self::Arr(tokens))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arr_from_vec(vec: Vec<String>) -> Self {
|
pub fn arr_from_vec(vec: Vec<String>) -> Self {
|
||||||
let tokens: HashMap<usize, String> = vec.into_iter().enumerate().collect();
|
Self::Arr(VecDeque::from(vec))
|
||||||
|
|
||||||
Self::Arr(tokens)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,7 +680,6 @@ impl Display for VarKind {
|
|||||||
VarKind::Str(s) => write!(f, "{s}"),
|
VarKind::Str(s) => write!(f, "{s}"),
|
||||||
VarKind::Int(i) => write!(f, "{i}"),
|
VarKind::Int(i) => write!(f, "{i}"),
|
||||||
VarKind::Arr(items) => {
|
VarKind::Arr(items) => {
|
||||||
let items = hashmap_to_vec(items.clone());
|
|
||||||
let mut item_iter = items.iter().peekable();
|
let mut item_iter = items.iter().peekable();
|
||||||
while let Some(item) = item_iter.next() {
|
while let Some(item) = item_iter.next() {
|
||||||
write!(f, "{item}")?;
|
write!(f, "{item}")?;
|
||||||
@@ -702,8 +738,9 @@ impl Display for Var {
|
|||||||
pub struct VarTab {
|
pub struct VarTab {
|
||||||
vars: HashMap<String, Var>,
|
vars: HashMap<String, Var>,
|
||||||
params: HashMap<ShellParam, String>,
|
params: HashMap<ShellParam, String>,
|
||||||
sh_argv: VecDeque<String>, /* Using a VecDeque makes the implementation of `shift`
|
sh_argv: VecDeque<String>, /* Using a VecDeque makes the implementation of `shift` straightforward */
|
||||||
* straightforward */
|
|
||||||
|
maps: HashMap<String, MapNode>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VarTab {
|
impl VarTab {
|
||||||
@@ -715,6 +752,7 @@ impl VarTab {
|
|||||||
vars,
|
vars,
|
||||||
params,
|
params,
|
||||||
sh_argv: VecDeque::new(),
|
sh_argv: VecDeque::new(),
|
||||||
|
maps: HashMap::new(),
|
||||||
};
|
};
|
||||||
var_tab.init_sh_argv();
|
var_tab.init_sh_argv();
|
||||||
var_tab
|
var_tab
|
||||||
@@ -833,6 +871,24 @@ impl VarTab {
|
|||||||
self.update_arg_params();
|
self.update_arg_params();
|
||||||
arg
|
arg
|
||||||
}
|
}
|
||||||
|
pub fn set_map(&mut self, map_name: &str, map: MapNode) {
|
||||||
|
self.maps.insert(map_name.to_string(), map);
|
||||||
|
}
|
||||||
|
pub fn remove_map(&mut self, map_name: &str) -> Option<MapNode> {
|
||||||
|
self.maps.remove(map_name)
|
||||||
|
}
|
||||||
|
pub fn get_map(&self, map_name: &str) -> Option<&MapNode> {
|
||||||
|
self.maps.get(map_name)
|
||||||
|
}
|
||||||
|
pub fn get_map_mut(&mut self, map_name: &str) -> Option<&mut MapNode> {
|
||||||
|
self.maps.get_mut(map_name)
|
||||||
|
}
|
||||||
|
pub fn maps(&self) -> &HashMap<String, MapNode> {
|
||||||
|
&self.maps
|
||||||
|
}
|
||||||
|
pub fn maps_mut(&mut self) -> &mut HashMap<String, MapNode> {
|
||||||
|
&mut self.maps
|
||||||
|
}
|
||||||
pub fn vars(&self) -> &HashMap<String, Var> {
|
pub fn vars(&self) -> &HashMap<String, Var> {
|
||||||
&self.vars
|
&self.vars
|
||||||
}
|
}
|
||||||
@@ -906,7 +962,10 @@ impl VarTab {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
items.insert(idx, val);
|
if idx >= items.len() {
|
||||||
|
items.resize(idx + 1, String::new());
|
||||||
|
}
|
||||||
|
items[idx] = val;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@@ -945,6 +1004,9 @@ impl VarTab {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
pub fn map_exists(&self, map_name: &str) -> bool {
|
||||||
|
self.maps.contains_key(map_name)
|
||||||
|
}
|
||||||
pub fn var_exists(&self, var_name: &str) -> bool {
|
pub fn var_exists(&self, var_name: &str) -> bool {
|
||||||
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);
|
||||||
|
|||||||
Reference in New Issue
Block a user