commit ff337b1c073a652acd1fc8c39bbae1409914526f Author: LordMZTE Date: Sat Sep 11 01:24:39 2021 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7f250c5 --- /dev/null +++ b/Cargo.toml @@ -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" diff --git a/assets/lib.js b/assets/lib.js new file mode 100644 index 0000000..91fdabe --- /dev/null +++ b/assets/lib.js @@ -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; diff --git a/assets/runscript.js b/assets/runscript.js new file mode 100644 index 0000000..6a1b870 --- /dev/null +++ b/assets/runscript.js @@ -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; diff --git a/assets/template.html.tera b/assets/template.html.tera new file mode 100644 index 0000000..bbf0f1a --- /dev/null +++ b/assets/template.html.tera @@ -0,0 +1,105 @@ + + + + + + Plan + + + + + {% for i in range(end=repeat) %} + + + + + {% if times %}{% endif %} + + + + + + + + + {% for r in rows %} + + + + {% if times %} + + {% endif %} + + + + + + + + {% endfor %} + +
#{{ locale.time }}{{ locale.mo }}{{ locale.tu }}{{ locale.we }}{{ locale.th }}{{ locale.fr }}
{{ r.idx }}{{ r.time }} + {% if r.mo %} {% if r.mo.room_nr %} + {{ r.mo.room_nr }} + {% endif %} + + {{ r.mo.name }} + {% endif %} + + {% if r.tu %} {% if r.tu.room_nr %} + {{ r.tu.room_nr }} + {% endif %} + + {{ r.tu.name }} + {% endif %} + + {% if r.we %} {% if r.we.room_nr %} + {{ r.we.room_nr }} + {% endif %} + + {{ r.we.name }} + {% endif %} + + {% if r.th %} {% if r.th.room_nr %} + {{ r.th.room_nr }} + {% endif %} + + {{ r.th.name }} + {% endif %} + + {% if r.fr %} {% if r.fr.room_nr %} + {{ r.fr.room_nr }} + {% endif %} + + {{ r.fr.name }} + {% endif %} +
+ {% if i != repeat - 1 %} +
+ {% endif %}{% endfor %} + + diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..24e5eb5 --- /dev/null +++ b/build.rs @@ -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(); +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..1059111 --- /dev/null +++ b/rustfmt.toml @@ -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 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b759d91 --- /dev/null +++ b/src/main.rs @@ -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(()) +} diff --git a/src/renderer.rs b/src/renderer.rs new file mode 100644 index 0000000..a87e62c --- /dev/null +++ b/src/renderer.rs @@ -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> { + 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, + locale: Locale, + repeat: usize, + times: bool, +} + +#[derive(Debug, Serialize)] +struct Row { + idx: usize, + time: String, + mo: Option, + tu: Option, + we: Option, + th: Option, + fr: Option, +} diff --git a/src/script.rs b/src/script.rs new file mode 100644 index 0000000..6ce7ed1 --- /dev/null +++ b/src/script.rs @@ -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 { + 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::(&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, +} + +#[derive(Debug, Deserialize)] +pub struct Days { + pub mo: Vec, + pub tu: Vec, + pub we: Vec, + pub th: Vec, + pub fr: Vec, +} + +impl Days { + pub fn cols(&self) -> [&Vec; 5] { + [&self.mo, &self.tu, &self.we, &self.th, &self.fr] + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Class { + pub room_nr: Option, + 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, +}