penne/
stdout.rs

1//
2// Part of penne
3// Copyright (c) 2020-2023 Sander in 't Veld
4// License: MIT
5//
6
7//! The command line interface outputs colored dumps of intermediate code
8//! when run with the _verbose_ flag.
9
10use crate::common;
11use crate::lexer;
12use crate::rebuilder;
13use crate::resolved;
14
15use serde::Deserialize;
16use std::io::Write;
17use termcolor::{Color, ColorSpec, StandardStream, WriteColor};
18
19#[derive(Debug, Default, Deserialize, clap::Args)]
20#[serde(default, deny_unknown_fields)]
21pub struct Options
22{
23	/// Show a lot of intermediate output
24	#[clap(short, long)]
25	verbose: bool,
26
27	/// When to use ANSI colors in error messages and intermediate output
28	#[clap(hide(true), long, value_name("WHEN"))]
29	#[clap(value_enum, default_value_t=ColorChoice::Auto)]
30	color: ColorChoice,
31
32	/// Which character set to use to draw the arrows in error messages
33	#[clap(long, value_name("CHARSET"))]
34	#[clap(value_enum, default_value_t=CharSet::Unicode)]
35	arrows: CharSet,
36}
37
38#[derive(Debug, Default, Clone, Copy, Deserialize, clap::ValueEnum)]
39pub enum ColorChoice
40{
41	#[default]
42	Auto,
43	Always,
44	Never,
45}
46
47impl From<ColorChoice> for termcolor::ColorChoice
48{
49	fn from(choice: ColorChoice) -> termcolor::ColorChoice
50	{
51		match choice
52		{
53			ColorChoice::Auto => termcolor::ColorChoice::Auto,
54			ColorChoice::Always => termcolor::ColorChoice::Always,
55			ColorChoice::Never => termcolor::ColorChoice::Never,
56		}
57	}
58}
59
60#[derive(Debug, Default, Clone, Copy, Deserialize, clap::ValueEnum)]
61pub enum CharSet
62{
63	#[default]
64	Unicode,
65	Ascii,
66}
67
68impl From<CharSet> for ariadne::CharSet
69{
70	fn from(choice: CharSet) -> ariadne::CharSet
71	{
72		match choice
73		{
74			CharSet::Unicode => ariadne::CharSet::Unicode,
75			CharSet::Ascii => ariadne::CharSet::Ascii,
76		}
77	}
78}
79
80pub struct StdOut
81{
82	stdout: StandardStream,
83	is_verbose: bool,
84	ariadne_config: ariadne::Config,
85}
86
87impl StdOut
88{
89	pub fn new(options: Options) -> StdOut
90	{
91		let stdout = StandardStream::stdout(options.color.into());
92		let is_verbose = options.verbose;
93		let with_color = match options.color
94		{
95			ColorChoice::Auto => stdout.supports_color(),
96			ColorChoice::Always => true,
97			ColorChoice::Never => false,
98		};
99		let ariadne_config = ariadne::Config::default()
100			.with_color(with_color)
101			.with_char_set(options.arrows.into());
102		StdOut {
103			stdout,
104			is_verbose,
105			ariadne_config,
106		}
107	}
108
109	pub fn newline(&mut self) -> Result<(), std::io::Error>
110	{
111		if self.is_verbose
112		{
113			writeln!(self.stdout)?;
114		}
115		Ok(())
116	}
117
118	pub fn io_header(
119		&mut self,
120		preamble: &str,
121		path: &std::path::Path,
122	) -> Result<(), std::io::Error>
123	{
124		let colorspec_header = ColorSpec::new();
125		self.stdout.set_color(&colorspec_header)?;
126		writeln!(self.stdout, "{} {}...", preamble, path.to_string_lossy())?;
127		Ok(())
128	}
129
130	pub fn cmd_header(
131		&mut self,
132		preamble: &str,
133		command: String,
134	) -> Result<(), std::io::Error>
135	{
136		let colorspec_header = ColorSpec::new();
137		self.stdout.set_color(&colorspec_header)?;
138		writeln!(self.stdout)?;
139		writeln!(self.stdout, "{} {}...", preamble, &command)?;
140		Ok(())
141	}
142
143	pub fn header(
144		&mut self,
145		preamble: &str,
146		filename: &str,
147	) -> Result<(), std::io::Error>
148	{
149		if self.is_verbose
150		{
151			let colorspec_header = ColorSpec::new();
152			self.stdout.set_color(&colorspec_header)?;
153			writeln!(self.stdout, "{} {}...", preamble, filename)?;
154		}
155		Ok(())
156	}
157
158	pub fn basic_header(
159		&mut self,
160		section_name: &str,
161	) -> Result<(), std::io::Error>
162	{
163		if self.is_verbose
164		{
165			let colorspec_header = ColorSpec::new();
166			self.stdout.set_color(&colorspec_header)?;
167			writeln!(self.stdout, "{}...", section_name)?;
168		}
169		Ok(())
170	}
171
172	pub fn dump_tokens(
173		&mut self,
174		tokens: &[lexer::LexedToken],
175	) -> Result<(), anyhow::Error>
176	{
177		if self.is_verbose
178		{
179			let colorspec_dump = ColorSpec::new().set_dimmed(true).to_owned();
180			self.stdout.set_color(&colorspec_dump)?;
181			writeln!(self.stdout, "{:?}", tokens)?;
182			writeln!(self.stdout)?;
183			for token in tokens
184			{
185				match &token.result
186				{
187					Result::Ok(token) => write!(self.stdout, "{:?}   ", token)?,
188					Result::Err(_) => write!(self.stdout, "ERROR   ")?,
189				}
190			}
191			writeln!(self.stdout)?;
192			writeln!(self.stdout)?;
193		}
194		Ok(())
195	}
196
197	pub fn dump_code(
198		&mut self,
199		filename: &str,
200		declarations: &[common::Declaration],
201	) -> Result<(), anyhow::Error>
202	{
203		if self.is_verbose
204		{
205			let colorspec_dump = ColorSpec::new().set_dimmed(true).to_owned();
206			self.stdout.set_color(&colorspec_dump)?;
207			writeln!(self.stdout, "{:?}", declarations)?;
208			writeln!(self.stdout)?;
209
210			self.header("Rebuilding", filename)?;
211
212			self.stdout.set_color(&colorspec_dump)?;
213			let indentation = rebuilder::Indentation {
214				value: "\u{00a6}   ",
215				amount: 1,
216			};
217			let code = rebuilder::rebuild(declarations, &indentation)?;
218			writeln!(self.stdout, "{}", code)?;
219		}
220		Ok(())
221	}
222
223	pub fn dump_resolved(
224		&mut self,
225		_filename: &str,
226		declarations: &[resolved::Declaration],
227	) -> Result<(), anyhow::Error>
228	{
229		if self.is_verbose
230		{
231			let colorspec_dump = ColorSpec::new().set_dimmed(true).to_owned();
232			self.stdout.set_color(&colorspec_dump)?;
233			writeln!(self.stdout, "{:?}", declarations)?;
234			writeln!(self.stdout)?;
235		}
236		Ok(())
237	}
238
239	pub fn dump_text(&mut self, text: &str) -> Result<(), anyhow::Error>
240	{
241		if self.is_verbose
242		{
243			let colorspec_dump = ColorSpec::new().set_dimmed(true).to_owned();
244			self.stdout.set_color(&colorspec_dump)?;
245			writeln!(self.stdout, "{}", text)?;
246			writeln!(self.stdout)?;
247		}
248		Ok(())
249	}
250
251	pub fn linting(
252		&mut self,
253		was_successful: bool,
254	) -> Result<(), std::io::Error>
255	{
256		if self.is_verbose
257		{
258			if was_successful
259			{
260				let colorspec_success =
261					ColorSpec::new().set_fg(Some(Color::Green)).to_owned();
262				self.stdout.set_color(&colorspec_success)?;
263				writeln!(self.stdout, "Linting complete.")?;
264			}
265			else
266			{
267				let colorspec_warning = ColorSpec::new()
268					.set_fg(Some(Color::Yellow))
269					.set_bold(true)
270					.to_owned();
271				self.stdout.set_color(&colorspec_warning)?;
272				writeln!(self.stdout, "Linting raised some warnings.")?;
273			}
274		}
275		Ok(())
276	}
277
278	pub fn prepare_for_errors(&mut self) -> Result<(), std::io::Error>
279	{
280		let colorspec_error = ColorSpec::new()
281			.set_fg(Some(Color::Red))
282			.set_bold(true)
283			.to_owned();
284		self.stdout.set_color(&colorspec_error)?;
285		writeln!(self.stdout)?;
286		Ok(())
287	}
288
289	pub fn show_errors(
290		&mut self,
291		errors: impl IntoIterator<Item = crate::error::Error>,
292		mut source_cache: impl ariadne::Cache<String>,
293	) -> Result<(), std::io::Error>
294	{
295		for error in errors
296		{
297			writeln!(self.stdout)?;
298			let report = error.build_report(self.ariadne_config);
299			report.eprint(&mut source_cache)?;
300		}
301		writeln!(self.stdout)?;
302		Ok(())
303	}
304
305	pub fn output(&mut self, value: i32) -> Result<(), std::io::Error>
306	{
307		self.stdout.reset()?;
308		writeln!(self.stdout, "Output: {}", value)?;
309		Ok(())
310	}
311
312	pub fn done(&mut self) -> Result<(), std::io::Error>
313	{
314		self.stdout.reset()?;
315		writeln!(self.stdout, "Done.")?;
316		Ok(())
317	}
318}