src/backend/git.rs, src/backend/render.rs: improvements to API; test_render(1): modifications to fit new API

This commit is contained in:
2026-05-06 11:00:02 +00:00
parent 5940e203b9
commit b11134c754
3 changed files with 143 additions and 42 deletions

View File

@@ -31,14 +31,21 @@ use tera::Context;
use time_format::format_iso8601_utc;
#[derive(Default, Serialize)]
/// Directory Entry inside git
struct Entry {
/// Kind of this entry; file or directory
class: String,
/// Latest commit for this entry
last_commit: String,
/// Message of the latest commit for this entry
last_commit_message: String,
/// Time of the latest commit for this entry
last_commit_time: String,
/// Path to this entry
path: String,
}
/// return a vector of entries from any given git Tree
fn get_entries(tree: Tree) -> Result<Vec<Entry>, Box<dyn Error>> {
let mut entries = Vec::new();
let order = Sorting::ByCommitTime(CommitTimeOrder::NewestFirst);
@@ -54,6 +61,10 @@ fn get_entries(tree: Tree) -> Result<Vec<Entry>, Box<dyn Error>> {
_ => "file",
}.to_owned();
/* TODO: rewrite to not assume we are at the root of the repository, to
* compare entries in each commit to determine the last commit to touch
* the file, and to get the last commit to touch any file in a subtree
* and use that as the commit for that subtree */
for i in tree.repo.head_commit()?.ancestors().sorting(order).all()? {
let commit = i?.id().object()?.peel_to_commit()?;
let path_slice: &[u8] = entry_t.path.as_ref();
@@ -77,11 +88,14 @@ fn get_entries(tree: Tree) -> Result<Vec<Entry>, Box<dyn Error>> {
Ok(entries)
}
/// Converts a git type to a Tera Context
pub trait ToContext {
type Error;
fn to_context(&self) -> Result<Context, Self::Error>;
}
/// Implementation of git Object to Tera Context
impl ToContext for Object<'_> {
type Error = Box<dyn Error>;
@@ -94,12 +108,11 @@ impl ToContext for Object<'_> {
.shorten()
.to_string();
use gix::object::Kind;
use gix::object::Kind::*;
match self.kind {
Kind::Blob => todo!("need sasha to make a file page"),
Kind::Tag => todo!("idk what this needs to look like"),
Kind::Commit => todo!("need sasha to make a commit page"),
Kind::Tree => {
Blob => todo!("need sasha to make a file page"),
Commit | Tag => todo!("need sasha to make a commit page"),
Tree => {
let tree = self.clone().peel_to_tree()?;
let entries = get_entries(tree)?;
let repo = self.repo;

View File

@@ -22,6 +22,7 @@ use std::{
error::Error,
fmt::{ self, Display, Formatter },
io::Write,
path::PathBuf,
};
use gix::open;
@@ -29,18 +30,63 @@ use tera::Tera;
use crate::backend::git::ToContext;
#[derive(Debug, Clone)]
/// A revision for which a repository page may be rendered
pub enum Revision {
Branch(String),
Commit(String),
Tag(String),
}
#[derive(Debug, Clone)]
/// A type representing the git repository requested from the frontend
pub struct Repo {
/// The user or project this repository is associated with
entity: String,
/// The name of this repository (equivalent to its path under its entity)
name: String,
/// The subtree of the repository the page should be rendered for
path: Option<String>,
/// The revision name that should be checked out when rendering pages
revision: Option<Revision>,
}
impl Repo {
/// Instanatiate a new Repo
pub fn new(
entity: String,
name: String,
revision: Option<Revision>,
path: Option<String>,
) -> Self {
Self {
entity,
name,
path,
revision,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
/// Type of page
pub enum PageKind {
Code,
/// Main homepage of the site
Dashboard,
/// Project page
Project,
Repo,
RepoSubDir,
/// Repository page
Repo(Repo),
/// Settings page for a user; Should not be used if there is no
/// authenticated user
Settings,
Tickets,
/// Tickets page for a repository
Tickets(Repo),
/// User page
User,
/* TODO: silt exports a generic frontend non-success status trait */
/// Page does not exist or is not well-formed
Invalid,
}
@@ -49,13 +95,11 @@ impl Display for PageKind {
use PageKind::*;
let path = match self {
Code => "repo/code.html",
Dashboard => "dashboard.html",
Project => "project.html",
Repo => "repo/repo.html",
RepoSubDir => "repo/repo.html",
Repo(_) => "repo/repo.html",
Settings => "user/settings.html",
Tickets => "repo/tickets.html",
Tickets(_) => "repo/tickets.html",
User => "user.html",
Invalid => todo!("generate error pages based on error type"),
};
@@ -64,43 +108,79 @@ impl Display for PageKind {
}
}
#[derive(Debug, Clone)]
pub struct Page {
pub kind: PageKind,
pub path: String,
pub branch: Option<String>,
pub commit: Option<String>,
pub tag: Option<String>,
pub user: Option<String>,
/// Type of page
kind: PageKind,
/// Currently-authenticated user for whose perspective pages will be
/// rendered
user: Option<String>,
}
impl Page {
/// Instantiate a new page
pub fn new(
kind: PageKind,
user: Option<String>,
) -> Self {
Self {
kind,
user,
}
}
/// Render a page to a Write-able stream
pub fn render(
&self, mut dest: Box<dyn Write>
) -> Result<(), Box<dyn Error>> {
/* TODO: replace ./assets/ with the actual templates directory,
* i.e. /usr/local/share/mintee/ */
let tera = Tera::new("./assets/templates/**/*")?;
let mut page_template = self.kind.to_string();
use PageKind::*;
let ctx = match self.kind {
Code => todo!(),
Dashboard | Project | Settings | Tickets | User | Invalid => {
let ctx = match self.kind.clone() {
Dashboard | Project | Settings | Tickets(_) | User | Invalid => {
todo!()
},
Repo => {
open(&self.path)?
/*.rev_parse_single("@")?*/
.head_tree()?
.id()
.object()?
.to_context()?
Repo(r) => {
let repo = open(format!("{}/{}", r.entity, r.name))?;
/* TODO: handle specifying a commit, tag, or branch */
let head_tree = repo.head_tree()?;
let object;
if let Some(dir) = r.path {
let path = PathBuf::from(dir);
object = head_tree
.lookup_entry(path.iter().map(|c| {
/* unwrap() is okay because self.path is guaranteed
* to be valid UTF-8 */
String::from_utf8(
c.to_os_string().into_encoded_bytes()
).unwrap()
}))?
/* replace this expect() with proper error handling */
.expect("attempted to render nonexistent page")
.to_owned()
.id()
.object()?;
use gix::object::Kind::*;
page_template = match object.kind {
Blob => "repo/file.html",
Commit | Tag => "repo/commit.html",
Tree => "repo/repo.html",
}.to_string();
} else { object = head_tree.id().object()?; }
object.to_context()?
},
RepoSubDir => todo!(),
};
Ok(dest.write_all(
tera.render(&self.kind.to_string(), &ctx)?.as_bytes()
tera.render(&page_template, &ctx)?.as_bytes()
)?)
}
}

View File

@@ -24,21 +24,29 @@ use std::{
io::stdout,
};
use mintee::backend::render::{ Page, PageKind };
use mintee::backend::render::{ Page, PageKind, Repo };
fn main() -> Result<(), Box<dyn Error>> {
let path = String::from_utf8(
current_dir()?.into_os_string().into_encoded_bytes()
let path = current_dir()?;
let name = String::from_utf8(
path.file_name().unwrap().to_os_string().into_encoded_bytes()
)?;
let entity = String::from_utf8(
path.parent().unwrap().as_os_str().to_os_string().into_encoded_bytes()
)?;
let page = Page {
kind: PageKind::Repo,
path,
branch: None,
commit: None,
tag: None,
user: None,
};
let repo = Repo::new(
entity,
name,
None,
Some("src".to_string()),
);
let page = Page::new(
PageKind::Repo(repo),
None,
);
page.render(Box::new(stdout()))
}