1use super::{config_update_string_enum, prelude::*};
2use crate as nu_protocol;
3
4#[derive(Clone, Copy, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
5pub enum TableMode {
6 Basic,
7 Thin,
8 Light,
9 Compact,
10 WithLove,
11 CompactDouble,
12 #[default]
13 Rounded,
14 Reinforced,
15 Heavy,
16 None,
17 Psql,
18 Markdown,
19 Dots,
20 Restructured,
21 AsciiRounded,
22 BasicCompact,
23 Single,
24 Double,
25}
26
27impl FromStr for TableMode {
28 type Err = &'static str;
29
30 fn from_str(s: &str) -> Result<Self, Self::Err> {
31 match s.to_ascii_lowercase().as_str() {
32 "basic" => Ok(Self::Basic),
33 "thin" => Ok(Self::Thin),
34 "light" => Ok(Self::Light),
35 "compact" => Ok(Self::Compact),
36 "with_love" => Ok(Self::WithLove),
37 "compact_double" => Ok(Self::CompactDouble),
38 "default" => Ok(TableMode::default()),
39 "rounded" => Ok(Self::Rounded),
40 "reinforced" => Ok(Self::Reinforced),
41 "heavy" => Ok(Self::Heavy),
42 "none" => Ok(Self::None),
43 "psql" => Ok(Self::Psql),
44 "markdown" => Ok(Self::Markdown),
45 "dots" => Ok(Self::Dots),
46 "restructured" => Ok(Self::Restructured),
47 "ascii_rounded" => Ok(Self::AsciiRounded),
48 "basic_compact" => Ok(Self::BasicCompact),
49 "single" => Ok(Self::Single),
50 "double" => Ok(Self::Double),
51 _ => Err(
52 "'basic', 'thin', 'light', 'compact', 'with_love', 'compact_double', 'rounded', 'reinforced', 'heavy', 'none', 'psql', 'markdown', 'dots', 'restructured', 'ascii_rounded', 'basic_compact', 'single', or 'double'",
53 ),
54 }
55 }
56}
57
58impl UpdateFromValue for TableMode {
59 fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
60 config_update_string_enum(self, value, path, errors)
61 }
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
65pub enum FooterMode {
66 Never,
68 Always,
70 RowCount(u64),
72 Auto,
74}
75
76impl FromStr for FooterMode {
77 type Err = &'static str;
78
79 fn from_str(s: &str) -> Result<Self, Self::Err> {
80 match s.to_ascii_lowercase().as_str() {
81 "always" => Ok(FooterMode::Always),
82 "never" => Ok(FooterMode::Never),
83 "auto" => Ok(FooterMode::Auto),
84 _ => Err("'never', 'always', 'auto', or int"),
85 }
86 }
87}
88
89impl UpdateFromValue for FooterMode {
90 fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
91 match value {
92 Value::String { val, .. } => match val.parse() {
93 Ok(val) => *self = val,
94 Err(err) => errors.invalid_value(path, err.to_string(), value),
95 },
96 &Value::Int { val, .. } => {
97 if val >= 0 {
98 *self = Self::RowCount(val as u64);
99 } else {
100 errors.invalid_value(path, "a non-negative integer", value);
101 }
102 }
103 _ => errors.type_mismatch(
104 path,
105 Type::custom("'never', 'always', 'auto', or int"),
106 value,
107 ),
108 }
109 }
110}
111
112impl IntoValue for FooterMode {
113 fn into_value(self, span: Span) -> Value {
114 match self {
115 FooterMode::Always => "always".into_value(span),
116 FooterMode::Never => "never".into_value(span),
117 FooterMode::Auto => "auto".into_value(span),
118 FooterMode::RowCount(c) => (c as i64).into_value(span),
119 }
120 }
121}
122
123#[derive(Clone, Copy, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
124pub enum TableIndexMode {
125 Always,
127 Never,
129 Auto,
131}
132
133impl FromStr for TableIndexMode {
134 type Err = &'static str;
135
136 fn from_str(s: &str) -> Result<Self, Self::Err> {
137 match s.to_ascii_lowercase().as_str() {
138 "always" => Ok(TableIndexMode::Always),
139 "never" => Ok(TableIndexMode::Never),
140 "auto" => Ok(TableIndexMode::Auto),
141 _ => Err("'never', 'always' or 'auto'"),
142 }
143 }
144}
145
146impl UpdateFromValue for TableIndexMode {
147 fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
148 config_update_string_enum(self, value, path, errors)
149 }
150}
151
152#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
155pub enum TrimStrategy {
156 Wrap {
160 try_to_keep_words: bool,
163 },
164 Truncate {
167 suffix: Option<String>,
173 },
174}
175
176impl TrimStrategy {
177 pub fn wrap(dont_split_words: bool) -> Self {
178 Self::Wrap {
179 try_to_keep_words: dont_split_words,
180 }
181 }
182
183 pub fn truncate(suffix: Option<String>) -> Self {
184 Self::Truncate { suffix }
185 }
186}
187
188impl Default for TrimStrategy {
189 fn default() -> Self {
190 Self::Wrap {
191 try_to_keep_words: true,
192 }
193 }
194}
195
196impl IntoValue for TrimStrategy {
197 fn into_value(self, span: Span) -> Value {
198 match self {
199 TrimStrategy::Wrap { try_to_keep_words } => {
200 record! {
201 "methodology" => "wrapping".into_value(span),
202 "wrapping_try_keep_words" => try_to_keep_words.into_value(span),
203 }
204 }
205 TrimStrategy::Truncate { suffix } => {
206 record! {
207 "methodology" => "truncating".into_value(span),
208 "truncating_suffix" => suffix.into_value(span),
209 }
210 }
211 }
212 .into_value(span)
213 }
214}
215
216impl UpdateFromValue for TrimStrategy {
217 fn update<'a>(
218 &mut self,
219 value: &'a Value,
220 path: &mut ConfigPath<'a>,
221 errors: &mut ConfigErrors,
222 ) {
223 let Value::Record { val: record, .. } = value else {
224 errors.type_mismatch(path, Type::record(), value);
225 return;
226 };
227
228 let Some(methodology) = record.get("methodology") else {
229 errors.missing_column(path, "methodology", value.span());
230 return;
231 };
232
233 match methodology.as_str() {
234 Ok("wrapping") => {
235 let mut try_to_keep_words = if let &mut Self::Wrap { try_to_keep_words } = self {
236 try_to_keep_words
237 } else {
238 false
239 };
240 for (col, val) in record.iter() {
241 let path = &mut path.push(col);
242 match col.as_str() {
243 "wrapping_try_keep_words" => try_to_keep_words.update(val, path, errors),
244 "methodology" | "truncating_suffix" => (),
245 _ => errors.unknown_option(path, val),
246 }
247 }
248 *self = Self::Wrap { try_to_keep_words };
249 }
250 Ok("truncating") => {
251 let mut suffix = if let Self::Truncate { suffix } = self {
252 suffix.take()
253 } else {
254 None
255 };
256 for (col, val) in record.iter() {
257 let path = &mut path.push(col);
258 match col.as_str() {
259 "truncating_suffix" => match val {
260 Value::Nothing { .. } => suffix = None,
261 Value::String { val, .. } => suffix = Some(val.clone()),
262 _ => errors.type_mismatch(path, Type::String, val),
263 },
264 "methodology" | "wrapping_try_keep_words" => (),
265 _ => errors.unknown_option(path, val),
266 }
267 }
268 *self = Self::Truncate { suffix };
269 }
270 Ok(_) => errors.invalid_value(
271 &path.push("methodology"),
272 "'wrapping' or 'truncating'",
273 methodology,
274 ),
275 Err(_) => errors.type_mismatch(&path.push("methodology"), Type::String, methodology),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
281pub struct TableIndent {
282 pub left: usize,
283 pub right: usize,
284}
285
286impl TableIndent {
287 pub fn new(left: usize, right: usize) -> Self {
288 Self { left, right }
289 }
290}
291
292impl IntoValue for TableIndent {
293 fn into_value(self, span: Span) -> Value {
294 record! {
295 "left" => (self.left as i64).into_value(span),
296 "right" => (self.right as i64).into_value(span),
297 }
298 .into_value(span)
299 }
300}
301
302impl Default for TableIndent {
303 fn default() -> Self {
304 Self { left: 1, right: 1 }
305 }
306}
307
308impl UpdateFromValue for TableIndent {
309 fn update<'a>(
310 &mut self,
311 value: &'a Value,
312 path: &mut ConfigPath<'a>,
313 errors: &mut ConfigErrors,
314 ) {
315 match value {
316 &Value::Int { val, .. } => {
317 if let Ok(val) = val.try_into() {
318 self.left = val;
319 self.right = val;
320 } else {
321 errors.invalid_value(path, "a non-negative integer", value);
322 }
323 }
324 Value::Record { val: record, .. } => {
325 for (col, val) in record.iter() {
326 let path = &mut path.push(col);
327 match col.as_str() {
328 "left" => self.left.update(val, path, errors),
329 "right" => self.right.update(val, path, errors),
330 _ => errors.unknown_option(path, val),
331 }
332 }
333 }
334 _ => errors.type_mismatch(path, Type::custom("int or record"), value),
335 }
336 }
337}
338
339#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
340pub struct TableConfig {
341 pub mode: TableMode,
342 pub index_mode: TableIndexMode,
343 pub show_empty: bool,
344 pub padding: TableIndent,
345 pub trim: TrimStrategy,
346 pub header_on_separator: bool,
347 pub abbreviated_row_count: Option<usize>,
348 pub footer_inheritance: bool,
349 pub missing_value_symbol: String,
350}
351
352impl IntoValue for TableConfig {
353 fn into_value(self, span: Span) -> Value {
354 let abbv_count = self
355 .abbreviated_row_count
356 .map(|t| t as i64)
357 .into_value(span);
358
359 record! {
360 "mode" => self.mode.into_value(span),
361 "index_mode" => self.index_mode.into_value(span),
362 "show_empty" => self.show_empty.into_value(span),
363 "padding" => self.padding.into_value(span),
364 "trim" => self.trim.into_value(span),
365 "header_on_separator" => self.header_on_separator.into_value(span),
366 "abbreviated_row_count" => abbv_count,
367 "footer_inheritance" => self.footer_inheritance.into_value(span),
368 "missing_value_symbol" => self.missing_value_symbol.into_value(span),
369 }
370 .into_value(span)
371 }
372}
373
374impl Default for TableConfig {
375 fn default() -> Self {
376 Self {
377 mode: TableMode::Rounded,
378 index_mode: TableIndexMode::Always,
379 show_empty: true,
380 trim: TrimStrategy::default(),
381 header_on_separator: false,
382 padding: TableIndent::default(),
383 abbreviated_row_count: None,
384 footer_inheritance: false,
385 missing_value_symbol: "❎".into(),
386 }
387 }
388}
389
390impl UpdateFromValue for TableConfig {
391 fn update<'a>(
392 &mut self,
393 value: &'a Value,
394 path: &mut ConfigPath<'a>,
395 errors: &mut ConfigErrors,
396 ) {
397 let Value::Record { val: record, .. } = value else {
398 errors.type_mismatch(path, Type::record(), value);
399 return;
400 };
401
402 for (col, val) in record.iter() {
403 let path = &mut path.push(col);
404 match col.as_str() {
405 "mode" => self.mode.update(val, path, errors),
406 "index_mode" => self.index_mode.update(val, path, errors),
407 "show_empty" => self.show_empty.update(val, path, errors),
408 "trim" => self.trim.update(val, path, errors),
409 "header_on_separator" => self.header_on_separator.update(val, path, errors),
410 "padding" => self.padding.update(val, path, errors),
411 "abbreviated_row_count" => match val {
412 Value::Nothing { .. } => self.abbreviated_row_count = None,
413 &Value::Int { val: count, .. } => {
414 if let Ok(count) = count.try_into() {
415 self.abbreviated_row_count = Some(count);
416 } else {
417 errors.invalid_value(path, "a non-negative integer", val);
418 }
419 }
420 _ => errors.type_mismatch(path, Type::custom("int or nothing"), val),
421 },
422 "footer_inheritance" => self.footer_inheritance.update(val, path, errors),
423 "missing_value_symbol" => match val.as_str() {
424 Ok(val) => self.missing_value_symbol = val.to_string(),
425 Err(_) => errors.type_mismatch(path, Type::String, val),
426 },
427 _ => errors.unknown_option(path, val),
428 }
429 }
430 }
431}