From ecc4f3ef8a23e3d4143954fdccc5a28a83f9039f Mon Sep 17 00:00:00 2001 From: bnewbold Date: Wed, 11 Jan 2017 18:26:04 -0800 Subject: basic sandbox wiki editing and creation --- Cargo.lock | 3 + Cargo.toml | 2 + sandbox/.gitkeep | 0 src/bin/mt-webface.rs | 169 +++++++++++++++++++++++++++++++++++- webface/templates/base.html | 5 +- webface/templates/model_create.html | 23 +++++ webface/templates/model_edit.html | 27 ++++++ webface/templates/model_list.html | 8 +- webface/templates/model_view.html | 8 +- 9 files changed, 237 insertions(+), 8 deletions(-) create mode 100644 sandbox/.gitkeep create mode 100644 webface/templates/model_create.html create mode 100644 webface/templates/model_edit.html diff --git a/Cargo.lock b/Cargo.lock index 776a291..7cfe8d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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)", ] diff --git a/Cargo.toml b/Cargo.toml index 2522069..53c2c4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 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> = 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 = 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///", "model_view", model_view); app.get("/m///raw/", "model_raw", model_raw); + app.get("/m///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///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; }

-examples index - -full readme - +examples - +sandbox - +readme - source code

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}} +

Create New Model

+ +Model will be created with a dummy template modelica model and markdown wiki +page, which you can then edit. + +
+ +
+ + +
Namespace: + +
Name ("Slug"): + (alphanumeric with underscores) +
+
+ + +
+ +{{/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}} +

+ + {{ namespace }}/{{ model_name }} +

+ +
+ +

Modelica Code

+The computable model must conform to a subset of the Modelica language. + +

WARNING: you won't get errors or help if your code is wrong, +just a 500 error. + +

+ + +

Markdown

+ + +

+ + +
+ +{{/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}} -

Models

+{{#each namespace }} +

Models: {{ this[0] }}

+{{/each}} +{{# if model_slug_list }} +{{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}} -

{{ model_name }}

+

+ + {{ namespace }}/{{ model_name }} +

+{{# if editable }} edit - +{{/if }} +markdown - modelica - scheme - javascript - -- cgit v1.2.3