stanhope/
write_script.rs

1//! *Write various kinds of script files* to files in a Process folder, based on the "Command" lines in that process's EBML file.
2//! 
3//! General workflow, most of which occurs outside of this module:
4//! * User composes a process in an EBML file, with one or more "Command" lines
5//! * A call to stanhope to "scriptify" a process invokes the **read_ebml** module to collect the Command lines
6//! * Functions in this module know how to write scripts to the filesystem in various languages
7//! 
8//! NOTE: this module doesn't help the user compose their "Command" lines in proper syntax, nor do any of these functions convert "Command" lines from one language to another. As far as these functions are concerned, it is assumed that "Command" line text is already in proper syntax for the script file type selected when running stanhope to *scriptify*.
9use std::{
10	fs::{OpenOptions},
11	io::{Write},
12};
13use crate::read_ebml::Process;
14//use crate::read_ebml::Section;
15//use crate::read_ebml::Step;
16use crate::read_ebml::SubStep;
17
18
19//  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄         ▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄ 
20// ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌
21// ▐░█▀▀▀▀▀▀▀▀▀  ▀▀▀▀█░█▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░▌       ▐░▌▐░█▀▀▀▀▀▀▀▀▀  ▀▀▀▀█░█▀▀▀▀ 
22// ▐░▌               ▐░▌     ▐░▌       ▐░▌▐░▌       ▐░▌▐░▌               ▐░▌     
23// ▐░█▄▄▄▄▄▄▄▄▄      ▐░▌     ▐░█▄▄▄▄▄▄▄█░▌▐░▌       ▐░▌▐░▌               ▐░▌     
24// ▐░░░░░░░░░░░▌     ▐░▌     ▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░▌               ▐░▌     
25//  ▀▀▀▀▀▀▀▀▀█░▌     ▐░▌     ▐░█▀▀▀▀█░█▀▀ ▐░▌       ▐░▌▐░▌               ▐░▌     
26//           ▐░▌     ▐░▌     ▐░▌     ▐░▌  ▐░▌       ▐░▌▐░▌               ▐░▌     
27//  ▄▄▄▄▄▄▄▄▄█░▌     ▐░▌     ▐░▌      ▐░▌ ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄▄▄      ▐░▌     
28// ▐░░░░░░░░░░░▌     ▐░▌     ▐░▌       ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌     ▐░▌     
29//  ▀▀▀▀▀▀▀▀▀▀▀       ▀       ▀         ▀  ▀▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀▀       ▀   
30
31/// Scriptification requires a set of information describing the basic components of a scripting language. Each field of the Language struct includes sytax information.
32pub struct Language {
33
34	/// Human-readable name of the scripting language
35	name: 	String,
36
37	/// First line of a script file in this language (e.g. shebang statement)
38	first: 	String,
39
40	/// Single-line comments start with this string. "c_open" means "single-line Comment OPENing"
41	c_open:	String,
42
43	/// Single-line comments end with this string. Most comments only need an "open" but for completeness a "single-line Comment CLOSE" is defined
44	c_close:String,
45
46	/// Last line of a script file in this language (e.g. exit or return statement)
47	last: 	String,
48
49	/// File extension for scripts in this language, including the period (e.g. ".sh")
50	ext: 	String,
51
52	/// Syntactically correct way to wait for the user to press the ENTER key (functionality might vary by language limitations)
53	wait: 	String,
54
55	/// Printing information out to stdout starts with this text (e.g. "echo ")
56	p_open: String,
57
58	/// Printing information out to stdout ends with this text
59	p_close:String,
60}
61
62//  ▄▄▄▄▄▄▄▄▄▄▄  ▄         ▄  ▄▄        ▄  ▄▄▄▄▄▄▄▄▄▄▄ 
63// ▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░░▌      ▐░▌▐░░░░░░░░░░░▌
64// ▐░█▀▀▀▀▀▀▀▀▀ ▐░▌       ▐░▌▐░▌░▌     ▐░▌▐░█▀▀▀▀▀▀▀▀▀ 
65// ▐░▌          ▐░▌       ▐░▌▐░▌▐░▌    ▐░▌▐░▌          
66// ▐░█▄▄▄▄▄▄▄▄▄ ▐░▌       ▐░▌▐░▌ ▐░▌   ▐░▌▐░▌          
67// ▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░▌  ▐░▌  ▐░▌▐░▌          
68// ▐░█▀▀▀▀▀▀▀▀▀ ▐░▌       ▐░▌▐░▌   ▐░▌ ▐░▌▐░▌          
69// ▐░▌          ▐░▌       ▐░▌▐░▌    ▐░▌▐░▌▐░▌          
70// ▐░▌          ▐░█▄▄▄▄▄▄▄█░▌▐░▌     ▐░▐░▌▐░█▄▄▄▄▄▄▄▄▄ 
71// ▐░▌          ▐░░░░░░░░░░░▌▐░▌      ▐░░▌▐░░░░░░░░░░░▌
72//  ▀            ▀▀▀▀▀▀▀▀▀▀▀  ▀        ▀▀  ▀▀▀▀▀▀▀▀▀▀▀ 
73
74/// "Learn" a script language from user input (which language), simply by populating a Language struct
75/// 
76/// | Key | Status |
77/// | --- | ------ |
78/// | ⬜ | In backlog, not implemented |
79/// | 🟨 | Implemented, according to syntax found on the internet (send feedback!) |
80/// | 🟩 | Implemented and ad hoc tested (send feedback!) |
81/// 
82/// 
83/// 🟨 ActionScript\
84/// 🟩 AppleScript\
85/// ⬜ Based C++\
86/// 🟩 Bash\
87/// 🟨 CoffeeScript\
88/// 🟨 Dart\
89/// 🟨 Elixir\
90/// ⬜ Erg\
91/// ⬜ GDScript\
92/// ⬜ Gleam\
93/// ⬜ Groovy\
94/// 🟨 JavaScript\
95/// ⬜ JoraLang\
96/// 🟨 Julia\
97/// ⬜ KeyVa\
98/// 🟨 Lua\
99/// 🟩 MATLAB\
100/// ⬜ Mojo\
101/// ⬜ Nushell\
102/// 🟨 Perl\
103/// 🟨 PHP\
104/// 🟩 PowerShell\
105/// 🟩 Python 3\
106/// 🟨 R\
107/// ⬜ Roto\
108/// 🟨 Ruby\
109/// ⬜ SQL -> T-SQL\
110/// ⬜ SQL -> PL/SQL\
111/// ⬜ SQL -> PL/pgSQL\
112/// ⬜ SQL -> MySQL\
113/// ⬜ SQL -> MariaDB SQL\
114/// ⬜ SQL -> SQLite\
115/// ⬜ SQL -> PRQL\
116/// ⬜ SQL -> Spark SQL\
117/// ⬜ SQL -> HQL\
118/// ⬜ Tilde\
119/// 🟨 TypeScript\
120/// 🟨 VB.NET\
121/// ⬜ VBScript\
122/// ⬜ Verse
123/// 
124/// To request a language for the backlog, or to report a bug in the implementation of any scripting language, please email <mailto:feature.request@stanhope.strativusgroup.com>
125/// 
126/// When requesting a scripting language to add, please include syntax guides (or links to such) sufficient to fill a [Language] struct, and contact information. It would be great if you could help test your language's implementation!
127pub fn learn(lang:&str) -> Language { 
128	
129	// Whatever is passed into this function, we're going to strip it of all whitespace, collapse it, force it uppercase, and then use that string to compare
130	match lang.split_whitespace().collect::<Vec<_>>().join("").to_uppercase().as_str() {
131		"ACTIONSCRIPT" | "AS3" => Language{
132			name:	String::from("AppleScript"),
133			first:	String::from("#!/usr/bin/as3shebang //\nimport shell.Program;\nimport flash.system.System;"),
134			c_open:	String::from("// "),
135			c_close:String::from(""),
136			last:	String::from("function onKeyPress(event:KeyboardEvent):void\n{\n  if (event.keyCode == Keyboard.ENTER)\n  {    stage.removeEventListener(KeyboardEvent.KEY_UP, onKeyPress);\n    trace(\"Enter key pressed! Proceeding...\");\n    play();\n  }\n}\n\nreturn;"),
137			ext:	String::from(".asc"),
138			wait:	String::from("stop();\nstage.addEventListener(KeyboardEvent.KEY_UP, onKeyPress);"),
139			p_open: String::from("System.output(\""),
140			p_close:String::from("\n\");"),
141		},
142		"APPLESCRIPT" | "APPLE" => Language{
143			name:	String::from("AppleScript"),
144			first:	String::from("#!/usr/bin/osascript"),
145			c_open:	String::from("-- "),
146			c_close:String::from(""),
147			last:	String::from(""),
148			ext:	String::from(".command"),
149			wait:	String::from("read -p \"Press Enter to continue...\""),
150			p_open: String::from("echo '"),
151			p_close:String::from("'"),
152		},
153		"BASH" => Language{
154			name:	String::from("bash"),
155			first:	String::from("#!/bin/bash"),
156			c_open:	String::from("# "),
157			c_close:String::from(""),
158			last:	String::from("exit 0"),
159			ext:	String::from(".sh"),
160			wait:	String::from("read -p \"Press Enter to continue...\""),
161			p_open: String::from("echo '"),
162			p_close:String::from("'"),
163		},
164		"COFFEESCRIPT" | "COFFEE" => Language{
165			name:	String::from("CoffeeScript"),
166			first:	String::from("#!/usr/bin/env coffee\n\nreadline = require 'readline'\n\nwaitForEnter = ->\n  new Promise (resolve) ->\n    rl = readline.createInterface\n      input: process.stdin\n      output: process.stdout\n    rl.question 'Press Enter to continue...', ->\n      rl.close()\n      resolve()\n\n"),
167			c_open:	String::from("# "),
168			c_close:String::from(""),
169			last:	String::from("process.exit(1)"),
170			ext:	String::from(".coffee"),
171			wait:	String::from("await waitForEnter()"),
172			p_open: String::from("console.log \""),
173			p_close:String::from("\""),
174		},
175		"DART" => Language{
176			name:	String::from("Dart"),
177			first:	String::from("#!/usr/bin/env dart\nimport 'dart:io';\nvoid main() {"),
178			c_open:	String::from("// "),
179			c_close:String::from(""),
180			last:	String::from("exit(0);\n}"),
181			ext:	String::from(".dart"),
182			wait:	String::from("print('Press Enter to continue...');\nstdin.readLineSync();"),
183			p_open: String::from("print('"),
184			p_close:String::from("');"),
185		},
186		"ELIXIR" | "EXS" => Language{
187			name:	String::from("Elixir"),
188			first:	String::from("#!/usr/bin/env elixir"),
189			c_open:	String::from("# "),
190			c_close:String::from(""),
191			last:	String::from("Process.exit(self, :normal)"),
192			ext:	String::from(".exs"),
193			wait:	String::from("IO.gets(\"Press Enter to continue...\")"),
194			p_open: String::from("IO.puts(\""),
195			p_close:String::from("\")"),
196		},
197		"JAVASCRIPT" | "JS" | "NODE.JS" => Language{
198			name:	String::from("JavaScript"),
199			first:	String::from("#!/usr/bin/env node"),
200			c_open:	String::from("// "),
201			c_close:String::from(""),
202			last:	String::from("process.exit(0)"),
203			ext:	String::from(".js"),
204			wait:	String::from(""),
205			p_open: String::from("console.log(\""),
206			p_close:String::from("\");"),
207		},
208		"JULIA" | "JL" => Language{
209			name:	String::from("Julia"),
210			first:	String::from("#!/usr/bin/env -S julia --color=yes --startup-file=no"),
211			c_open:	String::from("# "),
212			c_close:String::from(""),
213			last:	String::from("exit(0)"),
214			ext:	String::from(".jl"),
215			wait:	String::from("Base.prompt(\"Press Enter to continue...\")"),
216			p_open: String::from("println(\""),
217			p_close:String::from("\")"),
218		},
219		"LUA" => Language{
220			name:	String::from("Lua"),
221			first:	String::from("#!/usr/bin/env lua"),
222			c_open:	String::from("-- "),
223			c_close:String::from(""),
224			last:	String::from("return"),
225			ext:	String::from(".lua"),
226			wait:	String::from("print(\"Press Enter to continue...\")\nio.read()"),
227			p_open: String::from("println(\""),
228			p_close:String::from("\")"),
229		},
230		"MATLAB" | "MATHWORKS" | "MAT" | "M" => Language{
231			name:	String::from("MATLAB"),
232			first:	String::from("% no shebang statement for MATLAB"),
233			c_open:	String::from("% "),
234			c_close:String::from(""),
235			last:	String::from("exit(0)"),
236			ext:	String::from(".m"),
237			wait:	String::from("disp('Press Enter to continue...'); input('');"),
238			p_open: String::from("disp('"),
239			p_close:String::from("');"),
240		},
241		"PERL" | "PL" => Language{
242			name:	String::from("Perl"),
243			first:	String::from("#!/usr/bin/env perl"),
244			c_open:	String::from("# "),
245			c_close:String::from(""),
246			last:	String::from("exit 1;"),
247			ext:	String::from(".pl"),
248			wait:	String::from("print \"Press Enter to continue...\";\n<STDIN>;"),
249			p_open: String::from("print \""),
250			p_close:String::from("\";"),
251		},
252		"PHP" => Language{
253			name:	String::from("PHP"),
254			first:	String::from("#!/usr/bin/env php"),
255			c_open:	String::from("// "),
256			c_close:String::from(""),
257			last:	String::from("exit(0);"),
258			ext:	String::from(".php"),
259			wait:	String::from("readline(\"Press Enter to proceed...\");"),
260			p_open: String::from("echo \""),
261			p_close:String::from("\";"),
262		},
263		"POWERSHELL" | "PS" | "PS1" => Language{
264			name:	String::from("PowerShell"),
265			first:	String::from("#!/usr/bin/env pwsh"),
266			c_open:	String::from("# "),
267			c_close:String::from(""),
268			last:	String::from("exit 0"),
269			ext:	String::from(".ps1"),
270			wait:	String::from("Read-Host -Prompt \"Press Enter to continue...\""),
271			p_open: String::from("Write-Host \""),
272			p_close:String::from("\""),
273		},
274		"PYTHON" | "PYTHON3" | "PY" => Language{
275			name:	String::from("Python 3"),
276			first:	String::from("#!/usr/bin/python3"),
277			c_open:	String::from("-- "),
278			c_close:String::from(""),
279			last:	String::from(""),
280			ext:	String::from(".py"),
281			wait:	String::from("input(\"Press Enter to continue...\")"),
282			p_open: String::from("print(\""),
283			p_close:String::from("\")"),
284		},
285		"R" => Language{
286			name:	String::from("R"),
287			first:	String::from("#!/usr/bin/env Rscript"),
288			c_open:	String::from("# "),
289			c_close:String::from(""),
290			last:	String::from("quit()"),
291			ext:	String::from(".R"),
292			wait:	String::from("readline(prompt = \"Press Enter to continue...\")"),
293			p_open: String::from("print(\""),
294			p_close:String::from("\")"),
295		},
296		"RUBY" | "RB" => Language{
297			name:	String::from("Ruby"),
298			first:	String::from("#!/usr/bin/env ruby"),
299			c_open:	String::from("// "),
300			c_close:String::from(""),
301			last:	String::from("exit(0)"),
302			ext:	String::from(".rb"),
303			wait:	String::from("puts \"Press Enter to continue...\"\ngets"),
304			p_open: String::from("puts \""),
305			p_close:String::from("\""),
306		},
307		"TYPESCRIPT" | "TS" => Language{
308			name:	String::from("TypeScript"),
309			first:	String::from("#!/usr/bin/env tsx"),
310			c_open:	String::from("# "),
311			c_close:String::from(""),
312			last:	String::from("process.exit(0)"),
313			ext:	String::from(".ts"),
314			wait:	String::from(""),
315			p_open: String::from("console.log(\""),
316			p_close:String::from("\")"),
317		},
318		"VB.NET" | "VBNET" | "VB" => Language{
319			name:	String::from("VB.NET"),
320			first:	String::from("#!/usr/bin/env dotnet run"),
321			c_open:	String::from("' "),
322			c_close:String::from(""),
323			last:	String::from("Return"),
324			ext:	String::from(".vb"),
325			wait:	String::from("Console.WriteLine(\"Press Enter to continue...\")\nConsole.ReadLine()"),
326			p_open: String::from("Console.WriteLine(\""),
327			p_close:String::from("\""),
328		},
329		_ => Language{
330			name:	String::from("Unknown"),
331			first:	String::from("#!/bin/sh"),
332			c_open:	String::from("# "),
333			c_close:String::from(""),
334			last:	String::from("exit 0"),
335			ext:	String::from(".sh"),
336			wait:	String::from("read -p \"Press Enter to continue...\""),
337			p_open: String::from("echo '"),
338			p_close:String::from("'"),
339		},
340	}
341}
342
343/// Create a script file using the Process struct, interpreted in a specified Language.
344/// * Some pre-canned comment blocks and stdout messages appear first
345/// * "Context" EBML lines appear as comments in the script
346/// * "Command" EBML lines appear as their own lines, verbatim
347/// * All other EBML is ignored
348/// 
349/// The script file appears alongside the process itself in its Process Folder.
350pub fn script_file_from_process(p:Process,lang:Language,wait:bool) {
351	
352	// An empty vector to fill with lines of text
353	let mut script_lines:Vec<String> = vec![];
354
355	// The [[Language]] struct includes a field for the language-specific first line of a script, e.g. shebang
356	script_lines.push(lang.first.to_owned());
357
358	// 
359	script_lines.push(comment_to_file(&("=============================================================".to_string()),&lang));
360	script_lines.push(comment_to_file(&(p.get_number().to_owned() + " Rev " + &p.get_revision() + "   " + &p.get_title()),&lang));
361	script_lines.push(comment_to_file(&("Author:   ".to_owned() + &p.get_author()),&lang));
362	script_lines.push(comment_to_file(&("Reviewer: ".to_owned() + &p.get_reviewer()),&lang));
363	script_lines.push(comment_to_file(&("".to_string()),&lang));
364	script_lines.push(comment_to_file(&(lang.name.to_owned()+ " generated by stanhope " + env!("CARGO_PKG_VERSION")),&lang));
365	script_lines.push(comment_to_file(&("=============================================================".to_string()),&lang));
366	
367	script_lines.push("".to_string());
368	
369	script_lines.push(print_to_stdout(&(p.get_number().to_owned() + " Rev " + &p.get_revision() + " " + &p.get_title()),&lang));
370	script_lines.push(print_to_stdout(&("".to_string()),&lang));
371	script_lines.push(print_to_stdout(&("Author:   ".to_owned() + &p.get_author()),&lang));
372	script_lines.push(print_to_stdout(&("Reviewer: ".to_owned() + &p.get_reviewer()),&lang));
373	script_lines.push(print_to_stdout(&("".to_string()),&lang));
374
375	script_lines.push("".to_string());
376
377	// Hold before start if we're in slow mode
378	if wait { script_lines.push(lang.wait.to_owned()) };
379
380	script_lines.push("".to_string());
381
382	// Loop through all of the sections
383	let mut secnum:u16 = 0;
384	for section in p.get_all_sections() {
385		secnum += 1;
386		let mut stpnum:u16 = 0;
387
388		// Add section title as a comment
389		script_lines.push(comment_to_file(&("=============================================================".to_string()),&lang));
390		script_lines.push(comment_to_file(&("Section ".to_string() + &secnum.to_string() + ": " + &section.get_title()),&lang));
391		script_lines.push(comment_to_file(&("=============================================================".to_string()),&lang));
392		script_lines.push("".to_string());
393
394		// Loop through all of the steps in this section
395		for step in section.get_all_steps() {
396			stpnum += 1;
397			// Add step title as a comment
398			script_lines.push(print_to_stdout(&("Step ".to_string() + &secnum.to_string() + "." + &stpnum.to_string()),&lang));
399			script_lines.push(print_to_stdout(&(step.get_text().trim().to_string()),&lang));
400			script_lines.push(print_to_stdout(&("".to_string()),&lang));
401
402			// Loop though all substeps of this step
403			for substep in step.get_all_sub_steps() {
404				match substep {
405
406					// Context lines are added as comments
407					// TODO: verbose stdout
408					SubStep::Context(txt) => { script_lines.push(comment_to_file(&(txt.trim().to_string()),&lang)); },
409
410					SubStep::Command(txt) => {
411						// Command lines are passed through unmolested
412						script_lines.push(txt.trim().to_string());
413						// TODO: verbose stdout
414
415						// If "wait" is true, add language-specific wait
416						if wait { script_lines.push(lang.wait.to_owned()) };
417					},
418
419					/*
420					* * * * * * * * * *
421					Objective Achieved!
422					*/
423					SubStep::Objective(txt) => {
424						script_lines.push(print_to_stdout(&("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *".to_string()),&lang));
425						script_lines.push(print_to_stdout(&("Objective Achieved!".to_string()),&lang));
426						script_lines.push(print_to_stdout(&(&txt.trim().to_string()),&lang));
427						script_lines.push(print_to_stdout(&("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *".to_string()),&lang));
428					},
429					
430					// Ignore all other substep types
431					// TODO: consider including verifications to stdout
432					_ => {},
433				}
434			}
435			// add a spacer line to stdout at the end of the step, and in the script itself for readability
436			script_lines.push(print_to_stdout(&("".to_string()),&lang));
437			script_lines.push("".to_string());
438		}
439		// Add another spacer line at the end of the section
440		script_lines.push("".to_string());
441	}
442
443	// Last line
444	script_lines.push(lang.last.to_owned());
445
446	// Now all the lines of the script are stored in script_lines, and they need to be written out to a new file (or overwritten...)
447
448	// Assumption: every process has its own folder, and that folder is named the PROCESS NUMBER
449	let path_to_file = "./".to_string() + p.get_number() + " - " + p.get_title() + "/" + p.get_number() + " - " + p.get_title() + &lang.ext;
450
451	// Open the file to write
452	let mut file = OpenOptions::new()
453	            .read(true)
454	            .write(true)
455	            .create(true)
456	            .truncate(true)
457	            .open(&path_to_file)
458	            .expect("Could not open the file!");
459
460	for line in script_lines {
461		file.write((line + "\n").as_bytes()).expect("Could not write template line into new file!");
462	}
463
464	println!("You will need to make the script file executable if it isn't already. For example, you might need to run:\nchmod +x \"{}\"",&path_to_file);
465
466}
467
468/// Standardize how to write a comment line, given the text to comment, and the language in which to comment
469fn comment_to_file(txt:&String,lang:&Language) -> String { lang.c_open.to_owned() + txt + &lang.c_close }
470
471/// Standardize how to print to stdout, given the text to print out, and the language in which to print to stdout
472fn print_to_stdout(txt:&String,lang:&Language) -> String { 
473
474	let txt_san = txt.replace('"',"").replace("'","");
475	lang.p_open.to_owned() + &txt_san + &lang.p_close 
476}
477
478
479//  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄ 
480// ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌
481//  ▀▀▀▀█░█▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀  ▀▀▀▀█░█▀▀▀▀ 
482//      ▐░▌     ▐░▌          ▐░▌               ▐░▌     
483//      ▐░▌     ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄      ▐░▌     
484//      ▐░▌     ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌     ▐░▌     
485//      ▐░▌     ▐░█▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀█░▌     ▐░▌     
486//      ▐░▌     ▐░▌                    ▐░▌     ▐░▌     
487//      ▐░▌     ▐░█▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄█░▌     ▐░▌     
488//      ▐░▌     ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌     ▐░▌     
489//       ▀       ▀▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀▀       ▀     
490
491#[cfg(test)]
492mod tests {
493
494	//use super::*;
495
496
497}