csskit_source_finder/
lib.rs1use std::collections::HashSet;
2use std::io;
3use std::path::PathBuf;
4use std::str::from_utf8;
5
6use glob::glob;
7use grep_matcher::{Captures, Matcher};
8use grep_regex::{RegexMatcher, RegexMatcherBuilder};
9use grep_searcher::{Searcher, SearcherBuilder, Sink, SinkError, SinkMatch};
10use quote::ToTokens;
11use syn::{Type, parse_str};
12
13pub struct NodeMatcher<'a> {
14 matcher: &'a RegexMatcher,
15 matches: &'a mut HashSet<String>,
16}
17
18impl Sink for NodeMatcher<'_> {
19 type Error = io::Error;
20
21 fn matched(&mut self, _searcher: &Searcher, mat: &SinkMatch<'_>) -> Result<bool, io::Error> {
22 let mut captures = self.matcher.new_captures()?;
23 let line = match from_utf8(mat.bytes()) {
24 Ok(matched) => matched,
25 Err(err) => return Err(io::Error::error_message(err)),
26 };
27 self.matcher.captures_iter(mat.bytes(), &mut captures, |captures| -> bool {
28 dbg!(&line, &captures, captures.get(2).map(|r| &line[r]), captures.get(5).map(|r| &line[r]));
29 let capture = &line[captures.get(5).unwrap()];
30 if !capture.is_empty() {
31 if let Ok(ty) = parse_str::<Type>(capture) {
32 self.matches.insert(ty.to_token_stream().to_string());
33 }
34 } else {
35 dbg!(&line);
36 panic!("#[visit] or unknown");
37 }
38 true
39 })?;
40 Ok(true)
41 }
42}
43
44pub fn find_visitable_nodes(dir: &str, matches: &mut HashSet<String>, path_callback: impl Fn(&PathBuf)) {
45 let matcher = RegexMatcherBuilder::new()
46 .multi_line(true)
47 .dot_matches_new_line(true)
48 .ignore_whitespace(true)
49 .build(
50 r#"
51 ^\s*\#\[
52 # munch `cfg_atr(...,` and optional `derive(...)`.
53 (?:cfg_attr\([^,]+,\s*(?:derive\([^\)]+\),\s*)?)?
54 # match the #[visit] attribute
55 (visit)
56 # munch the data between the attribute and the definition
57 .*?
58 (
59 # Is this a public definition?
60 pub\s*(?:struct|enum)\s*
61 )
62 # munch any comments/attributes between this and our name (for macros)
63 (:?\n?\s*(:?\/\/|\#)[^\n]*)*
64 # finally grab the word (plus any generics)
65 \s*(\w*(:?<[^>]+>)?)"#,
66 )
67 .unwrap();
68 let mut searcher = SearcherBuilder::new().line_number(false).multi_line(true).build();
69 let entries = glob(dir).unwrap();
70 for entry in entries.filter_map(|p| p.ok()) {
71 path_callback(&entry);
72 let context = NodeMatcher { matcher: &matcher, matches };
73 searcher.search_path(&matcher, entry, context).unwrap();
74 }
75}
76
77#[test]
78fn test_find_visitable_nodes() {
79 use itertools::Itertools;
80 let mut matches = HashSet::new();
81 find_visitable_nodes("../css_ast/src/**/*.rs", &mut matches, |_| {});
82 ::insta::assert_ron_snapshot!("all_visitable_nodes", matches.iter().sorted().collect::<Vec<_>>());
83}