This commit is contained in:
LordMZTE 2021-09-11 01:24:39 +02:00
commit ff337b1c07
10 changed files with 390 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

20
Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "stundenplaner"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.43"
deno_core = "0.98.0"
minify-html = { version = "0.6.8", features = ["js-esbuild"] }
serde = { version = "1.0.130", features = ["derive"] }
structopt = "0.3.23"
tera = "1.12.1"
tokio = { version = "1.11.0", features = ["rt-multi-thread", "macros", "fs"] }
[build-dependencies]
# used to create engine snapshot in build script
deno_console = "0.16.0"
deno_core = "0.98.0"

36
assets/lib.js Normal file
View File

@ -0,0 +1,36 @@
// replace default console impl (which uses some remote debugger stuff) with the deno_console one.
globalThis.console = new globalThis.__bootstrap.console.Console((msg, level) =>
Deno.core.print(msg, level > 1)
);
spInt = {};
spInt.Classes = class {
classes = [];
add(name, roomNr) {
if (name == undefined) {
throw "name of class must not be undefined!";
}
this.classes.push({ name: name, room_nr: roomNr });
}
};
spInt.collectClasses = function (day) {
const classes = new spInt.Classes();
getClasses(day, classes);
return classes.classes;
};
spInt.defaultLocale = {
time: "Time",
mo: "Monday",
tu: "Tuesday",
we: "Wednesday",
th: "Thursday",
fr: "Friday",
};
spInt.defaultRepeat = 1;

19
assets/runscript.js Normal file
View File

@ -0,0 +1,19 @@
if (typeof outfile === "undefined") {
throw "`outfile` does not exist!";
}
var obj = {
days: {
mo: spInt.collectClasses("mo"),
tu: spInt.collectClasses("tu"),
we: spInt.collectClasses("we"),
th: spInt.collectClasses("th"),
fr: spInt.collectClasses("fr"),
},
outfile: outfile,
locale: typeof locale === "undefined" ? spInt.defaultLocale : locale,
repeat: typeof repeat === "undefined" ? spInt.defaultRepeat : repeat,
times: typeof times === "undefined" ? [] : times,
};
obj;

105
assets/template.html.tera Normal file
View File

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Plan</title>
<style type="text/css">
body {
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica,
Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
}
table td,
table th {
padding: 4px 10px;
border: 1px solid #dfe2e5;
}
tr {
background-color: #fff;
border-top: 1px solid #c6cbd1;
}
table {
display: block;
width: 100%;
overflow: auto;
border-spacing: 0;
border-collapse: collapse;
}
</style>
</head>
<body>
{% for i in range(end=repeat) %}
<table>
<thead>
<tr>
<th>&#35;</th>
{% if times %}<th align="left">{{ locale.time }}</th>{% endif %}
<th align="left">{{ locale.mo }}</th>
<th align="left">{{ locale.tu }}</th>
<th align="left">{{ locale.we }}</th>
<th align="left">{{ locale.th }}</th>
<th align="left">{{ locale.fr }}</th>
</tr>
</thead>
<tbody>
{% for r in rows %}
<tr>
<td>{{ r.idx }}</td>
{% if times %}
<td>{{ r.time }}</td>
{% endif %}
<td>
{% if r.mo %} {% if r.mo.room_nr %}
<b>{{ r.mo.room_nr }}</b>
{% endif %}
{{ r.mo.name }}
{% endif %}
</td>
<td>
{% if r.tu %} {% if r.tu.room_nr %}
<b>{{ r.tu.room_nr }}</b>
{% endif %}
{{ r.tu.name }}
{% endif %}
</td>
<td>
{% if r.we %} {% if r.we.room_nr %}
<b>{{ r.we.room_nr }}</b>
{% endif %}
{{ r.we.name }}
{% endif %}
</td>
<td>
{% if r.th %} {% if r.th.room_nr %}
<b>{{ r.th.room_nr }}</b>
{% endif %}
{{ r.th.name }}
{% endif %}
</td>
<td>
{% if r.fr %} {% if r.fr.room_nr %}
<b>{{ r.fr.room_nr }}</b>
{% endif %}
{{ r.fr.name }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if i != repeat - 1 %}
<hr />
{% endif %}{% endfor %}
</body>
</html>

24
build.rs Normal file
View File

@ -0,0 +1,24 @@
use deno_core::{include_js_files, Extension, JsRuntime, RuntimeOptions};
use std::path::Path;
fn main() {
let mut js = JsRuntime::new(RuntimeOptions {
will_snapshot: true,
extensions: vec![
deno_console::init(),
Extension::builder()
.js(include_js_files!(
prefix "stundenplaner",
"assets/lib.js",
))
.build(),
],
..Default::default()
});
std::fs::write(
Path::new(&std::env::var("OUT_DIR").unwrap()).join("runtime_state.bin"),
js.snapshot(),
)
.unwrap();
}

12
rustfmt.toml Normal file
View File

@ -0,0 +1,12 @@
unstable_features = true
binop_separator = "Back"
format_code_in_doc_comments = true
format_macro_matchers = true
format_strings = true
imports_layout = "HorizontalVertical"
match_block_trailing_comma = true
merge_imports = true
normalize_comments = true
use_field_init_shorthand = true
use_try_shorthand = true
wrap_comments = true

26
src/main.rs Normal file
View File

@ -0,0 +1,26 @@
use anyhow::Context;
use std::path::PathBuf;
use structopt::StructOpt;
mod renderer;
mod script;
#[derive(StructOpt)]
struct Opt {
infile: PathBuf,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let opt = Opt::from_args();
let s_data = script::run_buildscript(opt.infile).await?;
let path = s_data.outfile.clone();
let rendered = renderer::render(s_data)?;
tokio::fs::write(path, rendered)
.await
.context("failed to write outfile")?;
Ok(())
}

73
src/renderer.rs Normal file
View File

@ -0,0 +1,73 @@
use crate::script::{Class, Locale};
use anyhow::Context;
use serde::Serialize;
use tera::Tera;
use crate::script::ScriptData;
pub fn render(sdata: ScriptData) -> anyhow::Result<Vec<u8>> {
let days = sdata.days;
let max_len = days.cols().iter().map(|c| c.len()).max().unwrap_or(0);
let mut rows = vec![];
for i in 0..max_len {
rows.push(Row {
idx: i + 1,
time: sdata.times.get(i).cloned().unwrap_or_default(),
mo: days.mo.get(i).cloned(),
tu: days.tu.get(i).cloned(),
we: days.we.get(i).cloned(),
th: days.th.get(i).cloned(),
fr: days.fr.get(i).cloned(),
});
}
let rendered = Tera::one_off(
include_str!("../assets/template.html.tera"),
&tera::Context::from_serialize(RenderData {
rows,
times: !sdata.times.is_empty(),
locale: sdata.locale,
repeat: sdata.repeat,
})
.context("failed to serialize template data")?,
false,
)
.context("failed to render template")?;
Ok(minify_html::minify(
rendered.as_bytes(),
&minify_html::Cfg {
do_not_minify_doctype: true,
ensure_spec_compliant_unquoted_attribute_values: false,
keep_closing_tags: true,
keep_html_and_head_opening_tags: true,
keep_spaces_between_attributes: true,
keep_comments: false,
minify_css: true,
minify_js: true,
remove_bangs: true,
remove_processing_instructions: false,
},
))
}
#[derive(Debug, Serialize)]
struct RenderData {
rows: Vec<Row>,
locale: Locale,
repeat: usize,
times: bool,
}
#[derive(Debug, Serialize)]
struct Row {
idx: usize,
time: String,
mo: Option<Class>,
tu: Option<Class>,
we: Option<Class>,
th: Option<Class>,
fr: Option<Class>,
}

73
src/script.rs Normal file
View File

@ -0,0 +1,73 @@
use anyhow::Context;
use deno_core::{v8::Local, JsRuntime, RuntimeOptions, Snapshot};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
pub async fn run_buildscript(infile: PathBuf) -> anyhow::Result<ScriptData> {
let mut js = JsRuntime::new(RuntimeOptions {
startup_snapshot: Some(Snapshot::Static(include_bytes!(concat!(
env!("OUT_DIR"),
"/runtime_state.bin"
)))),
..Default::default()
});
js.execute_script(
"script_init",
std::str::from_utf8(&tokio::fs::read(infile).await?)?,
)
.context("failed to initialize script")?;
let data = js
.execute_script("script_run", include_str!("../assets/runscript.js"))
.context("failed to run script")?;
let mut scope = js.handle_scope();
let local = Local::new(&mut scope, data);
let data = deno_core::serde_v8::from_v8::<ScriptData>(&mut scope, local)
.context("failed to deserialize script data")?;
drop(scope);
js.run_event_loop(false).await?;
Ok(data)
}
#[derive(Debug, Deserialize)]
pub struct ScriptData {
pub days: Days,
pub outfile: PathBuf,
pub locale: Locale,
pub repeat: usize,
pub times: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct Days {
pub mo: Vec<Class>,
pub tu: Vec<Class>,
pub we: Vec<Class>,
pub th: Vec<Class>,
pub fr: Vec<Class>,
}
impl Days {
pub fn cols(&self) -> [&Vec<Class>; 5] {
[&self.mo, &self.tu, &self.we, &self.th, &self.fr]
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Class {
pub room_nr: Option<String>,
pub name: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Locale {
pub time: String,
pub mo: String,
pub tu: String,
pub we: String,
pub th: String,
pub fr: String,
}