1use crate::{
2 entry::EntryArgs, format::FormatOption, AttributeExt as _, ItemExt as _, PathExt as _,
3};
4use quote::ToTokens as _;
5use serde::{Deserialize, Serialize};
6use std::{
7 collections::{BTreeMap, BTreeSet},
8 iter::FromIterator,
9};
10use syn::{
11 parse::Parse as _,
12 visit::{self, Visit},
13 Attribute, Item, ItemMod, Path,
14};
15
16#[derive(Debug, Default, Clone, Serialize, Deserialize)]
17pub struct SnippetMap {
18 pub map: BTreeMap<String, LinkedSnippet>,
19}
20
21#[derive(Debug, Default, Clone, Serialize, Deserialize)]
22pub struct LinkedSnippet {
23 pub contents: String,
24 pub includes: BTreeSet<String>,
25}
26
27#[derive(Debug, Copy, Clone)]
28pub struct Filter<'a, 'i> {
29 filter_attr: &'a [Path],
30 filter_item: &'i [Path],
31}
32
33struct CollectEntries<'m, 'i, 'a> {
34 map: &'m mut SnippetMap,
35 filter: Filter<'i, 'a>,
36}
37
38impl SnippetMap {
39 pub fn new() -> Self {
40 Default::default()
41 }
42 fn get_mut(&mut self, name: &str) -> &mut LinkedSnippet {
43 if !self.map.contains_key(name) {
44 self.map.insert(name.to_string(), Default::default());
45 }
46 self.map
47 .get_mut(name)
48 .expect("BTreeMap is not working properly.")
49 }
50 pub fn extend_with_filter(&mut self, item: &Item, filter: Filter) {
51 CollectEntries { map: self, filter }.visit_item(item);
52 }
53 fn resolve_includes<'s>(
54 &'s self,
55 used: &BTreeSet<&'s str>,
56 includes: impl IntoIterator<Item = &'s str>,
57 ) -> BTreeSet<&'s str> {
58 let mut visited = used.clone();
59 let mut stack: Vec<_> = includes.into_iter().collect();
60 visited.extend(&stack);
61 while let Some(include) = stack.pop() {
62 if let Some(nlink) = self.map.get(include) {
63 for ninclude in nlink.includes.iter().map(|s| s.as_str()) {
64 if !visited.contains(ninclude) {
65 visited.insert(ninclude);
66 stack.push(ninclude);
67 }
68 }
69 }
70 }
71 visited
72 }
73 pub fn bundle<'s>(
74 &self,
75 name: &'s str,
76 link: &LinkedSnippet,
77 mut excludes: BTreeSet<&'s str>,
78 guard: bool,
79 ) -> String {
80 fn push_guard(contents: &mut String, name: &str) {
81 if contents.chars().next_back().is_some_and(|ch| ch != '\n') {
82 contents.push('\n');
83 }
84 contents.push_str("// codesnip-guard: ");
85 contents.push_str(name);
86 contents.push('\n');
87 }
88
89 if excludes.contains(name) {
90 return Default::default();
91 }
92 excludes.insert(name);
93 let visited = self.resolve_includes(&excludes, link.includes.iter().map(|s| s.as_str()));
94 let mut contents = String::new();
95 if guard {
96 push_guard(&mut contents, name);
97 }
98 contents.push_str(link.contents.as_str());
99 for include in visited.difference(&excludes).cloned() {
100 if guard {
101 push_guard(&mut contents, include);
102 }
103 if let Some(nlink) = self.map.get(include) {
104 contents.push_str(nlink.contents.as_str());
105 }
106 }
107 contents
108 }
109 pub fn keys(&self, hide: bool) -> Vec<&str> {
110 if hide {
111 self.map
112 .keys()
113 .filter(|name| !name.starts_with('_'))
114 .map(|name| name.as_ref())
115 .collect()
116 } else {
117 self.map.keys().map(|name| name.as_ref()).collect()
118 }
119 }
120}
121
122impl IntoIterator for SnippetMap {
123 type Item = (String, LinkedSnippet);
124 type IntoIter = <BTreeMap<String, LinkedSnippet> as IntoIterator>::IntoIter;
125 fn into_iter(self) -> Self::IntoIter {
126 self.map.into_iter()
127 }
128}
129
130impl Extend<(String, LinkedSnippet)> for SnippetMap {
131 fn extend<T: IntoIterator<Item = (String, LinkedSnippet)>>(&mut self, iter: T) {
132 for (name, link) in iter {
133 self.map.entry(name).or_default().append(link);
134 }
135 }
136}
137
138impl FromIterator<(String, LinkedSnippet)> for SnippetMap {
139 fn from_iter<T: IntoIterator<Item = (String, LinkedSnippet)>>(iter: T) -> Self {
140 let mut map = Self::new();
141 map.extend(iter);
142 map
143 }
144}
145
146impl LinkedSnippet {
147 pub fn push_contents(&mut self, contents: &str) {
148 self.contents.push_str(contents);
149 }
150 pub fn push_item_with_filter(&mut self, item: &Item, filter: Filter) {
151 if let Some(item) = filter.modify_item(item.clone()) {
152 self.contents
153 .push_str(&item.into_token_stream().to_string());
154 }
155 }
156 pub fn push_include(&mut self, include: String) {
157 self.includes.insert(include);
158 }
159 pub fn push_includes(&mut self, includes: impl IntoIterator<Item = String>) {
160 self.includes.extend(includes);
161 }
162 pub fn append(&mut self, mut other: Self) {
163 self.contents.push_str(&other.contents);
164 self.includes.append(&mut other.includes);
165 }
166 pub fn format(&mut self, option: &FormatOption) -> bool {
167 if let Some(formatted) = option.format(&self.contents) {
168 self.contents = formatted;
169 true
170 } else {
171 false
172 }
173 }
174}
175
176impl<'a, 'i> Filter<'a, 'i> {
177 pub fn new(filter_attr: &'a [Path], filter_item: &'i [Path]) -> Self {
178 Self {
179 filter_attr,
180 filter_item,
181 }
182 }
183}
184
185impl Visit<'_> for CollectEntries<'_, '_, '_> {
186 fn visit_item(&mut self, item: &Item) {
187 if let Some(attrs) = item.get_attributes() {
188 for entry in attrs
189 .iter()
190 .filter(|attr| attr.path().is_codesnip_entry())
191 .filter_map(|attr| attr.parse_args_empty_with(EntryArgs::parse).ok())
192 .filter_map(|args| args.try_to_entry(item).ok())
193 {
194 let link = self.map.get_mut(&entry.name);
195 let filter = self.filter;
196 match (entry.inline, item) {
197 (true, Item::Mod(ItemMod { attrs, content, .. })) => {
198 if !filter.is_skip_item(attrs) {
199 if let Some((_, items)) = content {
200 for item in items {
201 link.push_item_with_filter(item, filter);
202 }
203 }
204 }
205 }
206 _ => link.push_item_with_filter(item, filter),
207 }
208 link.push_includes(entry.include);
209 }
210 }
211 visit::visit_item(self, item);
212 }
213}
214
215impl Filter<'_, '_> {
216 fn is_skip_item(self, attrs: &[Attribute]) -> bool {
217 attrs.iter().any(|attr| {
218 attr.path().is_codesnip_skip() || self.filter_item.iter().any(|pat| pat == attr.path())
219 })
220 }
221
222 fn filter_attributes(self, attrs: &mut Vec<Attribute>) {
223 attrs.retain(|attr| {
224 !(attr.path().is_codesnip_entry()
225 || self.filter_attr.iter().any(|pat| pat == attr.path()))
226 })
227 }
228
229 fn modify_item(self, mut item: Item) -> Option<Item> {
230 if let Some(attrs) = item.get_attributes() {
231 if self.is_skip_item(attrs) {
232 return None;
233 }
234 }
235
236 if let Some(attrs) = item.get_attributes_mut() {
237 self.filter_attributes(attrs);
238 }
239
240 if let Item::Mod(ItemMod {
241 content: Some((_, items)),
242 ..
243 }) = &mut item
244 {
245 *items = items
246 .drain(..)
247 .filter_map(|item| self.modify_item(item))
248 .collect::<Vec<_>>();
249 }
250
251 Some(item)
252 }
253}