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//! - **`proc-macro2`**: `(T, TokenStream)` where `T: AsRef<str>`
17//!   formats the token stream with [prettyplease] and writes it
18//!   to the path given by `T`.
19//!
20//! [prettyplease]: https://docs.rs/prettyplease/latest/prettyplease/
21
22use std::path::Path;
23
24use miette::{Context, IntoDiagnostic};
25
26pub mod unique;
27
28pub use unique::{AsKebabCase, AsPascalCase, AsSnakeCase, NamePart, UniqueName, UniqueNames};
29
30/// A record of a file that [`write_to_disk`] wrote.
31#[derive(Clone, Debug, Eq, PartialEq)]
32pub struct WrittenFile {
33    /// The path to the file, relative to the output directory.
34    pub path: String,
35    /// The size of the written contents in bytes.
36    pub size: usize,
37}
38
39pub fn write_to_disk(output: &Path, code: impl IntoCode) -> miette::Result<WrittenFile> {
40    let code = code.into_code();
41    let relative = code.path().to_owned();
42    let absolute = output.join(&relative);
43    let string = code.into_string()?;
44    if let Some(parent) = absolute.parent() {
45        std::fs::create_dir_all(parent)
46            .into_diagnostic()
47            .with_context(|| format!("Failed to create directory `{}`", parent.display()))?;
48    }
49    let size = string.len();
50    std::fs::write(&absolute, string)
51        .into_diagnostic()
52        .with_context(|| format!("Failed to write `{}`", absolute.display()))?;
53    Ok(WrittenFile {
54        path: relative,
55        size,
56    })
57}
58
59pub trait Code {
60    fn path(&self) -> &str;
61    fn into_string(self) -> miette::Result<String>;
62}
63
64#[cfg(feature = "proc-macro2")]
65impl<T: AsRef<str>> Code for (T, proc_macro2::TokenStream) {
66    fn path(&self) -> &str {
67        self.0.as_ref()
68    }
69
70    fn into_string(self) -> miette::Result<String> {
71        use quote::ToTokens;
72        let file = syn::parse2(self.1.into_token_stream()).into_diagnostic();
73        match file {
74            Ok(file) => Ok(prettyplease::unparse(&file)),
75            Err(err) => Err(err.context(format!("Failed to format `{}`", self.0.as_ref()))),
76        }
77    }
78}
79
80pub trait IntoCode {
81    type Code: Code;
82
83    fn into_code(self) -> Self::Code;
84}
85
86impl<T: Code> IntoCode for T {
87    type Code = T;
88
89    fn into_code(self) -> Self::Code {
90        self
91    }
92}