Krolyxon
Commit 4e844bb

ferreniumsrchandlerssummary.rs

187 lines · 5.3 KB · Rust

1//! `/{repo}` — root tree, README and a rail with stats, clone URLs and the
2//! latest commit.
3
4use askama::Template;
5use axum::http::StatusCode;
6use axum::response::Response;
7
8use super::repo::{Active, Ctx, RepoNav, set_cache_headers, short};
9use super::tree::{Readme, TreeRow, listing, readme, rows};
10use super::{Layout, render};
11use crate::cache::Key;
12use crate::git::refs::{RefKind, Rev};
13use crate::git::{self, CommitInfo};
14use crate::render::time;
15
16pub struct Stat {
17 pub label: &'static str,
18 pub value: String,
19 pub title: Option<String>,
20 pub url: Option<String>,
21}
22
23pub struct CloneUrl {
24 pub label: String,
25 pub url: String,
26}
27
28pub struct Latest {
29 pub subject: String,
30 pub author: String,
31 pub age: String,
32 pub iso: String,
33 pub id: String,
34 pub url: String,
35}
36
37#[derive(Template)]
38#[template(path = "summary.html")]
39pub struct SummaryPage {
40 pub layout: Layout,
41 pub nav: RepoNav,
42 pub name: String,
43 pub description: Option<String>,
44 pub stats: Vec<Stat>,
45 pub clone_urls: Vec<CloneUrl>,
46 pub latest: Option<Latest>,
47 /// `None` for an empty repository.
48 pub rows: Option<Vec<TreeRow>>,
49 pub tree_url: String,
50 pub readme: Option<Readme>,
51}
52
53pub fn page(ctx: &Ctx) -> Response {
54 let meta = &ctx.repo.meta;
55 let rev = meta
56 .default_branch
57 .clone()
58 .zip(meta.head)
59 .map(|(name, commit)| Rev {
60 name,
61 kind: RefKind::Branch,
62 commit,
63 });
64 let etag = ctx.etag(("summary", rev.as_ref().map(|r| r.commit)));
65 if let Some(res) = ctx.not_modified(&etag, false) {
66 return res;
67 }
68 let mut page = SummaryPage {
69 layout: Layout::new(ctx.state, ctx.config, Some(&ctx.repo.name)),
70 nav: ctx.nav(Active::Summary, rev.as_ref(), |name| {
71 ctx.url("tree", name, b"")
72 }),
73 name: ctx.repo.name.clone(),
74 description: meta.description.clone(),
75 stats: Vec::new(),
76 clone_urls: clone_urls(ctx),
77 latest: None,
78 rows: None,
79 tree_url: String::new(),
80 readme: None,
81 };
82
83 if let Some(rev) = &rev {
84 let result = (|| -> anyhow::Result<()> {
85 let commits = ctx.state.cache.count(Key::CommitCount(rev.commit), || {
86 git::commit_count(&ctx.git, rev.commit)
87 })?;
88 let info = git::commit_info(&ctx.git, rev.commit)?;
89 page.stats = stats(ctx, Some(commits));
90 page.latest = Some(latest(ctx, &info));
91 let root = git::tree::lookup(&ctx.git, rev.commit, b"")?;
92 if let Some(git::tree::PathTarget::Tree(tree_id)) = root {
93 page.rows = Some(rows(ctx, rev, b"", &listing(ctx, tree_id)?));
94 }
95 page.tree_url = ctx.url("tree", &rev.name, b"");
96 page.readme = meta.readme.as_ref().and_then(|r| {
97 let (dir, name) = r.path.rsplit_once('/').unwrap_or(("", &r.path));
98 readme(ctx, rev, dir.as_bytes(), name, r.oid)
99 });
100 Ok(())
101 })();
102 if let Err(e) = result {
103 return ctx.internal_error(e);
104 }
105 } else {
106 page.stats = stats(ctx, None);
107 }
108
109 let mut res = render(StatusCode::OK, &page);
110 set_cache_headers(&mut res, &etag, false);
111 res
112}
113
114fn stats(ctx: &Ctx, commits: Option<u64>) -> Vec<Stat> {
115 let meta = &ctx.repo.meta;
116 let mut stats = vec![
117 Stat {
118 label: "Commits",
119 value: commits.unwrap_or(0).to_string(),
120 title: None,
121 url: commits.map(|_| format!("{}/log", ctx.repo_url())),
122 },
123 Stat {
124 label: "Branches",
125 value: meta.branches.to_string(),
126 title: None,
127 url: None,
128 },
129 Stat {
130 label: "Tags",
131 value: meta.tags.to_string(),
132 title: None,
133 url: None,
134 },
135 ];
136 if let Some(t) = meta.last_activity {
137 stats.push(Stat {
138 label: "Updated",
139 value: time::relative(t, time::now()),
140 title: Some(time::iso(t)),
141 url: None,
142 });
143 }
144 if let Some(owner) = &meta.owner {
145 stats.push(Stat {
146 label: "Owner",
147 value: owner.clone(),
148 title: None,
149 url: None,
150 });
151 }
152 stats
153}
154
155fn latest(ctx: &Ctx, info: &CommitInfo) -> Latest {
156 let id = info.id.to_string();
157 Latest {
158 subject: info.subject.clone(),
159 author: ctx
160 .mailmap()
161 .resolve(&info.author_name, &info.author_email)
162 .0,
163 age: time::relative(info.committer_time, time::now()),
164 iso: time::iso(info.committer_time),
165 url: ctx.commit_url(&info.id),
166 id: short(&id),
167 }
168}
169
170/// `site.clone_urls` with `{name}` filled in, labeled by protocol.
171fn clone_urls(ctx: &Ctx) -> Vec<CloneUrl> {
172 ctx.config
173 .site
174 .clone_urls
175 .iter()
176 .map(|template| {
177 let url = template.replace("{name}", &ctx.repo.name);
178 let label = match url.split_once("://") {
179 Some(("ssh", _)) => "SSH".to_string(),
180 Some((scheme, _)) => scheme.to_ascii_uppercase(),
181 // scp-like `user@host:path`
182 None => "SSH".to_string(),
183 };
184 CloneUrl { label, url }
185 })
186 .collect()
187}