Skip to content

Commit

Permalink
#2: add default_order annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
Mikhail Mikhailov committed Feb 28, 2023
1 parent 03acf43 commit 21bd557
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 105 deletions.
6 changes: 4 additions & 2 deletions config-manager-proc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pub fn generate_config(input: TokenStream0) -> TokenStream0 {
configs,
debug_cmd_input,
table_name,
default_order,
} = AppTopLevelInfo::extract(&input.attrs);

let class: DataStruct = match input.data {
Expand All @@ -86,7 +87,7 @@ pub fn generate_config(input: TokenStream0) -> TokenStream0 {
} else if field_is_subcommand(&field) {
process_subcommand_field(field, &debug_cmd_input)
} else {
process_field(field, &table_name)
process_field(field, &table_name, &default_order)
};
((name, initialization), clap_field)
})
Expand All @@ -111,6 +112,7 @@ pub fn generate_flatten(input: TokenStream0) -> TokenStream0 {
let input = parse_macro_input!(input as DeriveInput);

let table_name = extract_table_name(&input.attrs);
let default_order = extract_source_order(&input.attrs);

let class_ident = input.ident;
let class: DataStruct = match input.data {
Expand All @@ -135,7 +137,7 @@ pub fn generate_flatten(input: TokenStream0) -> TokenStream0 {
} else if field_is_subcommand(&field) {
panic!("subcommands are forbidden in the nested structures")
} else {
process_field(field, &table_name)
process_field(field, &table_name, &default_order)
};
((name, initialization), clap_field)
})
Expand Down
1 change: 1 addition & 0 deletions config-manager-proc/src/utils/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub(crate) const SOURCE_KEY: &str = "source";
pub(crate) const CONFIG_FILE_KEY: &str = "file";
pub(crate) const DEBUG_INPUT_KEY: &str = "__debug_cmd_input__";
pub(crate) const TABLE_NAME_KEY: &str = "table";
pub(crate) const SOURCE_ORDER_KEY: &str = "default_order";
pub(crate) const FLATTEN: &str = "flatten";
pub(crate) const SUBCOMMAND: &str = "subcommand";

Expand Down
19 changes: 16 additions & 3 deletions config-manager-proc/src/utils/field.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 JSRPC “Kryptonite”

mod utils;
pub(crate) mod utils;

use super::{attributes::*, format_to_tokens};
use crate::*;
Expand Down Expand Up @@ -29,7 +29,11 @@ pub(crate) struct ProcessFieldResult {
pub(crate) initialization: TokenStream,
}

pub(crate) fn process_field(field: Field, table_name: &Option<String>) -> ProcessFieldResult {
pub(crate) fn process_field(
field: Field,
table_name: &Option<String>,
default_order: &Option<ExtractedAttributes>,
) -> ProcessFieldResult {
let field_name = field.ident.clone().expect("Unnamed fields are forbidden");
if number_of_crate_attribute(&field) > 1 {
panic!(
Expand All @@ -39,7 +43,16 @@ pub(crate) fn process_field(field: Field, table_name: &Option<String>) -> Proces
);
}

let attributes_order = extract_attributes(field, table_name.clone());
let attributes_order = extract_attributes(field, table_name)
.or_else(|| default_order.clone())
.unwrap_or_else(|| ExtractedAttributes {
variables: vec![
FieldAttribute::Clap(std::default::Default::default()),
FieldAttribute::Env(std::default::Default::default()),
FieldAttribute::Config(std::default::Default::default()),
],
..std::default::Default::default()
});

ProcessFieldResult {
initialization: attributes_order.gen_init(&field_name.to_string()),
Expand Down
208 changes: 110 additions & 98 deletions config-manager-proc/src/utils/field/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ impl ToTokens for NormalClapFieldInfo {
}
}

#[derive(Default)]
pub(super) struct ExtractedAttributes {
variables: Vec<FieldAttribute>,
default: Option<Default>,
deserializer: Option<String>,
#[derive(Default, Clone)]
pub(crate) struct ExtractedAttributes {
pub(crate) variables: Vec<FieldAttribute>,
pub(crate) default: Option<Default>,
pub(crate) deserializer: Option<String>,
}

impl ExtractedAttributes {
Expand Down Expand Up @@ -144,7 +144,8 @@ impl ExtractedAttributes {
}
}

pub(super) enum FieldAttribute {
#[derive(Clone)]
pub(crate) enum FieldAttribute {
Clap(ClapFieldParseResult),
Env(Env),
Config(Config),
Expand Down Expand Up @@ -193,8 +194,8 @@ impl Display for FieldAttribute {
}
}

#[derive(Default)]
pub(super) struct Env {
#[derive(Default, Clone)]
pub(crate) struct Env {
pub(super) inner: Option<String>,
}

Expand Down Expand Up @@ -231,8 +232,8 @@ impl Env {
}
}

#[derive(Default)]
pub(super) struct Config {
#[derive(Default, Clone)]
pub(crate) struct Config {
pub(super) key: Option<String>,
pub(super) table: Option<String>,
}
Expand All @@ -251,107 +252,118 @@ impl Config {
}
}

pub(super) struct Default {
#[derive(Default, Clone)]
pub(crate) struct Default {
pub(super) inner: Option<String>,
}

pub(super) fn extract_attributes(field: Field, table_name: Option<String>) -> ExtractedAttributes {
pub(super) fn extract_attributes(
field: Field,
table_name: &Option<String>,
) -> Option<ExtractedAttributes> {
let field_name = field.ident.expect("Unnamed fields are forbidden");
let mut res = ExtractedAttributes::default();

if let Some(atr) = field
field
.attrs
.iter()
.find(|a| compare_attribute_name(a, SOURCE_KEY))
{
match atr.parse_meta() {
Err(err) => panic!("Can't parse attribute as meta: {err}"),
Ok(meta) => match meta {
Meta::List(MetaList { nested: args, .. }) => {
for arg in args {
match arg {
NestedMeta::Lit(lit) => {
panic!("source attribute ({:#?}) can't be a literal", lit)
}
NestedMeta::Meta(arg) => match path_to_string(arg.path()).as_str() {
CLAP_KEY => match arg {
Meta::Path(_) => res.variables.push(FieldAttribute::Clap(
ClapFieldParseResult::default(),
)),
Meta::List(clap_metalist) => {
res.variables.push(FieldAttribute::Clap(
parse_clap_field_attribute(&clap_metalist),
));
}
_ => {
panic!("clap attribute must match #[clap(...)] or #[clap]")
}
},
DEFAULT => {
if res.default.is_some() {
panic!("Default can be assigned only once per field")
}
res.default = Some(Default {
inner: match_literal_or_init_from(
&arg,
AcceptedLiterals::AnyLiteral,
)
.map(|init| match init {
InitFrom::Fn(func) => format!("{{{func}}}"),
InitFrom::Literal(lit) => match lit {
Lit::Str(str) => str.value(),
lit => lit.to_token_stream().to_string(),
},
}),
})
.map(|atr| {
match atr.parse_meta() {
Err(err) => panic!("Can't parse attribute as meta: {err}"),
Ok(meta) => match meta {
Meta::List(MetaList { nested: args, .. }) => {
for arg in args {
match arg {
NestedMeta::Lit(lit) => {
panic!("source attribute ({:#?}) can't be a literal", lit)
}
ENV_KEY => res.variables.push(FieldAttribute::Env(Env {
inner: match_literal_or_init_from(
&arg,
AcceptedLiterals::StringOnly,
)
.as_ref()
.map(InitFrom::as_string),
})),
CONFIG_KEY => res.variables.push(FieldAttribute::Config(Config {
key: match_literal_or_init_from(
&arg,
AcceptedLiterals::StringOnly,
)
.as_ref()
.map(InitFrom::as_string),
table: table_name.clone(),
})),
DESERIALIZER => {
if res.deserializer.is_some() {
panic!(
"Deserialize_with can be assigned only once per field"
)
NestedMeta::Meta(arg) => {
match path_to_string(arg.path()).as_str() {
CLAP_KEY => match arg {
Meta::Path(_) => {
res.variables.push(FieldAttribute::Clap(
ClapFieldParseResult::default(),
))
}
Meta::List(clap_metalist) => {
res.variables.push(FieldAttribute::Clap(
parse_clap_field_attribute(&clap_metalist),
));
}
_ => {
panic!(
"clap attribute must match #[clap(...)] or \
#[clap]"
)
}
},
DEFAULT => {
if res.default.is_some() {
panic!(
"Default can be assigned only once per field"
)
}
res.default = Some(Default {
inner: match_literal_or_init_from(
&arg,
AcceptedLiterals::AnyLiteral,
)
.map(|init| match init {
InitFrom::Fn(func) => format!("{{{func}}}"),
InitFrom::Literal(lit) => match lit {
Lit::Str(str) => str.value(),
lit => lit.to_token_stream().to_string(),
},
}),
})
}
ENV_KEY => res.variables.push(FieldAttribute::Env(Env {
inner: match_literal_or_init_from(
&arg,
AcceptedLiterals::StringOnly,
)
.as_ref()
.map(InitFrom::as_string),
})),
CONFIG_KEY => {
res.variables.push(FieldAttribute::Config(Config {
key: match_literal_or_init_from(
&arg,
AcceptedLiterals::StringOnly,
)
.as_ref()
.map(InitFrom::as_string),
table: table_name.clone(),
}))
}
DESERIALIZER => {
if res.deserializer.is_some() {
panic!(
"Deserialize_with can be assigned only once \
per field"
)
}
res.deserializer = match_literal_or_init_from(
&arg,
AcceptedLiterals::StringOnly,
)
.as_ref()
.map(InitFrom::as_string);
}
other => panic!(
"Unknown source attribute {other} of the field \
{field_name}"
),
}
res.deserializer = match_literal_or_init_from(
&arg,
AcceptedLiterals::StringOnly,
)
.as_ref()
.map(InitFrom::as_string);
}
other => panic!(
"Unknown source attribute {other} of the field {field_name}"
),
},
}
}
}
}
_ => panic!("source attribute must match #[source(...)]"),
},
}
} else {
res.variables = vec![
FieldAttribute::Clap(ClapFieldParseResult::default()),
FieldAttribute::Env(Env::default()),
FieldAttribute::Config(Config::default()),
];
}
_ => panic!("source attribute must match #[source(...)]"),
},
}

res
res
})
}
4 changes: 2 additions & 2 deletions config-manager-proc/src/utils/parser/clap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use super::{super::attributes::*, *};
use crate::*;

#[derive(Default)]
#[derive(Default, Clone)]
pub(crate) enum ClapOption<T> {
#[default]
None,
Expand All @@ -14,7 +14,7 @@ pub(crate) enum ClapOption<T> {

type MaybeString = ClapOption<String>;

#[derive(Default)]
#[derive(Default, Clone)]
pub(crate) struct ClapFieldParseResult {
pub(crate) long: MaybeString,
pub(crate) short: MaybeString,
Expand Down
Loading

0 comments on commit 21bd557

Please sign in to comment.