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 /src | |
| parent | 640a433fa8be6c0269db148e08ccf2bfe03957ca (diff) | |
| download | modelthing-ecc4f3ef8a23e3d4143954fdccc5a28a83f9039f.tar.gz modelthing-ecc4f3ef8a23e3d4143954fdccc5a28a83f9039f.zip  | |
basic sandbox wiki editing and creation
Diffstat (limited to 'src')
| -rw-r--r-- | src/bin/mt-webface.rs | 169 | 
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;  | 
