aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorbnewbold <bnewbold@robocracy.org>2017-01-11 18:26:04 -0800
committerbnewbold <bnewbold@robocracy.org>2017-01-11 18:26:37 -0800
commitecc4f3ef8a23e3d4143954fdccc5a28a83f9039f (patch)
tree0d835d82c53e04064b694adbe26e34ef4af86315 /src
parent640a433fa8be6c0269db148e08ccf2bfe03957ca (diff)
downloadmodelthing-ecc4f3ef8a23e3d4143954fdccc5a28a83f9039f.tar.gz
modelthing-ecc4f3ef8a23e3d4143954fdccc5a28a83f9039f.zip
basic sandbox wiki editing and creation
Diffstat (limited to 'src')
-rw-r--r--src/bin/mt-webface.rs169
1 files changed, 165 insertions, 4 deletions
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;