|
55 | 55 | (str csi sgr))
|
56 | 56 |
|
57 | 57 | (def ^:private font-terms
|
| 58 | + ;; Map a keyword to a tuple of characteristic and SGR parameter value. |
| 59 | + ;; We track the current value for each characteristic. |
58 | 60 | (reduce merge
|
59 | 61 | {:bold [:bold "1"]
|
60 | 62 | :plain [:bold "22"]
|
|
83 | 85 | current-value)))
|
84 | 86 |
|
85 | 87 | (defn- compose-font
|
86 |
| - ^String [active current] |
| 88 | + "Uses values in current to build a font string that will reset all fonts characteristics then, |
| 89 | + as necessary, add back needed font characteristics." |
| 90 | + ^String [current] |
87 | 91 | (when-color-enabled
|
88 |
| - (let [codes (keep #(delta active current %) [:foreground :background :bold :italic :inverse :underlined])] |
89 |
| - (when (seq codes) |
90 |
| - (str csi (str/join ";" codes) sgr))))) |
| 92 | + (let [codes (keep #(get current %) [:foreground :background :bold :italic :inverse :underlined])] |
| 93 | + (if (seq codes) |
| 94 | + (str csi "0;" (str/join ";" codes) sgr) |
| 95 | + ;; there were active characteristics, but current has none, so just reset font characteristics |
| 96 | + reset-font)))) |
91 | 97 |
|
92 | 98 | (defn- split-font-def*
|
93 | 99 | [font-def]
|
|
138 | 144 | (throw (ex-info "invalid span declaration"
|
139 | 145 | {:font-decl value}))))
|
140 | 146 |
|
141 |
| -(defn- blank? [value] |
| 147 | +(defn- nil-or-empty-string? |
| 148 | + "True if an empty string, or nil; false otherwise, such as for numbers, etc." |
| 149 | + [value] |
142 | 150 | (or (nil? value)
|
143 | 151 | (= "" value)))
|
144 | 152 |
|
|
152 | 160 | (mod x 2)
|
153 | 161 | 0))))
|
154 | 162 |
|
155 |
| -(defn- apply-padding [terms pad width actual-width] |
| 163 | +(defn- apply-padding |
| 164 | + [terms pad width actual-width] |
156 | 165 | (let [padding-needed (- width actual-width)
|
157 | 166 | left-padding (case pad
|
158 | 167 | (:left nil) padding-needed
|
|
197 | 206 | [coll *width]
|
198 | 207 | (let [f (fn reducer [result input]
|
199 | 208 | (cond
|
200 |
| - (blank? input) |
| 209 | + (nil-or-empty-string? input) |
201 | 210 | result
|
202 | 211 |
|
203 | 212 | (vector? input)
|
|
225 | 234 | (defn- collect-markup
|
226 | 235 | [state input]
|
227 | 236 | (cond
|
228 |
| - (blank? input) |
| 237 | + (nil-or-empty-string? input) |
229 | 238 | state
|
230 | 239 |
|
231 | 240 | (vector? input)
|
|
236 | 245 | ;; Normal (no width tracking)
|
237 | 246 | (let [{:keys [current]} state]
|
238 | 247 | (-> (reduce collect-markup
|
239 |
| - (-> state |
240 |
| - (update :current update-font-data-from-font-def font) |
241 |
| - (update :stack conj current)) |
| 248 | + (update state :current update-font-data-from-font-def font) |
242 | 249 | inputs)
|
243 |
| - (assoc :current current) |
244 |
| - (update :stack pop))))) |
| 250 | + ;; At the end of the vector, return current (but not the active) |
| 251 | + ;; to what it was previously. We leave active alone until we're about |
| 252 | + ;; to output. |
| 253 | + (assoc :current current))))) |
245 | 254 |
|
246 | 255 | ;; Lists, lazy-lists, etc: processed recursively
|
247 | 256 | (sequential? input)
|
|
251 | 260 | (let [{:keys [active current ^StringBuilder buffer]} state
|
252 | 261 | state' (if (= active current)
|
253 | 262 | state
|
254 |
| - (let [font-str (compose-font active current)] |
| 263 | + (let [font-str (compose-font current)] |
255 | 264 | (when font-str
|
256 | 265 | (.append buffer font-str))
|
257 | 266 | (cond-> (assoc state :active current)
|
258 | 267 | ;; Signal that a reset is needed at the very end
|
259 |
| - font-str (assoc :dirty? true))))] |
| 268 | + font-str |
| 269 | + (assoc :dirty? (not= font-str reset-font)))))] |
260 | 270 | (.append buffer (str input))
|
261 | 271 | state')))
|
262 | 272 |
|
263 | 273 | (defn- compose*
|
264 | 274 | [inputs]
|
265 |
| - (let [initial-font {:foreground "39" |
266 |
| - :background "49" |
267 |
| - :bold "22" |
268 |
| - :italic "23" |
269 |
| - :inverse "27" |
270 |
| - :underlined "24"} |
271 |
| - buffer (StringBuilder. 100) |
272 |
| - {:keys [dirty?]} (collect-markup {:stack [] |
273 |
| - :active initial-font |
274 |
| - :current initial-font |
| 275 | + (let [buffer (StringBuilder. 100) |
| 276 | + {:keys [dirty?]} (collect-markup {:active {} |
| 277 | + :current {} |
275 | 278 | :buffer buffer}
|
276 | 279 | inputs)]
|
277 | 280 | (when dirty?
|
|
289 | 292 | `map` or `for` to be mixed into the composed string seamlessly.
|
290 | 293 |
|
291 | 294 | Nested vectors represent _spans_, a sequence of values with a specific visual representation.
|
292 |
| - The first element in a span vector declares the visual properties of the span: the color (including |
293 |
| - other characteristics such as bold or underline), and the width and padding (described later). |
| 295 | + The first element in a span vector declares the visual properties of the span: the font color |
| 296 | + and other font characteristics, and the width and padding (described later). |
294 | 297 | Spans may be nested.
|
295 | 298 |
|
296 | 299 | The declaration is usually a keyword, to define just the font.
|
|
322 | 325 | Font defs apply on top of the font def of the enclosing span, and the outer span's font def
|
323 | 326 | is restored at the end of the inner span, e.g. `[:red \" RED \" [:bold \"RED/BOLD\"] \" RED \"]`.
|
324 | 327 |
|
325 |
| - Alternately, a font def may be a vector of individual keyword, e.g., `[[:bold :red] ...]` rather than |
| 328 | + Alternately, a font def may be a vector of individual keywords, e.g., `[[:bold :red] ...]` rather than |
326 | 329 | `[:bold.red ...]`. This works better when the exact font characteristics are determined
|
327 | 330 | dynamically.
|
328 | 331 |
|
|
370 | 373 | [& inputs]
|
371 | 374 | (compose* inputs))
|
372 | 375 |
|
373 |
| -(defn pcompose |
| 376 | +(defn pout |
374 | 377 | "Composes its inputs as with [[compose]] and then prints the results, with a newline."
|
375 |
| - {:added "2.2"} |
| 378 | + {:added "3.2"} |
| 379 | + [& inputs] |
| 380 | + (println (compose* inputs))) |
| 381 | + |
| 382 | +(defn pcompose |
| 383 | + "Composes its inputs as with [[compose]] and then prints the results, with a newline. |
| 384 | +
|
| 385 | + Deprecated: use [[pout]] instead." |
| 386 | + {:added "2.2" |
| 387 | + :deprecated "3.2.0"} |
376 | 388 | [& inputs]
|
377 | 389 | (println (compose* inputs)))
|
378 | 390 |
|
|
382 | 394 | [& inputs]
|
383 | 395 | (binding [*out* *err*]
|
384 | 396 | (println (compose* inputs))))
|
| 397 | + |
| 398 | + |
0 commit comments