add css
This commit is contained in:
parent
dd446e25a1
commit
a0f56e58cf
15 changed files with 715 additions and 380 deletions
199
Cargo.lock
generated
199
Cargo.lock
generated
|
@ -30,6 +30,15 @@ version = "0.4.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
|
||||
dependencies = [
|
||||
"nodrop",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ascii"
|
||||
version = "0.9.3"
|
||||
|
@ -95,6 +104,12 @@ version = "0.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0017894339f586ccb943b01b9555de56770c11cda818e7e3d8bd93f4ed7f46e"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.4"
|
||||
|
@ -180,6 +195,23 @@ dependencies = [
|
|||
"unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "comrak"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e17058cc536cf290563e88787d7b9e6030ce4742943017cc2ffb71f88034021c"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"entities",
|
||||
"lazy_static",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"regex",
|
||||
"twoway",
|
||||
"typed-arena",
|
||||
"unicode_categories",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.4.2"
|
||||
|
@ -228,6 +260,12 @@ version = "1.5.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
||||
|
||||
[[package]]
|
||||
name = "entities"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
|
@ -239,12 +277,15 @@ name = "fedhub"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"comrak",
|
||||
"futures",
|
||||
"git2",
|
||||
"hyper",
|
||||
"lazy_static",
|
||||
"packer",
|
||||
"parking_lot",
|
||||
"redis",
|
||||
"rsass",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
|
@ -382,6 +423,12 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.5"
|
||||
|
@ -579,6 +626,19 @@ version = "1.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7043aa5c05dd34fb73b47acb8c3708eac428de4545ea3682ed2f11293ebd890"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"cfg-if",
|
||||
"rustc_version",
|
||||
"ryu",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.68"
|
||||
|
@ -726,6 +786,23 @@ dependencies = [
|
|||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6"
|
||||
dependencies = [
|
||||
"lexical-core",
|
||||
"memchr",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.42"
|
||||
|
@ -736,6 +813,17 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.11"
|
||||
|
@ -761,6 +849,29 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||
|
||||
[[package]]
|
||||
name = "packer"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "546814b26ef40511a1475217e799571ce49c2154fe41bd0b61766cb5c2dfb9a9"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"packer_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packer_derive"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb687c5bdc13808a293f1077d65907aa0c169f694bf613faa2f8ef8357d5eb3a"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.10.2"
|
||||
|
@ -889,9 +1000,9 @@ checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "0.4.12"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7"
|
||||
checksum = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
|
@ -902,9 +1013,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "0.4.12"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de"
|
||||
checksum = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1016,6 +1127,29 @@ version = "0.6.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
|
||||
|
||||
[[package]]
|
||||
name = "rsass"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c78e9b85b8816881cad9aa1142eec337020cfd79c6983e338811f5c521a80463"
|
||||
dependencies = [
|
||||
"bytecount",
|
||||
"lazy_static",
|
||||
"nom",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.3"
|
||||
|
@ -1037,6 +1171,21 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.105"
|
||||
|
@ -1126,6 +1275,12 @@ dependencies = [
|
|||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
|
@ -1134,9 +1289,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
|||
|
||||
[[package]]
|
||||
name = "structopt"
|
||||
version = "0.3.12"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8faa2719539bbe9d77869bfb15d4ee769f99525e707931452c97b693b3f159d"
|
||||
checksum = "ff6da2e8d107dfd7b74df5ef4d205c6aebee0706c647f6bc6a2d5789905c00fb"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"lazy_static",
|
||||
|
@ -1145,9 +1300,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "structopt-derive"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f88b8e18c69496aad6f9ddf4630dd7d585bcaf765786cb415b9aec2fe5a0430"
|
||||
checksum = "a489c87c08fbaf12e386665109dd13470dcc9c4583ea3e10dd2b4523e5ebd9ac"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
|
@ -1313,6 +1468,22 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382"
|
||||
|
||||
[[package]]
|
||||
name = "twoway"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"unchecked-index",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-arena"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.12.0"
|
||||
|
@ -1325,6 +1496,12 @@ version = "0.1.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||
|
||||
[[package]]
|
||||
name = "unchecked-index"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
|
||||
|
||||
[[package]]
|
||||
name = "unic-char-property"
|
||||
version = "0.9.0"
|
||||
|
@ -1411,6 +1588,12 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_categories"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
||||
|
||||
[[package]]
|
||||
name = "unreachable"
|
||||
version = "1.0.0"
|
||||
|
|
21
Cargo.toml
21
Cargo.toml
|
@ -7,18 +7,23 @@ edition = "2018"
|
|||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.28"
|
||||
comrak = "0.7.0"
|
||||
futures = { version = "0.3.4", default-features = false }
|
||||
git2 = { version = "0.13.0", default-features = false }
|
||||
hyper = "0.13.5"
|
||||
lazy_static = "1.4.0"
|
||||
parking_lot = "0.10.2"
|
||||
redis = "0.15.1"
|
||||
serde = "1.0.105"
|
||||
serde_derive = "1.0.105"
|
||||
serde_json = "1.0.48"
|
||||
structopt = "0.3.12"
|
||||
structopt = "0.3.13"
|
||||
tera = "1.2.0"
|
||||
toml = "0.5.6"
|
||||
tokio = { version = "0.2.18", features = ["full"] }
|
||||
anyhow = "1.0.28"
|
||||
toml = "0.5.6"
|
||||
walkdir = "2.3.1"
|
||||
lazy_static = "1.4.0"
|
||||
parking_lot = "0.10.2"
|
||||
hyper = "0.13.5"
|
||||
redis = "0.15.1"
|
||||
futures = { version = "0.3.4", default-features = false }
|
||||
packer = "0.5.3"
|
||||
|
||||
[build-dependencies]
|
||||
rsass = "0.13.0"
|
||||
|
|
0
Justfile
Normal file
0
Justfile
Normal file
1
build.rs
Normal file
1
build.rs
Normal file
|
@ -0,0 +1 @@
|
|||
fn main() {}
|
|
@ -1,3 +1,3 @@
|
|||
addr = "127.0.0.1:3000"
|
||||
repo_root = "./repos"
|
||||
redis_url = "redis://127.0.0.1"
|
||||
redis_url = "redis://127.0.0.1"
|
||||
|
|
55
dev-server.old.rs
Normal file
55
dev-server.old.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use std::convert::Infallible;
|
||||
use std::sync::mpsc::{self};
|
||||
use std::time::Duration;
|
||||
use std::thread;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use hyper::{
|
||||
service::{make_service_fn, service_fn},
|
||||
Body, Response, Server,
|
||||
};
|
||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let (watch_tx, watch_rx) = mpsc::channel();
|
||||
let mut watcher = RecommendedWatcher::new(watch_tx, Duration::from_secs(3))?;
|
||||
|
||||
let path = PathBuf::from(".");
|
||||
watcher.watch(&path, RecursiveMode::Recursive)?;
|
||||
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
match watch_rx.recv() {
|
||||
Ok(evt) => {
|
||||
println!("event: {:?}", evt);
|
||||
}
|
||||
Err(err) => eprintln!("watch error: {:?}", err),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let make_svc = make_service_fn(move |_conn| {
|
||||
let fedhub = fedhub.clone();
|
||||
let main = move |req| {
|
||||
let fedhub = fedhub.clone();
|
||||
future::ready(match fedhub.handle(req) {
|
||||
Ok(res) => Ok::<_, Infallible>(res),
|
||||
Err(err) => {
|
||||
eprintln!("Error: {:?}", err);
|
||||
Ok(Response::new(Body::from(format!("Error: {:?}", err))))
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
future::ok::<_, Infallible>(service_fn(main))
|
||||
});
|
||||
|
||||
let server = Server::bind(&addr).serve(make_svc);
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("server error: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -3,6 +3,7 @@ use std::path::PathBuf;
|
|||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub site_name: Option<String>,
|
||||
pub addr: SocketAddr,
|
||||
pub repo_root: PathBuf,
|
||||
pub redis_url: String,
|
||||
|
|
344
src/lib.rs
Normal file
344
src/lib.rs
Normal file
|
@ -0,0 +1,344 @@
|
|||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
|
||||
mod config;
|
||||
|
||||
use std::path::{Component as PathComponent, Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use git2::{ObjectType, Oid, Repository, Tree};
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
use lazy_static::lazy_static;
|
||||
use packer::Packer;
|
||||
use parking_lot::RwLock;
|
||||
use redis::{Client as RedisClient, Cmd as RedisCmd};
|
||||
use tera::{Context as TeraContext, Tera};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub use crate::config::Config;
|
||||
|
||||
macro_rules! template {
|
||||
($file:expr) => {
|
||||
($file, include_str!(concat!("../templates/", $file)))
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref TERA: Tera = {
|
||||
let mut tera = Tera::default();
|
||||
tera.add_raw_templates(vec![
|
||||
template!("layout.html"),
|
||||
template!("index.html"),
|
||||
template!("repo_blob.html"),
|
||||
template!("repo_index.html"),
|
||||
template!("repo_tree.html"),
|
||||
])
|
||||
.unwrap();
|
||||
tera
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Packer)]
|
||||
#[packer(source = "static", prefixed = false)]
|
||||
pub struct Statics;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Fedhub {
|
||||
config: Config,
|
||||
state: Arc<RwLock<State>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct State {
|
||||
cache: RedisClient,
|
||||
}
|
||||
|
||||
impl Fedhub {
|
||||
pub fn new(config: Config) -> Result<Self> {
|
||||
let cache = RedisClient::open(config.redis_url.as_ref())?;
|
||||
Ok(Fedhub {
|
||||
config,
|
||||
state: Arc::new(RwLock::new(State { cache })),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn context(&self) -> TeraContext {
|
||||
let mut ctx = TeraContext::new();
|
||||
let site_name = self
|
||||
.config
|
||||
.site_name
|
||||
.as_ref()
|
||||
.map(|s| s.as_ref())
|
||||
.unwrap_or_else(|| "Fedhub");
|
||||
ctx.insert(
|
||||
"fedhub",
|
||||
&json!({
|
||||
"site_name": site_name,
|
||||
}),
|
||||
);
|
||||
ctx
|
||||
}
|
||||
|
||||
pub fn get_dir_list(&self) -> Result<Vec<PathBuf>> {
|
||||
let state = self.state.read();
|
||||
let mut con = state.cache.get_connection()?;
|
||||
if RedisCmd::exists("repos").query(&mut con)? {
|
||||
let path_strs: Vec<String> = RedisCmd::smembers("repos").query(&mut con)?;
|
||||
return Ok(path_strs.into_iter().map(|s| PathBuf::from(s)).collect());
|
||||
} else {
|
||||
let repo_root = self.config.repo_root.clone();
|
||||
let mut dir_iter = WalkDir::new(&repo_root).into_iter();
|
||||
let mut dirs = Vec::new();
|
||||
loop {
|
||||
let entry = match dir_iter.next() {
|
||||
None => break,
|
||||
Some(Err(err)) => panic!("error: {:?}", err),
|
||||
Some(Ok(entry)) => entry,
|
||||
};
|
||||
let path = entry.path();
|
||||
if let Ok(_) = Repository::open(path) {
|
||||
let new_path = path.strip_prefix(&repo_root).unwrap();
|
||||
dirs.push(new_path.to_path_buf());
|
||||
dir_iter.skip_current_dir();
|
||||
}
|
||||
}
|
||||
RedisCmd::sadd(
|
||||
"repos",
|
||||
dirs.iter()
|
||||
.map(|path| path.to_str().unwrap().to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.execute(&mut con);
|
||||
Ok(dirs)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_index(&self) -> Result<Response<Body>> {
|
||||
let directories = self.get_dir_list()?;
|
||||
let mut ctx = self.context();
|
||||
ctx.insert("repos", &directories);
|
||||
Ok(Response::new(TERA.render("index.html", &ctx)?.into()))
|
||||
}
|
||||
|
||||
pub fn render_repo_index(&self, path: PathBuf, repo: &Repository) -> Result<Response<Body>> {
|
||||
let mut ctx = self.context();
|
||||
ctx.insert("repo_name", &path);
|
||||
let mut branches = Vec::new();
|
||||
for branch in repo.branches(None)? {
|
||||
let (branch, _) = branch?;
|
||||
let name = branch.name()?.unwrap().to_string();
|
||||
let branch_ref = branch.into_reference();
|
||||
let url = format!(
|
||||
"/{}/+/{}",
|
||||
path.to_str().unwrap(),
|
||||
branch_ref.name().unwrap()
|
||||
);
|
||||
branches.push(json!({
|
||||
"name": name,
|
||||
"url": url,
|
||||
}));
|
||||
}
|
||||
ctx.insert("branches", &branches);
|
||||
return Ok(Response::new(TERA.render("repo_index.html", &ctx)?.into()));
|
||||
}
|
||||
|
||||
pub fn render_repo_tree(
|
||||
&self,
|
||||
path: PathBuf,
|
||||
repo: &Repository,
|
||||
tree_id: Oid,
|
||||
tree_name: String,
|
||||
filepath: Option<PathBuf>,
|
||||
) -> Result<Response<Body>> {
|
||||
let tree = repo.find_tree(tree_id)?;
|
||||
let mut ctx = self.context();
|
||||
ctx.insert("repo_name", &path);
|
||||
ctx.insert("tree_name", &tree_name);
|
||||
ctx.insert("filepath", &filepath);
|
||||
let mut entries = Vec::new();
|
||||
for entry in tree.iter() {
|
||||
let url = match &filepath {
|
||||
Some(filepath) => format!(
|
||||
"/{}/+/{}/{}/{}",
|
||||
path.to_str().unwrap(),
|
||||
tree_name,
|
||||
filepath.to_str().unwrap(),
|
||||
entry.name().unwrap()
|
||||
),
|
||||
None => format!(
|
||||
"/{}/+/{}/{}",
|
||||
path.to_str().unwrap(),
|
||||
tree_name,
|
||||
entry.name().unwrap()
|
||||
),
|
||||
};
|
||||
entries.push(json!({
|
||||
"name": entry.name().unwrap(),
|
||||
"url": url,
|
||||
}));
|
||||
}
|
||||
ctx.insert("entries", &entries);
|
||||
return Ok(Response::new(TERA.render("repo_tree.html", &ctx)?.into()));
|
||||
}
|
||||
|
||||
pub fn render_repo_blob(
|
||||
&self,
|
||||
path: PathBuf,
|
||||
repo: &Repository,
|
||||
tree_name: String,
|
||||
filepath: impl AsRef<Path>,
|
||||
blob_id: Oid,
|
||||
) -> Result<Response<Body>> {
|
||||
let filepath = filepath.as_ref();
|
||||
let blob = repo.find_blob(blob_id)?;
|
||||
let mut ctx = self.context();
|
||||
ctx.insert("repo_name", &path);
|
||||
ctx.insert("tree_name", &tree_name);
|
||||
ctx.insert(
|
||||
"blob",
|
||||
&json!({
|
||||
"name": filepath,
|
||||
"binary": blob.is_binary(),
|
||||
"contents": if blob.is_binary() {
|
||||
json!(null)
|
||||
} else {
|
||||
json!(String::from_utf8(blob.content().to_vec())?)
|
||||
},
|
||||
}),
|
||||
);
|
||||
return Ok(Response::new(TERA.render("repo_blob.html", &ctx)?.into()));
|
||||
}
|
||||
|
||||
pub fn handle(self, req: Request<Body>) -> Result<Response<Body>> {
|
||||
let repo_root = self.config.repo_root.clone();
|
||||
let uri = req.uri();
|
||||
let path = uri.path();
|
||||
|
||||
if path == "/" {
|
||||
return self.render_index();
|
||||
}
|
||||
|
||||
if path.starts_with("/static") {
|
||||
let rest = path.trim_start_matches("/static/");
|
||||
println!("REST: {:?}", rest);
|
||||
println!("LIST: {:?}", Statics::list().collect::<Vec<_>>());
|
||||
return match Statics::get(rest) {
|
||||
Some(data) => Ok(Response::builder().body(data.into())?),
|
||||
None => Ok(Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body("not found".into())?),
|
||||
};
|
||||
}
|
||||
|
||||
let repo_info = {
|
||||
let mut repo_info = None;
|
||||
for repo_dir in self.get_dir_list()? {
|
||||
let path = PathBuf::from(path.trim_start_matches("/"));
|
||||
if path.starts_with(&repo_dir) {
|
||||
let repo = Repository::open(repo_root.join(&repo_dir))?;
|
||||
let remainder = PathBuf::from("/").join(path.strip_prefix(&repo_dir)?);
|
||||
repo_info = Some((repo_dir, repo, remainder));
|
||||
}
|
||||
}
|
||||
repo_info
|
||||
};
|
||||
|
||||
if let Some((path, repo, remainder)) = repo_info {
|
||||
if remainder == PathBuf::from("/") {
|
||||
return self.render_repo_index(path, &repo);
|
||||
} else if remainder.starts_with("/+") {
|
||||
let mut iter = remainder.strip_prefix("/+").unwrap().components();
|
||||
let tree_name = iter.next();
|
||||
if let Some(PathComponent::Normal(tree_name)) = tree_name {
|
||||
let tree_name = tree_name.to_str().unwrap().to_string();
|
||||
let tree_name = if tree_name == "refs" {
|
||||
let second = iter.next().unwrap();
|
||||
let third = iter.next().unwrap();
|
||||
format!(
|
||||
"{}/{}/{}",
|
||||
tree_name,
|
||||
second.as_os_str().to_str().unwrap(),
|
||||
third.as_os_str().to_str().unwrap()
|
||||
)
|
||||
} else {
|
||||
tree_name
|
||||
};
|
||||
let (tree_id, shortname) = {
|
||||
let (tree, shortname) = get_tree_from_name(&repo, tree_name)?;
|
||||
(tree.id(), shortname)
|
||||
};
|
||||
let filepath = iter.collect::<PathBuf>();
|
||||
println!("filepath: {:?}", filepath);
|
||||
if filepath.iter().collect::<Vec<_>>().len() == 0 {
|
||||
return self.render_repo_tree(path, &repo, tree_id, shortname, None);
|
||||
} else {
|
||||
let (object_id, object_type) =
|
||||
get_path_from_tree(&repo, tree_id, &filepath)?;
|
||||
println!("{:?} {:?}", object_id, object_type);
|
||||
match object_type {
|
||||
ObjectType::Tree => {
|
||||
return self.render_repo_tree(
|
||||
path,
|
||||
&repo,
|
||||
object_id,
|
||||
shortname,
|
||||
Some(filepath),
|
||||
)
|
||||
}
|
||||
ObjectType::Blob => {
|
||||
return self
|
||||
.render_repo_blob(path, &repo, shortname, filepath, object_id)
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body("not found".into())?)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tree_from_name(repo: &Repository, name: impl AsRef<str>) -> Result<(Tree, String)> {
|
||||
Ok(
|
||||
if let Ok(commit_ref) = repo.resolve_reference_from_short_name(name.as_ref()) {
|
||||
let tree_name = commit_ref.shorthand().unwrap().to_string();
|
||||
(commit_ref.peel_to_commit()?.tree()?, tree_name)
|
||||
} else if let Ok(commit) = repo.find_commit(Oid::from_str(name.as_ref())?) {
|
||||
let tree_name = name.as_ref().to_string();
|
||||
(commit.tree()?, tree_name)
|
||||
} else {
|
||||
anyhow::bail!("Fuck!");
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn get_path_from_tree<'a>(
|
||||
repo: &Repository,
|
||||
curr_oid: Oid,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<(Oid, ObjectType)> {
|
||||
let mut components = path.as_ref().components();
|
||||
let tree = repo.find_tree(curr_oid)?;
|
||||
if let Some(next_file) = components.next() {
|
||||
if let Some(entry) = tree.get_name(next_file.as_os_str().to_str().unwrap()) {
|
||||
let kind = entry.kind().unwrap();
|
||||
match kind {
|
||||
ObjectType::Tree => {
|
||||
get_path_from_tree(repo, entry.id(), components.collect::<PathBuf>())
|
||||
}
|
||||
_ => Ok((entry.id(), kind)),
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("doesn't exist bro: {:?}", next_file);
|
||||
}
|
||||
} else {
|
||||
Ok((tree.id(), ObjectType::Tree))
|
||||
}
|
||||
}
|
323
src/main.rs
323
src/main.rs
|
@ -1,329 +1,20 @@
|
|||
#![feature(async_closure)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
|
||||
mod config;
|
||||
|
||||
use std::convert::Infallible;
|
||||
|
||||
use std::path::{Component as PathComponent, Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use futures::future::{self};
|
||||
use git2::{ObjectType, Oid, Repository, Tree};
|
||||
use fedhub::{Config, Fedhub};
|
||||
use futures::future;
|
||||
use hyper::{
|
||||
service::{make_service_fn, service_fn},
|
||||
Body, Request, Response, Server, StatusCode,
|
||||
Body, Response, Server,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use redis::{Client as RedisClient, Cmd as RedisCmd};
|
||||
use tera::{Context as TeraContext, Tera};
|
||||
use tokio::{fs::File, io::AsyncReadExt};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
macro_rules! template {
|
||||
($file:expr) => {
|
||||
($file, include_str!(concat!("../templates/", $file)))
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref TERA: Tera = {
|
||||
let mut tera = Tera::default();
|
||||
tera.add_raw_templates(vec![
|
||||
template!("index.html"),
|
||||
template!("repo_blob.html"),
|
||||
template!("repo_index.html"),
|
||||
template!("repo_tree.html"),
|
||||
])
|
||||
.unwrap();
|
||||
tera
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Fedhub {
|
||||
config: Config,
|
||||
state: Arc<RwLock<State>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct State {
|
||||
cache: RedisClient,
|
||||
}
|
||||
|
||||
impl Fedhub {
|
||||
pub fn new(config: Config) -> Result<Self> {
|
||||
let cache = RedisClient::open(config.redis_url.as_ref())?;
|
||||
Ok(Fedhub {
|
||||
config,
|
||||
state: Arc::new(RwLock::new(State { cache })),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_dir_list(&self) -> Result<Vec<PathBuf>> {
|
||||
let state = self.state.read();
|
||||
let mut con = state.cache.get_connection()?;
|
||||
if RedisCmd::exists("repos").query(&mut con)? {
|
||||
let path_strs: Vec<String> = RedisCmd::smembers("repos").query(&mut con)?;
|
||||
return Ok(path_strs.into_iter().map(|s| PathBuf::from(s)).collect());
|
||||
} else {
|
||||
let repo_root = self.config.repo_root.clone();
|
||||
let mut dir_iter = WalkDir::new(&repo_root).into_iter();
|
||||
let mut dirs = Vec::new();
|
||||
loop {
|
||||
let entry = match dir_iter.next() {
|
||||
None => break,
|
||||
Some(Err(err)) => panic!("error: {:?}", err),
|
||||
Some(Ok(entry)) => entry,
|
||||
};
|
||||
let path = entry.path();
|
||||
if let Ok(_) = Repository::open(path) {
|
||||
let new_path = path.strip_prefix(&repo_root).unwrap();
|
||||
dirs.push(new_path.to_path_buf());
|
||||
dir_iter.skip_current_dir();
|
||||
}
|
||||
}
|
||||
RedisCmd::sadd(
|
||||
"repos",
|
||||
dirs.iter()
|
||||
.map(|path| path.to_str().unwrap().to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.execute(&mut con);
|
||||
Ok(dirs)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_index(&self) -> Result<Response<Body>> {
|
||||
let directories = self.get_dir_list()?;
|
||||
let mut ctx = TeraContext::new();
|
||||
ctx.insert("repos", &directories);
|
||||
Ok(Response::new(TERA.render("index.html", &ctx)?.into()))
|
||||
}
|
||||
|
||||
pub fn render_repo_index(&self, path: PathBuf, repo: &Repository) -> Result<Response<Body>> {
|
||||
let mut ctx = TeraContext::new();
|
||||
ctx.insert("repo_name", &path);
|
||||
let mut branches = Vec::new();
|
||||
for branch in repo.branches(None)? {
|
||||
let (branch, _) = branch?;
|
||||
let name = branch.name()?.unwrap().to_string();
|
||||
let branch_ref = branch.into_reference();
|
||||
let url = format!(
|
||||
"/{}/+/{}",
|
||||
path.to_str().unwrap(),
|
||||
branch_ref.name().unwrap()
|
||||
);
|
||||
branches.push(json!({
|
||||
"name": name,
|
||||
"url": url,
|
||||
}));
|
||||
}
|
||||
ctx.insert("branches", &branches);
|
||||
return Ok(Response::new(TERA.render("repo_index.html", &ctx)?.into()));
|
||||
}
|
||||
|
||||
pub fn render_repo_tree(
|
||||
&self,
|
||||
path: PathBuf,
|
||||
repo: &Repository,
|
||||
tree_id: Oid,
|
||||
tree_name: String,
|
||||
filepath: Option<PathBuf>,
|
||||
) -> Result<Response<Body>> {
|
||||
let tree = repo.find_tree(tree_id)?;
|
||||
let mut ctx = TeraContext::new();
|
||||
ctx.insert("repo_name", &path);
|
||||
ctx.insert("tree_name", &tree_name);
|
||||
ctx.insert("filepath", &filepath);
|
||||
let mut entries = Vec::new();
|
||||
for entry in tree.iter() {
|
||||
let url = match &filepath {
|
||||
Some(filepath) => format!(
|
||||
"/{}/+/{}/{}/{}",
|
||||
path.to_str().unwrap(),
|
||||
tree_name,
|
||||
filepath.to_str().unwrap(),
|
||||
entry.name().unwrap()
|
||||
),
|
||||
None => format!(
|
||||
"/{}/+/{}/{}",
|
||||
path.to_str().unwrap(),
|
||||
tree_name,
|
||||
entry.name().unwrap()
|
||||
),
|
||||
};
|
||||
entries.push(json!({
|
||||
"name": entry.name().unwrap(),
|
||||
"url": url,
|
||||
}));
|
||||
}
|
||||
ctx.insert("entries", &entries);
|
||||
return Ok(Response::new(TERA.render("repo_tree.html", &ctx)?.into()));
|
||||
}
|
||||
|
||||
pub fn render_repo_blob(
|
||||
&self,
|
||||
path: PathBuf,
|
||||
repo: &Repository,
|
||||
tree_name: String,
|
||||
filepath: impl AsRef<Path>,
|
||||
blob_id: Oid,
|
||||
) -> Result<Response<Body>> {
|
||||
let filepath = filepath.as_ref();
|
||||
let blob = repo.find_blob(blob_id)?;
|
||||
let mut ctx = TeraContext::new();
|
||||
ctx.insert("repo_name", &path);
|
||||
ctx.insert("tree_name", &tree_name);
|
||||
ctx.insert(
|
||||
"blob",
|
||||
&json!({
|
||||
"name": filepath,
|
||||
"binary": blob.is_binary(),
|
||||
"contents": if blob.is_binary() {
|
||||
json!(null)
|
||||
} else {
|
||||
json!(String::from_utf8(blob.content().to_vec())?)
|
||||
},
|
||||
}),
|
||||
);
|
||||
return Ok(Response::new(TERA.render("repo_blob.html", &ctx)?.into()));
|
||||
}
|
||||
|
||||
pub fn handle(self, req: Request<Body>) -> Result<Response<Body>> {
|
||||
let repo_root = self.config.repo_root.clone();
|
||||
let uri = req.uri();
|
||||
let path = uri.path();
|
||||
|
||||
if path == "/" {
|
||||
return self.render_index();
|
||||
}
|
||||
|
||||
let repo_info = {
|
||||
let mut repo_info = None;
|
||||
for repo_dir in self.get_dir_list()? {
|
||||
let path = PathBuf::from(path.trim_start_matches("/"));
|
||||
if path.starts_with(&repo_dir) {
|
||||
let repo = Repository::open(repo_root.join(&repo_dir))?;
|
||||
let remainder = PathBuf::from("/").join(path.strip_prefix(&repo_dir)?);
|
||||
repo_info = Some((repo_dir, repo, remainder));
|
||||
}
|
||||
}
|
||||
repo_info
|
||||
};
|
||||
|
||||
if let Some((path, repo, remainder)) = repo_info {
|
||||
if remainder == PathBuf::from("/") {
|
||||
return self.render_repo_index(path, &repo);
|
||||
} else if remainder.starts_with("/+") {
|
||||
let mut iter = remainder.strip_prefix("/+").unwrap().components();
|
||||
let tree_name = iter.next();
|
||||
if let Some(PathComponent::Normal(tree_name)) = tree_name {
|
||||
let tree_name = tree_name.to_str().unwrap().to_string();
|
||||
let tree_name = if tree_name == "refs" {
|
||||
let second = iter.next().unwrap();
|
||||
let third = iter.next().unwrap();
|
||||
format!(
|
||||
"{}/{}/{}",
|
||||
tree_name,
|
||||
second.as_os_str().to_str().unwrap(),
|
||||
third.as_os_str().to_str().unwrap()
|
||||
)
|
||||
} else {
|
||||
tree_name
|
||||
};
|
||||
let (tree_id, shortname) = {
|
||||
let (tree, shortname) = get_tree_from_name(&repo, tree_name)?;
|
||||
(tree.id(), shortname)
|
||||
};
|
||||
let filepath = iter.collect::<PathBuf>();
|
||||
println!("filepath: {:?}", filepath);
|
||||
if filepath.iter().collect::<Vec<_>>().len() == 0 {
|
||||
return self.render_repo_tree(path, &repo, tree_id, shortname, None);
|
||||
} else {
|
||||
let (object_id, object_type) =
|
||||
get_path_from_tree(&repo, tree_id, &filepath)?;
|
||||
println!("{:?} {:?}", object_id, object_type);
|
||||
match object_type {
|
||||
ObjectType::Tree => {
|
||||
return self.render_repo_tree(
|
||||
path,
|
||||
&repo,
|
||||
object_id,
|
||||
shortname,
|
||||
Some(filepath),
|
||||
)
|
||||
}
|
||||
ObjectType::Blob => {
|
||||
return self
|
||||
.render_repo_blob(path, &repo, shortname, filepath, object_id)
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body("not found".into())
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tree_from_name(repo: &Repository, name: impl AsRef<str>) -> Result<(Tree, String)> {
|
||||
Ok(
|
||||
if let Ok(commit_ref) = repo.resolve_reference_from_short_name(name.as_ref()) {
|
||||
let tree_name = commit_ref.shorthand().unwrap().to_string();
|
||||
(commit_ref.peel_to_commit()?.tree()?, tree_name)
|
||||
} else if let Ok(commit) = repo.find_commit(Oid::from_str(name.as_ref())?) {
|
||||
let tree_name = name.as_ref().to_string();
|
||||
(commit.tree()?, tree_name)
|
||||
} else {
|
||||
anyhow::bail!("Fuck!");
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn get_path_from_tree<'a>(
|
||||
repo: &Repository,
|
||||
curr_oid: Oid,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<(Oid, ObjectType)> {
|
||||
let mut components = path.as_ref().components();
|
||||
let tree = repo.find_tree(curr_oid)?;
|
||||
if let Some(next_file) = components.next() {
|
||||
if let Some(entry) = tree.get_name(next_file.as_os_str().to_str().unwrap()) {
|
||||
let kind = entry.kind().unwrap();
|
||||
match kind {
|
||||
ObjectType::Tree => {
|
||||
get_path_from_tree(repo, entry.id(), components.collect::<PathBuf>())
|
||||
}
|
||||
_ => Ok((entry.id(), kind)),
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("doesn't exist bro: {:?}", next_file);
|
||||
}
|
||||
} else {
|
||||
Ok((tree.id(), ObjectType::Tree))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let mut config_file = File::open("config.toml").await?;
|
||||
let mut config_file = File::open("config.toml")?;
|
||||
let mut config_str = String::new();
|
||||
config_file.read_to_string(&mut config_str).await?;
|
||||
config_file.read_to_string(&mut config_str)?;
|
||||
|
||||
let config = toml::from_str::<Config>(&config_str)?;
|
||||
let addr = config.addr.clone();
|
||||
|
|
46
static/main.css
Normal file
46
static/main.css
Normal file
|
@ -0,0 +1,46 @@
|
|||
:root {
|
||||
--background-color: #15202B;
|
||||
--text-color: #D4D4D4;
|
||||
--link-color: lightskyblue;
|
||||
--sans-font: "Helvetica", "Arial", "Liberation Sans", sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 850px;
|
||||
margin: auto;
|
||||
min-height: 100%;
|
||||
padding-bottom: 20px;
|
||||
font-family: var(--sans-font);
|
||||
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
header {
|
||||
margin: auto 12px;
|
||||
}
|
||||
|
||||
header #header {
|
||||
border-bottom: 2px solid var(--link-color);
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
header #header #title {
|
||||
font-size: 2em;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
header #header #title:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
|
@ -1,14 +1,7 @@
|
|||
<html>
|
||||
<head>
|
||||
</head>
|
||||
{% extends "layout.html" %}
|
||||
|
||||
<body>
|
||||
<h1>Fedhub</h1>
|
||||
|
||||
<ul>
|
||||
{% for repo in repos %}
|
||||
<li><a href="/{{ repo }}">{{ repo }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
{% block content %}
|
||||
{% for repo in repos %}
|
||||
<li><a href="/{{ repo }}">{{ repo }}</a></li>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
|
25
templates/layout.html
Normal file
25
templates/layout.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
|
||||
<link rel="stylesheet" href="/static/main.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div id="header" class="container">
|
||||
<a href="/" id="title" class="title nocolorlink">{{ fedhub.site_name }}</a>
|
||||
|
||||
<div id="nav">
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container" style="padding: 5px 40px;">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,10 +1,7 @@
|
|||
<html>
|
||||
<head>
|
||||
</head>
|
||||
{% extends "layout.html" %}
|
||||
|
||||
<body>
|
||||
<h1>{{ repo_name }}: {{ tree_name }}: {{ blob.name }}</h1>
|
||||
{% block content %}
|
||||
<h1>{{ repo_name }}: {{ tree_name }}: {{ blob.name }}</h1>
|
||||
|
||||
<pre>{{ blob.contents }}</pre>
|
||||
</body>
|
||||
</html>
|
||||
<pre>{{ blob.contents }}</pre>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
<html>
|
||||
<head>
|
||||
</head>
|
||||
{% extends "layout.html" %}
|
||||
|
||||
<body>
|
||||
<h1>{{ repo_name }}</h1>
|
||||
{% block content %}
|
||||
<h1>{{ repo_name }}</h1>
|
||||
|
||||
<h3>branches:</h3>
|
||||
<ul>
|
||||
{% for branch in branches %}
|
||||
<a href="{{ branch.url }}">{{ branch.name }}</a>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
<h3>branches:</h3>
|
||||
<ul>
|
||||
{% for branch in branches %}
|
||||
<a href="{{ branch.url }}">{{ branch.name }}</a>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
<html>
|
||||
<head>
|
||||
</head>
|
||||
{% extends "layout.html" %}
|
||||
|
||||
<body>
|
||||
<h1>{{ repo_name }}: {{ tree_name }}{% if filepath %}: {{ filepath }}{% endif %}</h1>
|
||||
{% block content %}
|
||||
<h1>{{ repo_name }}: {{ tree_name }}{% if filepath %}: {{ filepath }}{% endif %}</h1>
|
||||
|
||||
<h3>entries:</h3>
|
||||
<ul>
|
||||
{% for entry in entries %}
|
||||
<li><a href="{{ entry.url }}">{{ entry.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
<h3>entries:</h3>
|
||||
<ul>
|
||||
{% for entry in entries %}
|
||||
<li><a href="{{ entry.url }}">{{ entry.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in a new issue