mirror of
https://github.com/wez/wezterm.git
synced 2025-01-05 20:43:57 +03:00
config: derive some metadata about the config
This isn't used by anything yet, but will enable some runtime config editing functions that I have planeed for the future.
This commit is contained in:
parent
694ffdbed2
commit
8508860136
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -723,6 +723,7 @@ dependencies = [
|
|||||||
"toml",
|
"toml",
|
||||||
"umask",
|
"umask",
|
||||||
"wezterm-bidi",
|
"wezterm-bidi",
|
||||||
|
"wezterm-config-derive",
|
||||||
"wezterm-dynamic",
|
"wezterm-dynamic",
|
||||||
"wezterm-input-types",
|
"wezterm-input-types",
|
||||||
"wezterm-term",
|
"wezterm-term",
|
||||||
@ -5444,6 +5445,15 @@ dependencies = [
|
|||||||
"wezterm-dynamic",
|
"wezterm-dynamic",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wezterm-config-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wezterm-dynamic"
|
name = "wezterm-dynamic"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -39,6 +39,7 @@ smol = "1.2"
|
|||||||
termwiz = { path = "../termwiz", features=["use_serde"] }
|
termwiz = { path = "../termwiz", features=["use_serde"] }
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
umask = { path = "../umask" }
|
umask = { path = "../umask" }
|
||||||
|
wezterm-config-derive = { version="0.1", path="derive" }
|
||||||
wezterm-dynamic = { path = "../wezterm-dynamic" }
|
wezterm-dynamic = { path = "../wezterm-dynamic" }
|
||||||
wezterm-bidi = { path = "../bidi" }
|
wezterm-bidi = { path = "../bidi" }
|
||||||
wezterm-input-types = { path = "../wezterm-input-types" }
|
wezterm-input-types = { path = "../wezterm-input-types" }
|
||||||
|
15
config/derive/Cargo.toml
Normal file
15
config/derive/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "wezterm-config-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
repository = "https://github.com/wez/wezterm"
|
||||||
|
description = "derive config metadata"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
quote = "1.0.2"
|
||||||
|
syn = "1.0"
|
21
config/derive/LICENSE.md
Normal file
21
config/derive/LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Wez Furlong
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
279
config/derive/src/attr.rs
Normal file
279
config/derive/src/attr.rs
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{
|
||||||
|
Attribute, Error, Field, GenericArgument, Ident, Lit, Meta, NestedMeta, Path, PathArguments,
|
||||||
|
Result, Type,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ContainerInfo {
|
||||||
|
pub into: Option<Path>,
|
||||||
|
pub try_from: Option<Path>,
|
||||||
|
pub debug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn container_info(attrs: &[Attribute]) -> Result<ContainerInfo> {
|
||||||
|
let mut into = None;
|
||||||
|
let mut try_from = None;
|
||||||
|
let mut debug = false;
|
||||||
|
|
||||||
|
for attr in attrs {
|
||||||
|
if !attr.path.is_ident("dynamic") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = match attr.parse_meta()? {
|
||||||
|
Meta::List(list) => list,
|
||||||
|
other => return Err(Error::new_spanned(other, "unsupported attribute")),
|
||||||
|
};
|
||||||
|
|
||||||
|
for meta in &list.nested {
|
||||||
|
match meta {
|
||||||
|
NestedMeta::Meta(Meta::Path(path)) => {
|
||||||
|
if path.is_ident("debug") {
|
||||||
|
debug = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NestedMeta::Meta(Meta::NameValue(value)) => {
|
||||||
|
if value.path.is_ident("into") {
|
||||||
|
if let Lit::Str(s) = &value.lit {
|
||||||
|
into = Some(s.parse()?);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value.path.is_ident("try_from") {
|
||||||
|
if let Lit::Str(s) = &value.lit {
|
||||||
|
try_from = Some(s.parse()?);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
return Err(Error::new_spanned(meta, "unsupported attribute"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ContainerInfo {
|
||||||
|
into,
|
||||||
|
try_from,
|
||||||
|
debug,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DefValue {
|
||||||
|
None,
|
||||||
|
Default,
|
||||||
|
Path(Path),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FieldInfo<'a> {
|
||||||
|
pub field: &'a Field,
|
||||||
|
pub type_name: String,
|
||||||
|
pub name: String,
|
||||||
|
pub skip: bool,
|
||||||
|
pub flatten: bool,
|
||||||
|
pub allow_default: DefValue,
|
||||||
|
pub into: Option<Path>,
|
||||||
|
pub try_from: Option<Path>,
|
||||||
|
pub deprecated: Option<String>,
|
||||||
|
pub validate: Option<Path>,
|
||||||
|
pub doc: String,
|
||||||
|
pub container_type: ContainerType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ContainerType {
|
||||||
|
None,
|
||||||
|
Option,
|
||||||
|
Vec,
|
||||||
|
Map,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FieldInfo<'a> {
|
||||||
|
pub fn to_option(&self) -> TokenStream {
|
||||||
|
let name = &self.name;
|
||||||
|
let doc = &self.doc;
|
||||||
|
let type_name = &self.type_name;
|
||||||
|
let container_type = Ident::new(&format!("{:?}", self.container_type), Span::call_site());
|
||||||
|
let get_default = match self.compute_default() {
|
||||||
|
Some(def) => quote!(Some(|| #def.to_dynamic())),
|
||||||
|
None => quote!(None),
|
||||||
|
};
|
||||||
|
quote!(
|
||||||
|
crate::meta::ConfigOption {
|
||||||
|
name: #name,
|
||||||
|
doc: #doc,
|
||||||
|
tags: &[],
|
||||||
|
container: crate::meta::ConfigContainer::#container_type,
|
||||||
|
type_name: #type_name,
|
||||||
|
default_value: #get_default,
|
||||||
|
possible_values: &[],
|
||||||
|
fields: &[],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_default(&self) -> Option<TokenStream> {
|
||||||
|
let ty = &self.field.ty;
|
||||||
|
match &self.allow_default {
|
||||||
|
DefValue::Default => Some(quote!(
|
||||||
|
<#ty>::default()
|
||||||
|
)),
|
||||||
|
DefValue::Path(default) => Some(quote!(
|
||||||
|
#default()
|
||||||
|
)),
|
||||||
|
DefValue::None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn field_info(field: &Field) -> Result<FieldInfo> {
|
||||||
|
let mut name = field.ident.as_ref().unwrap().to_string();
|
||||||
|
let mut skip = false;
|
||||||
|
let mut flatten = false;
|
||||||
|
let mut allow_default = DefValue::None;
|
||||||
|
let mut try_from = None;
|
||||||
|
let mut validate = None;
|
||||||
|
let mut into = None;
|
||||||
|
let mut deprecated = None;
|
||||||
|
let mut doc = String::new();
|
||||||
|
let mut container_type = ContainerType::None;
|
||||||
|
|
||||||
|
let type_name = match &field.ty {
|
||||||
|
Type::Path(p) => {
|
||||||
|
let last_seg = p.path.segments.last().unwrap();
|
||||||
|
match &last_seg.arguments {
|
||||||
|
PathArguments::None => last_seg.ident.to_string(),
|
||||||
|
PathArguments::AngleBracketed(args) if args.args.len() == 1 => {
|
||||||
|
let arg = args.args.first().unwrap();
|
||||||
|
match arg {
|
||||||
|
GenericArgument::Type(Type::Path(t)) => {
|
||||||
|
container_type = match last_seg.ident.to_string().as_str() {
|
||||||
|
"Option" => ContainerType::Option,
|
||||||
|
"Vec" => ContainerType::Vec,
|
||||||
|
_ => panic!("unhandled type for {name}: {:#?}", field.ty),
|
||||||
|
};
|
||||||
|
t.path.segments.last().unwrap().ident.to_string()
|
||||||
|
}
|
||||||
|
_ => panic!("unhandled type for {name}: {:#?}", field.ty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PathArguments::AngleBracketed(args) if args.args.len() == 2 => {
|
||||||
|
let arg = args.args.last().unwrap();
|
||||||
|
match arg {
|
||||||
|
GenericArgument::Type(Type::Path(t)) => {
|
||||||
|
container_type = match last_seg.ident.to_string().as_str() {
|
||||||
|
"HashMap" => ContainerType::Map,
|
||||||
|
_ => panic!("unhandled type for {name}: {:#?}", field.ty),
|
||||||
|
};
|
||||||
|
t.path.segments.last().unwrap().ident.to_string()
|
||||||
|
}
|
||||||
|
_ => panic!("unhandled type for {name}: {:#?}", field.ty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("unhandled type for {name}: {:#?}", field.ty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("unhandled type for {name}: {:#?}", field.ty),
|
||||||
|
};
|
||||||
|
|
||||||
|
for attr in &field.attrs {
|
||||||
|
if !attr.path.is_ident("dynamic") && !attr.path.is_ident("doc") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = match attr.parse_meta()? {
|
||||||
|
Meta::List(list) => list,
|
||||||
|
Meta::NameValue(value) if value.path.is_ident("doc") => {
|
||||||
|
if let Lit::Str(s) = &value.lit {
|
||||||
|
if !doc.is_empty() {
|
||||||
|
doc.push('\n');
|
||||||
|
}
|
||||||
|
doc.push_str(&s.value());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
other.clone(),
|
||||||
|
format!("unsupported attribute {other:?}"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for meta in &list.nested {
|
||||||
|
match meta {
|
||||||
|
NestedMeta::Meta(Meta::NameValue(value)) => {
|
||||||
|
if value.path.is_ident("rename") {
|
||||||
|
if let Lit::Str(s) = &value.lit {
|
||||||
|
name = s.value();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value.path.is_ident("default") {
|
||||||
|
if let Lit::Str(s) = &value.lit {
|
||||||
|
allow_default = DefValue::Path(s.parse()?);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value.path.is_ident("deprecated") {
|
||||||
|
if let Lit::Str(s) = &value.lit {
|
||||||
|
deprecated.replace(s.value());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value.path.is_ident("into") {
|
||||||
|
if let Lit::Str(s) = &value.lit {
|
||||||
|
into = Some(s.parse()?);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value.path.is_ident("try_from") {
|
||||||
|
if let Lit::Str(s) = &value.lit {
|
||||||
|
try_from = Some(s.parse()?);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value.path.is_ident("validate") {
|
||||||
|
if let Lit::Str(s) = &value.lit {
|
||||||
|
validate = Some(s.parse()?);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NestedMeta::Meta(Meta::Path(path)) => {
|
||||||
|
if path.is_ident("skip") {
|
||||||
|
skip = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if path.is_ident("flatten") {
|
||||||
|
flatten = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if path.is_ident("default") {
|
||||||
|
allow_default = DefValue::Default;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
return Err(Error::new_spanned(meta, "unsupported attribute"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(FieldInfo {
|
||||||
|
type_name,
|
||||||
|
field,
|
||||||
|
name,
|
||||||
|
skip,
|
||||||
|
flatten,
|
||||||
|
allow_default,
|
||||||
|
try_from,
|
||||||
|
into,
|
||||||
|
deprecated,
|
||||||
|
validate,
|
||||||
|
doc,
|
||||||
|
container_type,
|
||||||
|
})
|
||||||
|
}
|
16
config/derive/src/bound.rs
Normal file
16
config/derive/src/bound.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use syn::{parse_quote, Generics, WhereClause, WherePredicate};
|
||||||
|
|
||||||
|
pub fn where_clause_with_bound(generics: &Generics, bound: TokenStream) -> WhereClause {
|
||||||
|
let new_predicates = generics.type_params().map::<WherePredicate, _>(|param| {
|
||||||
|
let param = ¶m.ident;
|
||||||
|
parse_quote!(#param : #bound)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut generics = generics.clone();
|
||||||
|
generics
|
||||||
|
.make_where_clause()
|
||||||
|
.predicates
|
||||||
|
.extend(new_predicates);
|
||||||
|
generics.where_clause.unwrap()
|
||||||
|
}
|
64
config/derive/src/configmeta.rs
Normal file
64
config/derive/src/configmeta.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use crate::{attr, bound};
|
||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse_quote, Data, DataStruct, DeriveInput, Error, Fields, FieldsNamed, Ident, Result};
|
||||||
|
|
||||||
|
pub fn derive(input: DeriveInput) -> Result<TokenStream> {
|
||||||
|
match &input.data {
|
||||||
|
Data::Struct(DataStruct {
|
||||||
|
fields: Fields::Named(fields),
|
||||||
|
..
|
||||||
|
}) => derive_struct(&input, fields),
|
||||||
|
Data::Struct(_) => Err(Error::new(
|
||||||
|
Span::call_site(),
|
||||||
|
"currently only structs with named fields are supported",
|
||||||
|
)),
|
||||||
|
_ => Err(Error::new(
|
||||||
|
Span::call_site(),
|
||||||
|
"currently only structs and enums are supported by this derive",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_struct(input: &DeriveInput, fields: &FieldsNamed) -> Result<TokenStream> {
|
||||||
|
let info = attr::container_info(&input.attrs)?;
|
||||||
|
let ident = &input.ident;
|
||||||
|
let (impl_generics, ty_generics, _where_clause) = input.generics.split_for_impl();
|
||||||
|
let dummy = Ident::new(
|
||||||
|
&format!("_IMPL_CONFIGMETA_FOR_{}", ident),
|
||||||
|
Span::call_site(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let options = fields
|
||||||
|
.named
|
||||||
|
.iter()
|
||||||
|
.map(attr::field_info)
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
let options = options
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|f| if f.skip { None } else { Some(f.to_option()) })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let bound = parse_quote!(crate::ConfigMeta);
|
||||||
|
let bounded_where_clause = bound::where_clause_with_bound(&input.generics, bound);
|
||||||
|
|
||||||
|
let tokens = quote! {
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
const #dummy: () = {
|
||||||
|
impl #impl_generics crate::meta::ConfigMeta for #ident #ty_generics #bounded_where_clause {
|
||||||
|
fn get_config_options(&self) -> &'static [crate::meta::ConfigOption]
|
||||||
|
{
|
||||||
|
&[
|
||||||
|
#( #options, )*
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if info.debug {
|
||||||
|
eprintln!("{}", tokens);
|
||||||
|
}
|
||||||
|
Ok(tokens)
|
||||||
|
}
|
13
config/derive/src/lib.rs
Normal file
13
config/derive/src/lib.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
|
mod attr;
|
||||||
|
mod bound;
|
||||||
|
mod configmeta;
|
||||||
|
|
||||||
|
#[proc_macro_derive(ConfigMeta, attributes(config))]
|
||||||
|
pub fn derive_config(input: TokenStream) -> TokenStream {
|
||||||
|
configmeta::derive(parse_macro_input!(input as DeriveInput))
|
||||||
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
|
.into()
|
||||||
|
}
|
@ -38,11 +38,12 @@ use std::time::Duration;
|
|||||||
use termwiz::hyperlink;
|
use termwiz::hyperlink;
|
||||||
use termwiz::surface::CursorShape;
|
use termwiz::surface::CursorShape;
|
||||||
use wezterm_bidi::ParagraphDirectionHint;
|
use wezterm_bidi::ParagraphDirectionHint;
|
||||||
|
use wezterm_config_derive::ConfigMeta;
|
||||||
use wezterm_dynamic::{FromDynamic, ToDynamic};
|
use wezterm_dynamic::{FromDynamic, ToDynamic};
|
||||||
use wezterm_input_types::{Modifiers, WindowDecorations};
|
use wezterm_input_types::{Modifiers, WindowDecorations};
|
||||||
use wezterm_term::TerminalSize;
|
use wezterm_term::TerminalSize;
|
||||||
|
|
||||||
#[derive(Debug, Clone, FromDynamic, ToDynamic)]
|
#[derive(Debug, Clone, FromDynamic, ToDynamic, ConfigMeta)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// The font size, measured in points
|
/// The font size, measured in points
|
||||||
#[dynamic(default = "default_font_size")]
|
#[dynamic(default = "default_font_size")]
|
||||||
|
@ -30,6 +30,7 @@ mod frontend;
|
|||||||
pub mod keyassignment;
|
pub mod keyassignment;
|
||||||
mod keys;
|
mod keys;
|
||||||
pub mod lua;
|
pub mod lua;
|
||||||
|
pub mod meta;
|
||||||
mod scheme_data;
|
mod scheme_data;
|
||||||
mod ssh;
|
mod ssh;
|
||||||
mod terminal;
|
mod terminal;
|
||||||
|
33
config/src/meta.rs
Normal file
33
config/src/meta.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use wezterm_dynamic::Value;
|
||||||
|
|
||||||
|
/// Trait for returning metadata about config options
|
||||||
|
pub trait ConfigMeta {
|
||||||
|
fn get_config_options(&self) -> &'static [ConfigOption];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ConfigContainer {
|
||||||
|
None,
|
||||||
|
Option,
|
||||||
|
Vec,
|
||||||
|
Map,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Metadata about a config option
|
||||||
|
pub struct ConfigOption {
|
||||||
|
/// The field name
|
||||||
|
pub name: &'static str,
|
||||||
|
/// Brief documentation
|
||||||
|
pub doc: &'static str,
|
||||||
|
/// TODO: tags to categorize the option
|
||||||
|
pub tags: &'static [&'static str],
|
||||||
|
pub container: ConfigContainer,
|
||||||
|
/// The type of the field
|
||||||
|
pub type_name: &'static str,
|
||||||
|
/// call this to get the default value
|
||||||
|
pub default_value: Option<fn() -> Value>,
|
||||||
|
/// TODO: For enum types, the set of possible values
|
||||||
|
pub possible_values: &'static [&'static Value],
|
||||||
|
/// TODO: For struct types, the fields in the child struct
|
||||||
|
pub fields: &'static [ConfigOption],
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user