From cc16329196cb44618bbbb00c4337ec6447b6b75d Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sat, 28 Mar 2015 19:32:13 +0000 Subject: [PATCH 01/14] Added link to Goldsmith in README. --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 68bca8d..2e99c39 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,18 @@ -## Part of the overall Microworld system +## Part of the overall MicroWorld system *mw-ui* is not a stand-alone project. To use it you also need at least [mw-parser](https://github.com/simon-brooke/mw-parser) and [mw-engine](https://github.com/simon-brooke/mw-engine). There will be other modules in due course. +You can see MicroWorld in action [here](http://www.journeyman.cc/microworld/) - +but please don't be mean to my poor little server. If you want to run big maps +or complex rule-sets, please run it on your own machines. + ## What this is about -Screenshot +Screenshot MicroWorld is a rule driven cellular automaton. What does that mean? Well, it's a two dimensional world made up of squares called **cells**. The world develops in steps, and at each step, each cell is modified by applying the rules. From 4daedee4fc5bb30482b130fce0d4b19b3aa090fc Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 10 Aug 2016 20:06:07 +0100 Subject: [PATCH 02/14] Base template updated so that nicely formatted rule errors are displayed correctly. --- resources/templates/base.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/templates/base.html b/resources/templates/base.html index 35c830b..81255cb 100644 --- a/resources/templates/base.html +++ b/resources/templates/base.html @@ -45,7 +45,9 @@ {% endif %} {% if error %}
-

{{error}}

+
+{{error}}
+      
{% endif %} From 4861ee809b613db9c25bebfb8d47430e088e6652 Mon Sep 17 00:00:00 2001 From: simon Date: Sun, 21 Aug 2016 14:16:18 +0100 Subject: [PATCH 03/14] Standardised header documentation in line with current best practice. --- resources/templates/base.html | 3 +- src/mw_ui/handler.clj | 32 +++++++++++++++++++-- src/mw_ui/layout.clj | 32 +++++++++++++++++++-- src/mw_ui/middleware.clj | 31 +++++++++++++++++++- src/mw_ui/render_world.clj | 33 +++++++++++++++++++++- src/mw_ui/repl.clj | 31 ++++++++++++++++++-- src/mw_ui/routes/home.clj | 36 +++++++++++++++++++++--- src/mw_ui/routes/load.clj | 28 +++++++++++++++++- src/mw_ui/routes/params.clj | 45 +++++++++++++++++++++++------ src/mw_ui/routes/rules.clj | 53 ++++++++++++++++++++++++++--------- src/mw_ui/routes/save.clj | 29 +++++++++++++++++-- src/mw_ui/util.clj | 33 ++++++++++++++++++++-- 12 files changed, 343 insertions(+), 43 deletions(-) diff --git a/resources/templates/base.html b/resources/templates/base.html index 81255cb..2be5573 100644 --- a/resources/templates/base.html +++ b/resources/templates/base.html @@ -59,7 +59,8 @@ Built with LuminusWeb || λ Powered by Clojure || Engineering and hosting by Journeyman || - World generated using MicroWorld Engine + World generated using MicroWorld Engine || + Version {{version}} diff --git a/src/mw_ui/handler.clj b/src/mw_ui/handler.clj index 7e614d1..66d7fe7 100644 --- a/src/mw_ui/handler.clj +++ b/src/mw_ui/handler.clj @@ -1,4 +1,6 @@ -(ns mw-ui.handler +(ns ^{:doc "Set up and tear down the request handler." + :author "Simon Brooke"} + mw-ui.handler (:require [compojure.core :refer [defroutes]] [mw-ui.routes.home :refer [home-routes]] [mw-ui.middleware :refer [load-middleware]] @@ -10,10 +12,34 @@ [selmer.parser :as parser] [environ.core :refer [env]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +;;;; +;;;; This program is free software; you can redistribute it and/or +;;;; modify it under the terms of the GNU General Public License +;;;; as published by the Free Software Foundation; either version 2 +;;;; of the License, or (at your option) any later version. +;;;; +;;;; This program is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with this program; if not, write to the Free Software +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +;;;; USA. +;;;; +;;;; Copyright (C) 2014 Simon Brooke +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defroutes app-routes (route/resources "/") (route/not-found "Not Found")) + (defn init "init will be called once when app is deployed as a servlet on @@ -27,14 +53,15 @@ :async? false ; should be always false for rotor :max-message-per-msecs nil :fn rotor/appender-fn}) - (timbre/set-config! [:shared-appender-config :rotor] {:path "mw_ui.log" :max-size (* 512 1024) :backlog 10}) + (if (env :dev) (parser/cache-off!)) (timbre/info "mw-ui started successfully")) + (defn destroy "destroy will be called when your application shuts down, put any clean up code here" @@ -42,7 +69,6 @@ (timbre/info "mw-ui is shutting down...")) - (def app (app-handler ;; add your application routes here [home-routes app-routes] diff --git a/src/mw_ui/layout.clj b/src/mw_ui/layout.clj index 6a6c224..9861426 100644 --- a/src/mw_ui/layout.clj +++ b/src/mw_ui/layout.clj @@ -1,16 +1,43 @@ -(ns mw-ui.layout +(ns ^{:doc "Layout content as HTML." + :author "Simon Brooke"} + mw-ui.layout (:require [selmer.parser :as parser] [clojure.string :as s] [ring.util.response :refer [content-type response]] [compojure.response :refer [Renderable]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +;;;; +;;;; This program is free software; you can redistribute it and/or +;;;; modify it under the terms of the GNU General Public License +;;;; as published by the Free Software Foundation; either version 2 +;;;; of the License, or (at your option) any later version. +;;;; +;;;; This program is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with this program; if not, write to the Free Software +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +;;;; USA. +;;;; +;;;; Copyright (C) 2014 Simon Brooke +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (def template-path "templates/") + (deftype RenderableTemplate [template params] Renderable (render [this request] (content-type - (->> (assoc params + (->> (assoc (merge params {:version (System/getProperty "mw-ui.version")}) (keyword (s/replace template #".html" "-selected")) "active" :servlet-context (if-let [context (:servlet-context request)] @@ -19,6 +46,7 @@ response) "text/html; charset=utf-8"))) + (defn render [template & [params]] (RenderableTemplate. template params)) diff --git a/src/mw_ui/middleware.clj b/src/mw_ui/middleware.clj index 3440b32..99c1a34 100644 --- a/src/mw_ui/middleware.clj +++ b/src/mw_ui/middleware.clj @@ -1,4 +1,6 @@ -(ns mw-ui.middleware +(ns ^{:doc "In truth, boilerplate from Luminus." + :author "Simon Brooke"} + mw-ui.middleware (:require [taoensso.timbre :as timbre] [selmer.parser :as parser] [environ.core :refer [env]] @@ -6,19 +8,46 @@ [noir-exception.core :refer [wrap-internal-error wrap-exceptions]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +;;;; +;;;; This program is free software; you can redistribute it and/or +;;;; modify it under the terms of the GNU General Public License +;;;; as published by the Free Software Foundation; either version 2 +;;;; of the License, or (at your option) any later version. +;;;; +;;;; This program is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with this program; if not, write to the Free Software +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +;;;; USA. +;;;; +;;;; Copyright (C) 2014 Simon Brooke +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (defn log-request [handler] (fn [req] (timbre/debug req) (handler req))) + (def development-middleware [log-request wrap-error-page wrap-exceptions]) + (def production-middleware [#(wrap-internal-error % :log (fn [e] (timbre/error e)))]) + (defn load-middleware [] (concat (when (env :dev) development-middleware) production-middleware)) diff --git a/src/mw_ui/render_world.clj b/src/mw_ui/render_world.clj index 9d103d5..2d91180 100644 --- a/src/mw_ui/render_world.clj +++ b/src/mw_ui/render_world.clj @@ -1,4 +1,6 @@ -(ns mw-ui.render-world +(ns ^{:doc "Render the state of the world as an HTML table." + :author "Simon Brooke"} + mw-ui.render-world (:require [clojure.java.io :as jio] [mw-engine.core :as engine] [mw-engine.world :as world] @@ -8,21 +10,47 @@ [noir.io :as io] [noir.session :as session])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +;;;; +;;;; This program is free software; you can redistribute it and/or +;;;; modify it under the terms of the GNU General Public License +;;;; as published by the Free Software Foundation; either version 2 +;;;; of the License, or (at your option) any later version. +;;;; +;;;; This program is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with this program; if not, write to the Free Software +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +;;;; USA. +;;;; +;;;; Copyright (C) 2014 Simon Brooke +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn format-css-class [statekey] "Format this statekey, assumed to be a keyword indicating a state in the world, into a CSS class" (subs (str statekey) 1)) + (defn format-image-path "Render this statekey, assumed to be a keyword indicating a state in the world, into a path which should recover the corresponding image file." [statekey] (format "img/tiles/%s.png" (format-css-class statekey))) + (defn format-mouseover [cell] (str cell)) + (defn render-cell "Render this world cell as a Hiccup table cell." [cell] @@ -31,11 +59,13 @@ [:a {:href (format "inspect?x=%d&y=%d" (:x cell) (:y cell))} [:img {:alt (:state cell) :src (format-image-path state)}]]])) + (defn render-world-row "Render this world row as a Hiccup table row." [row] (apply vector (cons :tr (map render-cell row)))) + (defn render-world-table "Render the world implied by the current session as a complete HTML table in a DIV." [] @@ -59,6 +89,7 @@ [:p (str "Generation " generation)]])) + (defn render-inspector "Render in Hiccup format the HTML content of an inspector on this cell." [cell table] diff --git a/src/mw_ui/repl.clj b/src/mw_ui/repl.clj index 3563fac..e034f28 100644 --- a/src/mw_ui/repl.clj +++ b/src/mw_ui/repl.clj @@ -1,9 +1,34 @@ -(ns mw-ui.repl +(ns ^{:doc "In truth, boilerplate from Luminus." + :author "Simon Brooke"} + mw-ui.repl (:use mw-ui.handler ring.server.standalone [ring.middleware file-info file]) - (:gen-class) -) + (:gen-class)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +;;;; +;;;; This program is free software; you can redistribute it and/or +;;;; modify it under the terms of the GNU General Public License +;;;; as published by the Free Software Foundation; either version 2 +;;;; of the License, or (at your option) any later version. +;;;; +;;;; This program is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with this program; if not, write to the Free Software +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +;;;; USA. +;;;; +;;;; Copyright (C) 2014 Simon Brooke +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defonce server (atom nil)) diff --git a/src/mw_ui/routes/home.clj b/src/mw_ui/routes/home.clj index ac779aa..efdda85 100644 --- a/src/mw_ui/routes/home.clj +++ b/src/mw_ui/routes/home.clj @@ -1,4 +1,6 @@ -(ns mw-ui.routes.home +(ns ^{:doc "Routes which serve the main pages of the application." + :author "Simon Brooke"} + mw-ui.routes.home (:use clojure.walk compojure.core) (:require [clojure.pprint :only [pprint]] @@ -15,6 +17,29 @@ [noir.session :as session] [ring.util.response :as response])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +;;;; +;;;; This program is free software; you can redistribute it and/or +;;;; modify it under the terms of the GNU General Public License +;;;; as published by the Free Software Foundation; either version 2 +;;;; of the License, or (at your option) any later version. +;;;; +;;;; This program is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with this program; if not, write to the Free Software +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +;;;; USA. +;;;; +;;;; Copyright (C) 2014 Simon Brooke +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn list-states [] (sort @@ -27,19 +52,22 @@ (layout/render "trusted-content.html" {:title "About MicroWorld" :about-selected "active" - :content (util/md->html "/md/about.md")})) + :content (util/md->html "/md/about.md") + :version (System/getProperty "mw-ui.version")})) (defn docs-page [] (layout/render "docs.html" {:title "Documentation" :parser (util/md->html "/md/mw-parser.md" ) :states (util/list-resources "/img/tiles" #"([0-9a-z-_]+).png") :lessons (util/list-resources "/md/lesson-plans" #"([0-9a-z-_]+).md") - :components ["mw-engine" "mw-parser" "mw-ui"]})) + :components ["mw-engine" "mw-parser" "mw-ui"] + :version (System/getProperty "mw-ui.version")})) (defn home-page [] "Render the home page." (layout/render "trusted-content.html" {:title "Welcome to MicroWorld" - :content (util/md->html "/md/mw-ui.md")})) + :content (util/md->html "/md/mw-ui.md") + :version (System/getProperty "mw-ui.version")})) (defn inspect-page [request] "Open an inspector on the cell at the co-ordinates specified in this request" diff --git a/src/mw_ui/routes/load.clj b/src/mw_ui/routes/load.clj index ce33831..0636ccf 100644 --- a/src/mw_ui/routes/load.clj +++ b/src/mw_ui/routes/load.clj @@ -1,4 +1,6 @@ -(ns mw-ui.routes.load +(ns ^{:doc "Route which handles the upload of worlds/rules from the client." + :author "Simon Brooke"} + mw-ui.routes.load (:use clojure.walk compojure.core) (:require [hiccup.core :refer [html]] @@ -8,6 +10,29 @@ [mw-ui.layout :as layout] )) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +;;;; +;;;; This program is free software; you can redistribute it and/or +;;;; modify it under the terms of the GNU General Public License +;;;; as published by the Free Software Foundation; either version 2 +;;;; of the License, or (at your option) any later version. +;;;; +;;;; This program is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with this program; if not, write to the Free Software +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +;;;; USA. +;;;; +;;;; Copyright (C) 2014 Simon Brooke +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn- upload [file] (io/upload-file "/tmp/" file) @@ -16,6 +41,7 @@ (with-open [eddi (java.io.FileReader. (:tempfile file))] (read))) (str "Successfully loaded your world from " (:filename file)))) + (defn load-page "If no args, show the load form; with args, load a world file from the client. diff --git a/src/mw_ui/routes/params.clj b/src/mw_ui/routes/params.clj index fe05161..f68fcb3 100644 --- a/src/mw_ui/routes/params.clj +++ b/src/mw_ui/routes/params.clj @@ -1,4 +1,6 @@ -(ns mw-ui.routes.params +(ns ^{:doc "Route which serves and handles the parameters page." + :author "Simon Brooke"} + mw-ui.routes.params (:use clojure.walk clojure.java.io compojure.core) @@ -11,6 +13,30 @@ [noir.io :as io] [noir.session :as session])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +;;;; +;;;; This program is free software; you can redistribute it and/or +;;;; modify it under the terms of the GNU General Public License +;;;; as published by the Free Software Foundation; either version 2 +;;;; of the License, or (at your option) any later version. +;;;; +;;;; This program is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with this program; if not, write to the Free Software +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +;;;; USA. +;;;; +;;;; Copyright (C) 2014 Simon Brooke +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (defn- send-params [] {:title "Choose your world" :heightmaps (util/list-resources "/img/heightmaps" #"([0-9a-z-_]+).png") @@ -18,7 +44,8 @@ :rulesets (util/list-resources "/rulesets" #"([0-9a-z-_]+).txt") }) -(defn params-page + +(defn params-page "Handler for params request. If no `request` passed, show empty params form. If `request` is passed, put parameters from request into session and show the world page." @@ -31,25 +58,25 @@ pause (:pause params) rulefile (:ruleset params) rulepath (str "/rulesets/" rulefile ".txt")] - (if (not= map "") - (session/put! :world - (heightmap/apply-heightmap + (if (not= map "") + (session/put! :world + (heightmap/apply-heightmap (io/get-resource (str "/img/heightmaps/" map ".png"))))) (when (not= rulefile "") (session/put! :rule-text (io/slurp-resource rulepath)) (session/put! :rules (compiler/compile-file (io/get-resource rulepath)))) (if (not= pause "") (session/put! :pause pause)) - (layout/render "params.html" - (merge (send-params) + (layout/render "params.html" + (merge (send-params) {:r rulefile :h map :message "Your parameters are saved, now look at your world"}))) (catch Exception e (let [params (keywordize-keys (:form-params request))] - (layout/render "params.html" + (layout/render "params.html" (merge (send-params) - {:title "Choose your world" + {:title "Choose your world" :r (:ruleset params) :h (:heightmap params) :message "Your paramters are not saved" diff --git a/src/mw_ui/routes/rules.clj b/src/mw_ui/routes/rules.clj index 7512605..b49a4c8 100644 --- a/src/mw_ui/routes/rules.clj +++ b/src/mw_ui/routes/rules.clj @@ -1,4 +1,6 @@ -(ns mw-ui.routes.rules +(ns ^{:doc "Route which serves and handles the rules page." + :author "Simon Brooke"} + mw-ui.routes.rules (:use clojure.walk compojure.core) (:require [hiccup.core :refer [html]] @@ -10,31 +12,56 @@ [noir.session :as session] [ring.util.response :as response])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +;;;; +;;;; This program is free software; you can redistribute it and/or +;;;; modify it under the terms of the GNU General Public License +;;;; as published by the Free Software Foundation; either version 2 +;;;; of the License, or (at your option) any later version. +;;;; +;;;; This program is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with this program; if not, write to the Free Software +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +;;;; USA. +;;;; +;;;; Copyright (C) 2014 Simon Brooke +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (defn process-rules-request [request] (let [src (:src (keywordize-keys (:form-params request)))] - (try - (cond src + (try + (cond src (let [rules (compiler/compile-string src)] {:rule-text src :rules rules - :message (str "Successfully compiled " - (count rules) + :message (str "Successfully compiled " + (count rules) " rules") }) - true {:rule-text (or - (session/get :rule-text) + true {:rule-text (or + (session/get :rule-text) (io/slurp-resource "/rulesets/basic.txt")) :message "No rules found in request; loading defaults"}) - (catch Exception e + (catch Exception e {:rule-text src :message "An error occurred during compilation" :error (str (.getName (.getClass e)) ": " (.getMessage e))})))) -(defn rules-page + +(defn rules-page "Request handler for the `rules` request. If the `request` contains a value - for `:src`, treat that as rule source and try to compile it. If compilation - succeeds, stash the compiled rules and the rule text on the session, and - provide feedback; if not, provide feedback. + for `:src`, treat that as rule source and try to compile it. If compilation + succeeds, stash the compiled rules and the rule text on the session, and + provide feedback; if not, provide feedback. If `request` doesn't contain a value for `:src`, load basic rule source from the session or from `resources/rulesets/basic.txt` and pass that back." @@ -44,7 +71,7 @@ (session/put! :rules (:rules processed))) (if (:rule-text processed) (session/put! :rule-text (:rule-text processed))) - (layout/render "rules.html" + (layout/render "rules.html" (merge {:title "Edit Rules"} processed)))) ([] (rules-page nil))) diff --git a/src/mw_ui/routes/save.clj b/src/mw_ui/routes/save.clj index 26f3688..d75417a 100644 --- a/src/mw_ui/routes/save.clj +++ b/src/mw_ui/routes/save.clj @@ -1,7 +1,32 @@ -(ns mw-ui.routes.save +(ns ^{:doc "Route which handles the saving of world state the client." + :author "Simon Brooke"} + mw-ui.routes.save (:require [clojure.pprint :as pretty :only [pprint]] [noir.session :as session] - [ring.util.response :as response])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +;;;; +;;;; This program is free software; you can redistribute it and/or +;;;; modify it under the terms of the GNU General Public License +;;;; as published by the Free Software Foundation; either version 2 +;;;; of the License, or (at your option) any later version. +;;;; +;;;; This program is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with this program; if not, write to the Free Software +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +;;;; USA. +;;;; +;;;; Copyright (C) 2014 Simon Brooke +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn save-page [] "Save the current world to the browser, using our own custom mime-type in diff --git a/src/mw_ui/util.clj b/src/mw_ui/util.clj index 46def67..fa59383 100644 --- a/src/mw_ui/util.clj +++ b/src/mw_ui/util.clj @@ -1,8 +1,34 @@ -(ns mw-ui.util +(ns ^{:doc "Utility functions used by other namespaces in this package." + :author "Simon Brooke"} + mw-ui.util (:require [noir.io :as io] [noir.session :as session] [markdown.core :as md])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +;;;; +;;;; This program is free software; you can redistribute it and/or +;;;; modify it under the terms of the GNU General Public License +;;;; as published by the Free Software Foundation; either version 2 +;;;; of the License, or (at your option) any later version. +;;;; +;;;; This program is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with this program; if not, write to the Free Software +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +;;;; USA. +;;;; +;;;; Copyright (C) 2014 Simon Brooke +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (defn md->html "reads a markdown file from public/md and returns an HTML string" [filename] @@ -10,12 +36,13 @@ (io/slurp-resource filename) (md/md-to-html-string))) + (defn list-resources [directory pattern] "List resource files matching `pattern` in `directory`." - (let + (let [path (str (io/resource-path) directory)] (session/put! :list-resources-path path) (sort - (remove nil? + (remove nil? (map #(first (rest (re-matches pattern (.getName %)))) (file-seq (clojure.java.io/file path))))))) From c18b74fef6926f717af20575d73934ceb20a0f6a Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 27 Dec 2016 16:02:53 +0000 Subject: [PATCH 04/14] Dockerisation. --- Dockerfile | 3 +++ project.clj | 5 ++++- src/mw_ui/routes/save.clj | 8 ++++---- 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..afcd747 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM tomcat:alpine +COPY target/microworld.war $CATALINA_HOME/webapps/ + diff --git a/project.clj b/project.clj index e280238..a3a3dfe 100644 --- a/project.clj +++ b/project.clj @@ -23,7 +23,10 @@ :repl-options {:init-ns mw-ui.repl} :plugins [[lein-ring "0.8.11"] [lein-environ "0.5.0"] - [lein-marginalia "0.7.1"]] + [lein-marginalia "0.7.1"] + [io.sarnowski/lein-docker "1.1.0"]] + :docker {:image-name "simonbrooke/microworld" + :dockerfile "Dockerfile"} :ring {:handler mw-ui.handler/app :init mw-ui.handler/init :destroy mw-ui.handler/destroy diff --git a/src/mw_ui/routes/save.clj b/src/mw_ui/routes/save.clj index d75417a..407495b 100644 --- a/src/mw_ui/routes/save.clj +++ b/src/mw_ui/routes/save.clj @@ -3,6 +3,7 @@ mw-ui.routes.save (:require [clojure.pprint :as pretty :only [pprint]] [noir.session :as session] + [noir.response :as response])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; @@ -33,8 +34,7 @@ an attempt to prevent the browser trying to do anything clever with it. Note that it is saved as a raw Clojure data structure, not as XML or any proprietary format." - (response/header - (response/response - (with-out-str (pretty/pprint (session/get :world)))) - "Content-Type" "application/journeyman-mwm; charset=utf-8")) + (response/content-type + "application/journeyman-mwm; charset=utf-8" + (with-out-str (pretty/pprint (session/get :world))))) From b07f69fb5186bcc14b0879e5d2c434aaec93914e Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Mon, 1 Jun 2020 10:46:58 +0100 Subject: [PATCH 05/14] Upversioned to "0.1.6-SNAPSHOT" in line with mw-engine --- .gitignore | 2 + docs/uberdoc.html | 3690 +++++++++++++++++++ project.clj | 29 +- resources/public/docs/mw-engine | 1 + resources/public/docs/mw-parser | 1 + resources/public/docs/mw-ui | 1 + resources/public/img/heightmaps/mgi_med.png | Bin 0 -> 27805 bytes 7 files changed, 3710 insertions(+), 14 deletions(-) create mode 100644 docs/uberdoc.html create mode 120000 resources/public/docs/mw-engine create mode 120000 resources/public/docs/mw-parser create mode 120000 resources/public/docs/mw-ui create mode 100644 resources/public/img/heightmaps/mgi_med.png diff --git a/.gitignore b/.gitignore index cf11b31..7e819cf 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ resources/public/docs/mw-*/uberdoc.html # Artefacts: mw_ui.log pom.xml + +buildall.tmp.*/ diff --git a/docs/uberdoc.html b/docs/uberdoc.html new file mode 100644 index 0000000..4d7c64b --- /dev/null +++ b/docs/uberdoc.html @@ -0,0 +1,3690 @@ + +mw-ui -- Marginalia

mw-ui

0.1.5


Web-based user interface for MicroWorld

+

dependencies

org.clojure/clojure
1.6.0
mw-engine
0.1.5
mw-parser
0.1.5
lib-noir
0.8.4
ring-server
0.3.1
selmer
0.6.8
hiccup
1.0.5
com.taoensso/timbre
3.2.1
com.taoensso/tower
2.0.2
markdown-clj
0.9.44
environ
0.5.0
noir-exception
0.2.2



(this space intentionally left almost blank)
 

Set up and tear down the request handler.

+
(ns ^{:doc 
+      :author "Simon Brooke"}
+  mw-ui.handler
+  (:require [compojure.core :refer [defroutes]]
+            [mw-ui.routes.home :refer [home-routes]]
+            [mw-ui.middleware :refer [load-middleware]]
+            [noir.response :refer [redirect]]
+            [noir.util.middleware :refer [app-handler]]
+            [compojure.route :as route]
+            [taoensso.timbre :as timbre]
+            [taoensso.timbre.appenders.rotor :as rotor]
+            [selmer.parser :as parser]
+            [environ.core :refer [env]]))

mw-ui: a servlet user/visualisation interface for MicroWorld.

+ +

This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version.

+ +

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details.

+ +

You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA.

+ +

Copyright (C) 2014 Simon Brooke

+
+
(defroutes app-routes
+  (route/resources "/")
+  (route/not-found "Not Found"))

init will be called once when + app is deployed as a servlet on + an app server such as Tomcat + put any initialization code here

+
(defn init
+  []
+  (timbre/set-config!
+    [:appenders :rotor]
+    {:min-level :info
+     :enabled? true
+     :async? false ; should be always false for rotor
+     :max-message-per-msecs nil
+     :fn rotor/appender-fn})
+  (timbre/set-config!
+    [:shared-appender-config :rotor]
+    {:path "mw_ui.log" :max-size (* 512 1024) :backlog 10})
+  (if (env :dev) (parser/cache-off!))
+  (timbre/info "mw-ui started successfully"))

destroy will be called when your application + shuts down, put any clean up code here

+
(defn destroy
+  []
+  (timbre/info "mw-ui is shutting down..."))
+
(def app (app-handler
+           ;; add your application routes here
+           [home-routes app-routes]
+           ;; add custom middleware here
+           :middleware (load-middleware)
+           ;; timeout sessions after 30 minutes
+           :session-options {:timeout (* 60 30)
+                             :timeout-response (redirect "/")}
+           ;; add access rules here
+           :access-rules []
+           ;; serialize/deserialize the following data formats
+           ;; available formats:
+           ;; :json :json-kw :yaml :yaml-kw :edn :yaml-in-html
+           :formats [:json-kw :edn]))
 

Layout content as HTML.

+
(ns ^{:doc 
+      :author "Simon Brooke"}
+  mw-ui.layout
+  (:require [selmer.parser :as parser]
+            [clojure.string :as s]
+            [ring.util.response :refer [content-type response]]
+            [compojure.response :refer [Renderable]]))

mw-ui: a servlet user/visualisation interface for MicroWorld.

+ +

This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version.

+ +

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details.

+ +

You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA.

+ +

Copyright (C) 2014 Simon Brooke

+
+
(def template-path "templates/")
+
(deftype RenderableTemplate [template params]
+  Renderable
+  (render [this request]
+    (content-type
+      (->> (assoc (merge params {:version (System/getProperty "mw-ui.version")})
+                  (keyword (s/replace template #".html" "-selected")) "active"
+                  :servlet-context
+                  (if-let [context (:servlet-context request)]
+                    (.getContextPath context)))
+        (parser/render-file (str template-path template))
+        response)
+      "text/html; charset=utf-8")))
+
(defn render [template & [params]]
+  (RenderableTemplate. template params))
 

In truth, boilerplate from Luminus.

+
(ns ^{:doc 
+      :author "Simon Brooke"}
+  mw-ui.middleware
+  (:require [taoensso.timbre :as timbre]
+            [selmer.parser :as parser]
+            [environ.core :refer [env]]
+            [selmer.middleware :refer [wrap-error-page]]
+            [noir-exception.core
+              :refer [wrap-internal-error wrap-exceptions]]))

mw-ui: a servlet user/visualisation interface for MicroWorld.

+ +

This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version.

+ +

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details.

+ +

You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA.

+ +

Copyright (C) 2014 Simon Brooke

+
+
(defn log-request [handler]
+  (fn [req]
+    (timbre/debug req)
+    (handler req)))
+
(def development-middleware
+  [log-request
+   wrap-error-page
+   wrap-exceptions])
+
(def production-middleware
+  [#(wrap-internal-error % :log (fn [e] (timbre/error e)))])
+
(defn load-middleware []
+  (concat (when (env :dev) development-middleware)
+          production-middleware))
 

Render the state of the world as an HTML table.

+
(ns ^{:doc 
+      :author "Simon Brooke"}
+  mw-ui.render-world
+  (:require [clojure.java.io :as jio]
+            [mw-engine.core :as engine]
+            [mw-engine.world :as world]
+            [mw-engine.heightmap :as heightmap]
+            [mw-parser.bulk :as compiler]
+            [hiccup.core :refer [html]]
+            [noir.io :as io]
+            [noir.session :as session]))

mw-ui: a servlet user/visualisation interface for MicroWorld.

+ +

This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version.

+ +

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details.

+ +

You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA.

+ +

Copyright (C) 2014 Simon Brooke

+
+
(defn format-css-class [statekey]
+  "Format this statekey, assumed to be a keyword indicating a state in the
+   world, into a CSS class"
+  (subs (str statekey) 1))

Render this statekey, assumed to be a keyword indicating a state in the + world, into a path which should recover the corresponding image file.

+
(defn format-image-path
+  [statekey]
+  (format "img/tiles/%s.png" (format-css-class statekey)))
+
(defn format-mouseover [cell]
+  (str cell))

Render this world cell as a Hiccup table cell.

+
(defn render-cell
+  [cell]
+  (let [state (:state cell)]
+    [:td {:class (format-css-class state) :title (format-mouseover cell)}
+     [:a {:href (format "inspect?x=%d&y=%d" (:x cell) (:y cell))}
+      [:img {:alt (:state cell) :src (format-image-path state)}]]]))

Render this world row as a Hiccup table row.

+
(defn render-world-row
+  [row]
+  (apply vector (cons :tr (map render-cell row))))

Render the world implied by the current session as a complete HTML table in a DIV.

+
(defn render-world-table
+  []
+  (let [world (or (session/get :world)
+                  (heightmap/apply-heightmap
+                      (io/get-resource "/img/heightmaps/small_hill.png")))
+        rules (or (session/get :rules)
+                  (do (session/put! :rules
+                                    (compiler/compile-file
+                                      (io/get-resource "/rulesets/basic.txt")))
+                    (session/get :rules)))
+        generation (inc (or (session/get :generation) 0))
+        w2 (engine/transform-world world rules)
+        ]
+    (session/put! :world w2)
+    (session/put! :generation generation)
+    [:div {:class "world"}
+      (apply vector
+                 (cons :table
+                       (map render-world-row w2)))
+      [:p
+       (str "Generation " generation)]]))

Render in Hiccup format the HTML content of an inspector on this cell.

+
(defn render-inspector
+  [cell table]
+  [:table {:class "music-ruled"}
+   [:tr
+    [:td {:colspan 2 :style "text-align: center;"}
+     [:img {:src (str "img/tiles/" (name (:state cell)) ".png")
+            :width 64
+            :height 64}]]]
+   [:tr [:th "Key"][:th "Value"]]
+   (map #(vector :tr (vector :th %)(vector :td (cell %))) (keys cell))])
 

In truth, boilerplate from Luminus.

+
(ns ^{:doc 
+      :author "Simon Brooke"}
+  mw-ui.repl
+  (:use mw-ui.handler
+        ring.server.standalone
+        [ring.middleware file-info file])
+  (:gen-class))

mw-ui: a servlet user/visualisation interface for MicroWorld.

+ +

This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version.

+ +

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details.

+ +

You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA.

+ +

Copyright (C) 2014 Simon Brooke

+
+
(defonce server (atom nil))
+
(defn get-handler []
+  ;; #'app expands to (var app) so that when we reload our code,
+  ;; the server is forced to re-resolve the symbol in the var
+  ;; rather than having its own copy. When the root binding
+  ;; changes, the server picks it up without having to restart.
+  (-> #'app
+      ; Makes static assets in $PROJECT_DIR/resources/public/ available.
+      (wrap-file "resources")
+      ; Content-Type, Content-Length, and Last Modified headers for files in body
+      (wrap-file-info)))

used for starting the server in development mode from REPL

+
(defn start-server
+  [& [port]]
+  (let [port (if port (Integer/parseInt port) 3000)]
+    (reset! server
+            (serve (get-handler)
+                   {:port port
+                    :init init
+                    :auto-reload? true
+                    :destroy destroy
+                    :join? false}))
+    (println (str "You can view the site at http://localhost:" port))))
+
(defn stop-server []
+  (.stop @server)
+  (reset! server nil))
+
(defn -main []
+  (start-server))
 

Routes which serve the main pages of the application.

+
(ns ^{:doc 
+      :author "Simon Brooke"}
+  mw-ui.routes.home
+  (:use clojure.walk
+        compojure.core)
+  (:require [clojure.pprint :only [pprint]]
+            [hiccup.core :refer [html]]
+            [mw-engine.utils :as engine-utils]
+            [mw-ui.layout :as layout]
+            [mw-ui.render-world :as world]
+            [mw-ui.routes.load :as load]
+            [mw-ui.routes.rules :as rules]
+            [mw-ui.routes.params :as params]
+            [mw-ui.routes.save :as save]
+            [mw-ui.util :as util]
+            [noir.io :as io]
+            [noir.session :as session]
+            [ring.util.response :as response]))

mw-ui: a servlet user/visualisation interface for MicroWorld.

+ +

This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version.

+ +

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details.

+ +

You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA.

+ +

Copyright (C) 2014 Simon Brooke

+
+
(defn list-states []
+  (sort
+    (filter #(not (nil? %))
+            (map #(first (rest (re-matches #"([0-9a-z-]+).png" (.getName %))))
+                 (file-seq (clojure.java.io/file "resources/public/img/tiles"))))))
+
(defn about-page []
+  (layout/render "trusted-content.html"
+                 {:title "About MicroWorld"
+                  :about-selected "active"
+                  :content (util/md->html "/md/about.md")
+                  :version (System/getProperty "mw-ui.version")}))
+
(defn docs-page []
+  (layout/render "docs.html" {:title "Documentation"
+                              :parser (util/md->html "/md/mw-parser.md" )
+                              :states (util/list-resources "/img/tiles" #"([0-9a-z-_]+).png")
+                              :lessons (util/list-resources "/md/lesson-plans"  #"([0-9a-z-_]+).md")
+                              :components ["mw-engine" "mw-parser" "mw-ui"]
+                              :version (System/getProperty "mw-ui.version")}))
+
(defn home-page []
+  "Render the home page."
+  (layout/render "trusted-content.html" {:title "Welcome to MicroWorld"
+                              :content (util/md->html "/md/mw-ui.md")
+                              :version (System/getProperty "mw-ui.version")}))
+
(defn inspect-page [request]
+  "Open an inspector on the cell at the co-ordinates specified in this request"
+  (let [params (keywordize-keys (:params request))
+        xs (:x params)
+        ys (:y params)
+        x (if (seq xs) (read-string xs) 0)
+        y (if (seq ys) (read-string ys) 0)
+        world (session/get :world)
+        cell (engine-utils/get-cell world x y)
+        state (:state params)]
+    (cond state
+      (do
+        (session/put! :world (engine-utils/set-property world cell :state (keyword state)))
+        (response/redirect "world"))
+      true
+      (layout/render "inspector.html"
+                     {:title (format "Inspect cell at %d, %d" x y)
+                      :content (html (world/render-inspector cell world))
+                      :cell cell
+                      :x (:x cell)
+                      :y (:y cell)
+                      :states (util/list-resources
+                                "/img/tiles" #"([0-9a-z-_]+).png")}))))

Render the markdown page specified in this request, if any. Probably undesirable, + should be removed.

+
(defn md-page
+  [request]
+  (let [params (keywordize-keys (:params request))
+        content (or (:content params) "missing.md")]
+    (layout/render "trusted-content.html"
+                   {:title "Welcome to MicroWorld"
+                    :content (util/md->html (str "/md/" content))})))
+
(defn world-page []
+  "Render the world in the current session (or a default one if none)."
+  (layout/render "trusted-content.html"
+                 {:title "Watch your world grow"
+                 :world-selected "active"
+                 :content (html (world/render-world-table))
+                 :pause (or (session/get :pause) 5)
+                 :maybe-refresh "refresh"}))
+
(defroutes home-routes
+  (GET  "/" [] (home-page))
+  (GET  "/about" [] (about-page))
+  (GET  "/docs"  [] (docs-page))
+  (GET  "/inspect" request (inspect-page request))
+  (POST "/inspect" request (inspect-page request))
+  (GET  "/load" [] (load/load-page))
+  (POST "/load" request (load/load-page request))
+  (GET  "/md" request (md-page request))
+  (GET  "/params" [] (params/params-page))
+  (POST "/params" request (params/params-page request))
+  (GET  "/rules" request (rules/rules-page request))
+  (POST "/rules" request (rules/rules-page request))
+  (GET  "/saved-map.mwm" [] (save/save-page))
+  (GET  "/world"  [] (world-page)))
 

Route which handles the upload of worlds/rules from the client.

+
(ns ^{:doc 
+      :author "Simon Brooke"}
+  mw-ui.routes.load
+  (:use clojure.walk
+        compojure.core)
+  (:require [hiccup.core :refer [html]]
+            [noir.io :as io]
+            [noir.session :as session]
+            [ring.util.response :as response]
+            [mw-ui.layout :as layout]))

mw-ui: a servlet user/visualisation interface for MicroWorld.

+ +

This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version.

+ +

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details.

+ +

You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA.

+ +

Copyright (C) 2014 Simon Brooke

+
+
(defn- upload [file]
+  (io/upload-file "/tmp/" file)
+  (cond
+   (session/put! :world
+                (with-open [eddi (java.io.FileReader. (:tempfile file))] (read)))
+    (str "Successfully loaded your world from " (:filename file))))

If no args, show the load form; with args, load a world file from the client.

+ +

NOTE that this reads a Clojure form from an untrusted client and should almost + certainly NOT be enabled on a public-facing site, especially not on the Internet.

+ +

TODO doesn't work yet.

+
(defn load-page
+  ([]
+   (load-page nil))
+  ([request]
+     (let [params (keywordize-keys (:params request))
+           file (:file request)]
+       (try
+         (layout/render "load.html"
+                        {:title "Load World"
+                         :message (upload file)})
+         (catch Exception any
+               (layout/render "load.html"
+                            {:title "Load World"
+                             :message "Failed to load your world"
+                             :error (str (.getName (.getClass any)) ": " (.getMessage any))}))))))
 

Route which serves and handles the parameters page.

+
(ns ^{:doc 
+      :author "Simon Brooke"}
+  mw-ui.routes.params
+  (:use clojure.walk
+        clojure.java.io
+        compojure.core)
+  (:require [hiccup.core :refer [html]]
+            [mw-engine.heightmap :as heightmap]
+            [mw-parser.bulk :as compiler]
+            [mw-ui.layout :as layout]
+            [mw-ui.util :as util]
+            [mw-ui.render-world :as world]
+            [noir.io :as io]
+            [noir.session :as session]))

mw-ui: a servlet user/visualisation interface for MicroWorld.

+ +

This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version.

+ +

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details.

+ +

You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA.

+ +

Copyright (C) 2014 Simon Brooke

+
+
(defn- send-params []
+  {:title "Choose your world"
+   :heightmaps (util/list-resources "/img/heightmaps" #"([0-9a-z-_]+).png")
+   :pause (or (session/get :pause) 5)
+   :rulesets (util/list-resources "/rulesets" #"([0-9a-z-_]+).txt")
+   })

Handler for params request. If no request passed, show empty params form. + If request is passed, put parameters from request into session and show + the world page.

+
(defn params-page
+  ([]
+    (layout/render "params.html" (send-params)))
+  ([request]
+    (try
+      (let [params (keywordize-keys (:form-params request))
+            map (:heightmap params)
+            pause (:pause params)
+            rulefile (:ruleset params)
+            rulepath (str "/rulesets/" rulefile ".txt")]
+        (if (not= map "")
+          (session/put! :world
+                        (heightmap/apply-heightmap
+                          (io/get-resource (str "/img/heightmaps/" map ".png")))))
+        (when (not= rulefile "")
+          (session/put! :rule-text (io/slurp-resource rulepath))
+          (session/put! :rules (compiler/compile-file (io/get-resource rulepath))))
+        (if (not= pause "")
+          (session/put! :pause pause))
+        (layout/render "params.html"
+                       (merge (send-params)
+                              {:r rulefile
+                               :h map
+                               :message "Your parameters are saved, now look at your world"})))
+      (catch Exception e
+        (let [params (keywordize-keys (:form-params request))]
+          (layout/render "params.html"
+                         (merge (send-params)
+                                {:title "Choose your world"
+                                 :r (:ruleset params)
+                                 :h (:heightmap params)
+                                 :message "Your paramters are not saved"
+                                 :error (str (.getName (.getClass e)) ": " (.getMessage e) "; " params)})))))))
 

Route which serves and handles the rules page.

+
(ns ^{:doc 
+      :author "Simon Brooke"}
+  mw-ui.routes.rules
+  (:use clojure.walk
+        compojure.core)
+  (:require [hiccup.core :refer [html]]
+            [mw-parser.bulk :as compiler]
+            [mw-ui.layout :as layout]
+            [mw-ui.util :as util]
+            [mw-ui.render-world :as world]
+            [noir.io :as io]
+            [noir.session :as session]
+            [ring.util.response :as response]))

mw-ui: a servlet user/visualisation interface for MicroWorld.

+ +

This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version.

+ +

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details.

+ +

You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA.

+ +

Copyright (C) 2014 Simon Brooke

+
+
(defn process-rules-request
+  [request]
+  (let [src (:src (keywordize-keys (:form-params request)))]
+      (try
+        (cond src
+          (let [rules (compiler/compile-string src)]
+            {:rule-text src
+             :rules rules
+             :message (str "Successfully compiled "
+                           (count rules)
+                           " rules")           })
+          true {:rule-text (or
+                             (session/get :rule-text)
+                             (io/slurp-resource "/rulesets/basic.txt"))
+                :message "No rules found in request; loading defaults"})
+        (catch Exception e
+          {:rule-text src
+           :message "An error occurred during compilation"
+           :error (str (.getName (.getClass e)) ": " (.getMessage e))}))))

Request handler for the rules request. If the request contains a value + for :src, treat that as rule source and try to compile it. If compilation + succeeds, stash the compiled rules and the rule text on the session, and + provide feedback; if not, provide feedback.

+ +

If request doesn't contain a value for :src, load basic rule source from + the session or from resources/rulesets/basic.txt and pass that back.

+
(defn rules-page
+  ([request]
+    (let [processed (process-rules-request request)]
+      (if (:rules processed)
+        (session/put! :rules (:rules processed)))
+      (if (:rule-text processed)
+        (session/put! :rule-text (:rule-text processed)))
+      (layout/render "rules.html"
+                     (merge {:title "Edit Rules"} processed))))
+  ([]
+    (rules-page nil)))
 

Route which handles the saving of world state the client.

+
(ns ^{:doc 
+      :author "Simon Brooke"}
+  mw-ui.routes.save
+  (:require [clojure.pprint :as pretty :only [pprint]]
+            [noir.session :as session]
+            [noir.response :as response]))

mw-ui: a servlet user/visualisation interface for MicroWorld.

+ +

This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version.

+ +

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details.

+ +

You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA.

+ +

Copyright (C) 2014 Simon Brooke

+
+
(defn save-page []
+  "Save the current world to the browser, using our own custom mime-type in
+   an attempt to prevent the browser trying to do anything clever with it.
+   Note that it is saved as a raw Clojure data structure, not as XML or
+   any proprietary format."
+  (response/content-type
+    "application/journeyman-mwm; charset=utf-8"
+    (with-out-str (pretty/pprint  (session/get :world)))))
 

Utility functions used by other namespaces in this package.

+
(ns ^{:doc 
+      :author "Simon Brooke"}
+  mw-ui.util
+  (:require [noir.io :as io]
+            [noir.session :as session]
+            [markdown.core :as md]))

mw-ui: a servlet user/visualisation interface for MicroWorld.

+ +

This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version.

+ +

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details.

+ +

You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA.

+ +

Copyright (C) 2014 Simon Brooke

+

reads a markdown file from public/md and returns an HTML string

+
(defn md->html
+  [filename]
+  (->>
+    (io/slurp-resource filename)
+    (md/md-to-html-string)))
+
(defn list-resources [directory pattern]
+  "List resource files matching `pattern` in `directory`."
+  (let
+    [path (str (io/resource-path) directory)]
+    (session/put! :list-resources-path path)
+    (sort
+      (remove nil?
+            (map #(first (rest (re-matches pattern (.getName %))))
+                 (file-seq (clojure.java.io/file path)))))))
 
\ No newline at end of file diff --git a/project.clj b/project.clj index a3a3dfe..2fee3f8 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject mw-ui "0.1.5-SNAPSHOT" +(defproject mw-ui "0.1.6-SNAPSHOT" :description "Web-based user interface for MicroWorld" :url "http://www.journeyman.cc/microworld" :manifest { @@ -8,17 +8,18 @@ "build-signature-timestamp" "unset" "Implementation-Version" "unset" } - :dependencies [[org.clojure/clojure "1.6.0"] - [mw-engine "0.1.5-SNAPSHOT"] - [mw-parser "0.1.5-SNAPSHOT"] - [lib-noir "0.8.4"] - [ring-server "0.3.1"] - [selmer "0.6.8"] - [com.taoensso/timbre "3.2.1"] - [com.taoensso/tower "2.0.2"] - [markdown-clj "0.9.44"] - [environ "0.5.0"] - [noir-exception "0.2.2"]] + :dependencies [[org.clojure/clojure "1.8.0"] + [mw-engine "0.1.6-SNAPSHOT"] + [mw-parser "0.1.6-SNAPSHOT"] + [lib-noir "0.9.9"] + [ring-server "0.5.0"] + [selmer "1.12.25"] + [hiccup "1.0.5"] + [com.taoensso/timbre "4.10.0"] + [com.taoensso/tower "3.0.2"] + [markdown-clj "1.10.4"] + [environ "1.2.0"] + [noir-exception "0.2.5"]] :repl-options {:init-ns mw-ui.repl} :plugins [[lein-ring "0.8.11"] @@ -40,8 +41,8 @@ :stacktraces? false :auto-reload? false}} :dev {:dependencies [[ring-mock "0.1.5"] - [ring/ring-devel "1.3.0"] - [pjstadig/humane-test-output "0.6.0"]] + [ring/ring-devel "1.8.1"] + [pjstadig/humane-test-output "0.10.0"]] :injections [(require 'pjstadig.humane-test-output) (pjstadig.humane-test-output/activate!)] :env {:dev true}}} diff --git a/resources/public/docs/mw-engine b/resources/public/docs/mw-engine new file mode 120000 index 0000000..09eca7a --- /dev/null +++ b/resources/public/docs/mw-engine @@ -0,0 +1 @@ +../../../../mw-engine/docs \ No newline at end of file diff --git a/resources/public/docs/mw-parser b/resources/public/docs/mw-parser new file mode 120000 index 0000000..5ed9c16 --- /dev/null +++ b/resources/public/docs/mw-parser @@ -0,0 +1 @@ +../../../../mw-parser/docs \ No newline at end of file diff --git a/resources/public/docs/mw-ui b/resources/public/docs/mw-ui new file mode 120000 index 0000000..a2968b0 --- /dev/null +++ b/resources/public/docs/mw-ui @@ -0,0 +1 @@ +../../../../mw-ui/docs \ No newline at end of file diff --git a/resources/public/img/heightmaps/mgi_med.png b/resources/public/img/heightmaps/mgi_med.png new file mode 100644 index 0000000000000000000000000000000000000000..cb4e875a1dad444cee571bc9d3b50ab8df457b26 GIT binary patch literal 27805 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 E2L~ Date: Thu, 9 Dec 2021 20:12:59 +0000 Subject: [PATCH 06/14] Tackling bit-rot --- .gitignore | 3 ++ project.clj | 50 +++++++++++----------- resources/public/img/heightmaps/barra.png | Bin 0 -> 4185 bytes resources/public/img/heightmaps/barra.xcf | Bin 0 -> 6741 bytes resources/public/img/tiles/harbour.png | Bin 0 -> 719 bytes resources/public/img/tiles/harbour.xcf | Bin 0 -> 1835 bytes resources/public/rulesets/harbours.txt | 4 ++ src/mw_ui/handler.clj | 24 +++++------ src/mw_ui/middleware.clj | 5 +-- src/mw_ui/repl.clj | 14 +++--- 10 files changed, 54 insertions(+), 46 deletions(-) create mode 100644 resources/public/img/heightmaps/barra.png create mode 100644 resources/public/img/heightmaps/barra.xcf create mode 100644 resources/public/img/tiles/harbour.png create mode 100644 resources/public/img/tiles/harbour.xcf create mode 100644 resources/public/rulesets/harbours.txt diff --git a/.gitignore b/.gitignore index 7e819cf..f583146 100644 --- a/.gitignore +++ b/.gitignore @@ -5,10 +5,13 @@ pom.xml *.jar *.class +/.calva/ /.lein-* /.env *.log +.nrepl-port + # Links to other places: resources/public/docs/mw-*/uberdoc.html # Artefacts: diff --git a/project.clj b/project.clj index 2fee3f8..582373c 100644 --- a/project.clj +++ b/project.clj @@ -1,49 +1,49 @@ (defproject mw-ui "0.1.6-SNAPSHOT" :description "Web-based user interface for MicroWorld" - :url "http://www.journeyman.cc/microworld" - :manifest { - "build-signature-version" "unset" - "build-signature-user" "unset" - "build-signature-email" "unset" - "build-signature-timestamp" "unset" - "Implementation-Version" "unset" - } - :dependencies [[org.clojure/clojure "1.8.0"] + :dependencies [[org.clojure/clojure "1.10.3"] [mw-engine "0.1.6-SNAPSHOT"] [mw-parser "0.1.6-SNAPSHOT"] [lib-noir "0.9.9"] [ring-server "0.5.0"] - [selmer "1.12.25"] + [selmer "1.12.45"] [hiccup "1.0.5"] - [com.taoensso/timbre "4.10.0"] + [com.taoensso/timbre "5.1.2"] [com.taoensso/tower "3.0.2"] - [markdown-clj "1.10.4"] + [markdown-clj "1.10.7"] [environ "1.2.0"] [noir-exception "0.2.5"]] + :docker {:image-name "simonbrooke/microworld" + :dockerfile "Dockerfile"} + :main mw-ui.repl + :manifest {"build-signature-version" "0.1.6-SNAPSHOT" + "build-signature-user" "Simon Brooke" + "build-signature-email" "unset" + "build-signature-timestamp" "2021-05-17 13:31:22+01:00" + "Implementation-Version" "0.1.6-SNAPSHOT built by Simon Brooke on 2021-05-17 13:31:22+01:00"} + :min-lein-version "2.0.0" - :repl-options {:init-ns mw-ui.repl} :plugins [[lein-ring "0.8.11"] [lein-environ "0.5.0"] [lein-marginalia "0.7.1"] [io.sarnowski/lein-docker "1.1.0"]] - :docker {:image-name "simonbrooke/microworld" - :dockerfile "Dockerfile"} - :ring {:handler mw-ui.handler/app - :init mw-ui.handler/init - :destroy mw-ui.handler/destroy - :resources-path "resources" - :war-resources-path "war-resources" - :uberwar-name "microworld.war" - } :profiles {:uberjar {:aot :all} :production {:ring {:open-browser? false :stacktraces? false :auto-reload? false}} :dev {:dependencies [[ring-mock "0.1.5"] - [ring/ring-devel "1.8.1"] - [pjstadig/humane-test-output "0.10.0"]] + [ring/ring-devel "1.9.4"] + [pjstadig/humane-test-output "0.11.0"]] :injections [(require 'pjstadig.humane-test-output) (pjstadig.humane-test-output/activate!)] :env {:dev true}}} - :min-lein-version "2.0.0") + :repl-options {:init-ns mw-ui.repl} +:ring {:handler mw-ui.handler/app + :init mw-ui.handler/init + :destroy mw-ui.handler/destroy + :resources-path "resources" + :war-resources-path "war-resources" + :uberwar-name "microworld.war"} + + :url "http://www.journeyman.cc/microworld" +) diff --git a/resources/public/img/heightmaps/barra.png b/resources/public/img/heightmaps/barra.png new file mode 100644 index 0000000000000000000000000000000000000000..b5f07999293f7ad3c310b0e26d905ba4f79c39c6 GIT binary patch literal 4185 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 literal 0 HcmV?d00001 diff --git a/resources/public/img/heightmaps/barra.xcf b/resources/public/img/heightmaps/barra.xcf new file mode 100644 index 0000000000000000000000000000000000000000..766fe5218509d74f2c542e4f84428013b0fe011d GIT binary patch literal 6741 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 D)hW`) literal 0 HcmV?d00001 diff --git a/resources/public/img/tiles/harbour.png b/resources/public/img/tiles/harbour.png new file mode 100644 index 0000000000000000000000000000000000000000..2fe5133cead86c47ede77b9c1179c35b46388918 GIT binary patch literal 719 zcmV;=0xt90(aN>P#z20000TX;fHrLvL+uWo~o;00000Lvm$d zbY)~9cWHEJAV*0}P-HG;2LJ#9MM*?KR5;6Rlflj$MGS z9XGUIBG~QB=}iBp1SwJsqipBC?*Q&;5~vtsEUVVbdi`d-UbU|4x{|d0{Pelm8q0FI zN>@qe%jeTzbdUfdpjaY?x)S8kYG+wNi6Sgy3PA3=Bp_?TZ3O{zAP_O62;|Xwxtbji z6ax|u^_gj>bMy8kw=eI0e8%>I?FIYBnc)e5W#sOB!M2}jA0q|nZqD5i0BXM-yN~m( z9e@jsG)0C0P?E%6v+`C(FOv*7;5tU+s}`*=MR5B)<6I9-6_eLo_40wm04BCXv6sU*AxC1?>>Bc zk_R9&f!pXyglQkzX6^tY;BDy@T7teGf4z?vx`u*uod@X+c;|Io|7{Q3J zeJjs>^3ZJCneRXS^p(ty-@|rQWpx{faZpCatJiNN;lwiJ$rxG&Icz)2U3a$>14Dzs zTlF^Q-DfK8?lJ&pO<>{RGA<)CJIhWWyHs6{asl?8FmVZCpZ08^oS01^7A?!gk^2TC zfrJTXHQaH2jKRTGAt703zRRV)EbAI#%)301f5IDp)YLIv+xwqJQM0zk_N>?TX1!5TiAUgq z2jB^O!E;z}i4an+kx(x}qX?`v+bo<(H;{@l((L)>oA3CX^Le!!ka)_TxIXKX30{Ns z(P~rtK5T^G<3rfDzWBEC9o>MbsIuChtRmHY4Ek+|cLI2<>ZWmwlR2E%=;a4r&hTiC zJ@%N)Lv~v;EPC_am6XJz0IR`_c$5P*S`)MRqX}lSbV5Aow&(|LI!R}&coo!H;EdQt zt#P$p1DdNV2~wTcq+Z{%{|{*DNj4q1BnjB&buAzty^dUQ>QO@GEJ@rDXEZ|BmwL&(no50GE%g00f_U@&+&%_BXSHb(Pyw@2Z9t?i=P%f|R+#$#)(S&d`}79B zeFMLJ1HW?v-?@R`1x%lNz1{>s>(O=n#klSTC?>qR`M2|R;J$v9W4@thhP~i;p5s`C zeZdO?hd72s+?i07s{+rlEH73?QD70v2tUcSnoWxO;Y~&mHb2w8CQWtr%SdGR|eS-e}f0IhKRQAc2veDfQ--%(Fb=pWT(@dK0E%eiMbGUAffTyUZ+@(jNKv#Jo6pMlv6 zj*dnzd9b&_GTKr%kg?P?Mtg3UrlIQ^(vW^;_WC{B&^67lJ6+qPq_Pmxa#gKN=>K>$Xj!pnYhCrfqNU=o&nB*y|J>ix$?jd-v{ZT4#6f!Jt?4 zd&Nd^4rjAF=y%#hr_(Je#rZZcx=9-^Ow&RZYA+yr2d20*Y?@(O-Cnzb|`7GyBS zsnzZGVI(lYYMI@B*D}EvV0+NK-Lb8f0qmed+22hItg#2fcFQo#&T!Z@7^Bp8plD~e z-_ntOVYG)s6TqcLt6mu0zDb>3eAOOU;Pvu8d@OYEo07T#4p%hBy}*P7*k#=30R`Aa zF2ZMII-5p7yFq-I#)OjONt~S=&(nxtTu$dlhk@%xQ93_CC#Odl&}Dphltr$KNtDj! z%>0*ZIxWYO*(?Di4#I$x-2VQ7>&Dsf(KIR3xZEiJgfltJGI(~3;y21C4}rjBnvBCT z2tpD? #'app + (-> #'handler/app ; Makes static assets in $PROJECT_DIR/resources/public/ available. (wrap-file "resources") ; Content-Type, Content-Length, and Last Modified headers for files in body @@ -50,9 +51,9 @@ (reset! server (serve (get-handler) {:port port - :init init + :init handler/init :auto-reload? true - :destroy destroy + :destroy handler/destroy :join? false})) (println (str "You can view the site at http://localhost:" port)))) @@ -62,3 +63,4 @@ (defn -main [] (start-server)) + From 534d2d54a5cb0042a76e31c68c0e21625eb40b86 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Thu, 9 Dec 2021 21:05:55 +0000 Subject: [PATCH 07/14] Mainly, fix linting errors and add TODOs --- src/mw_ui/handler.clj | 1 + src/mw_ui/layout.clj | 4 ++-- src/mw_ui/render_world.clj | 8 +++++--- src/mw_ui/routes/home.clj | 18 ++++++++++-------- src/mw_ui/routes/load.clj | 13 ++++--------- src/mw_ui/routes/params.clj | 10 +++------- src/mw_ui/routes/rules.clj | 15 +++++---------- src/mw_ui/routes/save.clj | 7 ++++--- src/mw_ui/util.clj | 11 ++++++++--- 9 files changed, 42 insertions(+), 45 deletions(-) diff --git a/src/mw_ui/handler.clj b/src/mw_ui/handler.clj index 322aadf..7915b62 100644 --- a/src/mw_ui/handler.clj +++ b/src/mw_ui/handler.clj @@ -46,6 +46,7 @@ an app server such as Tomcat put any initialization code here" [] + ;; TODO fix timbre config! ;; (timbre/set-config! ;; [:appenders :rotor] ;; {:min-level :info diff --git a/src/mw_ui/layout.clj b/src/mw_ui/layout.clj index 9861426..da3175b 100644 --- a/src/mw_ui/layout.clj +++ b/src/mw_ui/layout.clj @@ -35,12 +35,12 @@ (deftype RenderableTemplate [template params] Renderable - (render [this request] + (render [_ request] (content-type (->> (assoc (merge params {:version (System/getProperty "mw-ui.version")}) (keyword (s/replace template #".html" "-selected")) "active" :servlet-context - (if-let [context (:servlet-context request)] + (when-let [context (:servlet-context request)] (.getContextPath context))) (parser/render-file (str template-path template)) response) diff --git a/src/mw_ui/render_world.clj b/src/mw_ui/render_world.clj index 2d91180..6499339 100644 --- a/src/mw_ui/render_world.clj +++ b/src/mw_ui/render_world.clj @@ -34,9 +34,10 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn format-css-class [statekey] +(defn format-css-class "Format this statekey, assumed to be a keyword indicating a state in the world, into a CSS class" + [statekey] (subs (str statekey) 1)) @@ -92,7 +93,8 @@ (defn render-inspector "Render in Hiccup format the HTML content of an inspector on this cell." - [cell table] + ([cell _] (render-inspector cell)) + ([cell] [:table {:class "music-ruled"} [:tr [:td {:colspan 2 :style "text-align: center;"} @@ -100,6 +102,6 @@ :width 64 :height 64}]]] [:tr [:th "Key"][:th "Value"]] - (map #(vector :tr (vector :th %)(vector :td (cell %))) (keys cell))]) + (map #(vector :tr (vector :th %)(vector :td (cell %))) (keys cell))])) diff --git a/src/mw_ui/routes/home.clj b/src/mw_ui/routes/home.clj index efdda85..0e82dc1 100644 --- a/src/mw_ui/routes/home.clj +++ b/src/mw_ui/routes/home.clj @@ -1,9 +1,9 @@ (ns ^{:doc "Routes which serve the main pages of the application." :author "Simon Brooke"} mw-ui.routes.home - (:use clojure.walk - compojure.core) - (:require [clojure.pprint :only [pprint]] + (:require [clojure.java/io :refer [file]] + [clojure.walk :refer [keywordize-keys]] + [compojure.core :refer [defroutes GET POST]] [hiccup.core :refer [html]] [mw-engine.utils :as engine-utils] [mw-ui.layout :as layout] @@ -13,7 +13,6 @@ [mw-ui.routes.params :as params] [mw-ui.routes.save :as save] [mw-ui.util :as util] - [noir.io :as io] [noir.session :as session] [ring.util.response :as response])) @@ -45,7 +44,8 @@ (sort (filter #(not (nil? %)) (map #(first (rest (re-matches #"([0-9a-z-]+).png" (.getName %)))) - (file-seq (clojure.java.io/file "resources/public/img/tiles")))))) + ;; TODO: this will not work when running from jar; see utils.clj + (file-seq (file "resources/public/img/tiles")))))) (defn about-page [] @@ -63,14 +63,16 @@ :components ["mw-engine" "mw-parser" "mw-ui"] :version (System/getProperty "mw-ui.version")})) -(defn home-page [] +(defn home-page "Render the home page." + [] (layout/render "trusted-content.html" {:title "Welcome to MicroWorld" :content (util/md->html "/md/mw-ui.md") :version (System/getProperty "mw-ui.version")})) -(defn inspect-page [request] +(defn inspect-page "Open an inspector on the cell at the co-ordinates specified in this request" + [request] (let [params (keywordize-keys (:params request)) xs (:x params) ys (:y params) @@ -86,7 +88,7 @@ true (layout/render "inspector.html" {:title (format "Inspect cell at %d, %d" x y) - :content (html (world/render-inspector cell world)) + :content (html (world/render-inspector cell)) :cell cell :x (:x cell) :y (:y cell) diff --git a/src/mw_ui/routes/load.clj b/src/mw_ui/routes/load.clj index 0636ccf..7894ab6 100644 --- a/src/mw_ui/routes/load.clj +++ b/src/mw_ui/routes/load.clj @@ -1,14 +1,10 @@ (ns ^{:doc "Route which handles the upload of worlds/rules from the client." :author "Simon Brooke"} mw-ui.routes.load - (:use clojure.walk - compojure.core) - (:require [hiccup.core :refer [html]] + (:require [clojure.walk :refer [keywordize-keys]] [noir.io :as io] [noir.session :as session] - [ring.util.response :as response] - [mw-ui.layout :as layout] - )) + [mw-ui.layout :as layout])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; @@ -38,7 +34,7 @@ (io/upload-file "/tmp/" file) (cond (session/put! :world - (with-open [eddi (java.io.FileReader. (:tempfile file))] (read))) + (with-open [_ (java.io.FileReader. (:tempfile file))] (read))) (str "Successfully loaded your world from " (:filename file)))) @@ -52,8 +48,7 @@ ([] (load-page nil)) ([request] - (let [params (keywordize-keys (:params request)) - file (:file request)] + (let [file (:file request)] (try (layout/render "load.html" {:title "Load World" diff --git a/src/mw_ui/routes/params.clj b/src/mw_ui/routes/params.clj index f68fcb3..2d6d781 100644 --- a/src/mw_ui/routes/params.clj +++ b/src/mw_ui/routes/params.clj @@ -1,15 +1,11 @@ (ns ^{:doc "Route which serves and handles the parameters page." :author "Simon Brooke"} mw-ui.routes.params - (:use clojure.walk - clojure.java.io - compojure.core) - (:require [hiccup.core :refer [html]] + (:require [clojure.walk :refer [keywordize-keys]] [mw-engine.heightmap :as heightmap] [mw-parser.bulk :as compiler] [mw-ui.layout :as layout] [mw-ui.util :as util] - [mw-ui.render-world :as world] [noir.io :as io] [noir.session :as session])) @@ -58,14 +54,14 @@ pause (:pause params) rulefile (:ruleset params) rulepath (str "/rulesets/" rulefile ".txt")] - (if (not= map "") + (when (not= map "") (session/put! :world (heightmap/apply-heightmap (io/get-resource (str "/img/heightmaps/" map ".png"))))) (when (not= rulefile "") (session/put! :rule-text (io/slurp-resource rulepath)) (session/put! :rules (compiler/compile-file (io/get-resource rulepath)))) - (if (not= pause "") + (when (not= pause "") (session/put! :pause pause)) (layout/render "params.html" (merge (send-params) diff --git a/src/mw_ui/routes/rules.clj b/src/mw_ui/routes/rules.clj index b49a4c8..bd43291 100644 --- a/src/mw_ui/routes/rules.clj +++ b/src/mw_ui/routes/rules.clj @@ -1,16 +1,11 @@ (ns ^{:doc "Route which serves and handles the rules page." :author "Simon Brooke"} mw-ui.routes.rules - (:use clojure.walk - compojure.core) - (:require [hiccup.core :refer [html]] + (:require [clojure.walk :refer [keywordize-keys]] [mw-parser.bulk :as compiler] [mw-ui.layout :as layout] - [mw-ui.util :as util] - [mw-ui.render-world :as world] [noir.io :as io] - [noir.session :as session] - [ring.util.response :as response])) + [noir.session :as session])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; @@ -47,7 +42,7 @@ :message (str "Successfully compiled " (count rules) " rules") }) - true {:rule-text (or + :else {:rule-text (or (session/get :rule-text) (io/slurp-resource "/rulesets/basic.txt")) :message "No rules found in request; loading defaults"}) @@ -67,9 +62,9 @@ the session or from `resources/rulesets/basic.txt` and pass that back." ([request] (let [processed (process-rules-request request)] - (if (:rules processed) + (when (:rules processed) (session/put! :rules (:rules processed))) - (if (:rule-text processed) + (when (:rule-text processed) (session/put! :rule-text (:rule-text processed))) (layout/render "rules.html" (merge {:title "Edit Rules"} processed)))) diff --git a/src/mw_ui/routes/save.clj b/src/mw_ui/routes/save.clj index 407495b..6f1f760 100644 --- a/src/mw_ui/routes/save.clj +++ b/src/mw_ui/routes/save.clj @@ -1,7 +1,7 @@ (ns ^{:doc "Route which handles the saving of world state the client." :author "Simon Brooke"} mw-ui.routes.save - (:require [clojure.pprint :as pretty :only [pprint]] + (:require [clojure.pprint :refer [pprint]] [noir.session :as session] [noir.response :as response])) @@ -29,12 +29,13 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn save-page [] +(defn save-page "Save the current world to the browser, using our own custom mime-type in an attempt to prevent the browser trying to do anything clever with it. Note that it is saved as a raw Clojure data structure, not as XML or any proprietary format." + [] (response/content-type "application/journeyman-mwm; charset=utf-8" - (with-out-str (pretty/pprint (session/get :world))))) + (with-out-str (pprint (session/get :world))))) diff --git a/src/mw_ui/util.clj b/src/mw_ui/util.clj index fa59383..c5ff74d 100644 --- a/src/mw_ui/util.clj +++ b/src/mw_ui/util.clj @@ -1,7 +1,8 @@ (ns ^{:doc "Utility functions used by other namespaces in this package." :author "Simon Brooke"} mw-ui.util - (:require [noir.io :as io] + (:require [clojure.java.io :refer [file]] + [noir.io :as io] [noir.session :as session] [markdown.core :as md])) @@ -36,13 +37,17 @@ (io/slurp-resource filename) (md/md-to-html-string))) +;; TODO: The reason we can't list files in a jar file, and what to do about it, +;; is here. Too tired to fix this tonight. +;; https://stackoverflow.com/questions/46488466/clojure-list-subfolders-in-resources-in-uberjar -(defn list-resources [directory pattern] +(defn list-resources "List resource files matching `pattern` in `directory`." + [directory pattern] (let [path (str (io/resource-path) directory)] (session/put! :list-resources-path path) (sort (remove nil? (map #(first (rest (re-matches pattern (.getName %)))) - (file-seq (clojure.java.io/file path))))))) + (file-seq (file path))))))) From c673b3e134e08512443184716d2fbda6d4518c70 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Fri, 10 Dec 2021 11:45:24 +0000 Subject: [PATCH 08/14] "It's a good sort of brake but it doesn't work yet" Trying to list resource names from jar file... --- project.clj | 2 ++ src/mw_ui/routes/home.clj | 2 +- src/mw_ui/routes/params.clj | 4 +-- src/mw_ui/util.clj | 59 +++++++++++++++++++++++++++++-------- 4 files changed, 51 insertions(+), 16 deletions(-) diff --git a/project.clj b/project.clj index 582373c..3a05862 100644 --- a/project.clj +++ b/project.clj @@ -1,6 +1,8 @@ (defproject mw-ui "0.1.6-SNAPSHOT" :description "Web-based user interface for MicroWorld" :dependencies [[org.clojure/clojure "1.10.3"] + [clj-time "0.15.2"] ;; this is a hack. Something in libnoir requires + ;; JodaTime, but doesn't request it. clj-time does. [mw-engine "0.1.6-SNAPSHOT"] [mw-parser "0.1.6-SNAPSHOT"] [lib-noir "0.9.9"] diff --git a/src/mw_ui/routes/home.clj b/src/mw_ui/routes/home.clj index 0e82dc1..fe6b86d 100644 --- a/src/mw_ui/routes/home.clj +++ b/src/mw_ui/routes/home.clj @@ -1,7 +1,7 @@ (ns ^{:doc "Routes which serve the main pages of the application." :author "Simon Brooke"} mw-ui.routes.home - (:require [clojure.java/io :refer [file]] + (:require [clojure.java.io :refer [file]] [clojure.walk :refer [keywordize-keys]] [compojure.core :refer [defroutes GET POST]] [hiccup.core :refer [html]] diff --git a/src/mw_ui/routes/params.clj b/src/mw_ui/routes/params.clj index 2d6d781..993d45e 100644 --- a/src/mw_ui/routes/params.clj +++ b/src/mw_ui/routes/params.clj @@ -35,9 +35,9 @@ (defn- send-params [] {:title "Choose your world" - :heightmaps (util/list-resources "/img/heightmaps" #"([0-9a-z-_]+).png") + :heightmaps (util/list-resources "/img/heightmaps" #"/?([0-9a-z-_]+).png") :pause (or (session/get :pause) 5) - :rulesets (util/list-resources "/rulesets" #"([0-9a-z-_]+).txt") + :rulesets (util/list-resources "/rulesets" #"/?([0-9a-z-_]+).txt") }) diff --git a/src/mw_ui/util.clj b/src/mw_ui/util.clj index c5ff74d..5f975b6 100644 --- a/src/mw_ui/util.clj +++ b/src/mw_ui/util.clj @@ -1,10 +1,12 @@ (ns ^{:doc "Utility functions used by other namespaces in this package." :author "Simon Brooke"} - mw-ui.util + mw-ui.util (:require [clojure.java.io :refer [file]] + [clojure.string :refer [starts-with?]] + [markdown.core :as md] [noir.io :as io] [noir.session :as session] - [markdown.core :as md])) + [taoensso.timbre :as timbre])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; @@ -29,25 +31,56 @@ ;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(def running-from-filesystem (atom true)) + +(def compile-time-resources + "The resources which were visible at compile time. If we are running from + a JAR file, it is highly likely that these are all the resources available + at run time." + (let [n (count (io/resource-path))] + (remove nil? + (map #(let [s (str %)] + (when (> (count s) n) + (subs s 56))) + (file-seq (file (io/resource-path))))))) + (defn md->html "reads a markdown file from public/md and returns an HTML string" [filename] (->> - (io/slurp-resource filename) - (md/md-to-html-string))) + (io/slurp-resource filename) + (md/md-to-html-string))) -;; TODO: The reason we can't list files in a jar file, and what to do about it, -;; is here. Too tired to fix this tonight. -;; https://stackoverflow.com/questions/46488466/clojure-list-subfolders-in-resources-in-uberjar -(defn list-resources +(defn cache-seq-match + "Do the same processing that list-resources does on names fetched from + the file system, except on the resource list cached at compile time." + [path pattern] + (let [n (count path)] + (remove nil? + (map #(when (> (count %) n) + (let [name (subs % n)] + (first (rest (re-matches pattern name))))) + (filter #(starts-with? % path) + compile-time-resources))))) + + +(defn list-resources "List resource files matching `pattern` in `directory`." [directory pattern] (let - [path (str (io/resource-path) directory)] + [path (str (io/resource-path) directory)] (session/put! :list-resources-path path) - (sort - (remove nil? - (map #(first (rest (re-matches pattern (.getName %)))) - (file-seq (file path))))))) + (try + (sort + (remove nil? + (if @running-from-filesystem + (map #(first (rest (re-matches pattern (.getName %)))) + (file-seq (file path))) + (cache-seq-match directory pattern)))) + (catch Exception any + (timbre/log (str "Not running from filesystem?" + (.getName (.getClass any)))) + (reset! running-from-filesystem false) + (cache-seq-match directory pattern))))) From 73c71e4df04a647defa2888934efb69333a12dd2 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Fri, 10 Dec 2021 12:44:48 +0000 Subject: [PATCH 09/14] No idea whether or not this is progress. --- src/mw_ui/layout.clj | 1 - src/mw_ui/render_world.clj | 5 +---- src/mw_ui/util.clj | 19 ++++++++++++------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/mw_ui/layout.clj b/src/mw_ui/layout.clj index da3175b..32568a8 100644 --- a/src/mw_ui/layout.clj +++ b/src/mw_ui/layout.clj @@ -32,7 +32,6 @@ (def template-path "templates/") - (deftype RenderableTemplate [template params] Renderable (render [_ request] diff --git a/src/mw_ui/render_world.clj b/src/mw_ui/render_world.clj index 6499339..0a1f2f7 100644 --- a/src/mw_ui/render_world.clj +++ b/src/mw_ui/render_world.clj @@ -1,12 +1,9 @@ (ns ^{:doc "Render the state of the world as an HTML table." :author "Simon Brooke"} mw-ui.render-world - (:require [clojure.java.io :as jio] - [mw-engine.core :as engine] - [mw-engine.world :as world] + (:require [mw-engine.core :as engine] [mw-engine.heightmap :as heightmap] [mw-parser.bulk :as compiler] - [hiccup.core :refer [html]] [noir.io :as io] [noir.session :as session])) diff --git a/src/mw_ui/util.clj b/src/mw_ui/util.clj index 5f975b6..19f9b61 100644 --- a/src/mw_ui/util.clj +++ b/src/mw_ui/util.clj @@ -31,18 +31,22 @@ ;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(def running-from-filesystem (atom true)) +(def running-from-filesystem + "We assume we're running from the filesystem unless we find we're not." + (atom true)) + (def compile-time-resources "The resources which were visible at compile time. If we are running from a JAR file, it is highly likely that these are all the resources available at run time." - (let [n (count (io/resource-path))] + (let [f (file "resources/public") + n (count (.getCanonicalPath f))] (remove nil? - (map #(let [s (str %)] + (map #(let [s (.getCanonicalPath %)] (when (> (count s) n) - (subs s 56))) - (file-seq (file (io/resource-path))))))) + (subs s n))) + (file-seq f))))) (defn md->html @@ -57,12 +61,13 @@ "Do the same processing that list-resources does on names fetched from the file system, except on the resource list cached at compile time." [path pattern] + (timbre/info compile-time-resources) (let [n (count path)] (remove nil? (map #(when (> (count %) n) (let [name (subs % n)] - (first (rest (re-matches pattern name))))) - (filter #(starts-with? % path) + (last (re-matches pattern name)))) + (filter #(starts-with? % path) compile-time-resources))))) From 83db0cb2cbb07f98e7982a4426ddf3bf26032a5d Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sat, 8 Jul 2023 10:24:20 +0100 Subject: [PATCH 10/14] Compiles; all tests pass `lein ring` is broken on MacOS due to out-of-date homebrew build, so can't build uberwar. --- project.clj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/project.clj b/project.clj index 3a05862..8f5015c 100644 --- a/project.clj +++ b/project.clj @@ -1,17 +1,17 @@ (defproject mw-ui "0.1.6-SNAPSHOT" :description "Web-based user interface for MicroWorld" - :dependencies [[org.clojure/clojure "1.10.3"] + :dependencies [[org.clojure/clojure "1.11.1"] [clj-time "0.15.2"] ;; this is a hack. Something in libnoir requires ;; JodaTime, but doesn't request it. clj-time does. [mw-engine "0.1.6-SNAPSHOT"] [mw-parser "0.1.6-SNAPSHOT"] [lib-noir "0.9.9"] [ring-server "0.5.0"] - [selmer "1.12.45"] + [selmer "1.12.59"] [hiccup "1.0.5"] - [com.taoensso/timbre "5.1.2"] + [com.taoensso/timbre "6.2.1"] [com.taoensso/tower "3.0.2"] - [markdown-clj "1.10.7"] + [markdown-clj "1.11.4"] [environ "1.2.0"] [noir-exception "0.2.5"]] :docker {:image-name "simonbrooke/microworld" @@ -34,7 +34,7 @@ :stacktraces? false :auto-reload? false}} :dev {:dependencies [[ring-mock "0.1.5"] - [ring/ring-devel "1.9.4"] + [ring/ring-devel "1.10.0"] [pjstadig/humane-test-output "0.11.0"]] :injections [(require 'pjstadig.humane-test-output) (pjstadig.humane-test-output/activate!)] From 5ee254ca3df3a479968bab45cba4bf42b7c50061 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sat, 8 Jul 2023 10:55:29 +0100 Subject: [PATCH 11/14] Updated Ring plugin. Uberwar build now works, d'oh! `lein ring server` still breaks, to be investigated. --- .gitignore | 2 ++ project.clj | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index f583146..98bc2f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.clj-kondo/ +.lsp/ /target /lib /classes diff --git a/project.clj b/project.clj index 8f5015c..daa3555 100644 --- a/project.clj +++ b/project.clj @@ -24,10 +24,10 @@ "Implementation-Version" "0.1.6-SNAPSHOT built by Simon Brooke on 2021-05-17 13:31:22+01:00"} :min-lein-version "2.0.0" - :plugins [[lein-ring "0.8.11"] - [lein-environ "0.5.0"] - [lein-marginalia "0.7.1"] - [io.sarnowski/lein-docker "1.1.0"]] + :plugins [[lein-ring "0.12.6"] + [lein-environ "1.2.0"] + [lein-marginalia "0.9.1"] + [gorillalabs/lein-docker "1.6.0"]] :profiles {:uberjar {:aot :all} :production {:ring {:open-browser? false From 04ce4d7f83c3ccb5a94057fb28f724b005beeb40 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Mon, 10 Jul 2023 13:47:05 +0100 Subject: [PATCH 12/14] Upversioning whole system to 0.2.0, for flow feature --- project.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/project.clj b/project.clj index daa3555..7c70c7e 100644 --- a/project.clj +++ b/project.clj @@ -1,10 +1,10 @@ -(defproject mw-ui "0.1.6-SNAPSHOT" +(defproject mw-ui "0.2.0-SNAPSHOT" :description "Web-based user interface for MicroWorld" :dependencies [[org.clojure/clojure "1.11.1"] [clj-time "0.15.2"] ;; this is a hack. Something in libnoir requires ;; JodaTime, but doesn't request it. clj-time does. - [mw-engine "0.1.6-SNAPSHOT"] - [mw-parser "0.1.6-SNAPSHOT"] + [mw-engine "0.2.0-SNAPSHOT"] + [mw-parser "0.2.0-SNAPSHOT"] [lib-noir "0.9.9"] [ring-server "0.5.0"] [selmer "1.12.59"] From 357f4f53d4fc0ec95d2d53097ac206b6c8f8d938 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 11 Jul 2023 09:20:16 +0100 Subject: [PATCH 13/14] Mainly code tidyup, also documentation. --- docs/cloverage/coverage.css | 40 ++ docs/cloverage/index.html | 200 +++++++ docs/cloverage/mw_ui/handler.clj.html | 266 ++++++++++ docs/cloverage/mw_ui/layout.clj.html | 161 ++++++ docs/cloverage/mw_ui/middleware.clj.html | 164 ++++++ docs/cloverage/mw_ui/render_world.clj.html | 320 ++++++++++++ docs/cloverage/mw_ui/repl.clj.html | 206 ++++++++ docs/cloverage/mw_ui/routes/home.clj.html | 407 +++++++++++++++ docs/cloverage/mw_ui/routes/load.clj.html | 194 +++++++ docs/cloverage/mw_ui/routes/params.clj.html | 248 +++++++++ docs/cloverage/mw_ui/routes/rules.clj.html | 224 ++++++++ docs/cloverage/mw_ui/routes/save.clj.html | 131 +++++ docs/cloverage/mw_ui/util.clj.html | 281 ++++++++++ docs/codox/css/default.css | 551 ++++++++++++++++++++ docs/codox/css/highlight.css | 97 ++++ docs/codox/index.html | 14 + docs/codox/js/highlight.min.js | 2 + docs/codox/js/jquery.min.js | 4 + docs/codox/js/page_effects.js | 112 ++++ docs/codox/mw-ui.handler.html | 8 + docs/codox/mw-ui.layout.html | 6 + docs/codox/mw-ui.middleware.html | 8 + docs/codox/mw-ui.render-world.html | 11 + docs/codox/mw-ui.repl.html | 9 + docs/codox/mw-ui.routes.home.html | 12 + docs/codox/mw-ui.routes.load.html | 7 + docs/codox/mw-ui.routes.params.html | 5 + docs/codox/mw-ui.routes.rules.html | 7 + docs/codox/mw-ui.routes.save.html | 5 + docs/codox/mw-ui.util.html | 9 + project.clj | 40 +- 31 files changed, 3732 insertions(+), 17 deletions(-) create mode 100644 docs/cloverage/coverage.css create mode 100644 docs/cloverage/index.html create mode 100644 docs/cloverage/mw_ui/handler.clj.html create mode 100644 docs/cloverage/mw_ui/layout.clj.html create mode 100644 docs/cloverage/mw_ui/middleware.clj.html create mode 100644 docs/cloverage/mw_ui/render_world.clj.html create mode 100644 docs/cloverage/mw_ui/repl.clj.html create mode 100644 docs/cloverage/mw_ui/routes/home.clj.html create mode 100644 docs/cloverage/mw_ui/routes/load.clj.html create mode 100644 docs/cloverage/mw_ui/routes/params.clj.html create mode 100644 docs/cloverage/mw_ui/routes/rules.clj.html create mode 100644 docs/cloverage/mw_ui/routes/save.clj.html create mode 100644 docs/cloverage/mw_ui/util.clj.html create mode 100644 docs/codox/css/default.css create mode 100644 docs/codox/css/highlight.css create mode 100644 docs/codox/index.html create mode 100644 docs/codox/js/highlight.min.js create mode 100644 docs/codox/js/jquery.min.js create mode 100644 docs/codox/js/page_effects.js create mode 100644 docs/codox/mw-ui.handler.html create mode 100644 docs/codox/mw-ui.layout.html create mode 100644 docs/codox/mw-ui.middleware.html create mode 100644 docs/codox/mw-ui.render-world.html create mode 100644 docs/codox/mw-ui.repl.html create mode 100644 docs/codox/mw-ui.routes.home.html create mode 100644 docs/codox/mw-ui.routes.load.html create mode 100644 docs/codox/mw-ui.routes.params.html create mode 100644 docs/codox/mw-ui.routes.rules.html create mode 100644 docs/codox/mw-ui.routes.save.html create mode 100644 docs/codox/mw-ui.util.html diff --git a/docs/cloverage/coverage.css b/docs/cloverage/coverage.css new file mode 100644 index 0000000..2be4e57 --- /dev/null +++ b/docs/cloverage/coverage.css @@ -0,0 +1,40 @@ +.covered { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; + background-color: #558B55; +} + +.not-covered { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; + background-color: red; +} + +.partial { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; + background-color: orange; +} + +.not-tracked { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; +} + +.blank { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; +} + +td { + padding-right: 10px; +} + +td.with-bar { + width: 250px; + text-align: center; +} + +td.with-number { + text-align: right; +} + +td.ns-name { + min-width: 150px; + padding-right: 25px; +} diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html new file mode 100644 index 0000000..bab5f30 --- /dev/null +++ b/docs/cloverage/index.html @@ -0,0 +1,200 @@ + + + + + Coverage Summary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Namespace Forms Forms % Lines Lines %TotalBlankInstrumented
mw-ui.handler
37
39
48.68 %
13
3
81.25 %861016
mw-ui.layout
47
7
87.04 %
9
2
1
91.67 %51712
mw-ui.middleware
45
16
73.77 %
13
1
100.00 %52914
mw-ui.render-world
8
199
3.86 %
8
33
19.51 %1041741
mw-ui.repl
14
47
22.95 %
5
1
13
31.58 %66819
mw-ui.routes.home
97
268
26.58 %
12
14
42
38.24 %1331268
mw-ui.routes.load
3
58
4.92 %
3
14
17.65 %62917
mw-ui.routes.params
3
145
2.03 %
3
31
8.82 %80534
mw-ui.routes.rules
30
65
31.58 %
10
1
13
45.83 %72624
mw-ui.routes.save
2
23
8.00 %
2
2
50.00 %4144
mw-ui.util
29
129
18.35 %
14
1
24
38.46 %911039
Totals:24.03 %38.89 %
+ + diff --git a/docs/cloverage/mw_ui/handler.clj.html b/docs/cloverage/mw_ui/handler.clj.html new file mode 100644 index 0000000..033626f --- /dev/null +++ b/docs/cloverage/mw_ui/handler.clj.html @@ -0,0 +1,266 @@ + + + + mw_ui/handler.clj + + + + 001  (ns ^{:doc "Set up and tear down the request handler." +
+ + 002        :author "Simon Brooke"} +
+ + 003    mw-ui.handler +
+ + 004    (:require [compojure.core :refer [defroutes]] +
+ + 005              [mw-ui.routes.home :refer [home-routes]] +
+ + 006              [mw-ui.middleware :refer [load-middleware]] +
+ + 007              [noir.response :refer [redirect]] +
+ + 008              [noir.util.middleware :refer [app-handler]] +
+ + 009              [compojure.route :as route] +
+ + 010              [taoensso.timbre :as timbre] +
+ + 011              ;; [taoensso.timbre.appenders.rotor :as rotor] +
+ + 012              [selmer.parser :as parser] +
+ + 013              [environ.core :refer [env]])) +
+ + 014   +
+ + 015  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 016  ;;;; +
+ + 017  ;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +
+ + 018  ;;;; +
+ + 019  ;;;; This program is free software; you can redistribute it and/or +
+ + 020  ;;;; modify it under the terms of the GNU General Public License +
+ + 021  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 022  ;;;; of the License, or (at your option) any later version. +
+ + 023  ;;;; +
+ + 024  ;;;; This program is distributed in the hope that it will be useful, +
+ + 025  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 026  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 027  ;;;; GNU General Public License for more details. +
+ + 028  ;;;; +
+ + 029  ;;;; You should have received a copy of the GNU General Public License +
+ + 030  ;;;; along with this program; if not, write to the Free Software +
+ + 031  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 032  ;;;; USA. +
+ + 033  ;;;; +
+ + 034  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 035  ;;;; +
+ + 036  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 037   +
+ + 038  (defroutes app-routes +
+ + 039    (route/resources "/") +
+ + 040    (route/not-found "Not Found")) +
+ + 041   +
+ + 042   +
+ + 043  (defn init +
+ + 044    "init will be called once when +
+ + 045     app is deployed as a servlet on +
+ + 046     an app server such as Tomcat +
+ + 047     put any initialization code here" +
+ + 048    [] +
+ + 049    ;; TODO fix timbre config! +
+ + 050    ;; (timbre/set-config! +
+ + 051    ;;   [:appenders :rotor] +
+ + 052    ;;   {:min-level :info +
+ + 053    ;;    :enabled? true +
+ + 054    ;;    :async? false ; should be always false for rotor +
+ + 055    ;;    :max-message-per-msecs nil +
+ + 056    ;;    :fn rotor/appender-fn}) +
+ + 057    ;; (timbre/set-config! +
+ + 058    ;;   [:shared-appender-config :rotor] +
+ + 059    ;;   {:path "mw_ui.log" :max-size (* 512 1024) :backlog 10}) +
+ + 060   +
+ + 061   +
+ + 062    (when (env :dev) (parser/cache-off!)) +
+ + 063    (timbre/info "mw-ui started successfully")) +
+ + 064   +
+ + 065   +
+ + 066  (defn destroy +
+ + 067    "destroy will be called when your application +
+ + 068     shuts down, put any clean up code here" +
+ + 069    [] +
+ + 070    (timbre/info "mw-ui is shutting down...")) +
+ + 071   +
+ + 072   +
+ + 073  (def app (app-handler +
+ + 074             ;; add your application routes here +
+ + 075             [home-routes app-routes] +
+ + 076             ;; add custom middleware here +
+ + 077             :middleware (load-middleware) +
+ + 078             ;; timeout sessions after 30 minutes +
+ + 079             :session-options {:timeout (* 60 30) +
+ + 080                               :timeout-response (redirect "/")} +
+ + 081             ;; add access rules here +
+ + 082             :access-rules [] +
+ + 083             ;; serialize/deserialize the following data formats +
+ + 084             ;; available formats: +
+ + 085             ;; :json :json-kw :yaml :yaml-kw :edn :yaml-in-html +
+ + 086             :formats [:json-kw :edn])) +
+ + diff --git a/docs/cloverage/mw_ui/layout.clj.html b/docs/cloverage/mw_ui/layout.clj.html new file mode 100644 index 0000000..4334018 --- /dev/null +++ b/docs/cloverage/mw_ui/layout.clj.html @@ -0,0 +1,161 @@ + + + + mw_ui/layout.clj + + + + 001  (ns ^{:doc "Layout content as HTML." +
+ + 002        :author "Simon Brooke"} +
+ + 003    mw-ui.layout +
+ + 004    (:require [selmer.parser :as parser] +
+ + 005              [clojure.string :as s] +
+ + 006              [ring.util.response :refer [content-type response]] +
+ + 007              [compojure.response :refer [Renderable]])) +
+ + 008   +
+ + 009  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 010  ;;;; +
+ + 011  ;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +
+ + 012  ;;;; +
+ + 013  ;;;; This program is free software; you can redistribute it and/or +
+ + 014  ;;;; modify it under the terms of the GNU General Public License +
+ + 015  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 016  ;;;; of the License, or (at your option) any later version. +
+ + 017  ;;;; +
+ + 018  ;;;; This program is distributed in the hope that it will be useful, +
+ + 019  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 020  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 021  ;;;; GNU General Public License for more details. +
+ + 022  ;;;; +
+ + 023  ;;;; You should have received a copy of the GNU General Public License +
+ + 024  ;;;; along with this program; if not, write to the Free Software +
+ + 025  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 026  ;;;; USA. +
+ + 027  ;;;; +
+ + 028  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 029  ;;;; +
+ + 030  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 031   +
+ + 032   +
+ + 033  (def template-path "templates/") +
+ + 034   +
+ + 035  (deftype RenderableTemplate [template params] +
+ + 036    Renderable +
+ + 037    (render [_ request] +
+ + 038      (content-type +
+ + 039        (->> (assoc (merge params {:version (System/getProperty "mw-ui.version")}) +
+ + 040                    (keyword (s/replace template #".html" "-selected")) "active" +
+ + 041                    :servlet-context +
+ + 042                    (when-let [context (:servlet-context request)] +
+ + 043                      (.getContextPath context))) +
+ + 044          (parser/render-file (str template-path template)) +
+ + 045          response) +
+ + 046        "text/html; charset=utf-8"))) +
+ + 047   +
+ + 048   +
+ + 049  (defn render [template & [params]] +
+ + 050    (RenderableTemplate. template params)) +
+ + 051   +
+ + diff --git a/docs/cloverage/mw_ui/middleware.clj.html b/docs/cloverage/mw_ui/middleware.clj.html new file mode 100644 index 0000000..27b3edb --- /dev/null +++ b/docs/cloverage/mw_ui/middleware.clj.html @@ -0,0 +1,164 @@ + + + + mw_ui/middleware.clj + + + + 001  (ns ^{:doc "In truth, boilerplate from Luminus." +
+ + 002        :author "Simon Brooke"} +
+ + 003   mw-ui.middleware +
+ + 004    (:require [taoensso.timbre :as timbre] +
+ + 005              [environ.core :refer [env]] +
+ + 006              [selmer.middleware :refer [wrap-error-page]] +
+ + 007              [noir-exception.core +
+ + 008               :refer [wrap-internal-error wrap-exceptions]])) +
+ + 009   +
+ + 010  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 011  ;;;; +
+ + 012  ;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +
+ + 013  ;;;; +
+ + 014  ;;;; This program is free software; you can redistribute it and/or +
+ + 015  ;;;; modify it under the terms of the GNU General Public License +
+ + 016  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 017  ;;;; of the License, or (at your option) any later version. +
+ + 018  ;;;; +
+ + 019  ;;;; This program is distributed in the hope that it will be useful, +
+ + 020  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 021  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 022  ;;;; GNU General Public License for more details. +
+ + 023  ;;;; +
+ + 024  ;;;; You should have received a copy of the GNU General Public License +
+ + 025  ;;;; along with this program; if not, write to the Free Software +
+ + 026  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 027  ;;;; USA. +
+ + 028  ;;;; +
+ + 029  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 030  ;;;; +
+ + 031  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 032   +
+ + 033   +
+ + 034  (defn log-request [handler] +
+ + 035    (fn [req] +
+ + 036      (timbre/debug req) +
+ + 037      (handler req))) +
+ + 038   +
+ + 039   +
+ + 040  (def development-middleware +
+ + 041    [log-request +
+ + 042     wrap-error-page +
+ + 043     wrap-exceptions]) +
+ + 044   +
+ + 045   +
+ + 046  (def production-middleware +
+ + 047    [#(wrap-internal-error % :log (fn [e] (timbre/error e)))]) +
+ + 048   +
+ + 049   +
+ + 050  (defn load-middleware [] +
+ + 051    (concat (when (env :dev) development-middleware) +
+ + 052            production-middleware)) +
+ + diff --git a/docs/cloverage/mw_ui/render_world.clj.html b/docs/cloverage/mw_ui/render_world.clj.html new file mode 100644 index 0000000..79fe6e2 --- /dev/null +++ b/docs/cloverage/mw_ui/render_world.clj.html @@ -0,0 +1,320 @@ + + + + mw_ui/render_world.clj + + + + 001  (ns ^{:doc "Render the state of the world as an HTML table." +
+ + 002        :author "Simon Brooke"} +
+ + 003    mw-ui.render-world +
+ + 004    (:require [mw-engine.core :as engine] +
+ + 005              [mw-engine.heightmap :as heightmap] +
+ + 006              [mw-parser.bulk :as compiler] +
+ + 007              [noir.io :as io] +
+ + 008              [noir.session :as session])) +
+ + 009   +
+ + 010  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 011  ;;;; +
+ + 012  ;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +
+ + 013  ;;;; +
+ + 014  ;;;; This program is free software; you can redistribute it and/or +
+ + 015  ;;;; modify it under the terms of the GNU General Public License +
+ + 016  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 017  ;;;; of the License, or (at your option) any later version. +
+ + 018  ;;;; +
+ + 019  ;;;; This program is distributed in the hope that it will be useful, +
+ + 020  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 021  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 022  ;;;; GNU General Public License for more details. +
+ + 023  ;;;; +
+ + 024  ;;;; You should have received a copy of the GNU General Public License +
+ + 025  ;;;; along with this program; if not, write to the Free Software +
+ + 026  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 027  ;;;; USA. +
+ + 028  ;;;; +
+ + 029  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 030  ;;;; +
+ + 031  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 032   +
+ + 033   +
+ + 034  (defn format-css-class  +
+ + 035    "Format this statekey, assumed to be a keyword indicating a state in the +
+ + 036     world, into a CSS class" +
+ + 037    [statekey] +
+ + 038    (subs (str statekey) 1)) +
+ + 039   +
+ + 040   +
+ + 041  (defn format-image-path +
+ + 042    "Render this statekey, assumed to be a keyword indicating a state in the +
+ + 043     world, into a path which should recover the corresponding image file." +
+ + 044    [statekey] +
+ + 045    (format "img/tiles/%s.png" (format-css-class statekey))) +
+ + 046   +
+ + 047   +
+ + 048  (defn format-mouseover [cell] +
+ + 049    (str cell)) +
+ + 050   +
+ + 051   +
+ + 052  (defn render-cell +
+ + 053    "Render this world cell as a Hiccup table cell." +
+ + 054    [cell] +
+ + 055    (let [state (:state cell)] +
+ + 056      [:td {:class (format-css-class state) :title (format-mouseover cell)} +
+ + 057       [:a {:href (format "inspect?x=%d&y=%d" (:x cell) (:y cell))} +
+ + 058        [:img {:alt (:state cell) :src (format-image-path state)}]]])) +
+ + 059   +
+ + 060   +
+ + 061  (defn render-world-row +
+ + 062    "Render this world row as a Hiccup table row." +
+ + 063    [row] +
+ + 064    (apply vector (cons :tr (map render-cell row)))) +
+ + 065   +
+ + 066   +
+ + 067  (defn render-world-table +
+ + 068    "Render the world implied by the current session as a complete HTML table in a DIV." +
+ + 069    [] +
+ + 070    (let [world (or (session/get :world) +
+ + 071                    (heightmap/apply-heightmap +
+ + 072                        (io/get-resource "/img/heightmaps/small_hill.png"))) +
+ + 073          rules (or (session/get :rules) +
+ + 074                    (do (session/put! :rules +
+ + 075                                      (compiler/compile-file +
+ + 076                                        (io/get-resource "/rulesets/basic.txt"))) +
+ + 077                      (session/get :rules))) +
+ + 078          generation (inc (or (session/get :generation) 0)) +
+ + 079          w2 (engine/transform-world world rules) +
+ + 080          ] +
+ + 081      (session/put! :world w2) +
+ + 082      (session/put! :generation generation) +
+ + 083      [:div {:class "world"} +
+ + 084        (apply vector +
+ + 085                   (cons :table +
+ + 086                         (map render-world-row w2))) +
+ + 087        [:p +
+ + 088         (str "Generation " generation)]])) +
+ + 089   +
+ + 090   +
+ + 091  (defn render-inspector +
+ + 092    "Render in Hiccup format the HTML content of an inspector on this cell." +
+ + 093    ([cell _] (render-inspector cell)) +
+ + 094    ([cell] +
+ + 095    [:table {:class "music-ruled"} +
+ + 096     [:tr +
+ + 097      [:td {:colspan 2 :style "text-align: center;"} +
+ + 098       [:img {:src (str "img/tiles/" (name (:state cell)) ".png") +
+ + 099              :width 64 +
+ + 100              :height 64}]]] +
+ + 101     [:tr [:th "Key"][:th "Value"]] +
+ + 102     (map #(vector :tr (vector :th %)(vector :td (cell %))) (keys cell))])) +
+ + 103   +
+ + 104   +
+ + diff --git a/docs/cloverage/mw_ui/repl.clj.html b/docs/cloverage/mw_ui/repl.clj.html new file mode 100644 index 0000000..df14b67 --- /dev/null +++ b/docs/cloverage/mw_ui/repl.clj.html @@ -0,0 +1,206 @@ + + + + mw_ui/repl.clj + + + + 001  (ns ^{:doc "In truth, boilerplate from Luminus." +
+ + 002        :author "Simon Brooke"} +
+ + 003    mw-ui.repl +
+ + 004    (:require [mw-ui.handler :as handler] +
+ + 005          [ring.server.standalone :refer [serve]] +
+ + 006          [ring.middleware.file :refer [wrap-file]] +
+ + 007          [ring.middleware.file-info :refer [wrap-file-info]]) +
+ + 008    (:gen-class)) +
+ + 009   +
+ + 010  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 011  ;;;; +
+ + 012  ;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +
+ + 013  ;;;; +
+ + 014  ;;;; This program is free software; you can redistribute it and/or +
+ + 015  ;;;; modify it under the terms of the GNU General Public License +
+ + 016  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 017  ;;;; of the License, or (at your option) any later version. +
+ + 018  ;;;; +
+ + 019  ;;;; This program is distributed in the hope that it will be useful, +
+ + 020  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 021  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 022  ;;;; GNU General Public License for more details. +
+ + 023  ;;;; +
+ + 024  ;;;; You should have received a copy of the GNU General Public License +
+ + 025  ;;;; along with this program; if not, write to the Free Software +
+ + 026  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 027  ;;;; USA. +
+ + 028  ;;;; +
+ + 029  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 030  ;;;; +
+ + 031  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 032   +
+ + 033   +
+ + 034  (defonce server (atom nil)) +
+ + 035   +
+ + 036  (defn get-handler [] +
+ + 037    ;; #'app expands to (var app) so that when we reload our code, +
+ + 038    ;; the server is forced to re-resolve the symbol in the var +
+ + 039    ;; rather than having its own copy. When the root binding +
+ + 040    ;; changes, the server picks it up without having to restart. +
+ + 041    (-> #'handler/app +
+ + 042        ; Makes static assets in $PROJECT_DIR/resources/public/ available. +
+ + 043        (wrap-file "resources") +
+ + 044        ; Content-Type, Content-Length, and Last Modified headers for files in body +
+ + 045        (wrap-file-info))) +
+ + 046   +
+ + 047  (defn start-server +
+ + 048    "used for starting the server in development mode from REPL" +
+ + 049    [& [port]] +
+ + 050    (let [port (if port (Integer/parseInt port) 3000)] +
+ + 051      (reset! server +
+ + 052              (serve (get-handler) +
+ + 053                     {:port port +
+ + 054                      :init handler/init +
+ + 055                      :auto-reload? true +
+ + 056                      :destroy handler/destroy +
+ + 057                      :join? false})) +
+ + 058      (println (str "You can view the site at http://localhost:" port)))) +
+ + 059   +
+ + 060  (defn stop-server [] +
+ + 061    (.stop @server) +
+ + 062    (reset! server nil)) +
+ + 063   +
+ + 064  (defn -main [] +
+ + 065    (start-server)) +
+ + 066   +
+ + diff --git a/docs/cloverage/mw_ui/routes/home.clj.html b/docs/cloverage/mw_ui/routes/home.clj.html new file mode 100644 index 0000000..ef48113 --- /dev/null +++ b/docs/cloverage/mw_ui/routes/home.clj.html @@ -0,0 +1,407 @@ + + + + mw_ui/routes/home.clj + + + + 001  (ns ^{:doc "Routes which serve the main pages of the application." +
+ + 002        :author "Simon Brooke"} +
+ + 003    mw-ui.routes.home +
+ + 004    (:require [clojure.java.io :refer [file]] +
+ + 005              [clojure.walk :refer [keywordize-keys]] +
+ + 006              [compojure.core :refer [defroutes GET POST]] +
+ + 007              [hiccup.core :refer [html]] +
+ + 008              [mw-engine.utils :as engine-utils] +
+ + 009              [mw-ui.layout :as layout] +
+ + 010              [mw-ui.render-world :as world] +
+ + 011              [mw-ui.routes.load :as load] +
+ + 012              [mw-ui.routes.rules :as rules] +
+ + 013              [mw-ui.routes.params :as params] +
+ + 014              [mw-ui.routes.save :as save] +
+ + 015              [mw-ui.util :as util] +
+ + 016              [noir.session :as session] +
+ + 017              [ring.util.response :as response])) +
+ + 018   +
+ + 019  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 020  ;;;; +
+ + 021  ;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +
+ + 022  ;;;; +
+ + 023  ;;;; This program is free software; you can redistribute it and/or +
+ + 024  ;;;; modify it under the terms of the GNU General Public License +
+ + 025  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 026  ;;;; of the License, or (at your option) any later version. +
+ + 027  ;;;; +
+ + 028  ;;;; This program is distributed in the hope that it will be useful, +
+ + 029  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 030  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 031  ;;;; GNU General Public License for more details. +
+ + 032  ;;;; +
+ + 033  ;;;; You should have received a copy of the GNU General Public License +
+ + 034  ;;;; along with this program; if not, write to the Free Software +
+ + 035  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 036  ;;;; USA. +
+ + 037  ;;;; +
+ + 038  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 039  ;;;; +
+ + 040  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 041   +
+ + 042   +
+ + 043  (defn list-states [] +
+ + 044    (sort +
+ + 045      (filter #(not (nil? %)) +
+ + 046              (map #(first (rest (re-matches #"([0-9a-z-]+).png" (.getName %)))) +
+ + 047                   ;; TODO: this will not work when running from jar; see utils.clj +
+ + 048                   (file-seq (file "resources/public/img/tiles")))))) +
+ + 049   +
+ + 050   +
+ + 051  (defn about-page [] +
+ + 052    (layout/render "trusted-content.html" +
+ + 053                   {:title "About MicroWorld" +
+ + 054                    :about-selected "active" +
+ + 055                    :content (util/md->html "/md/about.md") +
+ + 056                    :version (System/getProperty "mw-ui.version")})) +
+ + 057   +
+ + 058  (defn docs-page [] +
+ + 059    (layout/render "docs.html" {:title "Documentation" +
+ + 060                                :parser (util/md->html "/md/mw-parser.md" ) +
+ + 061                                :states (util/list-resources "/img/tiles" #"([0-9a-z-_]+).png") +
+ + 062                                :lessons (util/list-resources "/md/lesson-plans"  #"([0-9a-z-_]+).md") +
+ + 063                                :components ["mw-engine" "mw-parser" "mw-ui"] +
+ + 064                                :version (System/getProperty "mw-ui.version")})) +
+ + 065   +
+ + 066  (defn home-page  +
+ + 067    "Render the home page." +
+ + 068    [] +
+ + 069    (layout/render "trusted-content.html" {:title "Welcome to MicroWorld" +
+ + 070                                :content (util/md->html "/md/mw-ui.md") +
+ + 071                                :version (System/getProperty "mw-ui.version")})) +
+ + 072   +
+ + 073  (defn inspect-page  +
+ + 074    "Open an inspector on the cell at the co-ordinates specified in this request" +
+ + 075    [request] +
+ + 076    (let [params (keywordize-keys (:params request)) +
+ + 077          xs (:x params) +
+ + 078          ys (:y params) +
+ + 079          x (if (seq xs) (read-string xs) 0) +
+ + 080          y (if (seq ys) (read-string ys) 0) +
+ + 081          world (session/get :world) +
+ + 082          cell (engine-utils/get-cell world x y) +
+ + 083          state (:state params)] +
+ + 084      (cond state +
+ + 085        (do +
+ + 086          (session/put! :world (engine-utils/set-property world cell :state (keyword state))) +
+ + 087          (response/redirect "world")) +
+ + 088        true +
+ + 089        (layout/render "inspector.html" +
+ + 090                       {:title (format "Inspect cell at %d, %d" x y) +
+ + 091                        :content (html (world/render-inspector cell)) +
+ + 092                        :cell cell +
+ + 093                        :x (:x cell) +
+ + 094                        :y (:y cell) +
+ + 095                        :states (util/list-resources +
+ + 096                                  "/img/tiles" #"([0-9a-z-_]+).png")})))) +
+ + 097   +
+ + 098  (defn md-page +
+ + 099    "Render the markdown page specified in this request, if any. Probably undesirable, +
+ + 100     should be removed." +
+ + 101    [request] +
+ + 102    (let [params (keywordize-keys (:params request)) +
+ + 103          content (or (:content params) "missing.md")] +
+ + 104      (layout/render "trusted-content.html" +
+ + 105                     {:title "Welcome to MicroWorld" +
+ + 106                      :content (util/md->html (str "/md/" content))}))) +
+ + 107   +
+ + 108  (defn world-page [] +
+ + 109    "Render the world in the current session (or a default one if none)." +
+ + 110    (layout/render "trusted-content.html" +
+ + 111                   {:title "Watch your world grow" +
+ + 112                   :world-selected "active" +
+ + 113                   :content (html (world/render-world-table)) +
+ + 114                   :pause (or (session/get :pause) 5) +
+ + 115                   :maybe-refresh "refresh"})) +
+ + 116   +
+ + 117   +
+ + 118  (defroutes home-routes +
+ + 119    (GET  "/" [] (home-page)) +
+ + 120    (GET  "/about" [] (about-page)) +
+ + 121    (GET  "/docs"  [] (docs-page)) +
+ + 122    (GET  "/inspect" request (inspect-page request)) +
+ + 123    (POST "/inspect" request (inspect-page request)) +
+ + 124    (GET  "/load" [] (load/load-page)) +
+ + 125    (POST "/load" request (load/load-page request)) +
+ + 126    (GET  "/md" request (md-page request)) +
+ + 127    (GET  "/params" [] (params/params-page)) +
+ + 128    (POST "/params" request (params/params-page request)) +
+ + 129    (GET  "/rules" request (rules/rules-page request)) +
+ + 130    (POST "/rules" request (rules/rules-page request)) +
+ + 131    (GET  "/saved-map.mwm" [] (save/save-page)) +
+ + 132    (GET  "/world"  [] (world-page)) +
+ + 133    ) +
+ + diff --git a/docs/cloverage/mw_ui/routes/load.clj.html b/docs/cloverage/mw_ui/routes/load.clj.html new file mode 100644 index 0000000..7b7e98f --- /dev/null +++ b/docs/cloverage/mw_ui/routes/load.clj.html @@ -0,0 +1,194 @@ + + + + mw_ui/routes/load.clj + + + + 001  (ns ^{:doc "Route which handles the upload of worlds/rules from the client." +
+ + 002        :author "Simon Brooke"} +
+ + 003    mw-ui.routes.load +
+ + 004    (:require [clojure.walk :refer [keywordize-keys]] +
+ + 005              [noir.io :as io] +
+ + 006              [noir.session :as session] +
+ + 007              [mw-ui.layout :as layout])) +
+ + 008   +
+ + 009  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 010  ;;;; +
+ + 011  ;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +
+ + 012  ;;;; +
+ + 013  ;;;; This program is free software; you can redistribute it and/or +
+ + 014  ;;;; modify it under the terms of the GNU General Public License +
+ + 015  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 016  ;;;; of the License, or (at your option) any later version. +
+ + 017  ;;;; +
+ + 018  ;;;; This program is distributed in the hope that it will be useful, +
+ + 019  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 020  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 021  ;;;; GNU General Public License for more details. +
+ + 022  ;;;; +
+ + 023  ;;;; You should have received a copy of the GNU General Public License +
+ + 024  ;;;; along with this program; if not, write to the Free Software +
+ + 025  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 026  ;;;; USA. +
+ + 027  ;;;; +
+ + 028  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 029  ;;;; +
+ + 030  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 031   +
+ + 032   +
+ + 033  (defn- upload [file] +
+ + 034    (io/upload-file "/tmp/" file) +
+ + 035    (cond +
+ + 036     (session/put! :world +
+ + 037                  (with-open [_ (java.io.FileReader. (:tempfile file))] (read))) +
+ + 038      (str "Successfully loaded your world from " (:filename file)))) +
+ + 039   +
+ + 040   +
+ + 041  (defn load-page +
+ + 042    "If no args, show the load form; with args, load a world file from the client. +
+ + 043   +
+ + 044     *NOTE* that this reads a Clojure form from an untrusted client and should almost +
+ + 045     certainly NOT be enabled on a public-facing site, especially not on the Internet. +
+ + 046   +
+ + 047     *TODO* doesn't work yet." +
+ + 048    ([] +
+ + 049     (load-page nil)) +
+ + 050    ([request] +
+ + 051       (let [file (:file request)] +
+ + 052         (try +
+ + 053           (layout/render "load.html" +
+ + 054                          {:title "Load World" +
+ + 055                           :message (upload file)}) +
+ + 056   +
+ + 057           (catch Exception any +
+ + 058                 (layout/render "load.html" +
+ + 059                              {:title "Load World" +
+ + 060                               :message "Failed to load your world" +
+ + 061                               :error (str (.getName (.getClass any)) ": " (.getMessage any))})))))) +
+ + 062   +
+ + diff --git a/docs/cloverage/mw_ui/routes/params.clj.html b/docs/cloverage/mw_ui/routes/params.clj.html new file mode 100644 index 0000000..4e4e355 --- /dev/null +++ b/docs/cloverage/mw_ui/routes/params.clj.html @@ -0,0 +1,248 @@ + + + + mw_ui/routes/params.clj + + + + 001  (ns ^{:doc "Route which serves and handles the parameters page." +
+ + 002        :author "Simon Brooke"} +
+ + 003    mw-ui.routes.params +
+ + 004    (:require [clojure.walk :refer [keywordize-keys]] +
+ + 005              [mw-engine.heightmap :as heightmap] +
+ + 006              [mw-parser.bulk :as compiler] +
+ + 007              [mw-ui.layout :as layout] +
+ + 008              [mw-ui.util :as util] +
+ + 009              [noir.io :as io] +
+ + 010              [noir.session :as session])) +
+ + 011   +
+ + 012  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 013  ;;;; +
+ + 014  ;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +
+ + 015  ;;;; +
+ + 016  ;;;; This program is free software; you can redistribute it and/or +
+ + 017  ;;;; modify it under the terms of the GNU General Public License +
+ + 018  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 019  ;;;; of the License, or (at your option) any later version. +
+ + 020  ;;;; +
+ + 021  ;;;; This program is distributed in the hope that it will be useful, +
+ + 022  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 023  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 024  ;;;; GNU General Public License for more details. +
+ + 025  ;;;; +
+ + 026  ;;;; You should have received a copy of the GNU General Public License +
+ + 027  ;;;; along with this program; if not, write to the Free Software +
+ + 028  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 029  ;;;; USA. +
+ + 030  ;;;; +
+ + 031  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 032  ;;;; +
+ + 033  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 034   +
+ + 035   +
+ + 036  (defn- send-params [] +
+ + 037    {:title "Choose your world" +
+ + 038     :heightmaps (util/list-resources "/img/heightmaps" #"/?([0-9a-z-_]+).png") +
+ + 039     :pause (or (session/get :pause) 5) +
+ + 040     :rulesets (util/list-resources "/rulesets" #"/?([0-9a-z-_]+).txt") +
+ + 041     }) +
+ + 042   +
+ + 043   +
+ + 044  (defn params-page +
+ + 045    "Handler for params request. If no `request` passed, show empty params form. +
+ + 046     If `request` is passed, put parameters from request into session and show +
+ + 047     the world page." +
+ + 048    ([] +
+ + 049      (layout/render "params.html" (send-params))) +
+ + 050    ([request] +
+ + 051      (try +
+ + 052        (let [params (keywordize-keys (:form-params request)) +
+ + 053              map (:heightmap params) +
+ + 054              pause (:pause params) +
+ + 055              rulefile (:ruleset params) +
+ + 056              rulepath (str "/rulesets/" rulefile ".txt")] +
+ + 057          (when (not= map "") +
+ + 058            (session/put! :world +
+ + 059                          (heightmap/apply-heightmap +
+ + 060                            (io/get-resource (str "/img/heightmaps/" map ".png"))))) +
+ + 061          (when (not= rulefile "") +
+ + 062            (session/put! :rule-text (io/slurp-resource rulepath)) +
+ + 063            (session/put! :rules (compiler/compile-file (io/get-resource rulepath)))) +
+ + 064          (when (not= pause "") +
+ + 065            (session/put! :pause pause)) +
+ + 066          (layout/render "params.html" +
+ + 067                         (merge (send-params) +
+ + 068                                {:r rulefile +
+ + 069                                 :h map +
+ + 070                                 :message "Your parameters are saved, now look at your world"}))) +
+ + 071        (catch Exception e +
+ + 072          (let [params (keywordize-keys (:form-params request))] +
+ + 073            (layout/render "params.html" +
+ + 074                           (merge (send-params) +
+ + 075                                  {:title "Choose your world" +
+ + 076                                   :r (:ruleset params) +
+ + 077                                   :h (:heightmap params) +
+ + 078                                   :message "Your paramters are not saved" +
+ + 079                                   :error (str (.getName (.getClass e)) ": " (.getMessage e) "; " params)} +
+ + 080                                  ))))))) +
+ + diff --git a/docs/cloverage/mw_ui/routes/rules.clj.html b/docs/cloverage/mw_ui/routes/rules.clj.html new file mode 100644 index 0000000..1f2d7e2 --- /dev/null +++ b/docs/cloverage/mw_ui/routes/rules.clj.html @@ -0,0 +1,224 @@ + + + + mw_ui/routes/rules.clj + + + + 001  (ns ^{:doc "Route which serves and handles the rules page." +
+ + 002        :author "Simon Brooke"} +
+ + 003    mw-ui.routes.rules +
+ + 004    (:require [clojure.walk :refer [keywordize-keys]] +
+ + 005              [mw-parser.bulk :as compiler] +
+ + 006              [mw-ui.layout :as layout] +
+ + 007              [noir.io :as io] +
+ + 008              [noir.session :as session])) +
+ + 009   +
+ + 010  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 011  ;;;; +
+ + 012  ;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +
+ + 013  ;;;; +
+ + 014  ;;;; This program is free software; you can redistribute it and/or +
+ + 015  ;;;; modify it under the terms of the GNU General Public License +
+ + 016  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 017  ;;;; of the License, or (at your option) any later version. +
+ + 018  ;;;; +
+ + 019  ;;;; This program is distributed in the hope that it will be useful, +
+ + 020  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 021  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 022  ;;;; GNU General Public License for more details. +
+ + 023  ;;;; +
+ + 024  ;;;; You should have received a copy of the GNU General Public License +
+ + 025  ;;;; along with this program; if not, write to the Free Software +
+ + 026  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 027  ;;;; USA. +
+ + 028  ;;;; +
+ + 029  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 030  ;;;; +
+ + 031  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 032   +
+ + 033   +
+ + 034  (defn process-rules-request +
+ + 035    [request] +
+ + 036    (let [src (:src (keywordize-keys (:form-params request)))] +
+ + 037        (try +
+ + 038          (cond src +
+ + 039            (let [rules (compiler/compile-string src)] +
+ + 040              {:rule-text src +
+ + 041               :rules rules +
+ + 042               :message (str "Successfully compiled " +
+ + 043                             (count rules) +
+ + 044                             " rules")           }) +
+ + 045            :else {:rule-text (or +
+ + 046                               (session/get :rule-text) +
+ + 047                               (io/slurp-resource "/rulesets/basic.txt")) +
+ + 048                  :message "No rules found in request; loading defaults"}) +
+ + 049          (catch Exception e +
+ + 050            {:rule-text src +
+ + 051             :message "An error occurred during compilation" +
+ + 052             :error (str (.getName (.getClass e)) ": " (.getMessage e))})))) +
+ + 053   +
+ + 054   +
+ + 055  (defn rules-page +
+ + 056    "Request handler for the `rules` request. If the `request` contains a value +
+ + 057     for `:src`, treat that as rule source and try to compile it. If compilation +
+ + 058     succeeds, stash the compiled rules and the rule text on the session, and +
+ + 059     provide feedback; if not, provide feedback. +
+ + 060   +
+ + 061     If `request` doesn't contain a value for `:src`, load basic rule source from +
+ + 062     the session or from `resources/rulesets/basic.txt` and pass that back." +
+ + 063    ([request] +
+ + 064      (let [processed (process-rules-request request)] +
+ + 065        (when (:rules processed) +
+ + 066          (session/put! :rules (:rules processed))) +
+ + 067        (when (:rule-text processed) +
+ + 068          (session/put! :rule-text (:rule-text processed))) +
+ + 069        (layout/render "rules.html" +
+ + 070                       (merge {:title "Edit Rules"} processed)))) +
+ + 071    ([] +
+ + 072      (rules-page nil))) +
+ + diff --git a/docs/cloverage/mw_ui/routes/save.clj.html b/docs/cloverage/mw_ui/routes/save.clj.html new file mode 100644 index 0000000..3b99a4d --- /dev/null +++ b/docs/cloverage/mw_ui/routes/save.clj.html @@ -0,0 +1,131 @@ + + + + mw_ui/routes/save.clj + + + + 001  (ns ^{:doc "Route which handles the saving of world state the client." +
+ + 002        :author "Simon Brooke"} +
+ + 003    mw-ui.routes.save +
+ + 004    (:require [clojure.pprint :refer [pprint]] +
+ + 005              [noir.session :as session] +
+ + 006              [noir.response :as response])) +
+ + 007   +
+ + 008  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 009  ;;;; +
+ + 010  ;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +
+ + 011  ;;;; +
+ + 012  ;;;; This program is free software; you can redistribute it and/or +
+ + 013  ;;;; modify it under the terms of the GNU General Public License +
+ + 014  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 015  ;;;; of the License, or (at your option) any later version. +
+ + 016  ;;;; +
+ + 017  ;;;; This program is distributed in the hope that it will be useful, +
+ + 018  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 019  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 020  ;;;; GNU General Public License for more details. +
+ + 021  ;;;; +
+ + 022  ;;;; You should have received a copy of the GNU General Public License +
+ + 023  ;;;; along with this program; if not, write to the Free Software +
+ + 024  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 025  ;;;; USA. +
+ + 026  ;;;; +
+ + 027  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 028  ;;;; +
+ + 029  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 030   +
+ + 031   +
+ + 032  (defn save-page  +
+ + 033    "Save the current world to the browser, using our own custom mime-type in +
+ + 034     an attempt to prevent the browser trying to do anything clever with it. +
+ + 035     Note that it is saved as a raw Clojure data structure, not as XML or +
+ + 036     any proprietary format." +
+ + 037    [] +
+ + 038    (response/content-type +
+ + 039      "application/journeyman-mwm; charset=utf-8" +
+ + 040      (with-out-str (pprint  (session/get :world))))) +
+ + 041   +
+ + diff --git a/docs/cloverage/mw_ui/util.clj.html b/docs/cloverage/mw_ui/util.clj.html new file mode 100644 index 0000000..776f37a --- /dev/null +++ b/docs/cloverage/mw_ui/util.clj.html @@ -0,0 +1,281 @@ + + + + mw_ui/util.clj + + + + 001  (ns ^{:doc "Utility functions used by other namespaces in this package." +
+ + 002        :author "Simon Brooke"} +
+ + 003   mw-ui.util +
+ + 004    (:require [clojure.java.io :refer [file]] +
+ + 005              [clojure.string :refer [starts-with?]] +
+ + 006              [markdown.core :as md] +
+ + 007              [noir.io :as io] +
+ + 008              [noir.session :as session] +
+ + 009              [taoensso.timbre :as timbre])) +
+ + 010   +
+ + 011  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 012  ;;;; +
+ + 013  ;;;; mw-ui: a servlet user/visualisation interface for MicroWorld. +
+ + 014  ;;;; +
+ + 015  ;;;; This program is free software; you can redistribute it and/or +
+ + 016  ;;;; modify it under the terms of the GNU General Public License +
+ + 017  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 018  ;;;; of the License, or (at your option) any later version. +
+ + 019  ;;;; +
+ + 020  ;;;; This program is distributed in the hope that it will be useful, +
+ + 021  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 022  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 023  ;;;; GNU General Public License for more details. +
+ + 024  ;;;; +
+ + 025  ;;;; You should have received a copy of the GNU General Public License +
+ + 026  ;;;; along with this program; if not, write to the Free Software +
+ + 027  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 028  ;;;; USA. +
+ + 029  ;;;; +
+ + 030  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 031  ;;;; +
+ + 032  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 033   +
+ + 034  (def running-from-filesystem  +
+ + 035    "We assume we're running from the filesystem unless we find we're not." +
+ + 036    (atom true)) +
+ + 037   +
+ + 038   +
+ + 039  (def compile-time-resources +
+ + 040    "The resources which were visible at compile time. If we are running from +
+ + 041     a JAR file, it is highly likely that these are all the resources available +
+ + 042     at run time." +
+ + 043    (let [f (file "resources/public") +
+ + 044          n (count (.getCanonicalPath f))] +
+ + 045      (remove nil? +
+ + 046              (map #(let [s (.getCanonicalPath %)] +
+ + 047                      (when (> (count s) n) +
+ + 048                        (subs s n))) +
+ + 049                   (file-seq f))))) +
+ + 050   +
+ + 051   +
+ + 052  (defn md->html +
+ + 053    "reads a markdown file from public/md and returns an HTML string" +
+ + 054    [filename] +
+ + 055    (->> +
+ + 056     (io/slurp-resource filename) +
+ + 057     (md/md-to-html-string))) +
+ + 058   +
+ + 059   +
+ + 060  (defn cache-seq-match +
+ + 061    "Do the same processing that list-resources does on names fetched from +
+ + 062     the file system, except on the resource list cached at compile time." +
+ + 063    [path pattern] +
+ + 064    (timbre/info compile-time-resources) +
+ + 065    (let [n (count path)] +
+ + 066      (remove nil? +
+ + 067              (map #(when (> (count %) n) +
+ + 068                      (let [name (subs % n)] +
+ + 069                        (last (re-matches pattern name)))) +
+ + 070                  (filter #(starts-with? % path)  +
+ + 071                           compile-time-resources))))) +
+ + 072   +
+ + 073   +
+ + 074  (defn list-resources +
+ + 075    "List resource files matching `pattern` in `directory`." +
+ + 076    [directory pattern] +
+ + 077    (let +
+ + 078     [path (str (io/resource-path) directory)] +
+ + 079      (session/put! :list-resources-path path) +
+ + 080      (try +
+ + 081        (sort +
+ + 082         (remove nil? +
+ + 083                 (if @running-from-filesystem +
+ + 084                   (map #(first (rest (re-matches pattern (.getName %)))) +
+ + 085                        (file-seq (file path))) +
+ + 086                   (cache-seq-match directory pattern)))) +
+ + 087        (catch Exception any +
+ + 088          (timbre/log (str "Not running from filesystem?" +
+ + 089                           (.getName (.getClass any)))) +
+ + 090          (reset! running-from-filesystem false) +
+ + 091          (cache-seq-match directory pattern))))) +
+ + diff --git a/docs/codox/css/default.css b/docs/codox/css/default.css new file mode 100644 index 0000000..33f78fe --- /dev/null +++ b/docs/codox/css/default.css @@ -0,0 +1,551 @@ +body { + font-family: Helvetica, Arial, sans-serif; + font-size: 15px; +} + +pre, code { + font-family: Monaco, DejaVu Sans Mono, Consolas, monospace; + font-size: 9pt; + margin: 15px 0; +} + +h1 { + font-weight: normal; + font-size: 29px; + margin: 10px 0 2px 0; + padding: 0; +} + +h2 { + font-weight: normal; + font-size: 25px; +} + +h5.license { + margin: 9px 0 22px 0; + color: #555; + font-weight: normal; + font-size: 12px; + font-style: italic; +} + +.document h1, .namespace-index h1 { + font-size: 32px; + margin-top: 12px; +} + +#header, #content, .sidebar { + position: fixed; +} + +#header { + top: 0; + left: 0; + right: 0; + height: 22px; + color: #f5f5f5; + padding: 5px 7px; +} + +#content { + top: 32px; + right: 0; + bottom: 0; + overflow: auto; + background: #fff; + color: #333; + padding: 0 18px; +} + +.sidebar { + position: fixed; + top: 32px; + bottom: 0; + overflow: auto; +} + +.sidebar.primary { + background: #e2e2e2; + border-right: solid 1px #cccccc; + left: 0; + width: 250px; +} + +.sidebar.secondary { + background: #f2f2f2; + border-right: solid 1px #d7d7d7; + left: 251px; + width: 200px; +} + +#content.namespace-index, #content.document { + left: 251px; +} + +#content.namespace-docs { + left: 452px; +} + +#content.document { + padding-bottom: 10%; +} + +#header { + background: #3f3f3f; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.4); + z-index: 100; +} + +#header h1 { + margin: 0; + padding: 0; + font-size: 18px; + font-weight: lighter; + text-shadow: -1px -1px 0px #333; +} + +#header h1 .project-version { + font-weight: normal; +} + +.project-version { + padding-left: 0.15em; +} + +#header a, .sidebar a { + display: block; + text-decoration: none; +} + +#header a { + color: #f5f5f5; +} + +.sidebar a { + color: #333; +} + +#header h2 { + float: right; + font-size: 9pt; + font-weight: normal; + margin: 4px 3px; + padding: 0; + color: #bbb; +} + +#header h2 a { + display: inline; +} + +.sidebar h3 { + margin: 0; + padding: 10px 13px 0 13px; + font-size: 19px; + font-weight: lighter; +} + +.sidebar h3 a { + color: #444; +} + +.sidebar h3.no-link { + color: #636363; +} + +.sidebar ul { + padding: 7px 0 6px 0; + margin: 0; +} + +.sidebar ul.index-link { + padding-bottom: 4px; +} + +.sidebar li { + display: block; + vertical-align: middle; +} + +.sidebar li a, .sidebar li .no-link { + border-left: 3px solid transparent; + padding: 0 10px; + white-space: nowrap; +} + +.sidebar li .no-link { + display: block; + color: #777; + font-style: italic; +} + +.sidebar li .inner { + display: inline-block; + padding-top: 7px; + height: 24px; +} + +.sidebar li a, .sidebar li .tree { + height: 31px; +} + +.depth-1 .inner { padding-left: 2px; } +.depth-2 .inner { padding-left: 6px; } +.depth-3 .inner { padding-left: 20px; } +.depth-4 .inner { padding-left: 34px; } +.depth-5 .inner { padding-left: 48px; } +.depth-6 .inner { padding-left: 62px; } + +.sidebar li .tree { + display: block; + float: left; + position: relative; + top: -10px; + margin: 0 4px 0 0; + padding: 0; +} + +.sidebar li.depth-1 .tree { + display: none; +} + +.sidebar li .tree .top, .sidebar li .tree .bottom { + display: block; + margin: 0; + padding: 0; + width: 7px; +} + +.sidebar li .tree .top { + border-left: 1px solid #aaa; + border-bottom: 1px solid #aaa; + height: 19px; +} + +.sidebar li .tree .bottom { + height: 22px; +} + +.sidebar li.branch .tree .bottom { + border-left: 1px solid #aaa; +} + +.sidebar.primary li.current a { + border-left: 3px solid #a33; + color: #a33; +} + +.sidebar.secondary li.current a { + border-left: 3px solid #33a; + color: #33a; +} + +.namespace-index h2 { + margin: 30px 0 0 0; +} + +.namespace-index h3 { + font-size: 16px; + font-weight: bold; + margin-bottom: 0; +} + +.namespace-index .topics { + padding-left: 30px; + margin: 11px 0 0 0; +} + +.namespace-index .topics li { + padding: 5px 0; +} + +.namespace-docs h3 { + font-size: 18px; + font-weight: bold; +} + +.public h3 { + margin: 0; + float: left; +} + +.usage { + clear: both; +} + +.public { + margin: 0; + border-top: 1px solid #e0e0e0; + padding-top: 14px; + padding-bottom: 6px; +} + +.public:last-child { + margin-bottom: 20%; +} + +.members .public:last-child { + margin-bottom: 0; +} + +.members { + margin: 15px 0; +} + +.members h4 { + color: #555; + font-weight: normal; + font-variant: small-caps; + margin: 0 0 5px 0; +} + +.members .inner { + padding-top: 5px; + padding-left: 12px; + margin-top: 2px; + margin-left: 7px; + border-left: 1px solid #bbb; +} + +#content .members .inner h3 { + font-size: 12pt; +} + +.members .public { + border-top: none; + margin-top: 0; + padding-top: 6px; + padding-bottom: 0; +} + +.members .public:first-child { + padding-top: 0; +} + +h4.type, +h4.dynamic, +h4.added, +h4.deprecated { + float: left; + margin: 3px 10px 15px 0; + font-size: 15px; + font-weight: bold; + font-variant: small-caps; +} + +.public h4.type, +.public h4.dynamic, +.public h4.added, +.public h4.deprecated { + font-size: 13px; + font-weight: bold; + margin: 3px 0 0 10px; +} + +.members h4.type, +.members h4.added, +.members h4.deprecated { + margin-top: 1px; +} + +h4.type { + color: #717171; +} + +h4.dynamic { + color: #9933aa; +} + +h4.added { + color: #508820; +} + +h4.deprecated { + color: #880000; +} + +.namespace { + margin-bottom: 30px; +} + +.namespace:last-child { + margin-bottom: 10%; +} + +.index { + padding: 0; + font-size: 80%; + margin: 15px 0; + line-height: 16px; +} + +.index * { + display: inline; +} + +.index p { + padding-right: 3px; +} + +.index li { + padding-right: 5px; +} + +.index ul { + padding-left: 0; +} + +.type-sig { + clear: both; + color: #088; +} + +.type-sig pre { + padding-top: 10px; + margin: 0; +} + +.usage code { + display: block; + color: #008; + margin: 2px 0; +} + +.usage code:first-child { + padding-top: 10px; +} + +p { + margin: 15px 0; +} + +.public p:first-child, .public pre.plaintext { + margin-top: 12px; +} + +.doc { + margin: 0 0 26px 0; + clear: both; +} + +.public .doc { + margin: 0; +} + +.namespace-index .doc { + margin-bottom: 20px; +} + +.namespace-index .namespace .doc { + margin-bottom: 10px; +} + +.markdown p, .markdown li, .markdown dt, .markdown dd, .markdown td { + line-height: 22px; +} + +.markdown li { + padding: 2px 0; +} + +.markdown h2 { + font-weight: normal; + font-size: 25px; + margin: 30px 0 10px 0; +} + +.markdown h3 { + font-weight: normal; + font-size: 20px; + margin: 30px 0 0 0; +} + +.markdown h4 { + font-size: 15px; + margin: 22px 0 -4px 0; +} + +.doc, .public, .namespace .index { + max-width: 680px; + overflow-x: visible; +} + +.markdown pre > code { + display: block; + padding: 10px; +} + +.markdown pre > code, .src-link a { + border: 1px solid #e4e4e4; + border-radius: 2px; +} + +.markdown code:not(.hljs), .src-link a { + background: #f6f6f6; +} + +pre.deps { + display: inline-block; + margin: 0 10px; + border: 1px solid #e4e4e4; + border-radius: 2px; + padding: 10px; + background-color: #f6f6f6; +} + +.markdown hr { + border-style: solid; + border-top: none; + color: #ccc; +} + +.doc ul, .doc ol { + padding-left: 30px; +} + +.doc table { + border-collapse: collapse; + margin: 0 10px; +} + +.doc table td, .doc table th { + border: 1px solid #dddddd; + padding: 4px 6px; +} + +.doc table th { + background: #f2f2f2; +} + +.doc dl { + margin: 0 10px 20px 10px; +} + +.doc dl dt { + font-weight: bold; + margin: 0; + padding: 3px 0; + border-bottom: 1px solid #ddd; +} + +.doc dl dd { + padding: 5px 0; + margin: 0 0 5px 10px; +} + +.doc abbr { + border-bottom: 1px dotted #333; + font-variant: none; + cursor: help; +} + +.src-link { + margin-bottom: 15px; +} + +.src-link a { + font-size: 70%; + padding: 1px 4px; + text-decoration: none; + color: #5555bb; +} diff --git a/docs/codox/css/highlight.css b/docs/codox/css/highlight.css new file mode 100644 index 0000000..d0cdaa3 --- /dev/null +++ b/docs/codox/css/highlight.css @@ -0,0 +1,97 @@ +/* +github.com style (c) Vasily Polovnyov +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #f8f8f8; +} + +.hljs-comment, +.hljs-quote { + color: #998; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-literal, +.hljs-variable, +.hljs-template-variable, +.hljs-tag .hljs-attr { + color: #008080; +} + +.hljs-string, +.hljs-doctag { + color: #d14; +} + +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #900; + font-weight: bold; +} + +.hljs-subst { + font-weight: normal; +} + +.hljs-type, +.hljs-class .hljs-title { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #000080; + font-weight: normal; +} + +.hljs-regexp, +.hljs-link { + color: #009926; +} + +.hljs-symbol, +.hljs-bullet { + color: #990073; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #0086b3; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/docs/codox/index.html b/docs/codox/index.html new file mode 100644 index 0000000..990c719 --- /dev/null +++ b/docs/codox/index.html @@ -0,0 +1,14 @@ + +Mw-ui 0.2.0-SNAPSHOT

Mw-ui 0.2.0-SNAPSHOT

Web-based user interface for MicroWorld.

Installation

To install, add the following dependency to your project or build file:

[mw-ui "0.2.0-SNAPSHOT"]

Namespaces

mw-ui.handler

Set up and tear down the request handler.

+

Public variables and functions:

mw-ui.layout

Layout content as HTML.

+

Public variables and functions:

mw-ui.middleware

In truth, boilerplate from Luminus.

+

mw-ui.render-world

Render the state of the world as an HTML table.

+

mw-ui.repl

In truth, boilerplate from Luminus.

+

Public variables and functions:

mw-ui.routes.home

Routes which serve the main pages of the application.

+

mw-ui.routes.load

Route which handles the upload of worlds/rules from the client.

+

Public variables and functions:

mw-ui.routes.params

Route which serves and handles the parameters page.

+

Public variables and functions:

mw-ui.routes.rules

Route which serves and handles the rules page.

+

Public variables and functions:

mw-ui.routes.save

Route which handles the saving of world state the client.

+

Public variables and functions:

mw-ui.util

Utility functions used by other namespaces in this package.

+
\ No newline at end of file diff --git a/docs/codox/js/highlight.min.js b/docs/codox/js/highlight.min.js new file mode 100644 index 0000000..6486ffd --- /dev/null +++ b/docs/codox/js/highlight.min.js @@ -0,0 +1,2 @@ +/*! highlight.js v9.6.0 | BSD3 License | git.io/hljslicense */ +!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/[&<>]/gm,function(e){return I[e]})}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return R(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||R(i))return i}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){l+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===s);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):E(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"===e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function g(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function h(e,n,t,r){var a=r?"":y.classPrefix,i='',i+n+o}function p(){var e,t,r,a;if(!E.k)return n(B);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(B);r;)a+=n(B.substr(t,r.index-t)),e=g(E,r),e?(M+=e[1],a+=h(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(B);return a+n(B.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!x[E.sL])return n(B);var t=e?l(E.sL,B,!0,L[E.sL]):f(B,E.sL.length?E.sL:void 0);return E.r>0&&(M+=t.r),e&&(L[E.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){k+=null!=E.sL?d():p(),B=""}function v(e){k+=e.cN?h(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(B+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?B+=n:(t.eB&&(B+=n),b(),t.rB||t.eB||(B=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?B+=n:(a.rE||a.eE||(B+=n),b(),a.eE&&(B=n));do E.cN&&(k+=C),E.skip||(M+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return B+=n,n.length||1}var N=R(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var w,E=i||N,L={},k="";for(w=E;w!==N;w=w.parent)w.cN&&(k=h(w.cN,"",!0)+k);var B="",M=0;try{for(var I,j,O=0;;){if(E.t.lastIndex=O,I=E.t.exec(t),!I)break;j=m(t.substr(O,I.index-O),I[0]),O=I.index+j}for(m(t.substr(O)),w=E;w.parent;w=w.parent)w.cN&&(k+=C);return{r:M,value:k,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function f(e,t){t=t||y.languages||E(x);var r={r:0,value:n(e)},a=r;return t.filter(R).forEach(function(n){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function g(e){return y.tabReplace||y.useBR?e.replace(M,function(e,n){return y.useBR&&"\n"===e?"
":y.tabReplace?n.replace(/\t/g,y.tabReplace):void 0}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n,t,r,o,s,p=i(e);a(p)||(y.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,s=n.textContent,r=p?l(p,s,!0):f(s),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),s)),r.value=g(r.value),e.innerHTML=r.value,e.className=h(e.className,p,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function d(e){y=o(y,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");w.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function N(){return E(x)}function R(e){return e=(e||"").toLowerCase(),x[e]||x[L[e]]}var w=[],E=Object.keys,x={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",y={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},I={"&":"&","<":"<",">":">"};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=R,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("clojure",function(e){var t={"builtin-name":"def defonce cond apply if-not if-let if not not= = < > <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"},r="a-zA-Z_\\-!.?+*=<>&#'",n="["+r+"]["+r+"0-9/;:]*",a="[-+]?\\d+(\\.\\d+)?",o={b:n,r:0},s={cN:"number",b:a,r:0},i=e.inherit(e.QSM,{i:null}),c=e.C(";","$",{r:0}),d={cN:"literal",b:/\b(true|false|nil)\b/},l={b:"[\\[\\{]",e:"[\\]\\}]"},m={cN:"comment",b:"\\^"+n},p=e.C("\\^\\{","\\}"),u={cN:"symbol",b:"[:]{1,2}"+n},f={b:"\\(",e:"\\)"},h={eW:!0,r:0},y={k:t,l:n,cN:"name",b:n,starts:h},b=[f,i,m,p,c,u,l,s,d,o];return f.c=[e.C("comment",""),y,h],h.c=b,l.c=b,{aliases:["clj"],i:/\S/,c:[f,i,m,p,c,u,l,s,d]}});hljs.registerLanguage("clojure-repl",function(e){return{c:[{cN:"meta",b:/^([\w.-]+|\s*#_)=>/,starts:{e:/$/,sL:"clojure"}}]}}); \ No newline at end of file diff --git a/docs/codox/js/jquery.min.js b/docs/codox/js/jquery.min.js new file mode 100644 index 0000000..73f33fb --- /dev/null +++ b/docs/codox/js/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.11.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m="1.11.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(l.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:k&&!k.call("\ufeff\xa0")?function(a){return null==a?"":k.call(a)}:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||n.guid++,e):void 0},now:function(){return+new Date},support:l}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=a.document,A=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,B=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:A.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:z,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=z.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return y.find(a);this.length=1,this[0]=d}return this.context=z,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};B.prototype=n.fn,y=n(z);var C=/^(?:parents|prev(?:Until|All))/,D={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!n(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function E(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return E(a,"nextSibling")},prev:function(a){return E(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(D[a]||(e=n.unique(e)),C.test(a)&&(e=e.reverse())),this.pushStack(e)}});var F=/\S+/g,G={};function H(a){var b=G[a]={};return n.each(a.match(F)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?G[a]||H(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&n.each(arguments,function(a,c){var d;while((d=n.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){if(a===!0?!--n.readyWait:!n.isReady){if(!z.body)return setTimeout(n.ready);n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(z,[n]),n.fn.trigger&&n(z).trigger("ready").off("ready"))}}});function J(){z.addEventListener?(z.removeEventListener("DOMContentLoaded",K,!1),a.removeEventListener("load",K,!1)):(z.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(z.addEventListener||"load"===event.type||"complete"===z.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===z.readyState)setTimeout(n.ready);else if(z.addEventListener)z.addEventListener("DOMContentLoaded",K,!1),a.addEventListener("load",K,!1);else{z.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&z.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!n.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}J(),n.ready()}}()}return I.promise(b)};var L="undefined",M;for(M in n(l))break;l.ownLast="0"!==M,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c=z.getElementsByTagName("body")[0];c&&(a=z.createElement("div"),a.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",b=z.createElement("div"),c.appendChild(a).appendChild(b),typeof b.style.zoom!==L&&(b.style.cssText="border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1",(l.inlineBlockNeedsLayout=3===b.offsetWidth)&&(c.style.zoom=1)),c.removeChild(a),a=b=null)}),function(){var a=z.createElement("div");if(null==l.deleteExpando){l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}}a=null}(),n.acceptData=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(n.acceptData(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f +}}function S(a,b,c){if(n.acceptData(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d]));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},X=/^(?:checkbox|radio)$/i;!function(){var a=z.createDocumentFragment(),b=z.createElement("div"),c=z.createElement("input");if(b.setAttribute("className","t"),b.innerHTML="
a",l.leadingWhitespace=3===b.firstChild.nodeType,l.tbody=!b.getElementsByTagName("tbody").length,l.htmlSerialize=!!b.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==z.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,a.appendChild(c),l.appendChecked=c.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,a.appendChild(b),b.innerHTML="",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){l.noCloneEvent=!1}),b.cloneNode(!0).click()),null==l.deleteExpando){l.deleteExpando=!0;try{delete b.test}catch(d){l.deleteExpando=!1}}a=b=c=null}(),function(){var b,c,d=z.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),l[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var Y=/^(?:input|select|textarea)$/i,Z=/^key/,$=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,ab=/^([^.]*)(?:\.(.+)|)$/;function bb(){return!0}function cb(){return!1}function db(){try{return z.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof n===L||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(F)||[""],h=b.length;while(h--)f=ab.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(F)||[""],j=b.length;while(j--)if(h=ab.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,m,o=[d||z],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||z,3!==d.nodeType&&8!==d.nodeType&&!_.test(p+n.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[n.expando]?b:new n.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),k=n.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!n.isWindow(d)){for(i=k.delegateType||p,_.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||z)&&o.push(l.defaultView||l.parentWindow||a)}m=0;while((h=o[m++])&&!b.isPropagationStopped())b.type=m>1?i:k.bindType||p,f=(n._data(h,"events")||{})[b.type]&&n._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&n.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&n.acceptData(d)&&g&&d[p]&&!n.isWindow(d)){l=d[g],l&&(d[g]=null),n.event.triggered=p;try{d[p]()}catch(r){}n.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((n.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?n(c,this).index(i)>=0:n.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ib=/^\s+/,jb=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,kb=/<([\w:]+)/,lb=/\s*$/g,sb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:l.htmlSerialize?[0,"",""]:[1,"X
","
"]},tb=eb(z),ub=tb.appendChild(z.createElement("div"));sb.optgroup=sb.option,sb.tbody=sb.tfoot=sb.colgroup=sb.caption=sb.thead,sb.th=sb.td;function vb(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==L?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==L?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,vb(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function wb(a){X.test(a.type)&&(a.defaultChecked=a.checked)}function xb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function yb(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function zb(a){var b=qb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ab(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}function Bb(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Cb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(yb(b).text=a.text,zb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&X.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}n.extend({clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!hb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ub.innerHTML=a.outerHTML,ub.removeChild(f=ub.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=vb(f),h=vb(a),g=0;null!=(e=h[g]);++g)d[g]&&Cb(e,d[g]);if(b)if(c)for(h=h||vb(a),d=d||vb(f),g=0;null!=(e=h[g]);g++)Bb(e,d[g]);else Bb(a,f);return d=vb(f,"script"),d.length>0&&Ab(d,!i&&vb(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k,m=a.length,o=eb(b),p=[],q=0;m>q;q++)if(f=a[q],f||0===f)if("object"===n.type(f))n.merge(p,f.nodeType?[f]:f);else if(mb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(kb.exec(f)||["",""])[1].toLowerCase(),k=sb[i]||sb._default,h.innerHTML=k[1]+f.replace(jb,"<$1>")+k[2],e=k[0];while(e--)h=h.lastChild;if(!l.leadingWhitespace&&ib.test(f)&&p.push(b.createTextNode(ib.exec(f)[0])),!l.tbody){f="table"!==i||lb.test(f)?""!==k[1]||lb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)n.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}n.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),l.appendChecked||n.grep(vb(p,"input"),wb),q=0;while(f=p[q++])if((!d||-1===n.inArray(f,d))&&(g=n.contains(f.ownerDocument,f),h=vb(o.appendChild(f),"script"),g&&Ab(h),c)){e=0;while(f=h[e++])pb.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.deleteExpando,m=n.event.special;null!=(d=a[h]);h++)if((b||n.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k?delete d[i]:typeof d.removeAttribute!==L?d.removeAttribute(i):d[i]=null,c.push(f))}}}),n.fn.extend({text:function(a){return W(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||z).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(vb(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&Ab(vb(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(vb(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return W(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(gb,""):void 0;if(!("string"!=typeof a||nb.test(a)||!l.htmlSerialize&&hb.test(a)||!l.leadingWhitespace&&ib.test(a)||sb[(kb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(jb,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(vb(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(vb(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,o=k-1,p=a[0],q=n.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&ob.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(i=n.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=n.map(vb(i,"script"),yb),f=g.length;k>j;j++)d=i,j!==o&&(d=n.clone(d,!0,!0),f&&n.merge(g,vb(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,n.map(g,zb),j=0;f>j;j++)d=g[j],pb.test(d.type||"")&&!n._data(d,"globalEval")&&n.contains(h,d)&&(d.src?n._evalUrl&&n._evalUrl(d.src):n.globalEval((d.text||d.textContent||d.innerHTML||"").replace(rb,"")));i=c=null}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],g=n(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Db,Eb={};function Fb(b,c){var d=n(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:n.css(d[0],"display");return d.detach(),e}function Gb(a){var b=z,c=Eb[a];return c||(c=Fb(a,b),"none"!==c&&c||(Db=(Db||n("