├── .gitignore ├── LICENSE ├── README.md ├── docs └── uberdoc.html ├── project.clj ├── src └── gssrden │ └── core.clj └── test └── gssrden └── core_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /.idea 11 | *.iml 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GSSrden 2 | ======= 3 | 4 | A [GSS][gss] plugin for [Garden][garden]. 5 | 6 | Installation 7 | ------------ 8 | 9 | ### Leiningen 10 | 11 | ````clojure 12 | [gssrden "0.2.0"] 13 | ``` 14 | 15 | ### Gradle 16 | 17 | ``` 18 | compile "gssrden:gssrden:0.2.0" 19 | ``` 20 | 21 | ### Maven 22 | 23 | ```xml 24 | 25 | gssrden 26 | gssrden 27 | 0.2.0 28 | 29 | ``` 30 | 31 | Usage 32 | ----- 33 | 34 | Install GSS as detailed [here][gss-install]. Direct your Garden-produced CSS 35 | into a `.gss` file (say, `resources/gss/screen.gss`) and link to it as detailed 36 | in the GSS installation, i.e. 37 | 38 | ```html 39 | 40 | ``` 41 | 42 | or [Hiccup][hiccup] and similar templates: 43 | 44 | ```clojure 45 | [:link {:rel "stylesheet", :type "test/gss", :href "gss/screen.gss"}] 46 | ``` 47 | 48 | For Hiccup in particular, there is also a helper function, include-gss: 49 | 50 | ```clojure 51 | (gssrden.core/include-gss "gss/screen.gss" "gss/colors.gss") 52 | ``` 53 | 54 | In addition to that Hiccup helper, the GSSrden API consists of just the 55 | `constraints` macro, which can be used like this: 56 | 57 | ```clojure 58 | (ns super-responsive.styles.screen 59 | (:require [garden.def :refer [defstyles]] 60 | [gssrden.core :refer [constraints]]) 61 | 62 | (defstyles screen 63 | [:body 64 | (constraints 65 | (== :width (:window :width)) 66 | (== :height (:window :height))) 67 | {:background-color "red"}]) 68 | ``` 69 | 70 | If you are on ClojureScript, you will have to use `:require-macros` instead. 71 | 72 | ### The constraints macro 73 | 74 | `constraints` takes a number of constraint expressions and turns them into a 75 | valid Garden property map whose keys are GSS properties and values GSS 76 | constraint strings: 77 | 78 | ```clojure 79 | (constraints 80 | (== :width (:body :width) 81 | :strength :medium 82 | :weight 1000) 83 | (<= :height (- (/ (:parent :$col-width) 2) :$my-var 15) 84 | :strong)) 85 | ;=> 86 | {:width "== body[width] !medium1000" 87 | :height "<= (::parent[$col-width] / 2 - [$my-var] - 15) !strong"} 88 | ``` 89 | 90 | The constraints can take the following forms: 91 | 92 | * `(eq-operator property goal-expression)` 93 | * `(eq-operator property goal-expression :strength strength :weight weight)` 94 | * `(eq-operator property goal-expression :s strength :w weight)` 95 | * `(eq-operator property goal-expression :strength strength)` 96 | * `(eq-operator property goal-expression :s strength)` 97 | * `(eq-operator property goal-expression strength weight)` 98 | * `(eq-operator property goal-expression strength)` 99 | * `(center-in center-target)` 100 | * `(fill fill-target)` 101 | * `center-in` and `fill` with strength and weight specified just like for 102 | the other forms. 103 | 104 | Where eq-operator is an (in)equality operator symbol, property is a GSS 105 | property (a Garden key) and goal-expression is a linear function of the 106 | properties of certain elements and GSS variables. As described in the 107 | [GSS CCSS documentation][ccss-doc], strength can be one of 108 | 109 | * `:weak` / `"weak"` / `weak` 110 | * `:medium` / `"medium"` / `medium` 111 | * `:strong` / `"strong"` / `strong` 112 | * `:require` / `"require"` / `require` 113 | 114 | and weight is just an integer. 115 | 116 | You can get a property prop of element elem like this: `(:elem :prop)`. 117 | 118 | `center-in` and `fill` are sugar inspired by QML and are simply 119 | 120 | ```clojure 121 | (= (constraints 122 | (center-in :parent)) 123 | (constraints 124 | (== :center-x (:parent :center-x)) 125 | (== :center-y (:parent :center-y)))) 126 | ``` 127 | 128 | and 129 | 130 | ```clojure 131 | (= (constraints 132 | (fill :parent)) 133 | (constraints 134 | (== :center-x (:parent :center-x)) 135 | (== :center-y (:parent :center-y)) 136 | (== :width (:parent :width)) 137 | (== :height (:parent :height)))) 138 | ``` 139 | 140 | In GSSrden custom constraint and element variables are keywords beginning 141 | with $: `:$my-var`. The special pseudo selectors are provided as the 142 | keywords `:window`, `:this` and `:parent`. You can use intrinsic properties 143 | exactly like in GSS, by prefixing with intrinsic-: `:intrinsic-height`. 144 | 145 | Note that due to the output being a map, it is not possible to declare 146 | multiple constraint for a single property in one `constraints` form. You also 147 | cannot do non-constraint property assigments in a `constraints` form (this is 148 | intentional). Since a Garden rule can contain multiple maps, you can instead 149 | do this: 150 | 151 | ```clojure 152 | [:li :a 153 | (constraints 154 | (>= :line-height 16)) 155 | (constraints 156 | (<= :line-height (/ (:window :height) 2))) 157 | {:color "purple"}] 158 | ``` 159 | 160 | See the included tests and [Marginalia][marginalia] documentation for more 161 | insight into the inner life of GSSrden. 162 | 163 | Future plans 164 | ------------ 165 | 166 | The rest of GSS: 167 | 168 | * Support `@stay` 169 | * Add [VFL-like sugar][vfl-doc] 170 | * Support [@if and @else][ifelse] 171 | 172 | The lack of raw CSS directive support in Garden complicates these tasks. 173 | 174 | Known issues 175 | ------------ 176 | 177 | Symbols won't work as element, property or variable names. Using them would risk 178 | confusion anyway, so this is not of high priority. 179 | 180 | License 181 | ------- 182 | 183 | Copyright © 2014 Pauli Jaakkola 184 | 185 | Distributed under the Eclipse Public License either version 1.0 or (at 186 | your option) any later version. 187 | 188 | [gss]: http://gridstylesheets.org/ 189 | [garden]: https://github.com/noprompt/garden 190 | [gss-install]: http://gridstylesheets.org/usage/ 191 | [hiccup]: https://github.com/weavejester/hiccup 192 | [ccss-doc]: http://gridstylesheets.org/guides/ccss/ 193 | [marginalia]: https://github.com/gdeer81/marginalia 194 | [qml]: http://en.wikipedia.org/wiki/QML 195 | [vfl-doc]: http://gridstylesheets.org/guides/vfl/ 196 | [ifelse]: http://gridstylesheets.org/guides/ifelse/ 197 | -------------------------------------------------------------------------------- /docs/uberdoc.html: -------------------------------------------------------------------------------- 1 | 2 | gssrden -- Marginalia

gssrden

0.2.0


A GSS plugin for Garden

3032 |

dependencies

org.clojure/clojure
1.6.0
org.clojure/core.match
0.2.2



(this space intentionally left almost blank)
 

A GSS plugin for Garden

3033 |
(ns gssrden.core
3034 |   (:require [clojure.string :as s]
3035 |             [clojure.core.match :refer [match]])
3036 |   (:import java.net.URI))

Internal utility functions

3037 |

True if k is a valid Garden key (a keyword, string or symbol).

3038 |
(defn- garden-key?
3039 |   [k]
3040 |   (or (keyword? k)
3041 |       (string? k)
3042 |       (symbol? k)))

Make Garden key k a string, i.e.

3043 | 3044 |
(= (key->string :foo)
3045 |    (key->string "foo")
3046 |    (key->string 'foo)
3047 |    "foo"
3048 | 
3049 |
(defn- key->string
3050 |   [k]
3051 |   (cond (keyword? k) (name k)
3052 |         (string? k) k
3053 |         (symbol? k) (str k)))

These are from hiccup.util:

3054 |
3055 |
(defprotocol ToURI
3056 |   (^URI to-uri- [x] "Convert a value into a URI."))
3057 |
(extend-protocol ToURI
3058 |   URI
3059 |   (to-uri- [u] u)
3060 |   String
3061 |   (to-uri- [s] (URI. s)))

The heavy lifting

3062 |

Generate a GSS string to specify strength or strength and weight:

3063 | 3064 |
(s-w-expr :strong 1000) ;=> "!strong1000"
3065 | (s-w-expr :strong) ;=> "!strong"
3066 | 
3067 | 3068 |

If strength is the empty string, return the empty string.

3069 |
(defn- s-w-expr
3070 |   ([s] (s-w-expr s ""))
3071 |   ([s w] (if-not (= s "")
3072 |            (str "!" (key->string s) w)
3073 |            "")))

Turn a goal expression expr (the right side of an (in)equality) into its GSS 3074 | counterpart string. To illustrate:

3075 | 3076 |
(goal-expr :$col-size) ;=> "[col-size]"
3077 | (goal-expr (- (:window :width) (:#ads :width) 15))
3078 | ;=> "::window[width] - #ads[width] - 15"
3079 | (goal-expr (:body :width)) ;=> "body[width]"
3080 | (goal-expr (:window :width)) ;=> "::window[width]"
3081 | 30 ;=> 30
3082 | 
3083 | 3084 |

Non-tail recursive, but won't consume much stack anyway.

3085 |
(defn- goal-expr
3086 |   [expr]
3087 |   (cond
3088 |     ;; Custom constraint variable
3089 |     ((every-pred garden-key? #(= (first (key->string %)) \$)) expr)
3090 |     (str "[" (key->string expr) "]")
3091 |     (coll? expr)
3092 |     (cond
3093 |       ;; Math operation
3094 |       (#{'+ '- '* '/} (first expr))
3095 |       (let [[operator & operands] expr
3096 |             math-expr (->> operands
3097 |                            (map goal-expr)
3098 |                            (interpose operator)
3099 |                            (s/join " "))]
3100 |         ;; operator precedence:
3101 |         (if (#{'+ '-} operator)
3102 |           (str "(" math-expr ")")
3103 |           math-expr))
3104 |       ;; Element property get
3105 |       ((every-pred coll? #(= (count %) 2)) expr)
3106 |       (let [[element property] (map key->string expr)
3107 |             special-pseudos #{"window" "this" "parent"}]
3108 |         (str (when (special-pseudos element) "::")
3109 |              element "[" property "]")))
3110 |     :else expr))

Turn a constraint expression c into a map {property value} where property 3111 | is the constrained property and value is the output gss string:

3112 | 3113 |
(== :width (body :width) :strengh :medium, :weight 1000))
3114 | ;=> {:width "== body[width] !medium1000"}
3115 | 
3116 | 3117 |

These can then be stuffed into the final constraint property map.

3118 |
(defn- handle-constraint
3119 |   [c]
3120 |   ;; Need to use vectors with quoted == since quoting the whole list form
3121 |   ;; would prevent center-target/fill-target from evaluating and
3122 |   ;; syntax-quoting would ns-qualify == into clojure.core/==. Furthermore,
3123 |   ;; a list starting with quoted == yields nil since '== is not a
3124 |   ;; function...
3125 |   (match (vec c)
3126 |          ;; "Full" syntax:
3127 |          [operator property goal (:or :strength :s) s (:or :weight :w) w]
3128 |          {property
3129 |            (s/trimr (s/join " " [operator (goal-expr goal) (s-w-expr s w)]))}
3130 |          ['center-in center-target (:or :strength :s) s (:or :weight :w) w]
3131 |          (into {}
3132 |                (map handle-constraint
3133 |                     [['== :center-x [center-target :center-x] s w]
3134 |                      ['== :center-y [center-target :center-y] s w]]))
3135 |          ['fill fill-target (:or :strength :s) s (:or :weight :w) w]
3136 |          (into {}
3137 |                (map handle-constraint
3138 |                     [['== :center-x [fill-target :center-x] s w]
3139 |                      ['== :center-y [fill-target :center-y] s w]
3140 |                      ['== :width [fill-target :width] s w]
3141 |                      ['== :height [fill-target :height] s w]]))
3142 |          ;; Shorter versions:
3143 |          [(:or 'center-in 'fill) _ (:or :strength :s) s] (handle-constraint
3144 |                                                            (concat c [:w ""]))
3145 |          [(:or 'center-in 'fill) _ s w] (handle-constraint
3146 |                                           (concat (take 3 c) [:s s :w w]))
3147 |          [(:or 'center-in 'fill) _ s] (handle-constraint
3148 |                                         (concat (take 3 c) [:s s :w ""]))
3149 |          [(:or 'center-in 'fill) _] (handle-constraint (concat c [:s "" :w ""]))
3150 |          [_ _ _] (handle-constraint (concat c [:s "" :w ""]))
3151 |          [_ _ _ (:or :strength :s) s] (handle-constraint (concat c [:w ""]))
3152 |          [_ _ _ s w] (handle-constraint (concat (take 3 c) [:s s :w w]))
3153 |          [_ _ _ s] (handle-constraint (concat (take 3 c) [:s s :w ""]))))

Public API

3154 |

Take the constraint expressions cs and turn them into a valid Garden 3155 | property map whose keys are GSS properties and values GSS constraint 3156 | strings:

3157 | 3158 |
(constraints
3159 |   (== :width (:body :width)
3160 |       :strength :medium
3161 |       :weight 1000)
3162 |   (<= :height (- (/ (:parent :$col-width) 2)
3163 |                  :$my-var 15)
3164 |       :strong))
3165 |   ;=> {:width "== body[width] !medium1000"
3166 |        :height
3167 |        "<= (::parent[$col-width] / 2
3168 |              - [$my-var] - 15) !strong"}
3169 | 
3170 | 3171 |

The constraints can take the following forms:

3172 | 3173 |
    3174 |
  • (eq-operator property goal-expression)
  • 3175 |
  • (eq-operator property goal-expression :strength strength :weight weight)
  • 3176 |
  • (eq-operator property goal-expression :s strength :w weight)
  • 3177 |
  • (eq-operator property goal-expression :strength strength)
  • 3178 |
  • (eq-operator property goal-expression :s strength)
  • 3179 |
  • (eq-operator property goal-expression strength weight)
  • 3180 |
  • (eq-operator property goal-expression strength)
  • 3181 |
  • (center-in center-target)
  • 3182 |
  • (fill fill-target)
  • 3183 |
  • center-in and fill with strength and weight specified just like for 3184 | the other forms.

    3185 | 3186 |

    Where eq-operator is an (in)equality operator symbol, property is a GSS 3187 | property (a Garden key) and goal-expression is a linear function of the 3188 | properties of certain elements and GSS variables. As described in the 3189 | GSS CCSS documentation, strength can be one of

  • 3190 |
  • :weak / "weak" / weak

  • 3191 |
  • :medium / "medium" / medium
  • 3192 |
  • :strong / "strong" / strong
  • 3193 |
  • :require / "require" / require

    3194 | 3195 |

    and weight is just an integer.

    3196 | 3197 |

    You can get a property prop of element elem like this: (:elem :prop).

    3198 | 3199 |

    center-in and fill are sugar inspired by QML and are simply

    3200 | 3201 |

    (= (constraints 3202 | (center-in :parent)) 3203 | (constraints 3204 | (== :center-x (:parent :center-x)) 3205 | (== :center-y (:parent :center-y))))

    3206 | 3207 |

    and

    3208 | 3209 |

    (= (constraints 3210 | (fill :parent)) 3211 | (constraints 3212 | (== :center-x (:parent :center-x)) 3213 | (== :center-y (:parent :center-y)) 3214 | (== :width (:parent :width)) 3215 | (== :height (:parent :height))))

    3216 | 3217 |

    In GSSrden custom constraint and element variables are keywords beginning 3218 | with $: :$my-var. The special pseudo selectors are provided as the 3219 | keywords :window, :this and :parent. You can use intrinsic properties 3220 | exactly like in GSS, by prefixing with intrinsic-: :intrinsic-height.

    3221 | 3222 |

    Note that due to the output being a map, it is not possible to declare 3223 | multiple constraints for a single property in one constraints form. 3224 | You also cannot do non-constraint property assigments in a constraints form 3225 | (this is intentional). Since a Garden rule can contain multiple maps, you can 3226 | instead do this:

    3227 | 3228 |

    [:li :a 3229 | (constraints 3230 | (>= :line-height 16)) 3231 | (constraints 3232 | (<= :line-height (/ (:window :height) 2))) 3233 | {:color "purple"}]

    3234 | 3235 |

    If you are confused, look at the example above and consult the GSSrden and 3236 | GSS documentation.

  • 3237 |
3238 |
(defmacro constraints
3239 |   [& cs]
3240 |   (into {} (map handle-constraint cs)))

Include a list of external gss stylesheet files.

3241 | 3242 |

adapted from hiccup.page/include-css:

3243 |
(defn include-gss
3244 |   [& styles]
3245 |   (for [style styles]
3246 |     [:link {:type "text/gss", :href (to-uri- style), :rel "stylesheet"}]))
 
-------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject gssrden "0.2.0" 2 | :description "A GSS plugin for Garden" 3 | :url "https://github.com/nilern/gssrden" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.6.0"] 7 | [org.clojure/core.match "0.2.2"]]) 8 | -------------------------------------------------------------------------------- /src/gssrden/core.clj: -------------------------------------------------------------------------------- 1 | (ns gssrden.core 2 | "A GSS plugin for Garden" 3 | (:require [clojure.string :as s] 4 | [clojure.core.match :refer [match]]) 5 | (:import java.net.URI)) 6 | 7 | ;;; Internal utility functions 8 | ;;; =========================================================================== 9 | 10 | (defn- garden-key? 11 | "True if k is a valid Garden key (a keyword, string or symbol)." 12 | [k] 13 | (or (keyword? k) 14 | (string? k) 15 | (symbol? k))) 16 | 17 | (defn- key->string 18 | "Make Garden key k a string, i.e. 19 | 20 | (= (key->string :foo) 21 | (key->string \"foo\") 22 | (key->string 'foo) 23 | \"foo\"" 24 | [k] 25 | (cond (keyword? k) (name k) 26 | (string? k) k 27 | (symbol? k) (str k))) 28 | 29 | ;;; These are from hiccup.util: 30 | 31 | (defprotocol ToURI 32 | (^URI to-uri- [x] "Convert a value into a URI.")) 33 | 34 | (extend-protocol ToURI 35 | URI 36 | (to-uri- [u] u) 37 | String 38 | (to-uri- [s] (URI. s))) 39 | 40 | ;;; The heavy lifting 41 | ;;; =========================================================================== 42 | 43 | (defn- s-w-expr 44 | "Generate a GSS string to specify strength or strength and weight: 45 | 46 | (s-w-expr :strong 1000) ;=> \"!strong1000\" 47 | (s-w-expr :strong) ;=> \"!strong\" 48 | 49 | If strength is the empty string, return the empty string." 50 | ([s] (s-w-expr s "")) 51 | ([s w] (if-not (= s "") 52 | (str "!" (key->string s) w) 53 | ""))) 54 | 55 | (defn- goal-expr 56 | "Turn a goal expression expr (the right side of an (in)equality) into its GSS 57 | counterpart string. To illustrate: 58 | 59 | (goal-expr :$col-size) ;=> \"[col-size]\" 60 | (goal-expr (- (:window :width) (:#ads :width) 15)) 61 | ;=> \"::window[width] - #ads[width] - 15\" 62 | (goal-expr (:body :width)) ;=> \"body[width]\" 63 | (goal-expr (:window :width)) ;=> \"::window[width]\" 64 | 30 ;=> 30 65 | 66 | Non-tail recursive, but won't consume much stack anyway." 67 | [expr] 68 | (cond 69 | ;; Custom constraint variable 70 | ((every-pred garden-key? #(= (first (key->string %)) \$)) expr) 71 | (str "[" (key->string expr) "]") 72 | (coll? expr) 73 | (cond 74 | ;; Math operation 75 | (#{'+ '- '* '/} (first expr)) 76 | (let [[operator & operands] expr 77 | math-expr (->> operands 78 | (map goal-expr) 79 | (interpose operator) 80 | (s/join " "))] 81 | ;; operator precedence: 82 | (if (#{'+ '-} operator) 83 | (str "(" math-expr ")") 84 | math-expr)) 85 | ;; Element property get 86 | ((every-pred coll? #(= (count %) 2)) expr) 87 | (let [[element property] (map key->string expr) 88 | special-pseudos #{"window" "this" "parent"}] 89 | (str (when (special-pseudos element) "::") 90 | element "[" property "]"))) 91 | :else expr)) 92 | 93 | (defn- handle-constraint 94 | "Turn a constraint expression c into a map {property value} where property 95 | is the constrained property and value is the output gss string: 96 | 97 | (== :width (body :width) :strengh :medium, :weight 1000)) 98 | ;=> {:width \"== body[width] !medium1000\"} 99 | 100 | These can then be stuffed into the final constraint property map." 101 | [c] 102 | ;; Need to use vectors with quoted == since quoting the whole list form 103 | ;; would prevent center-target/fill-target from evaluating and 104 | ;; syntax-quoting would ns-qualify == into clojure.core/==. Furthermore, 105 | ;; a list starting with quoted == yields nil since '== is not a 106 | ;; function... 107 | (match (vec c) 108 | ;; "Full" syntax: 109 | [operator property goal (:or :strength :s) s (:or :weight :w) w] 110 | {property 111 | (s/trimr (s/join " " [operator (goal-expr goal) (s-w-expr s w)]))} 112 | 113 | ['center-in center-target (:or :strength :s) s (:or :weight :w) w] 114 | (into {} 115 | (map handle-constraint 116 | [['== :center-x [center-target :center-x] s w] 117 | ['== :center-y [center-target :center-y] s w]])) 118 | ['fill fill-target (:or :strength :s) s (:or :weight :w) w] 119 | (into {} 120 | (map handle-constraint 121 | [['== :center-x [fill-target :center-x] s w] 122 | ['== :center-y [fill-target :center-y] s w] 123 | ['== :width [fill-target :width] s w] 124 | ['== :height [fill-target :height] s w]])) 125 | 126 | ;; Shorter versions: 127 | [(:or 'center-in 'fill) _ (:or :strength :s) s] (handle-constraint 128 | (concat c [:w ""])) 129 | [(:or 'center-in 'fill) _ s w] (handle-constraint 130 | (concat (take 3 c) [:s s :w w])) 131 | [(:or 'center-in 'fill) _ s] (handle-constraint 132 | (concat (take 3 c) [:s s :w ""])) 133 | [(:or 'center-in 'fill) _] (handle-constraint (concat c [:s "" :w ""])) 134 | 135 | [_ _ _] (handle-constraint (concat c [:s "" :w ""])) 136 | [_ _ _ (:or :strength :s) s] (handle-constraint (concat c [:w ""])) 137 | [_ _ _ s w] (handle-constraint (concat (take 3 c) [:s s :w w])) 138 | [_ _ _ s] (handle-constraint (concat (take 3 c) [:s s :w ""])))) 139 | 140 | ;;; Public API 141 | ;;; =========================================================================== 142 | 143 | (defmacro constraints 144 | "Take the constraint expressions cs and turn them into a valid Garden 145 | property map whose keys are GSS properties and values GSS constraint 146 | strings: 147 | 148 | (constraints 149 | (== :width (:body :width) 150 | :strength :medium 151 | :weight 1000) 152 | (<= :height (- (/ (:parent :$col-width) 2) 153 | :$my-var 15) 154 | :strong)) 155 | ;=> {:width \"== body[width] !medium1000\" 156 | :height 157 | \"<= (::parent[$col-width] / 2 158 | - [$my-var] - 15) !strong\"} 159 | 160 | The constraints can take the following forms: 161 | 162 | * `(eq-operator property goal-expression)` 163 | * `(eq-operator property goal-expression :strength strength :weight weight)` 164 | * `(eq-operator property goal-expression :s strength :w weight)` 165 | * `(eq-operator property goal-expression :strength strength)` 166 | * `(eq-operator property goal-expression :s strength)` 167 | * `(eq-operator property goal-expression strength weight)` 168 | * `(eq-operator property goal-expression strength)` 169 | * `(center-in center-target)` 170 | * `(fill fill-target)` 171 | * `center-in` and `fill` with strength and weight specified just like for 172 | the other forms. 173 | 174 | Where eq-operator is an (in)equality operator symbol, property is a GSS 175 | property (a Garden key) and goal-expression is a linear function of the 176 | properties of certain elements and GSS variables. As described in the 177 | GSS CCSS documentation, strength can be one of 178 | 179 | * `:weak` / `\"weak\"` / `weak` 180 | * `:medium` / `\"medium\"` / `medium` 181 | * `:strong` / `\"strong\"` / `strong` 182 | * `:require` / `\"require\"` / `require` 183 | 184 | and weight is just an integer. 185 | 186 | You can get a property prop of element elem like this: `(:elem :prop)`. 187 | 188 | `center-in` and `fill` are sugar inspired by QML and are simply 189 | 190 | (= (constraints 191 | (center-in :parent)) 192 | (constraints 193 | (== :center-x (:parent :center-x)) 194 | (== :center-y (:parent :center-y)))) 195 | 196 | and 197 | 198 | (= (constraints 199 | (fill :parent)) 200 | (constraints 201 | (== :center-x (:parent :center-x)) 202 | (== :center-y (:parent :center-y)) 203 | (== :width (:parent :width)) 204 | (== :height (:parent :height)))) 205 | 206 | In GSSrden custom constraint and element variables are keywords beginning 207 | with $: `:$my-var`. The special pseudo selectors are provided as the 208 | keywords `:window`, `:this` and `:parent`. You can use intrinsic properties 209 | exactly like in GSS, by prefixing with intrinsic-: `:intrinsic-height`. 210 | 211 | Note that due to the output being a map, it is not possible to declare 212 | multiple constraints for a single property in one `constraints` form. 213 | You also cannot do non-constraint property assigments in a `constraints` form 214 | (this is intentional). Since a Garden rule can contain multiple maps, you can 215 | instead do this: 216 | 217 | [:li :a 218 | (constraints 219 | (>= :line-height 16)) 220 | (constraints 221 | (<= :line-height (/ (:window :height) 2))) 222 | {:color \"purple\"}] 223 | 224 | If you are confused, look at the example above and consult the GSSrden and 225 | GSS documentation." 226 | [& cs] 227 | (into {} (map handle-constraint cs))) 228 | 229 | ;; adapted from hiccup.page/include-css: 230 | (defn include-gss 231 | "Include a list of external gss stylesheet files." 232 | [& styles] 233 | (for [style styles] 234 | [:link {:type "text/gss", :href (to-uri- style), :rel "stylesheet"}])) 235 | -------------------------------------------------------------------------------- /test/gssrden/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns gssrden.core-test 2 | (:require [clojure.test :refer :all] 3 | [gssrden.core :refer [constraints]])) 4 | 5 | (deftest garden-keys 6 | (testing "keys can be" 7 | (testing ":keywords" 8 | (is (= (constraints 9 | (== :width (- (:window :width) :$emptiness))) 10 | {:width "== (::window[width] - [$emptiness])"}))) 11 | (testing "\"strings\"" 12 | (is (= (constraints 13 | (== "width" (- ("window" "width") "$emptiness"))) 14 | {"width" "== (::window[width] - [$emptiness])"}))))) 15 | ;; symbols won't work, but using them is stupid anyway... 16 | ;(testing "symbols" 17 | ; (is (= (constraints 18 | ; (== width (- (window width) $emptiness))) 19 | ; {'width "== (::window[width] - [$emptiness])"}))))) 20 | 21 | (deftest inequalities 22 | (testing "inequality operators" 23 | (is (= (constraints 24 | (== :width (:body :width))) 25 | {:width "== body[width]"})))) 26 | 27 | (deftest indexing 28 | (testing "get element property" 29 | (is (= (constraints 30 | (== :width (:body :width))) 31 | {:width "== body[width]"})))) 32 | 33 | (deftest custom-vars 34 | (testing "custom constraint variable" 35 | (is (= (constraints 36 | (<= :line-height :$base-line-height)) 37 | {:line-height "<= [$base-line-height]"}))) 38 | (testing "custom element constraint variable" 39 | (is (= (constraints 40 | (>= :width (:body :$col-width))) 41 | {:width ">= body[$col-width]"})))) 42 | 43 | (deftest special-pseudos 44 | (testing "special pseudo selectors" 45 | (testing ":window" 46 | (is (= (constraints 47 | (<= :line-height (/ (:window :height) 12))) 48 | {:line-height "<= ::window[height] / 12"}))) 49 | (testing ":this" 50 | (is (= (constraints 51 | (== :height (:this :intrinsic-height))) 52 | {:height "== ::this[intrinsic-height]"}))) 53 | (testing ":parent" 54 | (is (= (constraints 55 | (== :width (:parent :width))) 56 | {:width "== ::parent[width]"}))))) 57 | 58 | (deftest intrinsic- 59 | (testing "intrinsic properties" 60 | (is (= (constraints 61 | (== :height (:this :intrinsic-height))) 62 | {:height "== ::this[intrinsic-height]"})))) 63 | 64 | (deftest strengths&weights 65 | (testing "strengths and weights" 66 | (testing "long keys" 67 | (testing "strength" 68 | (is (= (constraints 69 | (>= :width 200 :strength :strong)) 70 | {:width ">= 200 !strong"}))) 71 | (testing "strength and weight" 72 | (is (= (constraints 73 | (>= :width 200 :strength :strong :weight 300)) 74 | {:width ">= 200 !strong300"})))) 75 | (testing "short keys" 76 | (testing "strength" 77 | (is (= (constraints 78 | (>= :width 200 :s :strong)) 79 | {:width ">= 200 !strong"}))) 80 | (testing "strength and weight" 81 | (is (= (constraints 82 | (>= :width 200 :s :strong :w 300)) 83 | {:width ">= 200 !strong300"})))))) 84 | 85 | (deftest arithmetic 86 | (testing "linear arithmetic" 87 | (testing "addition" 88 | (is (= (constraints 89 | (== :width (+ (:this :intrinsic-width) 20))) 90 | {:width "== (::this[intrinsic-width] + 20)"}))) 91 | (testing "substraction" 92 | (is (= (constraints 93 | (== :width (- (:this :intrinsic-width) 20))) 94 | {:width "== (::this[intrinsic-width] - 20)"}))) 95 | (testing "multiplication" 96 | (is (= (constraints 97 | (== :height (* (:#divvy :height) 3))) 98 | {:height "== #divvy[height] * 3"}))) 99 | (testing "division" 100 | (is (= (constraints 101 | (== :height (/ (:#divvy :height) 3))) 102 | {:height "== #divvy[height] / 3"}))) 103 | (testing "combined operations" 104 | (is (= (constraints 105 | (== :width (/ (* (- (:window :width) (:aside :width)) 106 | (+ :$magnification :$compensation)) 107 | 2))) 108 | {:width (str "== (::window[width] - aside[width]) " 109 | "* ([$magnification] + [$compensation]) " 110 | "/ 2")}))))) 111 | 112 | (deftest sugar 113 | (testing "convenience functions" 114 | (testing "center-in" 115 | (is (= (constraints 116 | (center-in :parent)) 117 | {:center-x "== ::parent[center-x]" 118 | :center-y "== ::parent[center-y]"}))) 119 | (testing "fill" 120 | (is (= (constraints 121 | (fill :parent)) 122 | {:center-x "== ::parent[center-x]" 123 | :center-y "== ::parent[center-y]" 124 | :width "== ::parent[width]" 125 | :height "== ::parent[height]"}))))) 126 | --------------------------------------------------------------------------------