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