Permalink
Browse files

Convert Cascade into a view template library

The intention is to use Ring and Compojure for request handling and dispatch and just use Cascade as the view technology, with (eventually) added extras from Tapestry such as classpath assets, versioned asset URLs, etc.
  • Loading branch information...
1 parent 68b4929 commit 5d9ba7b80c9b7e90878b986cc1ea593eef54e2ae @hlship committed Sep 10, 2011
Showing with 183 additions and 2,613 deletions.
  1. +1 −0 .gitignore
  2. +8 −8 project.clj
  3. +6 −131 src/main/clojure/cascade.clj
  4. +0 −170 src/main/clojure/cascade/asset.clj
  5. +0 −112 src/main/clojure/cascade/change_tracker.clj
  6. +0 −40 src/main/clojure/cascade/collection_utils.clj
  7. +0 −87 src/main/clojure/cascade/config.clj
  8. +0 −199 src/main/clojure/cascade/dispatcher.clj
  9. +31 −31 src/main/clojure/cascade/dom.clj
  10. +7 −131 src/main/clojure/cascade/exception.clj
  11. +0 −120 src/main/clojure/cascade/filter.clj
  12. +0 −26 src/main/clojure/cascade/func_utils.clj
  13. +3 −17 src/main/clojure/cascade/internal/parse_functions.clj
  14. +1 −39 src/main/clojure/cascade/internal/utils.clj
  15. +2 −2 src/main/clojure/cascade/internal/viewbuilder.clj
  16. +0 −56 src/main/clojure/cascade/jetty.clj
  17. +0 −40 src/main/clojure/cascade/logging.clj
  18. +0 −58 src/main/clojure/cascade/mock.clj
  19. +0 −106 src/main/clojure/cascade/path_map.clj
  20. +0 −31 src/main/clojure/cascade/pipeline.clj
  21. +0 −142 src/main/clojure/cascade/renderer.clj
  22. +0 −154 src/main/clojure/cascade/urls.clj
  23. +1 −6 src/main/clojure/cascade/utils.clj
  24. +0 −22 src/main/clojure/cascade/version.clj
  25. +0 −38 src/test/clojure/all_tests.clj
  26. +0 −143 src/test/clojure/app1/views.clj
  27. +0 −69 src/test/clojure/cascade/test_asset.clj
  28. +76 −128 src/test/clojure/cascade/test_cascade.clj
  29. +0 −27 src/test/clojure/cascade/test_change_tracker.clj
  30. +0 −35 src/test/clojure/cascade/test_collection_utils.clj
  31. +0 −44 src/test/clojure/cascade/test_config.clj
  32. +32 −32 src/test/clojure/cascade/test_dom.clj
  33. +0 −119 src/test/clojure/cascade/test_integration.clj
  34. +11 −15 src/test/clojure/cascade/test_parse_functions.clj
  35. +0 −67 src/test/clojure/cascade/test_path_map.clj
  36. +0 −40 src/test/clojure/cascade/test_pipeline.clj
  37. +0 −68 src/test/clojure/cascade/test_urls.clj
  38. +4 −41 src/test/clojure/cascade/test_utils.clj
  39. +0 −19 src/test/clojure/test-app.clj
View
@@ -15,3 +15,4 @@ pom.xml
*.iml
*.ipr
*.iws
+.lein-failures
View
@@ -1,14 +1,14 @@
-(def jetty-version "7.0.0.RC4")
-(def slf4j-version "1.5.2")
+(def ring-version "0.3.11")
(defproject cascade "0.2-SNAPSHOT"
:description "Simple, fast, easy web applications in idiomatic Clojure"
:url "http://github.com/hlship/cascade"
+ :source-path "src/main/clojure"
+ :resources-path "src/main/resources"
+ :test-path "src/test/clojure"
+ :dev-resources-path "src/test/resources"
:dependencies [[org.clojure/clojure "1.2.1"]
[org.clojure/clojure-contrib "1.2.0"]
- [org.slf4j/slf4j-api ~slf4j-version]
- [org.slf4j/slf4j-log4j12 ~slf4j-version]
- [org.eclipse.jetty/jetty-server ~jetty-version]
- [org.eclipse.jetty/jetty-servlet ~jetty-version]]
- :dev-dependencies [[org.easymock/easymock "2.5.1"]
- [org.seleniumhq.selenium.server/selenium-server "1.0.3" :classifier "standalone"]])
+ [ring/ring-core ~ring-version]
+ [ring/ring-jetty-adapter ~ring-version]]
+ :dev-dependencies [[ring/ring-devel ~ring-version]])
@@ -1,4 +1,4 @@
-; Copyright 2009, 2010 Howard M. Lewis Ship
+; Copyright 2009, 2010, 2011 Howard M. Lewis Ship
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
@@ -14,11 +14,9 @@
(ns ^{:doc "Core functions and macros used when implementing Cascade views and actions"}
cascade
- (:import
- [javax.servlet.http HttpServletRequest HttpServletResponse])
(:use
[clojure.contrib json]
- [cascade asset config dom path-map fail urls logging collection-utils]
+ [cascade dom]
[cascade.internal utils viewbuilder parse-functions]))
(defmacro template
@@ -28,28 +26,12 @@
(defmacro defview
"Defines a Cascade view function, which uses an embedded template. A view function may have a doc string and meta data
- preceding the parameters vector. The parameters vector is followed by an optional vector to establish URL bindings.
- The function's forms are an implicit inline block."
+ preceding the parameters vector. The function's forms are an implicit inline block."
[& forms]
- (let [[fn-name fn-params fn-bindings template-forms] (parse-function-def forms)
- env-symbol (first fn-params)
+ (let [[fn-name fn-params template-forms] (parse-function-def forms)
full-meta (merge (meta fn-name) {:cascade-type :view})]
- `(add-mapped-function
- "view"
- (defn ~fn-name ~full-meta ~fn-params
- (parse-url ~env-symbol ~fn-bindings (template ~@template-forms))))))
-
-(defmacro defaction
- "Defines a Cascade action function. An action function may have a doc string and meta data
- preceding the parameters vector. The forms are interpreted normally (not as an embedded template)."
- [& forms]
- (let [[fn-name fn-params fn-bindings forms] (parse-function-def forms)
- env-symbol (first fn-params)
- full-meta (merge (meta fn-name) {:cascade-type :action})]
- `(add-mapped-function
- "action"
- (defn ~fn-name ~full-meta ~fn-params
- (parse-url ~env-symbol ~fn-bindings ~@forms)))))
+ `(defn ~fn-name ~(meta fn-name) ~fn-params
+ (template ~@template-forms))))
(defmacro block
"Encapsulates a block of template forms as a function with parameters, typically used as
@@ -66,114 +48,7 @@
linebreak
(text-node "\r"))
-(defmacro link-map-from-function
- "Creates a link map from a function reference, extra path info (as a seq), optional extra query parameters (as a map)."
- [function extra-path-info query-parameters]
- `(link-map-from-path (path-for-function ~function) ~extra-path-info ~query-parameters))
-
-(defn link-path
- "Converts a link map into an absolute path (but not a complete URL). The returned path string
- will include any query parameters and extra path info from the link map, and will have
- been encoded by the HttpServletResponse. Uses standard keys from the env map."
- [env link-map]
- (let [^HttpServletRequest request (-> env :servlet-api :request)
- ^HttpServletResponse response (-> env :servlet-api :response)
- context-path (.getContextPath request)
- link-path (construct-absolute-path context-path link-map)]
- (.encodeURL response link-path)))
-
-(defmacro link
- "Creates a link to a view or action function. Additional path info data may be specified (as a seq of
- data items), as well as query parameters (as a map whose keys are strings or keywords and whose values
- are converted to strings.). Uses standard keys from the env map. The resulting link is returned as a string."
- ([env function]
- `(link ~env ~function nil))
- ([env function extra-path-info]
- `(link ~env ~function ~extra-path-info nil))
- ([env function extra-path-info query-parameters]
- `(link-path ~env (link-map-from-function ~function ~extra-path-info ~query-parameters))))
-
-(defmacro render-link
- "Creates a hyperlink with an href as a link to a view or action function.
- Following the function is an optional vector of extra path information, then
- an optional map of query parameters. Additional forms after that form an implicit template."
- [env function & forms]
- (let [[extra-path query-parameters template-forms] (parse-render-link-forms forms)]
- `(template :a { :href (link ~env ~function ~extra-path ~query-parameters) } [ ~@template-forms ])))
-
-(defn send-redirect
- "Sends a redirect to the client using a link map created by the link macro. Returns true."
- [env ^String link-path]
- (let [^HttpServletResponse response (-> env :servlet-api :response)]
- (.sendRedirect response link-path)
- true))
-
-(defn asset-path
- "Returns the path to a context asset as a string."
- [env path]
- (to-asset-path env (get-context-asset path)))
-
-(defn classpath-asset-path
- "Returns the path to the given classpath asset as a string."
- [env asset-path]
- (to-asset-path env (get-classpath-asset asset-path)))
-
(defn raw
"Wraps a string as a :text DOM node, but does not do any filtering of the value."
[s]
(raw-node s))
-
-(defn import-path
- "Imports a path into a vector (identified by the key) inside the resource aggregation atom stored in the env."
- [env key type path]
- (let [asset-map (get-asset type path)
- aggregation (-> env :cascade :resource-aggregation)]
- (swap! aggregation update-in [key] conj-if-missing asset-map))
- nil)
-
-(defn import-javascript-library
- "Imports a JavaScript library from the given path. type identifies where it comes from (:classpath or :context,
- the two parameter version assumes :context).
- Path should not start with a slash. Duplicate imports of the same library are ignored.
- Imported libraries are added as <script> links
- to the final page at the top of the <html>/<head> element (if present). Returns nil."
- ([env path]
- (import-javascript-library env :context path))
- ([env type path]
- (import-path env :libraries type path)))
-
-(defn import-jquery
- [env]
- "Imports the jQuery library packaged with Cascade."
- (import-javascript-library env :classpath (read-config :jquery-path)))
-
-(defn javascript
- "Adds initialization JavaScript. type should be either :immediate or :onready. If ommitted (recognized
- when type is a string), then :onready is supplied. Returns nil."
- [env type & args]
- (if (string? type)
- (apply javascript env :onready type args)
- (let [aggregation (-> env :cascade :resource-aggregation)
- formatted (apply format args)]
- (swap! aggregation update-in [type] conj formatted)
- nil)))
-
-(defn import-stylesheet
- "Imports a stylesheet into the rendered document. Like JavaScript libraries, the stylesheet will
- only be added a single time regardless of how many times it is imported. If the type is omitted,
- it defaults to :context. Returns nil."
- ([env path]
- (import-stylesheet env :context path))
- ([env type path]
- (import-path env :stylesheets type path)))
-
-(defn render-json
- "Renders JSON content (typically, a map or a seq) as the response. The response
- content type is set to \"application/json\". Returns true."
- [env json-value]
- (let [^HttpServletResponse response (-> env :servlet-api :response)]
- (.setContentType response "application/json")
- (with-open [writer (.getWriter response)]
- ;; TODO: Pretty Print it in dev mode
- (write-json json-value writer)))
- true)
@@ -1,170 +0,0 @@
-; Copyright 2009, 2010 Howard M. Lewis Ship
-;
-; Licensed under the Apache License, Version 2.0 (the "License");
-; you may not use this file except in compliance with the License.
-; You may obtain a copy of the License at
-;
-; http://www.apache.org/licenses/LICENSE-2.0
-;
-; Unless required by applicable law or agreed to in writing, software
-; distributed under the License is distributed on an "AS IS" BASIS,
-; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-; implied. See the License for the specific language governing permissions
-; and limitations under the License.
-
-(ns
- ^{:doc "Asset management"}
- cascade.asset
- (:import
- [java.io InputStream OutputStream]
- [java.net URL URLConnection]
- [javax.servlet ServletContext]
- [javax.servlet.http HttpServletRequest HttpServletResponse])
- (:require
- [clojure.contrib [str-utils2 :as s2]])
- (:use
- [clojure.contrib [duck-streams :only (copy)]]
- [cascade config fail path-map logging]))
-
-(assoc-in-config :application-version
- (Long/toHexString (System/nanoTime)))
-
-(assoc-in-config :asset-blacklist
- [#"\.class$", #"\.clj$"])
-
-(def ten-years-from-now
- (+ (System/currentTimeMillis) (* 1000 60 60 24 365 10)))
-
-;; Assets represents files stored either under the web context or stored within the classpath.
-;; Identifying an asset will eventually incorporate a locale-specific search. Ultimately,
-;; assets will be (optionally) GZip compressed, for clients which support GZip compression.
-;; Once an asset is located, it is represented as a map, with keys:
-;; :type (:context or :classpath)
-;; :path string -- path relative to the "root" (the web context, or the root of the classpath), no leading slash
-;; TODO: Extra keys for date time modified, file size, mime type?
-;; TODO: lots of caching
-
-(defn is-allowed-path
- "Apply blacklist rules to see if the path is allowed (not on the blacklist)."
- [path]
- (not-any? #(re-find % path) (read-config :asset-blacklist)))
-
-(defn fail-if-blacklisted
- "Throws RuntimeException if the path is on the blacklist."
- [path]
- (fail-unless (is-allowed-path path) "Asset '%s' is on the blacklist." path))
-
-(defn get-classpath-asset
- "Locates an asset on the classpath. Throws RuntimeException if the asset does not exist.
- Returns an asset map. The path is relative to the classpath root and should not
- start with a slash."
- [path]
- (let [asset-url (.getResource context-class-loader path)]
- (fail-if (nil? asset-url) "Asset '%s' not found on the classpath." path)
- (fail-if-blacklisted path)
- { :type :classpath
- :path path
- }))
-
-(defn get-context-asset
- "Locates an asset in the web application context. Throws RuntimeException if the asset
- does not exist. Returns an asset map. The path is relative to the web context root and should
- not start with a slash."
- [path]
- (let [^ServletContext context (read-config :servlet-context)
- asset-url (.getResource context (str "/" path))]
- (fail-if (nil? asset-url) "Asset '%s' not found in the context." path)
- { :type :context
- :path path
- }))
-
-(assoc-in-config [:asset-resolver :classpath] #'get-classpath-asset)
-
-(assoc-in-config [:asset-resolver :context] #'get-context-asset)
-
-(defn get-asset
- "Gets an asset within a domain (defined by type, :context or :classpath). Throws RuntimeException if
- the asset can not be found, or if the asset is blacklisted. The behavior of this method can be
- extended via the :asset-resolver configuration key, which is a map to function that take an env and a path."
- [type path]
- ((read-config [:asset-resolver type]) path))
-
-(defn construct-asset-path
- [env folder asset-map]
- (let [^HttpServletRequest request (-> env :servlet-api :request)
- context-path (.getContextPath request)]
- (format "%s/asset/%s/%s/%s" context-path folder (read-config :application-version) (asset-map :path))))
-
-(defmulti to-asset-path
- "Converts an asset map into a URL path that can be used by the client to obtain the contents of the asset."
- #(:type %2))
-
-(defmethod to-asset-path :classpath
- [env asset-map]
- (construct-asset-path env "classpath" asset-map))
-
-(defmethod to-asset-path :context
- [env asset-map]
- (construct-asset-path env "context" asset-map))
-
-(defn get-mime-type
- "Determine the MIME type of the path."
- [path]
- (let [^ServletContext context (read-config :servlet-context)]
- (or
- (.getMimeType context path)
- ;; TODO: internal configuration lookup
- "application/octet-stream")))
-
-(defn open-output-stream-for-asset
- "Configures the response in preperation for writing content."
- [^HttpServletResponse response ^URL asset-url ^String mime-type]
- (let [^URLConnection connection (.openConnection asset-url)
- last-modified (.getLastModified connection)
- content-length (.getContentLength connection)]
- (if-not (zero? content-length)
- (.setContentLength response content-length))
- (doto response
- (.setDateHeader "Last-Modified" last-modified)
- (.setDateHeader "Expires" ten-years-from-now)
- (.setContentType mime-type))
- (.getOutputStream response)))
-
-(defn asset-request-dispatcher
- "Processes a request for an asset. The domain-name is used in exceptions. url-provider is a function passed
- an asset-path string that returns a URL for the asset, or nil if not found."
- [env domain-name url-provider]
- (let [^HttpServletResponse response (-> env :servlet-api :response)
- [_ _ application-version & asset-path-terms] (-> env :cascade :split-path)
- asset-path (s2/join "/" asset-path-terms)
- ; TODO: Should we just ignore bad requests, let the container send a 404?
- _ (fail-if (nil? application-version) "Invalid %s asset URL." domain-name)
- _ (fail-unless (= application-version (read-config :application-version)) "Incorrect application version.")
- ^URL asset-url (url-provider asset-path)
- _ (fail-if (nil? asset-url) "Could not locate %s asset %s." domain-name asset-path)
- mime-type (get-mime-type asset-path)]
- (with-open [^OutputStream output-stream (open-output-stream-for-asset response asset-url mime-type)
- ^InputStream input-stream (.openStream asset-url)]
- (copy input-stream output-stream)
- (.flush output-stream)))
- ; If we got this far, we copied the contents (or failed, throwing an exception)
- true)
-
-(defn classpath-asset-dispatcher
- [env]
- (asset-request-dispatcher env "classpath"
- (fn [asset-path]
- (fail-if-blacklisted asset-path)
- (.getResource context-class-loader asset-path))))
-
-(defn context-asset-dispatcher
- [env]
- (asset-request-dispatcher env "context"
- (fn [asset-path]
- ; TODO: check for WEB-INF
- (let [^ServletContext context (-> env :servlet-api :context)]
- (.getResource context (str "/" asset-path))))))
-
-(add-function-to-config :dispatchers "asset/classpath" #'classpath-asset-dispatcher)
-(add-function-to-config :dispatchers "asset/context" #'context-asset-dispatcher)
-
Oops, something went wrong.

0 comments on commit 5d9ba7b

Please sign in to comment.