mirror of
https://github.com/nushell/nushell.git
synced 2025-05-08 20:14:26 +02:00
feat(record): Add CasedRecord, Record wrapper that affects case sensitivity
Make case sensitivity explicit in various places.
This commit is contained in:
parent
ea31bd3918
commit
e8b6783529
@ -31,7 +31,7 @@ use fancy_regex::Regex;
|
|||||||
use nu_utils::{
|
use nu_utils::{
|
||||||
contains_emoji,
|
contains_emoji,
|
||||||
locale::{get_system_locale_string, LOCALE_OVERRIDE_ENV_VAR},
|
locale::{get_system_locale_string, LOCALE_OVERRIDE_ENV_VAR},
|
||||||
IgnoreCaseExt, SharedCow,
|
SharedCow,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
@ -1182,6 +1182,7 @@ impl Value {
|
|||||||
PathMember::String {
|
PathMember::String {
|
||||||
val: col_name,
|
val: col_name,
|
||||||
span,
|
span,
|
||||||
|
insensitive,
|
||||||
..
|
..
|
||||||
} => match self {
|
} => match self {
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
@ -1189,7 +1190,9 @@ impl Value {
|
|||||||
match val {
|
match val {
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
let record = record.to_mut();
|
let record = record.to_mut();
|
||||||
if let Some(val) = record.get_mut(col_name) {
|
if let Some(val) =
|
||||||
|
record.cased_mut(*insensitive).get_mut(col_name)
|
||||||
|
{
|
||||||
val.upsert_data_at_cell_path(path, new_val.clone())?;
|
val.upsert_data_at_cell_path(path, new_val.clone())?;
|
||||||
} else {
|
} else {
|
||||||
let new_col =
|
let new_col =
|
||||||
@ -1210,7 +1213,7 @@ impl Value {
|
|||||||
}
|
}
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
let record = record.to_mut();
|
let record = record.to_mut();
|
||||||
if let Some(val) = record.get_mut(col_name) {
|
if let Some(val) = record.cased_mut(*insensitive).get_mut(col_name) {
|
||||||
val.upsert_data_at_cell_path(path, new_val)?;
|
val.upsert_data_at_cell_path(path, new_val)?;
|
||||||
} else {
|
} else {
|
||||||
let new_col = Value::with_data_at_cell_path(path, new_val.clone())?;
|
let new_col = Value::with_data_at_cell_path(path, new_val.clone())?;
|
||||||
@ -1282,6 +1285,7 @@ impl Value {
|
|||||||
PathMember::String {
|
PathMember::String {
|
||||||
val: col_name,
|
val: col_name,
|
||||||
span,
|
span,
|
||||||
|
insensitive,
|
||||||
..
|
..
|
||||||
} => match self {
|
} => match self {
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
@ -1289,7 +1293,9 @@ impl Value {
|
|||||||
let v_span = val.span();
|
let v_span = val.span();
|
||||||
match val {
|
match val {
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
if let Some(val) =
|
||||||
|
record.to_mut().cased_mut(*insensitive).get_mut(col_name)
|
||||||
|
{
|
||||||
val.update_data_at_cell_path(path, new_val.clone())?;
|
val.update_data_at_cell_path(path, new_val.clone())?;
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
@ -1311,7 +1317,8 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
if let Some(val) = record.to_mut().cased_mut(*insensitive).get_mut(col_name)
|
||||||
|
{
|
||||||
val.update_data_at_cell_path(path, new_val)?;
|
val.update_data_at_cell_path(path, new_val)?;
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
@ -1377,7 +1384,11 @@ impl Value {
|
|||||||
let v_span = val.span();
|
let v_span = val.span();
|
||||||
match val {
|
match val {
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
if record.to_mut().remove(col_name).is_none() && !optional {
|
let value = record
|
||||||
|
.to_mut()
|
||||||
|
.cased_mut(*insensitive)
|
||||||
|
.remove(col_name);
|
||||||
|
if value.is_none() && !optional {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: Some(*span),
|
span: Some(*span),
|
||||||
@ -1397,7 +1408,13 @@ impl Value {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
if record.to_mut().remove(col_name).is_none() && !optional {
|
if record
|
||||||
|
.to_mut()
|
||||||
|
.cased_mut(*insensitive)
|
||||||
|
.remove(col_name)
|
||||||
|
.is_none()
|
||||||
|
&& !optional
|
||||||
|
{
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: Some(*span),
|
span: Some(*span),
|
||||||
@ -1453,7 +1470,11 @@ impl Value {
|
|||||||
let v_span = val.span();
|
let v_span = val.span();
|
||||||
match val {
|
match val {
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
let val = record
|
||||||
|
.to_mut()
|
||||||
|
.cased_mut(*insensitive)
|
||||||
|
.get_mut(col_name);
|
||||||
|
if let Some(val) = val {
|
||||||
val.remove_data_at_cell_path(path)?;
|
val.remove_data_at_cell_path(path)?;
|
||||||
} else if !optional {
|
} else if !optional {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
@ -1475,7 +1496,9 @@ impl Value {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
if let Some(val) =
|
||||||
|
record.to_mut().cased_mut(*insensitive).get_mut(col_name)
|
||||||
|
{
|
||||||
val.remove_data_at_cell_path(path)?;
|
val.remove_data_at_cell_path(path)?;
|
||||||
} else if !optional {
|
} else if !optional {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
@ -1532,6 +1555,7 @@ impl Value {
|
|||||||
PathMember::String {
|
PathMember::String {
|
||||||
val: col_name,
|
val: col_name,
|
||||||
span,
|
span,
|
||||||
|
insensitive,
|
||||||
..
|
..
|
||||||
} => match self {
|
} => match self {
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
@ -1540,7 +1564,9 @@ impl Value {
|
|||||||
match val {
|
match val {
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
let record = record.to_mut();
|
let record = record.to_mut();
|
||||||
if let Some(val) = record.get_mut(col_name) {
|
if let Some(val) =
|
||||||
|
record.cased_mut(*insensitive).get_mut(col_name)
|
||||||
|
{
|
||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
return Err(ShellError::ColumnAlreadyExists {
|
return Err(ShellError::ColumnAlreadyExists {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
@ -1574,7 +1600,7 @@ impl Value {
|
|||||||
}
|
}
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
let record = record.to_mut();
|
let record = record.to_mut();
|
||||||
if let Some(val) = record.get_mut(col_name) {
|
if let Some(val) = record.cased_mut(*insensitive).get_mut(col_name) {
|
||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
return Err(ShellError::ColumnAlreadyExists {
|
return Err(ShellError::ColumnAlreadyExists {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
@ -2095,14 +2121,9 @@ fn get_value_member<'a>(
|
|||||||
let span = current.span();
|
let span = current.span();
|
||||||
match current {
|
match current {
|
||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
if let Some(found) = val.iter().rev().find(|x| {
|
let found = val.cased(*insensitive).get(column_name);
|
||||||
if *insensitive {
|
if let Some(found) = found {
|
||||||
x.0.eq_ignore_case(column_name)
|
Ok(ControlFlow::Continue(Cow::Borrowed(found)))
|
||||||
} else {
|
|
||||||
x.0 == column_name
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Ok(ControlFlow::Continue(Cow::Borrowed(found.1)))
|
|
||||||
} else if *optional {
|
} else if *optional {
|
||||||
Ok(ControlFlow::Break(*origin_span))
|
Ok(ControlFlow::Break(*origin_span))
|
||||||
// short-circuit
|
// short-circuit
|
||||||
@ -2129,14 +2150,9 @@ fn get_value_member<'a>(
|
|||||||
let val_span = val.span();
|
let val_span = val.span();
|
||||||
match val {
|
match val {
|
||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
if let Some(found) = val.iter().rev().find(|x| {
|
let found = val.cased(*insensitive).get(column_name);
|
||||||
if *insensitive {
|
if let Some(found) = found {
|
||||||
x.0.eq_ignore_case(column_name)
|
Ok(found.clone())
|
||||||
} else {
|
|
||||||
x.0 == column_name
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Ok(found.1.clone())
|
|
||||||
} else if *optional {
|
} else if *optional {
|
||||||
Ok(Value::nothing(*origin_span))
|
Ok(Value::nothing(*origin_span))
|
||||||
} else if let Some(suggestion) =
|
} else if let Some(suggestion) =
|
||||||
|
@ -3,6 +3,7 @@ use std::{iter::FusedIterator, ops::RangeBounds};
|
|||||||
|
|
||||||
use crate::{ShellError, Span, Value};
|
use crate::{ShellError, Span, Value};
|
||||||
|
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
use serde::{de::Visitor, ser::SerializeMap, Deserialize, Serialize};
|
use serde::{de::Visitor, ser::SerializeMap, Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
@ -10,6 +11,60 @@ pub struct Record {
|
|||||||
inner: Vec<(String, Value)>,
|
inner: Vec<(String, Value)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct CasedRecord<R> {
|
||||||
|
record: R,
|
||||||
|
insensitive: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> CasedRecord<R> {
|
||||||
|
fn cmp(&self, lhs: &str, rhs: &str) -> bool {
|
||||||
|
if self.insensitive {
|
||||||
|
lhs.eq_ignore_case(rhs)
|
||||||
|
} else {
|
||||||
|
lhs == rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CasedRecord<&'a Record> {
|
||||||
|
pub fn contains(&self, col: impl AsRef<str>) -> bool {
|
||||||
|
self.record.columns().any(|k| self.cmp(k, col.as_ref()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index_of(&self, col: impl AsRef<str>) -> Option<usize> {
|
||||||
|
self.record
|
||||||
|
.columns()
|
||||||
|
.rposition(|k| self.cmp(k, col.as_ref()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self, col: impl AsRef<str>) -> Option<&'a Value> {
|
||||||
|
let idx = self.index_of(col)?;
|
||||||
|
let (_, value) = self.record.get_index(idx)?;
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CasedRecord<&'a mut Record> {
|
||||||
|
fn shared(&'a self) -> CasedRecord<&'a Record> {
|
||||||
|
CasedRecord {
|
||||||
|
record: &*self.record,
|
||||||
|
insensitive: self.insensitive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(self, col: impl AsRef<str>) -> Option<&'a mut Value> {
|
||||||
|
let idx = self.shared().index_of(col)?;
|
||||||
|
let (_, value) = self.record.get_index_mut(idx)?;
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, col: impl AsRef<str>) -> Option<Value> {
|
||||||
|
let idx = self.shared().index_of(col)?;
|
||||||
|
let (_, val) = self.record.inner.remove(idx);
|
||||||
|
Some(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Record {
|
impl Record {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
@ -21,6 +76,20 @@ impl Record {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cased(&self, insensitive: bool) -> CasedRecord<&Record> {
|
||||||
|
CasedRecord {
|
||||||
|
record: self,
|
||||||
|
insensitive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cased_mut(&mut self, insensitive: bool) -> CasedRecord<&mut Record> {
|
||||||
|
CasedRecord {
|
||||||
|
record: self,
|
||||||
|
insensitive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a [`Record`] from a `Vec` of columns and a `Vec` of [`Value`]s
|
/// Create a [`Record`] from a `Vec` of columns and a `Vec` of [`Value`]s
|
||||||
///
|
///
|
||||||
/// Returns an error if `cols` and `vals` have different lengths.
|
/// Returns an error if `cols` and `vals` have different lengths.
|
||||||
@ -108,6 +177,12 @@ impl Record {
|
|||||||
self.inner.get(idx).map(|(col, val): &(_, _)| (col, val))
|
self.inner.get(idx).map(|(col, val): &(_, _)| (col, val))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_index_mut(&mut self, idx: usize) -> Option<(&mut String, &mut Value)> {
|
||||||
|
self.inner
|
||||||
|
.get_mut(idx)
|
||||||
|
.map(|(col, val): &mut (_, _)| (col, val))
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove single value by key
|
/// Remove single value by key
|
||||||
///
|
///
|
||||||
/// Returns `None` if key not found
|
/// Returns `None` if key not found
|
||||||
|
Loading…
Reference in New Issue
Block a user