use super::{config_update_string_enum, prelude::*};
use crate as nu_protocol;
#[derive(Clone, Copy, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
pub enum TableMode {
Basic,
Thin,
Light,
Compact,
WithLove,
CompactDouble,
#[default]
Rounded,
Reinforced,
Heavy,
None,
Psql,
Markdown,
Dots,
Restructured,
AsciiRounded,
BasicCompact,
}
impl FromStr for TableMode {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"basic" => Ok(Self::Basic),
"thin" => Ok(Self::Thin),
"light" => Ok(Self::Light),
"compact" => Ok(Self::Compact),
"with_love" => Ok(Self::WithLove),
"compact_double" => Ok(Self::CompactDouble),
"default" => Ok(TableMode::default()),
"rounded" => Ok(Self::Rounded),
"reinforced" => Ok(Self::Reinforced),
"heavy" => Ok(Self::Heavy),
"none" => Ok(Self::None),
"psql" => Ok(Self::Psql),
"markdown" => Ok(Self::Markdown),
"dots" => Ok(Self::Dots),
"restructured" => Ok(Self::Restructured),
"ascii_rounded" => Ok(Self::AsciiRounded),
"basic_compact" => Ok(Self::BasicCompact),
_ => Err("'basic', 'thin', 'light', 'compact', 'with_love', 'compact_double', 'rounded', 'reinforced', 'heavy', 'none', 'psql', 'markdown', 'dots', 'restructured', 'ascii_rounded', or 'basic_compact'"),
}
}
}
impl UpdateFromValue for TableMode {
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
config_update_string_enum(self, value, path, errors)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum FooterMode {
Never,
Always,
RowCount(u64),
Auto,
}
impl FromStr for FooterMode {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"always" => Ok(FooterMode::Always),
"never" => Ok(FooterMode::Never),
"auto" => Ok(FooterMode::Auto),
_ => Err("'never', 'always', 'auto', or int"),
}
}
}
impl UpdateFromValue for FooterMode {
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
match value {
Value::String { val, .. } => match val.parse() {
Ok(val) => *self = val,
Err(err) => errors.invalid_value(path, err.to_string(), value),
},
&Value::Int { val, .. } => {
if val >= 0 {
*self = Self::RowCount(val as u64);
} else {
errors.invalid_value(path, "a non-negative integer", value);
}
}
_ => errors.type_mismatch(
path,
Type::custom("'never', 'always', 'auto', or int"),
value,
),
}
}
}
impl IntoValue for FooterMode {
fn into_value(self, span: Span) -> Value {
match self {
FooterMode::Always => "always".into_value(span),
FooterMode::Never => "never".into_value(span),
FooterMode::Auto => "auto".into_value(span),
FooterMode::RowCount(c) => (c as i64).into_value(span),
}
}
}
#[derive(Clone, Copy, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
pub enum TableIndexMode {
Always,
Never,
Auto,
}
impl FromStr for TableIndexMode {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"always" => Ok(TableIndexMode::Always),
"never" => Ok(TableIndexMode::Never),
"auto" => Ok(TableIndexMode::Auto),
_ => Err("'never', 'always' or 'auto'"),
}
}
}
impl UpdateFromValue for TableIndexMode {
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
config_update_string_enum(self, value, path, errors)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TrimStrategy {
Wrap {
try_to_keep_words: bool,
},
Truncate {
suffix: Option<String>,
},
}
impl TrimStrategy {
pub fn wrap(dont_split_words: bool) -> Self {
Self::Wrap {
try_to_keep_words: dont_split_words,
}
}
pub fn truncate(suffix: Option<String>) -> Self {
Self::Truncate { suffix }
}
}
impl Default for TrimStrategy {
fn default() -> Self {
Self::Wrap {
try_to_keep_words: true,
}
}
}
impl IntoValue for TrimStrategy {
fn into_value(self, span: Span) -> Value {
match self {
TrimStrategy::Wrap { try_to_keep_words } => {
record! {
"methodology" => "wrapping".into_value(span),
"wrapping_try_keep_words" => try_to_keep_words.into_value(span),
}
}
TrimStrategy::Truncate { suffix } => {
record! {
"methodology" => "truncating".into_value(span),
"truncating_suffix" => suffix.into_value(span),
}
}
}
.into_value(span)
}
}
impl UpdateFromValue for TrimStrategy {
fn update<'a>(
&mut self,
value: &'a Value,
path: &mut ConfigPath<'a>,
errors: &mut ConfigErrors,
) {
let Value::Record { val: record, .. } = value else {
errors.type_mismatch(path, Type::record(), value);
return;
};
let Some(methodology) = record.get("methodology") else {
errors.missing_column(path, "methodology", value.span());
return;
};
match methodology.as_str() {
Ok("wrapping") => {
let mut try_to_keep_words = if let &mut Self::Wrap { try_to_keep_words } = self {
try_to_keep_words
} else {
false
};
for (col, val) in record.iter() {
let path = &mut path.push(col);
match col.as_str() {
"wrapping_try_keep_words" => try_to_keep_words.update(val, path, errors),
"methodology" | "truncating_suffix" => (),
_ => errors.unknown_option(path, val),
}
}
*self = Self::Wrap { try_to_keep_words };
}
Ok("truncating") => {
let mut suffix = if let Self::Truncate { suffix } = self {
suffix.take()
} else {
None
};
for (col, val) in record.iter() {
let path = &mut path.push(col);
match col.as_str() {
"truncating_suffix" => match val {
Value::Nothing { .. } => suffix = None,
Value::String { val, .. } => suffix = Some(val.clone()),
_ => errors.type_mismatch(path, Type::String, val),
},
"methodology" | "wrapping_try_keep_words" => (),
_ => errors.unknown_option(path, val),
}
}
*self = Self::Truncate { suffix };
}
Ok(_) => errors.invalid_value(
&path.push("methodology"),
"'wrapping' or 'truncating'",
methodology,
),
Err(_) => errors.type_mismatch(&path.push("methodology"), Type::String, methodology),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TableIndent {
pub left: usize,
pub right: usize,
}
impl IntoValue for TableIndent {
fn into_value(self, span: Span) -> Value {
record! {
"left" => (self.left as i64).into_value(span),
"right" => (self.right as i64).into_value(span),
}
.into_value(span)
}
}
impl Default for TableIndent {
fn default() -> Self {
Self { left: 1, right: 1 }
}
}
impl UpdateFromValue for TableIndent {
fn update<'a>(
&mut self,
value: &'a Value,
path: &mut ConfigPath<'a>,
errors: &mut ConfigErrors,
) {
match value {
&Value::Int { val, .. } => {
if let Ok(val) = val.try_into() {
self.left = val;
self.right = val;
} else {
errors.invalid_value(path, "a non-negative integer", value);
}
}
Value::Record { val: record, .. } => {
for (col, val) in record.iter() {
let path = &mut path.push(col);
match col.as_str() {
"left" => self.left.update(val, path, errors),
"right" => self.right.update(val, path, errors),
_ => errors.unknown_option(path, val),
}
}
}
_ => errors.type_mismatch(path, Type::custom("int or record"), value),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TableConfig {
pub mode: TableMode,
pub index_mode: TableIndexMode,
pub show_empty: bool,
pub padding: TableIndent,
pub trim: TrimStrategy,
pub header_on_separator: bool,
pub abbreviated_row_count: Option<usize>,
pub footer_inheritance: bool,
}
impl IntoValue for TableConfig {
fn into_value(self, span: Span) -> Value {
let abbv_count = self
.abbreviated_row_count
.map(|t| t as i64)
.into_value(span);
record! {
"mode" => self.mode.into_value(span),
"index_mode" => self.index_mode.into_value(span),
"show_empty" => self.show_empty.into_value(span),
"padding" => self.padding.into_value(span),
"trim" => self.trim.into_value(span),
"header_on_separator" => self.header_on_separator.into_value(span),
"abbreviated_row_count" => abbv_count,
"footer_inheritance" => self.footer_inheritance.into_value(span),
}
.into_value(span)
}
}
impl Default for TableConfig {
fn default() -> Self {
Self {
mode: TableMode::Rounded,
index_mode: TableIndexMode::Always,
show_empty: true,
trim: TrimStrategy::default(),
header_on_separator: false,
padding: TableIndent::default(),
abbreviated_row_count: None,
footer_inheritance: false,
}
}
}
impl UpdateFromValue for TableConfig {
fn update<'a>(
&mut self,
value: &'a Value,
path: &mut ConfigPath<'a>,
errors: &mut ConfigErrors,
) {
let Value::Record { val: record, .. } = value else {
errors.type_mismatch(path, Type::record(), value);
return;
};
for (col, val) in record.iter() {
let path = &mut path.push(col);
match col.as_str() {
"mode" => self.mode.update(val, path, errors),
"index_mode" => self.index_mode.update(val, path, errors),
"show_empty" => self.show_empty.update(val, path, errors),
"trim" => self.trim.update(val, path, errors),
"header_on_separator" => self.header_on_separator.update(val, path, errors),
"padding" => self.padding.update(val, path, errors),
"abbreviated_row_count" => match val {
Value::Nothing { .. } => self.abbreviated_row_count = None,
&Value::Int { val: count, .. } => {
if let Ok(count) = count.try_into() {
self.abbreviated_row_count = Some(count);
} else {
errors.invalid_value(path, "a non-negative integer", val);
}
}
_ => errors.type_mismatch(path, Type::custom("int or nothing"), val),
},
"footer_inheritance" => self.footer_inheritance.update(val, path, errors),
_ => errors.unknown_option(path, val),
}
}
}
}