Krolyxon
Commit 55c1225

ferreniumsrchandlerssummary.rs

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