Stuff stored in maps can be eval'd on access by storing with the -F flag
This commit is contained in:
@@ -136,7 +136,7 @@ bitflags! {
|
|||||||
pub struct CompOptFlags: u32 {
|
pub struct CompOptFlags: u32 {
|
||||||
const DEFAULT = 0b0000000001;
|
const DEFAULT = 0b0000000001;
|
||||||
const DIRNAMES = 0b0000000010;
|
const DIRNAMES = 0b0000000010;
|
||||||
const NOSPACE = 0b0000000100;
|
const SPACE = 0b0000000100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +282,7 @@ pub fn get_comp_opts(opts: Vec<Opt>) -> ShResult<CompOpts> {
|
|||||||
Opt::ShortWithArg('o', opt_flag) => match opt_flag.as_str() {
|
Opt::ShortWithArg('o', opt_flag) => match opt_flag.as_str() {
|
||||||
"default" => comp_opts.opt_flags |= CompOptFlags::DEFAULT,
|
"default" => comp_opts.opt_flags |= CompOptFlags::DEFAULT,
|
||||||
"dirnames" => comp_opts.opt_flags |= CompOptFlags::DIRNAMES,
|
"dirnames" => comp_opts.opt_flags |= CompOptFlags::DIRNAMES,
|
||||||
"nospace" => comp_opts.opt_flags |= CompOptFlags::NOSPACE,
|
"space" => comp_opts.opt_flags |= CompOptFlags::SPACE,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::full(
|
||||||
ShErrKind::InvalidOpt,
|
ShErrKind::InvalidOpt,
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ use nix::{libc::STDOUT_FILENO, unistd::write};
|
|||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
use crate::{
|
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}
|
expand::expand_cmd_sub, 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum MapNode {
|
pub enum MapNode {
|
||||||
Leaf(String),
|
DynamicLeaf(String), // eval'd on access
|
||||||
|
StaticLeaf(String), // static value
|
||||||
Array(Vec<MapNode>),
|
Array(Vec<MapNode>),
|
||||||
Branch(HashMap<String, MapNode>),
|
Branch(HashMap<String, MapNode>),
|
||||||
}
|
}
|
||||||
@@ -40,7 +41,7 @@ impl From<MapNode> for serde_json::Value {
|
|||||||
.collect();
|
.collect();
|
||||||
Value::Array(arr)
|
Value::Array(arr)
|
||||||
}
|
}
|
||||||
MapNode::Leaf(leaf) => {
|
MapNode::StaticLeaf(leaf) | MapNode::DynamicLeaf(leaf) => {
|
||||||
Value::String(leaf)
|
Value::String(leaf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,8 +67,8 @@ impl From<Value> for MapNode {
|
|||||||
.collect();
|
.collect();
|
||||||
MapNode::Array(nodes)
|
MapNode::Array(nodes)
|
||||||
}
|
}
|
||||||
Value::String(s) => MapNode::Leaf(s),
|
Value::String(s) => MapNode::StaticLeaf(s),
|
||||||
v => MapNode::Leaf(v.to_string())
|
v => MapNode::StaticLeaf(v.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +78,7 @@ impl MapNode {
|
|||||||
match path {
|
match path {
|
||||||
[] => Some(self),
|
[] => Some(self),
|
||||||
[key, rest @ ..] => match self {
|
[key, rest @ ..] => match self {
|
||||||
MapNode::Leaf(_) => None,
|
MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_) => None,
|
||||||
MapNode::Array(map_nodes) => {
|
MapNode::Array(map_nodes) => {
|
||||||
let idx: usize = key.parse().ok()?;
|
let idx: usize = key.parse().ok()?;
|
||||||
map_nodes.get(idx)?.get(rest)
|
map_nodes.get(idx)?.get(rest)
|
||||||
@@ -91,7 +92,7 @@ impl MapNode {
|
|||||||
match path {
|
match path {
|
||||||
[] => *self = value,
|
[] => *self = value,
|
||||||
[key, rest @ ..] => {
|
[key, rest @ ..] => {
|
||||||
if matches!(self, MapNode::Leaf(_)) {
|
if matches!(self, MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_)) {
|
||||||
// promote leaf to branch if we still have path left to traverse
|
// promote leaf to branch if we still have path left to traverse
|
||||||
*self = Self::default();
|
*self = Self::default();
|
||||||
}
|
}
|
||||||
@@ -147,7 +148,7 @@ impl MapNode {
|
|||||||
match self {
|
match self {
|
||||||
MapNode::Branch(map) => map.keys().map(|k| k.to_string()).collect(),
|
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::Array(nodes) => nodes.iter().filter_map(|n| n.display(false, false).ok()).collect(),
|
||||||
MapNode::Leaf(s) => vec![],
|
MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_) => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +174,8 @@ impl MapNode {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match self {
|
match self {
|
||||||
MapNode::Leaf(leaf) => Ok(leaf.clone()),
|
MapNode::StaticLeaf(leaf) => Ok(leaf.clone()),
|
||||||
|
MapNode::DynamicLeaf(cmd) => expand_cmd_sub(cmd),
|
||||||
MapNode::Array(nodes) => {
|
MapNode::Array(nodes) => {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
@@ -195,7 +197,7 @@ impl MapNode {
|
|||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
fn map_opts_spec() -> [OptSpec; 5] {
|
fn map_opts_spec() -> [OptSpec; 6] {
|
||||||
[
|
[
|
||||||
OptSpec {
|
OptSpec {
|
||||||
opt: Opt::Short('r'),
|
opt: Opt::Short('r'),
|
||||||
@@ -213,6 +215,10 @@ fn map_opts_spec() -> [OptSpec; 5] {
|
|||||||
opt: Opt::Long("pretty".into()),
|
opt: Opt::Long("pretty".into()),
|
||||||
takes_arg: false
|
takes_arg: false
|
||||||
},
|
},
|
||||||
|
OptSpec {
|
||||||
|
opt: Opt::Short('F'),
|
||||||
|
takes_arg: false
|
||||||
|
},
|
||||||
OptSpec {
|
OptSpec {
|
||||||
opt: Opt::Short('l'),
|
opt: Opt::Short('l'),
|
||||||
takes_arg: false
|
takes_arg: false
|
||||||
@@ -233,6 +239,7 @@ bitflags! {
|
|||||||
const JSON = 0b000100;
|
const JSON = 0b000100;
|
||||||
const LOCAL = 0b001000;
|
const LOCAL = 0b001000;
|
||||||
const PRETTY = 0b010000;
|
const PRETTY = 0b010000;
|
||||||
|
const FUNC = 0b100000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,16 +267,20 @@ pub fn map(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()
|
|||||||
};
|
};
|
||||||
|
|
||||||
let is_json = map_opts.flags.contains(MapFlags::JSON);
|
let is_json = map_opts.flags.contains(MapFlags::JSON);
|
||||||
|
let is_func = map_opts.flags.contains(MapFlags::FUNC);
|
||||||
|
let make_leaf = |s: String| {
|
||||||
|
if is_func { MapNode::DynamicLeaf(s) } else { MapNode::StaticLeaf(s) }
|
||||||
|
};
|
||||||
let found = write_vars(|v| {
|
let found = write_vars(|v| {
|
||||||
if let Some(map) = v.get_map_mut(name) {
|
if let Some(map) = v.get_map_mut(name) {
|
||||||
if is_json {
|
if is_json {
|
||||||
if let Ok(parsed) = serde_json::from_str::<Value>(&rhs) {
|
if let Ok(parsed) = serde_json::from_str::<Value>(&rhs) {
|
||||||
map.set(&path[1..], parsed.into());
|
map.set(&path[1..], parsed.into());
|
||||||
} else {
|
} else {
|
||||||
map.set(&path[1..], MapNode::Leaf(rhs.clone()));
|
map.set(&path[1..], make_leaf(rhs.clone()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
map.set(&path[1..], MapNode::Leaf(rhs.clone()));
|
map.set(&path[1..], make_leaf(rhs.clone()));
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -283,7 +294,7 @@ pub fn map(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()
|
|||||||
let node: MapNode = parsed.into();
|
let node: MapNode = parsed.into();
|
||||||
new.set(&path[1..], node);
|
new.set(&path[1..], node);
|
||||||
} else {
|
} else {
|
||||||
new.set(&path[1..], MapNode::Leaf(rhs));
|
new.set(&path[1..], make_leaf(rhs));
|
||||||
}
|
}
|
||||||
write_vars(|v| v.set_map(name, new, map_opts.flags.contains(MapFlags::LOCAL)));
|
write_vars(|v| v.set_map(name, new, map_opts.flags.contains(MapFlags::LOCAL)));
|
||||||
}
|
}
|
||||||
@@ -325,20 +336,18 @@ pub fn map(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()
|
|||||||
format!("map not found: {}", name)
|
format!("map not found: {}", name)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let Some(output) = read_vars(|v| {
|
let Some(node) = read_vars(|v| {
|
||||||
v.get_map(name)
|
v.get_map(name)
|
||||||
.and_then(|map| map.get(&path[1..])
|
.and_then(|map| map.get(&path[1..]).cloned())
|
||||||
.and_then(|n| {
|
|
||||||
if keys {
|
|
||||||
Some(n.keys().join(" "))
|
|
||||||
} else {
|
|
||||||
n.display(json, pretty).ok()
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}) else {
|
}) else {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
let output = if keys {
|
||||||
|
node.keys().join(" ")
|
||||||
|
} else {
|
||||||
|
node.display(json, pretty)?
|
||||||
|
};
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
write(stdout, output.as_bytes())?;
|
write(stdout, output.as_bytes())?;
|
||||||
@@ -362,6 +371,7 @@ pub fn get_map_opts(opts: Vec<Opt>) -> MapOpts {
|
|||||||
Opt::Short('k') => map_opts.flags |= MapFlags::KEYS,
|
Opt::Short('k') => map_opts.flags |= MapFlags::KEYS,
|
||||||
Opt::Short('l') => map_opts.flags |= MapFlags::LOCAL,
|
Opt::Short('l') => map_opts.flags |= MapFlags::LOCAL,
|
||||||
Opt::Long(ref s) if s == "pretty" => map_opts.flags |= MapFlags::PRETTY,
|
Opt::Long(ref s) if s == "pretty" => map_opts.flags |= MapFlags::PRETTY,
|
||||||
|
Opt::Short('F') => map_opts.flags |= MapFlags::FUNC,
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ pub enum CompSpecResult {
|
|||||||
NoSpec, // No compspec registered
|
NoSpec, // No compspec registered
|
||||||
NoMatch { flags: CompOptFlags }, /* Compspec found but no candidates matched, returns
|
NoMatch { flags: CompOptFlags }, /* Compspec found but no candidates matched, returns
|
||||||
* behavior flags */
|
* behavior flags */
|
||||||
Match(CompResult), // Compspec found and candidates returned
|
Match { result: CompResult, flags: CompOptFlags }, // Compspec found and candidates returned
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
@@ -433,6 +433,7 @@ impl CompSpec for BashCompSpec {
|
|||||||
if self.function.is_some() {
|
if self.function.is_some() {
|
||||||
candidates.extend(self.exec_comp_func(ctx)?);
|
candidates.extend(self.exec_comp_func(ctx)?);
|
||||||
}
|
}
|
||||||
|
candidates.sort_by_key(|c| c.len()); // sort by length to prioritize shorter completions, ties are then sorted alphabetically
|
||||||
|
|
||||||
Ok(candidates)
|
Ok(candidates)
|
||||||
}
|
}
|
||||||
@@ -511,7 +512,7 @@ pub struct Completer {
|
|||||||
pub token_span: (usize, usize),
|
pub token_span: (usize, usize),
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
pub dirs_only: bool,
|
pub dirs_only: bool,
|
||||||
pub no_space: bool,
|
pub add_space: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completer {
|
impl Completer {
|
||||||
@@ -612,7 +613,7 @@ impl Completer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_spaces(&mut self) {
|
pub fn add_spaces(&mut self) {
|
||||||
if !self.no_space {
|
if self.add_space {
|
||||||
self.candidates = std::mem::take(&mut self.candidates)
|
self.candidates = std::mem::take(&mut self.candidates)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
@@ -744,9 +745,10 @@ impl Completer {
|
|||||||
flags: spec.get_flags(),
|
flags: spec.get_flags(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(CompSpecResult::Match(CompResult::from_candidates(
|
Ok(CompSpecResult::Match {
|
||||||
candidates,
|
result: CompResult::from_candidates(candidates),
|
||||||
)))
|
flags: spec.get_flags(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -776,12 +778,15 @@ impl Completer {
|
|||||||
return Ok(CompResult::NoMatch);
|
return Ok(CompResult::NoMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
if flags.contains(CompOptFlags::NOSPACE) {
|
if flags.contains(CompOptFlags::SPACE) {
|
||||||
self.no_space = true;
|
self.add_space = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CompSpecResult::Match(comp_result) => {
|
CompSpecResult::Match { result, flags } => {
|
||||||
return Ok(comp_result);
|
if flags.contains(CompOptFlags::SPACE) {
|
||||||
|
self.add_space = true;
|
||||||
|
}
|
||||||
|
return Ok(result);
|
||||||
}
|
}
|
||||||
CompSpecResult::NoSpec => { /* carry on */ }
|
CompSpecResult::NoSpec => { /* carry on */ }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -780,27 +780,16 @@ impl AsFd for TermReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Layout {
|
pub struct Layout {
|
||||||
pub w_calc: Box<dyn WidthCalculator>,
|
|
||||||
pub prompt_end: Pos,
|
pub prompt_end: Pos,
|
||||||
pub cursor: Pos,
|
pub cursor: Pos,
|
||||||
pub end: Pos,
|
pub end: Pos,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Layout {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
writeln!(f, "Layout: ")?;
|
|
||||||
writeln!(f, "\tPrompt End: {:?}", self.prompt_end)?;
|
|
||||||
writeln!(f, "\tCursor: {:?}", self.cursor)?;
|
|
||||||
writeln!(f, "\tEnd: {:?}", self.end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Layout {
|
impl Layout {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let w_calc = width_calculator();
|
|
||||||
Self {
|
Self {
|
||||||
w_calc,
|
|
||||||
prompt_end: Pos::default(),
|
prompt_end: Pos::default(),
|
||||||
cursor: Pos::default(),
|
cursor: Pos::default(),
|
||||||
end: Pos::default(),
|
end: Pos::default(),
|
||||||
@@ -811,7 +800,6 @@ impl Layout {
|
|||||||
let cursor = Self::calc_pos(term_width, to_cursor, prompt_end, prompt_end.col);
|
let cursor = Self::calc_pos(term_width, to_cursor, prompt_end, prompt_end.col);
|
||||||
let end = Self::calc_pos(term_width, to_end, prompt_end, prompt_end.col);
|
let end = Self::calc_pos(term_width, to_end, prompt_end, prompt_end.col);
|
||||||
Layout {
|
Layout {
|
||||||
w_calc: width_calculator(),
|
|
||||||
prompt_end,
|
prompt_end,
|
||||||
cursor,
|
cursor,
|
||||||
end,
|
end,
|
||||||
|
|||||||
37
src/state.rs
37
src/state.rs
@@ -211,12 +211,18 @@ impl ScopeStack {
|
|||||||
flat_vars
|
flat_vars
|
||||||
}
|
}
|
||||||
pub fn set_var(&mut self, var_name: &str, val: VarKind, flags: VarFlags) -> ShResult<()> {
|
pub fn set_var(&mut self, var_name: &str, val: VarKind, flags: VarFlags) -> ShResult<()> {
|
||||||
let is_local = self.is_local_var(var_name);
|
if flags.contains(VarFlags::LOCAL) {
|
||||||
if flags.contains(VarFlags::LOCAL) || is_local {
|
return self.set_var_local(var_name, val, flags);
|
||||||
self.set_var_local(var_name, val, flags)
|
|
||||||
} else {
|
|
||||||
self.set_var_global(var_name, val, flags)
|
|
||||||
}
|
}
|
||||||
|
// Dynamic scoping: walk scopes from innermost to outermost,
|
||||||
|
// update the nearest scope that already has this variable
|
||||||
|
for scope in self.scopes.iter_mut().rev() {
|
||||||
|
if scope.var_exists(var_name) {
|
||||||
|
return scope.set_var(var_name, val, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Not found in any scope — create in global scope
|
||||||
|
self.set_var_global(var_name, val, flags)
|
||||||
}
|
}
|
||||||
pub fn set_var_indexed(
|
pub fn set_var_indexed(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -225,18 +231,23 @@ impl ScopeStack {
|
|||||||
val: String,
|
val: String,
|
||||||
flags: VarFlags,
|
flags: VarFlags,
|
||||||
) -> ShResult<()> {
|
) -> ShResult<()> {
|
||||||
let is_local = self.is_local_var(var_name);
|
if flags.contains(VarFlags::LOCAL) {
|
||||||
if flags.contains(VarFlags::LOCAL) || is_local {
|
|
||||||
let Some(scope) = self.scopes.last_mut() else {
|
let Some(scope) = self.scopes.last_mut() else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
scope.set_index(var_name, idx, val)
|
return scope.set_index(var_name, idx, val);
|
||||||
} else {
|
|
||||||
let Some(scope) = self.scopes.first_mut() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
scope.set_index(var_name, idx, val)
|
|
||||||
}
|
}
|
||||||
|
// Dynamic scoping: find nearest scope with this variable
|
||||||
|
for scope in self.scopes.iter_mut().rev() {
|
||||||
|
if scope.var_exists(var_name) {
|
||||||
|
return scope.set_index(var_name, idx, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Not found — create in global scope
|
||||||
|
let Some(scope) = self.scopes.first_mut() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
scope.set_index(var_name, idx, val)
|
||||||
}
|
}
|
||||||
fn set_var_global(&mut self, var_name: &str, val: VarKind, flags: VarFlags) -> ShResult<()> {
|
fn set_var_global(&mut self, var_name: &str, val: VarKind, flags: VarFlags) -> ShResult<()> {
|
||||||
let Some(scope) = self.scopes.first_mut() else {
|
let Some(scope) = self.scopes.first_mut() else {
|
||||||
|
|||||||
Reference in New Issue
Block a user