ced/
command.rs

1#[cfg(feature = "cli")]
2use crate::cli::help;
3use crate::error::{CedError, CedResult};
4#[cfg(feature = "cli")]
5use crate::page::Page;
6use crate::processor::Processor;
7use crate::utils::{self, subprocess};
8use dcsv::{Column, Row, LIMITER_ATTRIBUTE_LEN, SCHEMA_HEADER};
9use dcsv::{Value, ValueLimiter, ValueType};
10use std::io::Write;
11use std::str::FromStr;
12use std::{ops::Sub, path::Path};
13use utils::DEFAULT_DELIMITER;
14
15/// Types of command
16#[derive(PartialEq, Debug, Clone, Copy)]
17pub enum CommandType {
18    #[cfg(feature = "cli")]
19    Version,
20    #[cfg(feature = "cli")]
21    Help,
22    Undo,
23    Redo,
24    Create,
25    Write,
26    Import,
27    ImportRaw,
28    Export,
29    AddRow,
30    AddColumn,
31    DeleteRow,
32    DeleteColumn,
33    EditCell,
34    EditColumn,
35    RenameColumn,
36    EditRow,
37    #[cfg(feature = "cli")]
38    EditRowMultiple,
39    MoveRow,
40    MoveColumn,
41    Exit,
42    Execute,
43    Print,
44    PrintCell,
45    PrintRow,
46    PrintColumn,
47    Limit,
48    #[cfg(feature = "cli")]
49    LimitPreset,
50    Schema,
51    SchemaInit,
52    SchemaExport,
53    History,
54    None,
55}
56
57impl std::fmt::Display for CommandType {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        write!(f, "{:#?}", self)
60    }
61}
62
63// TODO
64// Currently this never fails
65impl FromStr for CommandType {
66    type Err = CedError;
67    fn from_str(src: &str) -> Result<Self, Self::Err> {
68        let command_type = match src.to_lowercase().trim() {
69            #[cfg(feature = "cli")]
70            "version" | "v" => Self::Version,
71            #[cfg(feature = "cli")]
72            "help" | "h" => Self::Help,
73            "import" | "i" => Self::Import,
74            "import-raw" | "ir" => Self::ImportRaw,
75            "export" | "x" => Self::Export,
76            "execute" | "ex" => Self::Execute,
77            "create" | "c" => Self::Create,
78            "write" | "w" => Self::Write,
79            "print" | "p" => Self::Print,
80            "print-cell" | "pc" => Self::PrintCell,
81            "print-row" | "pr" => Self::PrintRow,
82            "print-column" | "pl" => Self::PrintColumn,
83            "add-row" | "ar" => Self::AddRow,
84            "exit" | "quit" | "q" => Self::Exit,
85            "add-column" | "ac" => Self::AddColumn,
86            "delete-row" | "dr" => Self::DeleteRow,
87            "delete-column" | "dc" => Self::DeleteColumn,
88            "edit" | "edit-cell" | "e" => Self::EditCell,
89            "edit-row" | "er" => Self::EditRow,
90            #[cfg(feature = "cli")]
91            "edit-row-multiple" | "erm" => Self::EditRowMultiple,
92            "edit-column" | "ec" => Self::EditColumn,
93            "rename-column" | "rc" => Self::RenameColumn,
94            "move-row" | "move" | "m" => Self::MoveRow,
95            "move-column" | "mc" => Self::MoveColumn,
96            "limit" | "l" => Self::Limit,
97            #[cfg(feature = "cli")]
98            "limit-preset" | "lp" => Self::LimitPreset,
99            "undo" | "u" => Self::Undo,
100            "redo" | "r" => Self::Redo,
101            "schema" | "s" => Self::Schema,
102            "schema-init" | "si" => Self::SchemaInit,
103            "schema-export" | "se" => Self::SchemaExport,
104            "history" | "y" => Self::History,
105            _ => {
106                Self::None
107
108                // TODO
109                // Disabled error branch for current compatiblity
110                //return Err(CedError::CommandError(format!(
111                //"{} is not a valid command type",
112                //src
113                //)))
114            }
115        };
116        Ok(command_type)
117    }
118}
119
120/// Ergonomic wrapper around processor api
121#[derive(Debug)]
122pub struct Command {
123    pub command_type: CommandType,
124    pub arguments: Vec<String>,
125}
126
127impl Default for Command {
128    fn default() -> Self {
129        Self {
130            command_type: CommandType::Print,
131            arguments: vec![],
132        }
133    }
134}
135
136impl FromStr for Command {
137    type Err = CedError;
138    fn from_str(src: &str) -> Result<Self, Self::Err> {
139        let src: Vec<String> = utils::tokens_with_quote(src);
140        let command = &src[0];
141        let command_type = CommandType::from_str(command)?;
142        Ok(Self {
143            command_type,
144            arguments: src[1..].iter().map(|s| s.to_string()).collect(),
145        })
146    }
147}
148
149/// Affected data container
150#[allow(dead_code)]
151enum CommandResult {
152    Cell(usize, usize, Value),
153    Rows(Vec<Row>),
154    Columns(Vec<Column>),
155}
156
157// Default history capacity is a double word
158#[cfg(feature = "cli")]
159const HISTORY_CAPACITY: usize = 16;
160#[cfg(feature = "cli")]
161pub struct CommandHistory {
162    pub index: usize,
163    newest_snapshot: Option<HistoryRecord>,
164    pub(crate) memento_history: Vec<HistoryRecord>,
165    history_capacity: usize,
166}
167
168#[cfg(feature = "cli")]
169impl CommandHistory {
170    pub fn new() -> Self {
171        let capacity = if let Ok(cap) = std::env::var("CED_HISTORY_CAPACITY") {
172            if let Ok(num) = cap.parse::<usize>() {
173                num
174            } else {
175                HISTORY_CAPACITY
176            }
177        } else {
178            HISTORY_CAPACITY
179        };
180        Self {
181            index: 0, // 0 should mean nothing rather than "first" element
182            newest_snapshot: None,
183            memento_history: vec![],
184            history_capacity: capacity,
185        }
186    }
187
188    pub(crate) fn is_empty(&self) -> bool {
189        self.memento_history.is_empty()
190    }
191
192    // If index is equal to lenth than there is no undo operation took in place
193    pub(crate) fn is_newest(&self) -> bool {
194        self.index >= self.memento_history.len()
195    }
196
197    pub(crate) fn set_current_backup(&mut self, data: Page) {
198        self.newest_snapshot
199            .replace(HistoryRecord::new(data, CommandType::Undo));
200    }
201
202    pub(crate) fn take_snapshot(&mut self, data: &Page, command: CommandType) {
203        // Remove discarded changes
204        // User will lose all undo history after current index if user undid several steps and had
205        // done a new action
206        self.drain_history();
207
208        self.memento_history
209            .push(HistoryRecord::new(data.clone(), command));
210        // You cannot redo if you have done something other than undo
211        if self.memento_history.len() > self.history_capacity {
212            self.memento_history.rotate_left(1);
213            self.memento_history.pop();
214        } else {
215            // Increase index according to history size increase
216            self.index += 1;
217        }
218    }
219
220    fn drain_history(&mut self) {
221        if !self.memento_history.is_empty() && self.index < self.memento_history.len() {
222            self.memento_history.drain(self.index..);
223            self.newest_snapshot.take();
224        }
225    }
226
227    pub(crate) fn get_undo(&mut self) -> Option<&HistoryRecord> {
228        // Cannot go backward because index is 0
229        if self.index == 0 {
230            None
231        } else {
232            self.index -= 1;
233            let target_index = self.index;
234            self.memento_history.get(target_index)
235        }
236    }
237
238    pub(crate) fn get_redo(&mut self) -> Option<&HistoryRecord> {
239        match self.index {
240            x if x == self.memento_history.len() => None,
241            y if y == self.memento_history.len() - 1 => {
242                self.index += 1;
243                self.newest_snapshot.as_ref()
244            }
245            _ => {
246                self.index += 1;
247                let target_index = self.index;
248                self.memento_history.get(target_index)
249            }
250        }
251    }
252}
253
254#[cfg(feature = "cli")]
255pub(crate) struct HistoryRecord {
256    pub(crate) data: Page,
257    pub(crate) command: CommandType,
258}
259
260#[cfg(feature = "cli")]
261impl HistoryRecord {
262    pub fn new(data: Page, command: CommandType) -> Self {
263        Self { data, command }
264    }
265}
266
267/// Main loop struct for interactive csv editing
268impl Processor {
269    /// Execute given command
270    pub fn execute_command(&mut self, command: &Command) -> CedResult<()> {
271        let page_name = &self
272            .get_cursor()
273            .ok_or_else(|| CedError::CommandError("Current page is empty".to_string()))?;
274        match &command.command_type {
275            #[cfg(feature = "cli")]
276            CommandType::Version => help::print_version(),
277            #[cfg(feature = "cli")]
278            CommandType::Help => self.print_help_from_args(&command.arguments)?,
279            CommandType::None => return Err(CedError::CommandError("No such command".to_string())),
280            CommandType::Import => {
281                #[cfg(feature = "cli")]
282                self.drop_pages()?;
283                self.import_file_from_args(&command.arguments, false)?
284            }
285            CommandType::ImportRaw => self.import_file_from_args(&command.arguments, true)?,
286            CommandType::Schema => self.import_schema_from_args(page_name, &command.arguments)?,
287            CommandType::SchemaInit => self.init_schema_from_args(&command.arguments)?,
288            CommandType::SchemaExport => {
289                self.export_schema_from_args(page_name, &command.arguments)?
290            }
291            CommandType::Export => self.write_to_file_from_args(page_name, &command.arguments)?,
292            CommandType::Write => {
293                self.overwrite_to_file_from_args(page_name, &command.arguments)?
294            }
295            CommandType::Create => {
296                self.add_column_array(page_name, &command.arguments)?;
297                self.log("New columns added\n")?;
298            }
299            CommandType::Print => self.print(page_name, &command.arguments)?,
300            CommandType::PrintCell => self.print_cell(page_name, &command.arguments)?,
301            CommandType::PrintRow => self.print_row(page_name, &command.arguments)?,
302            CommandType::PrintColumn => self.print_column(page_name, &command.arguments)?,
303            CommandType::AddRow => self.add_row_from_args(page_name, &command.arguments)?,
304            CommandType::DeleteRow => self.remove_row_from_args(page_name, &command.arguments)?,
305            CommandType::DeleteColumn => {
306                self.remove_column_from_args(page_name, &command.arguments)?
307            }
308            CommandType::AddColumn => self.add_column_from_args(page_name, &command.arguments)?,
309            CommandType::EditCell => self.edit_cell_from_args(page_name, &command.arguments)?,
310            CommandType::EditRow => self.edit_row_from_args(page_name, &command.arguments)?,
311            #[cfg(feature = "cli")]
312            CommandType::EditRowMultiple => {
313                self.edit_rows_from_args(page_name, &command.arguments)?
314            }
315            CommandType::EditColumn => self.edit_column_from_args(page_name, &command.arguments)?,
316            CommandType::RenameColumn => {
317                self.rename_column_from_args(page_name, &command.arguments)?
318            }
319            CommandType::MoveRow => self.move_row_from_args(page_name, &command.arguments)?,
320            CommandType::MoveColumn => self.move_column_from_args(page_name, &command.arguments)?,
321            CommandType::Limit => self.limit_column_from_args(page_name, &command.arguments)?,
322            #[cfg(feature = "cli")]
323            CommandType::LimitPreset => self.limit_preset(page_name, &command.arguments)?,
324            CommandType::Execute => self.execute_from_file(&command.arguments)?,
325
326            // NOTE
327            // This is not handled by processor in current implementation
328            CommandType::Exit | CommandType::Undo | CommandType::Redo | CommandType::History => (),
329        }
330        Ok(())
331    }
332
333    fn move_row_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
334        if args.len() < 2 {
335            return Err(CedError::CommandError(
336                "Insufficient arguments for move-row".to_string(),
337            ));
338        }
339        let src_number = args[0].parse::<usize>().map_err(|_| {
340            CedError::CommandError(format!("\"{}\" is not a valid row number", args[0]))
341        })?;
342        let target_number = args[1].parse::<usize>().map_err(|_| {
343            CedError::CommandError(format!("\"{}\" is not a valid row number", args[1]))
344        })?;
345        self.move_row(page_name, src_number, target_number)?;
346        self.log(&format!(
347            "Row moved from \"{}\" to \"{}\"\n",
348            src_number, target_number
349        ))?;
350        Ok(())
351    }
352
353    fn move_column_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
354        if args.len() < 2 {
355            return Err(CedError::CommandError(
356                "Insufficient arguments for move-column".to_string(),
357            ));
358        }
359        let src_number = self
360            .get_page_data(page_name)?
361            .try_get_column_index(&args[0])
362            .ok_or_else(|| {
363                CedError::InvalidColumn(format!("Column : \"{}\" is not valid", args[0]))
364            })?;
365        let target_number = self
366            .get_page_data(page_name)?
367            .try_get_column_index(&args[1])
368            .ok_or_else(|| {
369                CedError::InvalidColumn(format!("Column : \"{}\" is not valid", args[1]))
370            })?;
371        self.move_column(page_name, src_number, target_number)?;
372        self.log(&format!(
373            "Column moved from \"{}\" to \"{}\"\n",
374            src_number, target_number
375        ))?;
376        Ok(())
377    }
378
379    fn rename_column_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
380        if args.len() < 2 {
381            return Err(CedError::CommandError(
382                "Insufficient arguments for rename-column".to_string(),
383            ));
384        }
385
386        let column = &args[0];
387        let new_name = &args[1];
388
389        self.rename_column(page_name, column, new_name)?;
390        self.log(&format!(
391            "Column renamed from \"{}\" to \"{}\"\n",
392            column, new_name
393        ))?;
394        Ok(())
395    }
396
397    /// Edit single row
398    fn edit_row_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
399        let len = args.len();
400        let row_number: usize;
401        match len {
402            0 => {
403                // No row
404                return Err(CedError::CommandError(
405                    "Insufficient arguments for edit-row".to_string(),
406                ));
407            }
408            #[cfg(feature = "cli")]
409            1 => {
410                self.check_no_loop()?;
411                // Only row
412                row_number = args[0].parse::<usize>().map_err(|_| {
413                    CedError::CommandError(format!("\"{}\" is not a valid row number", args[0]))
414                })?;
415                utils::write_to_stdout("Type comma(,) to exit input\n")?;
416                let values = self.edit_row_loop(page_name, Some(row_number))?;
417                if values.is_empty() {
418                    return Ok(());
419                }
420                self.edit_row(page_name, row_number, &values)?;
421            }
422            _ => {
423                // From 2.. row + data
424                row_number = args[0].parse::<usize>().map_err(|_| {
425                    CedError::CommandError(format!("\"{}\" is not a valid row number", args[0]))
426                })?;
427                let values = args[1]
428                    .split(DEFAULT_DELIMITER)
429                    .map(|s| s.to_string())
430                    .collect::<Vec<String>>();
431                self.set_row_from_string_array(page_name, row_number, &values)?;
432            }
433        }
434
435        self.log(&format!("Row \"{}\" 's content changed\n", row_number))?;
436        Ok(())
437    }
438
439    /// Edit multiple rows
440    #[cfg(feature = "cli")]
441    fn edit_rows_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
442        self.check_no_loop()?;
443        let len = args.len();
444        let mut start_index = 0;
445        let mut end_index = self.get_row_count(page_name)?.max(1) - 1;
446        match len {
447            // No row
448            0 => {}
449            1 => {
450                // Only starting row
451                start_index = args[0].parse::<usize>().map_err(|_| {
452                    CedError::CommandError(format!("\"{}\" is not a valid row number", args[0]))
453                })?;
454            }
455            _ => {
456                // From 2.. Starting row + ending row
457                start_index = args[0].parse::<usize>().map_err(|_| {
458                    CedError::CommandError(format!("\"{}\" is not a valid row number", args[0]))
459                })?;
460                end_index = args[1].parse::<usize>().map_err(|_| {
461                    CedError::CommandError(format!("\"{}\" is not a valid row number", args[1]))
462                })?;
463            }
464        }
465
466        utils::write_to_stdout("Type comma(,) to exit input\n")?;
467        let mut edit_target_values = vec![];
468        // Inclusive range
469        for index in start_index..=end_index {
470            utils::write_to_stdout(&format!(": Line = {} :\n", index))?;
471            let values = self.edit_row_loop(page_name, Some(index))?;
472            if values.is_empty() {
473                return Ok(());
474            }
475            edit_target_values.push((index, values));
476        }
477
478        // Edit rows only if every operation was succesfull
479        for (index, values) in edit_target_values {
480            self.edit_row(page_name, index, &values)?;
481        }
482
483        self.log(&format!(
484            "Rows {}~{} contents changed\n",
485            start_index, end_index
486        ))?;
487        Ok(())
488    }
489
490    fn edit_column_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
491        if args.len() < 2 {
492            return Err(CedError::CommandError(
493                "Insufficient arguments for edit-column".to_string(),
494            ));
495        }
496
497        let column = &args[0];
498        let new_value = &args[1];
499
500        self.edit_column(page_name, column, new_value)?;
501        self.log(&format!(
502            "Column \"{}\" content changed to \"{}\"\n",
503            column, new_value
504        ))?;
505        Ok(())
506    }
507
508    fn edit_cell_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
509        if args.is_empty() {
510            return Err(CedError::CommandError("Edit needs coordinate".to_string()));
511        }
512
513        let coord = &args[0].split(',').collect::<Vec<&str>>();
514        let value = if args.len() >= 2 {
515            args[1..].join(" ")
516        } else {
517            String::new()
518        };
519        if coord.len() != 2 {
520            return Err(CedError::CommandError(
521                "Cell cooridnate should be in a form of \"row,column\"".to_string(),
522            ));
523        }
524
525        if !utils::is_valid_csv(&value) {
526            return Err(CedError::CommandError(
527                "Given cell value is not a valid csv value".to_string(),
528            ));
529        }
530
531        let row = coord[0].parse::<usize>().map_err(|_| {
532            CedError::CommandError(format!("\"{}\" is not a valid row number", coord[0]))
533        })?;
534        let column = self
535            .get_page_data(page_name)?
536            .try_get_column_index(coord[1])
537            .ok_or_else(|| {
538                CedError::InvalidColumn(format!("Column : \"{}\" is not valid", coord[1]))
539            })?;
540
541        self.edit_cell(page_name, row, column, &value)?;
542        self.log(&format!(
543            "Cell \"({},{})\" content changed to \"{}\"\n",
544            row, coord[1], &&value
545        ))?;
546        Ok(())
547    }
548
549    fn add_row_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
550        let len = args.len();
551        let row_number: usize;
552        match len {
553            #[cfg(feature = "cli")]
554            0 => {
555                self.check_no_loop()?;
556                // No row number
557                row_number = self.get_row_count(page_name)?;
558                if row_number > self.get_row_count(page_name)? {
559                    return Err(CedError::InvalidColumn(format!(
560                        "Cannot add row to out of range position : {}",
561                        row_number
562                    )));
563                }
564                utils::write_to_stdout("Type comma(,) to exit input\n")?;
565                let values = self.add_row_loop(page_name, None)?;
566                if values.is_empty() {
567                    return Ok(());
568                }
569                self.add_row(page_name, row_number, Some(&values))?;
570            }
571            #[cfg(feature = "cli")]
572            1 => {
573                self.check_no_loop()?;
574                // Only row number
575                row_number = args[0].parse::<usize>().map_err(|_| {
576                    CedError::CommandError(format!("\"{}\" is not a valid row number", args[0]))
577                })?;
578                if row_number > self.get_row_count(page_name)? {
579                    return Err(CedError::InvalidColumn(format!(
580                        "Cannot add row to out of range position : {}",
581                        row_number
582                    )));
583                }
584                utils::write_to_stdout("Type comma(,) to exit input\n")?;
585                let values = self.add_row_loop(page_name, None)?;
586                if values.is_empty() {
587                    return Ok(());
588                }
589                self.add_row(page_name, row_number, Some(&values))?;
590            }
591            _ => {
592                // From 2.. row + data
593                row_number = args[0].parse::<usize>().map_err(|_| {
594                    CedError::CommandError(format!("\"{}\" is not a valid row number", args[0]))
595                })?;
596                let values = args[1]
597                    .split(DEFAULT_DELIMITER)
598                    .map(|s| s.to_string())
599                    .collect::<Vec<String>>();
600                self.add_row_from_string_array(page_name, row_number, &values)?;
601            }
602        }
603        self.log(&format!("New row added to \"{}\"\n", row_number))?;
604        Ok(())
605    }
606
607    // DRY code for value check
608    /// Check value on loop variants
609    ///
610    /// Return value decided whether early return or not
611    #[cfg(feature = "cli")]
612    fn loop_value_check(value: &mut Value, type_mismatch: &mut bool) -> bool {
613        // Check value content
614        // Early return
615        if let Value::Text(content) = value {
616            if content == DEFAULT_DELIMITER {
617                return true;
618            }
619
620            // Check csv validity
621            if !utils::is_valid_csv(content) {
622                // It is considered as type_mismatch
623                *type_mismatch = true;
624            }
625        }
626        false
627    }
628
629    #[cfg(feature = "cli")]
630    fn check_no_loop(&self) -> CedResult<()> {
631        if self.no_loop {
632            return Err(CedError::CommandError(
633                "Interactive loop is restricted. Breaking...".to_owned(),
634            ));
635        }
636        Ok(())
637    }
638
639    #[cfg(feature = "cli")]
640    fn add_row_loop(
641        &mut self,
642        page_name: &str,
643        row_number: Option<usize>,
644    ) -> CedResult<Vec<Value>> {
645        let mut values = vec![];
646        let columns = &self.get_page_data(page_name)?.get_columns();
647        if columns.is_empty() {
648            utils::write_to_stdout(": Csv is empty : \n")?;
649            return Ok(vec![]);
650        }
651        for (idx, col) in columns.iter().enumerate() {
652            let mut type_mismatch = false;
653            let default = if let Some(row_number) = row_number {
654                self.get_cell(page_name, row_number, idx)?
655                    .ok_or(CedError::OutOfRangeError)?
656                    .to_owned()
657            } else {
658                col.get_default_value()
659            };
660            utils::write_to_stdout(&format!("{}~{{{}}} = ", col.name, default))?;
661            let value_src = utils::read_stdin(true)?;
662            let mut value = if !value_src.is_empty() {
663                match Value::from_str(&value_src, col.column_type) {
664                    Ok(value) => value,
665                    Err(_) => {
666                        type_mismatch = true;
667                        Value::Text(value_src)
668                    }
669                }
670            } else {
671                default.clone()
672            };
673
674            if Self::loop_value_check(&mut value, &mut type_mismatch) {
675                utils::write_to_stdout(": Prompt interrupted :\n")?;
676                return Ok(vec![]);
677            }
678
679            while !col.limiter.qualify(&value) || type_mismatch {
680                type_mismatch = false;
681                utils::write_to_stdout(
682                    "Given value doesn't qualify column limiter or is not a valid csv value\n",
683                )?;
684                utils::write_to_stdout(&format!("{}~{{{}}} = ", col.name, default))?;
685                let value_src = utils::read_stdin(true)?;
686                value = if !value_src.is_empty() {
687                    match Value::from_str(&value_src, col.column_type) {
688                        Ok(value) => value,
689                        Err(_) => {
690                            type_mismatch = true;
691                            Value::Text(value_src)
692                        }
693                    }
694                } else {
695                    default.clone()
696                };
697
698                if Self::loop_value_check(&mut value, &mut type_mismatch) {
699                    utils::write_to_stdout(": Prompt interrupted :\n")?;
700                    return Ok(vec![]);
701                }
702            }
703
704            values.push(value);
705        }
706        Ok(values)
707    }
708
709    // None means value should not change
710    #[cfg(feature = "cli")]
711    fn edit_row_loop(
712        &mut self,
713        page_name: &str,
714        row_number: Option<usize>,
715    ) -> CedResult<Vec<Option<Value>>> {
716        let mut values = vec![];
717        let columns = &self.get_page_data(page_name)?.get_columns();
718        if columns.is_empty() {
719            utils::write_to_stdout(": Csv is empty : \n")?;
720            return Ok(vec![]);
721        }
722        for (idx, col) in columns.iter().enumerate() {
723            let mut type_mismatch = false;
724            let default = if let Some(row_number) = row_number {
725                self.get_cell(page_name, row_number, idx)?
726                    .ok_or(CedError::OutOfRangeError)?
727                    .to_owned()
728            } else {
729                col.get_default_value()
730            };
731            utils::write_to_stdout(&format!("{}~{{{}}} = ", col.name, default))?;
732            let value_src = utils::read_stdin(true)?;
733            let mut value = if !value_src.is_empty() {
734                Some(match Value::from_str(&value_src, col.column_type) {
735                    Ok(value) => value,
736                    Err(_) => {
737                        type_mismatch = true;
738                        Value::Text(value_src)
739                    }
740                })
741            } else {
742                None
743            };
744
745            // Early return
746            if let Some(value) = value.as_mut() {
747                if Self::loop_value_check(value, &mut type_mismatch) {
748                    utils::write_to_stdout(": Prompt interrupted :\n")?;
749                    return Ok(vec![]);
750                }
751            }
752
753            while value != None {
754                // when value was not a "Not changed"
755                if col.limiter.qualify(value.as_ref().unwrap()) && !type_mismatch {
756                    break;
757                }
758                type_mismatch = false;
759                utils::write_to_stdout("Given value doesn't qualify column limiter\n")?;
760                utils::write_to_stdout(&format!("{}~{{{}}} = ", col.name, default))?;
761                let value_src = utils::read_stdin(true)?;
762                value = if !value_src.is_empty() {
763                    Some(match Value::from_str(&value_src, col.column_type) {
764                        Ok(value) => value,
765                        Err(_) => {
766                            type_mismatch = true;
767                            Value::Text(value_src)
768                        }
769                    })
770                } else {
771                    None
772                };
773
774                // Early return
775                if let Some(value) = value.as_mut() {
776                    if Self::loop_value_check(value, &mut type_mismatch) {
777                        utils::write_to_stdout(": Prompt interrupted :\n")?;
778                        return Ok(vec![]);
779                    }
780                }
781            }
782
783            values.push(value);
784        }
785        Ok(values)
786    }
787
788    fn add_column_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
789        let mut column_number = self.get_page_data(page_name)?.get_column_count();
790        let mut column_type = ValueType::Text;
791        let mut placeholder = None;
792
793        if args.is_empty() {
794            return Err(CedError::CommandError(
795                "Cannot add column without name".to_owned(),
796            ));
797        }
798
799        let column_name = args[0].as_str();
800
801        if args.len() >= 2 {
802            column_number = args[1].parse::<usize>().map_err(|_| {
803                CedError::CommandError(format!("\"{}\" is not a valid column number", args[1]))
804            })?;
805        }
806        if args.len() >= 3 {
807            column_type = ValueType::from_str(&args[2])?;
808        }
809
810        if args.len() >= 4 {
811            placeholder.replace(Value::from_str(&args[3], column_type)?);
812        }
813
814        self.add_column(
815            page_name,
816            column_number,
817            column_name,
818            column_type,
819            None,
820            placeholder,
821        )?;
822        self.log(&format!(
823            "New column \"{}\" added to \"{}\"\n",
824            column_name, column_number
825        ))?;
826        Ok(())
827    }
828
829    fn remove_row_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
830        let row_count = if args.is_empty() {
831            self.get_row_count(page_name)?
832        } else {
833            args[0].parse::<usize>().map_err(|_| {
834                CedError::CommandError(format!("\"{}\" is not a valid index", args[0]))
835            })?
836        }
837        .sub(1);
838
839        if !self.remove_row(page_name, row_count)? {
840            utils::write_to_stdout("No such row to remove\n")?;
841        }
842        self.log(&format!("A row removed from \"{}\"\n", row_count))?;
843        Ok(())
844    }
845
846    fn remove_column_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
847        let column_count = if args.is_empty() {
848            self.get_page_data(page_name)?.get_column_count()
849        } else {
850            self.get_page_data(page_name)?
851                .try_get_column_index(&args[0])
852                .ok_or_else(|| {
853                    CedError::InvalidColumn(format!(
854                        "Cannot remove non-existent column \"{}\"",
855                        args[0]
856                    ))
857                })?
858        };
859
860        self.remove_column(page_name, column_count)?;
861        self.log(&format!("A column \"{}\" removed\n", &args[0]))?;
862        Ok(())
863    }
864
865    #[cfg(feature = "cli")]
866    fn print_help_from_args(&mut self, args: &Vec<String>) -> CedResult<()> {
867        if args.is_empty() {
868            help::print_help_text();
869        } else {
870            help::print_command_help(Command::from_str(&args[0])?.command_type);
871        }
872        Ok(())
873    }
874
875    /// Read from args
876    ///
877    /// file is asssumed to have header
878    /// You can give has_header value as second parameter
879    fn import_file_from_args(&mut self, args: &Vec<String>, raw_mode: bool) -> CedResult<()> {
880        match args.len() {
881            0 => {
882                return Err(CedError::CommandError(
883                    "You have to specify a file name to import from".to_owned(),
884                ))
885            }
886            1 => self.import_from_file(Path::new(&args[0]), true, None, raw_mode)?,
887            _ => {
888                // Optional line ending configuration
889                let mut line_ending = None;
890                if args.len() > 2 && &args[2].to_lowercase() == "cr" {
891                    line_ending = Some('\r');
892                }
893
894                // Remove entry if already exists
895                let page_name = &args[0];
896                self.remove_page(page_name);
897
898                self.import_from_file(
899                    Path::new(page_name),
900                    args[1].parse().map_err(|_| {
901                        CedError::CommandError(format!(
902                            "Given value \"{}\" should be a valid boolean value. ( has_header )",
903                            args[1]
904                        ))
905                    })?,
906                    line_ending,
907                    raw_mode,
908                )?
909            }
910        }
911        let footer = if raw_mode { " as array mode" } else { "" };
912        self.log(&format!("File \"{}\" imported{}\n", &args[0], footer))?;
913        Ok(())
914    }
915
916    fn import_schema_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
917        if self.get_page_data(page_name)?.is_array() {
918            return Err(CedError::InvalidPageOperation(
919                "Cannot import schema in array mode".to_string(),
920            ));
921        }
922
923        if args.len() < 2 {
924            return Err(CedError::CommandError(
925                "Insufficient variable for schema".to_owned(),
926            ));
927        }
928        let schema_file = &args[0];
929        let force = &args[1];
930        self.set_schema(
931            page_name,
932            schema_file,
933            !force
934                .parse::<bool>()
935                .map_err(|_| CedError::CommandError(format!("{} is not a valid value", force)))?,
936        )?;
937        self.log(&format!("Schema \"{}\" applied\n", &args[0]))?;
938        Ok(())
939    }
940
941    fn export_schema_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
942        if self.get_page_data(page_name)?.is_array() {
943            return Err(CedError::InvalidPageOperation(
944                "Cannot export schema in array mode".to_string(),
945            ));
946        }
947
948        if args.is_empty() {
949            return Err(CedError::CommandError(
950                "Schema export needs a file path".to_owned(),
951            ));
952        }
953        let file = &args[0];
954        let mut file = std::fs::File::create(file)
955            .map_err(|err| CedError::io_error(err, "Failed to open file for schema write"))?;
956
957        file.write_all(self.export_schema(page_name)?.as_bytes())
958            .map_err(|err| CedError::io_error(err, "Failed to write schema to a file"))?;
959
960        self.log(&format!("Schema exported to \"{}\"\n", args[0]))?;
961        Ok(())
962    }
963
964    fn init_schema_from_args(&mut self, args: &Vec<String>) -> CedResult<()> {
965        let file_name = if args.is_empty() {
966            "ced_schema.csv"
967        } else {
968            &args[0]
969        };
970        let mut file = std::fs::File::create(file_name)
971            .map_err(|err| CedError::io_error(err, "Failed to open file for schema init"))?;
972
973        file.write_all(SCHEMA_HEADER.as_bytes())
974            .map_err(|err| CedError::io_error(err, "Failed to write schema to a file"))?;
975
976        self.log(&format!("Schema initiated to \"{}\"\n", file_name))?;
977        Ok(())
978    }
979
980    fn write_to_file_from_args(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
981        if args.is_empty() {
982            return Err(CedError::CommandError(
983                "Export requires file path".to_owned(),
984            ));
985        }
986        self.write_to_file(page_name, &args[0])?;
987        self.log(&format!("File exported to \"{}\"\n", &args[0]))?;
988        Ok(())
989    }
990
991    fn overwrite_to_file_from_args(
992        &mut self,
993        page_name: &str,
994        args: &Vec<String>,
995    ) -> CedResult<()> {
996        let cache: bool = if !args.is_empty() {
997            args[0].parse::<bool>().map_err(|_| {
998                CedError::CommandError(format!("\"{}\" is not a valid boolean value", args[0]))
999            })?
1000        } else {
1001            true
1002        };
1003        let success = self.overwrite_to_file(page_name, cache)?;
1004        if success {
1005            self.log("File overwritten successfully\n")?;
1006        } else {
1007            self.log(": No source file to write. Use export instead :\n")?;
1008        }
1009        Ok(())
1010    }
1011
1012    // Combine ths with viewer variant
1013    fn print(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
1014        let mut viewer = vec![];
1015        // Use given command
1016        // or use environment variable
1017        // External command has higher priority
1018        if !args.is_empty() {
1019            viewer = args[0..].to_vec();
1020        } else if let Ok(var) = std::env::var("CED_VIEWER") {
1021            viewer = utils::tokens_with_quote(&var);
1022        }
1023
1024        if viewer.is_empty() {
1025            self.print_virtual_container(page_name)?;
1026        } else {
1027            let csv = self.get_page_as_string(page_name)?;
1028            self.print_with_viewer(csv, &viewer)?;
1029        }
1030
1031        Ok(())
1032    }
1033
1034    fn print_cell(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
1035        if args.is_empty() {
1036            return Err(CedError::CommandError(
1037                "Cannot print cell without a cooridnate".to_owned(),
1038            ));
1039        }
1040        let mut print_mode = "simple";
1041        let coord = args[0].split(',').collect::<Vec<&str>>();
1042        if coord.len() < 2 {
1043            return Err(CedError::CommandError(format!(
1044                "\"{}\' is not a valid cell coordinate",
1045                args[0]
1046            )));
1047        }
1048        let (x, y) = (
1049            coord[0].parse::<usize>().map_err(|_| {
1050                CedError::CommandError("You need to feed usize number for coordinate".to_string())
1051            })?,
1052            self.get_page_data(page_name)?
1053                .try_get_column_index(coord[1])
1054                .ok_or_else(|| {
1055                    CedError::CommandError(
1056                        "You need to appropriate column for coordinate".to_string(),
1057                    )
1058                })?,
1059        );
1060
1061        if args.len() >= 2 {
1062            print_mode = &args[1];
1063        }
1064
1065        match self.get_page_data(page_name)?.get_cell(x, y) {
1066            Some(cell) => match print_mode.to_lowercase().as_str() {
1067                "v" | "verbose" => utils::write_to_stdout(&format!("{:?}\n", cell))?,
1068                "d" | "debug" => {
1069                    let col = self
1070                        .get_column(page_name, y)?
1071                        .ok_or(CedError::OutOfRangeError)?;
1072                    utils::write_to_stdout(&format!("{:#?}\n", col))?;
1073                    utils::write_to_stdout(&format!("Cell data : {:#?}\n", cell))?
1074                }
1075                _ => utils::write_to_stdout(&format!("{}\n", cell))?,
1076            },
1077            None => utils::write_to_stdout("No such cell\n")?,
1078        }
1079
1080        Ok(())
1081    }
1082
1083    fn print_row(&self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
1084        if args.is_empty() {
1085            return Err(CedError::CommandError(
1086                "Print-row needs row number".to_string(),
1087            ));
1088        }
1089        let row_index = args[0].parse::<usize>().map_err(|_| {
1090            CedError::CommandError("You need to feed a valid number as a row number".to_string())
1091        })?;
1092
1093        let row = self
1094            .get_page_data(page_name)?
1095            .get_row_as_string(row_index)?
1096            + "\n";
1097
1098        let viewer: Vec<_>;
1099        // Use given command
1100        // or use environment variable
1101        // External command has higher priority
1102        if args.len() >= 2 {
1103            viewer = args[1..].to_vec();
1104            subprocess(&viewer, Some(row))
1105        } else if let Ok(var) = std::env::var("CED_VIEWER") {
1106            viewer = var.split_whitespace().map(|s| s.to_string()).collect();
1107            subprocess(&viewer, Some(row))
1108        } else {
1109            self.print_virtual_data_row(page_name, row_index, false)
1110        }
1111    }
1112
1113    fn print_column(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
1114        let mut print_mode = "simple";
1115        if args.is_empty() {
1116            let columns = self
1117                .get_page_data(page_name)?
1118                .get_columns()
1119                .iter()
1120                .map(|c| c.name.as_str())
1121                .collect::<Vec<_>>()
1122                .join(",");
1123            utils::write_to_stdout(&format!(": --{}-- :\n", columns))?;
1124            return Ok(());
1125        }
1126
1127        if args.len() >= 2 {
1128            print_mode = &args[1];
1129        }
1130
1131        if let Some(col) = self
1132            .get_page_data(page_name)?
1133            .try_get_column_index(&args[0])
1134        {
1135            if col < self.get_page_data(page_name)?.get_column_count() {
1136                let col = self
1137                    .get_column(page_name, col)?
1138                    .ok_or(CedError::OutOfRangeError)?;
1139                match print_mode.to_lowercase().as_str() {
1140                    "debug" | "d" => utils::write_to_stdout(&format!("{:#?}\n", col))?,
1141                    "verbose" | "v" => utils::write_to_stdout(&format!(
1142                        "Column = \nName: {}\nType: {}\n---\nLimiter =\n{}",
1143                        col.get_name(),
1144                        col.get_column_type(),
1145                        col.limiter
1146                    ))?,
1147                    _ => utils::write_to_stdout(&format!(
1148                        "Name: {}\nType: {}\n",
1149                        col.get_name(),
1150                        col.get_column_type()
1151                    ))?,
1152                }
1153                return Ok(());
1154            }
1155        }
1156        utils::write_to_stdout("No such column\n")?;
1157        Ok(())
1158    }
1159
1160    fn print_with_viewer(&self, csv: String, viewer: &[String]) -> CedResult<()> {
1161        subprocess(viewer, Some(csv))
1162    }
1163
1164    fn print_virtual_data_row(
1165        &self,
1166        page_name: &str,
1167        row_index: usize,
1168        include_header: bool,
1169    ) -> CedResult<()> {
1170        let page = self.get_page_data(page_name)?;
1171        // Empty csv value, return early
1172        if page.get_row_count() == 0 {
1173            utils::write_to_stdout(": CSV is empty :\n")?;
1174            return Ok(());
1175        }
1176
1177        if page.get_row_count() <= row_index {
1178            utils::write_to_stdout(": Given row index is not available :")?;
1179            return Ok(());
1180        }
1181
1182        let digits_count = page.get_row_count().to_string().len();
1183        if include_header {
1184            // 0 length csv is panicking error at this moment, thus safe to unwrap
1185            let header_with_number = format!(
1186                "{: ^digits_count$}| {}\n",
1187                "H ",
1188                page.get_columns()
1189                    .iter()
1190                    .enumerate()
1191                    .map(|(i, col)| format!("[{}]:{}", i, col.name))
1192                    .collect::<Vec<String>>()
1193                    .join("")
1194            );
1195            utils::write_to_stdout(&header_with_number)?;
1196        }
1197
1198        let row_string = page.get_row_as_string(row_index)?;
1199        utils::write_to_stdout(&format!("{: ^digits_count$} | {}\n", row_index, row_string))?;
1200
1201        Ok(())
1202    }
1203
1204    /// Print virtual container to console
1205    fn print_virtual_container(&self, page_name: &str) -> CedResult<()> {
1206        let page = self.get_page_data(page_name)?;
1207        // Empty csv value, return early
1208        if page.get_row_count() == 0 {
1209            utils::write_to_stdout(": CSV is empty :\n")?;
1210            return Ok(());
1211        }
1212
1213        if page.is_array() {
1214            utils::write_to_stdout("-- Mode: Array --\n")?;
1215        }
1216        let digits_count = page.get_row_count().to_string().len();
1217        // 0 length csv is panicking error at this moment, thus safe to unwrap
1218        let header_with_number = format!(
1219            "{: <digits_count$} | {}\n",
1220            "H",
1221            page.get_columns()
1222                .iter()
1223                .enumerate()
1224                .map(|(i, col)| format!("[{}]:{}", i, col.name))
1225                .collect::<Vec<String>>()
1226                .join("")
1227        );
1228        utils::write_to_stdout(&header_with_number)?;
1229
1230        let rows = self.get_page_data(page_name)?.get_rows();
1231        for (index, row) in rows.iter().enumerate() {
1232            let row_string = row
1233                .iter()
1234                .enumerate()
1235                .map(|(i, cell)| format!("[{}]:{}", i, cell))
1236                .collect::<Vec<_>>()
1237                .join("");
1238            utils::write_to_stdout(&format!("{: <digits_count$} | {}\n", index, row_string))?;
1239        }
1240
1241        Ok(())
1242    }
1243
1244    pub fn limit_column_from_args(&mut self, page_name: &str, args: &[String]) -> CedResult<()> {
1245        if self.get_page_data(page_name)?.is_array() {
1246            return Err(CedError::InvalidPageOperation(
1247                "Cannot set limiter in array mode".to_string(),
1248            ));
1249        }
1250
1251        if args.is_empty() {
1252            self.add_limiter_prompt(page_name)?;
1253        } else {
1254            let args = if args.len() == 1 {
1255                args[0].split(',').collect::<Vec<_>>()
1256            } else {
1257                return Err(CedError::CommandError(
1258                    "Incorrect arguments for limit".to_string(),
1259                ));
1260            };
1261            if args.len() != LIMITER_ATTRIBUTE_LEN + 2 {
1262                println!("{:#?}", args);
1263                return Err(CedError::CommandError(
1264                    "Incorrect arguments for limit, needs 6 values".to_string(),
1265                ));
1266            }
1267
1268            let column_name = args.first().unwrap();
1269            let force_update = args.last().unwrap();
1270
1271            let force = if force_update.is_empty() {
1272                true
1273            } else {
1274                force_update.parse::<bool>().map_err(|_| {
1275                    CedError::CommandError(
1276                        "You need to feed boolean value for the force value".to_string(),
1277                    )
1278                })?
1279            };
1280
1281            let limiter = ValueLimiter::from_line(&args[1..=LIMITER_ATTRIBUTE_LEN])?;
1282
1283            self.set_limiter(page_name, column_name, &limiter, !force)?;
1284            self.log(&format!("Limited column \"{}\"\n", column_name))?;
1285        }
1286        Ok(())
1287    }
1288
1289    #[cfg(feature = "cli")]
1290    pub fn limit_preset(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
1291        if args.len() < 2 {
1292            return Err(CedError::CommandError(
1293                "Limit-preset needs column and preset_name".to_owned(),
1294            ));
1295        }
1296        let column = &args[0];
1297        let preset_name = &args[1];
1298        let mut panic = true;
1299        if args.len() >= 3 {
1300            panic = !args[2].parse::<bool>().map_err(|_| {
1301                CedError::CommandError(
1302                    "You need to feed boolean value for the force value".to_string(),
1303                )
1304            })?;
1305        }
1306        self.set_limiter_from_preset(page_name, column, preset_name, panic)?;
1307        Ok(())
1308    }
1309
1310    pub fn execute_from_file(&mut self, args: &Vec<String>) -> CedResult<()> {
1311        if args.is_empty() {
1312            return Err(CedError::CommandError(
1313                "Execute needs a file to read from".to_string(),
1314            ));
1315        }
1316        let file = &args[0];
1317        let content = std::fs::read_to_string(file).map_err(|err| {
1318            CedError::io_error(
1319                err,
1320                &format!("Failed to read file \"{}\" for execution", file),
1321            )
1322        })?;
1323        // Split by line
1324        for (idx, line) in content.lines().enumerate() {
1325            // Split by semi colon
1326            for comm in line.split_terminator(';') {
1327                if let Err(err) = self.execute_command(&Command::from_str(comm)?) {
1328                    utils::write_to_stderr(&format!(
1329                        "Line : {} -> Failed to execute command : \"{}\"\n",
1330                        idx + 1,
1331                        comm
1332                    ))?;
1333                    return Err(err);
1334                }
1335            }
1336        }
1337
1338        Ok(())
1339    }
1340
1341    fn add_limiter_prompt(&mut self, page_name: &str) -> CedResult<()> {
1342        let limiter_prompts = vec![
1343            "Column = ",
1344            "Type (Text|Number) = ",
1345            "Default = ",
1346            "Variants(a b c) = ",
1347            "Pattern = ",
1348            "Force update(default=true) = ",
1349        ];
1350        let mut limiter_attributes = vec![];
1351
1352        // Print columns before limiter prompt
1353        let page = self.pages.get(page_name);
1354        match page {
1355            Some(page) => {
1356                let columns = page.get_columns();
1357                if columns.is_empty() {
1358                    utils::write_to_stdout(": Csv is empty : \n")?;
1359                    return Ok(());
1360                }
1361
1362                let columns = columns
1363                    .iter()
1364                    .map(|c| c.name.as_str())
1365                    .collect::<Vec<_>>()
1366                    .join(",");
1367                utils::write_to_stdout(&format!(": --{}-- :\n", columns))?;
1368            }
1369            None => {
1370                utils::write_to_stdout(": Csv is empty : \n")?;
1371                return Ok(());
1372            }
1373        }
1374
1375        for prompt in limiter_prompts {
1376            utils::write_to_stdout(prompt)?;
1377            let input = utils::read_stdin(true)?;
1378
1379            // Interrupt
1380            if input == DEFAULT_DELIMITER {
1381                return Ok(());
1382            }
1383
1384            limiter_attributes.push(input);
1385        }
1386
1387        // +2 is necessary because given input also includes target column & force update
1388        if limiter_attributes.len() != LIMITER_ATTRIBUTE_LEN + 2 {
1389            return Err(CedError::InvalidPageOperation(format!(
1390                "Limit needs \"{}\" arguments but given \"{}\"",
1391                LIMITER_ATTRIBUTE_LEN + 2,
1392                limiter_attributes.len()
1393            )));
1394        }
1395
1396        let column_name = limiter_attributes.first().unwrap();
1397        let force_update = limiter_attributes.last().unwrap();
1398
1399        let force = if force_update.is_empty() {
1400            true
1401        } else {
1402            force_update.parse::<bool>().map_err(|_| {
1403                CedError::CommandError(
1404                    "You need to feed boolean value for the force value".to_string(),
1405                )
1406            })?
1407        };
1408
1409        let limiter = ValueLimiter::from_line(&limiter_attributes[1..=LIMITER_ATTRIBUTE_LEN])?;
1410        self.set_limiter(page_name, column_name, &limiter, !force)?;
1411        self.log(&format!("Limited column \"{}\"\n", column_name))?;
1412        Ok(())
1413    }
1414}