stanhope/
write_ebml.rs

1//! *Write an EBML file using a prototype, and some basic information about a new process*
2//! 
3//! Assumption: you know some basic information about a Process struct, enough to fill the basic fields:
4//! * **Number** - Document Number
5//! * **Title** - Document Title
6//! * **Subject** - To what the process applies
7//! * **Product** - Output of this process
8//! * **Author** - Who is to compose this process
9//! * **Reviewer** - Prior to publishing, who is to review
10//! 
11//! *Note: these aren't the only fields in the Process struct; these are the fields that are strictly required in a well-formatted CSV file when **stanhope** is invoked to read in a list of new processes to create.*
12//! 
13//! An EBML prototype file is required, and should be in the "assets" folder.
14
15use crate::read_ebml;
16use crate::read_ebml::Process;
17use crate::write_html;
18
19use std::{
20	fs::{self, OpenOptions, File},
21	io::{self, BufRead, BufReader, Write},
22	path::Path,
23};
24
25/// Apply the Library folder naming convention introduced in stanhope 0.56.0
26/// 
27/// The naming convention for a process folder within a library is:
28/// > Process Library/NUMBER - TITLE/
29/// 
30/// For reference, this breaks the previous convention (prior to 0.56.0) which was
31/// > Process Library/NUMBER/
32/// 
33/// The reason for this breaking change is the quality-of-life for users who can't memorize everything by document number alone
34/// 
35/// Note: the "TITLE" string is sanitized to replace illegal characers with underscores ("_"). Some "illegal" characers might be allowed in file systems, but this function takes a conservative approach to string sanitization. For this purpose, illegal characters include /\:*?"<>|#%&+{}
36pub fn get_process_folder_name(p:&Process) -> String {
37	String::from("./".to_owned() + p.get_number() + " - " + &p.get_title()
38		.replace("/","_")
39		.replace("\\","_")
40		.replace(":","_")
41		.replace("*","_")
42		.replace("?","_")
43		.replace("\"","_")
44		.replace("<","_")
45		.replace(">","_")
46		.replace("|","_")
47		.replace("#","_")
48		.replace("%","_")
49		.replace("&","_")
50		.replace("+","_")
51		.replace("{","_")
52		.replace("}","_")
53		)
54}
55
56/// Create a new folder with the same name as the Document **Number**, and write a new EBML file to it.
57/// 
58/// If the folder already exists, write the file in that directory.
59/// 
60/// If the folder *and* the file already exist, don't do anything! This function is really only to create a fresh EBML file as a starting point.
61pub fn write_ebml(p:&Process,verbose:bool) {
62
63	// New convention: PROCESS NUMBER - PROCESS TITLE is the folder name in the library...
64	let path = get_process_folder_name(p);
65
66	// Get loud in that stdout
67	if verbose { println!("\n{} folder already exists: {}", &path, Path::new(&path).exists()); }
68
69	/* CRITICAL BREAKING UPDATE!!
70	// Assumption: every process has its own folder, and that folder is named the PROCESS NUMBER
71	// 
72	// That folder also contains the EBML file (that's where we'll put it)
73	// let path = "./".to_string() + &p.get_number();
74	*/
75
76	// Assumption: EBML file names are the same as the folder name, e.g. "NUMBER - TITLE.ebml"
77	let path_to_file = path.to_string() + "/" + &path + ".ebml";
78
79	// Scream out to stdout whether or not the file and/or path already exist
80	if verbose {
81		println!("{} file already exists: {}", &path_to_file, Path::new(&path_to_file).exists());
82		println!("Path to the new file will be: {}",path_to_file);
83	}
84
85	// If the EBML file doesn't already exist in the folder
86	if !Path::new(&path_to_file).exists() {
87
88		// Check to see if the folder exists, and create it if it doesn't
89		match Path::new(&path).exists() {
90			true => (),
91			false => { let _ = fs::create_dir(&path); },
92		};
93
94		// Open the file to write
95		let mut file = OpenOptions::new()
96		            .read(true)
97		            .write(true)
98		            .create(true)
99		            .open(&path_to_file)
100		            .expect("Could not open the file!");
101
102		// Call function that references a template EBML file, and uses Process information to replace variables
103	    fill_in_template_ebml_file(&mut file,p);
104	    // That function actually does all the writing of the EBML file
105
106	    // The HTML file will be in the same folder with the same name, with .html as the extension
107	    let path_to_html_file = path.to_string() + "/" + &path + ".html";
108	    // We just wrote an EBML file, so we'll use the built-in function to read it back as a Process object
109	    let read_it_back_now = read_ebml::read_ebml(&path_to_file);
110	    // Use built-in functions to write HTML from the Process we extracted from the EBML we generated
111	    write_html::generate_complete_html(&path_to_html_file,&read_it_back_now);
112
113	    // Windows users: copy a specific batch script into each process folder (presume that it exists in .\assets folder).
114	    if cfg!(target_os = "windows") { let _did_copy_work = fs::copy("./assets/EasyButton.bat",path.to_string() + "/EasyButton.bat"); }
115	    // Mac users: copy a specific command script into each process folder (presume that it exists in .\assets folder). 
116	    if cfg!(target_os = "macos") { let _did_copy_work = fs::copy("./assets/EasyButton.command",path.to_string() + "/EasyButton.command"); }
117	    // Linux users: copy a specific shell script into each process folder (presume that it exists in .\assets folder). 
118	    if cfg!(target_os = "linux") { let _did_copy_work = fs::copy("./assets/EasyButton.sh",path.to_string() + "/EasyButton.sh"); }
119
120	// If the file already exists, this function doesn't do anything
121	} else { if verbose { println!("File already exists!"); print!("\n"); } };
122
123}
124
125/// Write a new EBML file that is line-for-line identical to a reference prototype file, except replace the file's variables with information from the Process struct.
126/// 
127/// Variables in the prototype follow this pattern: $VARIABLE, where VARIABLE represents the Process.variable field, accessed with Process.get_variable() << "variable" is not an actual field... used as syntactical example...
128fn fill_in_template_ebml_file(f:&mut File,p:&Process) {
129
130	// Assume the template file exists in the Assets folder
131	let template_file = "./assets/EBML-prototype.ebml";
132
133	// Read every line from the template file into a Vec<String>
134	fn lines_from_file(filename: impl AsRef<Path>) -> io::Result<Vec<String>> {
135		BufReader::new(File::open(filename)?).lines().collect()
136	}
137
138	// Call the function above
139	let lines = lines_from_file(template_file).expect("Could not load lines");
140	// Use built-in functions to pull out the information out of the process passed into this function
141	let number = p.get_number();
142	let title = p.get_title();
143	let subject = p.get_subject();
144	let product = p.get_product();
145	let author = p.get_author();
146	let reviewer = p.get_reviewer();
147
148	// Special case: Templates. This is the one piece of information in the Process structure that isn't as simple as find-and-replace.
149	//
150	// However, we will collapse the complexity into a simple find-and-replace by making the "replace" part very convoluted.
151	// 
152	// If there aren't any templates, then we'll comment out the Template line (clever!)
153	// If there are templates, then make lines like this: "Template|Template.css"
154	let all_templates = p.get_all_templates();
155	let mut template = if !(all_templates.len()==0) {
156		"Template|".to_string() + &all_templates[0]
157	} else {
158		"//Template|Example.css".to_string()
159	};
160	// That's enough if the number of Templates is 1, but for more than 1 we will add more lines with line breaks prior
161	// 
162	// All of this happens in the same string. When it is written out ONCE in the same find/replace as simpler variables, the line breaks will become good EBML formatting
163	if all_templates.len()>1 {
164		for ii in 1..all_templates.len() {
165			template = template + "\nTemplate|" + &all_templates[ii];
166		}
167	}
168
169	// For every line we read from the EBML template, write that **same** line out to the new file
170	// In so doing, pass the line to write through each of the find/replace filters, including the convoluted "Template" one
171	for line in lines {
172		let mut new_line = line;
173		new_line = new_line.replace("$NUMBER",number);
174		new_line = new_line.replace("$TITLE",title);
175		new_line = new_line.replace("$SUBJECT",subject);
176		new_line = new_line.replace("$PRODUCT",product);
177		new_line = new_line.replace("$AUTHOR",author);
178		new_line = new_line.replace("$REVIEWER",reviewer);
179		new_line = new_line.replace("Template|$TEMPLATE",&template);
180		new_line = new_line + "\n";
181		f.write(new_line.as_bytes()).expect("Could not write template line into new file!");
182	}
183	
184}