1use crate::{ConfigErr, ConfigItem, ConfigResult, ConfigType};
2
3#[derive(Debug, Clone)]
5pub enum OutputFormat {
6 Toml,
8 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
34pub 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}