Skip to main content

plot/lib/pipelines/
step6.rs

1use std::{fs, io, path::Path};
2
3use crate::lib::{
4    logic::{ScanNodeIs, file_save_safe_if_changed, tag_from_time_now},
5    mapping::LangMapper,
6    pipelines::step2::{FormattedRow, render_table},
7    schema::{ReadyJob, SharedJobRunMode},
8};
9
10// ============================================================================
11// EXPORTER (Step 6) - ZAPIS DO PLIKU MARKDOWN
12// ============================================================================
13
14pub fn engine_step6_data_save(job: &ReadyJob, formatted_rows: &[FormattedRow]) -> io::Result<()> {
15
16    // Funkcja prepare() dba o to, żeby DryRun usuwał flagi Save.
17    // My tu sprawdzamy po prostu, czy program ma autoryzację do zapisu.
18    let should_save = job.run_modes().contains(&SharedJobRunMode::Save)
19        || job.run_modes().contains(&SharedJobRunMode::SaveWithInspection);
20
21    if !should_save {
22        return Ok(()); // Tryb cichy/suchy bieg - wychodzimy bez błędu
23    }
24
25    let export = job.export();
26    let out_dir = export.out_dir();
27
28	// 1. Gwarantujemy istnienie folderu docelowego
29    if !out_dir.exists() {
30        fs::create_dir_all(out_dir)?;
31    }
32
33	// ========================================================================
34	// GENEROWANIE TABELI (Używamy gotowego generatora mock_render)
35	// ========================================================================
36	let table_plain_text = render_table(formatted_rows, job.attributes().select());
37
38	// ========================================================================
39	// GENEROWANIE TREŚCI
40	// ========================================================================
41	let tag = tag_from_time_now().0;
42	let name_is_prefix = export.name_is_first();
43    let export_name = export.name();
44
45	let name_index_sotc = build_filename(export_name, &tag, name_is_prefix, "SOTC-INDEX");
46    let name_files_cots = build_filename(export_name, &tag, name_is_prefix, "COTS-FILES");
47    let name_combined = build_filename(export_name, &tag, name_is_prefix, "COTS");
48
49	//-------------------------
50
51	let h1_title = format!("# {}\n\n", format!("{} (v:{})", export.title(), tag).trim());
52    let h2_title_sotc = format!("## INDEX [SOTC - Structure of the content] (v:{})\n", tag);
53    let h2_title_sotc2 = format!("## INDEX OF THE CONTENT\n");
54    let h2_title_cots = format!("## FILES [COTS - Content of the structure] (v:{})\n", tag);
55    let h3_title_file = |idx: usize, path: &str| -> String { format!("### {:03}: `{}`\n\n", idx, path) };
56
57	let mut sotc_index = String::new();
58	sotc_index.push_str(&h2_title_sotc);
59	sotc_index.push_str("\n```plaintext\n");
60	sotc_index.push_str(&table_plain_text);
61	sotc_index.push_str("```\n");
62
63	let mut sotc_index2 = String::new();
64	sotc_index2.push_str(&h2_title_sotc2);
65	sotc_index2.push_str("\n```plaintext\n");
66	sotc_index2.push_str(&table_plain_text);
67	sotc_index2.push_str("```\n");
68
69	let mut cots_files = String::new();
70	cots_files.push_str(&h2_title_cots);
71	cots_files.push_str("\n");
72	cots_files.push_str(&generate_cots_files(
73        formatted_rows,
74        h3_title_file,
75        job.explorer().workspace_dir(),
76    ));
77
78	// ========================================================================
79	// ZAPIS TREŚCI
80	// ========================================================================
81	if !export.save_separately() {
82		let path: std::path::PathBuf = out_dir.join(name_combined);
83		let mut text: String = String::new();
84
85		text.push_str(&h1_title);
86		text.push_str(&sotc_index);
87		text.push_str("\n");
88		text.push_str(&cots_files);
89		text.push_str(&sotc_index2);
90		text.push_str("\n---\n");
91
92		file_save_safe_if_changed(&path, &text);
93		println!("📦 Zapisano COTS (INDEX & FILES) do: {}", path.display());
94	}
95	else {
96		let path: (std::path::PathBuf,std::path::PathBuf) = (out_dir.join(name_index_sotc),out_dir.join(name_files_cots));
97		let mut text: (String,String) = (String::new(), String::new());
98
99		text.0.push_str(&h1_title);
100		text.1.push_str(&h1_title);
101		text.0.push_str(&sotc_index);
102		text.1.push_str(&cots_files);
103		text.0.push_str("\n---\n");
104		text.1.push_str("\n---\n");
105
106		file_save_safe_if_changed(&path.0, &text.0);
107		println!("📦 Zapisano SOTC (INDEX) do: {}", path.0.display());
108		file_save_safe_if_changed(&path.1, &text.1);
109		println!("📦 Zapisano COTS (FILES) do: {}", path.1.display());
110	}
111
112
113
114
115	Ok(())
116}
117
118
119
120/// ⚡ Pomocnicza funkcja generująca WSZYSTKIE bloki z kodem.
121fn generate_cots_files<F>(rows: &[FormattedRow], mut format_title: F, workspace_dir: &Path) -> String
122where
123    F: FnMut(usize, &str) -> String,
124{
125    let mut content = String::new();
126
127    // Enumerate() działa na całości, zachowując zgodność numeracji plików z tabelą ("dziury" dla folderów)
128    for (i, row) in rows.iter().enumerate() {
129        let n = &row.raw.node;
130
131        if n.node == ScanNodeIs::Dir {
132            continue;
133        }
134
135        let rel_path = &n.path.str;
136        let actual_idx = i + 1; // Numer zgodny z wierszem tabeli
137
138        // Użycie przekazanego callbacka do tytułu
139        content.push_str(&format_title(actual_idx, rel_path));
140
141        let ext = Path::new(rel_path).extension().unwrap_or_default().to_string_lossy().to_string();
142        let lang = LangMapper::get_md_lang(&ext);
143        let is_bin = n.file_is_binary.unwrap_or(false);
144
145        // Zrzut kodu lub błędu
146        if LangMapper::is_blacklisted(&ext) || is_bin {
147            content.push_str("> *(Plik binarny/graficzny - pominięto zawartość)*\n\n");
148        } else {
149			// ⚡ Używamy workspace_dir jako bazy do odczytu zawartości
150            let clean_rel = rel_path.strip_prefix("./").unwrap_or(rel_path);
151            let absolute_path = workspace_dir.join(clean_rel);
152
153            match fs::read_to_string(&absolute_path) {
154                Ok(file_content) => {
155                    let safe_content = file_content.replace('\t', "    ");
156                    content.push_str(&format!("```{}\n{}\n```\n\n", lang, safe_content));
157                }
158                Err(_) => {
159                    content.push_str("> *(Błąd odczytu / plik nie jest UTF-8)*\n\n");
160                }
161            }
162        }
163    }
164
165    content
166}
167
168/// Helper: Generuje nazwę pliku w zależności od podanej nazwy, prefiksu i tagu.
169fn build_filename(name: &str, hash: &str, is_prefix: bool, tag: &str) -> String {
170    match (name.is_empty(), is_prefix) {
171        (true, _)      => format!("{}{}.md", hash, tag),           // Brak nazwy
172        (false, true)  => format!("{}_{}{}.md", name, hash, tag),  // Nazwa jako prefiks
173        (false, false) => format!("{}{}_{}.md", hash, tag, name),  // Nazwa jako sufiks
174    }
175}