Skip to content

Commit a240648

Browse files
[inspect] Refactor
1 parent e692cbe commit a240648

File tree

2 files changed

+99
-106
lines changed

2 files changed

+99
-106
lines changed

src/orchard/inspect.clj

Lines changed: 83 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848

4949
(defn- reset-render-state [inspector]
5050
(-> inspector
51-
(assoc :counter 0, :index [], :indentation 0, :rendered [])
51+
(assoc :index [], :indentation 0, :rendered (transient []))
5252
(dissoc :chunk :start-idx :last-page)))
5353

5454
(defn- print-string
@@ -83,49 +83,54 @@
8383
`(when-not ~x
8484
(throw (ex-info (str "Precondition failed: " (pr-str '~x)) {}))))
8585

86-
(defn- counted-length [obj]
86+
(defn- pageable? [obj]
87+
(contains? #{:list :map :set :array} (object-type obj)))
88+
89+
(defn- counted-length [{:keys [page-size]} obj]
8790
(cond (instance? clojure.lang.Counted obj) (count obj)
8891
(instance? Map obj) (.size ^Map obj)
8992
(array? obj) (java.lang.reflect.Array/getLength obj)
90-
;; Count small lazy collections <= 10 elements (arbitrary).
91-
(sequential? obj) (let [bc (bounded-count 11 obj)]
92-
(when (<= bc 10)
93-
bc))))
93+
;; Count small lazy collections (<= page-size).
94+
(pageable? obj) (let [bc (bounded-count (inc page-size) obj)]
95+
(when (<= bc page-size)
96+
bc))))
9497

9598
(defn- pagination-info
9699
"Calculate if the object should be paginated given the page size. Return a map
97100
with pagination info, or nil if object fits in a single page."
98-
[obj page-size current-page]
99-
(let [clength (counted-length obj)
101+
[{:keys [page-size current-page view-mode value] :as inspector}]
102+
(let [page-size (if (= view-mode :hex)
103+
(* page-size 16) ;; In hex view mode, each row is 16 bytes.
104+
page-size)
100105
start-idx (* current-page page-size)
101106
;; Try grab a chunk that is one element longer than asked in
102107
;; page-size. This is how we know there are elements beyond the
103108
;; current page.
104-
chunk+1 (->> obj
105-
(drop start-idx)
106-
(take (inc page-size)))
109+
chunk+1 (persistent! (transduce (comp (drop start-idx)
110+
(take (inc page-size)))
111+
conj! (transient []) value))
107112
count+1 (count chunk+1)
108113
paginate? (or (> current-page 0) ;; In non-paginated it's always 0.
109114
(> count+1 page-size))
110-
last-page (cond clength (quot (dec clength) page-size)
111-
(<= count+1 page-size) current-page
112-
;; Possibly infinite
113-
:else Integer/MAX_VALUE)]
115+
clength (or (counted-length inspector value)
116+
(when (<= count+1 page-size)
117+
(+ (* page-size current-page) count+1)))
118+
last-page (if clength
119+
(quot (dec clength) page-size)
120+
;; Possibly infinite
121+
Integer/MAX_VALUE)]
114122
(when paginate?
115-
{:chunk (take page-size chunk+1)
123+
{:chunk (cond-> chunk+1
124+
(> count+1 page-size) pop)
116125
:start-idx start-idx
117126
:last-page last-page})))
118127

119128
(defn- decide-if-paginated
120129
"Make early decision if the inspected object should be paginated. If so,
121130
assoc the `:chunk` to be displayed to `inspector`."
122-
[{:keys [value current-page page-size view-mode] :as inspector}]
123-
(let [pageable? (boolean (#{:list :map :set :array} (object-type value)))
124-
page-size (if (= view-mode :hex)
125-
(* page-size 16) ;; In hex view mode, each row is 16 bytes.
126-
page-size)]
127-
(cond-> (assoc inspector :pageable pageable?)
128-
pageable? (merge (pagination-info value page-size current-page)))))
131+
[{:keys [value] :as inspector}]
132+
(cond-> inspector
133+
(pageable? value) (merge (pagination-info inspector))))
129134

130135
(defn next-page
131136
"Jump to the next page when inspecting a paginated sequence/map. Does nothing
@@ -323,43 +328,42 @@
323328

324329
(defn render
325330
([{:keys [rendered] :as inspector} value]
326-
;; Special case: fuse two last strings together.
327-
(let [lst (peek (or rendered []))]
328-
(assoc inspector :rendered (if (and (string? lst) (string? value))
329-
(conj (pop rendered) (.concat ^String lst value))
330-
(conj rendered value)))))
331+
(assoc inspector :rendered (conj! rendered value)))
331332
([inspector value & values]
332333
(reduce render (render inspector value) values)))
333334

334335
(defn render-onto [inspector coll]
335336
(reduce render inspector coll))
336337

337-
(defn render-ln [inspector & values]
338-
(-> inspector
339-
(render-onto values)
340-
(render '(:newline))))
338+
(defn render-ln [inspector]
339+
(render inspector '(:newline)))
341340

342341
(defn- indent
343342
"Increment the `:indentation` of `inspector` by `n` or 2."
344-
[inspector & [n]]
345-
(update inspector :indentation + (or n 2)))
343+
([inspector] (update inspector :indentation + 2))
344+
([inspector n]
345+
(cond-> inspector
346+
(pos? n) (update :indentation + n))))
346347

347348
(defn- unindent
348349
"Decrement the `:indentation` of `inspector` by `n` or 2."
349-
[inspector & [n]]
350-
(indent inspector (- (or n 2))))
350+
([inspector] (update inspector :indentation - 2))
351+
([inspector n]
352+
(cond-> inspector
353+
(pos? n) (update :indentation - n))))
351354

352355
(defn- padding [{:keys [indentation]}]
353356
(when (and (number? indentation) (pos? indentation))
354-
(String. (char-array indentation \space))))
357+
(if (= indentation 2) " " ;; Fastpath
358+
(String. (char-array indentation \space)))))
355359

356-
(defn- render-indent [inspector & values]
357-
(let [padding (padding inspector)]
358-
(cond-> inspector
359-
padding
360-
(render padding)
361-
(seq values)
362-
(render-onto values))))
360+
(defn- render-indent
361+
([inspector]
362+
(if-let [padding (padding inspector)]
363+
(render inspector padding)
364+
inspector))
365+
([inspector & values]
366+
(render-onto (render-indent inspector) values)))
363367

364368
(defn- render-indent-ln [inspector & values]
365369
(let [padding (padding inspector)]
@@ -380,15 +384,14 @@
380384
`display-value` string can be provided explicitly."
381385
([inspector value] (render-value inspector value nil))
382386
([inspector value {:keys [value-role value-key display-value]}]
383-
(let [{:keys [counter]} inspector
387+
(let [{:keys [index]} inspector
384388
display-value (or display-value (print-string inspector value))
385-
expr (list :value display-value counter)]
389+
expr (seq [:value display-value (count index)])]
386390
(-> inspector
387391
(update :index conj {:value value
388392
:role value-role
389393
:key value-key})
390-
(update :counter inc)
391-
(update :rendered conj expr)))))
394+
(update :rendered conj! expr)))))
392395

393396
(defn render-indented-value [inspector value & [value-opts]]
394397
(-> inspector
@@ -410,7 +413,7 @@
410413
(render-labeled-value inspector "Class" (class obj)))
411414

412415
(defn- render-counted-length [inspector obj]
413-
(if-let [clength (counted-length obj)]
416+
(if-let [clength (counted-length inspector obj)]
414417
(render-indent-ln inspector "Count: " (str clength))
415418
inspector))
416419

@@ -507,10 +510,10 @@
507510
(reduce render-row ins pr-rows))))
508511

509512
(defn- leftpad [idx last-idx-len]
510-
(let [idx-s (str idx)
513+
(let [^String idx-s (str idx)
511514
idx-len (count idx-s)]
512515
(if (= idx-len last-idx-len)
513-
(str idx-s ". ")
516+
(.concat idx-s ". ")
514517
(str (String. (char-array (- last-idx-len idx-len) \space)) idx-s ". "))))
515518

516519
(defn- render-indexed-chunk
@@ -520,25 +523,26 @@
520523
[{:keys [pretty-print] :as inspector} chunk {:keys [start-idx mark-values? skip-nils?]}]
521524
(let [start-idx (or start-idx 0)
522525
n (count chunk)
526+
idx (volatile! start-idx)
523527
last-idx (+ start-idx n -1)
524528
last-idx-len (count (str last-idx))]
525-
(loop [ins inspector, chunk (seq chunk), idx start-idx]
526-
(if chunk
527-
(let [header (leftpad idx last-idx-len)
528-
indentation (if pretty-print (count header) 0)
529-
item (first chunk)]
530-
(recur (if-not (and (nil? item) skip-nils?)
531-
(-> ins
532-
(render-indent header)
533-
(indent indentation)
534-
(render-value item
535-
(when mark-values?
536-
{:value-role :seq-item, :value-key idx}))
537-
(unindent indentation)
538-
(render-ln))
539-
ins)
540-
(next chunk) (inc idx)))
541-
ins))))
529+
(reduce (fn [ins item]
530+
(let [i @idx
531+
header (leftpad i last-idx-len)
532+
indentation (if pretty-print (count header) 0)]
533+
(vswap! idx inc)
534+
(if-not (and (nil? item) skip-nils?)
535+
(-> ins
536+
(render-indent)
537+
(render header)
538+
(indent indentation)
539+
(render-value item
540+
(when mark-values?
541+
{:value-role :seq-item, :value-key i}))
542+
(unindent indentation)
543+
(render-ln))
544+
ins)))
545+
inspector chunk)))
542546

543547
(declare known-types)
544548

@@ -566,7 +570,7 @@
566570
"If `obj` is a collection smaller than page-size, then render it as a
567571
collection, otherwise as a compact value."
568572
[{:keys [page-size] :as inspector} obj]
569-
(if (some-> (counted-length obj) (<= page-size))
573+
(if (some-> (counted-length inspector obj) (<= page-size))
570574
(render-items inspector obj {:map? (map? obj), :start-idx 0})
571575
(render-indented-value inspector obj)))
572576

@@ -656,7 +660,7 @@
656660
"Datafy either the current value or its paginated view. Return datafied
657661
representation if it differs from value and boolean `mirror?` that tells if
658662
the datafied representation mirrors the structure of the input collection."
659-
[{:keys [value chunk pageable]}]
663+
[{:keys [value chunk]}]
660664
(if-let [datafied (datafy-root value)]
661665
;; If the root value has datafy representation, check if it's a collection.
662666
;; If so, additionally datafy its items or map values.
@@ -669,7 +673,7 @@
669673
(when-not (identical? datafied value)
670674
[datafied false]))
671675

672-
(when pageable
676+
(when (pageable? value)
673677
;; If the value is a type that can be paged, then only datafy the
674678
;; displayed chunk.
675679
(let [chunk (or chunk value)
@@ -751,7 +755,8 @@
751755

752756
(defmethod inspect :nil [inspector _obj]
753757
(-> inspector
754-
(render-ln "Value: nil")
758+
(render "Value: nil")
759+
(render-ln)
755760
(render-section-header "Contents")
756761
(indent)
757762
(render-indent-ln
@@ -898,8 +903,8 @@
898903
(render-ident-hashcode [inspector]
899904
(let [code (System/identityHashCode obj)]
900905
(-> inspector
901-
(render-indent "Identity hash code: " (str code) " "
902-
(format "(0x%s)" (Integer/toHexString code)))
906+
(render "Identity hash code: " (str code) " "
907+
(format "(0x%s)" (Integer/toHexString code)))
903908
(render-ln))))]
904909
(cond-> inspector
905910
true (render-labeled-value "Class" (class obj))
@@ -1058,7 +1063,6 @@
10581063

10591064
(defmethod inspect :namespace [inspector ^clojure.lang.Namespace obj]
10601065
(-> (render-class-name inspector obj)
1061-
(render-counted-length (ns-map obj))
10621066
(render-meta-information obj)
10631067
(render-ns-refers obj)
10641068
(render-ns-imports obj)
@@ -1088,6 +1092,9 @@
10881092
(unindent)))
10891093
inspector))
10901094

1095+
(defn- finalize-rendered [rendered]
1096+
(seq (persistent! rendered)))
1097+
10911098
(defn inspect-render
10921099
([{:keys [max-atom-length max-value-length max-coll-size max-nested-depth value pretty-print]
10931100
:as inspector}]
@@ -1108,7 +1115,7 @@
11081115
(inspect value)
11091116
(render-path)
11101117
(render-view-mode)
1111-
(update :rendered seq)))))
1118+
(update :rendered finalize-rendered)))))
11121119

11131120
;; Public entrypoints
11141121

@@ -1133,14 +1140,3 @@
11331140
"If necessary, use `(start nil)` instead."
11341141
[]
11351142
(start nil))
1136-
1137-
(defn inspect-print
1138-
"Get a human readable printout of rendered sequence."
1139-
[x]
1140-
(print
1141-
(with-out-str
1142-
(doseq [[type value :as component] (:rendered (start x))]
1143-
(print (case type
1144-
:newline \newline
1145-
:value (str value)
1146-
component))))))

test/orchard/inspect_test.clj

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,12 @@
105105

106106
(defn render
107107
[inspector]
108-
(:rendered inspector))
108+
(reduce (fn [acc x]
109+
(let [lst (peek acc)]
110+
(if (and (string? x) (string? lst))
111+
(conj (pop acc) (str lst x))
112+
(conj acc x))))
113+
[] (:rendered inspector)))
109114

110115
(defn set-page-size [inspector new-size]
111116
(inspect/refresh inspector {:page-size new-size}))
@@ -220,13 +225,9 @@
220225

221226
(deftest pagination-test
222227
(testing "big collections are paginated"
223-
(is (= 33 (-> long-sequence
224-
inspect
225-
:counter)))
228+
(is+ 33 (count (:index (inspect long-sequence))))
226229
;; Twice more for maps
227-
(is (= 65 (-> long-map
228-
inspect
229-
:counter)))
230+
(is+ 65 (count (:index (inspect long-map))))
230231
(is (-> long-vector
231232
inspect
232233
:rendered
@@ -237,21 +238,19 @@
237238
:rendered
238239
page-size-info))))
239240
(testing "changing page size"
240-
(is (= 21 (-> long-sequence
241-
inspect
242-
(set-page-size 20)
243-
:counter)))
244-
(is (= 41 (-> long-map
245-
inspect
246-
(set-page-size 20)
247-
:counter)))
241+
(is+ 21 (count (:index (-> long-sequence
242+
inspect
243+
(set-page-size 20)))))
244+
(is+ 41 (count (:index (-> long-map
245+
inspect
246+
(set-page-size 20)))))
248247
(is (nil? (-> long-sequence
249248
inspect
250249
(set-page-size 200)
251250
:rendered
252251
page-size-info))))
253252
(testing "uncounted collections have their size determined on the last page"
254-
(is (= " Page size: 32, showing page: 2 of 2"
253+
(is (= "Page size: 32, showing page: 2 of 2"
255254
(-> (range 50)
256255
inspect
257256
inspect/next-page
@@ -1110,9 +1109,7 @@
11101109
(testing "inspecting the clojure.string namespace"
11111110
(let [result (-> (find-ns 'clojure.string) inspect render)]
11121111
(testing "renders the header"
1113-
(is+ ["Class: " [:value "clojure.lang.Namespace" number?] [:newline]
1114-
#"^Count: " [:newline]
1115-
[:newline]]
1112+
(is+ (matchers/prefix ["Class: " [:value "clojure.lang.Namespace" number?]])
11161113
(header result)))
11171114
(testing "renders the meta section"
11181115
(is+ ["--- Meta Information:"

0 commit comments

Comments
 (0)