build_process.rs
1 pub mod build_process_handler { 2 3 use file_handling::file_handling::file_handling::{ 4 collect_asset_directories, handle_config_details_update, handle_storage, modify_pubspec, 5 }; 6 use markdown_backend_utilities::{ 7 data_processing::merge_json::merge_json, 8 file::{file_name_extractor, is_emacs_file::is_emacs_file}, 9 }; 10 use markdown_element_lib::markdown_line_element::MarkdownLineElement; 11 use serde_json::Value; 12 use std::collections::HashMap; 13 use std::fs; 14 use std::io::BufRead; 15 use std::path::Path; 16 17 pub fn build_handler(target_location: &Path, output_dir: &Path, build_command: &str) { 18 // Check target_location exists 19 if !target_location.exists() { 20 panic!("Target location does not exist: {:?}", target_location); 21 } 22 23 let build_dir_string = &format!( 24 "{}{}", 25 output_dir.to_string_lossy(), 26 "/assets/processed_data/" 27 ); 28 let build_dir: &Path = Path::new(&build_dir_string); 29 30 // Ensure we are not setting the output directory inside itself 31 if target_location == build_dir { 32 panic!("Target location cannot be 'processed_data/' itself."); 33 } 34 35 // Remove existing build 36 if build_dir.exists() { 37 fs::remove_dir_all(&build_dir).expect( 38 format!( 39 "Failed to delete existing processed_data directory in {:#?}", 40 target_location 41 ) 42 .as_str(), 43 ); 44 } 45 46 // Create new build direcory 47 fs::create_dir_all(&build_dir).expect("Failed to create output directory"); 48 49 let mut config_details: HashMap<String, HashMap<String, String>> = HashMap::new(); 50 let mut additional_config_details: HashMap<String, HashMap<String, Vec<String>>> = 51 HashMap::new(); 52 additional_config_details.insert( 53 "actionWidgetCallbackValuesListsMap".to_string(), 54 HashMap::new(), 55 ); 56 additional_config_details.insert("actionWidgetStringsListsMap".to_string(), HashMap::new()); 57 // Recursively processes md tree at target_location into toml tree at build_dir for Flutter to build with 58 process_directory( 59 &target_location, 60 &target_location, 61 &build_dir, 62 &mut config_details, 63 &mut additional_config_details, 64 ); 65 66 // Serialize config_details into JSON format 67 let config_details_json = serde_json::to_string(&config_details) 68 .expect("Failed to serialize config_details to JSON"); 69 let additional_config_details_json = serde_json::to_string(&additional_config_details) 70 .expect("Failed to serialize additional_config_details to JSON"); 71 72 // Define the output path for the JSON file 73 let config_details_output_path = build_dir.join("nexus_key_config.json"); 74 75 // Write the serialized JSON to the file 76 fs::write( 77 config_details_output_path, 78 serde_json::to_string(&merge_json( 79 &config_details_json, 80 &additional_config_details_json, 81 )) 82 .unwrap(), 83 ) 84 .expect("Failed to write config_details JSON to file"); 85 86 // Get list of all subdirectories to put in 87 // assets file (pubspec.yaml)because Flutter 88 // is annoying with its build system 89 let mut list_of_nested_directory_locations: Vec<String> = vec![]; 90 collect_asset_directories( 91 build_dir, 92 "processed_data", 93 &mut list_of_nested_directory_locations, 94 ); 95 96 // Write over the existing pubspec.yaml with 97 // the old assets locations removed and new 98 // ones added 99 modify_pubspec( 100 &output_dir.join("pubspec.yaml"), 101 list_of_nested_directory_locations, 102 ); 103 104 // build_and_launch_flutter(output_dir, build_command); 105 println!( 106 "build_and_launch_flutter(\noutput_dir={},\nbuild_command={}", 107 output_dir.display(), 108 build_command 109 ); 110 } 111 112 fn process_directory( 113 // Original Dir is needed to prevent copying the same substring showing where the root is every single time when using input_dir as key 114 original_dir: &Path, 115 input_dir: &Path, 116 output_dir: &Path, 117 config_details: &mut HashMap<String, HashMap<String, String>>, 118 additional_config_details: &mut HashMap<String, HashMap<String, Vec<String>>>, 119 ) { 120 if input_dir 121 .file_name() 122 .map_or(false, |name| name == "markdown_to_flutter_build_output") 123 { 124 return; 125 } 126 let output_key: String = input_dir 127 .strip_prefix(original_dir) 128 .map(|p| p.to_string_lossy().into_owned()) 129 .unwrap_or_else(|_| input_dir.to_string_lossy().into_owned()); 130 131 if let Some(map) = additional_config_details.get_mut("actionWidgetStringsListsMap") { 132 map.insert(output_key.to_string(), vec![]); 133 } 134 if let Some(map) = additional_config_details.get_mut("actionWidgetCallbackValuesListsMap") { 135 map.insert(output_key.to_string(), vec![]); 136 } 137 138 // Get and go through files in directory 139 for entry in fs::read_dir(input_dir).expect("Failed to read directory") { 140 // Get file 141 let entry = entry.expect("Failed to get directory entry"); 142 // File path 143 let path = entry.path(); 144 145 // Ensure we are not processing anything inside processed_data itself 146 if path.starts_with(&output_dir) { 147 continue; 148 } 149 150 // Get the text after the last slash 151 // Path => String 152 let file_name = file_name_extractor::extract_file_name_from_path( 153 path.to_string_lossy().to_string(), 154 ); 155 156 // Where the file is going to be saved 157 let output_path = output_dir.join(&file_name); 158 159 // Check if directory 160 if path.is_dir() { 161 // Make directory 162 fs::create_dir_all(&output_path).expect(&format!( 163 "Failed to create output directory: {:#?}", 164 &output_path 165 )); 166 167 // Recurse for current directory 168 process_directory( 169 &original_dir, 170 &path, 171 &output_path, 172 config_details, 173 additional_config_details, 174 ); 175 } 176 // Is a file 177 else if let Some(extension) = path.extension() { 178 // Not a file to be ignored 179 if !(extension == "md" || extension == "json") || is_emacs_file(&path) { 180 continue; 181 } 182 183 let content = match fs::read_to_string(&path) { 184 Ok(file_content) => file_content, 185 Err(e) => { 186 if (extension == "json") { 187 panic!( 188 "JSON formatting error in file: {:#?}\nERR MSG:\n{}", 189 path, e 190 ); 191 } else { 192 panic!("Error opening md file: {}", e); 193 } 194 } 195 }; 196 197 match extension.to_str().unwrap() { 198 "md" => { 199 let parsed_lines = MarkdownLineElement::parse_markdown(&content); 200 handle_config_details_update( 201 &parsed_lines, 202 config_details, 203 &output_key, 204 &file_name, 205 ); 206 207 handle_storage( 208 &file_name, 209 &parsed_lines, 210 output_path.with_extension("json"), 211 ); 212 } 213 "json" => { 214 let parsed_json: Value = serde_json::from_str(&content) 215 .expect(&format!("Failed to parse JSON file: {:#?}", output_dir)); 216 217 for (section, items) in 218 parsed_json.as_object().expect("JSON is not an object") 219 { 220 if let Some(array) = items.as_array() { 221 println!("Section: {}", section); 222 223 for item in array { 224 if let Some(obj) = item.as_object() { 225 println!("HERR {:#?}", obj); 226 227 if let Some(map) = additional_config_details 228 .get_mut("actionWidgetCallbackValuesListsMap") 229 { 230 if let Some(val) = map.get_mut(&output_key) { 231 if let Some(location) = 232 obj.get("location").and_then(|v| v.as_str()) 233 { 234 val.push(location.to_string()); 235 } 236 } 237 } 238 239 if let Some(map) = additional_config_details 240 .get_mut("actionWidgetStringsListsMap") 241 { 242 if let Some(val) = map.get_mut(&output_key) { 243 if let Some(title) = 244 obj.get("title").and_then(|v| v.as_str()) 245 { 246 val.push(title.to_string()); 247 } 248 } 249 } 250 } 251 } 252 } 253 } 254 255 fs::write(output_path.with_extension("json"), content) 256 .expect("Failed to write config JSON to file"); 257 } 258 _ => { 259 panic!("What the fuck? We're only supposed to have md and json files!") 260 } 261 }; 262 } 263 } 264 } 265 }