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;
|
use time_format::format_iso8601_utc;
|
||||||
|
|
||||||
#[derive(Default, Serialize)]
|
#[derive(Default, Serialize)]
|
||||||
|
/// Directory Entry inside git
|
||||||
struct Entry {
|
struct Entry {
|
||||||
|
/// Kind of this entry; file or directory
|
||||||
class: String,
|
class: String,
|
||||||
|
/// Latest commit for this entry
|
||||||
last_commit: String,
|
last_commit: String,
|
||||||
|
/// Message of the latest commit for this entry
|
||||||
last_commit_message: String,
|
last_commit_message: String,
|
||||||
|
/// Time of the latest commit for this entry
|
||||||
last_commit_time: String,
|
last_commit_time: String,
|
||||||
|
/// Path to this entry
|
||||||
path: String,
|
path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// return a vector of entries from any given git Tree
|
||||||
fn get_entries(tree: Tree) -> Result<Vec<Entry>, Box<dyn Error>> {
|
fn get_entries(tree: Tree) -> Result<Vec<Entry>, Box<dyn Error>> {
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
let order = Sorting::ByCommitTime(CommitTimeOrder::NewestFirst);
|
let order = Sorting::ByCommitTime(CommitTimeOrder::NewestFirst);
|
||||||
@@ -54,6 +61,10 @@ fn get_entries(tree: Tree) -> Result<Vec<Entry>, Box<dyn Error>> {
|
|||||||
_ => "file",
|
_ => "file",
|
||||||
}.to_owned();
|
}.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()? {
|
for i in tree.repo.head_commit()?.ancestors().sorting(order).all()? {
|
||||||
let commit = i?.id().object()?.peel_to_commit()?;
|
let commit = i?.id().object()?.peel_to_commit()?;
|
||||||
let path_slice: &[u8] = entry_t.path.as_ref();
|
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)
|
Ok(entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a git type to a Tera Context
|
||||||
pub trait ToContext {
|
pub trait ToContext {
|
||||||
type Error;
|
type Error;
|
||||||
fn to_context(&self) -> Result<Context, Self::Error>;
|
fn to_context(&self) -> Result<Context, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Implementation of git Object to Tera Context
|
||||||
impl ToContext for Object<'_> {
|
impl ToContext for Object<'_> {
|
||||||
type Error = Box<dyn Error>;
|
type Error = Box<dyn Error>;
|
||||||
|
|
||||||
@@ -94,12 +108,11 @@ impl ToContext for Object<'_> {
|
|||||||
.shorten()
|
.shorten()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
use gix::object::Kind;
|
use gix::object::Kind::*;
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Kind::Blob => todo!("need sasha to make a file page"),
|
Blob => todo!("need sasha to make a file page"),
|
||||||
Kind::Tag => todo!("idk what this needs to look like"),
|
Commit | Tag => todo!("need sasha to make a commit page"),
|
||||||
Kind::Commit => todo!("need sasha to make a commit page"),
|
Tree => {
|
||||||
Kind::Tree => {
|
|
||||||
let tree = self.clone().peel_to_tree()?;
|
let tree = self.clone().peel_to_tree()?;
|
||||||
let entries = get_entries(tree)?;
|
let entries = get_entries(tree)?;
|
||||||
let repo = self.repo;
|
let repo = self.repo;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use std::{
|
|||||||
error::Error,
|
error::Error,
|
||||||
fmt::{ self, Display, Formatter },
|
fmt::{ self, Display, Formatter },
|
||||||
io::Write,
|
io::Write,
|
||||||
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
use gix::open;
|
use gix::open;
|
||||||
@@ -29,18 +30,63 @@ use tera::Tera;
|
|||||||
|
|
||||||
use crate::backend::git::ToContext;
|
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]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
/// Type of page
|
||||||
pub enum PageKind {
|
pub enum PageKind {
|
||||||
Code,
|
/// Main homepage of the site
|
||||||
Dashboard,
|
Dashboard,
|
||||||
|
/// Project page
|
||||||
Project,
|
Project,
|
||||||
Repo,
|
/// Repository page
|
||||||
RepoSubDir,
|
Repo(Repo),
|
||||||
|
/// Settings page for a user; Should not be used if there is no
|
||||||
|
/// authenticated user
|
||||||
Settings,
|
Settings,
|
||||||
Tickets,
|
/// Tickets page for a repository
|
||||||
|
Tickets(Repo),
|
||||||
|
/// User page
|
||||||
User,
|
User,
|
||||||
/* TODO: silt exports a generic frontend non-success status trait */
|
/* TODO: silt exports a generic frontend non-success status trait */
|
||||||
|
/// Page does not exist or is not well-formed
|
||||||
Invalid,
|
Invalid,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,13 +95,11 @@ impl Display for PageKind {
|
|||||||
use PageKind::*;
|
use PageKind::*;
|
||||||
|
|
||||||
let path = match self {
|
let path = match self {
|
||||||
Code => "repo/code.html",
|
|
||||||
Dashboard => "dashboard.html",
|
Dashboard => "dashboard.html",
|
||||||
Project => "project.html",
|
Project => "project.html",
|
||||||
Repo => "repo/repo.html",
|
Repo(_) => "repo/repo.html",
|
||||||
RepoSubDir => "repo/repo.html",
|
|
||||||
Settings => "user/settings.html",
|
Settings => "user/settings.html",
|
||||||
Tickets => "repo/tickets.html",
|
Tickets(_) => "repo/tickets.html",
|
||||||
User => "user.html",
|
User => "user.html",
|
||||||
Invalid => todo!("generate error pages based on error type"),
|
Invalid => todo!("generate error pages based on error type"),
|
||||||
};
|
};
|
||||||
@@ -64,43 +108,79 @@ impl Display for PageKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
pub kind: PageKind,
|
/// Type of page
|
||||||
pub path: String,
|
kind: PageKind,
|
||||||
pub branch: Option<String>,
|
/// Currently-authenticated user for whose perspective pages will be
|
||||||
pub commit: Option<String>,
|
/// rendered
|
||||||
pub tag: Option<String>,
|
user: Option<String>,
|
||||||
pub user: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
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(
|
pub fn render(
|
||||||
&self, mut dest: Box<dyn Write>
|
&self, mut dest: Box<dyn Write>
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
/* TODO: replace ./assets/ with the actual templates directory,
|
/* TODO: replace ./assets/ with the actual templates directory,
|
||||||
* i.e. /usr/local/share/mintee/ */
|
* i.e. /usr/local/share/mintee/ */
|
||||||
let tera = Tera::new("./assets/templates/**/*")?;
|
let tera = Tera::new("./assets/templates/**/*")?;
|
||||||
|
let mut page_template = self.kind.to_string();
|
||||||
|
|
||||||
use PageKind::*;
|
use PageKind::*;
|
||||||
let ctx = match self.kind {
|
let ctx = match self.kind.clone() {
|
||||||
Code => todo!(),
|
Dashboard | Project | Settings | Tickets(_) | User | Invalid => {
|
||||||
Dashboard | Project | Settings | Tickets | User | Invalid => {
|
|
||||||
todo!()
|
todo!()
|
||||||
},
|
},
|
||||||
Repo => {
|
Repo(r) => {
|
||||||
open(&self.path)?
|
let repo = open(format!("{}/{}", r.entity, r.name))?;
|
||||||
/*.rev_parse_single("@")?*/
|
/* TODO: handle specifying a commit, tag, or branch */
|
||||||
.head_tree()?
|
let head_tree = repo.head_tree()?;
|
||||||
.id()
|
let object;
|
||||||
.object()?
|
|
||||||
.to_context()?
|
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(
|
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,
|
io::stdout,
|
||||||
};
|
};
|
||||||
|
|
||||||
use mintee::backend::render::{ Page, PageKind };
|
use mintee::backend::render::{ Page, PageKind, Repo };
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let path = String::from_utf8(
|
let path = current_dir()?;
|
||||||
current_dir()?.into_os_string().into_encoded_bytes()
|
|
||||||
|
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 {
|
let repo = Repo::new(
|
||||||
kind: PageKind::Repo,
|
entity,
|
||||||
path,
|
name,
|
||||||
branch: None,
|
None,
|
||||||
commit: None,
|
Some("src".to_string()),
|
||||||
tag: None,
|
);
|
||||||
user: None,
|
|
||||||
};
|
let page = Page::new(
|
||||||
|
PageKind::Repo(repo),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
page.render(Box::new(stdout()))
|
page.render(Box::new(stdout()))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user