1use std::{path::PathBuf, process};
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}
32pub enum ValidTablePartParse {
52 MD,
53 MF,
54 XD,
55 XF,
56}
57
58#[derive(Debug, Clone)]
59pub struct ValidTablePartConfig {
60 pub md: bool,
61 pub mf: bool,
62 pub xd: bool,
63 pub xf: bool,
64}
65
66impl ValidTablePartParse {
67 pub fn parse(s: &str) -> Result<Self, String> {
68 match s.trim().to_lowercase().as_str() {
69 "MD" | "md" | "match-dirs" => Ok(ValidTablePartParse::MD),
70 "MF" | "mf" | "match-files" => Ok(ValidTablePartParse::MF),
71 "XD" | "xd" | "mismatch-dirs" => Ok(ValidTablePartParse::XD),
72 "XF" | "xf" | "mismatch-files" => Ok(ValidTablePartParse::XF),
73 _ => Err(format!("Nieprawidłowa nazwa: '{}'. Dostępne: md, mf, xd, xf", s.trim())),
74 }
75 }
76
77 pub fn parses<'a, I>(inputs: I) -> Result<Vec<Self>, String>
78 where I: IntoIterator<Item = &'a str> {
79 inputs.into_iter().map(Self::parse).collect()
80 }
81
82 pub fn parse_vec<I>(inputs: I) -> Vec<Self>
83 where
84 I: IntoIterator,
85 I::Item: AsRef<str>, {
86 inputs.into_iter().map(|s| Self::parse(s.as_ref()).expect("invalid ValidTablePartParse input")).collect()
87 }
88 pub fn parse_vec_as_config<I>(inputs: I) -> ValidTablePartConfig
89 where
90 I: IntoIterator,
91 I::Item: AsRef<str>, {
92 let mut md = false;
93 let mut mf = false;
94 let mut xd = false;
95 let mut xf = false;
96
97 let mut has_any_valid = false;
98
99 for item in inputs {
100 if let Ok(parsed) = Self::parse(item.as_ref()) {
102 has_any_valid = true;
103 match parsed {
104 ValidTablePartParse::MD => md = true,
105 ValidTablePartParse::MF => mf = true,
106 ValidTablePartParse::XD => xd = true,
107 ValidTablePartParse::XF => xf = true,
108 }
109 }
110 }
111
112 if !has_any_valid {
114 md = true;
115 mf = true;
116 }
117
118 ValidTablePartConfig { md, mf, xd, xf }
119 }
120}
121
122pub enum ValidColumnItemParse {
127 ListNone,
128 ListTree,
129 ListFlat,
130 IconsNone,
131 IconsLite,
132 IconsMore,
133
134 NumPrefix,
135 NumSuffix,
136 NameNone,
137 NameShow,
138 WhitespaceTrailNone,
139 WhitespaceTrailShow,
140}
141
142#[derive(Debug)]
143pub struct ValidColumnItemConfig {
144 pub list: ModeListForValidColumnItem,
145 pub icons: ModeIconsForValidColumnItem,
146 pub name: bool,
147 pub align_end: bool,
148 pub num_is_first: bool,
149}
150
151#[derive(Debug, PartialEq, Eq)]
152pub enum ModeListForValidColumnItem {
153 None,
154 Flat,
155 Tree,
156}
157
158#[derive(Debug, PartialEq, Eq)]
159pub enum ModeIconsForValidColumnItem {
160 Lite,
161 More,
162 None,
163}
164
165impl ValidColumnItemParse {
166 pub fn parse(s: &str) -> Result<Self, String> {
167 match s.trim().to_lowercase().as_str() {
168 "list-none" => Ok(ValidColumnItemParse::ListNone),
169 "list-flat" => Ok(ValidColumnItemParse::ListFlat),
170 "list-tree" => Ok(ValidColumnItemParse::ListTree),
171
172 "icons-lite" => Ok(ValidColumnItemParse::IconsLite),
173 "icons-more" => Ok(ValidColumnItemParse::IconsMore),
174 "icons-none" => Ok(ValidColumnItemParse::IconsNone),
175
176 "num-prefix" => Ok(ValidColumnItemParse::NumPrefix),
177 "num-suffix" => Ok(ValidColumnItemParse::NumSuffix),
178
179 "name-none" => Ok(ValidColumnItemParse::NameNone),
180 "name-show" => Ok(ValidColumnItemParse::NameShow),
181
182 "ws-none" | "whitespace-none" => Ok(ValidColumnItemParse::WhitespaceTrailNone),
183 "ws-show" | "whitespace-show" => Ok(ValidColumnItemParse::WhitespaceTrailShow),
184
185 _ => Err(format!("Nieznany ValidColumnItemParse: '{}'", s.trim())),
186 }
187 }
188
189 pub fn parse_vec<I>(inputs: I) -> Result<Vec<Self>, String>
190 where
191 I: IntoIterator,
192 I::Item: AsRef<str>, {
193 inputs.into_iter().map(|s| Self::parse(s.as_ref())).collect()
194 }
195
196 pub fn parse_vec_as_config<I>(inputs: I) -> Result<ValidColumnItemConfig, String>
197 where
198 I: IntoIterator,
199 I::Item: AsRef<str>, {
200 let items = Self::parse_vec(inputs)?;
201 Ok(ValidColumnItemConfig::get(items))
202 }
203}
204
205impl ValidColumnItemConfig {
206 pub fn get(items: Vec<ValidColumnItemParse>) -> Self {
207 let mut list: Option<ModeListForValidColumnItem> = None;
208 let mut icons: Option<ModeIconsForValidColumnItem> = None;
209 let mut num_is_first: Option<bool> = None;
210 let mut name: Option<bool> = None;
211 let mut align_end: Option<bool> = None;
212
213 let mut list_count = 0usize;
214 let mut icons_count = 0usize;
215 let mut num_count = 0usize;
216 let mut name_count = 0usize;
217 let mut ws_count = 0usize;
218
219 for item in items {
220 match item {
221 ValidColumnItemParse::ListNone => {
223 list = Some(ModeListForValidColumnItem::None);
224 list_count += 1;
225 }
226 ValidColumnItemParse::ListFlat => {
227 list = Some(ModeListForValidColumnItem::Flat);
228 list_count += 1;
229 }
230 ValidColumnItemParse::ListTree => {
231 list = Some(ModeListForValidColumnItem::Tree);
232 list_count += 1;
233 }
234
235 ValidColumnItemParse::NumPrefix => {
237 num_is_first = Some(true);
238 num_count += 1;
239 }
240 ValidColumnItemParse::NumSuffix => {
241 num_is_first = Some(false);
242 num_count += 1;
243 }
244
245 ValidColumnItemParse::IconsLite => {
247 icons = Some(ModeIconsForValidColumnItem::Lite);
248 icons_count += 1;
249 }
250 ValidColumnItemParse::IconsMore => {
251 icons = Some(ModeIconsForValidColumnItem::More);
252 icons_count += 1;
253 }
254 ValidColumnItemParse::IconsNone => {
255 icons = Some(ModeIconsForValidColumnItem::None);
256 icons_count += 1;
257 }
258
259 ValidColumnItemParse::NameNone => {
261 name = Some(false);
262 name_count += 1;
263 }
264 ValidColumnItemParse::NameShow => {
265 name = Some(true);
266 name_count += 1;
267 }
268
269 ValidColumnItemParse::WhitespaceTrailNone => {
271 align_end = Some(false);
272 ws_count += 1;
273 }
274 ValidColumnItemParse::WhitespaceTrailShow => {
275 align_end = Some(true);
276 ws_count += 1;
277 }
278 }
279 }
280
281 Self {
282 list: if list_count == 1 { list.unwrap() } else { ModeListForValidColumnItem::Tree },
283
284 icons: if icons_count == 1 { icons.unwrap() } else { ModeIconsForValidColumnItem::Lite },
285
286 num_is_first: if num_count == 1 { num_is_first.unwrap() } else { false },
287
288 name: if name_count == 1 { name.unwrap() } else { false },
289
290 align_end: if ws_count == 1 { align_end.unwrap() } else { false },
291 }
292 }
293
294 pub fn from_strings<I>(inputs: I) -> Result<Self, String>
297 where
298 I: IntoIterator,
299 I::Item: AsRef<str>, {
300 let parsed_items = ValidColumnItemParse::parse_vec(inputs)?;
302
303 Ok(Self::get(parsed_items))
305 }
306
307 pub fn format_item(
308 &self,
309 u: (usize, usize, usize, usize),
310 row: &job::ValidResultMainRow,
311 tab: &[job::ValidResultMainRow],
312 ) -> String {
313 let (index, num_width, tier_max, name_len_max) = u;
314 let mut parts = Vec::new();
315 let num_str = format!("{:>width$}.", index + 1, width = num_width);
316
317 if self.num_is_first {
319 parts.push(num_str.clone());
320 }
321
322 if self.list != ModeListForValidColumnItem::None {
324 let l = job::gens::item_list::draw_list(&self.list, index, tab, tier_max);
325 parts.push(l);
326 }
327
328 if self.icons != ModeIconsForValidColumnItem::None {
330 let ic = job::gens::draw_icon(&self.icons, &row.node.node, &row.name_with_ext);
331 parts.push(ic.to_string());
332 }
333
334 if !self.num_is_first {
336 parts.push(num_str);
337 }
338
339 if self.name {
341 parts.push(row.name_with_ext.clone());
342 }
343
344 let mut base = parts.join(" ");
345
346 if self.align_end {
348 let p_tier = (tier_max - row.node.tier) * 3;
349 let p_name = name_len_max - row.node.name.chars().count();
350 base.push_str(&" ".repeat(p_tier + p_name));
351 }
352
353 base
354 }
355}
356
357#[derive(Debug, Clone, PartialEq, Eq)]
362pub enum ValidTableColumnsParse {
363 Date,
364 Time,
365 Size,
366 Item,
367 Path,
368}
369
370#[derive(Debug, Clone)]
371pub struct ValidTableColumnsConfig {
372 pub columns: Vec<ValidTableColumnsParse>,
373}
374
375impl ValidTableColumnsParse {
376 pub fn parse(s: &str) -> Result<Self, String> {
377 match s.trim().to_lowercase().as_str() {
378 "date" => Ok(ValidTableColumnsParse::Date),
379 "time" => Ok(ValidTableColumnsParse::Time),
380 "size" => Ok(ValidTableColumnsParse::Size),
381 "item" => Ok(ValidTableColumnsParse::Item),
382 "path" => Ok(ValidTableColumnsParse::Path),
383 _ => Err(format!("Nieprawidłowa nazwa kolumny: '{}'. Dostępne: date, time, size, item, path", s.trim())),
384 }
385 }
386
387 pub fn parse_vec<I>(inputs: I) -> Result<Vec<Self>, String>
388 where
389 I: IntoIterator,
390 I::Item: AsRef<str>, {
391 inputs.into_iter().map(|s| Self::parse(s.as_ref())).collect()
392 }
393
394 pub fn parse_vec_as_config<I>(inputs: I) -> ValidTableColumnsConfig
395 where
396 I: IntoIterator,
397 I::Item: AsRef<str>, {
398 let mut columns = Vec::new();
399
400 let mut has_date = false;
401 let mut has_time = false;
402 let mut has_size = false;
403 let mut has_item = false;
404 let mut has_path = false;
405
406 let mut has_any_valid = false;
407
408 for item in inputs {
409 if let Ok(parsed) = Self::parse(item.as_ref()) {
410 has_any_valid = true;
411 match parsed {
412 ValidTableColumnsParse::Date => {
413 if !has_date {
414 columns.push(ValidTableColumnsParse::Date);
415 has_date = true;
416 }
417 }
418 ValidTableColumnsParse::Time => {
419 if !has_time {
420 columns.push(ValidTableColumnsParse::Time);
421 has_time = true;
422 }
423 }
424 ValidTableColumnsParse::Size => {
425 if !has_size {
426 columns.push(ValidTableColumnsParse::Size);
427 has_size = true;
428 }
429 }
430 ValidTableColumnsParse::Item => {
431 if !has_item {
432 columns.push(ValidTableColumnsParse::Item);
433 has_item = true;
434 }
435 }
436 ValidTableColumnsParse::Path => {
437 if !has_path {
438 columns.push(ValidTableColumnsParse::Path);
439 has_path = true;
440 }
441 }
442 }
443 }
444 }
445
446 if !has_any_valid {
448 return ValidTableColumnsConfig {
449 columns: vec![
450 ValidTableColumnsParse::Date,
451 ValidTableColumnsParse::Time,
452 ValidTableColumnsParse::Size,
453 ValidTableColumnsParse::Item,
454 ValidTableColumnsParse::Path,
455 ],
456 };
457 }
458
459 if !has_item {
461 columns.push(ValidTableColumnsParse::Item);
462 }
463
464 ValidTableColumnsConfig { columns }
465 }
466}
467
468#[derive(Debug, Clone, Copy, PartialEq, Eq)]
473pub enum ValidColumnSizeParse {
474 Decimal, Binary, }
477
478#[derive(Debug, Clone)]
479pub struct ValidColumnSizeConfig {
480 pub mode: ValidColumnSizeParse,
481}
482
483impl ValidColumnSizeParse {
484 pub fn parse(s: &str) -> Result<Self, String> {
485 match s.trim().to_lowercase().as_str() {
486 "dec" | "decimal" => Ok(ValidColumnSizeParse::Decimal),
487 "bin" | "binary" => Ok(ValidColumnSizeParse::Binary),
488 _ => Err(format!("Nieznany system miar: '{}'. Dostępne: dec, bin", s.trim())),
489 }
490 }
491
492 pub fn parse_vec_as_config<I>(inputs: I) -> ValidColumnSizeConfig
493 where
494 I: IntoIterator,
495 I::Item: AsRef<str>, {
496 let mut mode = ValidColumnSizeParse::Decimal; for item in inputs {
499 if let Ok(parsed) = Self::parse(item.as_ref()) {
500 mode = parsed;
501 break; }
503 }
504
505 ValidColumnSizeConfig { mode }
506 }
507}
508
509impl ValidColumnSizeConfig {
510 pub fn format_size(&self, bytes: u64) -> String {
512 let base = match self.mode {
513 ValidColumnSizeParse::Decimal => 1000.0,
514 ValidColumnSizeParse::Binary => 1024.0,
515 };
516
517 let suffix = match self.mode {
518 ValidColumnSizeParse::Decimal => ["B ", "kB", "MB", "GB"],
519 ValidColumnSizeParse::Binary => ["B ", "KiB", "MiB", "GiB"], };
521
522 let bytes_f = bytes as f64;
523
524 if bytes_f < base {
525 format!("{:>6} {}", bytes, suffix[0])
526 } else if bytes_f < base.powi(2) {
527 format!("{:>6.2} {}", bytes_f / base, suffix[1])
528 } else if bytes_f < base.powi(3) {
529 format!("{:>6.2} {}", bytes_f / base.powi(2), suffix[2])
530 } else {
531 format!("{:>6.2} {}", bytes_f / base.powi(3), suffix[3])
532 }
533 }
534}
535#[derive(Debug, Clone, Copy, PartialEq, Eq)]
540pub enum ValidColumnDateParse {
541 Default,
542}
543
544#[derive(Debug, Clone)]
545pub struct ValidColumnDateConfig {
546 pub format: String,
547}
548
549impl ValidColumnDateParse {
550 pub fn parse(s: &str) -> Result<Self, String> {
551 match s.trim().to_lowercase().as_str() {
552 "date-default" | "default" => Ok(ValidColumnDateParse::Default),
553 _ => Err(format!("Nieznany format daty: '{}'", s.trim())),
554 }
555 }
556
557 pub fn parse_vec_as_config<I>(inputs: I) -> ValidColumnDateConfig
558 where
559 I: IntoIterator,
560 I::Item: AsRef<str>, {
561 let format = "%Y W%V %u-%a".to_string(); for item in inputs {
564 if let Ok(_) = Self::parse(item.as_ref()) {
565 break;
567 }
568 }
569
570 ValidColumnDateConfig { format }
571 }
572}
573
574impl ValidColumnDateConfig {
575 pub fn format_date(&self, dt: chrono::DateTime<chrono::Local>) -> String { dt.format(&self.format).to_string() }
576}
577
578#[derive(Debug, Clone, Copy, PartialEq, Eq)]
583pub enum ValidColumnTimeParse {
584 Default,
585}
586
587#[derive(Debug, Clone)]
588pub struct ValidColumnTimeConfig {
589 pub format: String,
590}
591
592impl ValidColumnTimeParse {
593 pub fn parse(s: &str) -> Result<Self, String> {
594 match s.trim().to_lowercase().as_str() {
595 "time-default" | "default" => Ok(ValidColumnTimeParse::Default),
596 _ => Err(format!("Nieznany format czasu: '{}'", s.trim())),
597 }
598 }
599
600 pub fn parse_vec_as_config<I>(inputs: I) -> ValidColumnTimeConfig
601 where
602 I: IntoIterator,
603 I::Item: AsRef<str>, {
604 let format = "%H:%M:%S.%3f".to_string(); for item in inputs {
607 if let Ok(_) = Self::parse(item.as_ref()) {
608 break;
609 }
610 }
611
612 ValidColumnTimeConfig { format }
613 }
614}
615
616impl ValidColumnTimeConfig {
617 pub fn format_time(&self, dt: chrono::DateTime<chrono::Local>) -> String { dt.format(&self.format).to_string() }
618}
619#[derive(Debug, Clone)]
624pub struct ValidPatternConfig {
625 pub patterns: Vec<String>,
626 pub ignore_case: bool,
627}
628
629impl ValidPatternConfig {
630 pub fn get(&self) -> logic::PatternsQueries {
632 let patterns_ref: Vec<&str> = self.patterns.iter().map(|s| s.as_str()).collect();
634 logic::PatternsQueries::new(patterns_ref, self.ignore_case)
635 }
636}
637
638pub struct ValidPatternParse;
639
640impl ValidPatternParse {
641 pub fn parse_vec_as_config<I>(inputs: I, ignore_case_sensitive: Option<bool>) -> ValidPatternConfig
642 where
643 I: IntoIterator,
644 I::Item: AsRef<str>, {
645 let patterns: Vec<String> = inputs.into_iter().map(|s| s.as_ref().to_string()).collect();
646
647 if patterns.is_empty() {
649 return ValidPatternConfig {
650 patterns: vec![
651 "./{.rustfmt,Cargo,rust-toolchain,Makefile}.toml&/".to_string(),
652 "./**/*.rs&/".to_string(),
653 "!./target/**".to_string(),
654 "!./.git/**".to_string(),
655 "./.{gitattributes,gitignore}".to_string(),
656 "./.github/workflows/*.yml&/".to_string(),
657 "./.vscode/settings.json&/".to_string(),
658 "./{API,ARCHITECTURE,AUTHORS,CHANGELOG,README,ROADMAP,TODO}.md".to_string(),
659 "./dist/{**/*,*}.{bat,exe}&/".to_string(),
660 ],
661 ignore_case: ignore_case_sensitive.unwrap_or(false),
662 };
663 }
664
665 ValidPatternConfig { patterns, ignore_case: ignore_case_sensitive.unwrap_or(false) }
666 }
667}
668
669#[derive(Debug, Clone)]
674pub struct ValidWorkspaceConfig {
675 pub workspace_dir: String,
676}
677
678impl ValidWorkspaceConfig {
679 pub fn get(&self) -> logic::AnchoredPathsDatum {
682 let path = PathBuf::from(&self.workspace_dir);
684
685 let work_path = match path.canonicalize() {
688 Ok(p) => p.to_string_lossy().to_string(),
689 Err(_) => {
690 match std::env::current_dir() {
693 Ok(current) => current.join(&path).to_string_lossy().to_string(),
694 Err(_) => self.workspace_dir.clone(),
695 }
696 }
697 };
698
699 logic::AnchoredPathsDatum::new(&work_path).unwrap_or_else(|x| {
701 eprintln!("❌ Błąd lokalizacji workspace: {}", x);
702 process::exit(1);
703 })
704 }
705}
706
707pub struct ValidWorkspaceParse;
708
709impl ValidWorkspaceParse {
710 pub fn parse_vec_as_config<I>(inputs: I) -> ValidWorkspaceConfig
711 where
712 I: IntoIterator,
713 I::Item: AsRef<str>, {
714 let mut workspace_dir = ".".to_string();
715
716 for item in inputs {
718 let s = item.as_ref().trim();
719 if !s.is_empty() {
720 workspace_dir = s.to_string();
721 break;
722 }
723 }
724
725 ValidWorkspaceConfig { workspace_dir }
726 }
727}
728#[derive(Debug, Clone, PartialEq, Eq)]
788pub enum ModeFileGroupForValidSortBy {
789 Name,
790 Exte,
791 None,
792}
793
794#[derive(Debug, Clone)]
795pub enum StrategyForValidSortBy {
796 Date { reverse: bool },
797 Size { reverse: bool },
798 Name { mode: String, reverse: bool, file_group: ModeFileGroupForValidSortBy },
799 Path { mode: String, reverse: bool, dir_split: bool, file_group: ModeFileGroupForValidSortBy },
800}
801
802#[derive(Debug, Clone)]
803pub struct ValidSortByConfig {
804 pub strategy: StrategyForValidSortBy,
805}
806
807impl ValidSortByConfig {
808 pub fn get(&self) -> logic::SortQueries { logic::SortQueries::new(self.clone()) }
812}
813
814pub struct ValidSortByParse;
815
816impl ValidSortByParse {
817 pub fn parse_vec_as_config<I>(inputs: I) -> ValidSortByConfig
818 where
819 I: IntoIterator,
820 I::Item: AsRef<str>, {
821 let mut strategy_name = "name";
823 let mut reverse = false;
824 let mut dir_split = true;
825 let mut file_group = ModeFileGroupForValidSortBy::None;
826 let mut mode_sort_name = "[Spec][Num][AZaz]".to_string();
827
828 for item in inputs {
830 let s = item.as_ref().trim();
831 let lower = s.to_lowercase();
832
833 match lower.as_str() {
834 "date" => strategy_name = "date",
835 "size" => strategy_name = "size",
836 "name" => strategy_name = "name",
837 "path" => strategy_name = "path",
838
839 "rev" | "reverse" => reverse = true,
840
841 "group-name" => file_group = ModeFileGroupForValidSortBy::Name,
842 "group-exte" => file_group = ModeFileGroupForValidSortBy::Exte,
843 "group-none" => file_group = ModeFileGroupForValidSortBy::None,
844
845 "dir-split-true" => dir_split = true,
846 "dir-split-false" => dir_split = false,
847
848 _ => {
849 if s.starts_with('[') && s.ends_with(']') {
851 mode_sort_name = s.to_string();
852 }
853 }
854 }
855 }
856
857 let strategy = match strategy_name {
859 "date" => StrategyForValidSortBy::Date { reverse },
860 "size" => StrategyForValidSortBy::Size { reverse },
861 "path" => StrategyForValidSortBy::Path { mode: mode_sort_name, reverse, dir_split, file_group },
862 _ => StrategyForValidSortBy::Name { mode: mode_sort_name, reverse, file_group },
863 };
864
865 ValidSortByConfig { strategy }
866 }
867}
868
869