s_crap/
lib.rs

1#[path = "arg.rs"]
2pub mod arg;
3#[path = "cli.rs"]
4pub mod cli;
5#[path = "commands/mod.rs"]
6pub mod commands;
7#[path = "error.rs"]
8pub mod error;
9
10use error::Error as ScrapError;
11use std::any::Any;
12
13impl<T> Downcast for T {}
14pub trait Downcast {
15	// TODO: find a way to convert Box<dyn Any> into Box<dyn Error>
16	// https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0b19ec3df257a583d4e560cbfb51b808
17	fn downcast<T: Any>(self) -> Result<T, ScrapError<'static>>
18	where
19		Self: Sized + 'static,
20	{
21		let this: Box<dyn Any> = Box::new(self);
22		this.downcast::<T>().map(|t| *t).map_err(|_| ScrapError::Downcast)
23	}
24}
25
26pub mod prompt {
27	use rustyline::config::{self, *};
28
29	pub const REPL: &str = "ยป";
30	pub const ERROR: &str = "ERROR:";
31
32	// TODO: PR are welcome ๐Ÿ˜†
33	pub const CONFIG: fn() -> config::Config = || {
34		config::Builder::new()
35			.history_ignore_dups(true)
36			.history_ignore_space(true)
37			.auto_add_history(true)
38			.completion_type(CompletionType::List)
39			.build()
40	};
41}
42
43pub mod print {
44	use prettyprint::{PagingMode, PrettyPrint, PrettyPrinter};
45
46	pub enum Mode {
47		REPL,
48		Debug,
49		Error,
50		MultiLine,
51		UseHeader,
52
53		#[allow(dead_code)]
54		Plain,
55	}
56
57	pub trait PrinterChange {
58		fn change(&self, mode: Mode) -> Self;
59		fn change_language(&self, lang: &str) -> Self;
60		fn change_theme(&self, lang: &str) -> Self;
61	}
62
63	impl PrinterChange for PrettyPrint {
64		fn change(&self, mode: Mode) -> Self {
65			(match mode {
66				Mode::Plain => self.configure().grid(false).header(false).line_numbers(false).build(),
67				Mode::UseHeader => self.configure().grid(true).header(true).build(),
68				Mode::MultiLine => self.configure().grid(true).build(),
69				Mode::REPL => self.configure().line_numbers(true).grid(true).build(),
70				Mode::Debug => self.configure().line_numbers(true).grid(true).header(true).build(),
71				Mode::Error => self
72					.configure()
73					.grid(true)
74					.theme("Sublime Snazzy")
75					.paging_mode(PagingMode::Error)
76					.build(),
77			})
78			.unwrap() // because it only throw error if field not been initialized
79		}
80		fn change_language(&self, lang: &str) -> Self {
81			self.configure().language(lang).build().unwrap()
82		}
83		fn change_theme(&self, theme: &str) -> Self {
84			self.configure().theme(theme).build().unwrap()
85		}
86	}
87
88	pub const PRINTER: fn(&str) -> PrettyPrint = |lang| {
89		let mut printer = PrettyPrinter::default();
90		printer // Default Mode::Plain ๐Ÿ‘‡
91			.header(false)
92			.grid(false)
93			.line_numbers(false)
94			.paging_mode(PagingMode::Never) // to support Alpine linux
95			.theme("TwoDark")
96			.language(match lang {
97				"smcat" => "perl",
98				"scxml" | "xmi" => "xml",
99				"ascii" | "boxart" => "txt",
100				_ => lang,
101			})
102			.build()
103			.unwrap() // because it only throw error if field not been initialized
104	};
105}
106
107pub mod iter {
108	use std::{hash::Hash, iter::FromIterator, ops::Deref};
109
110	impl Compare for &str {}
111	impl<T: PartialEq + Clone> Merge<T> for Vec<T> {
112		fn merge_value(&mut self, item: T) {
113			if !self.iter().any(|v| *v == item) {
114				self.push(item);
115			}
116		}
117	}
118
119	#[rustfmt::skip]
120	pub fn merge<T: Clone + Hash + Eq>(slices: &[&[T]]) -> Vec<T> {
121		use std::collections::HashSet;
122		slices.iter().cloned()
123			.flatten().cloned()
124			.collect::<HashSet<_>>() // https://www.rosettacode.org/wiki/Remove_duplicate_elements#Rust
125			.drain().collect()
126	}
127
128	pub trait Compare {
129		fn one_of(&self, /*TODO:replace*/ collection: &[Self] /*with impl IntoIterator*/) -> bool
130		where
131			Self: Sized + PartialEq,
132		{
133			collection.iter().any(|item| item == self)
134		}
135	}
136
137	pub trait Merge<T: PartialEq + Clone>: FromIterator<T> + Deref<Target = [T]> {
138		fn merge_value(&mut self, item: T);
139		fn merge_from_slice(&mut self, items: &[T]) {
140			for item in items {
141				self.merge_value(item.to_owned())
142			}
143		}
144		fn merge(&mut self, items: Self) {
145			self.merge_from_slice(&items)
146		}
147	}
148}
149
150// TODO: Hacktoberfest
151pub mod format {
152	pub const XSTATE: [&str; 1] = ["json" /*, typescript*/];
153	pub const SMCAT: &str = "json";
154	#[rustfmt::skip]
155	pub const BLOB: [&str; 13] = ["bmp", "gd", "gd2", "gif", "jpg", "jpeg", "jpe", "png", "svgz", "tif", "tiff", "vmlz", "webmp"];
156
157	#[rustfmt::skip]
158	pub mod ext {
159		pub const SMCAT: [&str; 7] = ["svg", "dot", "smcat", "json", "html", "scxml", "xmi"];
160		pub const DOT: [&str; 32] = ["bmp", "canon", "dot", "gv", "xdot", "eps", "fig", "gd", "gd2", "gif", "jpg", "jpeg", "jpe", "json", "json0", "dot_json", "xdot_json", "pic", "plain", "plain-ext", "png", "ps", "ps2", "svg", "svgz", "tif", "tiff", "tk", "vml", "vmlz", "vrml", "wbmp"];
161		pub const GRAPH_EASY: [&str; 13] = ["ascii", "boxart", "svg", "dot", "txt", "bmp", "gif", "jpg", "pdf", "png", "ps", "ps2", "tif"];
162	}
163
164	pub fn into_legacy_dot(input: &str) -> String {
165		use regex::Regex;
166		let re = Regex::new(re::OLD_TO_NEW_DOT).expect("valid regex");
167		re.replace_all(input, "").replace("note", "box")
168	}
169
170	mod re {
171		pub const OLD_TO_NEW_DOT: &str = r#"( style=["']\w+["'])|( penwidth=["']?\d+.\d["']?)"#;
172	}
173}
174
175// TODO: make a blog about "Categorizing process using trait system"
176pub mod spawn {
177	use std::{
178		io::{self, Read, Write},
179		process::*,
180	};
181
182	pub trait ShortProcess {
183		type Input;
184		type Output; //TODO:[String] wait associated type defaults to be stable
185		fn output_from(self, input: Self::Input) -> io::Result<Self::Output>;
186	}
187
188	pub fn smcat(fmt: &str) -> io::Result<impl ShortProcess<Input = String, Output = String>> {
189		let (input, output) = (None, None) as (Option<Stdio>, Option<Stdio>);
190		Process::new("smcat", format!("-I json -d left-right -T {}", fmt)).spawn(input, output)
191	}
192
193	pub fn graph_easy(fmt: &str) -> io::Result<impl ShortProcess<Input = String, Output = String>> {
194		let (input, output): (Option<Stdio>, Option<Stdio>) = (None, None);
195		Process::new("graph-easy", format!("--as {}", fmt)).spawn(input, output)
196	}
197
198	pub fn dot(fmt: &str) -> impl ShortProcess<Input = (String, Child), Output = Vec<u8>> {
199		Process::new("dot", format!("-T{}", fmt))
200	}
201
202	impl ShortProcess for Process<'_> {
203		type Input = (String, Child);
204		type Output = Vec<u8>;
205		fn output_from(self, input: Self::Input) -> io::Result<Self::Output> {
206			let mut output = Vec::new();
207			let (input, mut child) = input;
208
209			write!(child.stdin.as_mut().expect("process not exit"), "{}", input)?;
210			child = self.spawn(child.stdout.take(), None as Option<Stdio>)?;
211			child.wait()?;
212			child.stdout.as_mut().expect("process to exit").read_to_end(&mut output)?;
213
214			Ok(output)
215		}
216	}
217
218	impl ShortProcess for Child {
219		type Input = String;
220		type Output = String;
221		fn output_from(mut self, input: Self::Input) -> io::Result<Self::Output> {
222			let mut output = String::new();
223
224			write!(self.stdin.as_mut().expect("process not exit"), "{}", input)?;
225			self.wait()?;
226			self.stdout.as_mut().expect("process to exit").read_to_string(&mut output)?;
227
228			Ok(output)
229		}
230	}
231
232	pub trait ActiveProcess {
233		type Input;
234		fn output_from(&mut self, input: Self::Input) -> io::Result<String>;
235	}
236
237	// INSERT ActiveProcess cli that always keepalive here (if any)
238
239	impl ActiveProcess for Process<'_> {
240		type Input = (String, Child);
241		fn output_from(&mut self, input: Self::Input) -> io::Result<String> {
242			let mut output = String::new();
243			let (input, mut child) = input;
244
245			write!(child.stdin.as_mut().expect("process not exit"), "{}", input)?;
246			let mut child = self.spawn(child.stdout.take(), None as Option<Stdio>)?;
247			loop {
248				if let Some(stdout) = child.stdout.as_mut() {
249					stdout.read_to_string(&mut output)?;
250					break;
251				}
252			}
253
254			Ok(output)
255		}
256	}
257
258	impl ActiveProcess for Child {
259		type Input = String;
260		fn output_from(&mut self, input: Self::Input) -> io::Result<String> {
261			let mut output = String::new();
262
263			write!(self.stdin.as_mut().expect("process not exit"), "{}", input)?;
264			loop {
265				if let Some(stdout) = self.stdout.as_mut() {
266					stdout.read_to_string(&mut output)?;
267					break;
268				}
269			}
270			Ok(output)
271		}
272	}
273
274	pub struct Process<'p> {
275		cmd: &'p str,
276		args: String,
277	}
278
279	impl<'p> Process<'p> {
280		fn new(cmd: &'p str, args: String) -> Self {
281			Process { cmd, args }
282		}
283
284		fn spawn(&self, input: Option<impl Into<Stdio>>, output: Option<impl Into<Stdio>>) -> io::Result<Child> {
285			Command::new(self.cmd)
286				.args(self.args.split_whitespace())
287				.stdin(input.map_or(Stdio::piped(), Into::into))
288				.stdout(output.map_or(Stdio::piped(), Into::into))
289				.spawn()
290		}
291	}
292}