codesnip_core/
map.rs

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}