axconfig_gen/
output.rs

1use crate::{ConfigErr, ConfigItem, ConfigResult, ConfigType};
2
3/// The format of the generated file.
4#[derive(Debug, Clone)]
5pub enum OutputFormat {
6    /// Output is in TOML format.
7    Toml,
8    /// Output is Rust code.
9    Rust,
10}
11
12impl std::fmt::Display for OutputFormat {
13    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14        let s = match self {
15            Self::Toml => "toml",
16            Self::Rust => "rust",
17        };
18        s.fmt(f)
19    }
20}
21
22impl std::str::FromStr for OutputFormat {
23    type Err = String;
24
25    fn from_str(s: &str) -> Result<Self, Self::Err> {
26        match s {
27            "toml" => Ok(Self::Toml),
28            "rust" => Ok(Self::Rust),
29            _ => Err(s.into()),
30        }
31    }
32}
33
34/// The output writer.
35pub struct Output {
36    fmt: OutputFormat,
37    indent: usize,
38    result: String,
39}
40
41impl Output {
42    pub fn new(fmt: OutputFormat) -> Self {
43        Self {
44            fmt,
45            indent: 0,
46            result: String::new(),
47        }
48    }
49
50    pub fn result(&self) -> &str {
51        &self.result
52    }
53
54    pub fn println_fmt(&mut self, fmt: std::fmt::Arguments) {
55        self.result += &format!("{:indent$}{}\n", "", fmt, indent = self.indent);
56    }
57
58    pub fn println(&mut self, s: &str) {
59        self.println_fmt(format_args!("{}", s));
60    }
61
62    pub fn print_lines(&mut self, s: &str, line_op: impl Fn(&str) -> String) {
63        for line in s.lines() {
64            let line = line_op(line);
65            if !line.is_empty() {
66                self.println(&line);
67            }
68        }
69    }
70
71    pub fn table_begin(&mut self, name: &str, comments: &str) {
72        if !self.result.is_empty() {
73            self.println("");
74        }
75        match self.fmt {
76            OutputFormat::Toml => {
77                self.print_lines(comments, |l| l.trim().into());
78                self.println(&format!("[{}]", name));
79            }
80            OutputFormat::Rust => {
81                self.print_lines(comments, |l| l.trim().replacen("#", "///", 1));
82                self.println_fmt(format_args!("pub mod {} {{", mod_name(name)));
83                self.indent += 4;
84            }
85        }
86    }
87
88    pub fn table_end(&mut self) {
89        if let OutputFormat::Rust = self.fmt {
90            self.indent -= 4;
91            self.println("}");
92        }
93    }
94
95    pub fn write_item(&mut self, item: &ConfigItem) -> ConfigResult<()> {
96        match self.fmt {
97            OutputFormat::Toml => {
98                self.print_lines(item.comments(), |l| l.trim().into());
99                self.println_fmt(format_args!(
100                    "{} = {}{}",
101                    item.key(),
102                    item.value().to_toml_value(),
103                    if let Some(ty) = item.value().ty() {
104                        format!(" # {}", ty)
105                    } else {
106                        "".into()
107                    },
108                ));
109            }
110            OutputFormat::Rust => {
111                self.print_lines(item.comments(), |l| l.trim().replacen("#", "///", 1));
112                let key = const_name(item.key());
113                let val = item.value();
114                let ty = if let Some(ty) = val.ty() {
115                    ty.clone()
116                } else {
117                    val.inferred_type()?
118                };
119
120                if matches!(ty, ConfigType::Unknown) {
121                    return Err(ConfigErr::Other(format!(
122                        "Unknown type for key `{}`",
123                        item.key()
124                    )));
125                }
126                self.println_fmt(format_args!(
127                    "pub const {}: {} = {};",
128                    key,
129                    ty.to_rust_type(),
130                    val.to_rust_value(&ty, self.indent)?,
131                ));
132            }
133        }
134        Ok(())
135    }
136}
137
138fn mod_name(name: &str) -> String {
139    name.replace("-", "_")
140}
141
142fn const_name(name: &str) -> String {
143    name.to_uppercase().replace('-', "_")
144}