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 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 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() }
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 .header(false)
92 .grid(false)
93 .line_numbers(false)
94 .paging_mode(PagingMode::Never) .theme("TwoDark")
96 .language(match lang {
97 "smcat" => "perl",
98 "scxml" | "xmi" => "xml",
99 "ascii" | "boxart" => "txt",
100 _ => lang,
101 })
102 .build()
103 .unwrap() };
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<_>>() .drain().collect()
126 }
127
128 pub trait Compare {
129 fn one_of(&self, collection: &[Self] ) -> 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
150pub mod format {
152 pub const XSTATE: [&str; 1] = ["json" ];
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
175pub mod spawn {
177 use std::{
178 io::{self, Read, Write},
179 process::*,
180 };
181
182 pub trait ShortProcess {
183 type Input;
184 type Output; 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 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}