use syn::{Fields, Generics, ItemStruct, ItemUnion, Variant, Visibility};
use crate::directives::directive_options::{DirectiveOption, DirectiveVisibility, IndexEntryType};
use crate::directives::variable_directive::VariableDirective;
use crate::directives::{extract_doc_from_attrs, Directive, ImplDirective};
use crate::formats::{MdContent, MdDirective, RstContent, RstDirective};
use crate::nodes::{nodes_for_generics, nodes_for_type, nodes_for_where_clause, Node};
#[derive(Clone, Debug)]
pub struct StructDirective {
pub(crate) name: String,
pub(crate) ident: String,
pub(crate) options: Vec<DirectiveOption>,
pub(crate) content: Vec<String>,
pub(crate) fields: Vec<VariableDirective>,
pub(crate) self_impls: Vec<ImplDirective>,
pub(crate) trait_impls: Vec<ImplDirective>,
}
macro_rules! make_nodes {
($ident:expr, $fields:expr, $generics:expr, $item_keyword:expr) => {{
let mut nodes = if let Some(keyword) = $item_keyword {
vec![
Node::Keyword(keyword),
Node::Space,
Node::Name($ident.to_string()),
]
}
else {
vec![Node::Name($ident.to_string())]
};
if let Some(generics) = $generics {
nodes.extend(nodes_for_generics(generics));
}
if let Fields::Unnamed(fields) = &$fields {
nodes.push(Node::Punctuation("("));
for field in &fields.unnamed {
nodes.extend(nodes_for_type(&field.ty));
nodes.push(Node::Punctuation(", "));
}
nodes.pop();
nodes.push(Node::Punctuation(")"));
}
if let Some(generics) = $generics {
if let Some(wc) = &generics.where_clause {
nodes.extend(nodes_for_where_clause(wc));
}
}
nodes
}};
}
impl StructDirective {
const DIRECTIVE_NAME: &'static str = "struct";
pub(crate) fn from_variant(
parent_path: &str,
variant: &Variant,
inherited_visibility: &Option<&Visibility>,
) -> StructDirective {
let name = format!("{}::{}", parent_path, variant.ident);
let options = vec![
DirectiveOption::Index(IndexEntryType::SubEntry),
DirectiveOption::Vis(DirectiveVisibility::from(inherited_visibility.unwrap())),
DirectiveOption::Toc(variant.ident.to_string()),
DirectiveOption::Layout(make_nodes!(
variant.ident,
variant.fields,
None::<&Generics>,
None
)),
];
let fields = VariableDirective::from_fields(
&name,
&variant.fields,
inherited_visibility,
IndexEntryType::None,
);
StructDirective {
name,
ident: variant.ident.to_string(),
options,
content: extract_doc_from_attrs(&variant.attrs),
fields,
self_impls: vec![],
trait_impls: vec![],
}
}
pub(crate) fn from_item(parent_path: &str, item: &ItemStruct) -> Directive {
let name = format!("{}::{}", parent_path, item.ident);
let options = vec![
DirectiveOption::Index(IndexEntryType::WithSubEntries),
DirectiveOption::Vis(DirectiveVisibility::from(&item.vis)),
DirectiveOption::Toc(format!("struct {}", &item.ident)),
DirectiveOption::Layout(make_nodes!(
item.ident,
item.fields,
Some(&item.generics),
Some(Self::DIRECTIVE_NAME)
)),
];
let fields =
VariableDirective::from_fields(&name, &item.fields, &None, IndexEntryType::SubEntry);
Directive::Struct(StructDirective {
name,
ident: item.ident.to_string(),
options,
content: extract_doc_from_attrs(&item.attrs),
fields,
self_impls: vec![],
trait_impls: vec![],
})
}
pub(crate) fn from_union(parent_path: &str, item: &ItemUnion) -> Directive {
let name = format!("{parent_path}::{}", item.ident);
let fields = Fields::Named(item.fields.clone());
let options = vec![
DirectiveOption::Index(IndexEntryType::WithSubEntries),
DirectiveOption::Vis(DirectiveVisibility::from(&item.vis)),
DirectiveOption::Toc(format!("union {}", item.ident)),
DirectiveOption::Layout(make_nodes!(
item.ident,
fields,
Some(&item.generics),
Some("union")
)),
];
let fields =
VariableDirective::from_fields(&name, &fields, &None, IndexEntryType::SubEntry);
Directive::Struct(StructDirective {
name,
ident: item.ident.to_string(),
options,
content: extract_doc_from_attrs(&item.attrs),
fields,
self_impls: vec![],
trait_impls: vec![],
})
}
pub(crate) fn directive_visibility(&self) -> &DirectiveVisibility {
if let DirectiveOption::Vis(v) = &self.options[1] {
return v;
}
unreachable!("Struct: order of options changed")
}
pub(crate) fn change_parent(&mut self, new_parent: &str) {
self.name = format!("{new_parent}::{}", self.ident);
for field in &mut self.fields {
field.change_parent(&self.name);
}
for impl_ in &mut self.self_impls {
impl_.change_parent(new_parent);
}
for impl_ in &mut self.trait_impls {
impl_.change_parent(new_parent);
}
}
pub(crate) fn add_impl(&mut self, mut impl_: ImplDirective) {
impl_.change_parent(&self.name[0..self.name.rfind("::").unwrap()]);
impl_.set_directive_visibility(self.directive_visibility());
if impl_.trait_.is_some() {
self.trait_impls.push(impl_);
}
else {
self.self_impls.push(impl_);
}
}
}
impl RstDirective for StructDirective {
fn get_rst_text(self, level: usize, max_visibility: &DirectiveVisibility) -> Vec<String> {
if self.directive_visibility() > max_visibility {
return vec![];
}
let content_indent = Self::make_content_indent(level);
let mut text =
Self::make_rst_header(Self::DIRECTIVE_NAME, &self.name, &self.options, level);
text.extend(self.content.get_rst_text(&content_indent));
for field in self.fields {
text.extend(field.get_rst_text(level + 1, max_visibility));
}
text.extend(Self::make_rst_section(
"Implementations",
level,
self.self_impls.into_iter().map(Directive::Impl).collect(),
max_visibility,
));
text.extend(Self::make_rst_section(
"Traits implemented",
level,
self.trait_impls.into_iter().map(Directive::Impl).collect(),
max_visibility,
));
text
}
}
impl MdDirective for StructDirective {
fn get_md_text(self, fence_size: usize, max_visibility: &DirectiveVisibility) -> Vec<String> {
if self.directive_visibility() > max_visibility {
return vec![];
}
let fence = Self::make_fence(fence_size);
let mut text =
Self::make_md_header(Self::DIRECTIVE_NAME, &self.name, &self.options, &fence);
text.extend(self.content.get_md_text());
for field in self.fields {
text.extend(field.get_md_text(fence_size - 1, max_visibility));
}
text.extend(Self::make_md_section(
"Implementations",
fence_size,
self.self_impls.into_iter().map(Directive::Impl).collect(),
max_visibility,
));
text.extend(Self::make_md_section(
"Traits implemented",
fence_size,
self.trait_impls.into_iter().map(Directive::Impl).collect(),
max_visibility,
));
text.push(fence);
text
}
fn fence_size(&self) -> usize {
[
match self.fields.iter().map(VariableDirective::fence_size).max() {
Some(s) => s + 1,
None => 3,
},
match self.trait_impls.iter().map(ImplDirective::fence_size).max() {
Some(s) => s + 1,
None => 3,
},
match self.self_impls.iter().map(ImplDirective::fence_size).max() {
Some(s) => s + 1,
None => 3,
},
]
.into_iter()
.max()
.unwrap()
}
}