use crate::{models::{Attributes, ExternalModule, Tusk, TusksModule, TusksParameters}, parsing::util::get_attribute_value::AttributeValue};
use syn::spanned::Spanned;
use crate::parsing::util::attr::AttributeCheck;
use syn::{ItemMod, ItemStruct};
impl TusksModule {
pub fn from_module(module: ItemMod, is_tusks_root: bool, is_root: bool) -> syn::Result<Option<Self>> {
let allow_external_subcommands = module.get_attribute_bool(
"command",
"allow_external_subcommands"
);
let name = module.ident.clone();
let span = module.span();
if !matches!(module.vis, syn::Visibility::Public(_)) {
if module.has_attr("tusks") {
return Err(syn::Error::new_spanned(&name, "tusks module must be public"));
}
return Ok(None);
}
if module.has_attr("skip") {
return Ok(None);
}
let items = match module.content {
Some(content) => content.1, None => {
return Err(syn::Error::new(
span,
"tusks module must be inline (not a file reference)"
));
}
};
let mut tusks_module = TusksModule {
name,
attrs: Attributes(module.attrs),
external_parent: None,
parameters: None,
tusks: Vec::new(),
submodules: Vec::new(),
external_modules: Vec::new(),
allow_external_subcommands,
value_enums: Vec::new(),
};
tusks_module.extract_module_items(items, is_root)?;
tusks_module.validate_is_root_or_has_parent(is_tusks_root, is_root)?;
Ok(Some(tusks_module))
}
fn validate_is_root_or_has_parent(&self, is_tusks_root: bool, is_root: bool) -> syn::Result<()> {
if !is_root {
return Ok(());
}
if !is_tusks_root {
if self.external_parent.is_none() {
return Err(syn::Error::new_spanned(
&self.name,
"A tusks module must either be root \
or must declare a parent via `pub use path::to::parent::module as parent_`."
));
}
}
else {
if let Some(parent) = &self.external_parent {
let mut err = syn::Error::new_spanned(
&self.name,
"A tusks module must either be root or declare a parent but not both."
);
err.combine(syn::Error::new_spanned(&parent.alias, "Parent is declared here."));
return Err(err);
}
}
Ok(())
}
fn extract_module_items(&mut self, items: Vec<syn::Item>, is_root: bool) -> syn::Result<()> {
let mut has_default_tusk = false;
for item in items {
match item {
syn::Item::Struct(item_struct) => {
self.parse_struct(item_struct.clone())?;
}
syn::Item::Fn(item_fn) => {
if let Some(tusk) = Tusk::from_fn(
item_fn.clone(),
has_default_tusk,
self.allow_external_subcommands
)? {
has_default_tusk = has_default_tusk || tusk.is_default;
self.tusks.push(tusk);
}
}
syn::Item::Mod(item_mod) => {
if let Some(module) = Self::from_module(item_mod.clone(), false, false)? {
self.submodules.push(module);
}
}
syn::Item::Use(item_use) => {
if matches!(item_use.vis, syn::Visibility::Public(_)) {
self.extract_external_modules(&item_use.tree, &item_use, is_root);
}
}
syn::Item::Enum(item_enum) => {
if matches!(item_enum.vis, syn::Visibility::Public(_)) {
self.value_enums.push(item_enum);
}
}
_ => {
}
}
}
Ok(())
}
fn parse_struct(&mut self, item_struct: ItemStruct) -> syn::Result<()> {
if let Some(params) = TusksParameters::from_struct(item_struct)? {
self.parameters = Some(params);
}
Ok(())
}
fn extract_external_modules(
&mut self,
tree: &syn::UseTree,
item_use: &syn::ItemUse,
is_root: bool
) {
match tree {
syn::UseTree::Path(use_path) => {
self.extract_external_modules(&use_path.tree, item_use, is_root);
}
syn::UseTree::Name(use_name) => {
if use_name.ident == "parent_" && is_root {
self.external_parent = Some(ExternalModule {
alias: use_name.ident.clone(),
item_use: item_use.clone(),
});
} else {
self.external_modules.push(ExternalModule {
alias: use_name.ident.clone(),
item_use: item_use.clone(),
});
}
}
syn::UseTree::Rename(use_rename) => {
if use_rename.rename == "parent_" {
self.external_parent = Some(ExternalModule {
alias: use_rename.rename.clone(),
item_use: item_use.clone(),
});
} else {
self.external_modules.push(ExternalModule {
alias: use_rename.rename.clone(),
item_use: item_use.clone(),
});
}
}
syn::UseTree::Glob(_) => {
}
syn::UseTree::Group(use_group) => {
for item in &use_group.items {
self.extract_external_modules(item, item_use, is_root);
}
}
}
}
}