diff --git a/Cargo.lock b/Cargo.lock index 8427b33..27dbd71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" diff --git a/src/methods/index.rs b/src/methods/index.rs index 2bd3ac8..592001f 100644 --- a/src/methods/index.rs +++ b/src/methods/index.rs @@ -1,8 +1,12 @@ -use std::{collections::BTreeMap, sync::Arc}; +use std::{cell::RefCell, sync::Arc}; use anyhow::Context; use askama::Template; -use axum::{response::IntoResponse, Extension}; +use axum::{ + response::{IntoResponse, Response}, + Extension, +}; +use itertools::{Either, Itertools}; use super::filters; use crate::{ @@ -12,28 +16,45 @@ use crate::{ #[derive(Template)] #[template(path = "index.html")] -pub struct View { - pub repositories: BTreeMap, Vec>, +pub struct View< + 'a, + Group: Iterator, + GroupIter: Iterator, +> { + // this type sig is a necessary evil unfortunately, because askama takes a reference + // to the data for rendering. + pub repositories: RefCell>>, +} + +impl<'a, Group, GroupIter> View<'a, Group, GroupIter> +where + Group: Iterator, + GroupIter: Iterator, +{ + fn take_iter(&self) -> Either> { + self.repositories.replace(Either::Right(std::iter::empty())) + } } pub async fn handle( Extension(db): Extension>, -) -> Result { - let mut repositories: BTreeMap, Vec> = BTreeMap::new(); - +) -> Result { let fetched = tokio::task::spawn_blocking(move || Repository::fetch_all(&db)) .await .context("Failed to join Tokio task")??; - for (k, v) in fetched { - // TODO: fixme - let mut split: Vec<_> = k.split('/').collect(); - split.pop(); - let key = Some(split.join("/")).filter(|v| !v.is_empty()); - - let k = repositories.entry(key).or_default(); - k.push(v); - } - - Ok(into_response(View { repositories })) + // rocksdb returned the keys already ordered for us so group_by is a nice + // operation we can use here to avoid writing into a map to group. though, + // now that i think about it it might act a little bit strangely when mixing + // root repositories and nested repositories. we're going to have to prefix + // root repositories with a null byte or something. i'll just leave this here + // as a TODO. + let repositories = fetched + .iter() + .group_by(|(k, _)| memchr::memrchr(b'/', k.as_bytes()).map_or("", |idx| &k[..idx])); + + Ok(into_response(View { + repositories: Either::Left(repositories.into_iter()).into(), + }) + .into_response()) } diff --git a/templates/index.html b/templates/index.html index e9762a6..2abf597 100644 --- a/templates/index.html +++ b/templates/index.html @@ -13,21 +13,21 @@ - {%- for (path, repositories) in repositories %} - {%- if let Some(path) = path %} + {%- for (path, repositories) in self.take_iter() %} + {%- if !path.is_empty() %} {{ path }} {%- endif -%} {%- for repository in repositories %} - {% set repository = repository.get() %} - + {% set repository = repository.1.get() %} + - + {{- repository.name -}} - + {%- if let Some(description) = repository.description.as_ref() -%} {{- description -}} {%- else -%} @@ -36,14 +36,14 @@ - + {%- if let Some(owner) = repository.owner.as_ref() -%} {{- owner -}} {%- endif -%} - +