tusks_lib/parsing/
module.rs1use crate::{models::{Attributes, ExternalModule, Tusk, TusksModule, TusksParameters}, parsing::util::get_attribute_value::AttributeValue};
2use syn::spanned::Spanned;
3use crate::parsing::util::attr::AttributeCheck;
4
5use syn::{ItemMod, ItemStruct};
6
7impl TusksModule {
8 pub fn from_module(module: ItemMod, is_tusks_root: bool, is_root: bool) -> syn::Result<Option<Self>> {
10 let allow_external_subcommands = module.get_attribute_bool(
11 "command",
12 "allow_external_subcommands"
13 );
14
15 let name = module.ident.clone();
16 let span = module.span();
17
18 if !matches!(module.vis, syn::Visibility::Public(_)) {
20 if module.has_attr("tusks") {
21 return Err(syn::Error::new_spanned(&name, "tusks module must be public"));
22 }
23 return Ok(None);
24 }
25
26 if module.has_attr("skip") {
27 return Ok(None);
28 }
29
30 let items = match module.content {
32 Some(content) => content.1, None => {
34 return Err(syn::Error::new(
35 span,
36 "tusks module must be inline (not a file reference)"
37 ));
38 }
39 };
40
41 let mut tusks_module = TusksModule {
42 name,
43 attrs: Attributes(module.attrs),
44 external_parent: None,
45 parameters: None,
46 tusks: Vec::new(),
47 submodules: Vec::new(),
48 external_modules: Vec::new(),
49 allow_external_subcommands,
50 };
51
52 tusks_module.extract_module_items(items, is_root)?;
53
54 tusks_module.validate_is_root_or_has_parent(is_tusks_root, is_root)?;
55
56 Ok(Some(tusks_module))
57 }
58
59 fn validate_is_root_or_has_parent(&self, is_tusks_root: bool, is_root: bool) -> syn::Result<()> {
60 if !is_root {
61 return Ok(());
62 }
63
64 if !is_tusks_root {
65 if !self.external_parent.is_some() {
66 return Err(syn::Error::new_spanned(
67 &self.name,
68 "A tusks module must either be root \
69 or must declare a parent via `pub use path::to::parent::module as parent_`."
70 ));
71 }
72 }
73 else {
74 if let Some(parent) = &self.external_parent {
75 let mut err = syn::Error::new_spanned(
76 &self.name,
77 "A tusks module must either be root or declare a parent but not both."
78 );
79 err.combine(syn::Error::new_spanned(&parent.alias, "Parent is declared here."));
80 return Err(err);
81 }
82 }
83
84 return Ok(());
85 }
86
87 fn extract_module_items(&mut self, items: Vec<syn::Item>, is_root: bool) -> syn::Result<()> {
89 let mut has_default_tusk = false;
90 for item in items {
91 match item {
92 syn::Item::Struct(item_struct) => {
93 self.parse_struct(item_struct.clone())?;
94 }
95
96 syn::Item::Fn(item_fn) => {
97 if let Some(tusk) = Tusk::from_fn(
98 item_fn.clone(),
99 has_default_tusk,
100 self.allow_external_subcommands
101 )? {
102 has_default_tusk = has_default_tusk || tusk.is_default;
103 self.tusks.push(tusk);
104 }
105 }
106
107 syn::Item::Mod(item_mod) => {
108 if let Some(module) = Self::from_module(item_mod.clone(), false, false)? {
109 self.submodules.push(module);
110 }
111 }
112
113 syn::Item::Use(item_use) => {
114 if matches!(item_use.vis, syn::Visibility::Public(_)) {
116 self.extract_external_modules(&item_use.tree, &item_use, is_root);
118 }
119 }
120
121 _ => {
122 }
124 }
125 }
126 Ok(())
127 }
128
129 fn parse_struct(&mut self, item_struct: ItemStruct) -> syn::Result<()> {
131 if let Some(params) = TusksParameters::from_struct(item_struct)? {
132 self.parameters = Some(params);
133 }
134 Ok(())
135 }
136
137fn extract_external_modules(
139 &mut self,
140 tree: &syn::UseTree,
141 item_use: &syn::ItemUse,
142 is_root: bool
143) {
144 match tree {
145 syn::UseTree::Path(use_path) => {
146 self.extract_external_modules(&use_path.tree, item_use, is_root);
148 }
149 syn::UseTree::Name(use_name) => {
150 if use_name.ident == "parent_" && is_root {
152 self.external_parent = Some(ExternalModule {
153 alias: use_name.ident.clone(),
154 item_use: item_use.clone(),
155 });
156 } else {
157 self.external_modules.push(ExternalModule {
158 alias: use_name.ident.clone(),
159 item_use: item_use.clone(),
160 });
161 }
162 }
163 syn::UseTree::Rename(use_rename) => {
164 if use_rename.rename == "parent_" {
166 self.external_parent = Some(ExternalModule {
167 alias: use_rename.rename.clone(),
168 item_use: item_use.clone(),
169 });
170 } else {
171 self.external_modules.push(ExternalModule {
172 alias: use_rename.rename.clone(),
173 item_use: item_use.clone(),
174 });
175 }
176 }
177 syn::UseTree::Glob(_) => {
178 }
180 syn::UseTree::Group(use_group) => {
181 for item in &use_group.items {
183 self.extract_external_modules(item, item_use, is_root);
184 }
185 }
186 }
187}
188}