Skip to main content

ploidy_core/codegen/
mod.rs

1//! Code generation output and file writing.
2//!
3//! This module defines the [`Code`] trait, which represents a
4//! single generated output file with a relative path and a
5//! content string.
6//!
7//! [`IntoCode`] converts codegen types into [`Code`]. Any type
8//! that implements [`Code`] automatically implements
9//! [`IntoCode`], so codegen types can implement either trait.
10//!
11//! [`write_to_disk`] takes an output directory and any [`IntoCode`]
12//! value, creates intermediate directories as needed, and writes the file.
13//!
14//! # Feature-gated blanket implementations
15//!
16//! Two blanket implementations of [`Code`] are available behind
17//! feature flags:
18//!
19//! - **`proc-macro2`**: `(T, TokenStream)` where `T: AsRef<str>`
20//!   formats the token stream with [prettyplease] and writes it
21//!   to the path given by `T`.
22//! - **`cargo_toml`**: `(&str, Manifest<T>)` serializes a Cargo
23//!   manifest to TOML and writes it to the given path.
24//!
25//! [prettyplease]: https://docs.rs/prettyplease/latest/prettyplease/
26
27use std::path::Path;
28
29use miette::{Context, IntoDiagnostic};
30
31pub mod unique;
32
33pub use unique::{UniqueNames, WordSegments};
34
35pub fn write_to_disk(output: &Path, code: impl IntoCode) -> miette::Result<()> {
36    let code = code.into_code();
37    let path = output.join(code.path());
38    let string = code.into_string()?;
39    if let Some(parent) = path.parent() {
40        std::fs::create_dir_all(parent)
41            .into_diagnostic()
42            .with_context(|| format!("Failed to create directory `{}`", parent.display()))?;
43    }
44    std::fs::write(&path, string)
45        .into_diagnostic()
46        .with_context(|| format!("Failed to write `{}`", path.display()))?;
47    Ok(())
48}
49
50pub trait Code {
51    fn path(&self) -> &str;
52    fn into_string(self) -> miette::Result<String>;
53}
54
55#[cfg(feature = "proc-macro2")]
56impl<T: AsRef<str>> Code for (T, proc_macro2::TokenStream) {
57    fn path(&self) -> &str {
58        self.0.as_ref()
59    }
60
61    fn into_string(self) -> miette::Result<String> {
62        use quote::ToTokens;
63        let file = syn::parse2(self.1.into_token_stream()).into_diagnostic();
64        match file {
65            Ok(file) => Ok(prettyplease::unparse(&file)),
66            Err(err) => Err(err.context(format!("Failed to format `{}`", self.0.as_ref()))),
67        }
68    }
69}
70
71#[cfg(feature = "cargo_toml")]
72impl<T: serde::Serialize> Code for (&'static str, cargo_toml::Manifest<T>) {
73    fn path(&self) -> &str {
74        self.0
75    }
76
77    fn into_string(self) -> miette::Result<String> {
78        toml::to_string_pretty(&self.1)
79            .into_diagnostic()
80            .with_context(|| format!("Failed to serialize `{}`", self.0))
81    }
82}
83
84pub trait IntoCode {
85    type Code: Code;
86
87    fn into_code(self) -> Self::Code;
88}
89
90impl<T: Code> IntoCode for T {
91    type Code = T;
92
93    fn into_code(self) -> Self::Code {
94        self
95    }
96}