stanhope/
main.rs

1#![doc(html_logo_url = "https://images.squarespace-cdn.com/content/v1/60416644b68a5453a868e856/1628206791960-M61IU1QP850NRLI9CE07/Stanhope+Etching.jpg?format=2500w")]
2//! **Stanhope**, named after the Stanhope Printing Press that improved upon Gutenberg's design by using sturdy iron and steel parts and hand levers to increase printing speed and quality.
3//! 
4//! This application takes an input argument file in a specific markup and produces a static, printable HTML page based on the markup file's contents.
5//! 
6//! The real power in this application is the markup language, which has these features:
7//! * Single-purpose: just for writing processes that can be printed as documents
8//! * General: suitable for writing any process
9//! * Consistent: output branding and style is centrally controlled for 100% consistent processes in an organization's library
10//! * *Visually* flexible: custom templates can mold the look/feel of the HTML produced
11//! * *Format* rigid: we want process authors to think carefully to help operators in the field, and Stanhope forces authors to conform to pre-thought out process language and structure that solves this
12
13mod read_csv;
14mod read_ebml;
15mod write_ebml;
16mod write_html;
17mod write_script;
18mod write_webmenu;
19
20//use std::process::Command; // needed for current implementation of PDF generation
21use std::{
22    process::Command,
23    //fs::{self, File},
24    //io::{self, BufRead, BufReader},
25    fs::{self},
26    io::{self, Error, ErrorKind, Result},
27    path::Path,
28};
29use chrono::prelude::*;
30use read_ebml::Process;
31
32use clap::Parser; // used for command line parameters and --help generation
33use read_csv::read_csv; // 
34use write_ebml::get_process_folder_name;
35use write_ebml::write_ebml;
36use read_ebml::read_ebml;
37use write_html::generate_complete_html;
38use write_webmenu::generate_complete_webmenu;
39use glob::glob;
40
41
42/// Command Line Interface (CLI) argument structure
43#[derive(Parser, Debug)]
44#[command(version, about =
45"\n\n\x1b[1;30;47mStanope\x1b[0m\x1b[30;47m, the Easy Button process generation engine.\x1b[0m
46
47Arguments passed into the options should be surrounded by single or double quotes, e.g.
48% ./stanhope -p \"EB-WI-0010\"    \x1b[36m<< double quotes are accepted\x1b[0m
49% ./stanhope -a 'EB-WI-*'       \x1b[36m<< single quotes are accepted\x1b[0m",
50author = "Strativus Group <contact@strativusgroup.com>",
51long_about = None,
52after_help = 
53"
54")]
55struct StanhopeArgs {
56    /// Read a single CSV file to create many new first-draft processes from scratch
57    /// 
58    /// - CSV-FILE
59    ///   - Comma-separated text file that strictly conforms to the template below
60    ///   - Exception: _TFlag_ columns (7th column and beyond) are user-defined
61    ///   - Free Excel file to modify and export as CSV: <https://stanhope.strativusgroup.com/latest/DocumentList.xlsx>
62    /// 
63    /// | "Document Number" | "Title" | "Subject" | "Product" | "Author" | "Reviewer" | _TFlag_ | _TFlag_ | ... |
64    /// | ----------------- | ------- | --------- | --------- | -------- | ---------- | ------- | ------- | --- |
65    /// | ...               | ...     | ...       | ...       | ...      | ...        |    x    |         | ... |
66    /// | ...               | ...     | ...       | ...       | ...      | ...        |         |    x    | ... |
67    /// | ...               | ...     | ...       | ...       | ...      | ...        |         |         | ... |
68    /// | ...               | ...     | ...       | ...       | ...      | ...        |    x    |    x    | ... |
69    #[arg(short, long, value_name = "CSV-FILE", default_value_t = String::from(""), verbatim_doc_comment)]
70    listgen: String,
71
72    /// Generate (or overwrite) a menu that indexes useful information for every process in a library
73    /// 
74    /// WebMenu.html is generated in the Process Library root folder
75    #[arg(short, long, default_value_t = false, verbatim_doc_comment)]
76    webmenu: bool,
77
78    /// Report missing graphics in the library, or (verbose) report all graphics, indicating missing ones
79    /// 
80    /// The report is printed to stdout
81    #[arg(short, long, default_value_t = false, verbatim_doc_comment)]
82    graphics_audit: bool,
83
84    /// Report discrepancies in the library among folder names, EBML file names, and the actual Document Number and Title
85    /// 
86    /// The report is printed to stdout
87    #[arg(short, long, default_value_t = false, verbatim_doc_comment)]
88    filename_audit: bool,
89
90    /// Read a single EBML and produce an HTML file as well as a PDF of that process
91    /// 
92    /// - DOCUMENT-NUMBER should be surrounded by quotes, particularly if wildcards are used
93    ///   - Wildcards are accepted, e.g. *, ?, [0-9]
94    ///   - Wildcards follow "glob" formatting: <https://docs.rs/glob/0.3.3/glob/struct.Pattern.html>
95    #[arg(short, long, value_name = "DOCUMENT-NUMBER", default_value_t = String::from(""), verbatim_doc_comment)]
96    process_ebml: String,
97
98    /// Export every "Command" line from a single process into a script with a defined language/format
99    /// 
100    /// - DOCUMENT-NUMBER should be surrounded by quotes, particularly if wildcards are used
101    /// - SCRIPT-FORMAT currently accepts
102    ///     ActionScript, AppleScript, bash, CoffeeScript, Dart,
103    ///     Elixir, JavaScript, Julia, Lua, MATLAB, Perl, PHP,
104    ///     PowerShell, Python, R, Ruby, TypeScript, VB.NET
105    ///         Full list and backlog:  <https://stanhope.strativusgroup.com/doc/stanhope/write_script/fn.learn.html>
106    /// - PAUSE-AFTER-EACH is true or false, true meaning "pause after each command"
107    #[arg(short, long, use_value_delimiter = true, value_delimiter = ' ', num_args = 3, value_names = ["DOCUMENT-NUMBER","SCRIPT-FORMAT","PAUSE-AFTER-EACH"], verbatim_doc_comment)] 
108    scriptify_process: Vec<String>,
109
110    /// Only perform the archiving process, i.e. create a new archive in "previous"
111    /// 
112    /// Note: this operation is performed at the end of every "Process EBML" action
113    /// This option _only_ performs the archiving, not the processing.
114    /// - DOCUMENT-NUMBER should be surrounded by quotes, particularly if wildcards are used
115    ///   - Wildcards are accepted, e.g. *, ?, [0-9]
116    ///   - Wildcards follow "glob" formatting: <https://docs.rs/glob/0.3.3/glob/struct.Pattern.html>
117    #[arg(short, long, value_name = "DOCUMENT-NUMBER", default_value_t = String::from(""), verbatim_doc_comment)]
118    archive_process: String,
119
120    /// Inspect a document by reading its EBML and returning information (no file generation)
121    /// 
122    /// - DOCUMENT-NUMBER should be surrounded by quotes, particularly if wildcards are used
123    ///   - Wildcards are accepted, e.g. *, ?, [0-9]
124    ///   - Wildcards follow "glob" formatting: <https://docs.rs/glob/0.3.3/glob/struct.Pattern.html>
125    #[arg(short, long, value_name = "DOCUMENT-NUMBER", default_value_t = String::from(""), verbatim_doc_comment)]
126    inspect_process: String,
127
128    /// Delete all but one "previous" version for each Revision for a given process
129    /// 
130    ///   For example, for a document that has many "previous" copies across four
131    ///   Revisions (-,A,B,C), then this option deletes all subdirectories except
132    ///   four: the one with the latest timestamp for each Revision variant
133    #[arg(short, long, value_name = "DOCUMENT-NUMBER", default_value_t = String::from(""), verbatim_doc_comment)]
134    cleanup_previous: String,
135
136    /// Output more information to stdout as Stanhope executes (flag)
137    #[arg(short, long, default_value_t = false,)]
138    verbose: bool,
139
140    /// Display syntax help for Easy Button Markup Language (EBML)
141    #[arg(short, long, default_value_t = false,)]
142    ebml_help: bool,
143}
144
145/// Recursive copy of all contents in one folder to another
146fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
147    fs::create_dir_all(&dst)?;
148    for entry in fs::read_dir(src)? {
149        let entry = entry?;
150        let ty = entry.file_type()?;
151        if ty.is_dir() {
152            copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
153        } else {
154            fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
155        }
156    }
157    Ok(())
158}
159
160/// Copy everything that ISN'T a directory... just the files
161fn copy_files_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
162    fs::create_dir_all(&dst)?;
163    for entry in fs::read_dir(src)? {
164        let entry = entry?;
165        let ty = entry.file_type()?;
166        if ty.is_dir() {
167            ();
168        } else {
169            fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
170        }
171    }
172    Ok(())
173}
174
175/// Perform OS functions to stash a copy of all working material for a process
176fn archive_single_process(new_proc: Process, verbose: &bool) {
177    // if it doesn't already exist, make a new folder called "previous"
178    //let previous_folder = new_proc.get_number().to_string()+"/previous"; // Old convention: process number is the process folder name. Now it's ( NUMBER - TITLE )
179    let previous_folder = get_process_folder_name(&new_proc).to_owned() + "/previous";
180    match Path::new(&previous_folder).exists() {
181        true => (),
182        false => { let _ = fs::create_dir(&previous_folder); },
183    };
184    let root_assets_folder = "./assets";
185    let previous_assets_folder = previous_folder.clone() + "/assets";
186    match Path::new(&previous_assets_folder).exists() {
187        true => (),
188        false => { let _ = fs::create_dir(&previous_assets_folder); },
189    };
190
191    // make a new folder to archive everything we just did in "previous"
192    let rev_string = new_proc.get_revision();
193    let right_now_string = Utc::now().format("UTC-%Y-%m-%d-T%H-%M-%S").to_string();
194    let timestamp_folder = previous_folder.clone() + "/Rev_" + new_proc.get_revision() + "_" + &right_now_string;
195    if *verbose { 
196        println!("Revision: {}",&rev_string);
197        println!("SystemTime::now() ...ish: {}",right_now_string);
198        println!("Trying to create this folder: {:?}",timestamp_folder);
199    }
200    let _ = fs::create_dir(&timestamp_folder);
201    // copy assets
202    let _ = copy_dir_all(root_assets_folder,previous_assets_folder);
203    // copy EBML, HTML, and PDF plus any other non-folder files in the process directory
204    let _ = copy_files_all(get_process_folder_name(&new_proc),timestamp_folder.clone());
205}
206
207/// File management: return a vector of directories eligible to delete as a "cleanup" operation
208/// 
209/// Eligibility criterion: any directory that *isn't* the most recent for its revision.
210/// 
211/// Example: If the contents of "previous" is the following list of directories:
212/// 1. Rev_-_UTC-2025-01-01-T00-00-00
213/// 1. Rev_-_UTC-2025-01-01-T00-00-01
214/// 1. Rev_A_UTC-2025-01-01-T00-05-00
215/// 
216/// The only eligible directory for deletion would be #1, because #2 is the latest Rev -, and #3 is the latest (only) Rev A.
217fn list_directories_to_cleanup(prev:String) -> Vec<String> {
218
219    let mut all_revs:Vec<String> = vec![];
220    let mut directories_to_cleanup:Vec<String> = vec![];
221    let all_archives = glob(&(prev.clone() + "/Rev_*_UTC-*-*-*-T*-*-*")).expect("Failed to read glob pattern");
222
223    for entry in all_archives {
224        match entry {
225            Ok(path) => {
226                let archive = path.file_name().expect("huh?").to_str().expect("huh?");
227                let pos = archive.find("_UTC-").expect("rust is hard");
228                let rev = String::from(&archive[4..pos]); 
229                all_revs.push(rev);
230            },
231            Err(e) => println!("{:?}",e),
232        }
233    }
234
235    all_revs.dedup();
236    println!("For this process, these are all the revisions: {:?}",&all_revs);
237
238    for rev in all_revs {
239        println!("\nHere are all of the archive folders for Rev {}:",rev);
240        let these = glob(&(prev.clone() + "/Rev_" + &rev + "_UTC-*-*-*-T*-*-*")).expect("Failed to read glob pattern");
241        let mut vec:Vec<String> = vec![];
242        for this in these {
243            match this {
244                Ok(path) => vec.push(path.file_name().expect("huh?").to_str().expect("huh?").to_string()),
245                Err(e) => println!("{:?}",e),    
246            }
247        }
248        vec.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
249        let keeper = vec.pop().expect("pop?");
250        for d in &vec {
251            println!("      {} \x1b[93mDELETE\x1b[0m",&d);
252        }
253        println!("   💾 {} 💾  << only one to be saved...",&keeper);
254        directories_to_cleanup.append(&mut vec);
255    }
256    directories_to_cleanup
257}
258
259/// Accept wildcard statements describing a subset of process folders, and return all matches
260pub fn globify_document_number(input:&String) -> Vec<String> {
261    let all_results = match input.chars().last().unwrap() {
262        '*' => glob(&(input.to_owned())).expect("Failed to read glob pattern"),
263        _ => glob(&(input.to_owned()+"*")).expect("Failed to read glob pattern"),
264    };
265    let mut matching_process_folders:Vec<String> = vec![];
266
267    for entry in all_results {
268        match entry {
269            Ok(path) => {
270                let process_folder = path.file_name().expect("huh?").to_str().expect("huh?");
271                matching_process_folders.push(String::from(process_folder));
272            },
273            Err(e) => println!("{:?}",e),
274        }
275    }
276    matching_process_folders
277}
278
279/// Gracefully cycle through ordered OS commands until one of them works
280fn attempt_fallbacks(fallbacks: Vec<Box<dyn Fn() -> Result<i32>>>) -> Result<i32> {
281    let mut last_error = None;
282
283    for fallback in fallbacks {
284        match fallback() {
285            Ok(value) => return Ok(value),
286            Err(e) => {
287                println!("Call failed, trying next... (Error: {})", e);
288                last_error = Some(e);
289            }
290        }
291    }
292
293    Err(last_error.unwrap_or_else(|| Error::new(ErrorKind::Other, "No fallbacks provided")))
294}
295
296/// Look into each process folder, read the EBML, find the images, and check to see if they exist, print findings to stdout
297/// 
298/// This function finds all images that the EBML will use in its HTML output when processed:
299/// - "Image" lines in the EBML
300/// - "Image" lines in followed "Section Reference" links to other processes
301/// - "Subject Image" line
302/// - "Product Image" line
303/// 
304/// Verbose version lists all images, not just the missing ones
305fn print_graphics_audit_to_stdout(v:&bool) {
306    let list_of_process_folders = write_webmenu::find_all_process_folders(".");
307    //println!("{:?}",list_of_process_folders);
308    print_title_block_to_stdout("Library Graphics Audit");
309    for folder in list_of_process_folders {
310        let folder_string = folder.file_name().expect("No bueno?").to_str().expect("Really bueno?").to_string();
311        let new_ebml_str = folder_string.clone() + "/" + &folder_string + ".ebml";
312        let new_process = read_ebml(&("./".to_owned()+&new_ebml_str));
313
314        if new_process.get_missing_image_count() == 0 {
315            println!(" [ No missing images! ]    {}",&folder_string);
316        } else {
317            println!(" ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒    {}",&folder_string);
318        }
319        if new_process.get_missing_image_count() > 0 {
320            println!("            ▒▒▒▒▒▒▒▒▒▒▒      -> Missing Images");
321            for image in new_process.get_missing_images() {
322            println!("               ▒▒▒▒▒▒▒▒        {}",image);
323            }
324        }
325        if *v {
326            if new_process.get_all_images().len() > 0 {
327                println!("                             -> All Images");
328                for image in new_process.get_all_images() {
329                    println!("                               {}",image.0);
330                } 
331            } else { println!("                             -> No images at all, in fact..."); }
332        }
333    }
334}
335
336/// Look into each process folder, read the EBML, find the Number and Title, check consistency, print findings to stdout
337/// 
338/// The fundamental assumption is that stanhope will find any given EBML file in this location:
339/// ./<DOCUMENT NUMBER> - <DOCUMENT TITLE>/<DOCUMENT NUMBER> - <DOCUMENT TITLE>.ebml
340/// 
341/// That is,
342/// - a process's folder is named "<DOCUMENT NUMBER> - <DOCUMENT TITLE>"
343/// - the EBML file is named "<DOCUMENT NUMBER> - <DOCUMENT TITLE>.ebml"
344/// 
345/// This function attempts to traverse the library, checking these assumptions as it goes, and printing to stdout
346/// 
347/// Verbose version lists all diagnostic information about every process, not just the issues it finds
348fn print_filename_audit_to_stdout(v:&bool) {
349    let list_of_process_folders = write_webmenu::find_all_process_folders(".");
350    print_title_block_to_stdout("Library Filename Audit");
351    for folder in list_of_process_folders {
352        let expected_folder_string = folder.file_name().expect("No bueno?").to_str().expect("Really bueno?").to_string();
353        let expected_ebml_str = expected_folder_string.clone() + "/" + &expected_folder_string + ".ebml";
354        println!("{}→ {}",if *v {"\n"}else{""},expected_folder_string);
355        match Path::new(&expected_ebml_str).exists() {
356            true => {
357                if *v { println!(" ↳ {}",expected_ebml_str); }
358                let new_proc = read_ebml(&("./".to_owned()+&expected_ebml_str));
359                if *v { println!(" ↳ EBML file exists as named above."); }
360                if *v { println!("   ↳ EBML Contents 'Number' : {}",new_proc.get_number()); }
361                if *v { println!("   ↳ EBML Contents 'Title'  : {}",new_proc.get_title()); }
362                if expected_folder_string == (new_proc.get_number().to_owned() + " - " + new_proc.get_title()).to_string() {
363                    if *v { println!(" ↳ CLEAN AUDIT: EBML CONTENTS MATCH FOLDER AND FILE NAMES"); }
364                } else {
365                    println!(" ↳ \x1b[31mWARNING: SOMETHING IS MISMATCHED BETWEEN FOLDER NAME, FILE NAME, AND EBML CONTENTS\x1b[0m");
366                    println!(" ↳ \x1b[33mSpecial characters in EBML title converted to '_' in folder/file names might be OK\x1b[0m");
367                }
368            },
369            false => {
370                println!(" ↳ \x1b[31m{}\x1b[0m",expected_ebml_str);
371                println!(" ↳ \x1b[31mWARNING: LIKELY FOLDER NAME MISMATCH WITH EBML NAME\x1b[0m");
372            },
373        };
374    }
375}
376
377/// Utility function to print a box outline around text in stdout, perhaps for a title
378/// 
379/// Example for "Library Graphics Audit"
380/// ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜
381/// ▌ Library Graphics Audit ▐
382/// ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟
383fn print_title_block_to_stdout(s:&str) {
384    let to_print = s.trim();
385    print!("▛▀"); print!("{:▀<1$}", "", to_print.len()); print!("▀▜\n");
386    print!("▌ "); print!("{}",&to_print); print!(" ▐\n");
387    print!("▙▄"); print!("{:▄<1$}", "", to_print.len());print!("▄▟\n");
388}
389
390/// Run Stanhope in one of (or more!) of its modes
391/// 
392/// _Examples:_
393/// >    stanhope -vl "DocumentList.csv"
394/// >    stanhope --verbose --listgen "DocumentList.csv"
395/// >    stanhope -w
396/// >    stanhope --webmenu
397/// >    stanhope -vp "EB-WI-00*"
398/// >    stanhope --verbose --process-ebml "EB-WI-00*"
399/// >    stanhope -vs "EB-WI-0?00"
400/// >    stanhope --verbose --scriptify-process "EB-WI-0?00"
401fn main() {
402
403    let args = StanhopeArgs::parse();
404
405    // /////////////////////////////////////////////////////////////////////////////////////////////
406    // If user specified a CSV file to import, then process it appropriately 
407    if !(&args.listgen=="") {
408
409        // Read each line of the CSV file
410        let needed_processes_list = read_csv(&args.listgen,args.verbose.clone());
411        if args.verbose { println!("Stanhope found {} EBML files to create in the CSV file.",needed_processes_list.len()); }
412        // Write to new files
413        for new_proc_to_create in needed_processes_list {
414            if args.verbose { new_proc_to_create.display_process_to_stdout() };
415            write_ebml(&new_proc_to_create,args.verbose.clone());
416        }
417
418    }
419
420    // /////////////////////////////////////////////////////////////////////////////////////////////
421    // If user specified a single process to "scriptify" along with a scripting format
422    if !(args.scriptify_process.len()==0) {
423        // First, we'll allow wildcards and look into every matching Document Number
424        let matching_processes = globify_document_number(&args.scriptify_process[0]);
425        if args.verbose { println!("{:?}",&matching_processes) };
426
427        if matching_processes.len() > 0 {
428
429            for process in matching_processes {
430
431
432                let file_to_read = "./".to_owned() + &process + "/" + &process + ".ebml";
433
434                if args.verbose {
435                    println!("");
436                    println!("=== Stanhope Scriptification =========================================================");
437                    println!("===");
438                    println!("=== >> Process:          {}",&process);
439                    println!("=== >> Script Format:    {}",&args.scriptify_process[1]);
440                    println!("=== >> Pause after each? {}",&args.scriptify_process[2]);
441                    println!("===");
442                    println!("======================================================================================");
443                    println!("");
444                }
445                
446                if args.verbose { print!("[A] Attempting to read EBML -> {} ...",&file_to_read); }
447                let new_proc = read_ebml(&file_to_read);
448                if args.verbose { print!("success!\n"); }
449                
450                let all_commands = new_proc.get_all_command_lines();
451                if args.verbose { print!("[B] Stanhope found {} lines of type \"Command\" in the file.\n",all_commands.len()); }
452
453                if args.verbose { print!("[C] Processing script format \"{}\"\n",&args.scriptify_process[1]); }
454                
455
456                let wait:bool = match args.scriptify_process[2].to_uppercase().as_str() {
457                    "F" | "FALSE" | "N" | "NO" => false,
458                    _ => true,
459                };
460                write_script::script_file_from_process(new_proc,write_script::learn(&args.scriptify_process[1]),wait)
461
462            } // End For loop through matching_processes
463        }; // End conditional to check that more than zero processes are requested
464    } // End Scriptify Process(es)
465
466    // /////////////////////////////////////////////////////////////////////////////////////////////
467    // If user specified an EBML file, then convert it to HTML and subsequently PDF 
468    if !(&args.process_ebml=="") {
469        // First, we'll allow wildcards and look into every matching Document Number
470        let matching_processes = globify_document_number(&args.process_ebml);
471        if args.verbose { println!("{:?}",&matching_processes) };
472
473        if matching_processes.len() > 0 {
474
475            for process in matching_processes {
476
477                let file_to_read = "./".to_owned() + &process + "/" + &process + ".ebml";
478                if args.verbose { println!("Attempting to process this file: {}",&file_to_read) };
479
480                let new_proc = read_ebml(&file_to_read);
481                if args.verbose { new_proc.display_process_to_stdout() }; 
482
483                // first, delete old html file, then generate a new one
484                if args.verbose { println!("Attempting to delete old HTML file: {:?}",fs::remove_file(&file_to_read.replace(".ebml",".html"))); } else { let _ = fs::remove_file(&file_to_read.replace(".ebml",".html")); }
485                
486                generate_complete_html(&String::from(&file_to_read.replace(".ebml",".html")),&new_proc);
487
488                //windows chrome
489                let win_chrome_path = "& 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe' --headless --no-pdf-header-footer --print-to-pdf=\"";
490                let win_process_library = "$PWD\\";
491                let win_chrome_string_to_throw = &(win_chrome_path.to_string() + win_process_library + &file_to_read.replace(".ebml",".pdf") + "\" \"" + win_process_library + &file_to_read.clone().replace(".ebml",".html\""));
492                //posix chrome
493                let posix_chrome = "chrome --headless --print-to-pdf=\"".to_string() + &file_to_read.clone().replace(".ebml",".pdf") + "\" \"" + &file_to_read.clone().replace(".ebml",".html") + "\" --no-pdf-header-footer";
494                //posix chromium
495                let posix_chromium = "chromium --headless --print-to-pdf=\"".to_string() + &file_to_read.clone().replace(".ebml",".pdf") + "\" \"" + &file_to_read.clone().replace(".ebml",".html") + "\" --no-pdf-header-footer";
496
497                let _output = if cfg!(target_os = "windows") {
498
499                    let mut fallbacks: Vec<Box<dyn Fn() -> Result<i32>>> = Vec::new();
500                    fallbacks.push(Box::new(move || {
501                        
502                        if args.verbose { println!("Here's my command string to chrome:\n\n{}\n\n",win_chrome_string_to_throw); }
503                        Command::new("powershell.exe")
504                            .args(["-Command", win_chrome_string_to_throw])
505                            .output()
506                            .expect("failed to execute process");
507                        
508                        Err(Error::new(ErrorKind::Other, "Chrome call to OS attempted."))
509                    }));
510
511                } else {
512
513                    // Create a sequence of commands to try
514                    let mut fallbacks: Vec<Box<dyn Fn() -> Result<i32>>> = Vec::new();
515                    fallbacks.push(Box::new(move || {
516            
517                        Command::new("sh")
518                            .arg("-c")
519                            .arg(&(posix_chrome))
520                            .output()
521                            .expect("failed to execute process");
522
523                        Err(Error::new(ErrorKind::Other, "Chrome call to OS attempted."))
524                    }));
525                    fallbacks.push(Box::new(move || {
526                        
527                        Command::new("sh")
528                            .arg("-c")
529                            .arg(&(posix_chromium))
530                            .output()
531                            .expect("failed to execute process");
532                        
533                        Err(Error::new(ErrorKind::Other, "Chromium call to OS attempted."))
534                    }));
535                    let _result = attempt_fallbacks(fallbacks);
536                    
537                };
538
539                archive_single_process(new_proc,&args.verbose);
540
541            } // End For loop through matching_processes
542        } // End If condition for length of matching_processes
543    } // End PROCESS EBML
544
545    // /////////////////////////////////////////////////////////////////////////////////////////////
546    // If user specified a single process to archive, just do the archiving
547    if !(&args.archive_process=="") {
548        // First, we'll allow wildcards and look into every matching Document Number
549        let matching_processes = globify_document_number(&args.archive_process);
550        if args.verbose { println!("{:?}",&matching_processes) };
551
552        if matching_processes.len() > 0 {
553
554            for process in matching_processes {
555
556                let file_to_read = "./".to_owned() + &process + "/" + &process + ".ebml";
557
558                let new_proc = read_ebml(&file_to_read);
559                archive_single_process(new_proc,&args.verbose);
560            }
561        }
562    }
563
564    // /////////////////////////////////////////////////////////////////////////////////////////////
565    // If user specified to clean up a single process
566    if !(&args.cleanup_previous=="") {
567        // First, we'll allow wildcards and look into every matching Document Number
568        let matching_processes = globify_document_number(&args.cleanup_previous);
569        if args.verbose { println!("{:?}",&matching_processes) };
570
571        if matching_processes.len() > 0 {
572
573            for process in matching_processes {
574
575                let prevs = "./".to_owned() + &process + "/previous";
576
577                let delete_these = list_directories_to_cleanup(prevs);
578
579                for dir in delete_these {
580                    println!("Attempting to delete {} ... ",&dir);
581                    let delete_this = "./".to_owned() + &process + "/previous/" + &dir;
582                    let _ = fs::remove_dir_all(delete_this);
583                }
584            }
585        }
586    }
587
588    // /////////////////////////////////////////////////////////////////////////////////////////////
589    // If user specified a single process to inspect
590    if !(&args.inspect_process=="") {
591        // First, we'll allow wildcards and look into every matching Document Number
592        let matching_processes = globify_document_number(&args.inspect_process);
593        if args.verbose { println!("{:?}",&matching_processes) };
594
595        if matching_processes.len() > 0 {
596
597            for process in matching_processes {
598
599                let file_to_read = "./".to_owned() + &process + "/" + &process + ".ebml";
600
601                let new_proc = read_ebml(&file_to_read);
602                new_proc.display_process_to_stdout();
603            }
604        }
605    }
606
607    // /////////////////////////////////////////////////////////////////////////////////////////////
608    // If user requested WebMenu generation
609    if args.webmenu {
610        generate_complete_webmenu(&args.verbose);
611    }
612
613    // /////////////////////////////////////////////////////////////////////////////////////////////
614    // If user requested WebMenu generation
615    if args.graphics_audit {
616        print_graphics_audit_to_stdout(&args.verbose);
617    }
618
619    // /////////////////////////////////////////////////////////////////////////////////////////////
620    // If user requested Filename Audit generation
621    if args.filename_audit {
622        print_filename_audit_to_stdout(&args.verbose);
623    }
624
625    // /////////////////////////////////////////////////////////////////////////////////////////////
626    // If user requested EBML help, show some text to stdout
627    if args.ebml_help {
628        println!("{}",format!("
629
630Note: Process files passed into --process-ebml are specifically-formatted EBML files (Easy Button Markup Language).
631
632\x1b[36m
633=================================================
634==   Easy Button Markup Language Cheat Sheet   ==
635==                                             ==
636==  Everything below this block is valid EBML  ==
637=================================================
638\x1b[0m
639
640// <COMMENT>
641Template | <CSS FILENAME FOUND IN ASSETS FOLDER>
642
643Title        | <PROCESS TITLE>
644Number       | <PROCESS NUMBER>
645Author       | <PROCESS AUTHOR>
646Reviewer     | <PROCESS REVIEWER>
647Process Type | <CATEGORY>, e.g. Test Procedure
648
649Subject       | <THING TO WHICH THE PROCESS APPLIES> 
650Subject Image | <FILENAME OF IMAGE OF SUBJECT>
651Product       | <THING PRODUCED BY THIS PROCESS> - optional
652Product Image | <FILENAME OF IMAGE OF PRODUCT> - optional
653
654Revision | <LATEST REV NUM>       | <DESCRIPTION OF CHANGE>
655Revision | <INTERMEDIATE REV NUM> | <DESCRIPTION OF CHANGE>
656Revision | <FIRST REV NUM>        | <INITIAL DESCTIPTION>
657
658Section  | <TITLE OF SECTION>
659    Step | <TITLE OF STEP>
660        
661        // These all must occur within a STEP
662        
663        Context      | <PLAIN TEXT STATEMENTS FOR CONTEXT>
664        Command      | <VERBATIM COMPUTER CODE>
665        Warning      | <BIG BOLD TEXT TO CAPTURE ATTENTION>
666        Image        | <FILENAME> | <CAPTION TEXT>
667        Resource     | <THING YOU NEED TO COMPLETE THIS STEP> | <CALIBRATED?>
668        Objective    | <ACHIEVED PURPOSE OF THIS PROCESS>
669        Out of Scope | <NOTE ABOUT WHAT NOT TO DO HERE>
670
671        // Verification methods can be single letter: A, I, D, T, S
672        // They can also be full words: Analysis, Inspection, Demonstration, Test, Sampling
673        Verification | <REQUIREMENT ID> | <FULL TEXT OF REQUIREMENT> | <VERIFICATION METHOD>
674
675        // Action lines can be grouped together to form consecutive table rows
676        // To optionally include Two-Party Verification, <TPV> can be \"TPV\" 
677        // To NOT make an action Two-Party Verified, stop at the <EXPECTED RESULT>
678        Action   | <THING TO DO> | <EXPECTED RESULT> | <TPV?> - optional
679        Action   | <THING TO DO> | <EXPECTED RESULT> | <TPV?> - optional
680
681// You can reference a different process's section and stanhope will fetch it for you
682Section Reference | <OTHER PROCESS> | <TITLE OF SECTION>
683
684
685// Alternate Aliases for EBML lines are below
686// Everything here continues to be valid EBML
687// Note: the EBML line designators (left of the first pipe) are NOT case sensitive
688// 'section' is the same as 'SECTION'
689// 'rev' is the same as 'REV'
690
691REV | C | 'Revision' has a new alias!
692rev | B | EBML line indicators are NOT case sensitive!
693ReV | A | I mean, you *could* do that...
694Rev | - | This is revision 'dash'
695
696SEC | Section Alias 'SEC' (or 'sec' etc.) works!
697    STP | Step Alias 'STP' (or 'stp' etc.) works!
698
699        // 'Context' has several new aliases
700        Comment | Context alias
701        TXT | Context alias
702        CMT | Context alias, perhaps short for 'comment'
703
704        // 'Command' has a new alias
705        CMD | Command alias
706        > | Command alias
707        % | Command alias
708        $ | Command alias
709        # | Command alias
710
711        // 'Image' has a new alias
712        IMG | image.ext | This caption appears under the image
713        PICTURE | image.ext | This caption appears under the image
714        PIC | image.ext | This caption appears under the image
715        Figure | image.ext | This caption appears under the image
716        FIG | image.ext | This caption appears under the image
717
718        // 'Action' has a new alias
719        // The Two-Party Verification flag has some aliases as well
720        DO | Do this action | Expect this result
721        DO | Do this action | Expect this result | TPV
722        DO | Do this action | Expect this result | T
723        DO | Do this action | Expect this result | TRUE
724        DO | Do this action | Expect this result | Y
725        DO | Do this action | Expect this result | YES
726        DO | Do this action | Expect this result | Two Party Verification
727        DO | Do this action | Expect this result | Two-Party Verification
728        // That makes an eight-row Action table, with TPVs for the last seven actions
729
730        // 'Warning' has new aliases
731        WARN | Warning text
732        WRN | Warning text
733        WAR | Warning text
734        Alert | Warning text
735        ! | Warning text
736
737        // 'Verification' has new aliases
738        VER | ReqID | Verification Text | Method
739        REQUIREMENT | ReqID | Verification Text | Method
740        REQ | ReqID | Verification Text | Method
741        // The methods of verification have a few aliases as well
742        REQ | ReqID | Verification Text | Demonstration
743        REQ | ReqID | Verification Text | Demo
744        REQ | ReqID | Verification Text | D
745        REQ | ReqID | Verification Text | Inspection
746        REQ | ReqID | Verification Text | I
747        REQ | ReqID | Verification Text | Analysis
748        REQ | ReqID | Verification Text | A
749        REQ | ReqID | Verification Text | Sampling
750        REQ | ReqID | Verification Text | Sample
751        REQ | ReqID | Verification Text | S
752        REQ | ReqID | Verification Text | Test
753        REQ | ReqID | Verification Text | T
754
755        // 'Resource' has a new alias
756        // There are several ways to indicate if a resource needs calibration, too!
757        RES | Name of resource
758        RES | Name of calibrated resource | C
759        RES | Name of calibrated resource | CAL
760        RES | Name of calibrated resource | CALIB
761        RES | Name of calibrated resource | CALIBRATE
762        RES | Name of calibrated resource | CALIBRATED
763        RES | Name of calibrated resource | CALIBRATION
764        RES | Name of calibrated resource | Y
765        RES | Name of calibrated resource | YES
766        RES | Name of calibrated resource | T
767        RES | Name of calibrated resource | TRUE
768        
769        // 'Objective' has a new alias
770        OBJ | Objective achieved!
771
772        // 'Out of Scope' has a new alias
773        OOS | Description of what is not in this process scope
774
775// 'Section Reference' has a new alias
776SECREF | Process-Number | Title of Section to Fetch
777
778\x1b[36m
779=================================================
780==  Everything above this block is valid EBML  ==
781==                                             ==
782==   Easy Button Markup Language Cheat Sheet   ==
783=================================================
784\x1b[0m
785
786For extensive examples of EBML in action, see https://stanhope.strativusgroup.com/latest/stylesheetviewer.html"));
787    }
788
789}