bile |
| a simple self-hosted git server |
| git clone https://git.wayver.dev/bile |
| README | tree | log | refs |
Commit: f4279bc2ca927f6f651a20c5f0e7b5ebf76cc3a4 (tree)
Parent: a5fbdd41e56dc312103006ed0c7058b275594a83 (tree)
Author: wayverd
Date: 2026 M02 26, Thu 12:41:36 -0500
5 files changed; 56 insertions 40 deletions
diff --git a/TODO.md b/TODO.md
index 06a75bf..dbdedce 100644
`?raw` query maybe?
-## gitweb style project listing
-
-support something like gitwebs project listing config to allow for a custom repo listing layout
-
-might use the current config for it
-
-maybe something like this?
-
-```toml
-[[repo]]
-name = "bile"
-section = "web services"
-```
-
-could even have it be part of the repo config
-
-```ini
-[bile]
- section = "web services"
-```
-
## html sanitization for code view and readme
looking into using https://crates.io/crates/ammonia for this as there might be an injection possiblity with code and readme views
diff --git a/src/git/core.rs b/src/git/core.rs
index a774f68..435d0b7 100644
}
#[must_use]
- pub(crate) fn owner(&self) -> String {
+ pub(crate) fn owner(&self) -> Option<String> {
self.inner
.config()
.and_then(|config| config.get_string("gitweb.owner"))
- .unwrap_or_default()
+ .ok()
+ }
+
+ #[must_use]
+ pub(crate) fn section(&self) -> Option<String> {
+ self.inner
+ .config()
+ .and_then(|config| config.get_string("bile.section"))
+ .ok()
}
#[must_use]
diff --git a/src/handlers/index.rs b/src/handlers/index.rs
index 5aa93b0..3526b23 100644
#[template(path = "index.html")]
struct IndexTemplate<'a> {
config: &'a Config,
+ sections: Vec<Section>,
+}
+
+struct Section {
+ name: Option<String>,
repos: Vec<Repository>,
}
let Ok(read) = fs::read_dir(&state.config.project_root) else {
return Ok(Html(IndexTemplate {
config: &state.config,
- repos: Vec::new(),
+ sections: Vec::new(),
})
.into_response());
};
- let mut repos = Vec::new();
+ let mut sections = Vec::new();
for entry in read {
let entry = entry.context("failed to open directory entry")?;
continue;
}
- repos.push(repo);
+ let repo_section = repo.section();
+ let section = sections
+ .iter_mut()
+ .find(|s: &&mut Section| s.name == repo_section);
+ match section {
+ Some(section) => section.repos.push(repo),
+ None => sections.push(Section {
+ name: repo_section,
+ repos: vec![repo],
+ }),
+ }
+ }
+
+ sections.sort_by(|a, b| a.name.cmp(&b.name));
+ for section in &mut sections {
+ section.repos.sort_by(|a, b| a.name().cmp(&b.name()));
}
Ok(Html(IndexTemplate {
config: &state.config,
- repos,
+ sections,
})
.into_response())
}
diff --git a/src/utils/filters.rs b/src/utils/filters.rs
index 3643db8..c762854 100644
#[askama::filter_fn]
pub(crate) fn repo_owner(repo: &Repository, _: &dyn askama::Values) -> askama::Result<String> {
- Ok(repo.owner())
+ Ok(repo.owner().unwrap_or_default())
}
#[askama::filter_fn]
diff --git a/templates/index.html b/templates/index.html
index d1ac34b..c897e53 100644
{% extends "base.html" %}
+{% macro render_section(section) %}
+ {% if section.name.is_some() %}
+ <h2>{{ section.name.as_deref().unwrap_or("") }}</h2>
+ {% endif %}
+ {% for repo in section.repos %}
+ <table class="repo">
+ <tbody>
+ <tr>
+ <td class="repo-link"><a href="{{repo|repo_name|urlencode_strict}}">{{repo|repo_name}}</a></td>
+ <td class="repo-last-updated">last updated {{repo|last_modified|format_datetime("%Y-%m-%d")}}</td>
+ </tr>
+ <tr>
+ <td class="repo-description" colspan="2">{{repo|description}}</td>
+ </tr>
+ </tbody>
+ </table>
+ {% endfor %}
+{% endmacro %}
+
{% block content %}
<div>
- {% for repo in repos %}
- <table class="repo">
- <tbody>
- <tr>
- <td class="repo-link"><a href="{{repo|repo_name|urlencode_strict}}">{{repo|repo_name}}</a></td>
- <td class="repo-last-updated">last updated {{repo|last_modified|format_datetime("%Y-%m-%d")}}</td>
- </tr>
- <tr>
- <td class="repo-description" colspan="2">{{repo|description}}</td>
- </tr>
- </tbody>
- </table>
+ {% for section in sections %}
+ {{ render_section(section=section) }}
{% endfor %}
</div>
{% endblock %}=
\ No newline at end of file