use template

This commit is contained in:
Mark 2025-07-21 13:55:21 +02:00
parent 80a86e9f8a
commit 19f51c5732
6 changed files with 632 additions and 160 deletions

173
index.html Normal file
View 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>

View File

@ -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>&lt;!-- t: ... --&gt;</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>

View File

@ -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)),
}

View File

@ -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])

View File

@ -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
View 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);
}
}
}
}
}
}
}
}