1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
21#![warn(clippy::print_stderr)]
22#![warn(clippy::print_stdout)]
23
24use std::io::Write;
25
26#[cfg(feature = "clap")]
27use clap::Args;
28
29#[cfg(feature = "clap")]
43#[derive(Debug, Args)]
44pub struct CodeGenArgs {
45 #[arg(short('o'), long)]
46 output: std::path::PathBuf,
47
48 #[arg(long)]
49 check: bool,
50}
51
52#[cfg(feature = "clap")]
53impl CodeGenArgs {
54 pub fn write_str(&self, content: &str) -> Result<(), Box<dyn std::error::Error>> {
56 write_str(content, &self.output, self.check)
57 }
58}
59
60pub fn write_str(
64 content: &str,
65 output: &std::path::Path,
66 check: bool,
67) -> Result<(), Box<dyn std::error::Error>> {
68 if check {
69 let content: String = normalize_line_endings::normalized(content.chars()).collect();
70
71 let actual = std::fs::read_to_string(output)?;
72 let actual: String = normalize_line_endings::normalized(actual.chars()).collect();
73
74 if content != actual {
75 let allocation = content.lines().count() * actual.lines().count();
77 if 1_000_000_000 < allocation {
78 return Err(Box::new(CodeGenError {
79 message: format!("{} out of sync (too big to diff)", output.display()),
80 }));
81 } else {
82 let changeset = difference::Changeset::new(&actual, &content, "\n");
83 assert_ne!(changeset.distance, 0);
84 return Err(Box::new(CodeGenError {
85 message: format!("{} out of sync:\n{changeset}", output.display()),
86 }));
87 }
88 }
89 } else {
90 let mut file = std::io::BufWriter::new(std::fs::File::create(output)?);
91 write!(file, "{content}")?;
92 }
93
94 Ok(())
95}
96
97#[cfg(feature = "clap")]
113#[derive(Debug, Args)]
114pub struct RustfmtArgs {
115 #[arg(long)]
116 rustfmt_config: Option<std::path::PathBuf>,
117}
118
119#[cfg(feature = "clap")]
120impl RustfmtArgs {
121 pub fn reformat(
123 &self,
124 text: impl std::fmt::Display,
125 ) -> Result<String, Box<dyn std::error::Error>> {
126 rustfmt(text, self.rustfmt_config.as_deref())
127 }
128}
129
130pub fn rustfmt(
132 text: impl std::fmt::Display,
133 config: Option<&std::path::Path>,
134) -> Result<String, Box<dyn std::error::Error>> {
135 let mut rustfmt = std::process::Command::new("rustfmt");
136 rustfmt
137 .stdin(std::process::Stdio::piped())
138 .stdout(std::process::Stdio::piped());
139 if let Some(config) = config {
140 rustfmt.arg("--config-path").arg(config);
141 }
142 let mut rustfmt = rustfmt
143 .spawn()
144 .map_err(|err| format!("could not run `rustfmt`: {err}"))?;
145 write!(
146 rustfmt
147 .stdin
148 .take()
149 .expect("rustfmt was configured with stdin"),
150 "{text}"
151 )?;
152 let output = rustfmt.wait_with_output()?;
153 let stdout = String::from_utf8(output.stdout)?;
154 Ok(stdout)
155}
156
157#[derive(Clone, Debug)]
158struct CodeGenError {
159 message: String,
160}
161
162impl std::error::Error for CodeGenError {}
163
164impl std::fmt::Display for CodeGenError {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 self.message.fmt(f)
167 }
168}