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}
344
345impl IntoValue for TableConfig {
346 fn into_value(self, span: Span) -> Value {
347 let abbv_count = self
348 .abbreviated_row_count
349 .map(|t| t as i64)
350 .into_value(span);
351
352 record! {
353 "mode" => self.mode.into_value(span),
354 "index_mode" => self.index_mode.into_value(span),
355 "show_empty" => self.show_empty.into_value(span),
356 "padding" => self.padding.into_value(span),
357 "trim" => self.trim.into_value(span),
358 "header_on_separator" => self.header_on_separator.into_value(span),
359 "abbreviated_row_count" => abbv_count,
360 "footer_inheritance" => self.footer_inheritance.into_value(span),
361 }
362 .into_value(span)
363 }
364}
365
366impl Default for TableConfig {
367 fn default() -> Self {
368 Self {
369 mode: TableMode::Rounded,
370 index_mode: TableIndexMode::Always,
371 show_empty: true,
372 trim: TrimStrategy::default(),
373 header_on_separator: false,
374 padding: TableIndent::default(),
375 abbreviated_row_count: None,
376 footer_inheritance: false,
377 }
378 }
379}
380
381impl UpdateFromValue for TableConfig {
382 fn update<'a>(
383 &mut self,
384 value: &'a Value,
385 path: &mut ConfigPath<'a>,
386 errors: &mut ConfigErrors,
387 ) {
388 let Value::Record { val: record, .. } = value else {
389 errors.type_mismatch(path, Type::record(), value);
390 return;
391 };
392
393 for (col, val) in record.iter() {
394 let path = &mut path.push(col);
395 match col.as_str() {
396 "mode" => self.mode.update(val, path, errors),
397 "index_mode" => self.index_mode.update(val, path, errors),
398 "show_empty" => self.show_empty.update(val, path, errors),
399 "trim" => self.trim.update(val, path, errors),
400 "header_on_separator" => self.header_on_separator.update(val, path, errors),
401 "padding" => self.padding.update(val, path, errors),
402 "abbreviated_row_count" => match val {
403 Value::Nothing { .. } => self.abbreviated_row_count = None,
404 &Value::Int { val: count, .. } => {
405 if let Ok(count) = count.try_into() {
406 self.abbreviated_row_count = Some(count);
407 } else {
408 errors.invalid_value(path, "a non-negative integer", val);
409 }
410 }
411 _ => errors.type_mismatch(path, Type::custom("int or nothing"), val),
412 },
413 "footer_inheritance" => self.footer_inheritance.update(val, path, errors),
414 _ => errors.unknown_option(path, val),
415 }
416 }
417 }
418}