Skip to main content

plot/lib/job/
config.rs

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; // ⚡ GLOBALNA FLAGA DEBUGOWANIA
11// ============================================================================
12// OSTATECZNY WIERSZ DANYCH (Zhydratowany)
13// ============================================================================
14
15/// Reprezentuje finalny wiersz gotowy do tabeli, zawierający wszystkie
16/// zgromadzone metadane z dysku i z etapu skanowania.
17#[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// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
33/*
34 * 🧠 KAŻDY `Valid<NAZWA>Parse` posiada parę `Valid<NAZWA>Config` i odwrotnie.
35 *
36 * 🧠 KAŻDY `Valid<NAZWA>Parse` ma metodę `parse_vec_as_config` - automatyczne wywołanie bliźniaka
37 *
38 * 🧠 KAŻDY `Valid<NAZWA>Config` ma metodę `get` - pobiera znormalizowaną konfiguracje
39 *
40 * 🧠 DODATKI DLA `Valid<NAZWA><Config|Parse>` mają nazwę `<ZASTOSOWANIE>ForValid<NAZWA>`
41 *
42 * 🧠 CELEM NINIESZJEGO PLIKU JEST ZAPEWNIANIE PRAWIDŁOWEJ KONFIGURACJI,
43 *          CIĘŻKA LOGIKA POWINNA BYĆ W `Crate::lib::logic::*`;
44 * 	        W ETAPACH KOLEJNYCH W `Crate::lib::job::engine_step*` W GŁÓWNEJ FUNKCJI - ARGUMENTY SĄ
45 *          ZAWSZE TYPU `&job::Valid<NAZWA>Config`
46 *
47 *
48 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░ INDEX ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
49 *
50 * pub struct ValidResultMainRow
51 * pub struct ValidResultMainTab
52 * //0
53 * pub struct PreparedJob
54 * //1
55 * pub enum ValidExecutionParse
56 * pub struct ValidExecutionConfig
57 * //2
58 * pub enum ValidSaveAsParse
59 * pub struct ValidSaveAsConfig
60 * //3
61 * pub enum ValidTablePartParse
62 * pub struct ValidTablePartConfig
63 * //4
64 * pub enum ValidColumnItemParse
65 * pub struct ValidColumnItemConfig
66 * pub enum ModeListForValidColumnItem
67 * pub enum ModeIconsForValidColumnItem
68 * //5
69 * pub enum ValidTableColumnsParse
70 * pub struct ValidTableColumnsConfig
71 * //6
72 * pub enum ValidColumnSizeParse
73 * pub struct ValidColumnSizeConfig
74 * //7 i 8
75 * pub enum ValidColumnDateParse
76 * pub enum ValidColumnTimeParse
77 * pub struct ValidColumnDateConfig
78 * pub struct ValidColumnTimeConfig
79 * //9
80 * pub struct ValidPatternConfig
81 * pub struct ValidPatternParse
82 * //10
83 * pub struct ValidWorkspaceConfig
84 * pub struct ValidWorkspaceParse
85 * //11
86 * pub struct ValidSortByParse //brakuje enumu
87 * pub struct ValidSortByConfig
88 * pub enum ModeFileGroupForValidSortBy
89 * pub enum StrategyForValidSortBy
90 *
91 *  */
92
93// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
94// MODEL ZBIORCZY
95// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
96
97#[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			// 0. Execution Config (Domyślnie: Debug: true, SaveAs: true, CliColor: true)
117			exec: ValidExecutionParse::parse_vec_as_config(&blank),
118			// 1. Workspace (Domyślnie ".")
119			work: ValidWorkspaceParse::parse_vec_as_config(["."]),
120			// 2. Patterns (Domyślnie zbiór filtrów zdefiniowany w config.rs)
121			patt: ValidPatternParse::parse_vec_as_config(&blank),
122			// 3. Table Parts (Domyślnie md + mf)
123			part: ValidTablePartParse::parse_vec_as_config(&blank),
124			// 4. Column Item (Domyślnie Tree, IconsLite, Name: false, Align: false)
125			item: ValidColumnItemParse::parse_vec_as_config(["list-tree", "name-show", "ws-show", "icons-lite"]),
126			// 5. Table Columns (Domyślnie Date, Time, Size, Item, Path)
127			cols: ValidTableColumnsParse::parse_vec_as_config(&blank),
128			// 6. Column Date (Domyślnie "%Y W%V %u-%a")
129			date: ValidColumnDateParse::parse_vec_as_config(&blank),
130			// 7. Column Time (Domyślnie "%H:%M:%S.%3f")
131			time: ValidColumnTimeParse::parse_vec_as_config(&blank),
132			// 8. Column Size (Domyślnie Decimal / SI)
133			size: ValidColumnSizeParse::parse_vec_as_config(&blank),
134			// 9. Sort By (Domyślnie Name, [Spec][Num][AZaz], Reverse: false)
135			sort: ValidSortByParse::parse_vec_as_config(&blank),
136			// 10. Save As (Domyślnie "./target/.cargo-plot/", "Project Snapshot", SOTC/COTS names)
137			save: ValidSaveAsParse::parse_vec_as_config(&blank),
138		}
139	}
140}
141// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
142// MODELE PODSTAWOWE
143// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
144
145#[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, // System SI (podstawa 1000)
232	Binary,  // System IEC (podstawa 1024)
233}
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// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
279// MODELE POMOCNICZE
280// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
281
282#[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
313// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
314// ODCZYTYWANIE USTAWIEŃ Z FLAG
315// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
316
317pub trait ParseFromFlags: Sized {
318	/// Typ konfiguracji, który zostanie zwrócony
319	type Config;
320
321	/// Przetwarza wektor stringów (flag) na zwalidowaną strukturę konfiguracyjną
322	fn parse_vec_as_config<I>(inputs: I) -> Self::Config
323	where
324		I: IntoIterator,
325		I::Item: AsRef<str>;
326	/// Parsuje pojedynczego stringa (flagę CLI) na wariant Enuma.
327	/// Zwraca Result, aby umożliwić obsługę lub zignorowanie nieznanych flag.
328	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		// ⚡ USTAWIENIA DOMYŚLNE ZGODNIE Z TWOIM POLECENIEM
351		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		// 1. Zbieranie surowych wartości
403		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		// ⚡ Pakujemy surowe wartości do nowej struktury
420		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		// ⚡ Tworzymy PathBuf od razu
428		let out_dir = std::path::PathBuf::from(&out_dir_str);
429
430		// 2. Pobieramy znacznik czasu JEDEN RAZ, żeby pliki były w pełni zsynchronizowane
431		let time_tag = crate::lib::logic::tag_time().0;
432
433		// 3. Generowanie tytułów
434		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		// 4. Generowanie nazw plików (z zabezpieczeniem przed "wiszącym" podkreślnikiem gdy name to "")
440		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		// 5. Zwrócenie gotowej struktury
457		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			// Cicho ignorujemy błędy parsowania (zgodnie z założeniem o niepoprawnych elementach)
484			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		// Jeśli tablica była pusta lub wszystko było śmieciami, ładujemy domyślne ustawienia
496		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		// Jeśli wejście było puste lub same błędy -> wczytujemy konfigurację domyślną
603		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		// Jeśli podano cokolwiek poprawnego, ale brakuje 'Item', doklejamy na koniec
616		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; // Domyślnie Dec
637
638		for item in inputs {
639			if let Ok(parsed) = Self::parse(item.as_ref()) {
640				mode = parsed;
641				break; // Przyjmujemy pierwszy poprawny wpis
642			}
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(); // Domyślny format: Rok Tydzień Dzień(1-7) SkrótDnia
661
662		for item in inputs {
663			if let Ok(_) = Self::parse(item.as_ref()) {
664				// Tutaj można dodać logikę wyboru formatu, jeśli pojawi się więcej wariantów w enum
665				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(); // Domyślny format: Godz:Min:Sek.Milisekundy
685
686		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		// Jeśli lista jest pusta po zebraniu, ładujemy domyślne
705		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				//ignore_case: ignore_case_sensitive.unwrap_or(false),
719			};
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; // ⚡ Domyślnie false
734		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				// Pierwszy niepusty string niebędący flagą uznajemy za ścieżkę
742				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			//panic!("Nie można ustalić ścieżki '{}'", w.to_string_lossy());
753		});
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		// 1. Ustawienia domyślne
766		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		// 2. Płaskie parsowanie CLI/TOML (bez wchodzenia w detale algorytmów)
773		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					// Czyste przekazanie definicji maski (np. "[Num][Spec][azAZ]") do logiki
794					if s.starts_with('[') && s.ends_with(']') {
795						mode_sort_name = s.to_string();
796					}
797				}
798			}
799		}
800
801		// 3. Budowa wariantu konfiguracji
802		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
813// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
814// ZAPISYWANIE USTAWIEŃ DO FLAG
815// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
816
817pub trait ConfigToFlags {
818	/// Przekształca zwalidowaną konfigurację z powrotem na listę flag CLI
819	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		// Ponieważ aktualnie wspieramy tylko tryb "default", zawsze to zwracamy.
939		// Jeśli w przyszłości dodasz inne formaty, trzeba będzie tu dać `match`.
940		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
1008// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1009// POBIERANIE GOTOWYCH USTAWIEŃ
1010// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1011pub trait ConfigGetSetup {
1012	/// Typ zwracany przez metodę get (różny dla każdej struktury)
1013	type Output;
1014	/// Przekształca konfigurację w gotowy obiekt warstwy logicznej
1015	fn get(&self) -> Self::Output;
1016}
1017// impl ConfigGetSetup for ValidExecutionConfig {}
1018// impl ConfigGetSetup for ValidSaveAsConfig {}
1019// impl ConfigGetSetup for ValidTablePartConfig {}
1020// impl ConfigGetSetup for ValidColumnItemConfig {}
1021impl 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				// LIST
1038				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				// NUM
1052				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				// ICONS
1062				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				// NAME
1076				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				// WHITESPACE (ALIGN_END)
1086				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}
1110// impl ConfigGetSetup for ValidTableColumnsConfig {}
1111// impl ConfigGetSetup for ValidColumnSizeConfig {}
1112// impl ConfigGetSetup for ValidColumnDateConfig {}
1113// impl ConfigGetSetup for ValidColumnTimeConfig {}
1114// impl ConfigGetSetup for ValidPatternConfig {}
1115impl ValidPatternConfig {
1116	/// Zwraca gotowy obiekt logic::PatternsQueries używany przez skaner.
1117	/// ⚡ UWAGA: Przyjmuje teraz ignore_case z zewnątrz (z Workspace)!
1118	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}
1123// impl ConfigGetSetup for ValidWorkspaceConfig { }
1124impl ConfigGetSetup for ValidSortByConfig {
1125	type Output = logic::SortQueries;
1126	/// Zwraca gotowy obiekt z warstwy logic:: (np. logic::SortQueries),
1127	/// gdzie dopiero tam te proste stringi i bool'e zostaną przetworzone
1128	/// w potężny algorytm (wspomniane 24 warianty).
1129	fn get(&self) -> Self::Output { logic::SortQueries::new(self.clone()) }
1130}
1131
1132// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1133// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1134// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1135// PREPARED JOB (KONTENER NA WSZYSTKIE USTAWIENIA - STEP 0)
1136// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1137// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1138// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1139
1140impl ValidPreparedJobConfig {
1141	/// Generuje plik TOML będący odwrotnością parsowania (eksportuje flagi)
1142	pub fn to_toml(&self, _id: &str, _name: Option<&str>, _description: Option<&str>) -> String {
1143		use std::fmt::Write; // Wymagane, aby writeln! działało na Stringu
1144
1145		let i = "job";
1146		let mut out = String::from("# Cargo-Plot Configuration File\n\n");
1147
1148		// TWOJE ZMIENNE TESTOWE (Przywrócone)
1149		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		// Nagłówek zadania - {} dla braku cudzysłowów, {:?} dla wartości
1154		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		// Sekcja SCAN
1162		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		// Sekcja TABLE
1170		let _ = writeln!(out, "[{}.attributes]", i);
1171		let _ = writeln!(out, "select = {:?}\n", self.cols.to_flags());
1172
1173		// Sekcja COLUMNś
1174		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		// Sekcja SAVE (Odkomentowana i sformatowana)
1188		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	/// Wczytuje konfigurację z pliku TOML.
1198	/// Sztuczka: `path: None` użyje domyślnej lokalizacji, `Some("...")` użyje własnej.
1199	/// Główna metoda ładująca. Używa jednej ścieżki docelowej jako źródła prawdy.
1200	pub fn from_toml(path: Option<&str>) -> Result<Self, String> {
1201		// 1. Definiujemy jedną docelową ścieżkę (domyślną lub podaną przez użytkownika)
1202		let target_path = path.unwrap_or("./target/.cargo-plot/task.toml");
1203
1204		// 2. Krok I: Jeśli pliku nie ma - zostanie natychmiast utworzony
1205		Self::ensure_config_exists(target_path)?;
1206
1207		// 3. Krok II: Odczyt i ratunek
1208		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				// Składnia jest nieczytelna/zepsuta -> Odpalamy walec naprawczy!
1215				Self::backup_and_reset_config(target_path)?;
1216
1217				// Po resecie wczytujemy na bezczelnego - to nasz świeży plik, więc na 100% zadziała
1218				let fresh_content = std::fs::read_to_string(target_path).unwrap();
1219				toml::from_str(&fresh_content).unwrap()
1220			}
1221		};
1222
1223		// 4. Krok III: Wyciąganie wartości (logika z poprzednich etapów)
1224		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		// =====================================================================
1231		// ZWINNE HELPERY DO WYCIĄGANIA DANYCH Z TOML
1232		// =====================================================================
1233		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		// =====================================================================
1250		// NAWIGACJA PO SEKCJACH
1251		// =====================================================================
1252		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		// =====================================================================
1259		// MAPOWANIE NA FLAGI CLI (Przygotowanie wsadu dla ParseFromFlags)
1260		// =====================================================================
1261
1262		let run_mode = get_arr(Some(job), "run_mode");
1263
1264		// Work: Budujemy z pojedynczych wartości naszą flagową strukturę wejściową
1265		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		// Save: Budujemy flagi typu `klucz=wartosc`
1285		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		// =====================================================================
1307		// BUDOWANIE OSTATECZNEJ STRUKTURY (Recykling logiki!)
1308		// =====================================================================
1309		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	/// Gwarantuje, że pod wskazaną ścieżką istnieje plik. Jeśli nie – tworzy go z ustawień domyślnych.
1325	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			// Zapewniamy istnienie folderów nadrzędnych
1330			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	/// Zmienia nazwę zepsutego pliku na backup (dodając tag czasu) i tworzy na jego miejsce nowy, domyślny.
1347	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			// Sprytne doklejenie tagu: np. "moj_config_backup-R2026...Q43.toml"
1353			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		// Skoro zrobiliśmy miejsce, wzywamy naszą główną metodę do utworzenia świeżego pliku
1362		Self::ensure_config_exists(target_path)
1363	}
1364}
1365
1366// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1367// POZOSTAŁE PRZYPDKI
1368// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1369
1370impl ValidSaveAsConfig {
1371	/// Helper: Gwarantuje, że folder docelowy istnieje i zwraca do niego pełną ścieżkę
1372	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	/// Wygodny builder: parsuje kolekcję stringów i od razu zwraca gotowy config.
1382	/// Zwraca Result, ponieważ stringi wejściowe mogą być nieprawidłowe.
1383	//pub fn from_strings<I>(inputs: I) -> Self
1384	//where
1385	//	I: IntoIterator,
1386	//	I::Item: AsRef<str>, {
1387	//	// 1. Parsujemy stringi na wektor enumów ValidColumnItemParse
1388	//	let parsed_items = ValidColumnItemParse::parse_vec(inputs);
1389	//
1390	//	// 2. Budujemy i zwracamy ostateczny config
1391	//	Self::get(parsed_items)
1392	//}
1393
1394	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		// 1. Jeśli num_is_first = true, numer idzie na początek
1405		if self.num_is_first {
1406			parts.push(num_str.clone());
1407		}
1408
1409		// 2. Struktura Listy
1410		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		// 3. Ikony
1416		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		// 4. Jeśli num_is_first = false, numer idzie po liście i ikonach (ale przed nazwą)
1422		if !self.num_is_first {
1423			parts.push(num_str);
1424		}
1425
1426		// 5. Nazwa pliku
1427		if self.name {
1428			parts.push(row.name_with_ext.clone());
1429		}
1430
1431		let mut base = parts.join(" ");
1432
1433		// 6. Align End (Trailing space) - dodaje pusty string, co przy join(" ") da spację na końcu
1434		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	/// Metoda formatująca rozmiar w zależności od wybranego trybu (Dec/Bin)
1446	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"], // Lub kB/MB/GB wg preferencji
1455		};
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// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1479// NOTATKI
1480// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1481/*
1482 * pub enum ValidSaveAsParse{
1483 * 	// w obu plikach musi być ten sam `logic::tag_time().0`,
1484 * 	out_dir,
1485 *  				▶️ ścieżka - domyślnie `./target/.cargo-plot/` - niezależnie jaki jest tworzy jeśli nie istnieje (ensure_exist)
1486 * 	title,
1487 * 				    ▶️ tytuł - domyślnie `Project Snapshot`
1488 *                  ▶️ i wtedy-> `format!("# {} {} (v:{}{} [{}])",
1489 *                          "INDEX (lub) FILES",
1490 *                          title,
1491 *                          logic::tag_time().0,
1492 *                          "SOTC (lub) COTS",
1493 *                          "Content of the structure (lub) Structure of the content")`
1494 * 	name,
1495 *                 ▶️ nazwa pliku - domyślnie name=""
1496 *                 ▶️ i wtedy-> `format!("{}{}_{}.md",
1497 *                          logic::tag_time().0,
1498 *                          "SOTC (lub) COTS",
1499 *                          name)`
1500 * 	name_is_prefix,
1501 *                ▶️ nazwa pliku - domyślnie name=""
1502 *                ▶️ i wtedy-> `format!("{}_{}{}.md",
1503 *                          name,
1504 *                          logic::tag_time().0,
1505 *                          "SOTC (lub) COTS")`
1506 * }
1507 * */
1508// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1509/*
1510 * dodajemy `ValidSortByParse`
1511 *
1512 * - "Date" -  `(Date:DateTime, Reverse:bool)`
1513 * - "Size" -  `(Size:Num, Reverse:bool)`
1514 * - "Name" -  `(Name:ModeSortName, Reverse:bool, FileGroup:ModeGroup)`
1515 * - "Path" -  `(Name:ModeSortName, Reverse:bool, DirSplit:bool, FileGroup:ModeGroup)`
1516 *
1517 *
1518 * `ModeSortName`
1519 * - "[Spec][Num][AZaz]"
1520 * - "[Spec][Num][azAZ]"
1521 * - "[Spec][Num][aAzZ]"
1522 * - "[Spec][Num][AaZz]"
1523 * - "[Num][Spec][AZaz]"
1524 * - "[Num][Spec][azAZ]"
1525 * - "[Num][Spec][aAzZ]"
1526 * - "[Num][Spec][AaZz]"
1527 * - "[Spec][AZaz][Num]"
1528 * - "[Spec][azAZ][Num]"
1529 * - "[Spec][aAzZ][Num]"
1530 * - "[Spec][AaZz][Num]"
1531 * - "[Num][AZaz][Spec]"
1532 * - "[Num][azAZ][Spec]"
1533 * - "[Num][aAzZ][Spec]"
1534 * - "[Num][AaZz][Spec]"
1535 * - "[AZaz][Num][Spec]"
1536 * - "[azAZ][Num][Spec]"
1537 * - "[aAzZ][Num][Spec]"
1538 * - "[AaZz][Num][Spec]"
1539 * - "[AZaz][Spec][Num]"
1540 * - "[azAZ][Spec][Num]"
1541 * - "[aAzZ][Spec][Num]"
1542 * - "[AaZz][Spec][Num]"
1543 *
1544 * `ModeGroup`
1545 * - "Name"
1546 * - "Exte"
1547 * - "None"
1548 *
1549 * `Priority`
1550 * - "FirstFile"
1551 * - "FirstDir"
1552 * - "None"
1553 *
1554 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1555 * oraz dodajemy `ValidSortByConfig`
1556 *
1557 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1558 * Tu kluczowe na metode sortowania będzie miał wpływ tryb `ValidColumnItemParse`
1559 * - ValidColumnItemParse::ListTree
1560 * - ValidColumnItemParse::ListFlat = ValidColumnItemParse::ListNone
1561 *
1562 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1563 * */
1564
1565// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1566
1567/*
1568 * kiedyś dodać ubsługe wyśfietlania ukrywania pustych folderów
1569 * kiedyś dodać ubsługe wyśfietlania ukrywania pustych plików
1570 *
1571 *
1572 *  */