init
This commit is contained in:
commit
ff337b1c07
10 changed files with 390 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
20
Cargo.toml
Normal file
20
Cargo.toml
Normal 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
36
assets/lib.js
Normal 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
19
assets/runscript.js
Normal 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
105
assets/template.html.tera
Normal 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>#</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
24
build.rs
Normal 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
12
rustfmt.toml
Normal 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
26
src/main.rs
Normal 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
73
src/renderer.rs
Normal 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
73
src/script.rs
Normal 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,
|
||||||
|
}
|
Loading…
Reference in a new issue