Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 43 additions & 5 deletions src/analyse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,50 @@ use std::path::PathBuf;
use std::sync::{Arc, LazyLock, Mutex};
use std::time::Instant;

pub struct ProjectDisplayConfig {
/// Name of the project, displayed on the website.
name: &'static str,
/// Path under which the project will be available on the web.
/// If the `url` is e.g. `rust`, it will be available under `/rust/`.
url_path: &'static str,
/// Should this project be displayed as the main homepage project?
is_homepage: bool,
/// Returns true if the project does not track versions explicitly.
/// It will be rendered as a single page with all-time contributions.
is_versionless: bool,
}

impl ProjectDisplayConfig {
/// Name of the project, displayed on the website.
pub fn name(&self) -> &'static str {
self.name
}
/// Path under which the project will be available on the web.
/// If the `url` is e.g. `rust`, it will be available under `/rust/`.
pub fn url_path(&self) -> &'static str {
self.url_path
}
/// Should this project be displayed as the main homepage project?
pub fn is_homepage(&self) -> bool {
self.is_homepage
}
/// Returns true if the project does not track versions explicitly.
/// It will be rendered as a single page with all-time contributions.
pub fn is_versionless(&self) -> bool {
self.is_versionless
}
}

pub struct ProjectData {
pub project: Box<dyn Project>,
pub display_config: ProjectDisplayConfig,
pub by_version: BTreeMap<VersionTag, AuthorsWithScores>,
pub all_time: AuthorsWithScores,
}

pub fn compute_data(
project: Box<dyn Project>,
pub fn compute_data<P: Project>(
mailmap_path: Option<PathBuf>,
) -> Result<ProjectData, Box<dyn std::error::Error>> {
let by_version = generate_thanks(project.as_ref(), mailmap_path)?;
let by_version = generate_thanks::<P>(mailmap_path)?;
let by_version: BTreeMap<_, _> = by_version
.into_iter()
.map(|(k, v)| (k, AuthorsWithScores::new(v)))
Expand All @@ -34,7 +67,12 @@ pub fn compute_data(
}
let all_time = AuthorsWithScores::new(all_time);
Ok(ProjectData {
project,
display_config: ProjectDisplayConfig {
name: P::NAME,
url_path: P::URL_PATH,
is_homepage: P::IS_HOMEPAGE,
is_versionless: P::IS_VERSIONLESS,
},
by_version,
all_time,
})
Expand Down
9 changes: 9 additions & 0 deletions src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,12 @@ impl fmt::Debug for VersionTag {
write!(f, "{}", self.version)
}
}

/// Get the Oid of the latest commit to the given revision name
pub fn revision_latest_commit(repo: &Repository, revision: &str) -> Oid {
repo.revparse_single(revision)
.unwrap()
.peel_to_commit()
.unwrap()
.id()
}
55 changes: 26 additions & 29 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,18 @@ mod score;
mod site;

use crate::analyse::{
AuthorMap, AuthorsWithScores, build_author_map, compute_data, gather_all_commits,
AuthorMap, AuthorsWithScores, ProjectData, build_author_map, compute_data, gather_all_commits,
};
use crate::git::{VersionTag, mailmap_from_repo, update_repo};
use crate::projects::{CratesIo, DocsRs, Project, Rust, Rustup};
use crate::score::AuthorScore;

fn generate_thanks(
project: &dyn Project,
fn generate_thanks<P: Project>(
mailmap_path: Option<PathBuf>,
) -> Result<BTreeMap<VersionTag, AuthorMap>, Box<dyn std::error::Error>> {
eprintln!("Generating {}", project.name());
eprintln!("Generating {}", P::NAME);

let path = update_repo(project.repo_url())?;
let path = update_repo(P::REPO_URL)?;
let repo = git2::Repository::open(&path)?;

let mailmap = match mailmap_path {
Expand All @@ -44,7 +43,7 @@ fn generate_thanks(
};
let reviewers = Reviewers::new()?;

let versions = project.get_versions(&repo)?;
let versions = P::get_versions(&repo)?;

let start = Instant::now();

Expand All @@ -57,8 +56,7 @@ fn generate_thanks(
start.elapsed().as_secs_f64()
);

let ignored_emails: HashSet<UniCase<String>> = project
.ignored_emails()
let ignored_emails: HashSet<UniCase<String>> = P::IGNORED_EMAILS
.iter()
.map(|email| UniCase::new(email.to_string()))
.collect();
Expand Down Expand Up @@ -87,33 +85,32 @@ fn generate_thanks(
Ok(version_map)
}

/// Return all projects for which we currently generate contribution statistics.
fn get_all_projects() -> Vec<Box<dyn Project>> {
vec![
Box::new(Rust),
Box::new(Rustup),
Box::new(CratesIo),
Box::new(DocsRs),
]
}

fn run(
mode: OutputMode,
mailmap_path: Option<PathBuf>,
selected_project: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut projects = get_all_projects();
if let Some(selected) = selected_project {
projects.retain(|p| p.name().to_lowercase() == selected);
if projects.is_empty() {
panic!("No projects found with name {selected}");
macro_rules! known_projects {
( $( $name:literal => $project:ty, )+ ) => {
match selected_project {
$(
Some($name) => vec![compute_data::<$project>(mailmap_path.clone())?],
)+
Some(selected) => panic!("No projects found with name {selected}"),
None => vec![
$(
compute_data::<$project>(mailmap_path.clone())?
),+
]
}
}
}

let mut data = vec![];
for project in projects {
data.push(compute_data(project, mailmap_path.clone())?);
}
let data: Vec<ProjectData> = known_projects! {
"rust" => Rust,
"rustup" => Rustup,
"crates.io" => CratesIo,
"docs.rs" => DocsRs,
};

match mode {
OutputMode::Html => {
Expand All @@ -140,7 +137,7 @@ fn run(

let csv_dir = PathBuf::from("output/csv");
for data in data {
let directory = csv_dir.join(data.project.name().to_lowercase());
let directory = csv_dir.join(data.display_config.name().to_lowercase());
std::fs::create_dir_all(&directory)?;
for (version, authors) in data.by_version {
write(&directory.join(format!("{version}.csv")), authors)?;
Expand Down
Loading