plot/lib/pipelines/
step6.rs1use 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
10pub fn engine_step6_data_save(job: &ReadyJob, formatted_rows: &[FormattedRow]) -> io::Result<()> {
15
16 let should_save = job.run_modes().contains(&SharedJobRunMode::Save)
19 || job.run_modes().contains(&SharedJobRunMode::SaveWithInspection);
20
21 if !should_save {
22 return Ok(()); }
24
25 let export = job.export();
26 let out_dir = export.out_dir();
27
28 if !out_dir.exists() {
30 fs::create_dir_all(out_dir)?;
31 }
32
33 let table_plain_text = render_table(formatted_rows, job.attributes().select());
37
38 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 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 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
120fn 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 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; 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 if LangMapper::is_blacklisted(&ext) || is_bin {
147 content.push_str("> *(Plik binarny/graficzny - pominięto zawartość)*\n\n");
148 } else {
149 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
168fn 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), (false, true) => format!("{}_{}{}.md", name, hash, tag), (false, false) => format!("{}{}_{}.md", hash, tag, name), }
175}