1use crate::render::{constant::INDEX_FILENAME, util::format::docstring::create_preview};
3use anyhow::Result;
4use horrorshow::{box_html, Template};
5use std::{fmt::Write, path::PathBuf};
6use sway_core::language::CallPath;
7
8pub(crate) type ModulePrefixes = Vec<String>;
9
10#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
12pub struct ModuleInfo {
13 pub module_prefixes: ModulePrefixes,
15 pub(crate) attributes: Option<String>,
23}
24impl ModuleInfo {
25 pub(crate) fn location(&self) -> &str {
29 self.module_prefixes
30 .last()
31 .expect("Expected Some module location, found None")
32 }
33 pub(crate) fn project_name(&self) -> &str {
37 self.module_prefixes
38 .first()
39 .expect("Expected root module, project root missing")
40 }
41 pub(crate) fn parent(&self) -> Option<&String> {
45 if self.has_parent() {
46 let mut iter = self.module_prefixes.iter();
47 iter.next_back();
48 iter.next_back()
49 } else {
50 None
51 }
52 }
53 fn has_parent(&self) -> bool {
55 self.depth() > 1
56 }
57 pub(crate) fn is_root_module(&self) -> bool {
58 self.location() == self.project_name()
59 }
60 pub(crate) fn to_path_literal_string(&self, item_name: &str, location: &str) -> String {
64 let prefix = self.to_path_literal_prefix(location);
65 match prefix.is_empty() {
66 true => item_name.to_owned(),
67 false => format!("{prefix}::{item_name}"),
68 }
69 }
70 fn to_path_literal_prefix(&self, location: &str) -> String {
75 let mut iter = self.module_prefixes.iter();
76 for prefix in iter.by_ref() {
77 if prefix == location {
78 break;
79 }
80 }
81 iter.map(String::as_str).collect::<Vec<&str>>().join("::")
82 }
83 pub(crate) fn get_anchors(&self) -> Result<Vec<String>> {
86 let mut count = self.depth();
87 let mut rendered_module_anchors = Vec::with_capacity(self.depth());
88 for prefix in &self.module_prefixes {
89 let mut href = (1..count).map(|_| "../").collect::<String>();
90 href.push_str(INDEX_FILENAME);
91 rendered_module_anchors.push(
92 box_html! {
93 a(class="mod", href=href) {
94 : prefix;
95 }
96 span: "::";
97 }
98 .into_string()?,
99 );
100 count -= 1;
101 }
102 Ok(rendered_module_anchors)
103 }
104 pub(crate) fn file_path_at_location(&self, file_name: &str, location: &str) -> Result<String> {
109 let mut iter = self.module_prefixes.iter();
110 for prefix in iter.by_ref() {
111 if prefix == location {
112 break;
113 }
114 }
115 let mut file_path = iter.collect::<PathBuf>();
116 file_path.push(file_name);
117
118 file_path
119 .to_str()
120 .map(|file_path_str| file_path_str.to_string())
121 .ok_or_else(|| anyhow::anyhow!("There will always be at least the item name"))
122 }
123
124 pub(crate) fn file_path_from_location(
136 &self,
137 file_name: &str,
138 current_module_info: &ModuleInfo,
139 is_external_item: bool,
140 ) -> Result<String> {
141 if is_external_item {
142 let mut new_path = (0..current_module_info.module_prefixes.len())
143 .map(|_| "../")
144 .collect::<String>();
145 write!(new_path, "{}/{}", self.module_prefixes.join("/"), file_name)?;
146 Ok(new_path)
147 } else {
148 let mut mid = 0; let mut offset = 0; let mut next_location_iter = self.module_prefixes.iter().rev().enumerate().peekable();
151 while let Some((index, prefix)) = next_location_iter.peek() {
152 for (count, module) in current_module_info.module_prefixes.iter().rev().enumerate()
153 {
154 if module == *prefix {
155 offset = count;
156 mid = self.module_prefixes.len() - index;
157 break;
158 }
159 }
160 next_location_iter.next();
161 }
162 let mut new_path = (0..offset).map(|_| "../").collect::<String>();
163 write!(
164 new_path,
165 "{}/{}",
166 self.module_prefixes.split_at(mid).1.join("/"),
167 file_name
168 )?;
169 Ok(new_path)
170 }
171 }
172
173 pub(crate) fn path_to_root(&self) -> String {
182 (0..self.module_prefixes.len())
183 .map(|_| "..")
184 .collect::<Vec<_>>()
185 .join("/")
186 }
187
188 pub(crate) fn to_html_shorthand_path_string(&self, file_name: &str) -> String {
192 format!("{}{}", self.to_html_path_prefix(), file_name)
193 }
194 fn to_html_path_prefix(&self) -> String {
196 (0..self.depth()).map(|_| "../").collect::<String>()
197 }
198 pub(crate) fn depth(&self) -> usize {
200 self.module_prefixes.len()
201 }
202 pub(crate) fn from_ty_module(module_prefixes: Vec<String>, attributes: Option<String>) -> Self {
204 Self {
205 module_prefixes,
206 attributes,
207 }
208 }
209 pub(crate) fn from_call_path(call_path: &CallPath) -> Self {
211 let module_prefixes = call_path
212 .prefixes
213 .iter()
214 .map(|p| p.as_str().to_string())
215 .collect::<Vec<String>>();
216 Self {
217 module_prefixes,
218 attributes: None,
219 }
220 }
221 pub(crate) fn from_vec_str(module_prefixes: &[String]) -> Self {
223 Self {
224 module_prefixes: module_prefixes.to_owned(),
225 attributes: None,
226 }
227 }
228 pub(crate) fn preview_opt(&self) -> Option<String> {
229 create_preview(self.attributes.clone())
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::ModuleInfo;
236
237 #[test]
238 fn test_parent() {
239 let project = String::from("project_name");
240 let module = String::from("module_name");
241 let mut module_vec = vec![project.clone(), module];
242
243 let module_info = ModuleInfo::from_ty_module(module_vec.clone(), None);
244 let project_opt = module_info.parent();
245 assert_eq!(Some(&project), project_opt);
246
247 module_vec.pop();
248 let module_info = ModuleInfo::from_ty_module(module_vec, None);
249 let project_opt = module_info.parent();
250 assert_eq!(None, project_opt);
251 }
252}