Files
mintee/src/backend/render.rs

194 lines
4.5 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (c) 20252026 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This file is part of Mintee.
*
* Mintee is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Mintee is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Mintee. If not, see https://www.gnu.org/licenses/.
*/
use std::{
error::Error,
fmt::{ self, Display, Formatter },
io::Write,
path::PathBuf,
};
use gix::open;
use tera::Tera;
use crate::backend::{
MINTEE_DIR,
MINTEE_ASSETS,
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 {
/// Main homepage of the site
Dashboard,
/// Project page
Project,
/// Repository page
Repo(Repo),
/// Settings page for a user; Should not be used if there is no
/// authenticated user
Settings,
/// 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,
}
impl Display for PageKind {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
use PageKind::*;
let path = match self {
Dashboard => "dashboard.html",
Project => "project.html",
Repo(_) => "repo/repo.html",
Settings => "user/settings.html",
Tickets(_) => "repo/tickets.html",
User => "user.html",
Invalid => todo!("generate error pages based on error type"),
};
write!(f, "{}", path)
}
}
#[derive(Debug, Clone)]
pub struct Page {
/// 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<W: Write>(
&self, dest: &mut W
) -> Result<(), Box<dyn Error>> {
let assets = format!("{}/templates/**/*", MINTEE_ASSETS);
let tera = Tera::new(&assets)?;
let mut page_template = self.kind.to_string();
use PageKind::*;
let ctx = match self.kind.clone() {
Dashboard | Project | Settings | Tickets(_) | User | Invalid => {
todo!()
},
Repo(r) => {
let mut repo_path = PathBuf::from(MINTEE_DIR.to_string());
repo_path.push(r.entity);
repo_path.push(r.name);
let repo = open(repo_path)?;
/* 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()?
},
};
Ok(dest.write_all(
tera.render(&page_template, &ctx)?.as_bytes()
)?)
}
}