├── .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 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 |
--------------------------------------------------------------------------------