├── test
├── res
│ ├── nested
│ │ └── example2.eaml
│ └── example1.eaml
└── eaml
│ ├── core_test.clj
│ ├── file_test.clj
│ ├── fixtures
│ └── simple_res.clj
│ ├── test_helpers.clj
│ ├── xml_test.clj
│ ├── util_test.clj
│ ├── scope_test.clj
│ ├── parser_test.clj
│ └── compiler_test.clj
├── .gitignore
├── src
└── eaml
│ ├── error.clj
│ ├── transpile
│ ├── simple_res.clj
│ └── style.clj
│ ├── compiler.clj
│ ├── file.clj
│ ├── core.clj
│ ├── xml.clj
│ ├── scope.clj
│ ├── parser.clj
│ └── util.clj
├── project.clj
├── resources
└── grammar.bnf
├── doc
├── release_notes.md
├── 0.3.0
│ └── quickstart.md
└── 0.4.0
│ └── quickstart.md
├── README.md
└── LICENSE
/test/res/nested/example2.eaml:
--------------------------------------------------------------------------------
1 | color foo: #123456;
2 |
--------------------------------------------------------------------------------
/test/eaml/core_test.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.core-test
2 | (:require [clojure.test :refer :all]
3 | [eaml.core :refer :all]))
4 |
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | pom.xml
5 | pom.xml.asc
6 | *.jar
7 | *.class
8 | /.lein-*
9 | /.nrepl-port
10 |
11 | *.swp
12 |
13 | tmp/
14 |
15 | .idea/
16 | *.iml
17 |
--------------------------------------------------------------------------------
/test/res/example1.eaml:
--------------------------------------------------------------------------------
1 | color red: #f00;
2 | color green: #0f0;
3 | color blue : #00f;
4 |
5 | dimen activity_margins {
6 | default: 8dp;
7 | land: 12dp;
8 | }
9 |
10 | dimen button_text_size: 12dp;
11 |
12 | style Button {
13 | android:textSize: button_text_size;
14 | android:background: red;
15 | v21 {
16 | android:background: @drawable/btn_ripple;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/eaml/error.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.error
2 | (:require [clojure.pprint :refer [pprint]]
3 | [clojure.string :refer [join]]))
4 |
5 | (defn- ppr
6 | [obj]
7 | (with-out-str (clojure.pprint/pprint obj)))
8 |
9 | (defn raise!
10 | "Throws a RuntimeException with the given message as argument"
11 | [& messages]
12 | (throw (new IllegalStateException (join "" (map ppr messages)))))
13 |
14 |
15 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject eaml "0.4.0"
2 | :description "Describe your android styles expressively with eaml,
3 | an Android XML styles pre-processor."
4 | :main eaml.core
5 | :url "http://github.com/fhur/eaml"
6 | :license {:name "Eclipse Public License"
7 | :url "http://www.eclipse.org/legal/epl-v10.html"}
8 | :plugins [[lein-cloverage "1.0.6"]]
9 | :profiles {:uberjar {:aot :all}}
10 | :dependencies [[org.clojure/clojure "1.6.0"]
11 | [instaparse "1.4.1"]
12 | [presto "0.2.0"]
13 | [org.clojure/tools.cli "0.3.3"]])
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/eaml/file_test.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.file-test
2 | (:require [eaml.file :refer :all]
3 | [clojure.java.io :refer [as-file]]
4 | [presto.core :refer :all]
5 | [clojure.test :refer :all]))
6 |
7 |
8 | (expected-when "path-concat concatenates two paths" path-concat
9 | when ["foo" "bar"] = "foo/bar"
10 | when ["./foo" "./bar"] = "./foo/bar"
11 | when ["./" "bar"] = "./bar"
12 | when ["path" "foo/bar.baz"] = "path/foo/bar.baz"
13 | when ["foo/" "bar/"] = "foo/bar"
14 | when ["./foo/bar/" "./baz/"] = "./foo/bar/baz")
15 |
16 | (expected-when "filter-tree iterates recursively over a file system tree and
17 | returns all files that match the given filter" filter-tree
18 | when ["test/res" (extension-filter "eaml")]
19 | = [(as-file "test/res/nested/example2.eaml")
20 | (as-file "test/res/example1.eaml")]
21 | when ["test/res" (extension-filter "foo")]
22 | = [])
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/test/eaml/fixtures/simple_res.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.fixtures.simple-res)
2 |
3 | (def fix-simple-colors
4 | "color red: #f00;
5 | color green: #0f0;
6 | color blue: #00f;
7 | color main_color: red;")
8 |
9 | (def fix-simple-dimen
10 | "dimen small_margins: 8dp;
11 | dimen medium_margins: 12dp;
12 | dimen large_margins: 24dp;
13 | dimen default_margins: medium_margins;")
14 |
15 | (def fix-simple-strings
16 | "string hello_world: \"Hello World!\";
17 | string name: 'Pizza 123';")
18 |
19 | (def fix-simple-bools
20 | "bool is_true: true;
21 | bool aint_true: false;
22 | bool a_boolean: is_true;")
23 |
24 | (def fix-simple-res-with-configs
25 | "dimen padding {
26 | default: 12dp;
27 | v21: 24dp;
28 | land: 30dp;
29 | }
30 |
31 | string supports_ripples {
32 | default: 'nope';
33 | v21: 'yes';
34 | }
35 |
36 | color main_color: #f00;
37 | color button_color {
38 | default: main_color;
39 | v21: @drawable/btn_ripple;
40 | }
41 | ")
42 |
--------------------------------------------------------------------------------
/src/eaml/transpile/simple_res.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.transpile.simple-res
2 | (:require [eaml.scope :as scope]))
3 |
4 | (defn- mk-simple-res
5 | [nodetype id value]
6 | [nodetype {:name id} value])
7 |
8 | (defn simple-res?
9 | "true if the given node id is a color, dimen, bool, integer or string"
10 | [id]
11 | (#{:color :dimen :bool :integer :string} id))
12 |
13 | (defn transpile-simple-res
14 | "Given a simple res node, returns a map of config => [node {:name name} value]
15 | Example:
16 | {:node :dimen
17 | :id 'margins'
18 | :vals [{:config 'tablet' :value '24dp'
19 | :config 'default' :value '12dp'}]}
20 |
21 | Will return:
22 | {'default' [:color {:name 'margins'} 12dp]
23 | 'tablet' [:color {:name 'margins'} 24dp]}"
24 | [scope simple-res-node]
25 | (let [{vals :vals id :id nodetype :node} simple-res-node]
26 | (reduce (fn [config-map {config :config value :value}]
27 | (let [scoped-value (scope/resolve-expr :any value scope)]
28 | (assoc config-map config
29 | (mk-simple-res nodetype id scoped-value))))
30 | {} vals)))
31 |
--------------------------------------------------------------------------------
/test/eaml/test_helpers.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.test-helpers
2 | "Place common helper functions for testing"
3 | (:require [clojure.string :refer [split-lines join]]
4 | [eaml.util :refer :all]))
5 |
6 | (defn cleanup-xml
7 | [xml-string]
8 | (->> (split-lines xml-string)
9 | (map #(.trim %))
10 | (join)))
11 |
12 |
13 | (defn xml=?
14 | "Tests that two xml strings are semantically equal.
15 | This does not include new lines, indentation, etc."
16 | [expected actual]
17 | (= (cleanup-xml expected)
18 | (cleanup-xml actual)))
19 |
20 | (defmacro simpleres
21 | [type name value]
22 | [(keyword type) {:name name} value])
23 |
24 | (defmacro resources
25 | [& nodes]
26 | (vec (cons* nodes :resources {})))
27 |
28 | (defmacro color
29 | [name value]
30 | (simpleres :color name value))
31 |
32 | (defmacro dimen
33 | [name value]
34 | (simpleres :dimen name value))
35 |
36 | (defmacro string
37 | [name value]
38 | (simpleres :string name value))
39 |
40 | (defmacro bool
41 | [name value]
42 | (simpleres :bool name value))
43 |
44 | (defmacro item
45 | [name value]
46 | (simpleres :item name value))
47 |
48 | (defmacro style
49 | [attrs & items]
50 | (vec (cons* items :style attrs)))
51 |
52 |
53 |
--------------------------------------------------------------------------------
/test/eaml/xml_test.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.xml-test
2 | (:require [eaml.xml :refer :all]
3 | [eaml.file :refer [read-file]]
4 | [eaml.test-helpers :refer :all]
5 | [presto.core :refer :all]
6 | [clojure.test :refer :all]))
7 |
8 | (expected-when "test render-xml" render-xml-string
9 |
10 | when [[:foo]]
11 | xml=? ""
12 |
13 | when [[:foo {:name "bar" :age "12"} "a string value"]]
14 | xml=? "
15 | a string value
16 | "
17 |
18 | when [[:nested {:foo "bar"}
19 | [:first {:name "first"} "first-val"]
20 | [:second {:name "second"} [:foo {:hi "ho"}]]
21 | [:third {:name "third"} "third-val"]]]
22 | xml=? "
23 | first-val
24 |
25 |
26 |
27 | third-val
28 | "
29 |
30 | when [[:a {:x "y"}
31 | [:b {:z "w"}
32 | [:c {:foo "bar"}]]]]
33 | xml=? "
34 |
35 |
36 |
37 | ")
38 |
39 | (deftest test-config-writer
40 | (let [text (str (rand-int 100) "text")]
41 | (with-open [writer (config-writer "tmp" :foo)]
42 | (.write writer text))
43 | (is (= (str text "\n")
44 | (read-file "tmp/values-foo/values.xml")))))
45 |
--------------------------------------------------------------------------------
/src/eaml/compiler.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.compiler
2 | (:require [eaml.error :refer :all]
3 | [eaml.util :refer :all]
4 | [eaml.xml :as xml]
5 | [eaml.scope :as scope]
6 | [eaml.transpile.simple-res :refer :all]
7 | [eaml.transpile.style :refer :all]
8 | [eaml.parser :as parser]))
9 |
10 | (defn- transpile-node
11 | [scope node]
12 | (let [type (:node node)]
13 | (cond (style? type)
14 | (transpile-style scope node)
15 | (simple-res? type)
16 | (transpile-simple-res scope node)
17 | :else (raise! "Unknown node type: " node))))
18 |
19 | (defn- insert-resources-top-level
20 | [config-map]
21 | (loop [result-map config-map
22 | remaining-keys (keys result-map)]
23 | (if (empty? remaining-keys)
24 | result-map
25 | (let [key (first remaining-keys)
26 | value (get config-map key)]
27 | (recur (assoc result-map key (cons* value :resources {}))
28 | (rest remaining-keys))))))
29 |
30 | (defn- skip-node?
31 | [{node-type :node}]
32 | (= node-type :mixin))
33 |
34 | (defn transpile
35 | "Transpile the given AST into an XmlStruct.
36 | Returns a map of configuration => XmlStruct where
37 | configuration is a configuration name e.g. 'tablet'"
38 | [ast]
39 | (let [scope (scope/create ast)
40 | transpile-scoped #(transpile-node scope %)
41 | ast (filter (complement skip-node?) ast)]
42 | (->> (map transpile-scoped ast)
43 | (group-maps)
44 | (insert-resources-top-level))))
45 |
46 | (defn transpile-str
47 | "Equivalent to transpile but takes an eaml program string as input instead
48 | of an AST and transpiles it."
49 | [string]
50 | (let [ast (parser/parse-str string)]
51 | (transpile ast)))
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/eaml/file.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.file
2 | (:require [clojure.java.io :refer :all])
3 | (:import [java.io File]))
4 |
5 | (defn read-file
6 | "Reads the file located at file-path and returns the contents as a string"
7 | [file-path]
8 | (with-open [reader (clojure.java.io/reader file-path)]
9 | (loop [result ""
10 | line (.readLine reader)]
11 | (if (nil? line)
12 | result
13 | (recur (str result line "\n")
14 | (.readLine reader))))))
15 |
16 |
17 | (defn extension-filter
18 | "Creates an anonymous function which takes a file as argument and returns
19 | true if the file has the given extension.
20 | Example: (extension-filter \"clj\") will match clojure files."
21 | [ext]
22 | (fn [file]
23 | (let [file-name (.getName file)
24 | match (re-find #".+\.(.+)" file-name)]
25 | (if (nil? match)
26 | false
27 | (= ext (last match))))))
28 |
29 |
30 | (defn filter-tree
31 | "Reads a file tree recursively and returns a list of files that match the
32 | given filter function f. To match by extension see the extension-filter
33 | function."
34 | [root-dir-path f]
35 | (loop [files [(as-file root-dir-path)]
36 | result []]
37 | (if (empty? files)
38 | result
39 | (let [file (first files)]
40 | (if (.isDirectory file)
41 | (recur (into (rest files) (.listFiles file)) result)
42 | (recur (rest files) (if (f file) (conj result file) result)))))))
43 |
44 |
45 | (defn mkdirs!
46 | "Given a list of files or paths, creates any missing directories for every
47 | file or file-path."
48 | [& file-paths]
49 | (doseq [file-path file-paths]
50 | (let [file (as-file file-path)]
51 | (.mkdirs file))))
52 |
53 | (defn path-concat
54 | "Concatenates two file paths, the first one being a parent
55 | of the second one."
56 | [path1 path2]
57 | (let [clean-path1 (str (File. path1))
58 | clean-path2 (.replaceAll (str (File. path2)) "\\./" "")
59 | joined-path (str clean-path1 "/" clean-path2)]
60 | (str (File. joined-path))))
61 |
62 |
--------------------------------------------------------------------------------
/src/eaml/core.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.core
2 | (:gen-class)
3 | (:require [eaml.compiler :as compiler]
4 | [eaml.xml :as xml]
5 | [eaml.util :refer [itp]]
6 | [eaml.parser :refer [parse-dir]]
7 | [clojure.string :refer [join]]
8 | [clojure.tools.cli :refer [parse-opts]]))
9 |
10 | (defn eaml
11 | [in-dir out-dir]
12 | (println (itp "Transpiling from #{in-dir} to #{out-dir}"))
13 | (let [ast (parse-dir in-dir)
14 | config-map (compiler/transpile ast)]
15 | (doseq [[config xmlstruct] config-map]
16 | (with-open [writer (xml/config-writer out-dir config)]
17 | (println (itp "Transpiling done, writing to #{out-dir}"))
18 | (xml/render-xml xmlstruct writer)))))
19 |
20 |
21 | (def cli-options
22 | [["-i" "--in DIR" "Input directory"]
23 | ["-o" "--out DIR" "Output directory"]
24 | ["-h" "--help" "Print a help message"]])
25 |
26 | (defn exit
27 | "Print the given msg to stdout and terminate program with the given status code"
28 | [status msg]
29 | (println msg)
30 | (System/exit status))
31 |
32 | (defn error-msg
33 | [errors]
34 | (str "The following errors occurred while parsing your command:\n\n"
35 | (join \newline errors)))
36 |
37 | (defn usage
38 | [summary]
39 | (->> ["eaml transpiler"
40 | ""
41 | "Reads all .eaml files in the input directory and subdirectories, transpiles and writes XML to the output directory"
42 | ""
43 | "Usage:"
44 | ""
45 | "eaml [--in DIR] [--out DIR] [--help]"
46 | ""
47 | summary
48 | ""]
49 | (join \newline)))
50 |
51 | (defn -main
52 | [& args]
53 | (let [{:keys [options arguments errors summary]} (parse-opts args cli-options)]
54 | ;; Handle help and error conditions
55 | (cond
56 | (:help options)
57 | (exit 0 (usage summary))
58 | errors
59 | (exit 1 (error-msg errors))
60 | (and (contains? options :in) (contains? options :out))
61 | (eaml (:in options) (:out options))
62 | :else
63 | (exit 1 (usage summary)))))
64 |
--------------------------------------------------------------------------------
/resources/grammar.bnf:
--------------------------------------------------------------------------------
1 | = res-def* (* A program is composed of 1 or more resource definitions *)
2 |
3 | (* there are currently only 3 types of resources *)
4 | = simple-res-def
5 | | style-def
6 | | mixin-def
7 |
8 | (* simple resources *)
9 | simple-res-def = simple-res-type identifier <':'> simple-res-single-config <';'>
10 | | simple-res-type identifier <'{'> simple-res-configs <'}'>
11 |
12 | simple-res-single-config = simple-res-value
13 | simple-res-configs = ( simple-res-config )*
14 | simple-res-config = config-name <':'> simple-res-value <';'>
15 | = 'color' | 'dimen' | 'bool' | 'integer' | 'string'
16 | = string-literal
17 | | pointer
18 | | color-literal
19 | | dimen-literal
20 | | native-pointer
21 | | boolean-literal
22 | | integer-literal
23 |
24 | (* styles *)
25 | style-def = <'style'> identifier (<'<'> parent-iden)? <'{'> attrs <'}'>
26 | config-block = <'&:'> config-names <'{'> attr-def* <'}'>
27 | config-names = config-name (<','> config-name)*
28 |
29 | attrs = (attr-def | config-block | mixin-call)*
30 | attr-def = attr-name <':'> attr-value <';'>
31 | = string-literal
32 | | color-literal
33 | | dimen-literal
34 | | integer-literal
35 | | native-pointer
36 | | pointer
37 |
38 | (* mixins *)
39 | mixin-def = <'mixin'> identifier <'{'> mixin-attrs <'}'>
40 | mixin-attrs = (attr-def | config-block)*
41 | mixin-call = identifier <'('> <')'> <';'>
42 |
43 | (* tokens *)
44 | = #"[a-zA-Z]\w+"
45 | = #"[a-zA-Z][.\w]+"
46 | pointer = #"[a-zA-Z]\w+"
47 | boolean-literal = 'true' | 'false'
48 | string-literal = #'".*?"' | #"'.*?'"
49 | integer-literal = #"\d+"
50 | color-literal = #'#[a-fA-F0-9]{3}([a-fA-F0-9]{3})?'
51 | dimen-literal = #'\d+(sp|dp|px|dip)'
52 | config-name = #"[a-z][a-z0-9]*(-[a-z0-9]+)*"
53 | = #"[\w\d]+(:[\w\d]+)*"
54 | native-pointer = #"@(android:)?\w+(/\w[\w\.]+)?"
55 |
--------------------------------------------------------------------------------
/test/eaml/util_test.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.util-test
2 | (:require [eaml.util :refer :all]
3 | [presto.core :refer :all]
4 | [clojure.test :refer :all]))
5 |
6 | (expected-when "flat-coll flattens a coll of colls" flat-coll
7 | when [[]] = []
8 | when [[[1] [2]]] = [1 2]
9 | when [[[] [1 2 3] [4 5]]] = [1 2 3 4 5])
10 |
11 |
12 | (expected-when "group-maps joins values with the same key over different maps" group-maps
13 | when [[{:foo "foozie"}
14 | {:bar "bazie"
15 | :qux ["a" "list"]}
16 | {:foo "fun"
17 | :qux "quxie"}]]
18 | = {:foo ["foozie" "fun"]
19 | :bar ["bazie"]
20 | :qux [["a" "list"] "quxie"]})
21 |
22 | (expected-when "cons* test" cons*
23 | when [[] 1 2] = [1 2]
24 | when [[3 4] 1 2] = [1 2 3 4]
25 | when [(list 3 4) 1 2] = [1 2 3 4]
26 | when [(seq [3 4]) 1 2] = [1 2 3 4]
27 | when [[:foo :bar] :baz] = [:baz :foo :bar])
28 |
29 | (expected-when "case-match test" case-match
30 | when ["foobar" #"\Afoo" :foo] = :foo
31 | when ["foobar" #"\Abar" :bar] = nil
32 | when ["foobar" #"\Afoo" :foo
33 | #"\Afoobar" :foobar] = :foo)
34 |
35 | (expected-when "merge-lists test" merge-lists
36 | when [[] [1 2 3] identity] = [1 2 3]
37 | when [[1 2 3] [] identity] = [1 2 3]
38 | when [[] [] identity] = []
39 | when [[1 2 3] [4 5 6] identity] = [1 2 3 4 5 6]
40 | when [[1 2 3] [3 4 5] identity] = [1 2 4 5 3]
41 | when [[{:a 1 :b 2} {:a 2 :b 3} {:foo :bar}]
42 | [{:a 1 :b 3} {:a 2 :b 6} {:a 4 :b 5}]
43 | :a]
44 | = [{:foo :bar} {:a 4 :b 5} {:a 1 :b 3} {:a 2 :b 6}])
45 |
46 | (expected-when "find-first finds the first element that matches a predicate" find-first
47 | when [[1 2 3 4 5] #(> % 5)] = nil
48 | when [[1 2 3 4 5] #(> % 3)] = 4
49 | when [[] (fn [x] true)] = nil)
50 |
51 | (expected-when "singleton? returns true iff coll has only 1 element" singleton?
52 | when [nil] = false
53 | when [{}] = false
54 | when [[]] = false
55 | when [[1]] = true)
56 |
57 | (expected-when "remove-first-duplicates removes all duplicates and keeps the
58 | last one" remove-first-duplicates
59 | when [keys [{:b 0} {:a 1} {:a 2} {:b 1}]] = [{:a 2} {:b 1}]
60 | when [:a [{:a 0 :b 0} {:a 0 :b 1} {:a 1 :b 0} {:a 1 :b 1} {:a 0 :b "last one"}]] =
61 | [{:a 1 :b 1} {:a 0 :b "last one"}])
62 |
63 |
--------------------------------------------------------------------------------
/test/eaml/scope_test.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.scope-test
2 | (:require [eaml.scope :refer :all]
3 | [eaml.parser :refer [parse-str]]
4 | [eaml.parser :refer [parse-str]]
5 | [presto.core :refer :all]
6 | [clojure.test :refer :all]))
7 |
8 | (def nodes (parse-str "color red: #f00;
9 | color main_color: red;
10 | integer foo: 5;
11 | string name: 'a name';
12 | string my_name: name;
13 | string other_name: my_name;
14 | mixin RedText {
15 | android:textColor: red;
16 | }
17 | style Foo < Bar {
18 | android:textColor: main_color;
19 | android:foo: foo;
20 | android:text: my_name;
21 | }"))
22 |
23 | (def scope (create nodes))
24 |
25 | (expected-when "obtain-type returns the resolved type of an expression
26 | in the current scope" #(obtain-type % scope)
27 | when ["red"] = :color
28 | when ["main_color"] = :color
29 | when ["foo"] = :integer
30 | when ["name"] = :string
31 | when ["my_name"] = :string
32 | when ["other_name"] = :string
33 | when ["5"] = :integer
34 | when ["5123"] = :integer
35 | when ["true"] = :bool
36 | when ["false"] = :bool
37 | when ["'foo'"] = :string
38 | when ["\"\""] = :string
39 | when ["@string/foo"] = :string
40 | when ["@bool/foo"] = :bool
41 | when ["''"] = :string)
42 |
43 | (expected-when "resolve-expr obtains the value of an expression in the
44 | current scope" #(resolve-expr :any % scope)
45 | when ["#f00"] = "#f00"
46 | when ["true"] = "true"
47 | when ["false"] = "false"
48 | when ["'foo'"] = "foo"
49 | when ["12sp"] = "12sp"
50 | when ["1234"] = "1234"
51 | when ["red"] = "@color/red"
52 | when ["main_color"] = "@color/main_color"
53 | when ["foo"] = "@integer/foo"
54 | when ["my_name"] = "@string/my_name")
55 |
56 | (expected-when "include? returns true iff the scope contains
57 | the given id" #(include? scope %)
58 | when ["red"] = true
59 | when ["main_color"] = true
60 | when ["RedText"] = true
61 | when ["Foo"] = true
62 | when ["other_name"] = true
63 | when ["akaljsdhf"] = false)
64 |
65 | (deftest obtain-type-throws-when-not-found
66 | (is (thrown? IllegalStateException (obtain-type "asdf" scope)))
67 | (is (thrown? IllegalStateException (obtain-type "a%1234" scope)))
68 | (is (thrown? IllegalStateException (obtain-type "#" scope)))
69 | (is (thrown? IllegalStateException (resolve-expr :string "main_color" scope)))
70 | (is (thrown? IllegalStateException (create (parse-str "integer foo: 1; color foo: #f00;")))))
71 |
--------------------------------------------------------------------------------
/doc/release_notes.md:
--------------------------------------------------------------------------------
1 | # Planned features
2 |
3 | - [ ] Remove `;` to indicate line termination.
4 | - [ ] Gradle integration
5 | - [ ] Android studio plugin
6 | - Syntax highlighting
7 | - Compile from IDE
8 | - Go to definition
9 | - [ ] Mixins with arguments e.g.
10 | ```
11 | mixin coloredText(textColor, backgroundColor) {
12 | android:textColor: textColor;
13 | android:backgroundColor: backgroundColor;
14 | }
15 | ```
16 | Which could then be invoked using `coloredText(#f00,#fff)` inside a Style.
17 | - [ ] Reverse compiler: cleanup your existing styles.
18 | - [ ] Detailed documentation
19 | - [ ] Type checking at compile time
20 |
21 | # eaml 0.5 - WIP
22 |
23 | - Eaml is now a command line app. To run:
24 | ```bash
25 | # will create an ubjerjar in target/{something}-standalone.jar
26 | lein uberjar
27 | # execute it and show --help banner
28 | java -jar target/{something}-standalone.jar --help
29 | ```
30 |
31 | # eaml 0.4 - 1450616768
32 |
33 | - Added support for `@null`
34 | - Added mixins with no arguments:
35 | ```
36 | mixin redText {
37 | android:textColor: #f00;
38 | }
39 |
40 | style RedButton < Button {
41 | redText();
42 | }
43 | ```
44 | - Mixins with arguments will come in the next version
45 | - Fixed several issues with native resource pointers (e.g. @foo/bar)
46 |
47 | # eaml 0.3.0 - 1448745045
48 |
49 | Main features:
50 | - Removed multiple inheritance. Implementation proved to be way to complicated.
51 | Added support for single inheritance via piggy-backing on the style's native
52 | `parent` attribute.
53 | - Added support for `bool`, `string` and `integer`.
54 | - Added support for multiple configurations for simple resources:
55 | ```
56 | dimen margins {
57 | default: 8dp;
58 | land: 12dp;
59 | }
60 | ```
61 | - Added support for multiple configurations for styles:
62 | ```
63 | style Button < BaseButton {
64 | android:background: @drawable/btn_bkg;
65 | &:v21, v22 {
66 | android:background: @drawable/btn_ripple;
67 | }
68 | }
69 | ```
70 |
71 | # eaml 0.2.0 - 1445287611
72 |
73 | First minimal working version:
74 | - Support for `color` and `dimens` e.g. `color primary_color: #f00;`
75 | - Multiple inheritance between styles
76 | ```
77 | style BigRedButton < RedButton, BigButton {
78 | # your code here
79 | }
80 | ```
81 | - Nested directories
82 | You can organize your files with nested directores:
83 | ```
84 | ./styles/
85 | ├── variables.eaml
86 | ├── common_styles.eaml
87 | └── buttons
88 | ├── buttons.eaml
89 | └── more_buttons.eaml
90 | ```
91 | - One global scope.
92 | Currently there is only one scope: the global scope. This basically
93 | means that a `color foo` defined in one file is accesible everywhere.
94 | - No support for comments: no comments are supported yet. This feature
95 | coming soon.
96 | - No support for multiple configurations: Configuration support also
97 | coming soon.
98 |
99 |
100 |
--------------------------------------------------------------------------------
/src/eaml/transpile/style.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.transpile.style
2 | (:require [eaml.util :refer :all]
3 | [eaml.error :refer :all]
4 | [eaml.scope :refer [resolve-expr]]))
5 |
6 | (defn- mk-item
7 | [scope {name :name value :value}]
8 | [:item {:name name} (resolve-expr :any value scope)])
9 |
10 | (defn- mk-style-res
11 | [scope id parent? attrs]
12 | (let [style-attrs {:name id}
13 | style-attrs (if parent?
14 | (assoc style-attrs :parent parent?)
15 | style-attrs)
16 | items (map #(mk-item scope %) attrs)]
17 | (cons* items :style style-attrs)))
18 |
19 | (defn style?
20 | [id]
21 | (= :style id))
22 |
23 | (defn- mixin-attr?
24 | [attr]
25 | (contains-all? attr :mixin :args :config))
26 |
27 | (defn regular-attr?
28 | [attr]
29 | (contains-all? attr :name :value :config))
30 |
31 | (defn- expand-mixin
32 | [scope mixin]
33 | (:attrs (get scope (:mixin mixin))))
34 |
35 | (defn- expand-mixins
36 | "Invokes all mixins and replaces them with the mixin's contents. Also removes
37 | any duplicate attributes that would have resulted by mixin expansion."
38 | [scope attrs]
39 | (->> (map (fn [attr]
40 | (cond (mixin-attr? attr)
41 | (expand-mixin scope attr)
42 | (regular-attr? attr)
43 | [attr]
44 | :else (raise! (itp "Unrecognized attr: #{attr}"))))
45 | attrs)
46 | (flat-coll)
47 | (remove-first-duplicates (fn [{name :name config :config}]
48 | [name config]))))
49 |
50 | (defn transpile-style
51 | "Return a map of config => style XmlStruct"
52 | ;; TODO: handle repeated attrs correctly!
53 | [scope {id :id parent :parent attrs :attrs}]
54 | (if (empty? attrs)
55 | ;; if there are no attrs, return an empty style
56 | (mk-style-res id parent [])
57 | (let [attrs (expand-mixins scope attrs)
58 | by-config (group-by :config attrs)
59 | ;; obtain a list of all configurations
60 | configs (keys by-config)
61 | ;; get the 'default' config attrs
62 | default-attrs (:default by-config)
63 | ;; merge the default config attrs into other configs.
64 | ;; Why? default config attrs should be present by default.
65 | by-config (reduce-kv (fn [by-config config attrs]
66 | (assoc by-config config
67 | (merge-lists default-attrs attrs :name)))
68 | {} by-config)]
69 | ;; iterate over all configs and construct
70 | ;; a map that assocs a config key to the XmlStruct
71 | ;; for the attrs that should be rendered in that config
72 | (reduce (fn [config-map config]
73 | (assoc config-map config
74 | (mk-style-res scope id parent (get by-config config))))
75 | {} configs))))
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/eaml/xml.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.xml
2 | (:require [clojure.string :refer [join]]
3 | [eaml.util :refer :all]
4 | [eaml.error :refer :all]
5 | [clojure.java.io :refer [writer]]
6 | [eaml.file :refer [path-concat mkdirs!]])
7 | (:import [java.io StringWriter]))
8 |
9 | (declare render-xml-node-start
10 | render-xml-node-body
11 | render-xml-node-end)
12 |
13 | (defn string-writer
14 | "Initializes a new StringWriter"
15 | [] (StringWriter. ))
16 |
17 | (defn config-writer
18 | [root config]
19 | (let [config-name (name config)
20 | dir (if (= :default config)
21 | (itp "#{root}/values")
22 | (itp "#{root}/values-#{config-name}"))
23 | file-path (itp "#{dir}/values.xml")]
24 | (mkdirs! dir)
25 | (writer file-path)))
26 |
27 | (defn render-xml
28 | "Writes a tree of nodes to the given writer.
29 | node must be in the following form:
30 | [:node {:arg1 'val1' :arg2 'val2' ...}
31 | [:subnode1 {:arg1 'val1'} ...]
32 | [:subnode2 {:arg1 'val1'} ...]
33 | ...]
34 |
35 | Note: this function does not handle closing of the writer.
36 | You must do this yourself.
37 |
38 | Example:
39 | [:resources {}
40 | [:style {name 'Button' parent 'BaseButton'}
41 | [:item {name 'android:textSize'} '12sp']]]
42 |
43 | Will write
44 |
45 |
48 | "
49 | [node writer]
50 | (let [writer writer]
51 | (loop [node-queue (list node)]
52 | (if (empty? node-queue)
53 | writer
54 | (let [head (first node-queue)
55 | tail (rest node-queue)]
56 | (render-xml-node-start head writer)
57 | (render-xml-node-body head writer)
58 | (render-xml-node-end head writer)
59 | (recur tail))))))
60 |
61 | (defn render-xml-string
62 | "Writes a tree of nodes to a string and returns that string.
63 | Use this method only for debbuging.
64 | Equivalent to render-xml using string-writer as a writer"
65 | [nodes]
66 | (let [writer (string-writer)]
67 | (render-xml nodes writer)
68 | (.toString writer)))
69 |
70 | (defn- render-xml-node-start
71 | [[node-type attrs & subnodes] writer]
72 | (.write writer "<")
73 | (.write writer (name node-type))
74 | (.write writer " ")
75 | (.write writer (->> (map (fn [[k v]]
76 | (str (name k) "=\"" v \"))
77 | attrs)
78 | (join " ")))
79 | (.write writer ">"))
80 |
81 | (defn- render-xml-node-body
82 | [[node-type attrs & subnodes] writer]
83 | (when (seq subnodes)
84 | (doseq [node subnodes]
85 | (if (coll? node)
86 | (render-xml node writer)
87 | (.write writer node)))))
88 |
89 | (defn- render-xml-node-end
90 | [[node-type & _] writer]
91 | (.write writer "")
92 | (.write writer (name node-type))
93 | (.write writer ">"))
94 |
95 |
--------------------------------------------------------------------------------
/doc/0.3.0/quickstart.md:
--------------------------------------------------------------------------------
1 | Language QuickStart
2 | ===================
3 |
4 | This short guide will give you a basic understanding of most eaml features.
5 |
6 | ## Defining simple resources
7 |
8 | Simple resources include `color`, `dimen`, `bool`, `integer` and `string`
9 |
10 | ```
11 | # Let's define some colors
12 | color red: #f00;
13 | color green: #0f0;
14 | color blue: #00f;
15 |
16 | # And some margins
17 | dimen small_margins: 4dp;
18 | dimen medium_margins: 8dp;
19 | dimen large_margins: 8dp;
20 |
21 | # color pointers
22 | color main_color: red;
23 | ```
24 |
25 | ### Supporting multiple configurations
26 |
27 | Eaml simplifies supporting multiple configurations by providing a concise
28 | syntax to describe how resources behave on different configurations.
29 |
30 | ```
31 | # Example: paddings that behave differently when the device is in landscape
32 | dimen button_paddings {
33 | default: 4dp;
34 | land: 8dp;
35 | }
36 | ```
37 |
38 | ### Functions (TODO)
39 |
40 | Eaml supports several built-int functions which greatly facilitate calculating
41 | properties based on other properties.
42 |
43 | ```
44 | color red: #f00;
45 | color red_dark: darker(10%, red);
46 | color red_darker: darker(20%, red);
47 | color red_light: lighten(10%, red);
48 | color red_lighter: lighten(20%, red);
49 | ```
50 |
51 | #### Creating your own functions (TODO)
52 |
53 | You can create your own functions as follows:
54 |
55 | ```
56 | func bigger(dimen d, percent p) {
57 | d*(1 + p);
58 | }
59 |
60 | func smaller(dimen d, percent p) {
61 | d*(1 - p);
62 | }
63 | ```
64 |
65 | ## Defining styles
66 |
67 | Styles can be defined using the following syntax.
68 | ```
69 | style Button {
70 | android:paddingLeft: 8dp;
71 | android:paddingRight: 8dp;
72 | android:paddingTop: 4dp;
73 | android:paddingBottom: 4dp;
74 | android:textSize: 12sp;
75 | }
76 | ```
77 |
78 | ## Inheritance
79 | Eaml supports single inheritance via the `<` keyword. This is simply syntax sugar
80 | that piggy backs on android's `parent` attribute.
81 | ```
82 | style BigButton < Button {
83 | android:paddingTop: 8dp;
84 | android:paddingBottom: 8dp;
85 | }
86 | ```
87 |
88 | ## Mixins
89 | In cases where you would like to reuse functionality from more than one source
90 | you can use mixins. Mixins are for most purposes identical to styles except for
91 | the fact that they cannot inherit from other `mixins` or `styles`.
92 |
93 | ```
94 | # A simple mixin that just sets the color to red.
95 | mixin redText {
96 | android:textColor: red;
97 | }
98 |
99 | style RedButton < Button {
100 | redText()
101 | }
102 | ```
103 |
104 | Mixins can define behaviour for multiple configurations as follows:
105 |
106 | ```
107 | mixin redText {
108 | android:textColor: red;
109 | &:v21 {
110 | android:textColor: fancy_red;
111 | }
112 | }
113 |
114 | style RedButton < Button {
115 | redText();
116 | }
117 | ```
118 | NOTE: you cannot invoke a mixin from inside a configuration block:
119 |
120 | ```
121 | style RedButton < Button {
122 | &:v21 {
123 | redText(); # THIS WILL THROW AN ERROR!
124 | }
125 | }
126 | ```
127 |
128 | ## Multiple configuration support
129 | Eaml styles also come with built in syntax to support multiple configurations
130 | as follows:
131 |
132 | ```
133 | # A button that uses a ripple on v21 and a regular drawable by default
134 | style RedButton < Button {
135 | android:background {
136 | default: @drawable/btn_red
137 | v21: @drawable/btn_red_ripple
138 | }
139 | }
140 | ```
141 |
142 |
143 |
--------------------------------------------------------------------------------
/src/eaml/scope.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.scope
2 | "This namespace provides the build-scope function whose
3 | purpose is two build the global scope which will later be used
4 | by the transpiler to resolve variables and type checking
5 |
6 | attr-value: a tuple where the first element is the type and
7 | the second element is the value.
8 |
9 | Example: [:literal \"#ff0000\"] => an attr-value for a color
10 | [:]
11 | "
12 | (:require [eaml.error :refer :all]
13 | [eaml.util :refer :all]))
14 |
15 | (defn literal?
16 | [parsed]
17 | (#{:color :dimen :bool :integer :string} parsed))
18 |
19 | (defn pointer?
20 | [parsed]
21 | (= parsed :pointer))
22 |
23 | (defn null?
24 | [parsed]
25 | (= parsed :native-null))
26 |
27 | (defn native-res?
28 | [parsed]
29 | (= parsed :native-pointer))
30 |
31 | (defn- remove-quotes
32 | [string]
33 | (.replaceAll string "[\"']" ""))
34 |
35 | (defn parse-expr
36 | [string]
37 | (case-match string
38 | #"\A@null" :native-null
39 | #"\A@.*?(/.*)" :native-pointer
40 | #"\A#[a-fA-F\d]{3}" :color
41 | #"\A#[a-fA-F\d]{6}" :color
42 | #"\A#[a-fA-F\d]{8}" :color
43 | #"\A\d+(sp|px|dp)" :dimen
44 | #"\Atrue|false" :bool
45 | #"\A\d+" :integer
46 | #"\A\".*?\"" :string
47 | #"\A'.*?'" :string
48 | #"\A[a-zA-Z]\w*" :pointer))
49 |
50 | (defn create
51 | "Create the scope"
52 | [nodes]
53 | (reduce (fn [scope node]
54 | (let [id (:id node)]
55 | (if (contains? scope id)
56 | (raise! (itp "Scope already contains a mapping for #{id} => #{node}: #{(scope id)}"))
57 | (assoc scope id node))))
58 | {} nodes))
59 |
60 | (defn obtain-type
61 | [expr scope]
62 | (let [parsed (parse-expr expr)]
63 | (cond (null? parsed)
64 | parsed
65 | (literal? parsed)
66 | parsed
67 | (native-res? parsed)
68 | (->> (re-find #"@(.*?)/" expr)
69 | (last)
70 | (keyword))
71 | (pointer? parsed)
72 | (if (contains? scope expr)
73 | (:node (get scope expr))
74 | (raise! (itp "No mapping found for '#{expr}' in scope")))
75 | :else
76 | (raise! (itp "Unkown parse result: '#{parsed}'")))))
77 |
78 | (defn resolve-expr
79 | "Obtains the value for the given expr in the scope.
80 | If the resolved type is different from the expected type
81 | then return nil.
82 | If the given expr is not present in the scope, throw an exception.
83 | You may use :any as type to match any type"
84 | [type expr scope]
85 | (let [parsed (parse-expr expr)]
86 | (cond (or (literal? parsed) (native-res? parsed))
87 | (if (= :string parsed)
88 | (remove-quotes expr)
89 | expr)
90 | (null? parsed)
91 | expr
92 | (pointer? parsed)
93 | (let [resolved-type (obtain-type expr scope)]
94 | (if (or (= type :any) (= type resolved-type))
95 | (case resolved-type
96 | :dimen (itp "@dimen/#{expr}")
97 | :color (itp "@color/#{expr}")
98 | :bool (itp "@bool/#{expr}")
99 | :integer (itp "@integer/#{expr}")
100 | :string (itp "@string/#{expr}"))
101 | (raise! "Type mismatch: expected #{type} but got #{resolved-type}"))))))
102 |
103 | (defn include?
104 | "True if the given id exists for the current scope."
105 | [scope id]
106 | (contains? scope id))
107 |
--------------------------------------------------------------------------------
/src/eaml/parser.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.parser
2 | (:require [instaparse.core :as insta]
3 | [eaml.file :refer :all]
4 | [eaml.util :refer :all]
5 | [eaml.error :refer :all]))
6 |
7 | (defn or-empty
8 | [coll-or-nil]
9 | (if (nil? coll-or-nil)
10 | [] coll-or-nil))
11 |
12 | (defn normalize-literal
13 | [literal-value]
14 | literal-value)
15 |
16 | (defn normalize-pointer
17 | [value]
18 | value)
19 |
20 | (defn normalize-string
21 | [value]
22 | value)
23 |
24 | (defn normalize-simple-res-def
25 | [res-type id value]
26 | {:node (keyword res-type)
27 | :id id
28 | :vals value})
29 |
30 | (defn normalize-simple-res-single-config
31 | [value]
32 | [{:config :default
33 | :value value}])
34 |
35 | (defn normalize-simple-res-config
36 | [config value]
37 | {:value value
38 | :config config})
39 |
40 | (defn normalize-style
41 | ([id parent attrs?]
42 | {:id id
43 | :node :style
44 | :parent parent
45 | :attrs (or-empty attrs?)})
46 | ([id attrs?]
47 | (normalize-style id nil attrs?)))
48 |
49 | (defn normalize-mixin
50 | [id attrs?]
51 | {:id id
52 | :node :mixin
53 | :attrs (or-empty attrs?)})
54 |
55 |
56 | (defn normalize-attr
57 | [id value]
58 | {:name id
59 | :value value
60 | :config :default})
61 |
62 |
63 | (defn normalize-config-name
64 | [config-name]
65 | (keyword config-name))
66 |
67 | (defn normalize-config-block
68 | [config-names & attrs]
69 | {:config-block (for [config-name config-names
70 | attr attrs]
71 | (assoc attr :config config-name))})
72 |
73 | (defn normalize-style-attrs
74 | [& attrs]
75 | ;; TODO: rewrite this
76 | (reduce (fn [result attr]
77 | (if (:config-block attr)
78 | (into result (:config-block attr))
79 | (conj result attr)))
80 | [] attrs))
81 |
82 | (defn normalize-mixin-attrs
83 | [& attrs]
84 | ;; TODO: rewrite this
85 | (apply normalize-style-attrs attrs))
86 |
87 | (defn normalize-mixin-call
88 | [mixin-id & args?]
89 | {:mixin mixin-id
90 | :config :default
91 | :args (or-empty args?)})
92 |
93 |
94 | (defn normalize-nodes
95 | [ast-nodes]
96 | (insta/transform {:color-literal normalize-literal
97 | :dimen-literal normalize-literal
98 | :string-literal normalize-string
99 | :native-pointer normalize-literal
100 | :boolean-literal normalize-literal
101 | :integer-literal normalize-literal
102 | :pointer normalize-pointer
103 | :config-name normalize-config-name
104 | :config-names (fn [& args] args)
105 | :simple-res-def normalize-simple-res-def
106 | :simple-res-configs (fn [& args] args)
107 | :simple-res-config normalize-simple-res-config
108 | :simple-res-single-config normalize-simple-res-single-config
109 | :config-block normalize-config-block
110 | :mixin-call normalize-mixin-call
111 | :extends-expr (fn [& args] args)
112 | :mixin-def normalize-mixin
113 | :style-def normalize-style
114 | :attrs normalize-style-attrs
115 | :mixin-attrs normalize-mixin-attrs
116 | :attr-def normalize-attr}
117 | ast-nodes))
118 |
119 | (def parser (insta/parser (clojure.java.io/resource "grammar.bnf")
120 | :auto-whitespace :standard))
121 |
122 | (defn parse-str
123 | "Parses and normalizes a string. Raises an error if it is not possible to parse
124 | the given string."
125 | [string]
126 | (let [parsed (parser string)]
127 | (if (insta/failure? parsed)
128 | (do (instaparse.failure/pprint-failure (insta/get-failure parsed))
129 | (raise! "Error while parsing"))
130 | (normalize-nodes parsed))))
131 |
132 |
133 | (defn parse-dir
134 | "Finds all .eaml files under the given root-path (folder) recursively and returns
135 | an AST. See parse-str"
136 | [root-path]
137 | (->> (filter-tree root-path (extension-filter "eaml"))
138 | (map read-file)
139 | (map parse-str)
140 | (flat-coll)))
141 |
--------------------------------------------------------------------------------
/src/eaml/util.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.util
2 | (:require [clojure.set :refer [union intersection]]))
3 |
4 | (defn flat-coll
5 | "Given a coll of collections, flattens it by one level.
6 | Example: [[1] [2 3] [] [4 5 6]] will result in [1 2 3 4 5 6]"
7 | [colls]
8 | (apply concat colls))
9 |
10 |
11 | (defn group-maps
12 | "Takes a collection of maps and groups their k,v pairs by key"
13 | [maps]
14 | (loop [grouped {}
15 | maps maps]
16 | (if (empty? maps)
17 | grouped
18 | (let [m (first maps)]
19 | (recur (reduce-kv (fn [reduction k v]
20 | (let [current-val (get reduction k)]
21 | (if current-val
22 | (assoc reduction k (conj current-val v))
23 | (assoc reduction k [v]))))
24 | grouped m)
25 | (rest maps))))))
26 |
27 | (defn cons*
28 | "equivalent to calling cons over the values in reverse order.
29 | Example: (cons* [4 5 6] 1 2 3) == '(1 2 3 4 5 6)"
30 | [coll & values]
31 | (if (empty? values)
32 | coll
33 | (cons (first values)
34 | (apply cons* coll (rest values)))))
35 |
36 | (defmacro itp
37 | "Interpolation macro. Similar to ruby interpolation.
38 | Does not provide any escaping mechanism.
39 | Syntax: (itp 'Hi #{name}, how are you?')
40 | Will expand to (str 'Hi ' name ', how are you?')"
41 | [string]
42 | (loop [string string
43 | result []]
44 | (let [[_ match-sym-anywhere] (re-find #"(.*?)#\{.*?\}" string)
45 | [regex-match match-symbol] (re-find #"\A#\{(.*?)\}" string)]
46 | (cond match-symbol
47 | (recur (.substring string (count regex-match))
48 | (conj result (read-string match-symbol)))
49 | match-sym-anywhere
50 | (recur (.substring string (count match-sym-anywhere))
51 | (conj result match-sym-anywhere))
52 | :else
53 | (cons `str (conj result string))))))
54 |
55 | (defn case-match
56 | "Matches string agains the given regex, one by one until a match
57 | is found. Returns the matched form arg. Returns nil if there are no matches.
58 | form-pairs => [regex arg]"
59 | [string & form-pairs]
60 | (if (empty? form-pairs)
61 | nil
62 | (let [[regex form] (take 2 form-pairs)]
63 | (if (re-find regex string)
64 | form
65 | (apply case-match string (rest (rest form-pairs)))))))
66 |
67 |
68 | (defn singleton?
69 | [coll]
70 | (= (count coll) 1))
71 |
72 | (defn find-first
73 | "Return the first x in coll s.t. pred(x) = true"
74 | [coll pred]
75 | (first (filter pred coll)))
76 |
77 |
78 | (defn merge-lists
79 | "Preconditions:
80 | - f is a 1-1 function
81 | - Every element on both l1 and l2 is unique
82 |
83 | Merges the two lists into one. If for some a in l1 and b in l2
84 | (= (f a) (f b)) then only b is added."
85 | [l1 l2 f]
86 | (let [mapped-set1 (set (map f l1))
87 | mapped-set2 (set (map f l2))
88 | mapped-intersection (intersection mapped-set1 mapped-set2)
89 | l1-filtered (filter (fn [x]
90 | (if (contains? mapped-intersection (f x))
91 | false true))
92 | l1)]
93 | (loop [l2 l2
94 | clashes []
95 | result []]
96 | (if (empty? l2)
97 | (concat l1-filtered result clashes)
98 | (let [x (first l2)
99 | tail (rest l2)
100 | fx (f x)]
101 | (if (contains? mapped-intersection fx)
102 | (recur tail (conj clashes x) result)
103 | (recur tail clashes (conj result x))))))))
104 |
105 | (defn reduce-indexed
106 | "Equivalent to reduce but f takes three arguments:
107 | the reduction, index and value and value."
108 | [f init coll]
109 | (loop [reduction init
110 | coll coll
111 | index 0]
112 | (if (empty? coll)
113 | reduction
114 | (recur (f reduction index (first coll))
115 | (rest coll)
116 | (inc index)))))
117 |
118 | (defn contains-all?
119 | "Returns true if the given map contains all the specified keys"
120 | [m & ks]
121 | (if (empty? ks)
122 | true
123 | (and (contains? m (first ks))
124 | (contains-all? (rest ks)))))
125 |
126 | (defn remove-first-duplicates
127 | "Removes all duplicates from the given list, preserves the last
128 | duplicate. Two elements a and b are considered a duplicate if f(a) == f(b)
129 | Example (remove-first-duplicates keys [{:b 0} {:a 1} {:a 2} {:b 1}]) will result in
130 | [{:a 2} {:b 1}]"
131 | [f coll]
132 | (let [duplicates (reduce-indexed (fn [reduction index x]
133 | (assoc reduction (f x) [index x]))
134 | {} coll)
135 | without-dups (reduce-indexed (fn [reduction index x]
136 | (if (= (get duplicates (f x))
137 | [index x])
138 | (conj reduction x)
139 | reduction))
140 | [] coll)]
141 | without-dups))
142 |
--------------------------------------------------------------------------------
/doc/0.4.0/quickstart.md:
--------------------------------------------------------------------------------
1 | Language QuickStart
2 | ===================
3 |
4 | This short guide will give you a basic understanding of most eaml features.
5 |
6 | ## Introduction
7 |
8 | Eaml is a simple language for defining styles in Android. Eaml is targeted at android
9 | developers who already understand the Android styling framework but are having trouble
10 | organizing and maintaining their styles as the project grows.
11 |
12 | Eaml's most interesting features include:
13 | - Support for nested directories. You can nest your `.eaml` files as deep as possible.
14 | - Support for `mixin`s. See the mixin section for more information.
15 | - Configuration selectors. Using the `&:` syntax you can specify how a style varies its
16 | behaviour depending on the device's configuration.
17 |
18 | *NOTE:* A good understanding of how Android styles work is required in order to use Eaml
19 | proficiently as Eaml transpiles to Android's XML styles.
20 |
21 | ## Defining simple resources
22 |
23 | Eaml provides a simplified syntax for defining simple resources.
24 | Simple resources include `color`, `dimen`, `bool`, `integer` and `string`
25 |
26 | ### Syntax
27 | ` : ;`
28 |
29 | Example:
30 | ```
31 | # Let's define some colors
32 | color red: #f00;
33 | color green: #0f0;
34 | color blue: #00f;
35 |
36 | # And some dimen
37 | dimen small_margins: 4dp;
38 | dimen medium_margins: 8dp;
39 | dimen large_margins: 8dp;
40 | ```
41 |
42 | ### Resource Pointers
43 |
44 | When a resource reference another resource, this reference is called a resource pointer.
45 |
46 | Eaml provides two types of pointers:
47 |
48 | - Native resource pointers e.g. `@color/red`. Use these type of pointers when referencing a resource
49 | defined in an xml file. This serves as a way of interop between existing xml styles and Eaml programs.
50 | All form of native resource pointers are supported including `@null`.
51 | - Eaml resource pointers e.g. `color main_color: red;`. Use these pointers when referencing a resource
52 | defined in Eaml.
53 |
54 | Example:
55 | ```
56 | color a_cool_color: #f00;
57 | color some_other_color: @android:color/a_color;
58 | color my_favorite_color: a_cool_color;
59 | ```
60 |
61 | ### Supporting multiple configurations
62 |
63 | Eaml simplifies supporting multiple configurations by providing a concise
64 | syntax to describe how resources behave on different configurations.
65 |
66 | #### Syntax:
67 |
68 | ```
69 | {
70 | : ;
71 | : ;
72 | ...
73 | }
74 | ```
75 |
76 | Example:
77 | ```
78 | # Paddings that behave differently when the device is in landscape
79 | dimen button_paddings {
80 | default: 4dp;
81 | land: 8dp;
82 | }
83 |
84 | # Support for certain features
85 | bool has_ripples {
86 | v21: true;
87 | default: false;
88 | }
89 | ```
90 |
91 | ## Defining styles
92 |
93 | Styles can be defined using the following syntax.
94 | ```
95 | style (< )? {
96 | *
97 | }
98 | ```
99 |
100 | Example:
101 | ```
102 | style Button {
103 | android:paddingLeft: 8dp;
104 | android:paddingRight: 8dp;
105 | android:paddingTop: 4dp;
106 | android:paddingBottom: 4dp;
107 | android:textSize: 12sp;
108 | }
109 | ```
110 |
111 | ## Inheritance
112 | Eaml supports single inheritance via the `<` keyword. This is simply syntax sugar
113 | that piggy backs on android's `parent` attribute.
114 |
115 | Example:
116 | ```
117 | style BigButton < Button {
118 | android:paddingTop: 8dp;
119 | android:paddingBottom: 8dp;
120 | }
121 | ```
122 |
123 | ## Multiple configuration support
124 | Styles also come with built in syntax to support multiple configurations
125 | as follows:
126 |
127 | ```
128 | # A button that uses a ripple on v21 and a regular drawable by default
129 | style RedButton < Button {
130 | android:textColor: red;
131 | android:background: @drawable/btn_red;
132 | &:v21 {
133 | android:background: @drawable/btn_red_ripple;
134 | }
135 | }
136 | ```
137 |
138 | In this case `&:v21 { ... }` is called a configuration block for `v21`.
139 | You can add as many attributes as you want inside a configuration block.
140 | *NOTE*: Eaml technically does not know about Android's configuration management system so
141 | whatever configuration identifier you specify will be used to create a folder with
142 | the given name e.g. `&:foo-bar { ... }` will create `res/values-foo-bar/styles.xml`
143 | which will never be picked up by the Android OS.
144 |
145 | ## Mixins
146 | In cases where you would like to reuse functionality from more than one source
147 | you can use mixins. Mixins are for most purposes identical to styles except for
148 | the fact that they cannot inherit from other `mixins` or `styles`.
149 |
150 | Example:
151 | ```
152 | # A simple mixin that just sets the text color to red.
153 | mixin redText {
154 | android:textColor: red;
155 | }
156 |
157 | mixin bigText {
158 | android:textSize: 28sp;
159 | }
160 |
161 | style RedButton < Button {
162 | redText();
163 | }
164 |
165 | style BigRedButton < Button {
166 | redText();
167 | bigText();
168 | }
169 | ```
170 |
171 | Mixins can define behaviour for multiple configurations as follows:
172 |
173 | ```
174 | mixin redText {
175 | android:textColor: red;
176 | &:v21 {
177 | android:textColor: fancy_red;
178 | }
179 | }
180 |
181 | style RedButton < Button {
182 | redText();
183 | }
184 | ```
185 |
186 | *NOTE*: you cannot invoke a mixin from inside a configuration block:
187 | ```
188 | style RedButton < Button {
189 | &:v21 {
190 | redText(); # THIS WILL THROW AN ERROR!
191 | }
192 | }
193 | ```
194 |
195 |
196 |
--------------------------------------------------------------------------------
/test/eaml/parser_test.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.parser-test
2 | (:require [eaml.parser :refer :all]
3 | [presto.core :refer :all]
4 | [clojure.test :refer :all]))
5 |
6 | (defn parse-first
7 | [string]
8 | (first (parse-str string)))
9 |
10 | (expected-when "parsing a single color" parse-first
11 | when ["color red: #ff0000;"]
12 | = {:id "red"
13 | :node :color
14 | :vals [{:config :default
15 | :value "#ff0000"}]}
16 |
17 | when ["color some_other_color: #f12;"]
18 | = {:id "some_other_color"
19 | :node :color
20 | :vals [{:config :default
21 | :value "#f12"}]}
22 |
23 | when ["color multi_config_color {
24 | default: #ff0000;
25 | v21: @drawable/btn_red_ripple;
26 | }"]
27 | = {:id "multi_config_color"
28 | :node :color
29 | :vals [{:config :default
30 | :value "#ff0000"}
31 | {:config :v21
32 | :value "@drawable/btn_red_ripple"}]}
33 |
34 | when ["color some_color: some_other;"]
35 | = {:id "some_color"
36 | :node :color
37 | :vals [{:config :default
38 | :value "some_other"}]})
39 |
40 | (expected-when "parsing a boolean" parse-first
41 | when ["bool is_foo: true;"]
42 | = {:id "is_foo"
43 | :node :bool
44 | :vals [{:config :default
45 | :value "true"}]})
46 |
47 | (expected-when "parsing a string" parse-first
48 | when ["string a_string: 'foobar';"]
49 | = {:id "a_string"
50 | :node :string
51 | :vals [{:config :default
52 | :value "'foobar'"}]})
53 |
54 | (expected-when "parsing an integer" parse-first
55 | when ["integer foo: 123;"]
56 | = {:id "foo"
57 | :node :integer
58 | :vals [{:config :default
59 | :value "123"}]})
60 |
61 |
62 | (expected-when "parsing a single dimens" parse-first
63 | when ["dimen small_margins: 12dp;"]
64 | = {:id "small_margins"
65 | :node :dimen
66 | :vals [{:config :default
67 | :value "12dp"}]}
68 |
69 | when ["dimen large_text: 12sp;"]
70 | = {:id "large_text"
71 | :node :dimen
72 | :vals [{:config :default
73 | :value "12sp"}]})
74 |
75 |
76 | (expected-when "parsing a single style" parse-first
77 | when ["style Foo {}"]
78 | = {:id "Foo"
79 | :node :style
80 | :parent nil
81 | :attrs []}
82 |
83 | when ["style FooBar < Foo.Bar123.Baz {}"]
84 | = {:id "FooBar"
85 | :node :style
86 | :parent "Foo.Bar123.Baz"
87 | :attrs []}
88 |
89 | when ["style FooBar123 < Foo {
90 | colorPrimaryDark: #ff0000;
91 | android:textSize: 12sp;
92 | android:background: @null;
93 | foo: @android:foo/bar;
94 | bar123: @style/Text.Label2;
95 | }"]
96 | = {:id "FooBar123"
97 | :node :style
98 | :parent "Foo"
99 | :attrs [{:name "colorPrimaryDark" :value "#ff0000" :config :default}
100 | {:name "android:textSize" :value "12sp" :config :default}
101 | {:name "android:background" :value "@null" :config :default}
102 | {:name "foo" :value "@android:foo/bar" :config :default}
103 | {:name "bar123" :value "@style/Text.Label2" :config :default}]}
104 |
105 | when ["style Foo < Bar {
106 | android:textColor: #123;
107 | android:background: @drawable/foo_drawable;
108 | fooBar();
109 | android:textSize: small_text;
110 | android:text: \"some text\";
111 | qux();
112 | }"]
113 | = {:id "Foo"
114 | :node :style
115 | :parent "Bar"
116 | :attrs [{:name "android:textColor" :value "#123" :config :default}
117 | {:name "android:background" :value "@drawable/foo_drawable" :config :default}
118 | {:mixin "fooBar" :args [] :config :default}
119 | {:name "android:textSize" :value "small_text" :config :default}
120 | {:name "android:text" :value "\"some text\"" :config :default}
121 | {:mixin "qux" :args [] :config :default}]})
122 |
123 | (expected-when "Parsing a single mixin" parse-first
124 | when ["mixin Foo {}"]
125 | = {:id "Foo"
126 | :node :mixin
127 | :attrs []}
128 |
129 | when ["mixin FooBarBaz {
130 | android:textSize: 12sp;
131 | android:textColor: #123456;
132 | android:numFoo: 4;
133 | android:text: @string/a_string;
134 | &:foo-bar {
135 | android:textSize: 14sp;
136 | android:extraAttr: 'extra';
137 | }
138 | }"]
139 | = {:id "FooBarBaz"
140 | :node :mixin
141 | :attrs [{:name "android:textSize" :value "12sp" :config :default}
142 | {:name "android:textColor" :value "#123456" :config :default}
143 | {:name "android:numFoo" :value "4" :config :default}
144 | {:name "android:text" :value "@string/a_string" :config :default}
145 | {:name "android:textSize" :value "14sp" :config :foo-bar}
146 | {:name "android:extraAttr" :value "'extra'" :config :foo-bar}]})
147 |
148 |
149 | (expected-when "parsing several nodes" parse-str
150 | when ["color red: #f00;
151 | dimen normal_paddings: 12dp;
152 | style Foo < Bar {
153 | foo: 12dp;
154 | &:land { foo: 12dp; }
155 | }"]
156 | = [{:id "red"
157 | :node :color
158 | :vals [{:config :default
159 | :value "#f00"}]}
160 | {:id "normal_paddings"
161 | :node :dimen
162 | :vals [{:config :default
163 | :value "12dp"}]}
164 | {:id "Foo"
165 | :node :style
166 | :parent "Bar"
167 | :attrs [{:name "foo" :value "12dp" :config :default}
168 | {:name "foo" :value "12dp" :config :land}]}])
169 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # eaml
2 |
3 | [](https://circleci.com/gh/fhur/eaml/tree/master)
4 |
5 | eaml (pronounced e-mel) is the extended android modeling language. It is
6 | an XML preprocessor that will make your android resource definitions
7 | simple, readable, understandable and will greatly facilitate supporting
8 | several different configurations with a minimal code base.
9 |
10 | Read the [release notes](./doc/release_notes.md) to see what features
11 | are implemented in the current release.
12 |
13 | Language QuickStart
14 | ===================
15 |
16 | This short guide will give you a basic understanding of most eaml features.
17 |
18 | ## Introduction
19 |
20 | Eaml is a simple language for defining styles in Android. Eaml is targeted at android
21 | developers who already understand the Android styling framework but are having trouble
22 | organizing and maintaining their styles as the project grows.
23 |
24 | Eaml's most interesting features include:
25 | - Support for nested directories. You can nest your `.eaml` files as deep as possible.
26 | - Support for `mixin`s. See the mixin section for more information.
27 | - Configuration selectors. Using the `&:` syntax you can specify how a style varies its
28 | behaviour depending on the device's configuration.
29 |
30 | *NOTE:* A good understanding of how Android styles work is required in order to use Eaml
31 | proficiently as Eaml transpiles to Android's XML styles.
32 |
33 | ## Defining simple resources
34 |
35 | Eaml provides a simplified syntax for defining simple resources.
36 | Simple resources include `color`, `dimen`, `bool`, `integer` and `string`
37 |
38 | ### Syntax
39 | ` : ;`
40 |
41 | Example:
42 | ```
43 | # Let's define some colors
44 | color red: #f00;
45 | color green: #0f0;
46 | color blue: #00f;
47 |
48 | # And some dimen
49 | dimen small_margins: 4dp;
50 | dimen medium_margins: 8dp;
51 | dimen large_margins: 8dp;
52 | ```
53 |
54 | ### Resource Pointers
55 |
56 | When a resource reference another resource, this reference is called a resource pointer.
57 |
58 | Eaml provides two types of pointers:
59 |
60 | - Native resource pointers e.g. `@color/red`. Use these type of pointers when referencing a resource
61 | defined in an xml file. This serves as a way of interop between existing xml styles and Eaml programs.
62 | All form of native resource pointers are supported including `@null`.
63 | - Eaml resource pointers e.g. `color main_color: red;`. Use these pointers when referencing a resource
64 | defined in Eaml.
65 |
66 | Example:
67 | ```
68 | color a_cool_color: #f00;
69 | color some_other_color: @android:color/a_color;
70 | color my_favorite_color: a_cool_color;
71 | ```
72 |
73 | ### Supporting multiple configurations
74 |
75 | Eaml simplifies supporting multiple configurations by providing a concise
76 | syntax to describe how resources behave on different configurations.
77 |
78 | #### Syntax:
79 |
80 | ```
81 | {
82 | : ;
83 | : ;
84 | ...
85 | }
86 | ```
87 |
88 | Example:
89 | ```
90 | # Paddings that behave differently when the device is in landscape
91 | dimen button_paddings {
92 | default: 4dp;
93 | land: 8dp;
94 | }
95 | ```
96 |
97 | ## Defining styles
98 |
99 | Styles can be defined using the following syntax.
100 | ```
101 | style (< )? {
102 | *
103 | }
104 | ```
105 |
106 | Example:
107 | ```
108 | style Button {
109 | android:paddingLeft: 8dp;
110 | android:paddingRight: 8dp;
111 | android:paddingTop: 4dp;
112 | android:paddingBottom: 4dp;
113 | android:textSize: 12sp;
114 | }
115 | ```
116 |
117 | ## Inheritance
118 | Eaml supports single inheritance via the `<` keyword. This is simply syntax sugar
119 | that piggy backs on android's `parent` attribute.
120 |
121 | Example:
122 | ```
123 | style BigButton < Button {
124 | android:paddingTop: 8dp;
125 | android:paddingBottom: 8dp;
126 | }
127 | ```
128 |
129 | ## Multiple configuration support
130 | Styles also come with built in syntax to support multiple configurations
131 | as follows:
132 |
133 | ```
134 | # A button that uses a ripple on v21 and a regular drawable by default
135 | style RedButton < Button {
136 | android:textColor: red;
137 | android:background: @drawable/btn_red;
138 | &:v21 {
139 | android:background: @drawable/btn_red_ripple;
140 | }
141 | }
142 | ```
143 |
144 | ## Mixins
145 | In cases where you would like to reuse functionality from more than one source
146 | you can use mixins. Mixins are for most purposes identical to styles except for
147 | the fact that they cannot inherit from other `mixins` or `styles`.
148 |
149 | ```
150 | # A simple mixin that just sets the text color to red.
151 | mixin redText {
152 | android:textColor: red;
153 | }
154 |
155 | style RedButton < Button {
156 | redText()
157 | }
158 | ```
159 |
160 | Mixins can define behaviour for multiple configurations as follows:
161 |
162 | ```
163 | mixin redText {
164 | android:textColor: red;
165 | &:v21 {
166 | android:textColor: fancy_red;
167 | }
168 | }
169 |
170 | style RedButton < Button {
171 | redText();
172 | }
173 | ```
174 |
175 | *NOTE*: you cannot invoke a mixin from inside a configuration block:
176 | ```
177 | style RedButton < Button {
178 | &:v21 {
179 | redText(); # THIS WILL THROW AN ERROR!
180 | }
181 | }
182 | ```
183 |
184 | #### Android Studio Support
185 |
186 | There is an Android Studio being developed in [fhur/eaml-idea](https://github.com/fhur/eaml-idea).
187 | The plugin is still under construction but it will offer easy gradle integration, syntax highlighting,
188 | auto completion and other nice features.
189 |
190 | #### Feature requests
191 |
192 | eaml is still in its infancy and we are very interested in understanding
193 | what problems android devs encounter when writing styles.
194 |
195 | Is there a feature that you really need but is not here?
196 | Please create a new issue explaining the feature + use case and
197 | it might end up on the next version :)
198 |
199 | ## License
200 |
201 | Copyright © 2015 fhur
202 |
203 | Distributed under the Eclipse Public License either version 1.0.
204 |
--------------------------------------------------------------------------------
/test/eaml/compiler_test.clj:
--------------------------------------------------------------------------------
1 | (ns eaml.compiler-test
2 | (:require [eaml.compiler :refer :all]
3 | [eaml.test-helpers :refer :all]
4 | [eaml.fixtures.simple-res :refer :all]
5 | [presto.core :refer :all]
6 | [clojure.test :refer :all]))
7 |
8 |
9 | (expected-when "Transpiling simple resources" transpile-str
10 | when [fix-simple-colors]
11 | = {:default
12 | (resources
13 | (color "red" "#f00")
14 | (color "green" "#0f0")
15 | (color "blue" "#00f")
16 | (color "main_color" "@color/red"))}
17 |
18 | when [fix-simple-dimen]
19 | = {:default
20 | (resources
21 | (dimen "small_margins" "8dp")
22 | (dimen "medium_margins" "12dp")
23 | (dimen "large_margins" "24dp")
24 | (dimen "default_margins" "@dimen/medium_margins"))}
25 |
26 | when [fix-simple-strings]
27 | = {:default
28 | (resources
29 | (string "hello_world" "Hello World!")
30 | (string "name" "Pizza 123"))}
31 |
32 | when [fix-simple-bools]
33 | = {:default
34 | (resources
35 | (bool "is_true" "true")
36 | (bool "aint_true" "false")
37 | (bool "a_boolean" "@bool/is_true"))})
38 |
39 |
40 | (expected-when "Transpiling simple resources that support multiple configs" transpile-str
41 | when [fix-simple-res-with-configs]
42 | = {:default (resources
43 | (dimen "padding" "12dp")
44 | (string "supports_ripples" "nope")
45 | (color "main_color" "#f00")
46 | (color "button_color" "@color/main_color"))
47 | :v21 (resources
48 | (dimen "padding" "24dp")
49 | (string "supports_ripples" "yes")
50 | (color "button_color" "@drawable/btn_ripple"))
51 | :land (resources
52 | (dimen "padding" "30dp"))})
53 |
54 | (expected-when "Transpiling styles" transpile-str
55 | when ["color foo: #fff;
56 | mixin redColored {
57 | color: #f00;
58 | bar: 12dp;
59 | }
60 | style Foo {
61 | foo: foo;
62 | redColored();
63 | }"]
64 | = {:default (resources
65 | (color "foo" "#fff")
66 | (style {:name "Foo"}
67 | (item "foo" "@color/foo")
68 | (item "color" "#f00")
69 | (item "bar" "12dp")))}
70 |
71 | when ["style AppTheme < Theme.AppCompat.Light.NoActionBar {
72 | android:windowBackground: @null;
73 | colorPrimary: @color/red_1;
74 | colorPrimaryDark: @android:color/black;
75 | foo123: @style/SpinnerItem;
76 | bar123qwe: @style/Foo.Bar.Baz123;
77 | }"]
78 | = {:default (resources
79 | (style {:name "AppTheme" :parent "Theme.AppCompat.Light.NoActionBar"}
80 | (item "android:windowBackground" "@null")
81 | (item "colorPrimary" "@color/red_1")
82 | (item "colorPrimaryDark" "@android:color/black")
83 | (item "foo123" "@style/SpinnerItem")
84 | (item "bar123qwe" "@style/Foo.Bar.Baz123")))}
85 |
86 |
87 | when ["style Button < BaseButton {
88 | android:textSize: 12dp;
89 | android:background: @drawable/btn_back;
90 | &:v21,v22 {
91 | android:background: @drawable/btn_ripple;
92 | }
93 | &:land {
94 | android:textSize: 10dp;
95 | }
96 | }"]
97 | = {:default (resources
98 | (style {:name "Button" :parent "BaseButton"}
99 | (item "android:textSize" "12dp")
100 | (item "android:background" "@drawable/btn_back")))
101 | :v21 (resources
102 | (style {:name "Button" :parent "BaseButton"}
103 | (item "android:textSize" "12dp")
104 | (item "android:background" "@drawable/btn_ripple")))
105 | :v22 (resources
106 | (style {:name "Button" :parent "BaseButton"}
107 | (item "android:textSize" "12dp")
108 | (item "android:background" "@drawable/btn_ripple")))
109 | :land (resources
110 | (style {:name "Button" :parent "BaseButton"}
111 | (item "android:background" "@drawable/btn_back")
112 | (item "android:textSize" "10dp")))})
113 |
114 |
115 | (expected-when "mixins override any style attrs set by the style" transpile-str
116 | when ["mixin mixinA { attr: 12dp; }
117 | mixin mixinB {
118 | attr: 14dp;
119 | &:v21 { attr: 16dp; }
120 | }
121 | style Foo {
122 | attr: 10dp;
123 | mixinA();
124 | &:v21 {
125 | attr: 20dp;
126 | }
127 | mixinB();
128 | }"]
129 | = {:default (resources
130 | (style {:name "Foo"}
131 | (item "attr" "14dp")))
132 | :v21 (resources
133 | (style {:name "Foo"}
134 | (item "attr" "16dp")))})
135 |
136 | (expected-when "mixin provide a form of including common style attributes"
137 | transpile-str
138 | when ["color main_color: #f0f0f0;
139 | color main_color_lighter: #f2f2f2;
140 | mixin mainBackgroundColored {
141 | android:background: main_color;
142 | &:v21 {
143 | android:background: main_color_lighter;
144 | }
145 | }
146 | style Foo {
147 | mainBackgroundColored();
148 | }"]
149 | = {:default (resources
150 | (color "main_color" "#f0f0f0")
151 | (color "main_color_lighter" "#f2f2f2")
152 | (style {:name "Foo"}
153 | (item "android:background" "@color/main_color")))
154 | :v21 (resources
155 | (style {:name "Foo"}
156 | (item "android:background" "@color/main_color_lighter")))})
157 |
158 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------