Skip to content

Commit 6f2e243

Browse files
committed
[wip] Validate & destructure parts, including passed-in functions
1 parent 55c0c50 commit 6f2e243

File tree

8 files changed

+639
-65
lines changed

8 files changed

+639
-65
lines changed

.cljfmt.edn

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
{:extra-indents {re-com.util/part [[:inner 0]]}}
1+
{:extra-indents {re-com.util/part [[:inner 0]]
2+
part [[:inner 0]]}}

src/re_com/nested_v_grid.cljs

Lines changed: 370 additions & 22 deletions
Large diffs are not rendered by default.

src/re_com/nested_v_grid/parts.cljs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
(defn header-label [{:keys [path]}]
1414
(let [spec (peek path)]
1515
(or (:label spec)
16-
(some-> spec :id name)
16+
(some-> spec :id str)
1717
(some-> spec pr-str))))
1818

1919
(def row-header-label header-label)

src/re_com/part.cljs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
(ns re-com.part
2+
(:refer-clojure :exclude [name get])
3+
(:require
4+
[clojure.set :as set]
5+
[clojure.string :as str]
6+
[re-com.theme.util :as tu]
7+
[re-com.validate :as validate]
8+
[re-com.util :as u]))
9+
10+
(def id first)
11+
12+
(defn children [[a & [b & rest-children :as all-children]]]
13+
(if (map? b)
14+
rest-children
15+
all-children))
16+
17+
(def branch? sequential?)
18+
19+
(def unqualify (memoize (comp keyword clojure.core/name)))
20+
21+
(def depth
22+
(memoize
23+
(fn depth [tree k]
24+
(let [walk (fn walk [node depth]
25+
(cond
26+
(= (unqualify k)
27+
(unqualify (id node)))
28+
depth
29+
(branch? node)
30+
(some #(walk % (inc depth)) (children node))))]
31+
(walk tree 1)))))
32+
33+
(defn props [[_ b]]
34+
(when (map? b) b))
35+
36+
(def top-level-args
37+
(fn [structure]
38+
(->> structure
39+
(tree-seq branch? children)
40+
(filter (comp :top-level-arg? props))
41+
(map id)
42+
(mapcat (fn [k]
43+
[#_k ;;TODO support qualified keys?
44+
(unqualify k)]))
45+
set)))
46+
47+
(defn css-class [part-id]
48+
(str "rc-"
49+
(subs (namespace part-id) 7)
50+
"-"
51+
(clojure.core/name part-id)))
52+
53+
(def describe
54+
(memoize
55+
(fn describe [structure]
56+
(->> structure
57+
(tree-seq branch? children)
58+
(mapv (fn [node]
59+
(let [part-id (id node)
60+
part-name (unqualify part-id)
61+
{:keys [tag impl]
62+
:as part-props} (props node)]
63+
(merge
64+
part-props
65+
{:name part-name
66+
:class (css-class part-id)
67+
:level (depth structure part-name)
68+
:impl (or (when impl (-> (ns-name impl)
69+
(str/replace "$" ".")
70+
(str/replace "_" "-")))
71+
(when tag (str "[" tag "]"))
72+
"[:div]")}))))))))
73+
74+
(def top-level-arg?
75+
(memoize
76+
(fn [structure k]
77+
(contains? (top-level-args structure) k))))
78+
79+
(defn unqualify-set [s] (set (map unqualify s)))
80+
81+
(defn args-valid? [part-structure args problems]
82+
(let [part-seq (tree-seq branch? children part-structure)
83+
ks (unqualify-set (map id part-seq))
84+
top-ks (unqualify-set (top-level-args part-structure))
85+
top-args (set (filter top-ks (keys args)))
86+
part-args (set (filter ks (keys (:parts args))))
87+
top-level-collisions
88+
(->> (unqualify-set top-args)
89+
(set/intersection (unqualify-set part-args))
90+
(map #(do {:problem :part-top-level-collision
91+
:arg-name %})))
92+
top-level-unsupported-keys
93+
(->> (set (keys args))
94+
(set/intersection ks)
95+
(remove top-ks)
96+
(map #(do {:problem :part-top-level-unsupported
97+
:arg-name %})))]
98+
(vec (concat problems
99+
top-level-collisions
100+
top-level-unsupported-keys))))
101+
102+
(def part? (some-fn map? string? vector? ifn? nil?))
103+
104+
(defn describe-args [structure]
105+
(into [{:name :parts
106+
:required false
107+
:type "map"
108+
:parts-validate-fn (validate/parts?
109+
(set (map :name (describe structure))))
110+
:description "See Parts section below."}]
111+
(comp
112+
(filter (comp (top-level-args structure) :name))
113+
(map #(merge % {:validate-fn part?})))
114+
(describe structure)))
115+
116+
(defn destructure [part-structure props k]
117+
(or (when (top-level-arg? part-structure k)
118+
(clojure.core/get props (unqualify k)))
119+
(clojure.core/get-in props [:parts (unqualify k)])))
120+
121+
(defn default [{:keys [class style attr children tag]
122+
:or {tag :div}}]
123+
(into [tag (merge {:class class :style style} attr)]
124+
children))
125+
126+
(defn part
127+
([structure props k opts]
128+
(part (destructure structure props k)
129+
(assoc opts :part k)))
130+
([part-value {:keys [impl key theme post-props props]
131+
part-id :part
132+
:or {impl default}}]
133+
(cond->
134+
(cond
135+
(u/hiccup? part-value) part-value
136+
(string? part-value) part-value
137+
:else (let [component (cond (map? part-value) impl
138+
(ifn? part-value) part-value
139+
:else impl)
140+
props
141+
(cond-> {:part part-id}
142+
:do (merge props)
143+
theme (theme component)
144+
(map? part-value) (tu/merge-props part-value)
145+
post-props (tu/merge-props post-props))]
146+
[component props]))
147+
key (with-meta {:key key}))))
148+

src/re_com/theme.cljs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
(:refer-clojure :exclude [comp])
33
(:require
44
[re-com.theme.util :as tu]
5+
[re-com.part :as part]
56
[re-com.theme.default :as theme.default]))
67

78
(def ^:dynamic variables theme.default/variables)
@@ -16,11 +17,7 @@
1617
(def merge-props tu/merge-props)
1718

1819
(defn part-class [{:keys [part] :as props}]
19-
(update props :class merge-class
20-
(str "rc-"
21-
(subs (namespace part) 7)
22-
"-"
23-
(name part))))
20+
(update props :class merge-class (part/css-class part)))
2421

2522
(def part-class* (memoize part-class))
2623

src/re_com/validate.cljs

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
(:require
55
[clojure.string :as str]
66
[cljs-time.core :as time.core]
7-
[clojure.set :refer [superset?]]
7+
[clojure.set :refer [superset? intersection union difference]]
88
[re-com.config :refer [debug?]]
99
[re-com.debug :as debug]
10-
[re-com.util :refer [deref-or-value-peek]]
10+
[re-com.util :as u :refer [deref-or-value-peek]]
1111
[reagent.core :as reagent]
1212
[reagent.impl.component :as component]
1313
[reagent.impl.template :refer [valid-tag?]]
@@ -34,14 +34,17 @@
3434
(defn extract-arg-data
3535
"Package up all the relevant data for validation purposes from the xxx-args-desc map into a new map"
3636
[args-desc]
37-
{:arg-names (set (map :name args-desc))
38-
:required-args (->> args-desc
39-
(filter :required)
40-
(map :name)
41-
set)
42-
:validated-args (->> (filter :validate-fn args-desc)
43-
vec
44-
(hash-map-with-name-keys))})
37+
(merge
38+
{:arg-names (set (map :name args-desc))
39+
:required-args (->> args-desc
40+
(filter :required)
41+
(map :name)
42+
set)
43+
:validated-args (->> (filter :validate-fn args-desc)
44+
vec
45+
(hash-map-with-name-keys))}
46+
(when-let [parts-validate-fn (some :parts-validate-fn args-desc)]
47+
{:parts-validate-fn parts-validate-fn})))
4548

4649
;; ----------------------------------------------------------------------------
4750
;; Primary validation functions
@@ -119,7 +122,9 @@
119122
warning?)
120123
(do
121124
(log-warning
122-
(str "Validation failed for argument '" arg-name "' in component '" (component/component-name (reagent/current-component)) "': " (:message validate-result)))
125+
(str "Validation failed for argument '" arg-name "' in component '"
126+
(component/component-name (reagent/current-component))
127+
"': " (:message validate-result)))
123128
nil)
124129

125130
:else
@@ -148,12 +153,16 @@
148153
[arg-defs passed-args]
149154
(if-not debug?
150155
nil
151-
(let [passed-arg-keys (set (remove #{:theme :re-com :part} (set (keys passed-args))))
152-
problems (->> []
153-
(arg-names-known? (:arg-names arg-defs) passed-arg-keys)
154-
(required-args? (:required-args arg-defs) passed-arg-keys)
155-
(validate-fns? (:validated-args arg-defs) passed-args)
156-
(remove nil?))]
156+
(let [{:keys [parts-validate-fn]} arg-defs
157+
passed-arg-keys (set (remove #{:theme :re-com :part} (set (keys passed-args))))
158+
problems (as-> [] problems
159+
(cond-> problems
160+
parts-validate-fn
161+
(parts-validate-fn passed-args problems))
162+
(arg-names-known? (:arg-names arg-defs) passed-arg-keys problems)
163+
(required-args? (:required-args arg-defs) passed-arg-keys problems)
164+
(validate-fns? (:validated-args arg-defs) passed-args problems)
165+
(remove nil? problems))]
157166
(when-not (empty? problems)
158167
[debug/validate-args-error
159168
:problems problems
@@ -439,14 +448,10 @@
439448
{:status (if (or contains-class? contains-style?) :error :warning)
440449
:message result}))))))
441450

442-
(defn part?
443-
"Returns true if the passed argument is a part, otherwise false/error"
444-
[arg]
445-
(or (map? arg) (string-or-hiccup? arg) (ifn? arg) (nil? arg)))
451+
(def part? (some-fn map? string? vector? ifn? nil?))
446452

447453
(defn parts?
448-
"Returns a function that validates a value is a map that contains `keys` mapped to values that are either functions, hiccups, or maps containing
449-
`class`, `:style` and/or `:attr`."
454+
"Returns a function that validates a value is a map that contains `keys` mapped to part-values. A part-value can be either a function, a hiccup, a string, or a parts-map. A parts-map can only contain the keys `class`, `:style` and/or `:attr`."
450455
[part-keys]
451456
{:pre [(set? part-keys)]}
452457
(fn [arg]

src/re_demo/nested_grid.cljs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
[reagent.core :as r]
1111
[re-com.nested-grid :refer [nested-grid leaf-paths header-spec->header-paths
1212
nested-grid-args-desc nested-grid-parts-desc]]
13-
[re-com.nested-v-grid :as v-grid :refer [nested-v-grid]]
13+
[re-com.nested-v-grid :as nvg :refer [nested-v-grid]]
1414
[re-demo.utils :refer [source-reference panel-title title2 title3 args-table parts-table github-hyperlink status-text new-in-version]]))
1515

1616
(def arg-style {:style {:display "inline-block"
@@ -938,21 +938,26 @@
938938
:on-export-corner-header export-cell
939939
:on-resize (fn [{:keys [header-dimension size-dimension keypath size]}]
940940
(case [header-dimension size-dimension]
941-
[:column :height] (swap! column-header-heights assoc-in keypath size)
942-
[:row :width] (swap! row-header-widths assoc-in keypath size)
943-
[:row :height] (swap! row-tree update-in keypath assoc :size size)
944-
[:column :width] (swap! column-tree update-in keypath assoc :size size)))
941+
[:column :height]
942+
(swap! column-header-heights assoc-in keypath size)
943+
[:row :width]
944+
(swap! row-header-widths assoc-in keypath size)
945+
[:row :height]
946+
(swap! row-tree update-in keypath assoc :size size)
947+
[:column :width]
948+
(swap! column-tree update-in keypath assoc :size size)))
945949
:parts {:wrapper {:style {:height @wh
946950
:width @ww}}
947951

948952
:row-header-label
949-
(fn [{:keys [row-path]}]
950-
(let [{:keys [is-after?]} (meta row-path)
951-
row-spec (peek row-path)
952-
the-label (->> "placeholder"
953-
(get row-spec :id)
954-
(get row-spec :label))]
955-
(str the-label (when is-after? " (Total)"))))
953+
(fn [{:keys [row-path style]}]
954+
[:div {:style style}
955+
(let [{:keys [is-after?]} (meta row-path)
956+
row-spec (peek row-path)
957+
the-label (->> "placeholder"
958+
(get row-spec :id)
959+
(get row-spec :label))]
960+
(str the-label (when is-after? " (Total)")))])
956961
:corner-header
957962
(fn [{:keys [edge row-index column-index style class attr] :as props}]
958963
[:div (merge {:style style :class class} attr)
@@ -1062,4 +1067,5 @@
10621067
:on-change #(reset! !tab-id %)]
10631068
[(:view @!tab)]]]
10641069
[demos]]]
1065-
[parts-table "nested-grid" nested-grid-parts-desc]]])))
1070+
#_[parts-table "nested-grid" nested-grid-parts-desc]
1071+
[parts-table "nested-v-grid" nvg/nested-v-grid-parts-desc]]])))

test/re_com/part_test.cljs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
(ns re-com.part-test
2+
(:require
3+
[cljs.test :refer-macros [is are deftest]]
4+
[re-com.part :as part]
5+
[re-com.validate :as validate]))
6+
7+
(def structure
8+
[::wrapper
9+
[::cell-grid
10+
[::cell {:top-level-arg? true}
11+
[::cell-label]]]])
12+
13+
(deftest depth
14+
(is (= 1 (part/depth structure ::wrapper)))
15+
(is (= 4 (part/depth structure ::cell-label))))
16+
17+
(deftest tree-walk
18+
(is (= (tree-seq part/branch? part/children structure)
19+
'([::wrapper
20+
[::cell-grid
21+
[::cell {:top-level-arg? true}
22+
[::cell-label]]]]
23+
[::cell-grid
24+
[::cell {:top-level-arg? true}
25+
[::cell-label]]]
26+
[::cell {:top-level-arg? true}
27+
[::cell-label]]
28+
[::cell-label]))))
29+
30+
(deftest get-part
31+
(let [get-part (partial part/destructure structure)]
32+
(are [props k] (get-part props k)
33+
{:parts {:wrapper true}} :wrapper
34+
{:parts {:wrapper true}} :wrapper
35+
{:cell true} :cell)
36+
(is (= [true true true true]
37+
[(part/top-level-arg? structure :cell)
38+
(get-part {:cell true} :cell)
39+
(not (part/top-level-arg? structure :wrapper))
40+
(not
41+
(get-part {:wrapper true} :wrapper))])
42+
"The getter function looks in a component's top-level keys,
43+
but only when :top-level-arg? is explicitly declared in the part structure.")))
44+
45+
(deftest describe
46+
(is (= (part/describe structure)
47+
[{:name :wrapper, :class "rc-part-test-wrapper", :level 1, :impl "[:div]"}
48+
{:name :cell-grid, :class "rc-part-test-cell-grid", :level 2, :impl "[:div]"}
49+
{:name :cell, :class "rc-part-test-cell", :level 3, :impl "[:div]"}
50+
{:name :cell-label, :class "rc-part-test-cell-label", :level 4, :impl "[:div]"}])))
51+
52+
(deftest validate-props
53+
(are [props problems]
54+
(= (validate/part-keys-valid? structure props [])
55+
problems)
56+
{:cell true} []
57+
{:parts {:cell true}} []
58+
{:parts {:wrapper true}} []
59+
{:cell true
60+
:parts {:cell true}} [{:problem :part-top-level-collision
61+
:arg-name :cell}]
62+
{:wrapper true} [{:problem :part-top-level-unsupported
63+
:arg-name :wrapper}]
64+
{:wrapper true
65+
:cell true
66+
:parts {:cell true}} [{:problem :part-top-level-collision
67+
:arg-name :cell}
68+
{:problem :part-top-level-unsupported
69+
:arg-name :wrapper}]))

0 commit comments

Comments
 (0)