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#[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
63impl 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 }
115 };
116 Ok(command_type)
117 }
118}
119
120#[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#[allow(dead_code)]
151enum CommandResult {
152 Cell(usize, usize, Value),
153 Rows(Vec<Row>),
154 Columns(Vec<Column>),
155}
156
157#[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, 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 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 self.drain_history();
207
208 self.memento_history
209 .push(HistoryRecord::new(data.clone(), command));
210 if self.memento_history.len() > self.history_capacity {
212 self.memento_history.rotate_left(1);
213 self.memento_history.pop();
214 } else {
215 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 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
267impl Processor {
269 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 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 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 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 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 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 #[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 0 => {}
449 1 => {
450 start_index = args[0].parse::<usize>().map_err(|_| {
452 CedError::CommandError(format!("\"{}\" is not a valid row number", args[0]))
453 })?;
454 }
455 _ => {
456 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 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 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 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 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 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 #[cfg(feature = "cli")]
612 fn loop_value_check(value: &mut Value, type_mismatch: &mut bool) -> bool {
613 if let Value::Text(content) = value {
616 if content == DEFAULT_DELIMITER {
617 return true;
618 }
619
620 if !utils::is_valid_csv(content) {
622 *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 #[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 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 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 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 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 let mut line_ending = None;
890 if args.len() > 2 && &args[2].to_lowercase() == "cr" {
891 line_ending = Some('\r');
892 }
893
894 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 fn print(&mut self, page_name: &str, args: &Vec<String>) -> CedResult<()> {
1014 let mut viewer = vec![];
1015 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 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 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 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 fn print_virtual_container(&self, page_name: &str) -> CedResult<()> {
1206 let page = self.get_page_data(page_name)?;
1207 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 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 for (idx, line) in content.lines().enumerate() {
1325 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 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 if input == DEFAULT_DELIMITER {
1381 return Ok(());
1382 }
1383
1384 limiter_attributes.push(input);
1385 }
1386
1387 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}