Skip to content

Commit 2f905ba

Browse files
committed
extracting project-file-builder
1 parent 0d200e4 commit 2f905ba

File tree

6 files changed

+131
-59
lines changed

6 files changed

+131
-59
lines changed

Cargo.lock

Lines changed: 0 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@ edition = "2021"
66
[profile.release]
77
debug = true
88

9+
[lib]
10+
path = "src/lib.rs"
11+
912
[dependencies]
1013
clap = { version = "4.5.20", features = ["derive"] }
1114
clap_derive = "4.5.18"
1215
error-stack = "0.5.0"
1316
fast-glob = "0.4.0"
1417
ignore = "0.4.23"
1518
itertools = "0.13.0"
16-
path-clean = "1.0.1"
1719
lazy_static = "1.5.0"
18-
num_cpus = "1.16.0"
20+
path-clean = "1.0.1"
1921
rayon = "1.10.0"
2022
regex = "1.11.1"
2123
serde = { version = "1.0.214", features = ["derive"] }

src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pub(crate) mod common_test;
2+
pub(crate) mod config;
3+
pub mod ownership;
4+
pub(crate) mod project;
5+
pub mod project_builder;
6+
pub(crate) mod project_file_builder;

src/main.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use ownership::{FileOwner, Ownership};
1+
use codeowners::ownership::{self, FileOwner, Ownership};
22

3-
use crate::project_builder::ProjectBuilder;
43
use clap::{Parser, Subcommand};
4+
use codeowners::project_builder::ProjectBuilder;
55
use core::fmt;
66
use error_stack::{Context, Result, ResultExt};
77
use path_clean::PathClean;
@@ -11,12 +11,6 @@ use std::{
1111
process,
1212
};
1313

14-
mod common_test;
15-
mod config;
16-
mod ownership;
17-
mod project;
18-
mod project_builder;
19-
2014
#[derive(Subcommand, Debug)]
2115
enum Command {
2216
#[clap(about = "Finds the owner of a given file.", visible_alias = "f")]

src/project_builder.rs

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ use std::{
66
use error_stack::{Result, ResultExt};
77
use fast_glob::glob_match;
88
use ignore::WalkBuilder;
9-
use lazy_static::lazy_static;
9+
use project_file_builder::build_project_file;
1010
use rayon::iter::{IntoParallelIterator, ParallelIterator};
11-
use regex::Regex;
1211
use tracing::instrument;
1312

1413
use crate::{
1514
config::Config,
1615
project::{deserializers, DirectoryCodeownersFile, Error, Package, PackageType, Project, ProjectFile, Team, VendoredGem},
16+
project_file_builder,
1717
};
1818

1919
type AbsolutePath = PathBuf;
@@ -57,7 +57,9 @@ impl<'a> ProjectBuilder<'a> {
5757
let entry = entry.change_context(Error::Io)?;
5858
entry_types.push(self.build_entry_type(entry)?);
5959
}
60-
self.build_project_from_entry_types(entry_types)
60+
let project = self.build_project_from_entry_types(entry_types);
61+
project_file_builder::save_cache()?;
62+
project
6163
}
6264

6365
fn build_entry_type(&mut self, entry: ignore::DirEntry) -> Result<EntryType, Error> {
@@ -87,7 +89,7 @@ impl<'a> ProjectBuilder<'a> {
8789
Ok(EntryType::TeamFile(absolute_path.to_owned(), relative_path.to_owned()))
8890
}
8991
_ if matches_globs(&relative_path, &self.config.owned_globs) && !matches_globs(&relative_path, &self.config.unowned_globs) => {
90-
let project_file = build_project_file(absolute_path.to_path_buf());
92+
let project_file = build_project_file(absolute_path.to_path_buf(), false);
9193
Ok(EntryType::OwnedFile(project_file))
9294
}
9395
_ => Ok(EntryType::NullEntry()),
@@ -206,34 +208,6 @@ fn javascript_package_owner(path: &Path) -> Result<Option<String>, Error> {
206208
Ok(deserializer.metadata.and_then(|metadata| metadata.owner))
207209
}
208210

209-
lazy_static! {
210-
static ref TEAM_REGEX: Regex = Regex::new(r#"^(?:#|//) @team (.*)$"#).expect("error compiling regular expression");
211-
}
212-
213-
#[instrument(level = "debug", skip_all)]
214-
fn owned_files(owned_file_paths: Vec<PathBuf>) -> Vec<ProjectFile> {
215-
owned_file_paths.into_par_iter().map(build_project_file).collect()
216-
}
217-
218-
fn build_project_file(path: PathBuf) -> ProjectFile {
219-
let content = match std::fs::read_to_string(&path) {
220-
Ok(content) => content,
221-
Err(_) => return ProjectFile { path, owner: None },
222-
};
223-
224-
let first_line = content.lines().next();
225-
let Some(first_line) = first_line else {
226-
return ProjectFile { path, owner: None };
227-
};
228-
229-
let owner = TEAM_REGEX
230-
.captures(first_line)
231-
.and_then(|cap| cap.get(1))
232-
.map(|m| m.as_str().to_string());
233-
234-
ProjectFile { path, owner }
235-
}
236-
237211
#[cfg(test)]
238212
mod tests {
239213
use super::*;
@@ -252,4 +226,11 @@ mod tests {
252226
// should fail because hidden directories are ignored by glob patterns unless explicitly included
253227
assert!(glob_match(OWNED_GLOB, "script/.eslintrc.js"));
254228
}
229+
230+
#[test]
231+
fn test_build_project_file() {
232+
let project_file = build_project_file(PathBuf::from("script/.eslintrc.js"), false);
233+
assert_eq!(project_file.path, PathBuf::from("script/.eslintrc.js"));
234+
assert_eq!(project_file.owner, None);
235+
}
255236
}

src/project_file_builder.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use error_stack::{Result, ResultExt};
2+
use lazy_static::lazy_static;
3+
use regex::Regex;
4+
use std::{
5+
collections::HashMap,
6+
fs::{self, File, OpenOptions},
7+
io::{BufReader, BufWriter},
8+
path::PathBuf,
9+
sync::Mutex,
10+
};
11+
12+
use crate::project::{Error, ProjectFile};
13+
14+
pub(crate) fn build_project_file(path: PathBuf, use_cache: bool) -> ProjectFile {
15+
if !use_cache {
16+
return build_project_file_without_cache(&path);
17+
}
18+
19+
if let Ok(Some(cached_project_file)) = get_project_file_cache(&path) {
20+
return cached_project_file;
21+
}
22+
23+
let proejct_file = build_project_file_without_cache(&path);
24+
25+
if let Ok(mut cache) = PROJECT_FILE_CACHE.lock() {
26+
cache.insert(path.clone(), None);
27+
}
28+
proejct_file
29+
}
30+
31+
pub(crate) fn build_project_file_without_cache(path: &PathBuf) -> ProjectFile {
32+
let content = match std::fs::read_to_string(path) {
33+
Ok(content) => content,
34+
Err(_) => {
35+
return ProjectFile {
36+
path: path.clone(),
37+
owner: None,
38+
}
39+
}
40+
};
41+
42+
let first_line = content.lines().next();
43+
let Some(first_line) = first_line else {
44+
return ProjectFile {
45+
path: path.clone(),
46+
owner: None,
47+
};
48+
};
49+
50+
let owner = TEAM_REGEX
51+
.captures(first_line)
52+
.and_then(|cap| cap.get(1))
53+
.map(|m| m.as_str().to_string());
54+
55+
ProjectFile { path: path.clone(), owner }
56+
}
57+
58+
lazy_static! {
59+
static ref TEAM_REGEX: Regex = Regex::new(r#"^(?:#|//) @team (.*)$"#).expect("error compiling regular expression");
60+
static ref PROJECT_FILE_CACHE: Box<Mutex<HashMap<PathBuf, Option<String>>>> =
61+
Box::new(Mutex::new(load_cache().unwrap_or_else(|_| HashMap::with_capacity(10000))));
62+
}
63+
64+
fn load_cache() -> Result<HashMap<PathBuf, Option<String>>, Error> {
65+
let cache_path = get_cache_path();
66+
if !cache_path.exists() {
67+
return Ok(HashMap::with_capacity(10000));
68+
}
69+
70+
let file = File::open(cache_path).change_context(Error::Io)?;
71+
let reader = BufReader::new(file);
72+
serde_json::from_reader(reader).change_context(Error::SerdeJson)
73+
}
74+
75+
pub(crate) fn save_cache() -> Result<(), Error> {
76+
let cache_path = get_cache_path();
77+
let file = OpenOptions::new()
78+
.write(true)
79+
.create(true)
80+
.truncate(true)
81+
.open(cache_path)
82+
.change_context(Error::Io)?;
83+
84+
let writer = BufWriter::new(file);
85+
let cache = PROJECT_FILE_CACHE.lock().map_err(|_| Error::Io)?;
86+
serde_json::to_writer(writer, &*cache).change_context(Error::SerdeJson)
87+
}
88+
89+
fn get_cache_path() -> PathBuf {
90+
let cache_dir = PathBuf::from("tmp/cache/codeowners");
91+
fs::create_dir_all(&cache_dir).unwrap();
92+
93+
cache_dir.join("project_file_cache.json")
94+
}
95+
96+
fn get_project_file_cache(path: &PathBuf) -> Result<Option<ProjectFile>, Error> {
97+
if let Ok(cache) = PROJECT_FILE_CACHE.lock() {
98+
if let Some(cached_owner) = cache.get(path) {
99+
return Ok(Some(ProjectFile {
100+
path: path.clone(),
101+
owner: cached_owner.clone(),
102+
}));
103+
}
104+
}
105+
Ok(None)
106+
}

0 commit comments

Comments
 (0)