1use std::fs::File;
26use std::io::Write;
27use std::path::Path;
28
29#[cfg(feature = "cli")]
30use crate::cli::preset::Preset;
31use crate::error::{CedError, CedResult};
32use crate::page::Page;
33use crate::utils;
34use dcsv::Column;
35use dcsv::{Value, ValueLimiter, ValueType};
36use std::collections::HashMap;
37
38pub struct Processor {
43 pub(crate) pages: HashMap<String, Page>,
44 pub(crate) cursor: Option<String>,
45 pub(crate) print_logs: bool,
46 #[cfg(feature = "cli")]
47 preset: Preset,
48 #[cfg(feature = "cli")]
49 pub(crate) no_loop: bool,
50}
51
52impl Default for Processor {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl Processor {
59 pub fn new() -> Self {
61 Self {
62 pages: HashMap::new(),
63 cursor: None,
64 print_logs: true,
65 #[cfg(feature = "cli")]
66 preset: Preset::empty(),
67 #[cfg(feature = "cli")]
68 no_loop: false,
69 }
70 }
71
72 pub fn change_cursor(&mut self, page_name: &str) -> bool {
77 if !self.pages.contains_key(page_name) {
78 false
79 } else {
80 self.cursor = Some(page_name.to_owned());
81 true
82 }
83 }
84
85 pub fn get_cursor(&self) -> Option<String> {
87 self.cursor.as_ref().map(|s| s.to_string())
88 }
89
90 pub fn add_page(
100 &mut self,
101 page: &str,
102 data: &str,
103 has_header: bool,
104 line_ending: Option<char>,
105 raw_mode: bool,
106 ) -> CedResult<()> {
107 if self.pages.contains_key(page) {
108 return Err(CedError::InvalidPageOperation(format!(
109 "\"{}\" already exists",
110 page
111 )));
112 } else {
113 let mut ignore_empty_row = true;
114 if let Ok(val) = std::env::var("CED_READ_STRICT") {
115 if val.to_lowercase() == "true" {
116 ignore_empty_row = false;
117 }
118 }
119 let mut reader = dcsv::Reader::new()
120 .use_line_delimiter(line_ending.unwrap_or('\n'))
121 .has_header(has_header)
122 .ignore_empty_row(ignore_empty_row);
123
124 let page_data = if raw_mode {
125 Page::new_array(reader.array_from_stream(data.as_bytes())?)
126 } else {
127 Page::new_data(reader.data_from_stream(data.as_bytes())?)
128 };
129 self.pages.insert(page.to_owned(), page_data);
130 self.cursor = Some(page.to_owned());
131 Ok(())
132 }
133 }
134
135 pub fn remove_page(&mut self, page_name: &str) {
139 self.pages.remove_entry(page_name);
140 }
141
142 pub fn contains_page(&self, page: &str) -> bool {
144 self.pages.contains_key(page)
145 }
146
147 pub(crate) fn get_page_data_mut(&mut self, page: &str) -> CedResult<&mut Page> {
153 self.pages.get_mut(page).ok_or_else(|| {
154 CedError::InvalidPageOperation(format!(
155 "Cannot get page from cursor which is \"{:?}\"",
156 self.cursor
157 ))
158 })
159 }
160
161 pub(crate) fn get_page_data(&self, page: &str) -> CedResult<&Page> {
163 self.pages.get(page).ok_or_else(|| {
164 CedError::InvalidPageOperation(format!(
165 "Cannot get page from cursor which is \"{:?}\"",
166 self.cursor
167 ))
168 })
169 }
170
171 pub(crate) fn log(&self, log: &str) -> CedResult<()> {
172 if self.print_logs {
173 utils::write_to_stdout(log)?;
174 }
175 Ok(())
176 }
177
178 pub fn drop_pages(&mut self) -> CedResult<()> {
180 self.pages.clear();
181 self.cursor = None;
182 Ok(())
183 }
184
185 pub fn import_from_file(
196 &mut self,
197 path: impl AsRef<Path>,
198 has_header: bool,
199 line_ending: Option<char>,
200 raw_mode: bool,
201 ) -> CedResult<()> {
202 let content = std::fs::read_to_string(&path).map_err(|err| {
203 CedError::io_error(
204 err,
205 &format!("Failed to import file \"{}\"", path.as_ref().display()),
206 )
207 })?;
208 let page_name = &path.as_ref().display().to_string();
209
210 self.add_page(page_name, &content, has_header, line_ending, raw_mode)?;
211
212 self.pages
214 .get_mut(page_name)
215 .unwrap()
216 .set_source_file(path.as_ref().to_owned());
217 Ok(())
218 }
219
220 pub fn write_to_file(&self, page: &str, file: impl AsRef<Path>) -> CedResult<()> {
222 let mut file = File::create(file)
223 .map_err(|err| CedError::io_error(err, "Failed to open file for write"))?;
224
225 file.write_all(self.get_page_as_string(page)?.as_bytes())
226 .map_err(|err| CedError::io_error(err, "Failed to write csv content to file"))?;
227 Ok(())
228 }
229
230 pub fn overwrite_to_file(&self, page: &str, cache: bool) -> CedResult<bool> {
234 let page = self.get_page_data(page)?;
235 let file = page.source_file.as_ref();
236 if file.is_none() {
237 return Ok(false);
238 }
239
240 let file = file.unwrap();
241 let csv = page.to_string();
242 if cache {
244 std::fs::copy(file, std::env::temp_dir().join("cache.csv"))
245 .map_err(|err| CedError::io_error(err, "Failed to create cache for overwrite"))?;
246 }
247 std::fs::write(file, csv.as_bytes())
248 .map_err(|err| CedError::io_error(err, "Failed to overwrite file with content"))?;
249 Ok(true)
250 }
251
252 pub fn edit_cell(&mut self, page: &str, x: usize, y: usize, input: &str) -> CedResult<()> {
254 self.get_page_data_mut(page)?
255 .set_cell_from_string(x, y, input)?;
256 Ok(())
257 }
258
259 pub fn edit_column(&mut self, page: &str, column: &str, input: &str) -> CedResult<()> {
263 self.get_page_data_mut(page)?
264 .set_column(column, Value::Text(input.to_owned()))?;
265 Ok(())
266 }
267
268 pub fn edit_row(
278 &mut self,
279 page: &str,
280 row_index: usize,
281 input: &[Option<Value>],
282 ) -> CedResult<()> {
283 self.get_page_data_mut(page)?.edit_row(row_index, input)?;
284 Ok(())
285 }
286
287 pub fn set_row(&mut self, page: &str, row_index: usize, input: &[Value]) -> CedResult<()> {
292 self.get_page_data_mut(page)?.set_row(row_index, input)?;
293 Ok(())
294 }
295
296 pub fn set_row_from_string_array(
301 &mut self,
302 page: &str,
303 row_index: usize,
304 input: &[impl AsRef<str>],
305 ) -> CedResult<()> {
306 self.get_page_data_mut(page)?.set_row(
307 row_index,
308 &input
309 .iter()
310 .map(|s| Value::Text(s.as_ref().to_owned()))
311 .collect::<Vec<_>>(),
312 )?;
313 Ok(())
314 }
315
316 pub fn add_row(
326 &mut self,
327 page: &str,
328 row_index: usize,
329 values: Option<&[Value]>,
330 ) -> CedResult<()> {
331 self.get_page_data_mut(page)?
332 .insert_row(row_index, values)?;
333 Ok(())
334 }
335
336 pub fn add_row_from_string_array(
346 &mut self,
347 page: &str,
348 row_index: usize,
349 src: &[impl AsRef<str>],
350 ) -> CedResult<()> {
351 let values = src
352 .iter()
353 .map(|a| Value::Text(a.as_ref().to_string()))
354 .collect::<Vec<Value>>();
355 self.add_row(page, row_index, Some(&values))?;
356 Ok(())
357 }
358
359 pub fn add_column(
361 &mut self,
362 page: &str,
363 column_index: usize,
364 column_name: &str,
365 column_type: ValueType,
366 limiter: Option<ValueLimiter>,
367 placeholder: Option<Value>,
368 ) -> CedResult<()> {
369 self.get_page_data_mut(page)?.insert_column_with_type(
370 column_index,
371 column_name,
372 column_type,
373 limiter,
374 placeholder,
375 )?;
376 Ok(())
377 }
378
379 pub fn remove_row(&mut self, page: &str, row_index: usize) -> CedResult<bool> {
381 Ok(self.get_page_data_mut(page)?.delete_row(row_index))
382 }
383
384 pub fn remove_column(&mut self, page: &str, column_index: usize) -> CedResult<()> {
386 self.get_page_data_mut(page)?.delete_column(column_index)?;
387 Ok(())
388 }
389
390 pub fn add_column_array(&mut self, page: &str, columns: &[impl AsRef<str>]) -> CedResult<()> {
394 for col in columns {
395 let column_count = self.get_page_data_mut(page)?.get_column_count();
396 self.add_column(
397 page,
398 column_count,
399 col.as_ref(),
400 ValueType::Text,
401 None,
402 None,
403 )?;
404 }
405 Ok(())
406 }
407
408 pub fn move_row(&mut self, page: &str, src: usize, target: usize) -> CedResult<()> {
410 self.get_page_data_mut(page)?.move_row(src, target)?;
411 Ok(())
412 }
413
414 pub fn move_column(&mut self, page: &str, src: usize, target: usize) -> CedResult<()> {
416 self.get_page_data_mut(page)?.move_column(src, target)?;
417 Ok(())
418 }
419
420 pub fn rename_column(&mut self, page: &str, column: &str, new_name: &str) -> CedResult<()> {
422 let page = self.get_page_data_mut(page)?;
423 if let Some(column) = page.try_get_column_index(column) {
424 page.rename_column(column, new_name)?;
425 } else {
426 return Err(CedError::OutOfRangeError);
427 }
428 Ok(())
429 }
430
431 pub fn export_schema(&self, page: &str) -> CedResult<String> {
433 let page = self.get_page_data(page)?;
434 if page.is_array() {
435 return Err(CedError::InvalidPageOperation(String::from(
436 "Cannot export schmea from virtual array",
437 )));
438 }
439 if !page.is_array() {
440 Ok(page.get_data().unwrap().export_schema())
442 } else {
443 Err(CedError::InvalidPageOperation(
444 "Cannot export schmea when csv is imported as array".to_string(),
445 ))
446 }
447 }
448
449 pub fn set_schema(&mut self, page: &str, path: impl AsRef<Path>, panic: bool) -> CedResult<()> {
458 if self.get_page_data_mut(page)?.is_array() {
459 return Err(CedError::InvalidPageOperation(
460 "Cannot set schema in array mode".to_string(),
461 ));
462 }
463
464 let content = std::fs::read_to_string(&path).map_err(|err| {
465 CedError::io_error(
466 err,
467 &format!("Failed to import file \"{}\"", path.as_ref().display()),
468 )
469 })?;
470 let mut content = content.lines();
471
472 let header = content.next();
473 if header.is_none() {
474 return Err(CedError::InvalidRowData(
475 "Given file does not have a header".to_string(),
476 ));
477 }
478
479 let mut row = content.next();
480 while let Some(row_src) = row {
481 let row_args = dcsv::utils::csv_row_to_vector(row_src, None, false);
482 let limiter = ValueLimiter::from_line(&row_args[1..].to_vec())?;
483 self.set_limiter(page, &row_args[0], &limiter, panic)?;
484 row = content.next();
485 }
486 Ok(())
487 }
488
489 pub fn set_limiter(
499 &mut self,
500 page: &str,
501 column: &str,
502 limiter: &ValueLimiter,
503 panic: bool,
504 ) -> CedResult<()> {
505 if self.get_page_data(page)?.is_array() {
506 return Err(CedError::InvalidPageOperation(String::from(
507 "Cannot set limiter for virtual array",
508 )));
509 }
510 let column = self
511 .get_page_data_mut(page)?
512 .try_get_column_index(column)
513 .ok_or_else(|| CedError::InvalidColumn(format!("{} is not a valid column", column)))?;
514 self.get_page_data_mut(page)?
515 .set_limiter(column, limiter, panic)?;
516 Ok(())
517 }
518
519 #[cfg(feature = "cli")]
522 pub(crate) fn configure_preset(&mut self, use_defualt: bool) -> CedResult<()> {
523 self.preset = Preset::new(use_defualt)?;
524 Ok(())
525 }
526
527 #[cfg(feature = "cli")]
528 pub(crate) fn set_limiter_from_preset(
529 &mut self,
530 page: &str,
531 column: &str,
532 preset_name: &str,
533 panic: bool,
534 ) -> CedResult<()> {
535 let preset = self.preset.get(preset_name).cloned();
536 if let Some(limiter) = preset {
537 self.set_limiter(page, column, &limiter, panic)?;
538 }
539 Ok(())
540 }
541
542 pub fn get_row_count(&self, page: &str) -> CedResult<usize> {
544 Ok(self.get_page_data(page)?.get_row_count())
545 }
546
547 pub fn get_column_count(&self, page: &str) -> CedResult<usize> {
548 Ok(self.get_page_data(page)?.get_column_count())
549 }
550
551 pub fn last_row_index(&self, page: &str) -> CedResult<usize> {
553 Ok(self.get_page_data(page)?.get_row_count().max(1) - 1)
554 }
555
556 pub fn last_column_index(&self, page: &str) -> CedResult<usize> {
558 Ok(self.get_page_data(page)?.get_column_count().max(1) - 1)
559 }
560
561 pub fn get_page_as_string(&self, page: &str) -> CedResult<String> {
563 Ok(self.get_page_data(page)?.to_string())
564 }
565
566 pub fn get_cell(
570 &self,
571 page: &str,
572 row_index: usize,
573 column_index: usize,
574 ) -> CedResult<Option<&Value>> {
575 Ok(self.get_page_data(page)?.get_cell(row_index, column_index))
576 }
577
578 pub fn get_column(&self, page: &str, column_index: usize) -> CedResult<Option<&Column>> {
582 let page = self.get_page_data(page)?;
583 Ok(page.get_columns().get(column_index))
584 }
585
586 pub fn get_column_by_name(&self, page: &str, column_name: &str) -> CedResult<Option<&Column>> {
590 let page = self.get_page_data(page)?;
591 Ok(match page.try_get_column_index(column_name) {
592 Some(index) => page.get_columns().get(index),
593 None => None,
594 })
595 }
596}