help.rs0.00%
1
// Copyright 2024 Google LLC2
//3
// Licensed under the Apache License, Version 2.0 (the "License");4
// you may not use this file except in compliance with the License.5
// You may obtain a copy of the License at6
//7
// https://www.apache.org/licenses/LICENSE-2.08
//9
// Unless required by applicable law or agreed to in writing, software10
// distributed under the License is distributed on an "AS IS" BASIS,11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12
// See the License for the specific language governing permissions and13
// limitations under the License.14
15
use std::collections::HashSet;16
use std::ffi::{CStr, CString, OsStr, OsString};17
use std::num::NonZero;18
use std::path::{Path, PathBuf};19
20
pub use serde_aco_derive::Help;21
22
#[derive(Debug)]23
pub struct FieldHelp {24
pub ident: &'static str,25
pub doc: &'static str,26
pub ty: TypedHelp,27
}28
29
#[derive(Debug)]30
pub 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
51
pub trait Help {52
const HELP: TypedHelp;53
}54
55
macro_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
66
macro_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
74
macro_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
82
impl_help_for_num_types!(83
Int, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize84
);85
impl_help_for_types!(Float, f32, f64);86
impl_help_for_types!(Bool, bool);87
impl_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
PathBuf99
);100
impl_help_for_array_types!(&[T], Box<[T]>, Vec<T>);101
102
impl<T> Help for Option<T>103
where104
T: Help,105
{106
const HELP: TypedHelp = TypedHelp::Option(&T::HELP);107
}108
109
#[derive(Debug, Default)]110
struct ExtraHelp<'a> {111
types: HashSet<&'static str>,112
helps: Vec<&'a TypedHelp>,113
}114
115
fn 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
136
fn add_extra_help<'a>(extra: &mut ExtraHelp<'a>, v: &'a TypedHelp) {137
let v = match v {138
TypedHelp::Array(t) => t,139
TypedHelp::Option(t) => t,140
_ => v,141
};142
let (TypedHelp::Enum {143
name,144
variants: fields,145
}146
| TypedHelp::Struct { name, fields }) = v147
else {148
return;149
};150
if extra.types.insert(name) {151
extra.helps.push(v);152
for f in fields.iter() {153
add_extra_help(extra, &f.ty);154
}155
}156
}157
158
fn extra_help(s: &mut String, v: &TypedHelp) {159
s.push_str("# ");160
match v {161
TypedHelp::Struct { name, fields } => {162
struct_help(s, &mut None, name, fields, 2);163
}164
TypedHelp::Enum { name, variants } => {165
enum_help(s, &mut None, name, variants, 2);166
}167
_ => unreachable!(),168
}169
}170
171
fn next_line(s: &mut String, indent: usize) {172
s.push('\n');173
for _ in 0..indent {174
s.push(' ');175
}176
}177
178
fn one_key_val<'a>(s: &mut String, extra: &mut Option<&mut ExtraHelp<'a>>, f: &'a FieldHelp) {179
if f.ident.is_empty() {180
let fields = match f.ty {181
TypedHelp::Enum { variants, .. } => variants,182
_ => unreachable!(),183
};184
s.push('(');185
let mut need_separator = false;186
for field in fields {187
if need_separator {188
s.push('|');189
} else {190
need_separator = true;191
}192
one_key_val(s, extra, field)193
}194
s.push(')');195
} else {196
s.push_str(f.ident);197
s.push_str("=<");198
add_value_type(s, &f.ty);199
s.push('>');200
if let Some(extra) = extra {201
add_extra_help(extra, &f.ty)202
}203
}204
}205
206
fn key_val_pairs<'a>(207
s: &mut String,208
extra: &mut Option<&mut ExtraHelp<'a>>,209
variant: &str,210
fields: &'a [FieldHelp],211
) {212
let mut add_comma = false;213
if !variant.is_empty() {214
s.push_str(variant);215
add_comma = true;216
}217
for f in fields.iter() {218
if add_comma {219
s.push(',');220
} else {221
add_comma = true;222
}223
one_key_val(s, extra, f);224
}225
}226
227
fn value_helps(s: &mut String, indent: usize, width: usize, fields: &[FieldHelp]) {228
for f in fields.iter() {229
if f.ident.is_empty() {230
let fields = match f.ty {231
TypedHelp::Enum { variants, .. } => variants,232
_ => unreachable!(),233
};234
value_helps(s, indent, width, fields)235
} else if f.doc.is_empty() {236
continue;237
} else {238
next_line(s, indent);239
let mut first_line = true;240
for line in f.doc.lines() {241
if first_line {242
s.push_str(&format!("- {:width$}\t{}", f.ident, line, width = width));243
first_line = false;244
} else {245
next_line(s, indent + width + 2);246
s.push('\t');247
s.push_str(line);248
}249
}250
}251
}252
}253
254
fn fields_ident_len_max(fields: &[FieldHelp]) -> Option<usize> {255
let ident_len = |field: &FieldHelp| {256
if !field.ident.is_empty() {257
return Some(field.ident.len());258
}259
match field.ty {260
TypedHelp::Enum { variants, .. } => fields_ident_len_max(variants),261
TypedHelp::Struct { fields, .. } => fields_ident_len_max(fields),262
_ => unreachable!(),263
}264
};265
266
fields.iter().flat_map(ident_len).max()267
}268
269
fn field_helps(s: &mut String, indent: usize, fields: &[FieldHelp]) {270
let Some(width) = fields_ident_len_max(fields) else {271
return;272
};273
value_helps(s, indent, width, fields)274
}275
276
fn struct_help<'a>(277
s: &mut String,278
extra: &mut Option<&mut ExtraHelp<'a>>,279
desc: &str,280
fields: &'a [FieldHelp],281
indent: usize,282
) {283
s.push_str(desc);284
next_line(s, indent);285
s.push_str("* ");286
key_val_pairs(s, extra, "", fields);287
field_helps(s, indent + 2, fields);288
}289
290
fn enum_all_unit_help(s: &mut String, variants: &[FieldHelp], indent: usize) -> bool {291
if variants.iter().any(|f| !matches!(f.ty, TypedHelp::Unit)) {292
return false;293
}294
let Some(width) = variants.iter().map(|f| f.ident.len()).max() else {295
return false;296
};297
for variant in variants.iter() {298
next_line(s, indent);299
s.push_str(&format!(300
"* {:width$}\t{}",301
variant.ident,302
variant.doc,303
width = width304
));305
}306
true307
}308
309
fn enum_help<'a>(310
s: &mut String,311
extra: &mut Option<&mut ExtraHelp<'a>>,312
doc: &str,313
variants: &'a [FieldHelp],314
indent: usize,315
) {316
s.push_str(doc);317
if enum_all_unit_help(s, variants, indent) {318
return;319
}320
if variants.is_empty() {321
next_line(s, indent);322
s.push_str("No options available");323
}324
for variant in variants.iter() {325
next_line(s, indent);326
s.push_str("* ");327
match &variant.ty {328
TypedHelp::Struct { fields, .. } => {329
key_val_pairs(s, extra, variant.ident, fields);330
next_line(s, indent + 2);331
s.push_str(variant.doc);332
field_helps(s, indent + 2, fields);333
}334
TypedHelp::Unit => {335
s.push_str(variant.ident);336
next_line(s, indent + 2);337
s.push_str(variant.doc);338
}339
TypedHelp::String340
| TypedHelp::Int341
| TypedHelp::Float342
| TypedHelp::Bool343
| TypedHelp::Custom { .. } => {344
s.push_str(variant.ident);345
s.push_str(",<");346
add_value_type(s, &variant.ty);347
s.push('>');348
next_line(s, indent + 2);349
s.push_str(variant.doc);350
}351
_ => todo!("{:?}", variant.ty),352
};353
}354
}355
356
pub fn help_text<T: Help>(doc: &str) -> String {357
let help = T::HELP;358
let mut s = String::new();359
let mut extra = ExtraHelp::default();360
match &help {361
TypedHelp::Struct { fields, .. } => {362
struct_help(&mut s, &mut Some(&mut extra), doc, fields, 0);363
}364
TypedHelp::Enum { variants, .. } => {365
enum_help(&mut s, &mut Some(&mut extra), doc, variants, 0)366
}367
_ => unreachable!("{:?}", help),368
}369
for h in extra.helps {370
next_line(&mut s, 0);371
extra_help(&mut s, h);372
}373
s374
}375