1use std::collections::HashSet;
16use std::ffi::{CStr, CString, OsStr, OsString};
17use std::num::NonZero;
18use std::path::{Path, PathBuf};
19
20pub use serde_aco_derive::Help;
21
22#[derive(Debug)]
23pub struct FieldHelp {
24 pub ident: &'static str,
25 pub doc: &'static str,
26 pub ty: TypedHelp,
27}
28
29#[derive(Debug)]
30pub enum TypedHelp {
31 Struct {
32 name: &'static str,
33 fields: &'static [FieldHelp],
34 },
35 Enum {
36 name: &'static str,
37 variants: &'static [FieldHelp],
38 },
39 String,
40 Int,
41 Float,
42 Bool,
43 Unit,
44 Custom {
45 desc: &'static str,
46 },
47 Array(&'static TypedHelp),
48 Option(&'static TypedHelp),
49}
50
51pub trait Help {
52 const HELP: TypedHelp;
53}
54
55macro_rules! impl_help_for_num_types {
56 ($help_type:ident, $($ty:ty),+) => {
57 $(impl Help for $ty {
58 const HELP: TypedHelp = TypedHelp::$help_type;
59 })+
60 $(impl Help for NonZero<$ty> {
61 const HELP: TypedHelp = TypedHelp::$help_type;
62 })+
63 };
64}
65
66macro_rules! impl_help_for_types {
67 ($help_type:ident, $($ty:ty),+) => {
68 $(impl Help for $ty {
69 const HELP: TypedHelp = TypedHelp::$help_type;
70 })+
71 };
72}
73
74macro_rules! impl_help_for_array_types {
75 ($($ty:ty),+) => {
76 $(impl<T> Help for $ty where T: Help {
77 const HELP: TypedHelp = TypedHelp::Array(&T::HELP);
78 })+
79 };
80}
81
82impl_help_for_num_types!(
83 Int, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
84);
85impl_help_for_types!(Float, f32, f64);
86impl_help_for_types!(Bool, bool);
87impl_help_for_types!(
88 String,
89 &str,
90 Box<str>,
91 String,
92 CStr,
93 CString,
94 &OsStr,
95 OsString,
96 &Path,
97 Box<Path>,
98 PathBuf
99);
100impl_help_for_array_types!(&[T], Box<[T]>, Vec<T>);
101
102impl<T> Help for Option<T>
103where
104 T: Help,
105{
106 const HELP: TypedHelp = TypedHelp::Option(&T::HELP);
107}
108
109#[derive(Debug, Default)]
110struct ExtraHelp<'a> {
111 types: HashSet<&'static str>,
112 helps: Vec<&'a TypedHelp>,
113}
114
115fn add_value_type(s: &mut String, v: &TypedHelp) {
116 let type_s = match v {
117 TypedHelp::Bool => "bool",
118 TypedHelp::Int => "integer",
119 TypedHelp::Float => "float",
120 TypedHelp::String => "string",
121 TypedHelp::Unit => todo!(),
122 TypedHelp::Custom { desc } => desc,
123 TypedHelp::Struct { name, .. } => name,
124 TypedHelp::Enum { name, .. } => name,
125 TypedHelp::Option(o) => return add_value_type(s, o),
126 TypedHelp::Array(t) => {
127 s.push_str("array<");
128 add_value_type(s, t);
129 s.push('>');
130 return;
131 }
132 };
133 s.push_str(type_s);
134}
135
136fn add_extra_help<'a>(extra: &mut ExtraHelp<'a>, v: &'a TypedHelp) {
137 let (TypedHelp::Enum {
138 name,
139 variants: fields,
140 }
141 | TypedHelp::Struct { name, fields }) = v
142 else {
143 return;
144 };
145 if extra.types.insert(name) {
146 extra.helps.push(v);
147 for f in fields.iter() {
148 add_extra_help(extra, &f.ty);
149 }
150 }
151}
152
153fn extra_help(s: &mut String, v: &TypedHelp) {
154 s.push_str("# ");
155 match v {
156 TypedHelp::Struct { name, fields } => {
157 struct_help(s, &mut None, name, fields, 2);
158 }
159 TypedHelp::Enum { name, variants } => {
160 enum_help(s, &mut None, name, variants, 2);
161 }
162 _ => unreachable!(),
163 }
164}
165
166fn next_line(s: &mut String, indent: usize) {
167 s.push('\n');
168 for _ in 0..indent {
169 s.push(' ');
170 }
171}
172
173fn one_key_val<'a>(s: &mut String, extra: &mut Option<&mut ExtraHelp<'a>>, f: &'a FieldHelp) {
174 if f.ident.is_empty() {
175 let fields = match f.ty {
176 TypedHelp::Enum { variants, .. } => variants,
177 _ => unreachable!(),
178 };
179 s.push('(');
180 let mut need_separator = false;
181 for field in fields {
182 if need_separator {
183 s.push('|');
184 } else {
185 need_separator = true;
186 }
187 one_key_val(s, extra, field)
188 }
189 s.push(')');
190 } else {
191 s.push_str(f.ident);
192 s.push_str("=<");
193 add_value_type(s, &f.ty);
194 s.push('>');
195 if let Some(extra) = extra {
196 add_extra_help(extra, &f.ty)
197 }
198 }
199}
200
201fn key_val_pairs<'a>(
202 s: &mut String,
203 extra: &mut Option<&mut ExtraHelp<'a>>,
204 variant: &str,
205 fields: &'a [FieldHelp],
206) {
207 let mut add_comma = false;
208 if !variant.is_empty() {
209 s.push_str(variant);
210 add_comma = true;
211 }
212 for f in fields.iter() {
213 if add_comma {
214 s.push(',');
215 } else {
216 add_comma = true;
217 }
218 one_key_val(s, extra, f);
219 }
220}
221
222fn value_helps(s: &mut String, indent: usize, width: usize, fields: &[FieldHelp]) {
223 for f in fields.iter() {
224 if f.ident.is_empty() {
225 let fields = match f.ty {
226 TypedHelp::Enum { variants, .. } => variants,
227 _ => unreachable!(),
228 };
229 value_helps(s, indent, width, fields)
230 } else if f.doc.is_empty() {
231 continue;
232 } else {
233 next_line(s, indent);
234 let mut first_line = true;
235 for line in f.doc.lines() {
236 if first_line {
237 s.push_str(&format!("- {:width$}\t{}", f.ident, line, width = width));
238 first_line = false;
239 } else {
240 next_line(s, indent + width + 2);
241 s.push('\t');
242 s.push_str(line);
243 }
244 }
245 }
246 }
247}
248
249fn fields_ident_len_max(fields: &[FieldHelp]) -> Option<usize> {
250 let ident_len = |field: &FieldHelp| {
251 if !field.ident.is_empty() {
252 return Some(field.ident.len());
253 }
254 match field.ty {
255 TypedHelp::Enum { variants, .. } => fields_ident_len_max(variants),
256 TypedHelp::Struct { fields, .. } => fields_ident_len_max(fields),
257 _ => unreachable!(),
258 }
259 };
260
261 fields.iter().flat_map(ident_len).max()
262}
263
264fn field_helps(s: &mut String, indent: usize, fields: &[FieldHelp]) {
265 let Some(width) = fields_ident_len_max(fields) else {
266 return;
267 };
268 value_helps(s, indent, width, fields)
269}
270
271fn struct_help<'a>(
272 s: &mut String,
273 extra: &mut Option<&mut ExtraHelp<'a>>,
274 desc: &str,
275 fields: &'a [FieldHelp],
276 indent: usize,
277) {
278 s.push_str(desc);
279 next_line(s, indent);
280 s.push_str("* ");
281 key_val_pairs(s, extra, "", fields);
282 field_helps(s, indent + 2, fields);
283}
284
285fn enum_all_unit_help(s: &mut String, variants: &[FieldHelp], indent: usize) -> bool {
286 if variants.iter().any(|f| !matches!(f.ty, TypedHelp::Unit)) {
287 return false;
288 }
289 let Some(width) = variants.iter().map(|f| f.ident.len()).max() else {
290 return false;
291 };
292 for variant in variants.iter() {
293 next_line(s, indent);
294 s.push_str(&format!(
295 "* {:width$}\t{}",
296 variant.ident,
297 variant.doc,
298 width = width
299 ));
300 }
301 true
302}
303
304fn enum_help<'a>(
305 s: &mut String,
306 extra: &mut Option<&mut ExtraHelp<'a>>,
307 doc: &str,
308 variants: &'a [FieldHelp],
309 indent: usize,
310) {
311 s.push_str(doc);
312 if enum_all_unit_help(s, variants, indent) {
313 return;
314 }
315 if variants.is_empty() {
316 next_line(s, indent);
317 s.push_str("No options available");
318 }
319 for variant in variants.iter() {
320 next_line(s, indent);
321 s.push_str("* ");
322 match &variant.ty {
323 TypedHelp::Struct { fields, .. } => {
324 key_val_pairs(s, extra, variant.ident, fields);
325 next_line(s, indent + 2);
326 s.push_str(variant.doc);
327 field_helps(s, indent + 2, fields);
328 }
329 TypedHelp::Unit => {
330 s.push_str(variant.ident);
331 next_line(s, indent + 2);
332 s.push_str(variant.doc);
333 }
334 TypedHelp::String
335 | TypedHelp::Int
336 | TypedHelp::Float
337 | TypedHelp::Bool
338 | TypedHelp::Custom { .. } => {
339 s.push_str(variant.ident);
340 s.push_str(",<");
341 add_value_type(s, &variant.ty);
342 s.push('>');
343 next_line(s, indent + 2);
344 s.push_str(variant.doc);
345 }
346 _ => todo!("{:?}", variant.ty),
347 };
348 }
349}
350
351pub fn help_text<T: Help>(doc: &str) -> String {
352 let help = T::HELP;
353 let mut s = String::new();
354 let mut extra = ExtraHelp::default();
355 match &help {
356 TypedHelp::Struct { fields, .. } => {
357 struct_help(&mut s, &mut Some(&mut extra), doc, fields, 0);
358 }
359 TypedHelp::Enum { variants, .. } => {
360 enum_help(&mut s, &mut Some(&mut extra), doc, variants, 0)
361 }
362 _ => unreachable!("{:?}", help),
363 }
364 for h in extra.helps {
365 next_line(&mut s, 0);
366 extra_help(&mut s, h);
367 }
368 s
369}