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