s_crap/commands/code/
mod.rs1use 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 count_parse_err > 0 {
116 println!("Partial Result\n---\n{}\n---", result)
117 } else {
118 println!("{}", result)
119 }
120 } else {
121 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}