aboutsummaryrefslogtreecommitdiffstats
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
parent640a433fa8be6c0269db148e08ccf2bfe03957ca (diff)
downloadmodelthing-ecc4f3ef8a23e3d4143954fdccc5a28a83f9039f.tar.gz
modelthing-ecc4f3ef8a23e3d4143954fdccc5a28a83f9039f.zip
basic sandbox wiki editing and creation
-rw-r--r--Cargo.lock3
-rw-r--r--Cargo.toml2
-rw-r--r--sandbox/.gitkeep0
-rw-r--r--src/bin/mt-webface.rs169
-rw-r--r--webface/templates/base.html5
-rw-r--r--webface/templates/model_create.html23
-rw-r--r--webface/templates/model_edit.html27
-rw-r--r--webface/templates/model_list.html8
-rw-r--r--webface/templates/model_view.html8
9 files changed, 237 insertions, 8 deletions
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
--- /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> -