1use anyhow::{anyhow, Result};
2use std::path::PathBuf;
3use syn::{ItemFn, LitStr};
4
5#[derive(Debug, Clone)]
6pub struct PageEntry {
7 pub name: String,
8 pub is_dir: bool,
9 pub children: Vec<PageEntry>,
10}
11
12impl PageEntry {
13 pub fn generate(dir: &PathBuf) -> Result<PageEntry> {
14 let mut children: Vec<PageEntry> = Vec::new();
15
16 for entry in dir.read_dir()? {
18 if let Ok(entry) = entry {
19 let file_type = entry.file_type()?;
20
21 if file_type.is_dir() {
22 children.push(PageEntry::generate(&entry.path())?);
23 } else if file_type.is_file() {
24 let file_name = entry
25 .file_name()
26 .to_str()
27 .unwrap()
28 .split('.')
29 .collect::<Vec<&str>>()[0]
30 .to_owned();
31
32 children.push(PageEntry {
33 name: file_name,
34 is_dir: entry.file_type()?.is_dir(),
35 children: vec![],
36 })
37 }
38 }
39 }
40
41 let dir_name = dir.file_name().unwrap().to_str().unwrap().to_owned();
43 children.sort_by_key(|k| k.is_dir);
44
45 return Ok(PageEntry {
46 name: dir_name,
47 is_dir: true,
48 children,
49 });
50 }
51
52 pub fn get_mods_string(&self) -> Result<String> {
53 let mut out = String::new();
54 if self.is_dir && self.children.len() == 0 {
55 return Ok(out);
56 }
57
58 if self.name.contains(":") || self.name.contains("{") || self.name.contains("}") {
59 out += &format!("#[path = \"{}\"]\n", self.name);
60 }
61 out += &format!(
62 "pub mod {}",
63 self.name
64 .replace(":", "_")
65 .replace("{", "_")
66 .replace("}", "_")
67 );
68
69 if self.children.len() > 0 {
70 out += "{ \n";
71
72 for child in self.children.clone() {
73 out += &child.get_mods_string()?;
74 }
75
76 out += "} \n";
77 } else {
78 out += "; \n";
79 }
80
81 Ok(out)
82 }
83}
84
85#[derive(Debug, Clone)]
86pub struct FunctionRoute {
87 pub function: String,
88 pub route_type: RouteType,
89 pub route: Option<String>,
90}
91
92#[derive(Debug, Clone)]
93pub enum RouteType {
94 Get,
95 Post,
96 Put,
97 Delete,
98 Patch,
99 Head,
100 Options,
101 Any,
102 On,
103}
104
105impl RouteType {
106 pub fn from_str(s: &str) -> Result<RouteType> {
107 match s {
108 "get" => Ok(RouteType::Get),
109 "post" => Ok(RouteType::Post),
110 "put" => Ok(RouteType::Put),
111 "delete" => Ok(RouteType::Delete),
112 "patch" => Ok(RouteType::Patch),
113 "head" => Ok(RouteType::Head),
114 "options" => Ok(RouteType::Options),
115 "any" => Ok(RouteType::Any),
116 "on" => Ok(RouteType::On),
117 _ => Err(anyhow!("Invalid route type")),
118 }
119 }
120
121 pub fn to_str(&self) -> &str {
122 match self {
123 RouteType::Get => "get",
124 RouteType::Post => "post",
125 RouteType::Put => "put",
126 RouteType::Delete => "delete",
127 RouteType::Patch => "patch",
128 RouteType::Head => "head",
129 RouteType::Options => "options",
130 RouteType::Any => "any",
131 RouteType::On => "on",
132 }
133 }
134}
135
136pub fn get_file_routes(path: PathBuf) -> Result<Vec<FunctionRoute>> {
137 let file_content = std::fs::read_to_string(path)?;
138
139 let syntax = syn::parse_file(&file_content).unwrap();
140 let functions: Vec<FunctionRoute> = syntax
141 .items
142 .iter()
143 .filter_map(|item| {
144 if let syn::Item::Fn(item_fn) = item {
145 return get_macro_attr(item_fn);
146 }
147
148 None
149 })
150 .collect();
151
152 return Ok(functions);
153}
154
155const MACROS: [&'static str; 10] = [
156 "get", "post", "put", "delete", "patch", "head", "options", "trace", "any", "on",
157];
158fn get_macro_attr(item: &ItemFn) -> Option<FunctionRoute> {
159 for attr in item.attrs.clone() {
160 for segment in attr.path().segments.clone() {
161 let ident = segment.ident.to_string();
162 if MACROS.contains(&ident.as_str()) {
163 let route: Option<String> = attr.parse_args::<LitStr>().ok().map(|x| x.value());
164
165 return Some(FunctionRoute {
166 function: item.sig.ident.to_string(),
167 route_type: RouteType::from_str(&ident).unwrap(),
168 route,
169 });
170 }
171 }
172 }
173
174 return None;
175}