use std::fmt::{Debug, Display, Formatter};
pub trait Visit {
fn record_field(&mut self, name: &str, field: OptionField);
fn record_set(&mut self, name: &str, group: OptionSet);
}
pub trait OptionsMetadata {
fn record(visit: &mut dyn Visit);
fn documentation() -> Option<&'static str> {
None
}
fn metadata() -> OptionSet
where
Self: Sized + 'static,
{
OptionSet::of::<Self>()
}
}
impl<T> OptionsMetadata for Option<T>
where
T: OptionsMetadata,
{
fn record(visit: &mut dyn Visit) {
T::record(visit);
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize), serde(untagged))]
pub enum OptionEntry {
Field(OptionField),
Set(OptionSet),
}
impl OptionEntry {
pub fn into_field(self) -> Option<OptionField> {
match self {
OptionEntry::Field(field) => Some(field),
OptionEntry::Set(_) => None,
}
}
}
impl Display for OptionEntry {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
OptionEntry::Set(set) => std::fmt::Display::fmt(set, f),
OptionEntry::Field(field) => std::fmt::Display::fmt(&field, f),
}
}
}
#[derive(Copy, Clone)]
pub struct OptionSet {
record: fn(&mut dyn Visit),
doc: fn() -> Option<&'static str>,
}
impl OptionSet {
pub fn of<T>() -> Self
where
T: OptionsMetadata + 'static,
{
Self {
record: T::record,
doc: T::documentation,
}
}
pub fn record(&self, visit: &mut dyn Visit) {
let record = self.record;
record(visit);
}
pub fn documentation(&self) -> Option<&'static str> {
let documentation = self.doc;
documentation()
}
pub fn has(&self, name: &str) -> bool {
self.find(name).is_some()
}
pub fn find(&self, name: &str) -> Option<OptionEntry> {
struct FindOptionVisitor<'a> {
option: Option<OptionEntry>,
parts: std::str::Split<'a, char>,
needle: &'a str,
}
impl Visit for FindOptionVisitor<'_> {
fn record_set(&mut self, name: &str, set: OptionSet) {
if self.option.is_none() && name == self.needle {
if let Some(next) = self.parts.next() {
self.needle = next;
set.record(self);
} else {
self.option = Some(OptionEntry::Set(set));
}
}
}
fn record_field(&mut self, name: &str, field: OptionField) {
if self.option.is_none() && name == self.needle {
if self.parts.next().is_none() {
self.option = Some(OptionEntry::Field(field));
}
}
}
}
let mut parts = name.split('.');
if let Some(first) = parts.next() {
let mut visitor = FindOptionVisitor {
parts,
needle: first,
option: None,
};
self.record(&mut visitor);
visitor.option
} else {
None
}
}
pub fn collect_fields(&self) -> Vec<(String, OptionField)> {
struct FieldsCollector(Vec<(String, OptionField)>);
impl Visit for FieldsCollector {
fn record_field(&mut self, name: &str, field: OptionField) {
self.0.push((name.to_string(), field));
}
fn record_set(&mut self, _name: &str, _group: OptionSet) {}
}
let mut visitor = FieldsCollector(vec![]);
self.record(&mut visitor);
visitor.0
}
}
struct DisplayVisitor<'fmt, 'buf> {
f: &'fmt mut Formatter<'buf>,
result: std::fmt::Result,
}
impl<'fmt, 'buf> DisplayVisitor<'fmt, 'buf> {
fn new(f: &'fmt mut Formatter<'buf>) -> Self {
Self { f, result: Ok(()) }
}
fn finish(self) -> std::fmt::Result {
self.result
}
}
impl Visit for DisplayVisitor<'_, '_> {
fn record_set(&mut self, name: &str, _: OptionSet) {
self.result = self.result.and_then(|()| writeln!(self.f, "{name}"));
}
fn record_field(&mut self, name: &str, field: OptionField) {
self.result = self.result.and_then(|()| {
write!(self.f, "{name}")?;
if field.deprecated.is_some() {
write!(self.f, " (deprecated)")?;
}
writeln!(self.f)
});
}
}
impl Display for OptionSet {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut visitor = DisplayVisitor::new(f);
self.record(&mut visitor);
visitor.finish()
}
}
impl Debug for OptionSet {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize))]
pub struct OptionField {
pub doc: &'static str,
pub default: &'static str,
pub value_type: &'static str,
pub scope: Option<&'static str>,
pub example: &'static str,
pub deprecated: Option<Deprecated>,
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize))]
pub struct Deprecated {
pub since: Option<&'static str>,
pub message: Option<&'static str>,
}
impl Display for OptionField {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{}", self.doc)?;
writeln!(f)?;
writeln!(f, "Default value: {}", self.default)?;
writeln!(f, "Type: {}", self.value_type)?;
if let Some(deprecated) = &self.deprecated {
write!(f, "Deprecated")?;
if let Some(since) = deprecated.since {
write!(f, " (since {since})")?;
}
if let Some(message) = deprecated.message {
write!(f, ": {message}")?;
}
writeln!(f)?;
}
writeln!(f, "Example usage:\n```toml\n{}\n```", self.example)
}
}
#[cfg(feature = "serde")]
mod serde {
use super::{OptionField, OptionSet, Visit};
use serde::{Serialize, Serializer};
use std::collections::BTreeMap;
impl Serialize for OptionSet {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut entries = BTreeMap::new();
let mut visitor = SerializeVisitor {
entries: &mut entries,
};
self.record(&mut visitor);
entries.serialize(serializer)
}
}
struct SerializeVisitor<'a> {
entries: &'a mut BTreeMap<String, OptionField>,
}
impl Visit for SerializeVisitor<'_> {
fn record_set(&mut self, name: &str, set: OptionSet) {
let mut entries = BTreeMap::new();
let mut visitor = SerializeVisitor {
entries: &mut entries,
};
set.record(&mut visitor);
for (key, value) in entries {
self.entries.insert(format!("{name}.{key}"), value);
}
}
fn record_field(&mut self, name: &str, field: OptionField) {
self.entries.insert(name.to_string(), field);
}
}
}