mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 16:15:20 +02:00
Add into cell-path
for dynamic cell-path creation (#11322)
# Description The `cell-path` is a type that can be created statically with `$.nested.structure.5`, but can't be created from user input. This makes it difficult to take advantage of commands that accept a cell-path to operate on data structures. This PR adds `into cell-path` for dynamic cell-path creation. `into cell-path` accepts the following input shapes: * Bare integer (equivalent to `$.1`) * List of strings and integers * List of records with entries `value` and `optional` * String (parsed into a cell-path) ## Example usage An example of where `into cell-path` can be used is in working with `git config --list`. The git configuration has a tree structure that maps well to nushell records. With dynamic cell paths it is easy to convert `git config list` to a record: ```nushell git config --list | lines | parse -r '^(?<key>[^=]+)=(?<value>.*)' | reduce --fold {} {|entry, result| let path = $entry.key | into cell-path $result | upsert $path {|| $entry.value } } | select remote ``` Output: ``` ╭────────┬──────────────────────────────────────────────────────────────────╮ │ │ ╭──────────┬───────────────────────────────────────────────────╮ │ │ remote │ │ │ ╭───────┬───────────────────────────────────────╮ │ │ │ │ │ upstream │ │ url │ git@github.com:nushell/nushell.git │ │ │ │ │ │ │ │ fetch │ +refs/heads/*:refs/remotes/upstream/* │ │ │ │ │ │ │ ╰───────┴───────────────────────────────────────╯ │ │ │ │ │ │ ╭───────┬─────────────────────────────────────╮ │ │ │ │ │ origin │ │ url │ git@github.com:drbrain/nushell │ │ │ │ │ │ │ │ fetch │ +refs/heads/*:refs/remotes/origin/* │ │ │ │ │ │ │ ╰───────┴─────────────────────────────────────╯ │ │ │ │ ╰──────────┴───────────────────────────────────────────────────╯ │ ╰────────┴──────────────────────────────────────────────────────────────────╯ ``` ## Errors `lex()` + `parse_cell_path()` are forgiving about what is allowed in a cell-path so it will allow what appears to be nonsense to become a cell-path: ```nushell let table = [["!@$%^&*" value]; [key value]] $table | get ("!@$%^&*.0" | into cell-path) # => key ``` But it will reject bad cell-paths: ``` ❯ "a b" | into cell-path Error: nu:🐚:cant_convert × Can't convert to cell-path. ╭─[entry #14:1:1] 1 │ "a b" | into cell-path · ───────┬────── · ╰── can't convert string to cell-path ╰──── help: "a b" is not a valid cell-path (Parse mismatch during operation.) ``` # User-Facing Changes New conversion command `into cell-path` # Tests + Formatting - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib` # After Submitting Automatic documentation updates
This commit is contained in:
@ -1,9 +1,9 @@
|
||||
use super::Expression;
|
||||
use crate::Span;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::{cmp::Ordering, fmt::Display};
|
||||
|
||||
#[derive(Debug, Clone, PartialOrd, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum PathMember {
|
||||
String {
|
||||
val: String,
|
||||
@ -17,6 +17,51 @@ pub enum PathMember {
|
||||
},
|
||||
}
|
||||
|
||||
impl PathMember {
|
||||
pub fn int(val: usize, optional: bool, span: Span) -> Self {
|
||||
PathMember::Int {
|
||||
val,
|
||||
span,
|
||||
optional,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn string(val: String, optional: bool, span: Span) -> Self {
|
||||
PathMember::String {
|
||||
val,
|
||||
span,
|
||||
optional,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_int(val: usize, optional: bool) -> Self {
|
||||
PathMember::Int {
|
||||
val,
|
||||
optional,
|
||||
span: Span::test_data(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_string(val: String, optional: bool) -> Self {
|
||||
PathMember::String {
|
||||
val,
|
||||
optional,
|
||||
span: Span::test_data(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_optional(&mut self) {
|
||||
match self {
|
||||
PathMember::String {
|
||||
ref mut optional, ..
|
||||
} => *optional = true,
|
||||
PathMember::Int {
|
||||
ref mut optional, ..
|
||||
} => *optional = true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for PathMember {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
@ -49,6 +94,55 @@ impl PartialEq for PathMember {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for PathMember {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
match (self, other) {
|
||||
(
|
||||
PathMember::String {
|
||||
val: l_val,
|
||||
optional: l_opt,
|
||||
..
|
||||
},
|
||||
PathMember::String {
|
||||
val: r_val,
|
||||
optional: r_opt,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
let val_ord = Some(l_val.cmp(r_val));
|
||||
|
||||
if let Some(Ordering::Equal) = val_ord {
|
||||
Some(l_opt.cmp(r_opt))
|
||||
} else {
|
||||
val_ord
|
||||
}
|
||||
}
|
||||
(
|
||||
PathMember::Int {
|
||||
val: l_val,
|
||||
optional: l_opt,
|
||||
..
|
||||
},
|
||||
PathMember::Int {
|
||||
val: r_val,
|
||||
optional: r_opt,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
let val_ord = Some(l_val.cmp(r_val));
|
||||
|
||||
if let Some(Ordering::Equal) = val_ord {
|
||||
Some(l_opt.cmp(r_opt))
|
||||
} else {
|
||||
val_ord
|
||||
}
|
||||
}
|
||||
(PathMember::Int { .. }, PathMember::String { .. }) => Some(Ordering::Greater),
|
||||
(PathMember::String { .. }, PathMember::Int { .. }) => Some(Ordering::Less),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
pub struct CellPath {
|
||||
pub members: Vec<PathMember>,
|
||||
@ -57,14 +151,7 @@ pub struct CellPath {
|
||||
impl CellPath {
|
||||
pub fn make_optional(&mut self) {
|
||||
for member in &mut self.members {
|
||||
match member {
|
||||
PathMember::String {
|
||||
ref mut optional, ..
|
||||
} => *optional = true,
|
||||
PathMember::Int {
|
||||
ref mut optional, ..
|
||||
} => *optional = true,
|
||||
}
|
||||
member.make_optional();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -89,3 +176,39 @@ pub struct FullCellPath {
|
||||
pub head: Expression,
|
||||
pub tail: Vec<PathMember>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::cmp::Ordering::Greater;
|
||||
|
||||
#[test]
|
||||
fn path_member_partial_ord() {
|
||||
assert_eq!(
|
||||
Some(Greater),
|
||||
PathMember::test_int(5, true).partial_cmp(&PathMember::test_string("e".into(), true))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Some(Greater),
|
||||
PathMember::test_int(5, true).partial_cmp(&PathMember::test_int(5, false))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Some(Greater),
|
||||
PathMember::test_int(6, true).partial_cmp(&PathMember::test_int(5, true))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Some(Greater),
|
||||
PathMember::test_string("e".into(), true)
|
||||
.partial_cmp(&PathMember::test_string("e".into(), false))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Some(Greater),
|
||||
PathMember::test_string("f".into(), true)
|
||||
.partial_cmp(&PathMember::test_string("e".into(), true))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user