Skip to content

Commit 902737c

Browse files
committed
Add excel macro to capture output from print-table et al
1 parent 6001234 commit 902737c

File tree

2 files changed

+111
-5
lines changed

2 files changed

+111
-5
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Lein:
1818
- [Tables](#tables)
1919
- [Trees](#trees)
2020
- [PDF Generation](#pdf-generation)
21+
- [Redirecting output from print-table](#redirecting-output-from-print-table)
2122
- [Style & Cell Merging](#style-&-cell-merging)
2223
- [What are the options for styling?](#what-are-the-options-for-styling?)
2324
- [Grids](#grids)
@@ -128,6 +129,27 @@ generation works the same was as creating a spreadsheet:
128129

129130
![A PDF is opened](resources/quick-open-pdf.png)
130131

132+
### Redirecting output from print-table
133+
134+
For convenience, there's an `excel` macro which allows you to wrap forms which
135+
call into `clojure.pprint.print-table` or `excel-clj.tree/print-table` and
136+
capture their output in an Excel workbook instead of printing to stdout.
137+
138+
Useful for situations where you would ordinarily print data to the REPL, but
139+
sometimes want to generate an Excel file instead.
140+
141+
```clojure
142+
(excel-clj.core/excel
143+
(clojure.pprint/print-table
144+
(map
145+
(fn [i] {"Ch" (char i) "i" i})
146+
(range 33 43)))
147+
148+
(excel-clj.tree/print-table
149+
(excel-clj.tree/table
150+
excel-clj.tree/mock-balance-sheet)))
151+
```
152+
131153
### Style & Cell Merging
132154

133155
Each workbook is a map: `{sheet-name [[cell]]}`. Each `cell` is either

src/excel_clj/core.clj

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010
1111
See the (comment) form with examples at the bottom of this namespace."
1212
{:author "Matthew Downey"}
13-
(:require [excel-clj.cell :refer [style data dims wrapped?]]
13+
(:require [clojure.pprint :as pprint]
14+
[clojure.string :as string]
15+
16+
[excel-clj.cell :refer [data dims style wrapped?]]
17+
[excel-clj.deprecated :as deprecated]
1418
[excel-clj.file :as file]
1519
[excel-clj.tree :as tree]
16-
[excel-clj.deprecated :as deprecated]
17-
18-
[clojure.string :as string]
1920

2021
[taoensso.tufte :as tufte])
2122
(:import (clojure.lang Named)
@@ -53,7 +54,6 @@
5354

5455
:else nil)))
5556

56-
5757
(defn table-grid
5858
"Build a lazy sheet grid from `rows`.
5959
@@ -374,6 +374,90 @@
374374
(file/quick-open-pdf! workbook))
375375

376376

377+
;; Convenience macro to redirect print-table / print-tree to excel
378+
379+
380+
(defonce ^:private var->excel-rebinding (atom {}))
381+
382+
383+
(defn declare-excelable!
384+
"Redefine some function's `var` to generate Excel output when enclosed in an
385+
`excel` macro.
386+
387+
The `fn` returns a grid (optionally with :excel/sheet-name metadata)."
388+
[var fn]
389+
(swap! var->excel-rebinding assoc var fn))
390+
391+
392+
(declare-excelable! #'pprint/print-table
393+
(fn ;; This fn has the same signature as the var it's redefining
394+
([rows] (vary-meta (table-grid rows) merge (meta rows)))
395+
([ks rows] (vary-meta (table-grid ks rows) merge (meta rows)))))
396+
397+
398+
(declare-excelable! #'tree/print-table
399+
(fn this
400+
([rows]
401+
(this
402+
(into [""] (remove #{"" :tree/indent}) (keys (data (first rows))))
403+
rows))
404+
([ks rows]
405+
(vary-meta
406+
(table-grid ks
407+
(map
408+
(fn [{:keys [tree/indent] :as row}]
409+
(update row "" #(str (apply str (repeat (or indent 0) " ")) %)))
410+
rows))
411+
merge (meta rows)))))
412+
413+
414+
(defn -build-excel-rebindings [wb-atom var->excel-rebinding]
415+
(letfn [(conj-page [sheets contents]
416+
(let [sheet-name (or (:excel/sheet-name (meta contents))
417+
(str "Sheet" (inc (count sheets))))]
418+
(conj sheets [sheet-name contents])))
419+
(conj-page! [contents] (swap! wb-atom conj-page contents))]
420+
(update-vals var->excel-rebinding
421+
(fn [grid-fn] (comp conj-page! grid-fn)))))
422+
423+
424+
(defmacro excel
425+
"Build an Excel workbook with whatever data is emitted during the execution
426+
of `body` from functions on which `declare-excelable!` has been called.
427+
428+
If the first argument is a compile-time map, it may contain a :hook function
429+
to be called with the final workbook. If no hook is passed, it defaults to
430+
`quick-open!`.
431+
432+
(Compatible by default for `clojure.pprint/print-table` and
433+
`excel-clj.tree/print-table`.)
434+
435+
Returns the return value of `body`."
436+
[& body]
437+
(let [[opts body] (if (map? (first body))
438+
[(first body) (rest body)]
439+
[{} body])
440+
hook (or (:hook opts) quick-open!)]
441+
`(let [wb# (atom [])]
442+
(with-redefs-fn (-build-excel-rebindings wb# ~(deref var->excel-rebinding))
443+
(fn []
444+
(let [ret# (do ~@body)]
445+
(~hook (apply array-map (mapcat identity @wb#)))
446+
ret#))))))
447+
448+
449+
(comment
450+
;; For example
451+
(excel
452+
(do
453+
;; Print a table to one sheet
454+
(pprint/print-table (map (fn [i] {"Ch" (char i) "i" i}) (range 33 43)))
455+
;; And a tree to another
456+
(let [tbl (tree/table (tree/combined-header) tree/mock-balance-sheet)]
457+
(tree/print-table tbl))
458+
:ok)))
459+
460+
377461
;; Some v1.X backwards compatibility
378462

379463

0 commit comments

Comments
 (0)