diff options
author | bnewbold <bnewbold@robocracy.org> | 2017-01-11 18:26:04 -0800 |
---|---|---|
committer | bnewbold <bnewbold@robocracy.org> | 2017-01-11 18:26:37 -0800 |
commit | ecc4f3ef8a23e3d4143954fdccc5a28a83f9039f (patch) | |
tree | 0d835d82c53e04064b694adbe26e34ef4af86315 | |
parent | 640a433fa8be6c0269db148e08ccf2bfe03957ca (diff) | |
download | modelthing-ecc4f3ef8a23e3d4143954fdccc5a28a83f9039f.tar.gz modelthing-ecc4f3ef8a23e3d4143954fdccc5a28a83f9039f.zip |
basic sandbox wiki editing and creation
-rw-r--r-- | Cargo.lock | 3 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | sandbox/.gitkeep | 0 | ||||
-rw-r--r-- | src/bin/mt-webface.rs | 169 | ||||
-rw-r--r-- | webface/templates/base.html | 5 | ||||
-rw-r--r-- | webface/templates/model_create.html | 23 | ||||
-rw-r--r-- | webface/templates/model_edit.html | 27 | ||||
-rw-r--r-- | webface/templates/model_list.html | 8 | ||||
-rw-r--r-- | webface/templates/model_view.html | 8 |
9 files changed, 237 insertions, 8 deletions
@@ -7,10 +7,12 @@ dependencies = [ "env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "modelica-parser-lalrpop 0.1.0", "pencil 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -487,6 +489,7 @@ dependencies = [ "colored 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "lalrpop 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", "lalrpop-util 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -8,6 +8,8 @@ getopts = "^0.2" clap = "2" toml = "0.2" log = "0.3" +regex = "^0.1" +lazy_static = "^0.2" env_logger = "0.3" pulldown-cmark = "^0.0.8" error-chain = "0.7" diff --git a/sandbox/.gitkeep b/sandbox/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/sandbox/.gitkeep diff --git a/src/bin/mt-webface.rs b/src/bin/mt-webface.rs index 3d17bb5..96bc456 100644 --- a/src/bin/mt-webface.rs +++ b/src/bin/mt-webface.rs @@ -4,16 +4,21 @@ extern crate pencil; extern crate modelthing; extern crate env_logger; extern crate pulldown_cmark; - -#[macro_use] -extern crate log; +extern crate regex; +#[macro_use] extern crate lazy_static; +#[macro_use] extern crate log; use std::env; +use std::fs::{File, create_dir}; +use std::io::Write; use std::collections::BTreeMap; use std::path::Path; use getopts::Options; use pencil::Pencil; use pencil::{Request, PencilResult, Response, HTTPError, PencilError}; +use pencil::{redirect, abort}; +use pencil::method::{Get, Post}; +use regex::Regex; /* This command doesn't use error_chain (or raise errors in general) because the @@ -35,6 +40,13 @@ fn readme(r: &mut Request) -> PencilResult { fn model_list(r: &mut Request) -> PencilResult { let namespace = r.view_args.get("namespace").unwrap(); + if !is_clean_name(namespace) { + return abort(404); + } + if !(namespace == "examples" || namespace == "sandbox") { + // TODO: check if namespace path exists, else 404 + return abort(404); + } let paths = modelthing::search_models(Path::new(namespace)); let l: Vec<Vec<String>> = paths.iter() .map(|x| vec![namespace.to_string(), @@ -46,6 +58,7 @@ fn model_list(r: &mut Request) -> PencilResult { .collect(); let mut context = BTreeMap::new(); context.insert("model_slug_list".to_string(), l); + context.insert("namespace".to_string(), vec![vec![namespace.to_string()]]); return r.app.render_template("model_list.html", &context); } @@ -64,10 +77,13 @@ fn model_view(r: &mut Request) -> PencilResult { context.insert("namespace".to_string(), namespace.to_string()); context.insert("model_slug".to_string(), model_slug.to_string()); context.insert("model_name".to_string(), me.ast.name.clone()); - context.insert("model_description".to_string(), me.ast.description.clone().unwrap_or("".to_string())); + context.insert("model_description".to_string(), + me.ast.description.clone().unwrap_or("".to_string())); context.insert("markdown_html".to_string(), markdown_html); context.insert("markdown".to_string(), me.markdown.clone()); context.insert("modelica".to_string(), format!("{:?}", me.ast)); + context.insert("editable".to_string(), + if namespace == "sandbox" { "true".to_string() } else { "".to_string() }); r.app.render_template("model_view.html", &context) }, Err(_) => Err(PencilError::PenHTTPError(pencil::HTTPError::NotFound)), @@ -82,6 +98,146 @@ fn model_raw(r: &mut Request) -> PencilResult { pencil::helpers::send_from_directory(&model_path, "model.modelica", true) } +// Sends the markdown +fn model_markdown(r: &mut Request) -> PencilResult { + let namespace = r.view_args.get("namespace").unwrap(); + let model_slug = r.view_args.get("model_slug").unwrap(); + let model_path = Path::new(namespace).join(model_slug).to_string_lossy().to_string(); + pencil::helpers::send_from_directory(&model_path, "page.md", true) +} + +fn model_edit(r: &mut Request) -> PencilResult { + let namespace = r.view_args.get("namespace").unwrap().clone(); + let model_slug = r.view_args.get("model_slug").unwrap().clone(); + let model_path = Path::new(&namespace).join(&model_slug); + let mut context = BTreeMap::new(); + if namespace != "sandbox" { + warn!("Tried to write outside of sandbox: {}", namespace); + return abort(403); + } + match modelthing::load_model_entry(model_path.as_path()) { + Ok(me) => { + context.insert("namespace".to_string(), namespace.to_string()); + context.insert("model_slug".to_string(), model_slug.to_string()); + context.insert("model_name".to_string(), me.ast.name.clone()); + context.insert("markdown".to_string(), me.markdown.clone()); + context.insert("modelica".to_string(), format!("{:?}", me.ast)); + match r.method { + Get => { + r.app.render_template("model_edit.html", &context) + }, + Post => { + let modelica = r.form().get("modelica").expect("missing form val: modelica").clone(); + let markdown = r.form().get("markdown").expect("missing form val: markdown").clone(); + + // Check that modelica is valid + if let Err(e) = modelthing::modelica_parser::parse_model(&modelica) { + error!("Modelica code not valid: {:?}", e); + return abort(500); + }; + + { + // overwrite existing + let mut f = File::create(format!("{}/{}/page.md", namespace, model_slug)) + .expect("unable to create wiki file"); + f.write_all(markdown.as_bytes()).expect("unable to write wiki file"); + } + { + // overwrite existing + let mut f = File::create( format!("{}/{}/model.modelica", namespace, model_slug)) + .expect("unable to create modelicafile"); + f.write_all(modelica.as_bytes()).expect("unable to write modelica file"); + } + redirect(&format!("/m/{}/{}/", namespace, model_slug), 301) + }, + _ => { + warn!("Invalid method attempted"); + abort(404) + }, + } + }, + Err(_) => Err(PencilError::PenHTTPError(pencil::HTTPError::NotFound)), + } +} + +fn is_clean_name(s: &str) -> bool { + lazy_static! { + static ref RE: Regex = Regex::new(r"^[0-9A-Za-z_-]+$").unwrap(); + } + RE.is_match(s) +} + +#[test] +fn test_is_clean_name() { + assert_eq!(is_clean_name("name"), true); + assert_eq!(is_clean_name("name.two"), false); + assert_eq!(is_clean_name("name/thing"), false); + assert_eq!(is_clean_name("unicode_α"), false); + assert_eq!(is_clean_name("name_thing"), true); + assert_eq!(is_clean_name("name-thing"), true); + assert_eq!(is_clean_name("1234"), true); +} + +fn model_create(r: &mut Request) -> PencilResult { + let context: BTreeMap<String,String> = BTreeMap::new(); + match r.method { + Get => { + r.app.render_template("model_create.html", &context) + }, + Post => { + let namespace = r.form().get("namespace").expect("missing form val: namespace").clone(); + let model_slug = r.form().get("model_slug").expect("missing form val: model_slug").clone(); + if namespace != "sandbox" { + warn!("Tried to write outside of sandbox: {}", namespace); + return abort(403); + } + if !(is_clean_name(&namespace) && is_clean_name(&model_slug)) { + warn!("Unclean: {} or {}", namespace, model_slug); + return abort(404); + } + + let modelica = &format!( + "model {}\n\n\"blank description, edit me\"\n\tReal a;\nequation\n\ta = 1;\nend {};\n", + model_slug, + model_slug); + let wiki_text = "blank wikitext; edit me!"; + + // Check that modelica is valid + if let Err(e) = modelthing::modelica_parser::parse_model(modelica) { + error!("Modelica code not valid: {:?}", e); + return abort(500); + }; + + // Ensure that namespace directory exists + if let Err(e) = create_dir(format!("{}", namespace)) { + error!("Couldn't create namespace diectory: {:?}", e); + return abort(500); + }; + + if let Err(e) = create_dir(format!("{}/{}", namespace, model_slug)) { + error!("Couldn't create model diectory: {:?}", e); + return abort(500); + }; + + { + let mut f = File::create(format!("{}/{}/page.md", namespace, model_slug)) + .expect("unable to create wiki file"); + f.write_all(wiki_text.as_bytes()).expect("unable to write wiki file"); + } + { + let mut f = File::create( format!("{}/{}/model.modelica", namespace, model_slug)) + .expect("unable to create modelicafile"); + f.write_all(modelica.as_bytes()).expect("unable to write modelica file"); + } + redirect(&format!("/m/{}/{}/", namespace, model_slug), 301) + }, + _ => { + warn!("Invalid method attempted"); + abort(404) + }, + } +} + fn page_not_found(_: HTTPError) -> PencilResult { let mut response = Response::from("404: Not Found"); response.status_code = 404; @@ -149,6 +305,11 @@ fn main() { app.register_template("model_view.html"); app.get("/m/<namespace:string>/<model_slug:string>/", "model_view", model_view); app.get("/m/<namespace:string>/<model_slug:string>/raw/", "model_raw", model_raw); + app.get("/m/<namespace:string>/<model_slug:string>/markdown/", "model_markdown", model_markdown); + app.register_template("model_create.html"); + app.route("/create/", &[Get, Post], "model_create", model_create); + app.register_template("model_edit.html"); + app.route("/m/<namespace:string>/<model_slug:string>/edit/", &[Get, Post], "model_edit", model_edit); let bind = matches.opt_str("bind").unwrap_or("127.0.0.1:5000".to_string()); let bind_str: &str = &bind; diff --git a/webface/templates/base.html b/webface/templates/base.html index 66a0cca..65dafa3 100644 --- a/webface/templates/base.html +++ b/webface/templates/base.html @@ -16,8 +16,9 @@ body { font-family: monospace; font-size: larger; line-height: 1.3; } <br><br> <center style="font-size: 0.9em;"> -<a href="/m/examples/">examples index</a> - -<a href="/readme/">full readme</a> - +<a href="/m/examples/">examples</a> - +<a href="/m/sandbox/">sandbox</a> - +<a href="/readme/">readme</a> - <a href="https://git.bnewbold.net/modelthing">source code</a> </center> <br> diff --git a/webface/templates/model_create.html b/webface/templates/model_create.html new file mode 100644 index 0000000..0c7faa7 --- /dev/null +++ b/webface/templates/model_create.html @@ -0,0 +1,23 @@ +{{# partial content}} +<h1>Create New Model</h1> + +Model will be created with a dummy template modelica model and markdown wiki +page, which you can then edit. + +<br> + +<form action="/create/" method="POST"> + +<table> +<tr><td><b>Namespace: </b> + <td><input type="text" name="namespace" value="sandbox" readonly> +<tr><td><b>Name ("Slug"): </b> + <td><input type="text" name="model_slug" value=""> <i>(alphanumeric with underscores)</i> +</table> +<br> +<input type="submit" value="Create!"></input> + +</form> + +{{/partial}} +{{~> base.html~}} diff --git a/webface/templates/model_edit.html b/webface/templates/model_edit.html new file mode 100644 index 0000000..38aef60 --- /dev/null +++ b/webface/templates/model_edit.html @@ -0,0 +1,27 @@ +{{# partial content}} +<h1 style="margin-bottom: 0.1em;"> + <a href="/m/{{namespace}}/" style="color: black; text-decoration: none;"> + {{ namespace }}</a>/{{ model_name }} +</h1> + +<form action="/m/{{namespace}}/{{model_slug}}/edit/" method="POST"> + +<h3>Modelica Code</h3> +The computable model must conform to a subset of the Modelica language. + +<p><b>WARNING:</b> you won't get errors or help if your code is wrong, +just a 500 error. + +<br><br> +<textarea name="modelica" style="width:50em; height:19em;">{{ modelica }}</textarea> + +<h3>Markdown</h3> +<textarea name="markdown" style="width:50em; height:19em;">{{ markdown }}</textarea> + +<br><br> +<input type="submit" value="Submit!"></input> + +</form> + +{{/partial}} +{{~> base.html~}} diff --git a/webface/templates/model_list.html b/webface/templates/model_list.html index 4c80315..ad4f4f4 100644 --- a/webface/templates/model_list.html +++ b/webface/templates/model_list.html @@ -1,11 +1,17 @@ {{# partial content}} -<h1>Models</h1> +{{#each namespace }} +<h1>Models: {{ this[0] }}</h1> +{{/each}} +{{# if model_slug_list }} <ul> {{#each model_slug_list }} <li><a href="/m/{{ this[0] }}/{{ this[1] }}/">{{ this[1] }}</a> {{/each}} </ul> +{{else}} +None Yet! +{{/if}} {{/partial}} {{~> base.html~}} diff --git a/webface/templates/model_view.html b/webface/templates/model_view.html index 5d307c7..6062f33 100644 --- a/webface/templates/model_view.html +++ b/webface/templates/model_view.html @@ -1,7 +1,13 @@ {{# partial content}} -<h1 style="margin-bottom: 0.1em;">{{ model_name }}</h1> +<h1 style="margin-bottom: 0.1em;"> + <a href="/m/{{namespace}}/" style="color: black; text-decoration: none;"> + {{ namespace }}</a>/{{ model_name }} +</h1> <span style="font-size: 0.9em;"> +{{# if editable }} <a href="edit/">edit</a> - +{{/if }} +<a href="markdown/">markdown</a> - <a href="raw/">modelica</a> - <a href="repr/?format=scheme">scheme</a> - <a href="repr/?format=javascript">javascript</a> - |