tree_sitter_tests_formatter/
lib.rs1use std::{cmp::max, fs::read_to_string, fs::File, path::Path, path::PathBuf, vec};
2
3use lazy_static::lazy_static;
4use regex::Regex;
5
6mod s_exp_formatter;
7
8pub use s_exp_formatter::format_s_expr;
9
10pub fn format_tests_dir(path: &Path) {
15 assert!(path.exists(), "Path does not exist: {:?}", path);
16 assert!(path.is_dir(), "Path is not a directory: {:?}", path);
17 let test_files = walk_tests_dir(path);
18 for test_file in test_files.iter() {
19 test_file.format_file(test_file.path());
20 }
21}
22
23pub fn format_test_file(path: &Path) {
28 assert!(path.exists(), "Path does not exist: {:?}", path);
29 assert!(path.is_file(), "Path is not a file: {:?}", path);
30 let test_file = TestFile::from_file(path);
31 test_file.format_file(path);
32}
33
34#[derive(Debug, PartialEq, Eq, Default, Clone)]
35struct TestFile {
36 path: PathBuf,
37 fixtures: Vec<Fixture>,
38}
39
40impl TestFile {
41 pub fn from_file(path: &Path) -> Self {
42 enum State {
43 InFixtureName,
44 InFixture,
45 InExpected,
46 None,
47 }
48 lazy_static! {
49 static ref RE_FIXTURE_NAME_SEP: Regex = Regex::new(r"^===").unwrap();
50 static ref RE_FIXTURE_SEP: Regex = Regex::new(r"^---$").unwrap();
51 }
52 let mut state: State = State::None;
53 let mut fixtures = Vec::new();
54 let mut cur = Fixture::default();
55 for line in read_to_string(path).unwrap().lines() {
56 match state {
57 State::None => {
58 if RE_FIXTURE_NAME_SEP.is_match(line) {
60 state = State::InFixtureName;
61 }
62 }
63 State::InFixtureName => {
64 if RE_FIXTURE_NAME_SEP.is_match(line) {
66 state = State::InFixture;
67 }
68 else {
70 cur.name = line.to_string();
71 }
72 }
73 State::InFixture => {
74 if RE_FIXTURE_SEP.is_match(line) {
76 state = State::InExpected;
77 }
78 else {
80 cur.input.push_str(line);
81 cur.input.push('\n');
82 }
83 }
84 State::InExpected => {
85 if RE_FIXTURE_NAME_SEP.is_match(line) {
87 state = State::InFixtureName;
88 fixtures.push(cur.clone());
89 cur = Fixture::default();
90 } else {
91 cur.expected.push_str(line);
92 cur.expected.push('\n');
93 }
94 }
95 }
96 }
97 fixtures.push(cur);
98
99 Self {
100 path: path.to_path_buf(),
101 fixtures,
102 }
103 }
104
105 pub fn format_file(&self, path: &Path) {
106 use std::io::Write;
107 let formatted = self.format();
108 let mut file = File::create(path).unwrap();
109 write!(file, "{}", formatted).unwrap();
110 }
111
112 pub fn path(&self) -> &Path {
113 &self.path
114 }
115
116 fn format(&self) -> String {
117 let mut res = Vec::new();
118 for fixture in self.fixtures.iter() {
119 res.push(fixture.format());
120 }
121
122 res.join("\n".repeat(2).as_str())
123 }
124}
125
126#[derive(Debug, PartialEq, Eq, Default, Clone)]
127struct Fixture {
128 name: String,
129 input: String,
130 expected: String,
131}
132
133impl Fixture {
134 pub fn format(&self) -> String {
135 let res = vec![
136 self.format_name(),
137 "\n\n".to_string(),
138 self.format_input(),
139 "\n\n".to_string(),
140 "---".to_string(),
141 "\n\n".to_string(),
142 self.format_expected(),
143 ];
144
145 res.join("")
146 }
147
148 fn format_name(&self) -> String {
149 let mut res = Vec::new();
150 let n = max(3, self.name.trim().len());
151 let sep = "=".to_string().repeat(n);
152 res.push(sep.clone());
153 res.push(self.name.trim().to_string());
154 res.push(sep.clone());
155
156 res.join("\n")
157 }
158
159 fn format_input(&self) -> String {
160 self.input.trim().to_string()
161 }
162
163 fn format_expected(&self) -> String {
164 s_exp_formatter::format_s_expr(&self.expected)
165 }
166}
167
168fn walk_tests_dir(path: &Path) -> Vec<TestFile> {
169 let mut res: Vec<TestFile> = Vec::new();
170 for entry in path.read_dir().unwrap() {
171 let entry = entry.unwrap();
172 let path = entry.path();
173 if path.is_file() {
174 res.push(TestFile::from_file(&path));
175 }
176 }
177
178 res
179}