1mod error;
2pub use error::*;
3
4use log::debug;
5use regex::Regex;
6use std::{collections::HashMap, fmt::Display, fs, path::PathBuf, process::Command, str::from_utf8};
7use syn::{
8 Ident, Item,
9 Type::{self, Path, Tuple},
10};
11
12#[derive(Debug, Clone)]
14pub enum Migration {
15 Ok(String),
16 NotOk(),
17}
18
19impl Display for Migration {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 let s = match self {
22 Migration::Ok(s) => s.to_string(),
23 Migration::NotOk() => "err".to_string(),
24 };
25 f.write_str(&s)
26 }
27}
28
29impl From<&str> for Migration {
30 fn from(s: &str) -> Self {
31 Migration::Ok(s.to_string())
32 }
33}
34
35pub fn get_files(
39 repo: &PathBuf,
40 ) -> Result<Vec<PathBuf>> {
42 const PATTERN: &str = "pub type Migrations";
43
44 let mut grep = Command::new("git");
45 grep.args(["grep", "--name-only", PATTERN]);
46 let output = grep.current_dir(repo).output();
48
49 match output {
50 Ok(o) => {
51 let arr: Vec<PathBuf> = o
52 .stdout
53 .split(|c| c == &10)
54 .map(|a| from_utf8(a).unwrap())
55 .filter(|s| !s.is_empty())
56 .map(|s| PathBuf::from(repo).join(PathBuf::from(s)))
58 .collect();
59 Ok(arr)
60 }
61 Err(e) => {
62 eprintln!("repo: {repo:?}");
63 eprintln!("{e:?}");
64 todo!()
65 }
66 }
67}
68
69fn get_migration(t: &Type) -> Result<Vec<Migration>> {
72 match t {
73 Path(p) => {
74 let segment = p.path.segments.iter().nth(1).ok_or(SubmigError::NonStandard)?;
82 let ident = &(segment.ident.clone() as Ident);
83 Ok(vec![(Migration::Ok(ident.to_string()))])
84 }
85 Tuple(t) => {
86 log::debug!("tuple: nb elems: {}", t.elems.len());
87
88 let content = t.elems.iter().flat_map(get_migration).flatten().collect();
89
90 Ok(content)
91 }
92 Type::Paren(p) => {
93 log::debug!("{p:?}");
94 let content = p.elem.clone();
95 get_migration(content.as_ref())
96 }
97 x => {
98 log::warn!("Non standard: {x:?})");
99 Err(SubmigError::NonStandard)
100 }
101 }
102}
103fn get_migrations(it: &Item) -> Result<Vec<Migration>> {
105 log::debug!("get_migrations");
106
107 let migrations: Vec<Migration> = match it {
108 Item::Type(t) => get_migration(&t.ty).unwrap(),
109 _ => unreachable!(),
110 };
111 debug!("Migrations: {migrations:?}");
112 Ok(migrations)
113}
114
115fn check_naming(migrations: Vec<Migration>) -> (Vec<Migration>, Vec<Migration>) {
119 let version_regexp = Regex::new(r"^V.*$").unwrap();
120
121 let valid = migrations
122 .iter()
123 .filter(|m| m.to_string() == "Unreleased" || version_regexp.is_match(&m.to_string()))
124 .cloned()
125 .collect();
126 let invalid = migrations
127 .iter()
128 .filter(|m| m.to_string() != "Unreleased" && !version_regexp.is_match(&m.to_string()))
129 .cloned()
130 .collect();
131 (valid, invalid)
132}
133
134type SearchResult = HashMap<PathBuf, (Vec<Migration>, Vec<Migration>)>;
135
136pub fn find(
140 repo: &PathBuf,
141 ) -> Result<SearchResult> {
143 let files = get_files(
144 repo,
145 )?;
147 let mut res: SearchResult = HashMap::new();
148
149 for file in files {
150 let code = fs::read_to_string(&file).map_err(|_e| SubmigError::IO)?;
151 let syntax = syn::parse_file(&code).map_err(|_| SubmigError::Parsing)?;
152
153 let hits: Vec<&Item> =
154 syntax.items.iter().filter(|&item| matches!(item, syn::Item::Type(i) if i.ident == "Migrations")).collect();
155
156 debug!("Found {} Migration hits in {}", hits.len(), file.display());
157 if let Some(hit) = hits.first() {
158 let migrations: Vec<Migration> = get_migrations(hit)?;
159 let (valid, invalid) = check_naming(migrations);
160 res.insert(file, (valid, invalid));
161 }
162 }
163 Ok(res)
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use std::env;
170
171 fn setup() {
172 let repo_polkadot_sdk: &str = &env::var("REPO_POLKADOT_SDK").unwrap_or_default();
173 if repo_polkadot_sdk.is_empty() {
174 env::set_var("REPO_POLKADOT_SDK", "/projects/polkadot-sdk");
175 }
176
177 let repo_fellowship_runtimes: &str = &env::var("REPO_FELLOWSHIP_RUNTIMES").unwrap_or_default();
178 if repo_fellowship_runtimes.is_empty() {
179 env::set_var("REPO_FELLOWSHIP_RUNTIMES", "/projects/fellowship-runtimes");
180 }
181 }
182
183 #[test]
184 fn it_find_files() {
185 setup();
186 let polkadot_repo: &str = &env::var("REPO_POLKADOT_SDK").unwrap();
187 let result = get_files(&PathBuf::from(polkadot_repo)).unwrap();
188 assert_eq!(13, result.len());
189 }
190
191 #[test]
192 fn it_finds_migrations_polkadot_sdk() {
193 setup();
194 let polkadot_repo: &str = &env::var("REPO_POLKADOT_SDK").unwrap();
195 let result = find(&PathBuf::from(polkadot_repo)).unwrap();
196 assert_eq!(12, result.len());
197 println!("result = {:?}", result);
198 }
199
200 #[test]
201 fn it_finds_migrations_fellowship_runtimes() {
202 setup();
203 let polkadot_repo: &str = &env::var("REPO_FELLOWSHIP_RUNTIMES").unwrap();
204 let result = find(&PathBuf::from(polkadot_repo)).unwrap();
205 assert_eq!(6, result.len());
206 println!("result = {:?}", result);
207 }
208}