use template
This commit is contained in:
parent
80a86e9f8a
commit
19f51c5732
173
index.html
Normal file
173
index.html
Normal file
@ -0,0 +1,173 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>
|
||||
<!-- t: safe sitename -->
|
||||
</title>
|
||||
<style>
|
||||
a:link {
|
||||
color: DarkCyan;
|
||||
text-decoration: none;
|
||||
font-weight: normal;
|
||||
}
|
||||
a:hover {
|
||||
color: DarkCyan;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
a:visited {
|
||||
color: DarkCyan;
|
||||
text-decoration: underline;
|
||||
font-weight: normal;
|
||||
}
|
||||
span {
|
||||
color: SeaGreen;
|
||||
font-weight: normal;
|
||||
}
|
||||
.ohs {
|
||||
display: none;
|
||||
}
|
||||
.oht:hover + .ohs, .ohs:hover {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- t: if domain -->
|
||||
|
||||
<!-- t: if sitedecoration -->
|
||||
<h2><!-- t: safe sitedecoration --> <!-- t: safe sitename --> <!-- t: safe sitedecoration --></h2>
|
||||
<!-- t: else -->
|
||||
<h2><!-- t: safe sitename --></h2>
|
||||
<!-- t: end -->
|
||||
|
||||
|
||||
<!-- t: if running -->
|
||||
|
||||
<h3>things running on the server</h3>
|
||||
<ul>
|
||||
|
||||
<!-- t: for running -->
|
||||
<li>
|
||||
<!-- t: if info -->
|
||||
<a href="/info/<!-- t: raw info -->">
|
||||
<!-- t: else -->
|
||||
<!-- t: if redirect -->
|
||||
<a href="/<!-- t: raw redirect -->">
|
||||
<!-- t: else -->
|
||||
<span>
|
||||
<!-- t: end -->
|
||||
<!-- t: end -->
|
||||
<!-- t: safe id -->
|
||||
<!-- t: if info -->
|
||||
</a>
|
||||
<!-- t: else -->
|
||||
<!-- t: if redirect -->
|
||||
</a>
|
||||
<!-- t: else -->
|
||||
</span>
|
||||
<!-- t: end -->
|
||||
<!-- t: end -->
|
||||
: <!-- t: // comment to keep editors from removing the trailing space -->
|
||||
<!-- t: if additional -->
|
||||
<em class="oht">
|
||||
<!-- t: end -->
|
||||
<!-- t: safe status -->
|
||||
<!-- t: if additional -->
|
||||
</em><div class="ohs">
|
||||
<!-- t: set linebreak -->
|
||||
<!-- t: for line additional -->
|
||||
<!-- t: raw linebreak -->
|
||||
<!-- t: set linebreak <br>
|
||||
-->
|
||||
<!-- t: safe line -->
|
||||
<!-- t: end -->
|
||||
</div>
|
||||
<!-- t: end -->
|
||||
<!-- t: if debug -->
|
||||
<!-- t: if duration -->
|
||||
| <!-- t: safe duration -->ms
|
||||
<!-- t: end -->
|
||||
<!-- t: end -->
|
||||
</li>
|
||||
|
||||
<!-- t: end -->
|
||||
</ul>
|
||||
|
||||
<!-- t: end -->
|
||||
|
||||
<!-- t: if static -->
|
||||
|
||||
<h3>everything else</h3>
|
||||
<ul>
|
||||
|
||||
<!-- t: for static -->
|
||||
<li>
|
||||
<!-- t: if info -->
|
||||
<a href="/info/<!-- t: raw info -->">
|
||||
<!-- t: else -->
|
||||
<!-- t: if redirect -->
|
||||
<a href="/<!-- t: raw redirect -->">
|
||||
<!-- t: else -->
|
||||
<span>
|
||||
<!-- t: end -->
|
||||
<!-- t: end -->
|
||||
<!-- t: safe id -->
|
||||
<!-- t: if info -->
|
||||
</a>
|
||||
<!-- t: else -->
|
||||
<!-- t: if redirect -->
|
||||
</a>
|
||||
<!-- t: else -->
|
||||
</span>
|
||||
<!-- t: end -->
|
||||
<!-- t: end -->
|
||||
: <!-- t: // comment to keep editors from removing the trailing space -->
|
||||
<!-- t: if additional -->
|
||||
<em class="oht">
|
||||
<!-- t: end -->
|
||||
<!-- t: safe status -->
|
||||
<!-- t: if additional -->
|
||||
</em><div class="ohs">
|
||||
<!-- t: set linebreak -->
|
||||
<!-- t: for line additional -->
|
||||
<!-- t: raw linebreak -->
|
||||
<!-- t: set linebreak <br>
|
||||
-->
|
||||
<!-- t: safe line -->
|
||||
<!-- t: end -->
|
||||
</div>
|
||||
<!-- t: end -->
|
||||
<!-- t: if debug -->
|
||||
<!-- t: if duration -->
|
||||
| <!-- t: safe duration -->ms
|
||||
<!-- t: end -->
|
||||
<!-- t: end -->
|
||||
</li>
|
||||
|
||||
<!-- t: end -->
|
||||
</ul>
|
||||
|
||||
<!-- t: end -->
|
||||
|
||||
<!-- t: if docslink -->
|
||||
|
||||
<br><small><small><a href="/int">tomatenmharksite documentation</a></small></small>
|
||||
|
||||
<!-- t: end -->
|
||||
|
||||
<br><br><br><br><br><br>
|
||||
<footer><p><!-- t: safe domain --> and subdomains: <a href="<!-- t: raw ownerpage -->"><!-- t: safe ownername --></a></p></footer>
|
||||
|
||||
<!-- t: else -->
|
||||
|
||||
<h1>unconfigured tomatenmharksite server</h1>
|
||||
please fill out the required values in <code>int.html</code>
|
||||
and restart the tomatenmharksite server
|
||||
|
||||
<!-- t: end -->
|
||||
</body></html>
|
@ -1,4 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<!-- t: // fill out the following information -->
|
||||
<!-- t: set domain -->
|
||||
<!-- t: set sitename unconfigured tomatenmharksite -->
|
||||
<!-- t: set sitedecoration :) -->
|
||||
<!-- t: set ownername CHANGEME -->
|
||||
<!-- t: set ownerpage /info/me/ -->
|
||||
|
||||
<!-- t: // remove this to disable the "tomatenmharksite documentation" link -->
|
||||
<!-- t: set docslink true -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
@ -11,6 +21,8 @@
|
||||
<div>documentation of how the tomatenmharksite server works for people who care or people who keep forgetting (me)</div>
|
||||
<div>go to <a href="/">index without</a> or <a href="/dbg">with debugging information</a></div>
|
||||
|
||||
<h3>index</h3>
|
||||
|
||||
<h4>links</h4>
|
||||
In the "running" section on the website, the ids may or may not be links.
|
||||
If a <code>/srv/tomatenmhark-slashinfo/*/index.html</code> entry exists, the link will point to that info site,
|
||||
@ -18,8 +30,22 @@ If only a <code>/srv/tomatenmhark-redirect/*</code> entry exists, the link will
|
||||
and if neither exist, there will be no link.<br>
|
||||
In the "everything else" section, every entry links to its info page, if there is one.
|
||||
|
||||
<h4>texts</h4>
|
||||
Each id has a status text. This can be a file's content or dynamic output, and will be cached for 3 seconds.
|
||||
If a status consists of multiple lines, the all but the first one are additional information. In this case,
|
||||
the first line will be <em>emphasized</em> and the additional lines will be displayed on hover.
|
||||
|
||||
<h4>customizing</h4>
|
||||
|
||||
The contents of the index are specified in a template file, <code>index.html</code>.
|
||||
This file is served as-is, but comments like <code><!-- t: ... --></code> are
|
||||
given special meaning.
|
||||
See <code>index.html</code> and <code>int.html</code> for usage examples.
|
||||
|
||||
<h3>files</h3>
|
||||
|
||||
<h4>/tmp/tomatenmhark-status-*</h4>
|
||||
For each file <code>/tmp/tomatenmhark-status-*</code>, an entry will be created on <a href="/">tomatenmhark.org</a>.
|
||||
For each file <code>/tmp/tomatenmhark-status-*</code>, an entry will be created on <a href="/"><!-- t: safe domain --></a>.
|
||||
This entry will have the id <code>*</code> and the file's contents will be shown after that.
|
||||
|
||||
<h4>/srv/tomatenmhark-dystatus/*</h4>
|
||||
@ -37,7 +63,7 @@ Each file in <code>/srv/tomatenmhark-redirect/</code> contains a port number or
|
||||
If the file contains a port number, <code>/filename/...</code> will be redirected to <code>samedomain:portnumber/...</code>.<br>
|
||||
If the file contains anything else, <code>/filename/...</code> will be redirected to <code>filecontent/...</code>.<br>
|
||||
In the port number case, <code>http</code> is used. Otherwise, the protocol must be included in the file.
|
||||
<code>%DOMAIN%</code> will be replaced with <code>tomatenmhark.org</code> or whatever domain was used in the request.
|
||||
<code>%DOMAIN%</code> will be replaced with <code><!-- t: safe domain --></code> or whatever domain was used in the request.
|
||||
To redirect to port <code>8000</code> using <code>https</code>, use <code>https://%DOMAIN%:8000</code>
|
||||
|
||||
</body>
|
16
src/data.rs
16
src/data.rs
@ -2,22 +2,32 @@ use std::time::Instant;
|
||||
|
||||
use tokio::sync::{Mutex, MutexGuard};
|
||||
|
||||
use crate::status::Status;
|
||||
use crate::{status::Status, template::Template};
|
||||
|
||||
pub struct Data {
|
||||
pub int_html: String,
|
||||
pub globals: Vec<String>,
|
||||
pub index: Template,
|
||||
pub status: Mutex<Status>,
|
||||
pub status_updated: Mutex<(Instant, bool)>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Data {
|
||||
pub fn new_sync() -> Self {
|
||||
pub fn new_sync(int_html: String, index: Template, globals: Vec<String>) -> Self {
|
||||
Self {
|
||||
int_html,
|
||||
index,
|
||||
globals,
|
||||
status: Mutex::new(Status::query_sync(false)),
|
||||
status_updated: Mutex::new((Instant::now(), false)),
|
||||
}
|
||||
}
|
||||
pub async fn new_async() -> Self {
|
||||
pub async fn new_async(int_html: String, index: Template, globals: Vec<String>) -> Self {
|
||||
Self {
|
||||
int_html,
|
||||
index,
|
||||
globals,
|
||||
status: Mutex::new(Status::query_async(false).await),
|
||||
status_updated: Mutex::new((Instant::now(), false)),
|
||||
}
|
||||
|
185
src/main.rs
185
src/main.rs
@ -1,8 +1,7 @@
|
||||
mod data;
|
||||
mod redirecter;
|
||||
mod status;
|
||||
|
||||
use std::time::Duration;
|
||||
mod template;
|
||||
|
||||
use data::Data;
|
||||
use redirecter::Redirecter;
|
||||
@ -12,7 +11,8 @@ use rocket::{
|
||||
response::content::RawHtml,
|
||||
routes, State,
|
||||
};
|
||||
use tokio::time::Instant;
|
||||
use std::process::exit;
|
||||
use template::Template;
|
||||
|
||||
#[get("/")]
|
||||
async fn index(data: &State<Data>) -> RawHtml<String> {
|
||||
@ -25,161 +25,46 @@ async fn index_dbg(data: &State<Data>) -> RawHtml<String> {
|
||||
}
|
||||
|
||||
async fn index_gen(data: &State<Data>, dbg: bool) -> RawHtml<String> {
|
||||
let mut o = "<!DOCTYPE html><html><head>".to_owned();
|
||||
o.push_str(r#"<meta charset="UTF-8">"#);
|
||||
o.push_str(r#"<meta name="color-scheme" content="light dark">"#);
|
||||
o.push_str(r#"<meta name="viewport" content="width=device-width, initial-scale=1">"#);
|
||||
o.push_str("<title>tomatenmhark</title>");
|
||||
o.push_str(
|
||||
"<style>
|
||||
a:link {
|
||||
color: DarkCyan;
|
||||
text-decoration: none;
|
||||
font-weight: normal;
|
||||
}
|
||||
a:hover {
|
||||
color: DarkCyan;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
a:visited {
|
||||
color: DarkCyan;
|
||||
text-decoration: underline;
|
||||
font-weight: normal;
|
||||
}
|
||||
span {
|
||||
color: SeaGreen;
|
||||
font-weight: normal;
|
||||
}
|
||||
.ohs {
|
||||
display: none;
|
||||
}
|
||||
.oht:hover + .ohs, .ohs:hover {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
",
|
||||
);
|
||||
o.push_str("</head><body>");
|
||||
o.push_str("<h2>:) tomatenmhark :)</h2>");
|
||||
let status = data.status_async(dbg).await;
|
||||
let time_queried = Instant::now();
|
||||
fn push_elem_to_html(
|
||||
id: &str,
|
||||
info: bool,
|
||||
redirect: bool,
|
||||
status: &str,
|
||||
additional: Option<&str>,
|
||||
dur: Option<Duration>,
|
||||
o: &mut String,
|
||||
dbg: bool,
|
||||
) {
|
||||
o.push_str("<li>");
|
||||
if info {
|
||||
o.push_str(r#"<a href="/info/"#);
|
||||
o.push_str(&html_escape::encode_double_quoted_attribute(&id));
|
||||
o.push_str(r#"">"#);
|
||||
} else if redirect {
|
||||
o.push_str(r#"<a href="/"#);
|
||||
o.push_str(&html_escape::encode_double_quoted_attribute(&id));
|
||||
o.push_str(r#"">"#);
|
||||
} else {
|
||||
o.push_str("<span>");
|
||||
}
|
||||
o.push_str(&html_escape::encode_text(&id));
|
||||
if info || redirect {
|
||||
o.push_str("</a>");
|
||||
} else {
|
||||
o.push_str("</span>");
|
||||
}
|
||||
o.push_str(": ");
|
||||
if additional.is_some() {
|
||||
o.push_str(r#"<em class="oht">"#);
|
||||
}
|
||||
o.push_str(&html_escape::encode_text(&status));
|
||||
if let Some(additional) = additional {
|
||||
o.push_str(r#"</em><div class="ohs">"#);
|
||||
o.push_str(
|
||||
&html_escape::encode_text(additional)
|
||||
.replace('\r', "")
|
||||
.replace('\n', "<br>"),
|
||||
);
|
||||
o.push_str("</div>");
|
||||
}
|
||||
if dbg {
|
||||
if let Some(dur) = dur {
|
||||
o.push_str(&format!(" | {}ms", dur.as_millis()));
|
||||
}
|
||||
}
|
||||
o.push_str("</li>");
|
||||
}
|
||||
if !status.0.is_empty() {
|
||||
o.push_str("<h3>things running on the server</h3>");
|
||||
o.push_str("<ul>");
|
||||
for (id, (info, redirect, status, additional, dur)) in status.0.iter() {
|
||||
push_elem_to_html(
|
||||
id,
|
||||
*info,
|
||||
*redirect,
|
||||
status,
|
||||
additional.as_ref().map(|v| v.as_str()),
|
||||
*dur,
|
||||
&mut o,
|
||||
dbg,
|
||||
);
|
||||
}
|
||||
o.push_str("</ul>");
|
||||
}
|
||||
if !status.1.is_empty() {
|
||||
o.push_str("<h3>everything else</h3>");
|
||||
o.push_str("<ul>");
|
||||
for (id, (info, redirect, status, additional)) in status.1.iter() {
|
||||
push_elem_to_html(
|
||||
id,
|
||||
*info,
|
||||
*redirect,
|
||||
status,
|
||||
additional.as_ref().map(|v| v.as_str()),
|
||||
None,
|
||||
&mut o,
|
||||
dbg,
|
||||
);
|
||||
}
|
||||
o.push_str("</ul>");
|
||||
}
|
||||
o.push_str(
|
||||
r#"<br><small><small><a href="/int">tomatenmharksite documentation</a></small></small>"#,
|
||||
);
|
||||
o.push_str(r#"<br><br><br><br><br><br><footer><p>tomatenmhark.org und subdomains: <a href="/info/me">Mark</a>"#);
|
||||
if dbg {
|
||||
o.push_str(r#"<br><small><small>"#);
|
||||
let time_pagegen = Instant::now();
|
||||
o.push_str(&format!(
|
||||
"{}/{}ms querying + {:.2}ms pagegen",
|
||||
status
|
||||
.0
|
||||
.values()
|
||||
.filter_map(|v| v.4)
|
||||
.sum::<Duration>()
|
||||
.as_millis(),
|
||||
status.2.as_millis(),
|
||||
(time_pagegen - time_queried).as_micros() as f32 / 1000.0,
|
||||
));
|
||||
o.push_str(r#"</small></small>"#);
|
||||
}
|
||||
o.push_str(r#"</p></footer>"#);
|
||||
o.push_str("</body></html>");
|
||||
RawHtml(o)
|
||||
return RawHtml(data.index.gen(&status, dbg, data.globals.clone()));
|
||||
}
|
||||
|
||||
#[get("/int")]
|
||||
async fn int() -> RawHtml<&'static str> {
|
||||
RawHtml(include_str!("int.html"))
|
||||
async fn int(data: &State<Data>) -> RawHtml<&str> {
|
||||
RawHtml(&data.int_html)
|
||||
}
|
||||
|
||||
#[rocket::launch]
|
||||
fn rocket() -> _ {
|
||||
let data = Data::new_sync();
|
||||
let int_file = match std::fs::read_to_string("int.html") {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Could not read int.html: {e}");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
let index_file = match std::fs::read_to_string("index.html") {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Could not read index.html: {e}");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
let (int_html, variables, globals) = match Template::parse(&int_file, None) {
|
||||
Ok(int_template) => int_template.gen_int(),
|
||||
Err(e) => {
|
||||
eprintln!("Could not parse int.html: {e}");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
let index = match Template::parse(&index_file, Some(variables)) {
|
||||
Ok(index_template) => index_template,
|
||||
Err(e) => {
|
||||
eprintln!("Could not parse index.html: {e}");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
let data = Data::new_sync(int_html, index, globals);
|
||||
rocket::build()
|
||||
.manage(data)
|
||||
.mount("/", routes![index, index_dbg, int])
|
||||
|
@ -8,11 +8,12 @@ const REDIRECT: &'static str = "/srv/tomatenmhark-redirect/";
|
||||
pub struct Status(
|
||||
pub BTreeMap<String, (bool, bool, String, Option<String>, Option<Duration>)>,
|
||||
pub BTreeMap<String, (bool, bool, String, Option<String>)>,
|
||||
pub Duration,
|
||||
);
|
||||
impl Status {
|
||||
pub fn empty() -> Self {
|
||||
Self(Default::default(), Default::default())
|
||||
}
|
||||
pub fn query_sync(dbg: bool) -> Self {
|
||||
let start = Instant::now();
|
||||
let mut map = BTreeMap::new();
|
||||
query_status_sync(
|
||||
|k, e, r, v, dur| {
|
||||
@ -75,10 +76,9 @@ impl Status {
|
||||
}
|
||||
}
|
||||
}
|
||||
Self(map, rest, start.elapsed())
|
||||
Self(map, rest)
|
||||
}
|
||||
pub async fn query_async(dbg: bool) -> Self {
|
||||
let start = Instant::now();
|
||||
let mut map = BTreeMap::new();
|
||||
query_status_async(
|
||||
|k, e, r, v, dur| {
|
||||
@ -137,7 +137,7 @@ impl Status {
|
||||
}
|
||||
}
|
||||
}
|
||||
Self(map, rest, start.elapsed())
|
||||
Self(map, rest)
|
||||
}
|
||||
}
|
||||
|
||||
|
378
src/template.rs
Normal file
378
src/template.rs
Normal file
@ -0,0 +1,378 @@
|
||||
use std::{collections::HashMap, fmt::Display};
|
||||
|
||||
use crate::status::Status;
|
||||
|
||||
pub struct Template(Templates, TemplateInfo);
|
||||
|
||||
pub struct Templates(Vec<TemplateType>);
|
||||
|
||||
pub enum TemplateType {
|
||||
Html(String),
|
||||
Raw(usize),
|
||||
Safe(usize),
|
||||
Set(usize, String),
|
||||
Concat(usize, usize),
|
||||
If(usize, Templates, Option<Templates>),
|
||||
For(Vec<TemplateLoop>, Templates),
|
||||
}
|
||||
|
||||
pub enum TemplateLoop {
|
||||
Running,
|
||||
Static,
|
||||
Lines(usize),
|
||||
}
|
||||
|
||||
const TEMPLATE_FALSE: (usize, &str) = (0, "false");
|
||||
const TEMPLATE_TRUE: (usize, &str) = (1, "true");
|
||||
const TEMPLATE_DEBUG: (usize, &str) = (2, "debug");
|
||||
const TEMPLATE_RUNNING: (usize, &str) = (3, "running");
|
||||
const TEMPLATE_STATIC: (usize, &str) = (4, "static");
|
||||
const TEMPLATE_LOOP_ELEM_ID: (usize, &str) = (5, "id");
|
||||
const TEMPLATE_LOOP_ELEM_STATUS: (usize, &str) = (6, "status");
|
||||
const TEMPLATE_LOOP_ELEM_ADDITIONAL: (usize, &str) = (7, "additional");
|
||||
const TEMPLATE_LOOP_ELEM_INFO: (usize, &str) = (8, "info");
|
||||
const TEMPLATE_LOOP_ELEM_REDIRECT: (usize, &str) = (9, "redirect");
|
||||
const TEMPLATE_LOOP_ELEM_DURATION: (usize, &str) = (10, "duration");
|
||||
const TEMPLATE_LINE: (usize, &str) = (11, "line");
|
||||
const TEMPLATE_DEFAULT_VARS: [(usize, &str); 12] = [
|
||||
TEMPLATE_FALSE,
|
||||
TEMPLATE_TRUE,
|
||||
TEMPLATE_DEBUG,
|
||||
TEMPLATE_RUNNING,
|
||||
TEMPLATE_STATIC,
|
||||
TEMPLATE_LOOP_ELEM_ID,
|
||||
TEMPLATE_LOOP_ELEM_STATUS,
|
||||
TEMPLATE_LOOP_ELEM_ADDITIONAL,
|
||||
TEMPLATE_LOOP_ELEM_INFO,
|
||||
TEMPLATE_LOOP_ELEM_REDIRECT,
|
||||
TEMPLATE_LOOP_ELEM_DURATION,
|
||||
TEMPLATE_LINE,
|
||||
];
|
||||
|
||||
impl Template {
|
||||
pub fn gen_int(self) -> (String, HashMap<String, usize>, Vec<String>) {
|
||||
let mut vars = Vec::with_capacity(self.1.variables.len());
|
||||
// make space for all variables
|
||||
vars.resize_with(self.1.variables.len(), String::new);
|
||||
vars[TEMPLATE_TRUE.0] = "true".to_owned();
|
||||
|
||||
// generate html based on the template
|
||||
let mut out = String::new();
|
||||
self.0.gen(&Status::empty(), false, &mut vars, &mut out);
|
||||
(out, self.1.variables, vars)
|
||||
}
|
||||
pub fn gen(&self, status: &Status, debug: bool, mut vars: Vec<String>) -> String {
|
||||
// setup variables
|
||||
if self.1.variables.len() > vars.len() {
|
||||
vars.resize_with(self.1.variables.len(), String::new);
|
||||
}
|
||||
vars[TEMPLATE_DEBUG.0] = if debug {
|
||||
"dbg".to_owned()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
vars[TEMPLATE_RUNNING.0] = if status.0.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
status.0.len().to_string()
|
||||
};
|
||||
vars[TEMPLATE_STATIC.0] = if status.1.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
status.1.len().to_string()
|
||||
};
|
||||
|
||||
// generate html based on the template
|
||||
let mut out = String::new();
|
||||
self.0.gen(status, debug, &mut vars, &mut out);
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TemplateInfo {
|
||||
variables: HashMap<String, usize>,
|
||||
last_end_was_else: Option<bool>,
|
||||
}
|
||||
|
||||
const COMMENT_START: &'static str = "<!-- t: ";
|
||||
const COMMENT_END: &'static str = "-->";
|
||||
impl Template {
|
||||
pub fn parse(
|
||||
mut src: &str,
|
||||
variables: Option<HashMap<String, usize>>,
|
||||
) -> Result<Self, TemplateParseError> {
|
||||
let mut info = TemplateInfo {
|
||||
variables: variables.unwrap_or_else(|| {
|
||||
TEMPLATE_DEFAULT_VARS
|
||||
.iter()
|
||||
.map(|(i, name)| ((*name).to_owned(), *i))
|
||||
.collect()
|
||||
}),
|
||||
last_end_was_else: None,
|
||||
};
|
||||
let templates = Templates::parse(&mut src, &mut info)?;
|
||||
if !src.trim().is_empty() {
|
||||
return Err(TemplateParseError::DidNotReachEOF(src.trim().to_owned()));
|
||||
}
|
||||
Ok(Self(templates, info))
|
||||
}
|
||||
}
|
||||
impl Templates {
|
||||
fn parse(src: &mut &str, info: &mut TemplateInfo) -> Result<Self, TemplateParseError> {
|
||||
let mut out = vec![];
|
||||
'parsing: loop {
|
||||
let comment_start = src.find(COMMENT_START);
|
||||
let mut pre_comment = comment_start.map(|i| &src[..i]).unwrap_or(src);
|
||||
if comment_start.is_some() {
|
||||
pre_comment = pre_comment
|
||||
.strip_suffix('\n')
|
||||
.unwrap_or(pre_comment)
|
||||
.trim_end_matches('\r');
|
||||
}
|
||||
out.push(TemplateType::Html(pre_comment.to_owned()));
|
||||
*src = &src[pre_comment.len()..];
|
||||
|
||||
if comment_start.is_some() {
|
||||
*src = &src[COMMENT_START.len()..];
|
||||
|
||||
if let Some(comment_end) = src.find(COMMENT_END) {
|
||||
// extract comment content
|
||||
let comment_og = src[..comment_end].trim();
|
||||
*src = &src[comment_end + COMMENT_END.len()..].trim_start_matches('\r');
|
||||
*src = src.strip_prefix('\n').unwrap_or(src);
|
||||
// do template things
|
||||
let comment = comment_og.to_lowercase();
|
||||
let mut comment = comment.split(char::is_whitespace);
|
||||
match comment.next().unwrap_or("") {
|
||||
"//" | "#" => {}
|
||||
"end" => {
|
||||
break 'parsing;
|
||||
}
|
||||
"else" if info.last_end_was_else == Some(false) => {
|
||||
info.last_end_was_else = Some(true);
|
||||
break 'parsing;
|
||||
}
|
||||
"raw" => out.push(TemplateType::Raw(
|
||||
info.var(
|
||||
comment
|
||||
.next()
|
||||
.ok_or(TemplateParseError::MissingVariable("raw"))?,
|
||||
),
|
||||
)),
|
||||
"safe" => out.push(TemplateType::Safe(
|
||||
info.var(
|
||||
comment
|
||||
.next()
|
||||
.ok_or(TemplateParseError::MissingVariable("safe"))?,
|
||||
),
|
||||
)),
|
||||
"set" => {
|
||||
let var_name = comment
|
||||
.next()
|
||||
.ok_or(TemplateParseError::MissingVariable("set"))?;
|
||||
let value = comment_og[3..].trim_start()[var_name.len()..].trim_start();
|
||||
out.push(TemplateType::Set(info.var(var_name), value.to_owned()));
|
||||
}
|
||||
"concat" => {
|
||||
out.push(TemplateType::Concat(
|
||||
info.var(
|
||||
comment
|
||||
.next()
|
||||
.ok_or(TemplateParseError::MissingVariable("concat"))?,
|
||||
),
|
||||
info.var(
|
||||
comment
|
||||
.next()
|
||||
.ok_or(TemplateParseError::MissingVariable("concat"))?,
|
||||
),
|
||||
));
|
||||
}
|
||||
"if" => {
|
||||
let prev_lewe = info.last_end_was_else;
|
||||
info.last_end_was_else = Some(false);
|
||||
out.push(TemplateType::If(
|
||||
info.var(
|
||||
comment
|
||||
.next()
|
||||
.ok_or(TemplateParseError::MissingVariable("if"))?,
|
||||
),
|
||||
Templates::parse(src, info)?,
|
||||
if info.last_end_was_else.is_some_and(|v| v) {
|
||||
Some(Templates::parse(src, info)?)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
));
|
||||
info.last_end_was_else = prev_lewe;
|
||||
}
|
||||
"for" => {
|
||||
let mut sources = Vec::new();
|
||||
while let Some(source_name) = comment.next() {
|
||||
sources.push(match source_name {
|
||||
"running" => TemplateLoop::Running,
|
||||
"static" => TemplateLoop::Static,
|
||||
"line" => {
|
||||
TemplateLoop::Lines(info.var(comment.next().ok_or(
|
||||
TemplateParseError::MissingVariable("for line"),
|
||||
)?))
|
||||
}
|
||||
s => {
|
||||
return Err(TemplateParseError::UnknownForSource(
|
||||
s.to_owned(),
|
||||
))
|
||||
}
|
||||
});
|
||||
}
|
||||
if sources.is_empty() {
|
||||
return Err(TemplateParseError::UnknownForSource(String::new()));
|
||||
}
|
||||
out.push(TemplateType::For(sources, Self::parse(src, info)?));
|
||||
}
|
||||
unknown => {
|
||||
return Err(TemplateParseError::InvalidTemplateKeyword(
|
||||
unknown.to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(TemplateParseError::UnclosedComment);
|
||||
}
|
||||
} else {
|
||||
break 'parsing;
|
||||
}
|
||||
}
|
||||
Ok(Self(out))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TemplateParseError {
|
||||
UnclosedComment,
|
||||
InvalidTemplateKeyword(String),
|
||||
UnknownForSource(String),
|
||||
DidNotReachEOF(String),
|
||||
MissingVariable(&'static str),
|
||||
}
|
||||
impl Display for TemplateParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::UnclosedComment => write!(f, "There is an unclosed comment. Make sure all `<!-- t: ` comments are closed by a `-->`"),
|
||||
Self::InvalidTemplateKeyword(kw) => write!(f, "Invalid keyword `{kw}` after `<!-- t: `"),
|
||||
Self::UnknownForSource(s) => write!(f, "For loop can only be used with one or more of `running`, `static`, or `line varname`, not `{s}`"),
|
||||
Self::DidNotReachEOF(s) => write!(f, "Stopped parsing before the end of the file. Remaining, unparsed text: {s}"),
|
||||
Self::MissingVariable(t) => write!(f, "Missing a variable name for `<!-- t: {t}`."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TemplateInfo {
|
||||
fn var(&mut self, v: &str) -> usize {
|
||||
if let Some(i) = self.variables.get(v) {
|
||||
*i
|
||||
} else {
|
||||
let i = self.variables.len();
|
||||
self.variables.insert(v.to_owned(), i);
|
||||
i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Templates {
|
||||
pub fn gen(&self, status: &Status, debug: bool, vars: &mut Vec<String>, out: &mut String) {
|
||||
for template in &self.0 {
|
||||
template.gen(status, debug, vars, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TemplateType {
|
||||
pub fn gen(&self, status: &Status, debug: bool, vars: &mut Vec<String>, out: &mut String) {
|
||||
match self {
|
||||
Self::Html(html) => out.push_str(html),
|
||||
Self::Raw(v) => out.push_str(&vars[*v]),
|
||||
Self::Safe(v) => {
|
||||
html_escape::encode_safe_to_string(&vars[*v], out);
|
||||
}
|
||||
Self::Set(v, val) => vars[*v] = val.clone(),
|
||||
Self::Concat(v, a) => {
|
||||
if *v == *a {
|
||||
let a = &mut vars[*v];
|
||||
a.reserve(a.len());
|
||||
let mut i = 0;
|
||||
while let Some(ch) = a[i..].chars().next() {
|
||||
i += ch.len_utf8();
|
||||
a.push(ch);
|
||||
}
|
||||
} else {
|
||||
let a_temp = std::mem::replace(&mut vars[*a], String::new());
|
||||
vars[*v].push_str(&a_temp);
|
||||
vars[*a] = a_temp;
|
||||
}
|
||||
}
|
||||
Self::If(v, template, else_template) => {
|
||||
if !vars[*v].trim().is_empty() {
|
||||
template.gen(status, debug, vars, out);
|
||||
} else if let Some(else_template) = else_template {
|
||||
else_template.gen(status, debug, vars, out);
|
||||
}
|
||||
}
|
||||
Self::For(src, template) => {
|
||||
for src in src {
|
||||
match src {
|
||||
TemplateLoop::Running => {
|
||||
for (id, (info, redirect, main_status, additional, dur)) in
|
||||
status.0.iter()
|
||||
{
|
||||
vars[TEMPLATE_LOOP_ELEM_ID.0] = id.clone();
|
||||
vars[TEMPLATE_LOOP_ELEM_STATUS.0] = main_status.clone();
|
||||
vars[TEMPLATE_LOOP_ELEM_ADDITIONAL.0] =
|
||||
additional.clone().unwrap_or_else(String::new);
|
||||
vars[TEMPLATE_LOOP_ELEM_DURATION.0] = if let Some(dur) = dur {
|
||||
format!("{}", dur.as_millis())
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
vars[TEMPLATE_LOOP_ELEM_INFO.0] = if *info {
|
||||
html_escape::encode_double_quoted_attribute(&id).into_owned()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
vars[TEMPLATE_LOOP_ELEM_REDIRECT.0] = if *redirect {
|
||||
html_escape::encode_double_quoted_attribute(&id).into_owned()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
template.gen(status, debug, vars, out);
|
||||
}
|
||||
}
|
||||
TemplateLoop::Static => {
|
||||
for (id, (info, redirect, main_status, additional)) in status.1.iter() {
|
||||
vars[TEMPLATE_LOOP_ELEM_ID.0] = id.clone();
|
||||
vars[TEMPLATE_LOOP_ELEM_STATUS.0] = main_status.clone();
|
||||
vars[TEMPLATE_LOOP_ELEM_ADDITIONAL.0] =
|
||||
additional.clone().unwrap_or_else(String::new);
|
||||
vars[TEMPLATE_LOOP_ELEM_DURATION.0] = String::new();
|
||||
vars[TEMPLATE_LOOP_ELEM_INFO.0] = if *info {
|
||||
html_escape::encode_double_quoted_attribute(&id).into_owned()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
vars[TEMPLATE_LOOP_ELEM_REDIRECT.0] = if *redirect {
|
||||
html_escape::encode_double_quoted_attribute(&id).into_owned()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
template.gen(status, debug, vars, out);
|
||||
}
|
||||
}
|
||||
TemplateLoop::Lines(v) => {
|
||||
for line in vars[*v].lines().map(|v| v.to_owned()).collect::<Vec<_>>() {
|
||||
vars[TEMPLATE_LINE.0] = line;
|
||||
template.gen(status, debug, vars, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user