git.rs: cleaning; render.rs: refactor to fit new git.rs library; test_render: initial commit; assets/*: update templates
This commit is contained in:
parent
7265642b8b
commit
806a9aebca
@ -7,6 +7,10 @@ edition = "2024"
|
||||
name = "frontend"
|
||||
path = "src/frontend.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "test_render"
|
||||
path = "src/test-render.rs"
|
||||
|
||||
[dependencies]
|
||||
git2 = { version = "0.20.2", default-features = false }
|
||||
serde = { version = "1.0.219", default-features = false, features = ["derive"] }
|
||||
|
@ -28,7 +28,7 @@
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="res/style.css">
|
||||
<link rel="stylesheet" href="res/martian.css">
|
||||
<link rel="stylesheet" href="res/mint.css">
|
||||
<script>
|
||||
/*
|
||||
@licstart The following is the entire license notice for the
|
||||
@ -78,14 +78,13 @@
|
||||
</div>
|
||||
|
||||
<div class=buttonListWrap>
|
||||
<ul class=buttonList>
|
||||
<li><a href="./">code</a></li>
|
||||
<li><a href="./commits">commits</a></li>
|
||||
<li><a href="./tags">tags</a></li>
|
||||
<li><a href="./tickets">issues {{ ticket_count }}</a></li>
|
||||
<li><a href="/">releases</a></li>
|
||||
<li><a href="/">settings</a></li>
|
||||
</ul>
|
||||
<div class="tabList end"><span>code</span>
|
||||
<a href="./commits">history</a>
|
||||
<a href="./tags">tags</a>
|
||||
<a href="./tickets">tickets {{ ticket_count }}</a></li>
|
||||
<a href="./releases">releases</a>
|
||||
<a href="./settings">settings</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<tr>
|
||||
<td><a class={{ entry.class }} href="/">{{ entry.path }}</a></td>
|
||||
<td><a class=commit href="./commit/{{ entry.last_commit }}">
|
||||
{{ entry.last_commit_short }}
|
||||
{{ entry.last_commit_message }}
|
||||
</a></td>
|
||||
<td><time datetime="{{ entry.last_commit_time }}">
|
||||
{{ entry.last_commit_time | date(format="%Y-%m-%d %H:%M")}}
|
||||
|
119
src/git.rs
119
src/git.rs
@ -49,15 +49,77 @@ use std::{
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use git2::{ Branch, Commit, Repository, Sort, TreeEntry };
|
||||
use git2::{ Branch, Commit, FileMode, Repository, Sort, TreeEntry };
|
||||
use tera::Context;
|
||||
|
||||
/* TODO: implement From<T> for Context where T: all Git* types */
|
||||
|
||||
pub enum GitEntryKind {
|
||||
Directory,
|
||||
File,
|
||||
Link,
|
||||
}
|
||||
|
||||
impl ToString for GitEntryKind {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
GitEntryKind::Directory => "directory".to_owned(),
|
||||
GitEntryKind::File => "file".to_owned(),
|
||||
GitEntryKind::Link => "link".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GitEntry {
|
||||
pub entries: Vec<GitEntry>,
|
||||
pub kind: GitEntryKind,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
impl GitEntry {
|
||||
fn new(entries: Vec<GitEntry>, kind: GitEntryKind, path: String) -> Self {
|
||||
GitEntry { entries, kind, path }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TreeEntry<'_>> for GitEntry {
|
||||
type Error = std::string::FromUtf8Error;
|
||||
|
||||
fn try_from(entry: TreeEntry) -> Result<Self, Self::Error> {
|
||||
let entries = Vec::new();
|
||||
let path = String::from_utf8(entry.name_bytes().to_vec())?;
|
||||
let kind = match entry.filemode() {
|
||||
m if m == <i32>::from(FileMode::Blob) => GitEntryKind::File,
|
||||
m if m == <i32>::from(FileMode::BlobExecutable) => GitEntryKind::File,
|
||||
m if m == <i32>::from(FileMode::BlobGroupWritable) => unreachable!(),
|
||||
m if m == <i32>::from(FileMode::Link) => GitEntryKind::File,
|
||||
m if m == <i32>::from(FileMode::Tree) => GitEntryKind::Directory,
|
||||
m if m == <i32>::from(FileMode::Commit) => GitEntryKind::File,
|
||||
m if m == <i32>::from(FileMode::Unreadable) => todo!(),
|
||||
_ => GitEntryKind::Directory,
|
||||
};
|
||||
|
||||
Ok(Self::new(entries, kind, path))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GitCommit {
|
||||
author: (Option<String>, Option<String>), // name, e-mail
|
||||
entries: Vec<GitEntry>,
|
||||
hash: String,
|
||||
message: Option<String>,
|
||||
short_hash: Option<String>,
|
||||
time: i64, // seconds since Unix epoch
|
||||
pub author: (Option<String>, Option<String>), // name, e-mail
|
||||
pub entries: Vec<GitEntry>,
|
||||
pub hash: String,
|
||||
pub message: Option<String>,
|
||||
pub short_hash: Option<String>,
|
||||
pub time: i64, // seconds since Unix epoch
|
||||
}
|
||||
|
||||
impl From<GitCommit> for Context {
|
||||
fn from(value: GitCommit) -> Self {
|
||||
let mut ctx = Context::new();
|
||||
|
||||
todo!("proc_macro for populating");
|
||||
|
||||
ctx
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Commit<'_>> for GitCommit {
|
||||
@ -83,28 +145,9 @@ impl TryFrom<Commit<'_>> for GitCommit {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GitEntry {
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl GitEntry {
|
||||
fn new(path: String) -> Self {
|
||||
GitEntry { path }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TreeEntry<'_>> for GitEntry {
|
||||
type Error = std::string::FromUtf8Error;
|
||||
|
||||
fn try_from(entry: TreeEntry) -> Result<Self, Self::Error> {
|
||||
let path = String::from_utf8(entry.name_bytes().to_vec())?;
|
||||
Ok(Self { path })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GitBranch {
|
||||
commits: Vec<GitCommit>,
|
||||
name: String,
|
||||
pub commits: Vec<GitCommit>,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
struct GitBranchWrapper<'a> {
|
||||
@ -124,7 +167,7 @@ impl TryFrom<GitBranchWrapper<'_>> for GitBranch {
|
||||
fn try_from(branch: GitBranchWrapper) -> Result<Self, Self::Error> {
|
||||
let name = String::from_utf8(branch.branch.name_bytes()?.to_vec())?;
|
||||
let repo = branch.repo;
|
||||
let branch_oid = branch.branch.get().target().unwrap();
|
||||
let branch_oid = branch.branch.get().resolve()?.target().unwrap();
|
||||
|
||||
let mut revwalk = repo.revwalk()?;
|
||||
revwalk.set_sorting(Sort::TOPOLOGICAL | Sort::TIME)?;
|
||||
@ -142,13 +185,15 @@ impl TryFrom<GitBranchWrapper<'_>> for GitBranch {
|
||||
}
|
||||
|
||||
pub struct GitRepo {
|
||||
branches: Vec<GitBranch>,
|
||||
name: String,
|
||||
owner: String,
|
||||
pub branches: Vec<GitBranch>,
|
||||
pub name: String,
|
||||
pub owner: String,
|
||||
}
|
||||
|
||||
impl GitRepo {
|
||||
fn open(path: PathBuf) -> Result<Self, Box<dyn Error>> {
|
||||
pub fn open(
|
||||
path: PathBuf, prefix: Option<String>
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
let repo = Repository::open(path.clone())?;
|
||||
|
||||
let branches = repo
|
||||
@ -169,8 +214,10 @@ impl GitRepo {
|
||||
*/
|
||||
|
||||
let full_path = path.clone().as_path().canonicalize()?;
|
||||
|
||||
let relative_path = full_path.strip_prefix("/var/mintee/repos")?;
|
||||
let relative_path = match prefix {
|
||||
Some(p) => full_path.strip_prefix(p)?.canonicalize()?,
|
||||
None => full_path,
|
||||
};
|
||||
|
||||
let owner = String::from_utf8(
|
||||
relative_path
|
||||
@ -194,7 +241,7 @@ impl GitRepo {
|
||||
Ok(GitRepo { branches, name, owner })
|
||||
}
|
||||
|
||||
fn get_remote_repo() { // for AP repos
|
||||
pub fn get_remote_repo() { // for AP repos
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
@ -16,56 +16,50 @@
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Mintee. If not, see https://www.gnu.org/licenses/.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*/
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{ self, Display, Formatter },
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use git2::Repository;
|
||||
use tera::{ Context, Tera };
|
||||
|
||||
trait ToContext {
|
||||
fn to_context(self) -> Result<Context, git2::Error>;
|
||||
#[non_exhaustive]
|
||||
pub enum PageKind {
|
||||
Code,
|
||||
Dashboard,
|
||||
Tickets,
|
||||
User,
|
||||
}
|
||||
|
||||
// TODO: move this data into backend.rs
|
||||
impl ToContext for Repository {
|
||||
fn to_context(self) -> Result<Context, git2::Error> {
|
||||
let repo = self.commondir();
|
||||
let _index = self.index()?;
|
||||
let head = self.head()?;
|
||||
let branch = if head.is_branch() {
|
||||
head.shorthand().unwrap()
|
||||
} else { "detached" };
|
||||
let head_commit = head.peel_to_commit()?;
|
||||
let entries = get_entries(&self)?;
|
||||
let committer = head_commit.committer().name().unwrap().to_owned();
|
||||
let _author = head_commit.author().name().unwrap().to_owned();
|
||||
impl PageKind {
|
||||
pub fn render_page(&self, ctx: Context) -> Result<String, Box<dyn Error>> {
|
||||
let page_dir = self.to_string();
|
||||
let template = String::from_utf8(Path::new(&page_dir)
|
||||
.to_path_buf()
|
||||
.as_mut_os_string()
|
||||
.as_encoded_bytes()
|
||||
.to_vec()
|
||||
)?;
|
||||
|
||||
let mut ctx = Context::new();
|
||||
|
||||
// stub until we have database
|
||||
ctx.insert("user", "anon");
|
||||
ctx.insert("site", "TiB.");
|
||||
ctx.insert("notif_count", "");
|
||||
ctx.insert("ticket_count", "(47)");
|
||||
ctx.insert("owner", &committer);
|
||||
ctx.insert("repo", repo);
|
||||
ctx.insert("branch", &branch);
|
||||
ctx.insert("directory", repo);
|
||||
ctx.insert("entries", &entries);
|
||||
ctx.insert("readme_content", "this is a readme");
|
||||
|
||||
Ok(ctx)
|
||||
let tera = Tera::new(&page_dir)?;
|
||||
Ok(tera.render(&template, &ctx)?)
|
||||
}
|
||||
}
|
||||
|
||||
fn render_path(path: PathBuf) -> tera::Result<String> {
|
||||
let tera = Tera::new("./assets/templates/repo/*")?;
|
||||
let repo = Repository::discover(path).unwrap();
|
||||
let context = repo.to_context().unwrap();
|
||||
impl Display for PageKind {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
use PageKind::*;
|
||||
|
||||
tera.render("code.html", &context)
|
||||
let path = match self {
|
||||
Code => "repo/code.html",
|
||||
Dashboard => "dashboard.html",
|
||||
Tickets => "repo/tickets.html",
|
||||
User => "user.html",
|
||||
};
|
||||
|
||||
write!(f, "./assets/templates/{}", path)
|
||||
}
|
||||
}
|
||||
|
85
src/test-render.rs
Normal file
85
src/test-render.rs
Normal file
@ -0,0 +1,85 @@
|
||||
/* see render.rs for licensing information */
|
||||
|
||||
use std::{
|
||||
env::{ args, current_dir },
|
||||
error::Error,
|
||||
};
|
||||
|
||||
use serde::Serialize;
|
||||
use tera::{ Context, Tera };
|
||||
|
||||
#[path="git.rs"]
|
||||
mod git;
|
||||
|
||||
use git::GitRepo;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SampleEntry {
|
||||
class: String,
|
||||
last_commit_message: String,
|
||||
last_commit: String,
|
||||
last_commit_time: i64,
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl From<GitRepo> for Context {
|
||||
fn from(repo: GitRepo) -> Self {
|
||||
let mut ctx = Context::new();
|
||||
|
||||
let directory = format!("{}/{}", repo.owner, repo.name);
|
||||
|
||||
let main_branch = repo.branches
|
||||
.iter()
|
||||
.find(|b| b.name == "main")
|
||||
.unwrap();
|
||||
|
||||
let latest_commit = &main_branch.commits[0];
|
||||
let hash = latest_commit.hash.clone();
|
||||
|
||||
let mut entries = Vec::new();
|
||||
|
||||
for e in latest_commit.entries.iter() {
|
||||
let entry = SampleEntry {
|
||||
class: e.kind.to_string(),
|
||||
last_commit: hash.clone(),
|
||||
last_commit_message: latest_commit.message.clone().unwrap(),
|
||||
last_commit_time: latest_commit.time,
|
||||
path: e.path.clone(),
|
||||
};
|
||||
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
/* stubs til we have a real database */
|
||||
ctx.insert("user", "anon");
|
||||
ctx.insert("site", "TiB.");
|
||||
ctx.insert("notif_count", "");
|
||||
ctx.insert("ticket_count", "(47)");
|
||||
|
||||
ctx.insert("readme_content", "this is a readme");
|
||||
|
||||
ctx.insert("owner", &repo.owner);
|
||||
ctx.insert("branch", &main_branch.name);
|
||||
ctx.insert("repo", &repo.name);
|
||||
ctx.insert("directory", &directory);
|
||||
ctx.insert("entries", &entries);
|
||||
|
||||
ctx
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
if let Some(templates) = args().collect::<Vec<_>>().get(1) {
|
||||
let tera = Tera::new(templates.as_str())?;
|
||||
let ctx = Context::from(
|
||||
GitRepo::open(current_dir()?.to_path_buf(), None)?
|
||||
);
|
||||
|
||||
println!("{}", tera.render("code.html", &ctx)?);
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
eprintln!("Usage: {} template_glob", args().collect::<Vec<_>>()[0]);
|
||||
std::process::exit(64);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user