1use std::path::PathBuf;
2
3use chrono;
4
5use crate::lib::{
6 job::{self},
7 logic::{self},
8};
9
10pub const IS_DEBUG: bool = true; #[derive(Debug, Clone)]
18pub struct ValidResultMainRow {
19 pub dt_modified: chrono::DateTime<chrono::Local>,
20 pub name_with_ext: String,
21 pub size_real: u64,
22 pub node: logic::ScannedNode,
23}
24
25#[derive(Debug, Clone)]
26pub struct ValidResultMainTab {
27 pub rows: Vec<ValidResultMainRow>,
28 pub tier_max: usize,
29 pub name_len_max: usize,
30 pub path_len_max: usize,
31}
32#[derive(Debug, Clone)]
98pub struct ValidPreparedJobConfig {
99 pub exec: ValidExecutionConfig,
100 pub save: ValidSaveAsConfig,
101 pub part: ValidTablePartConfig,
102 pub item: ValidColumnItemConfig,
103 pub cols: ValidTableColumnsConfig,
104 pub size: ValidColumnSizeConfig,
105 pub date: ValidColumnDateConfig,
106 pub time: ValidColumnTimeConfig,
107 pub patt: ValidPatternConfig,
108 pub work: ValidWorkspaceConfig,
109 pub sort: ValidSortByConfig,
110}
111impl Default for ValidPreparedJobConfig {
112 fn default() -> Self {
113 let blank: Vec<&str> = Vec::new();
114
115 Self {
116 exec: ValidExecutionParse::parse_vec_as_config(&blank),
118 work: ValidWorkspaceParse::parse_vec_as_config(["."]),
120 patt: ValidPatternParse::parse_vec_as_config(&blank),
122 part: ValidTablePartParse::parse_vec_as_config(&blank),
124 item: ValidColumnItemParse::parse_vec_as_config(["list-tree", "name-show", "ws-show", "icons-lite"]),
126 cols: ValidTableColumnsParse::parse_vec_as_config(&blank),
128 date: ValidColumnDateParse::parse_vec_as_config(&blank),
130 time: ValidColumnTimeParse::parse_vec_as_config(&blank),
132 size: ValidColumnSizeParse::parse_vec_as_config(&blank),
134 sort: ValidSortByParse::parse_vec_as_config(&blank),
136 save: ValidSaveAsParse::parse_vec_as_config(&blank),
138 }
139 }
140}
141#[derive(Debug, Clone, PartialEq, Eq)]
146pub enum ValidExecutionParse {
147 Quiet(bool),
148 Mute(bool),
149 OnlyDry(bool),
150 Debug(bool),
151 SaveAs(bool),
152 CliColor(bool),
153}
154#[derive(Debug, Clone)]
155pub struct ValidExecutionConfig {
156 pub quiet: bool,
157 pub mute: bool,
158 pub only_dry: bool,
159 pub debug: bool,
160 pub save_as: bool,
161 pub cli_color: bool,
162}
163#[derive(Debug, Clone, PartialEq, Eq)]
164pub enum ValidSaveAsParse {
165 OutDir(String),
166 Title(String),
167 Name(String),
168 NameIsPrefix(bool),
169}
170#[derive(Debug, Clone)]
171pub struct ValidSaveAsConfig {
172 pub raw: KeepRawForValiSaveAs,
173 pub out_dir: PathBuf,
174 pub title_index_sotc: String,
175 pub name_index_sotc: String,
176 pub title_files_cots: String,
177 pub name_files_cots: String,
178}
179#[derive(Debug, Clone)]
180pub enum ValidTablePartParse {
181 MD,
182 MF,
183 XD,
184 XF,
185}
186#[derive(Debug, Clone)]
187pub struct ValidTablePartConfig {
188 pub md: bool,
189 pub mf: bool,
190 pub xd: bool,
191 pub xf: bool,
192}
193#[derive(Debug, Clone)]
194pub enum ValidColumnItemParse {
195 ListNone,
196 ListTree,
197 ListFlat,
198 IconsNone,
199 IconsLite,
200 IconsMore,
201
202 NumPrefix,
203 NumSuffix,
204 NameNone,
205 NameShow,
206 WhitespaceTrailNone,
207 WhitespaceTrailShow,
208}
209#[derive(Debug, Clone)]
210pub struct ValidColumnItemConfig {
211 pub list: ModeListForValidColumnItem,
212 pub icons: ModeIconsForValidColumnItem,
213 pub name: bool,
214 pub align_end: bool,
215 pub num_is_first: bool,
216}
217#[derive(Debug, Clone, PartialEq, Eq)]
218pub enum ValidTableColumnsParse {
219 Date,
220 Time,
221 Size,
222 Item,
223 Path,
224}
225#[derive(Debug, Clone)]
226pub struct ValidTableColumnsConfig {
227 pub columns: Vec<ValidTableColumnsParse>,
228}
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum ValidColumnSizeParse {
231 Decimal, Binary, }
234#[derive(Debug, Clone)]
235pub struct ValidColumnSizeConfig {
236 pub mode: ValidColumnSizeParse,
237}
238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239pub enum ValidColumnDateParse {
240 Default,
241}
242#[derive(Debug, Clone)]
243pub struct ValidColumnDateConfig {
244 pub format: String,
245}
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
247pub enum ValidColumnTimeParse {
248 Default,
249}
250#[derive(Debug, Clone)]
251pub struct ValidColumnTimeConfig {
252 pub format: String,
253}
254#[derive(Debug, Clone)]
255pub struct ValidPatternParse;
256#[derive(Debug, Clone)]
257pub struct ValidPatternConfig {
258 pub patterns: Vec<String>,
259}
260#[derive(Debug, Clone)]
261pub struct ValidWorkspaceParse;
262#[derive(Debug, Clone)]
263pub struct ValidWorkspaceConfig {
264 pub workspace_raw: String,
265 pub workspace_dir: PathBuf,
266 pub execution_dir: PathBuf,
267 pub ignore_case: bool,
268}
269#[derive(Debug, Clone)]
270pub struct ValidSortByParse;
271#[derive(Debug, Clone, PartialEq, Eq)]
272pub enum ModeFileGroupForValidSortBy {
273 Name,
274 Exte,
275 None,
276}
277
278#[derive(Debug, Clone, PartialEq, Eq)]
283pub enum ModeListForValidColumnItem {
284 None,
285 Flat,
286 Tree,
287}
288#[derive(Debug, Clone, PartialEq, Eq)]
289pub enum ModeIconsForValidColumnItem {
290 Lite,
291 More,
292 None,
293}
294#[derive(Debug, Clone, PartialEq, Eq)]
295pub struct KeepRawForValiSaveAs {
296 pub path: String,
297 pub title: String,
298 pub name: String,
299 pub prefix: bool,
300}
301#[derive(Debug, Clone)]
302pub enum StrategyForValidSortBy {
303 Date { reverse: bool },
304 Size { reverse: bool },
305 Name { mode: String, reverse: bool, file_group: ModeFileGroupForValidSortBy },
306 Path { mode: String, reverse: bool, dir_split: bool, file_group: ModeFileGroupForValidSortBy },
307}
308#[derive(Debug, Clone)]
309pub struct ValidSortByConfig {
310 pub strategy: StrategyForValidSortBy,
311}
312
313pub trait ParseFromFlags: Sized {
318 type Config;
320
321 fn parse_vec_as_config<I>(inputs: I) -> Self::Config
323 where
324 I: IntoIterator,
325 I::Item: AsRef<str>;
326 fn parse(s: &str) -> Result<Self, String>;
329}
330impl ParseFromFlags for ValidExecutionParse {
331 type Config = ValidExecutionConfig;
332
333 fn parse(s: &str) -> Result<Self, String> {
334 let lower = s.trim().to_lowercase();
335 match lower.as_str() {
336 "quiet" | "q" => Ok(ValidExecutionParse::Quiet(true)),
337 "mute" | "cli-off" | "silent" => Ok(ValidExecutionParse::Mute(true)),
338 "dry" | "dry-run" => Ok(ValidExecutionParse::OnlyDry(true)),
339 "debug" | "verbose" => Ok(ValidExecutionParse::Debug(true)),
340 "save" | "save-as" => Ok(ValidExecutionParse::SaveAs(true)),
341 "color" | "colors" => Ok(ValidExecutionParse::CliColor(true)),
342 _ => Err(format!("Nieznany ValidExecutionParse: '{}'", s)),
343 }
344 }
345
346 fn parse_vec_as_config<I>(inputs: I) -> Self::Config
347 where
348 I: IntoIterator,
349 I::Item: AsRef<str>, {
350 let mut quiet = false;
352 let mut mute = false;
353 let mut only_dry = false;
354 let mut debug = true;
355 let mut save_as = true;
356 let mut cli_color = true;
357
358 for item in inputs {
359 if let Ok(parsed) = Self::parse(item.as_ref()) {
360 match parsed {
361 ValidExecutionParse::Quiet(v) => quiet = v,
362 ValidExecutionParse::Mute(v) => mute = v,
363 ValidExecutionParse::OnlyDry(v) => only_dry = v,
364 ValidExecutionParse::Debug(v) => debug = v,
365 ValidExecutionParse::SaveAs(v) => save_as = v,
366 ValidExecutionParse::CliColor(v) => cli_color = v,
367 }
368 }
369 }
370 if mute {
371 quiet = true;
372 }
373 ValidExecutionConfig { quiet, mute, only_dry, debug, save_as, cli_color }
374 }
375}
376impl ParseFromFlags for ValidSaveAsParse {
377 type Config = ValidSaveAsConfig;
378 fn parse(s: &str) -> Result<Self, String> {
379 let lower = s.trim().to_lowercase();
380
381 if lower.starts_with("outdir=") || lower.starts_with("out_dir=") {
382 let val = s.split_once('=').unwrap().1.trim();
383 Ok(ValidSaveAsParse::OutDir(val.to_string()))
384 } else if lower.starts_with("title=") {
385 let val = s.split_once('=').unwrap().1.trim();
386 Ok(ValidSaveAsParse::Title(val.to_string()))
387 } else if lower.starts_with("name=") {
388 let val = s.split_once('=').unwrap().1.trim();
389 Ok(ValidSaveAsParse::Name(val.to_string()))
390 } else if lower == "name-prefix" || lower == "name_prefix" {
391 Ok(ValidSaveAsParse::NameIsPrefix(true))
392 } else if lower == "name-suffix" || lower == "name_suffix" {
393 Ok(ValidSaveAsParse::NameIsPrefix(false))
394 } else {
395 Err(format!("Nieznany ValidSaveAsParse: '{}'", s.trim()))
396 }
397 }
398 fn parse_vec_as_config<I>(inputs: I) -> Self::Config
399 where
400 I: IntoIterator,
401 I::Item: AsRef<str>, {
402 let mut out_dir_str = "./target/.cargo-plot/".to_string();
404 let mut title = "Project Snapshot".to_string();
405 let mut name = "".to_string();
406 let mut name_is_prefix = false;
407
408 for item in inputs {
409 if let Ok(parsed) = Self::parse(item.as_ref()) {
410 match parsed {
411 ValidSaveAsParse::OutDir(v) => out_dir_str = v,
412 ValidSaveAsParse::Title(v) => title = v,
413 ValidSaveAsParse::Name(v) => name = v,
414 ValidSaveAsParse::NameIsPrefix(v) => name_is_prefix = v,
415 }
416 }
417 }
418
419 let raw = KeepRawForValiSaveAs {
421 path: out_dir_str.clone(),
422 title: title.clone(),
423 name: name.clone(),
424 prefix: name_is_prefix,
425 };
426
427 let out_dir = std::path::PathBuf::from(&out_dir_str);
429
430 let time_tag = crate::lib::logic::tag_time().0;
432
433 let title_index_sotc =
435 format!("# {}: {} (v:{}{}OT{} [{} of the {}])", "INDEX", title, time_tag, "S", "C", "Structure", "content");
436 let title_files_cots =
437 format!("# {}: {} (v:{}{}OT{} [{} of the {}])", "FILES", title, time_tag, "C", "S", "Content", "structure");
438
439 let name_index_sotc = if name.is_empty() {
441 format!("{}{}.md", time_tag, "SOTC-INDEX")
442 } else if name_is_prefix {
443 format!("{}_{}{}.md", name, time_tag, "SOTC-INDEX")
444 } else {
445 format!("{}{}_{}.md", time_tag, "SOTC-INDEX", name)
446 };
447
448 let name_files_cots = if name.is_empty() {
449 format!("{}{}.md", time_tag, "COTS-FILES")
450 } else if name_is_prefix {
451 format!("{}_{}{}.md", name, time_tag, "COTS-FILES")
452 } else {
453 format!("{}{}_{}.md", time_tag, "COTS-FILES", name)
454 };
455
456 ValidSaveAsConfig { raw, out_dir, title_index_sotc, title_files_cots, name_index_sotc, name_files_cots }
458 }
459}
460impl ParseFromFlags for ValidTablePartParse {
461 type Config = ValidTablePartConfig;
462 fn parse(s: &str) -> Result<Self, String> {
463 match s.trim().to_lowercase().as_str() {
464 "MD" | "md" | "match-dirs" => Ok(ValidTablePartParse::MD),
465 "MF" | "mf" | "match-files" => Ok(ValidTablePartParse::MF),
466 "XD" | "xd" | "mismatch-dirs" => Ok(ValidTablePartParse::XD),
467 "XF" | "xf" | "mismatch-files" => Ok(ValidTablePartParse::XF),
468 _ => Err(format!("Nieprawidłowa nazwa: '{}'. Dostępne: md, mf, xd, xf", s.trim())),
469 }
470 }
471 fn parse_vec_as_config<I>(inputs: I) -> Self::Config
472 where
473 I: IntoIterator,
474 I::Item: AsRef<str>, {
475 let mut md = false;
476 let mut mf = false;
477 let mut xd = false;
478 let mut xf = false;
479
480 let mut has_any_valid = false;
481
482 for item in inputs {
483 if let Ok(parsed) = Self::parse(item.as_ref()) {
485 has_any_valid = true;
486 match parsed {
487 ValidTablePartParse::MD => md = true,
488 ValidTablePartParse::MF => mf = true,
489 ValidTablePartParse::XD => xd = true,
490 ValidTablePartParse::XF => xf = true,
491 }
492 }
493 }
494
495 if !has_any_valid {
497 md = true;
498 mf = true;
499 }
500
501 ValidTablePartConfig { md, mf, xd, xf }
502 }
503}
504impl ParseFromFlags for ValidColumnItemParse {
505 type Config = ValidColumnItemConfig;
506 fn parse(s: &str) -> Result<Self, String> {
507 match s.trim().to_lowercase().as_str() {
508 "list-none" => Ok(ValidColumnItemParse::ListNone),
509 "list-flat" => Ok(ValidColumnItemParse::ListFlat),
510 "list-tree" => Ok(ValidColumnItemParse::ListTree),
511
512 "icons-lite" => Ok(ValidColumnItemParse::IconsLite),
513 "icons-more" => Ok(ValidColumnItemParse::IconsMore),
514 "icons-none" => Ok(ValidColumnItemParse::IconsNone),
515
516 "num-prefix" => Ok(ValidColumnItemParse::NumPrefix),
517 "num-suffix" => Ok(ValidColumnItemParse::NumSuffix),
518
519 "name-none" => Ok(ValidColumnItemParse::NameNone),
520 "name-show" => Ok(ValidColumnItemParse::NameShow),
521
522 "ws-none" | "whitespace-none" => Ok(ValidColumnItemParse::WhitespaceTrailNone),
523 "ws-show" | "whitespace-show" => Ok(ValidColumnItemParse::WhitespaceTrailShow),
524
525 _ => Err(format!("Nieznany ValidColumnItemParse: '{}'", s.trim())),
526 }
527 }
528 fn parse_vec_as_config<I>(inputs: I) -> Self::Config
529 where
530 I: IntoIterator,
531 I::Item: AsRef<str>, {
532 let items: Vec<ValidColumnItemParse> =
533 inputs.into_iter().filter_map(|s| Self::parse(s.as_ref()).ok()).collect();
534
535 ValidColumnItemConfig::get(items)
536 }
537}
538impl ParseFromFlags for ValidTableColumnsParse {
539 type Config = ValidTableColumnsConfig;
540 fn parse(s: &str) -> Result<Self, String> {
541 match s.trim().to_lowercase().as_str() {
542 "date" => Ok(ValidTableColumnsParse::Date),
543 "time" => Ok(ValidTableColumnsParse::Time),
544 "size" => Ok(ValidTableColumnsParse::Size),
545 "item" => Ok(ValidTableColumnsParse::Item),
546 "path" => Ok(ValidTableColumnsParse::Path),
547 _ => Err(format!("Nieprawidłowa nazwa kolumny: '{}'. Dostępne: date, time, size, item, path", s.trim())),
548 }
549 }
550 fn parse_vec_as_config<I>(inputs: I) -> Self::Config
551 where
552 I: IntoIterator,
553 I::Item: AsRef<str>, {
554 let mut columns = Vec::new();
555
556 let mut has_date = false;
557 let mut has_time = false;
558 let mut has_size = false;
559 let mut has_item = false;
560 let mut has_path = false;
561
562 let mut has_any_valid = false;
563
564 for item in inputs {
565 if let Ok(parsed) = Self::parse(item.as_ref()) {
566 has_any_valid = true;
567 match parsed {
568 ValidTableColumnsParse::Date => {
569 if !has_date {
570 columns.push(ValidTableColumnsParse::Date);
571 has_date = true;
572 }
573 }
574 ValidTableColumnsParse::Time => {
575 if !has_time {
576 columns.push(ValidTableColumnsParse::Time);
577 has_time = true;
578 }
579 }
580 ValidTableColumnsParse::Size => {
581 if !has_size {
582 columns.push(ValidTableColumnsParse::Size);
583 has_size = true;
584 }
585 }
586 ValidTableColumnsParse::Item => {
587 if !has_item {
588 columns.push(ValidTableColumnsParse::Item);
589 has_item = true;
590 }
591 }
592 ValidTableColumnsParse::Path => {
593 if !has_path {
594 columns.push(ValidTableColumnsParse::Path);
595 has_path = true;
596 }
597 }
598 }
599 }
600 }
601
602 if !has_any_valid {
604 return ValidTableColumnsConfig {
605 columns: vec![
606 ValidTableColumnsParse::Date,
607 ValidTableColumnsParse::Time,
608 ValidTableColumnsParse::Size,
609 ValidTableColumnsParse::Item,
610 ValidTableColumnsParse::Path,
611 ],
612 };
613 }
614
615 if !has_item {
617 columns.push(ValidTableColumnsParse::Item);
618 }
619
620 ValidTableColumnsConfig { columns }
621 }
622}
623impl ParseFromFlags for ValidColumnSizeParse {
624 type Config = ValidColumnSizeConfig;
625 fn parse(s: &str) -> Result<Self, String> {
626 match s.trim().to_lowercase().as_str() {
627 "dec" | "decimal" => Ok(ValidColumnSizeParse::Decimal),
628 "bin" | "binary" => Ok(ValidColumnSizeParse::Binary),
629 _ => Err(format!("Nieznany system miar: '{}'. Dostępne: dec, bin", s.trim())),
630 }
631 }
632 fn parse_vec_as_config<I>(inputs: I) -> Self::Config
633 where
634 I: IntoIterator,
635 I::Item: AsRef<str>, {
636 let mut mode = ValidColumnSizeParse::Decimal; for item in inputs {
639 if let Ok(parsed) = Self::parse(item.as_ref()) {
640 mode = parsed;
641 break; }
643 }
644
645 ValidColumnSizeConfig { mode }
646 }
647}
648impl ParseFromFlags for ValidColumnDateParse {
649 type Config = ValidColumnDateConfig;
650 fn parse(s: &str) -> Result<Self, String> {
651 match s.trim().to_lowercase().as_str() {
652 "date-default" | "default" => Ok(ValidColumnDateParse::Default),
653 _ => Err(format!("Nieznany format daty: '{}'", s.trim())),
654 }
655 }
656 fn parse_vec_as_config<I>(inputs: I) -> Self::Config
657 where
658 I: IntoIterator,
659 I::Item: AsRef<str>, {
660 let format = "%Y W%V %u-%a".to_string(); for item in inputs {
663 if let Ok(_) = Self::parse(item.as_ref()) {
664 break;
666 }
667 }
668
669 ValidColumnDateConfig { format }
670 }
671}
672impl ParseFromFlags for ValidColumnTimeParse {
673 type Config = ValidColumnTimeConfig;
674 fn parse(s: &str) -> Result<Self, String> {
675 match s.trim().to_lowercase().as_str() {
676 "time-default" | "default" => Ok(ValidColumnTimeParse::Default),
677 _ => Err(format!("Nieznany format czasu: '{}'", s.trim())),
678 }
679 }
680 fn parse_vec_as_config<I>(inputs: I) -> Self::Config
681 where
682 I: IntoIterator,
683 I::Item: AsRef<str>, {
684 let format = "%H:%M:%S.%3f".to_string(); for item in inputs {
687 if let Ok(_) = Self::parse(item.as_ref()) {
688 break;
689 }
690 }
691
692 ValidColumnTimeConfig { format }
693 }
694}
695impl ParseFromFlags for ValidPatternParse {
696 type Config = ValidPatternConfig;
697 fn parse(s: &str) -> Result<Self, String> { Err(format!("Typ nie wspiera parsowania pojedynczej flagi: '{}'", s)) }
698 fn parse_vec_as_config<I>(inputs: I) -> Self::Config
699 where
700 I: IntoIterator,
701 I::Item: AsRef<str>, {
702 let patterns: Vec<String> = inputs.into_iter().map(|s| s.as_ref().to_string()).collect();
703
704 if patterns.is_empty() {
706 return ValidPatternConfig {
707 patterns: vec![
708 "./{.rustfmt,Cargo,rust-toolchain,Makefile}.toml&/".to_string(),
709 "./**/*.rs&/".to_string(),
710 "!./target/**".to_string(),
711 "!./.git/**".to_string(),
712 "./.{gitattributes,gitignore}".to_string(),
713 "./.github/workflows/*.yml&/".to_string(),
714 "./.vscode/settings.json&/".to_string(),
715 "./{API,ARCHITECTURE,AUTHORS,CHANGELOG,README,ROADMAP,TODO}.md".to_string(),
716 "./dist/{**/*,*}.{bat,exe}&/".to_string(),
717 ],
718 };
720 }
721
722 ValidPatternConfig { patterns }
723 }
724}
725impl ParseFromFlags for ValidWorkspaceParse {
726 type Config = ValidWorkspaceConfig;
727 fn parse(s: &str) -> Result<Self, String> { Err(format!("Typ nie wspiera parsowania pojedynczej flagi: '{}'", s)) }
728 fn parse_vec_as_config<I>(inputs: I) -> Self::Config
729 where
730 I: IntoIterator,
731 I::Item: AsRef<str>, {
732 let mut workspace_raw = ".".to_string();
733 let mut ignore_case = false; let mut dir_set = false;
735
736 for item in inputs {
737 let s = item.as_ref().trim();
738 if s.to_lowercase() == "ignore-case" {
739 ignore_case = true;
740 } else if !s.is_empty() && !dir_set {
741 workspace_raw = s.to_string();
743 dir_set = true;
744 }
745 }
746 let w = std::path::Path::new(&workspace_raw);
747 let execution_dir: PathBuf = std::env::current_dir().expect("Nie można odczytać katalogu roboczego (CWD)");
748
749 let workspace_dir: PathBuf = std::fs::canonicalize(w).unwrap_or_else(|x| {
750 eprintln!("❌ Błąd lokalizacji workspace: {}", x);
751 std::process::exit(1);
752 });
754
755 ValidWorkspaceConfig { workspace_raw, workspace_dir, execution_dir, ignore_case }
756 }
757}
758impl ParseFromFlags for ValidSortByParse {
759 type Config = ValidSortByConfig;
760 fn parse(s: &str) -> Result<Self, String> { Err(format!("Typ nie wspiera parsowania pojedynczej flagi: '{}'", s)) }
761 fn parse_vec_as_config<I>(inputs: I) -> Self::Config
762 where
763 I: IntoIterator,
764 I::Item: AsRef<str>, {
765 let mut strategy_name = "name";
767 let mut reverse = false;
768 let mut dir_split = true;
769 let mut file_group = ModeFileGroupForValidSortBy::None;
770 let mut mode_sort_name = "[Spec][Num][AZaz]".to_string();
771
772 for item in inputs {
774 let s = item.as_ref().trim();
775 let lower = s.to_lowercase();
776
777 match lower.as_str() {
778 "date" => strategy_name = "date",
779 "size" => strategy_name = "size",
780 "name" => strategy_name = "name",
781 "path" => strategy_name = "path",
782
783 "rev" | "reverse" => reverse = true,
784
785 "group-name" => file_group = ModeFileGroupForValidSortBy::Name,
786 "group-exte" => file_group = ModeFileGroupForValidSortBy::Exte,
787 "group-none" => file_group = ModeFileGroupForValidSortBy::None,
788
789 "dir-split-true" => dir_split = true,
790 "dir-split-false" => dir_split = false,
791
792 _ => {
793 if s.starts_with('[') && s.ends_with(']') {
795 mode_sort_name = s.to_string();
796 }
797 }
798 }
799 }
800
801 let strategy = match strategy_name {
803 "date" => StrategyForValidSortBy::Date { reverse },
804 "size" => StrategyForValidSortBy::Size { reverse },
805 "path" => StrategyForValidSortBy::Path { mode: mode_sort_name, reverse, dir_split, file_group },
806 _ => StrategyForValidSortBy::Name { mode: mode_sort_name, reverse, file_group },
807 };
808
809 ValidSortByConfig { strategy }
810 }
811}
812
813pub trait ConfigToFlags {
818 fn to_flags(&self) -> Vec<String>;
820}
821impl ConfigToFlags for ValidExecutionConfig {
822 fn to_flags(&self) -> Vec<String> {
823 let mut f = vec![];
824 if self.quiet {
825 f.push("quiet".to_string());
826 }
827 if self.mute {
828 f.push("mute".to_string());
829 }
830 if self.only_dry {
831 f.push("dry".to_string());
832 }
833 if self.debug {
834 f.push("verbose".to_string());
835 }
836 if self.save_as {
837 f.push("save".to_string());
838 }
839 if self.cli_color {
840 f.push("color".to_string());
841 }
842 f
843 }
844}
845impl ConfigToFlags for ValidSaveAsConfig {
846 fn to_flags(&self) -> Vec<String> {
847 let mut f = vec![];
848 f.push(format!("outdir={}", self.raw.path));
849 f.push(format!("title={}", self.raw.title));
850
851 if !self.raw.name.is_empty() {
852 f.push(format!("name={}", self.raw.name));
853 }
854 if self.raw.prefix {
855 f.push("name-prefix".to_string());
856 } else {
857 f.push("name-suffix".to_string());
858 }
859 f
860 }
861}
862impl ConfigToFlags for ValidTablePartConfig {
863 fn to_flags(&self) -> Vec<String> {
864 let mut f = vec![];
865 if self.md {
866 f.push("MD".to_string());
867 }
868 if self.mf {
869 f.push("MF".to_string());
870 }
871 if self.xd {
872 f.push("XD".to_string());
873 }
874 if self.xf {
875 f.push("XF".to_string());
876 }
877 f
878 }
879}
880impl ConfigToFlags for ValidColumnItemConfig {
881 fn to_flags(&self) -> Vec<String> {
882 let mut f = vec![];
883 match self.list {
884 ModeListForValidColumnItem::Tree => f.push("list-tree".to_string()),
885 ModeListForValidColumnItem::Flat => f.push("list-flat".to_string()),
886 ModeListForValidColumnItem::None => f.push("list-none".to_string()),
887 }
888 match self.icons {
889 ModeIconsForValidColumnItem::Lite => f.push("icons-lite".to_string()),
890 ModeIconsForValidColumnItem::More => f.push("icons-more".to_string()),
891 ModeIconsForValidColumnItem::None => f.push("icons-none".to_string()),
892 }
893 if self.name {
894 f.push("name-show".to_string());
895 } else {
896 f.push("name-none".to_string());
897 }
898 if self.align_end {
899 f.push("ws-show".to_string());
900 } else {
901 f.push("ws-none".to_string());
902 }
903 if self.num_is_first {
904 f.push("num-prefix".to_string());
905 } else {
906 f.push("num-suffix".to_string());
907 }
908 f
909 }
910}
911impl ConfigToFlags for ValidTableColumnsConfig {
912 fn to_flags(&self) -> Vec<String> {
913 self.columns
914 .iter()
915 .map(|c| {
916 match c {
917 ValidTableColumnsParse::Date => "date",
918 ValidTableColumnsParse::Time => "time",
919 ValidTableColumnsParse::Size => "size",
920 ValidTableColumnsParse::Item => "item",
921 ValidTableColumnsParse::Path => "path",
922 }
923 .to_string()
924 })
925 .collect()
926 }
927}
928impl ConfigToFlags for ValidColumnSizeConfig {
929 fn to_flags(&self) -> Vec<String> {
930 match self.mode {
931 ValidColumnSizeParse::Decimal => vec!["dec".to_string()],
932 ValidColumnSizeParse::Binary => vec!["bin".to_string()],
933 }
934 }
935}
936impl ConfigToFlags for ValidColumnDateConfig {
937 fn to_flags(&self) -> Vec<String> {
938 vec!["default".to_string()]
941 }
942}
943impl ConfigToFlags for ValidColumnTimeConfig {
944 fn to_flags(&self) -> Vec<String> { vec!["default".to_string()] }
945}
946impl ConfigToFlags for ValidPatternConfig {
947 fn to_flags(&self) -> Vec<String> { self.patterns.clone() }
948}
949impl ConfigToFlags for ValidWorkspaceConfig {
950 fn to_flags(&self) -> Vec<String> {
951 let mut f = vec![self.workspace_raw.clone()];
952 if self.ignore_case {
953 f.push("ignore-case".to_string());
954 }
955 f
956 }
957}
958impl ConfigToFlags for ValidSortByConfig {
959 fn to_flags(&self) -> Vec<String> {
960 let mut f = vec![];
961 match &self.strategy {
962 StrategyForValidSortBy::Date { reverse } => {
963 f.push("date".to_string());
964 if *reverse {
965 f.push("rev".to_string());
966 }
967 }
968 StrategyForValidSortBy::Size { reverse } => {
969 f.push("size".to_string());
970 if *reverse {
971 f.push("rev".to_string());
972 }
973 }
974 StrategyForValidSortBy::Name { mode, reverse, file_group } => {
975 f.push("name".to_string());
976 f.push(mode.clone());
977 if *reverse {
978 f.push("rev".to_string());
979 }
980 match file_group {
981 ModeFileGroupForValidSortBy::Name => f.push("group-name".to_string()),
982 ModeFileGroupForValidSortBy::Exte => f.push("group-exte".to_string()),
983 ModeFileGroupForValidSortBy::None => f.push("group-none".to_string()),
984 }
985 }
986 StrategyForValidSortBy::Path { mode, reverse, dir_split, file_group } => {
987 f.push("path".to_string());
988 f.push(mode.clone());
989 if *reverse {
990 f.push("rev".to_string());
991 }
992 if *dir_split {
993 f.push("dir-split-true".to_string());
994 } else {
995 f.push("dir-split-false".to_string());
996 }
997 match file_group {
998 ModeFileGroupForValidSortBy::Name => f.push("group-name".to_string()),
999 ModeFileGroupForValidSortBy::Exte => f.push("group-exte".to_string()),
1000 ModeFileGroupForValidSortBy::None => f.push("group-none".to_string()),
1001 }
1002 }
1003 }
1004 f
1005 }
1006}
1007
1008pub trait ConfigGetSetup {
1012 type Output;
1014 fn get(&self) -> Self::Output;
1016}
1017impl ValidColumnItemConfig {
1022 fn get(items: Vec<ValidColumnItemParse>) -> ValidColumnItemConfig {
1023 let mut list: Option<ModeListForValidColumnItem> = None;
1024 let mut icons: Option<ModeIconsForValidColumnItem> = None;
1025 let mut num_is_first: Option<bool> = None;
1026 let mut name: Option<bool> = None;
1027 let mut align_end: Option<bool> = None;
1028
1029 let mut list_count = 0usize;
1030 let mut icons_count = 0usize;
1031 let mut num_count = 0usize;
1032 let mut name_count = 0usize;
1033 let mut ws_count = 0usize;
1034
1035 for item in items {
1036 match item {
1037 ValidColumnItemParse::ListNone => {
1039 list = Some(ModeListForValidColumnItem::None);
1040 list_count += 1;
1041 }
1042 ValidColumnItemParse::ListFlat => {
1043 list = Some(ModeListForValidColumnItem::Flat);
1044 list_count += 1;
1045 }
1046 ValidColumnItemParse::ListTree => {
1047 list = Some(ModeListForValidColumnItem::Tree);
1048 list_count += 1;
1049 }
1050
1051 ValidColumnItemParse::NumPrefix => {
1053 num_is_first = Some(true);
1054 num_count += 1;
1055 }
1056 ValidColumnItemParse::NumSuffix => {
1057 num_is_first = Some(false);
1058 num_count += 1;
1059 }
1060
1061 ValidColumnItemParse::IconsLite => {
1063 icons = Some(ModeIconsForValidColumnItem::Lite);
1064 icons_count += 1;
1065 }
1066 ValidColumnItemParse::IconsMore => {
1067 icons = Some(ModeIconsForValidColumnItem::More);
1068 icons_count += 1;
1069 }
1070 ValidColumnItemParse::IconsNone => {
1071 icons = Some(ModeIconsForValidColumnItem::None);
1072 icons_count += 1;
1073 }
1074
1075 ValidColumnItemParse::NameNone => {
1077 name = Some(false);
1078 name_count += 1;
1079 }
1080 ValidColumnItemParse::NameShow => {
1081 name = Some(true);
1082 name_count += 1;
1083 }
1084
1085 ValidColumnItemParse::WhitespaceTrailNone => {
1087 align_end = Some(false);
1088 ws_count += 1;
1089 }
1090 ValidColumnItemParse::WhitespaceTrailShow => {
1091 align_end = Some(true);
1092 ws_count += 1;
1093 }
1094 }
1095 }
1096
1097 Self {
1098 list: if list_count == 1 { list.unwrap() } else { ModeListForValidColumnItem::Tree },
1099
1100 icons: if icons_count == 1 { icons.unwrap() } else { ModeIconsForValidColumnItem::Lite },
1101
1102 num_is_first: if num_count == 1 { num_is_first.unwrap() } else { false },
1103
1104 name: if name_count == 1 { name.unwrap() } else { false },
1105
1106 align_end: if ws_count == 1 { align_end.unwrap() } else { false },
1107 }
1108 }
1109}
1110impl ValidPatternConfig {
1116 pub fn get(&self, ignore_case: bool) -> logic::PatternsQueries {
1119 let patterns_ref: Vec<&str> = self.patterns.iter().map(|s| s.as_str()).collect();
1120 logic::PatternsQueries::new(patterns_ref, ignore_case)
1121 }
1122}
1123impl ConfigGetSetup for ValidSortByConfig {
1125 type Output = logic::SortQueries;
1126 fn get(&self) -> Self::Output { logic::SortQueries::new(self.clone()) }
1130}
1131
1132impl ValidPreparedJobConfig {
1141 pub fn to_toml(&self, _id: &str, _name: Option<&str>, _description: Option<&str>) -> String {
1143 use std::fmt::Write; let i = "job";
1146 let mut out = String::from("# Cargo-Plot Configuration File\n\n");
1147
1148 let only_test_id = "default_job";
1150 let only_test_name = "snapshot-project";
1151 let only_test_description = "【ENG】 Standard Rust project snapshot / 【POL】 Standardowy zrzut projektu Rust";
1152
1153 let _ = writeln!(out, "[[{}]]", i);
1155 let _ = writeln!(out, "id = {:?}", only_test_id);
1156 let _ = writeln!(out, "name = {:?}", only_test_name);
1157 let _ = writeln!(out, "description = {:?}", only_test_description);
1158 let _ = writeln!(out, "# [quiet, mute, dry, verbose, save, color]");
1159 let _ = writeln!(out, "run_mode = {:?}\n", self.exec.to_flags());
1160
1161 let _ = writeln!(out, "[{}.explorer]", i);
1163 let _ = writeln!(out, "workspace = {:?}", self.work.workspace_raw.as_str());
1164 let _ = writeln!(out, "ignore_case = {}", self.work.ignore_case);
1165 let _ = writeln!(out, "patterns = {:?}", self.patt.patterns);
1166 let _ = writeln!(out, "# [MD,MF,XD,XF]");
1167 let _ = writeln!(out, "parts = {:?}\n", self.part.to_flags());
1168
1169 let _ = writeln!(out, "[{}.attributes]", i);
1171 let _ = writeln!(out, "select = {:?}\n", self.cols.to_flags());
1172
1173 let _ = writeln!(out, "[{}.attributes.config]", i);
1175 let _ = writeln!(out, "item = {:?}", self.item.to_flags());
1176 let _ = writeln!(out, "date = {:?}", "default");
1177 let _ = writeln!(out, "time = {:?}", "default");
1178 let _ = writeln!(
1179 out,
1180 "size = {:?}\n",
1181 if self.size.mode == ValidColumnSizeParse::Decimal { "decimal" } else { "binary" }
1182 );
1183
1184 let _ = writeln!(out, "[{}.tuples]", i);
1185 let _ = writeln!(out, "sort = {:?}\n", self.sort.to_flags());
1186
1187 let _ = writeln!(out, "[{}.tuples.save]", i);
1189 let _ = writeln!(out, "out_dir = {:?}", self.save.raw.path);
1190 let _ = writeln!(out, "title = {:?}", self.save.raw.title);
1191 let _ = writeln!(out, "name = {:?}", self.save.raw.name);
1192 let _ = writeln!(out, "name_is_prefix = {}\n", self.save.raw.prefix);
1193
1194 out
1195 }
1196
1197 pub fn from_toml(path: Option<&str>) -> Result<Self, String> {
1201 let target_path = path.unwrap_or("./target/.cargo-plot/task.toml");
1203
1204 Self::ensure_config_exists(target_path)?;
1206
1207 let content = std::fs::read_to_string(target_path)
1209 .map_err(|e| format!("Nie można odczytać pliku {}: {}", target_path, e))?;
1210
1211 let toml_val: toml::Value = match toml::from_str(&content) {
1212 Ok(val) => val,
1213 Err(_) => {
1214 Self::backup_and_reset_config(target_path)?;
1216
1217 let fresh_content = std::fs::read_to_string(target_path).unwrap();
1219 toml::from_str(&fresh_content).unwrap()
1220 }
1221 };
1222
1223 let job = toml_val
1225 .get("job")
1226 .and_then(|j| j.as_array())
1227 .and_then(|arr| arr.get(0))
1228 .ok_or("Brak sekcji [[job]] w pliku TOML")?;
1229
1230 let get_str = |parent: Option<&toml::Value>, key: &str, default: &str| -> String {
1234 parent.and_then(|p| p.get(key)).and_then(|v| v.as_str()).unwrap_or(default).to_string()
1235 };
1236
1237 let get_bool = |parent: Option<&toml::Value>, key: &str, default: bool| -> bool {
1238 parent.and_then(|p| p.get(key)).and_then(|v| v.as_bool()).unwrap_or(default)
1239 };
1240
1241 let get_arr = |parent: Option<&toml::Value>, key: &str| -> Vec<String> {
1242 parent
1243 .and_then(|p| p.get(key))
1244 .and_then(|v| v.as_array())
1245 .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
1246 .unwrap_or_default()
1247 };
1248
1249 let explorer = job.get("explorer");
1253 let attributes = job.get("attributes");
1254 let config = attributes.and_then(|a| a.get("config"));
1255 let tuples = job.get("tuples");
1256 let save_cfg = tuples.and_then(|t| t.get("save"));
1257
1258 let run_mode = get_arr(Some(job), "run_mode");
1263
1264 let workspace = get_str(explorer, "workspace", ".");
1266 let ignore_case = get_bool(explorer, "ignore_case", false);
1267 let mut work_flags = vec![workspace];
1268 if ignore_case {
1269 work_flags.push("ignore-case".to_string());
1270 }
1271
1272 let patt_flags = get_arr(explorer, "patterns");
1273 let part_flags = get_arr(explorer, "parts");
1274
1275 let cols_flags = get_arr(attributes, "select");
1276
1277 let item_flags = get_arr(config, "item");
1278 let date_flags = vec![get_str(config, "date", "default")];
1279 let time_flags = vec![get_str(config, "time", "default")];
1280 let size_flags = vec![get_str(config, "size", "decimal")];
1281
1282 let sort_flags = get_arr(tuples, "sort");
1283
1284 let out_dir = get_str(save_cfg, "out_dir", "./target/.cargo-plot/");
1286 let title = get_str(save_cfg, "title", "Project Snapshot");
1287 let name = get_str(save_cfg, "name", "");
1288 let name_is_prefix = get_bool(save_cfg, "name_is_prefix", false);
1289
1290 let mut save_flags = Vec::new();
1291 if !out_dir.is_empty() {
1292 save_flags.push(format!("outdir={}", out_dir));
1293 }
1294 if !title.is_empty() {
1295 save_flags.push(format!("title={}", title));
1296 }
1297 if !name.is_empty() {
1298 save_flags.push(format!("name={}", name));
1299 }
1300 if name_is_prefix {
1301 save_flags.push("name-prefix".to_string());
1302 } else {
1303 save_flags.push("name-suffix".to_string());
1304 }
1305
1306 Ok(ValidPreparedJobConfig {
1310 exec: ValidExecutionParse::parse_vec_as_config(&run_mode),
1311 work: ValidWorkspaceParse::parse_vec_as_config(&work_flags),
1312 patt: ValidPatternParse::parse_vec_as_config(&patt_flags),
1313 part: ValidTablePartParse::parse_vec_as_config(&part_flags),
1314 cols: ValidTableColumnsParse::parse_vec_as_config(&cols_flags),
1315 item: ValidColumnItemParse::parse_vec_as_config(&item_flags),
1316 date: ValidColumnDateParse::parse_vec_as_config(&date_flags),
1317 time: ValidColumnTimeParse::parse_vec_as_config(&time_flags),
1318 size: ValidColumnSizeParse::parse_vec_as_config(&size_flags),
1319 sort: ValidSortByParse::parse_vec_as_config(&sort_flags),
1320 save: ValidSaveAsParse::parse_vec_as_config(&save_flags),
1321 })
1322 }
1323
1324 pub fn ensure_config_exists(target_path: &str) -> Result<(), String> {
1326 let p = std::path::Path::new(target_path);
1327
1328 if !p.exists() {
1329 if let Some(parent) = p.parent() {
1331 std::fs::create_dir_all(parent)
1332 .map_err(|e| format!("Nie udało się utworzyć folderu dla {}: {}", target_path, e))?;
1333 }
1334
1335 let default_cfg = Self::default();
1336 let toml_content = default_cfg.to_toml("default_job", None, None);
1337
1338 std::fs::write(p, toml_content)
1339 .map_err(|e| format!("Błąd zapisu pliku konfiguracyjnego {}: {}", target_path, e))?;
1340
1341 println!("🌱 Utworzono plik konfiguracji: {}", target_path);
1342 }
1343 Ok(())
1344 }
1345
1346 pub fn backup_and_reset_config(target_path: &str) -> Result<(), String> {
1348 let p = std::path::Path::new(target_path);
1349
1350 if p.exists() {
1351 let time_tag = crate::lib::logic::tag_time().0;
1352 let backup_path = format!("{}_backup-{}.toml", target_path.trim_end_matches(".toml"), time_tag);
1354
1355 std::fs::rename(p, &backup_path)
1356 .map_err(|e| format!("Błąd tworzenia backupu dla {}: {}", target_path, e))?;
1357
1358 println!("⚠️ Uszkodzony plik! Utworzono kopię zapasową: {}", backup_path);
1359 }
1360
1361 Self::ensure_config_exists(target_path)
1363 }
1364}
1365
1366impl ValidSaveAsConfig {
1371 pub fn ensure_out_dir(&self) -> std::io::Result<std::path::PathBuf> {
1373 if !self.out_dir.exists() {
1374 std::fs::create_dir_all(&self.out_dir)?;
1375 }
1376 Ok(self.out_dir.clone())
1377 }
1378}
1379
1380impl ValidColumnItemConfig {
1381 pub fn format_item(
1395 &self,
1396 u: (usize, usize, usize, usize),
1397 row: &job::ValidResultMainRow,
1398 tab: &[job::ValidResultMainRow],
1399 ) -> String {
1400 let (index, num_width, tier_max, name_len_max) = u;
1401 let mut parts = Vec::new();
1402 let num_str = format!("{:>width$}.", index + 1, width = num_width);
1403
1404 if self.num_is_first {
1406 parts.push(num_str.clone());
1407 }
1408
1409 if self.list != ModeListForValidColumnItem::None {
1411 let l = job::gens::item_list::draw_list(&self.list, index, tab, tier_max);
1412 parts.push(l);
1413 }
1414
1415 if self.icons != ModeIconsForValidColumnItem::None {
1417 let ic = job::gens::draw_icon(&self.icons, &row.node.node, &row.name_with_ext);
1418 parts.push(ic.to_string());
1419 }
1420
1421 if !self.num_is_first {
1423 parts.push(num_str);
1424 }
1425
1426 if self.name {
1428 parts.push(row.name_with_ext.clone());
1429 }
1430
1431 let mut base = parts.join(" ");
1432
1433 if self.align_end {
1435 let p_tier = (tier_max - row.node.tier) * 3;
1436 let p_name = name_len_max - row.node.name.chars().count();
1437 base.push_str(&" ".repeat(p_tier + p_name));
1438 }
1439
1440 base
1441 }
1442}
1443
1444impl ValidColumnSizeConfig {
1445 pub fn format_size(&self, bytes: u64) -> String {
1447 let base = match self.mode {
1448 ValidColumnSizeParse::Decimal => 1000.0,
1449 ValidColumnSizeParse::Binary => 1024.0,
1450 };
1451
1452 let suffix = match self.mode {
1453 ValidColumnSizeParse::Decimal => ["B ", "kB", "MB", "GB"],
1454 ValidColumnSizeParse::Binary => ["B ", "KiB", "MiB", "GiB"], };
1456
1457 let bytes_f = bytes as f64;
1458
1459 if bytes_f < base {
1460 format!("{:>6} {}", bytes, suffix[0])
1461 } else if bytes_f < base.powi(2) {
1462 format!("{:>6.2} {}", bytes_f / base, suffix[1])
1463 } else if bytes_f < base.powi(3) {
1464 format!("{:>6.2} {}", bytes_f / base.powi(2), suffix[2])
1465 } else {
1466 format!("{:>6.2} {}", bytes_f / base.powi(3), suffix[3])
1467 }
1468 }
1469}
1470
1471impl ValidColumnDateConfig {
1472 pub fn format_date(&self, dt: chrono::DateTime<chrono::Local>) -> String { dt.format(&self.format).to_string() }
1473}
1474
1475impl ValidColumnTimeConfig {
1476 pub fn format_time(&self, dt: chrono::DateTime<chrono::Local>) -> String { dt.format(&self.format).to_string() }
1477}
1478