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 value_enums: Vec::new(),
51 };
52
53 tusks_module.extract_module_items(items, is_root)?;
54
55 tusks_module.validate_is_root_or_has_parent(is_tusks_root, is_root)?;
56
57 Ok(Some(tusks_module))
58 }
59
60 fn validate_is_root_or_has_parent(&self, is_tusks_root: bool, is_root: bool) -> syn::Result<()> {
61 if !is_root {
62 return Ok(());
63 }
64
65 if !is_tusks_root {
66 if self.external_parent.is_none() {
67 return Err(syn::Error::new_spanned(
68 &self.name,
69 "A tusks module must either be root \
70 or must declare a parent via `pub use path::to::parent::module as parent_`."
71 ));
72 }
73 }
74 else {
75 if let Some(parent) = &self.external_parent {
76 let mut err = syn::Error::new_spanned(
77 &self.name,
78 "A tusks module must either be root or declare a parent but not both."
79 );
80 err.combine(syn::Error::new_spanned(&parent.alias, "Parent is declared here."));
81 return Err(err);
82 }
83 }
84
85 Ok(())
86 }
87
88 fn extract_module_items(&mut self, items: Vec<syn::Item>, is_root: bool) -> syn::Result<()> {
90 let mut has_default_tusk = false;
91 for item in items {
92 match item {
93 syn::Item::Struct(item_struct) => {
94 self.parse_struct(item_struct.clone())?;
95 }
96
97 syn::Item::Fn(item_fn) => {
98 if let Some(tusk) = Tusk::from_fn(
99 item_fn.clone(),
100 has_default_tusk,
101 self.allow_external_subcommands
102 )? {
103 has_default_tusk = has_default_tusk || tusk.is_default;
104 self.tusks.push(tusk);
105 }
106 }
107
108 syn::Item::Mod(item_mod) => {
109 if let Some(module) = Self::from_module(item_mod.clone(), false, false)? {
110 self.submodules.push(module);
111 }
112 }
113
114 syn::Item::Use(item_use) => {
115 if matches!(item_use.vis, syn::Visibility::Public(_)) {
117 self.extract_external_modules(&item_use.tree, &item_use, is_root);
119 }
120 }
121
122 syn::Item::Enum(item_enum) => {
123 if matches!(item_enum.vis, syn::Visibility::Public(_)) {
124 self.value_enums.push(item_enum);
125 }
126 }
127
128 _ => {
129 }
131 }
132 }
133 Ok(())
134 }
135
136 fn parse_struct(&mut self, item_struct: ItemStruct) -> syn::Result<()> {
138 if let Some(params) = TusksParameters::from_struct(item_struct)? {
139 self.parameters = Some(params);
140 }
141 Ok(())
142 }
143
144fn extract_external_modules(
146 &mut self,
147 tree: &syn::UseTree,
148 item_use: &syn::ItemUse,
149 is_root: bool
150) {
151 match tree {
152 syn::UseTree::Path(use_path) => {
153 self.extract_external_modules(&use_path.tree, item_use, is_root);
155 }
156 syn::UseTree::Name(use_name) => {
157 if use_name.ident == "parent_" && is_root {
159 self.external_parent = Some(ExternalModule {
160 alias: use_name.ident.clone(),
161 item_use: item_use.clone(),
162 });
163 } else {
164 self.external_modules.push(ExternalModule {
165 alias: use_name.ident.clone(),
166 item_use: item_use.clone(),
167 });
168 }
169 }
170 syn::UseTree::Rename(use_rename) => {
171 if use_rename.rename == "parent_" {
173 self.external_parent = Some(ExternalModule {
174 alias: use_rename.rename.clone(),
175 item_use: item_use.clone(),
176 });
177 } else {
178 self.external_modules.push(ExternalModule {
179 alias: use_rename.rename.clone(),
180 item_use: item_use.clone(),
181 });
182 }
183 }
184 syn::UseTree::Glob(_) => {
185 }
187 syn::UseTree::Group(use_group) => {
188 for item in &use_group.items {
190 self.extract_external_modules(item, item_use, is_root);
191 }
192 }
193 }
194}
195}