src/backend/git.rs, src/backend/render.rs: improvements to API; test_render(1): modifications to fit new API
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user