s_crap/commands/code/
mod.rs

1use crate::{
2	arg::output,
3	cli::*,
4	error::*,
5	format,
6	iter::*,
7	print::*,
8	spawn::{self, *},
9	Downcast,
10};
11use atty::Stream;
12use clap::{App, ArgMatches};
13use colored::*;
14use scdlang::Transpiler;
15use scdlang_smcat as smcat;
16use scdlang_xstate as xstate;
17use std::{
18	fs::{self, File},
19	io::{BufRead, BufReader},
20	str,
21};
22use which::which;
23
24pub struct Code;
25impl<'c> CLI<'c> for Code {
26	const NAME: &'c str = "code";
27	const USAGE: &'c str = "
28	<FILE> 'File to print / concatenate'
29	--stream 'Parse the file line by line'
30	";
31
32	fn additional_usage<'s>(cmd: App<'s, 'c>) -> App<'s, 'c> {
33		cmd.visible_aliases(&["generate", "gen", "declaration", "declr"])
34			.about("Generate from scdlang file declaration to another format")
35			.args(&[output::dist(), output::target(), output::format()])
36	}
37
38	fn invoke(args: &ArgMatches) -> Result<()> {
39		let filepath = args.value_of("FILE").unwrap_or_default();
40		let target = args.value_of(output::TARGET).unwrap_or_default();
41		let output_format = args.value_of(output::FORMAT).unwrap_or_default();
42		let mut print = PRINTER(output_format);
43
44		let mut machine: Box<dyn Transpiler> = match target {
45			"xstate" => Box::new(match output_format {
46				"json" => xstate::Machine::new(),
47				"typescript" => unreachable!("TODO: on the next update"),
48				_ => unreachable!("{} --as {:?}", Self::NAME, args.value_of(output::FORMAT)),
49			}),
50			"smcat" | "graph" => {
51				let mut machine = Box::new(smcat::Machine::new());
52				let config = machine.configure();
53				match output_format {
54					"ascii" | "boxart" => config.with_err_semantic(true),
55					_ => config.with_err_semantic(false),
56				};
57				machine
58			}
59			_ => unreachable!("{} --format {:?}", Self::NAME, args.value_of(output::TARGET)),
60		};
61
62		let mut count_parse_err = 0;
63		machine.configure().with_err_path(filepath);
64
65		if args.is_present("stream") {
66			let file = File::open(filepath)?;
67			let mut errors = String::new();
68
69			for (i, line) in BufReader::new(file).lines().enumerate() {
70				machine.configure().with_err_line(i);
71				let expression: String = line?;
72
73				if let Err(err) = machine.insert_parse(&expression) {
74					errors.push_str(&format!("{}\n\n", err));
75					count_parse_err += 1;
76				}
77			}
78
79			if !errors.is_empty() {
80				Error::StreamParse(errors.trim_matches('\n')).report();
81			}
82
83			print = print.change(Mode::UseHeader);
84		} else {
85			let file = fs::read_to_string(filepath)?;
86			machine.parse(&file)?;
87		}
88
89		let machine = machine.to_string();
90		let result = if which("smcat").is_ok() && target.one_of(&["smcat", "graph"]) {
91			use format::ext;
92			let smcat = spawn::smcat(if target == "graph" { "dot" } else { output_format })?;
93			match target {
94				"smcat" => smcat.output_from(machine)?.into(),
95				"graph" if which("dot").is_ok() && output_format.one_of(&ext::DOT) => {
96					let input = (machine, smcat.downcast()?);
97					spawn::dot(output_format).output_from(input)?
98				}
99				"graph" if which("graph-easy").is_ok() && output_format.one_of(&ext::GRAPH_EASY) => {
100					let input = format::into_legacy_dot(&smcat.output_from(machine)?);
101					spawn::graph_easy(output_format)?.output_from(input)?.into()
102				}
103				_ => unreachable!("--format {}", target),
104			}
105		} else {
106			machine.into_bytes()
107		};
108
109		match args.value_of(output::DIST) {
110			Some(dist) => fs::write(dist, result)?,
111			None => {
112				let result = String::from_utf8(result)?;
113				if atty::isnt(Stream::Stdout) {
114					//👇if run on non-interactive shell
115					if count_parse_err > 0 {
116						println!("Partial Result\n---\n{}\n---", result)
117					} else {
118						println!("{}", result)
119					}
120				} else {
121					//👇if run on interactive shell
122					if args.is_present("stream") {
123						print.string_with_header(
124							result,
125							format!(
126								"({fmt}.{ext}) {title}",
127								fmt = target,
128								ext = args.value_of(output::FORMAT).unwrap_or_default(),
129								title = (if count_parse_err > 0 { "Partial Result" } else { filepath }).magenta()
130							),
131						)?
132					} else {
133						print.string(result)?
134					}
135				}
136			}
137		}
138
139		if count_parse_err > 0 {
140			Err(Error::Count {
141				topic: "parsing",
142				count: count_parse_err,
143			}
144			.into())
145		} else {
146			Ok(())
147		}
148	}
149}