use std::path::PathBuf;
use crate::domain::repo::{GitInfo, Repo, RepoKind};
pub fn repos() -> Vec<Repo> {
let mut repos = vec![
git(
"acme-api",
"/Users/you/Code/acme-api",
Some("api"),
true,
clean("main", "acme-api"),
),
git(
"web-dashboard",
"/Users/you/Code/web-dashboard",
Some("web"),
false,
changes("main", "web-dashboard", 3),
),
git(
"payments-service",
"/Users/you/Code/payments-service",
None,
false,
diverged("main", "payments-service", 2, 1),
),
git(
"design-system",
"/Users/you/Code/design-system",
Some("ds"),
false,
busy("feature/tokens", "design-system", 7, 1),
),
git(
"dotfiles",
"/Users/you/Code/dotfiles",
None,
false,
behind("main", "dotfiles", 4),
),
git(
"legacy-cms",
"/Users/you/Code/legacy-cms",
None,
false,
broken(),
),
folder("Projects", "/Users/you/Code", Some("code"), Some("Code")),
folder("Rust workspace", "/Users/you/Code/rust", None, Some("Code")),
file(
"Architecture notes",
"/Users/you/Docs/architecture.md",
None,
Some("Docs"),
),
file(
"Meeting notes",
"/Users/you/Docs/meetings.md",
Some("notes"),
Some("Docs"),
),
folder("Downloads", "/Users/you/Downloads", None, None),
archived_git(
"old-prototype",
"/Users/you/Code/old-prototype",
clean("main", "old-prototype"),
),
archived_git(
"deprecated-api",
"/Users/you/Code/deprecated-api",
clean("main", "deprecated-api"),
),
archived_folder("Old exports", "/Users/you/Archive/exports"),
];
set_section(&mut repos, "acme-api", "Services");
set_section(&mut repos, "payments-service", "Services");
set_section(&mut repos, "web-dashboard", "Frontend");
set_section(&mut repos, "design-system", "Frontend");
set_section(&mut repos, "old-prototype", "Services");
repos
}
pub fn sections() -> Vec<String> {
vec!["Code".to_string(), "Docs".to_string()]
}
pub fn git_sections() -> Vec<String> {
vec!["Services".to_string(), "Frontend".to_string()]
}
fn set_section(repos: &mut [Repo], name: &str, section: &str) {
if let Some(repo) =
repos.iter_mut().find(|r| r.name.as_deref() == Some(name))
{
repo.section = Some(section.to_string());
}
}
fn git(
name: &str,
path: &str,
slug: Option<&str>,
fav: bool,
info: GitInfo,
) -> Repo {
let mut repo = Repo::new(PathBuf::from(path));
repo.name = Some(name.to_string());
repo.slug = slug.map(str::to_string);
repo.fav = fav;
repo.kind = RepoKind::Git;
repo.example_git_info = Some(info);
repo
}
fn archived_git(name: &str, path: &str, info: GitInfo) -> Repo {
let mut repo = git(name, path, None, false, info);
repo.archived = true;
repo
}
fn archived_folder(name: &str, path: &str) -> Repo {
let mut repo = path_entry(name, path, None, None);
repo.archived = true;
repo
}
fn folder(
name: &str,
path: &str,
slug: Option<&str>,
section: Option<&str>,
) -> Repo {
path_entry(name, path, slug, section)
}
fn file(
name: &str,
path: &str,
slug: Option<&str>,
section: Option<&str>,
) -> Repo {
path_entry(name, path, slug, section)
}
fn path_entry(
name: &str,
path: &str,
slug: Option<&str>,
section: Option<&str>,
) -> Repo {
let mut repo = Repo::new(PathBuf::from(path));
repo.name = Some(name.to_string());
repo.slug = slug.map(str::to_string);
repo.section = section.map(str::to_string);
repo.kind = RepoKind::Path;
repo
}
fn clean(branch: &str, github: &str) -> GitInfo {
GitInfo {
valid: true,
current_branch_name: Some(branch.to_string()),
github_repo_name: Some(github.to_string()),
..GitInfo::default()
}
}
fn changes(branch: &str, github: &str, count: u32) -> GitInfo {
GitInfo {
changes: Some(count),
..clean(branch, github)
}
}
fn diverged(branch: &str, github: &str, ahead: u32, behind: u32) -> GitInfo {
GitInfo {
ahead: Some(ahead),
behind: Some(behind),
..clean(branch, github)
}
}
fn busy(branch: &str, github: &str, changes: u32, ahead: u32) -> GitInfo {
GitInfo {
changes: Some(changes),
ahead: Some(ahead),
..clean(branch, github)
}
}
fn behind(branch: &str, github: &str, count: u32) -> GitInfo {
GitInfo {
behind: Some(count),
..clean(branch, github)
}
}
fn broken() -> GitInfo {
GitInfo {
valid: false,
error: Some("not a git repository".to_string()),
..GitInfo::default()
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::*;
use crate::domain::filter::{Tab, belongs_to_tab};
use crate::domain::slug;
#[test]
fn demo_spans_all_four_tabs() {
let repos = repos();
for tab in Tab::ALL {
assert!(
repos.iter().any(|repo| belongs_to_tab(repo, tab)),
"no demo entry for tab {tab:?}"
);
}
}
#[test]
fn demo_slugs_are_valid_and_unique() {
let mut seen = HashSet::new();
for repo in repos() {
let Some(slug) = repo.slug else { continue };
assert!(
slug::validate_format(&slug).is_ok(),
"invalid demo slug: {slug}"
);
assert!(seen.insert(slug.clone()), "duplicate demo slug: {slug}");
}
}
#[test]
fn every_git_entry_has_example_info() {
for repo in repos() {
if repo.kind == RepoKind::Git {
assert!(
repo.example_git_info.is_some(),
"git demo entry {} lacks example_git_info",
repo.display_name()
);
}
}
}
#[test]
fn demo_includes_a_broken_repo() {
assert!(repos().iter().any(|repo| repo.example_error().is_some()));
}
#[test]
fn entry_sections_are_declared_per_kind() {
let path_declared: HashSet<String> = sections().into_iter().collect();
let git_declared: HashSet<String> =
git_sections().into_iter().collect();
for repo in repos() {
let Some(section) = repo.section else {
continue;
};
let declared = match repo.kind {
RepoKind::Git => &git_declared,
RepoKind::Path => &path_declared,
};
assert!(
declared.contains(§ion),
"section {section} not declared for {:?}",
repo.kind
);
}
}
}