diff --git a/README.md b/README.md
index 497e339..826b7b1 100644
--- a/README.md
+++ b/README.md
@@ -15,15 +15,7 @@ Add this to your project.clj :dependencies list:
In most cases, you'll just want to use/require the table function.
```clojure
-> (use '[doric.core :only [table]])
-nil
-```
-
-But you can access other things if you'd like, for instance if you
-want to use the other formats.
-
-```clojure
-> (use '[doric.core :only [table csv html org raw]])
+> (require '[doric.core :refer [table]])
nil
```
@@ -59,19 +51,19 @@ nil
But you can also have raw, csv, and html tables pretty easily:
```clojure
-> (println (table {:format raw} [:a :b :c] [{:a 1 :b 2 :c 3}{:a 4 :b 5 :c 6}]))
+> (println (table {:format :raw} [:a :b :c] [{:a 1 :b 2 :c 3}{:a 4 :b 5 :c 6}]))
A B C
1 2 3
4 5 6
nil
-> (println (table {:format csv} [:a :b :c] [{:a 1 :b 2 :c 3}{:a 4 :b 5 :c 6}]))
+> (println (table {:format :csv} [:a :b :c] [{:a 1 :b 2 :c 3}{:a 4 :b 5 :c 6}]))
A,B,C
1,2,3
4,5,6
nil
-> (println (table {:format html} [{:a 1 :b 2 :c 3}{:a 4 :b 5 :c 6}]))
+> (println (table {:format :html} [{:a 1 :b 2 :c 3}{:a 4 :b 5 :c 6}]))
;; omg lots of
s and s here
```
@@ -79,7 +71,7 @@ You can also use a custom table format by specifying a namespace that
contains the functions th, td, and render.
```clojure
-> (println (table {:format 'my.sweet.ns} [{:a 1 :b 2 :c 3}{:a 4 :b 5 :c 6}]))
+> (println (table {:format thing-that-implements-Render} [{:a 1 :b 2 :c 3}{:a 4 :b 5 :c 6}]))
;; the sky's the limit, buddy
```
@@ -145,9 +137,9 @@ are displayed. For example, there's an included bar function for
creating text bar charts:
```clojure
-> (use '[doric.core :only [bar]])
+> (require '[doric.core :refer [table bar]])
nil
-> (println (table {:format raw} [:a :b {:name :c :format bar}]
+> (println (table {:format :raw} [:a :b {:name :c :format bar}]
[{:a 1 :b 2 :c 3}{:a 4 :b 5 :c 6}]))
A B C
1 2 ###
diff --git a/project.clj b/project.clj
index 9b78edb..f848503 100644
--- a/project.clj
+++ b/project.clj
@@ -3,10 +3,10 @@
:url "https://github.com/joegallo/doric"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
- :profiles {:1.2 {:dependencies [[org.clojure/clojure "1.2.1"]]}
- :1.3 {:dependencies [[org.clojure/clojure "1.3.0"]]}
- :1.4 {:dependencies [[org.clojure/clojure "1.4.0"]]}
- :1.5 {:dependencies [[org.clojure/clojure "1.5.1"]]}
- :dev {:dependencies [[org.clojure/clojure "1.6.0"]
+ :dependencies [[cheshire "5.7.0"]]
+ :profiles {:1.6 {:dependencies [[org.clojure/clojure "1.6.0"]]}
+ :1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]}
+ :1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]}
+ :dev {:dependencies [[org.clojure/clojure "1.9.0-alpha15"]
[org.apache.poi/poi "3.10.1"]]}}
- :aliases {"all" ["with-profile" "dev,1.2:dev,1.3:dev,1.4:dev,1.5:dev"]})
+ :aliases {"all" ["with-profile" "dev,1.6:dev,1.7:dev,1.8:dev"]})
diff --git a/src/doric/core.clj b/src/doric/core.clj
index f3f67f4..c350ab4 100644
--- a/src/doric/core.clj
+++ b/src/doric/core.clj
@@ -1,135 +1,55 @@
(ns doric.core
- (:refer-clojure :exclude [format name join split when])
- (:use [clojure.string :only [join split]]))
-
-(defn- title-case-word [w]
- (if (zero? (count w))
- w
- (str (Character/toTitleCase (first w))
- (subs w 1))))
-
-(defn title-case [s]
- (join " " (map title-case-word (split s #"\s"))))
-
-(defn align [col & [data]]
- (or (keyword (:align col))
- :left))
-
-(defn format [col & [data]]
- (or (:format col)
- identity))
-
-(defn title [col & [data]]
- (or (:title col)
- (title-case
- (.replaceAll (clojure.core/name (let [n (:name col)]
- (if (number? n)
- (str n)
- n)))
- "-" " "))))
-
-(defn title-align [col & [data]]
- (keyword (or (:title-align col)
- (:align col)
- :center)))
-
-(defn when [col & [data]]
- (:when col true))
-
-(defn width [col & [data]]
- (or (:width col)
- (apply max (map count (cons (:title col)
- (map str data))))))
-
-(defn format-cell [col s]
- ((:format col) s))
-
-(defn align-cell [col s align]
- (let [width (:width col)
- s (str s)
- s (cond (<= (count s) width) s
- (:ellipsis col) (str (subs s 0 (- width 3)) "...")
- :else (subs s 0 width))
- len (count s)
- pad #(apply str (take % (repeat " ")))
- padding (- width len)
- half-padding (/ (- width len) 2)]
- (case align
- :left (str s (pad padding))
- :right (str (pad padding) s)
- :center (str (pad (Math/ceil half-padding))
- s
- (pad (Math/floor half-padding))))))
-
-(defn header [th cols]
- (for [col cols :when (:when col)]
- (th col)))
-
-(defn body [td cols rows]
- (for [row rows]
- (for [col cols :when (:when col)]
- (td col row))))
-
-(defn- col-data [col rows]
- (map #(get % (:name col)) rows))
-
-(defn- column1 [col & [data]]
- {:align (align col data)
- :format (format col data)
- :title (title col data)
- :title-align (title-align col data)
- :when (when col data)})
-
-(defn- column-map [col]
+ (:require [doric.formatting :refer [titleize]]
+ [doric.protocols :refer [render render-lazy]]
+ [clojure.string :as str]
+ [doric.org]
+ [doric.raw]
+ [doric.html]
+ [doric.csv]
+ [doric.json]
+ [doric.unicode]))
+
+(defn column-defaults [col]
+ (merge col
+ {:align (keyword (get col :align :left))
+ :format (or (:format col)
+ identity)
+ :title (or (:title col)
+ (titleize (:name col)))
+ :title-align (keyword (or (:title-align col)
+ (:align col)
+ :center))
+ :when (:when col true)}))
+
+(defn column-map [col]
(if (map? col)
col
{:name col}))
-(defn- columns1 [cols rows]
- (for [col cols :let [col (column-map col)]]
- (merge col
- (column1 col (col-data col rows)))))
-
-(defn- format-rows [cols rows]
- (for [row rows]
- (into {}
- (for [col cols :let [name (:name col)]]
- [name (format-cell col (row name))]))))
-
-(defn- column2 [col & [data]]
- {:width (width col data)})
-
-(defn- columns2 [cols rows]
- (for [col cols]
- (merge col
- (column2 col (col-data col rows)))))
+(def columnize (comp column-defaults column-map))
-;; data formats
(defn bar [x]
(apply str (repeat x "#")))
-;; table formats
-(def csv 'doric.csv)
-(def html 'doric.html)
-(def org 'doric.org)
-(def raw 'doric.raw)
-
-;; table format helpers
-;; aligned th and td are useful for whitespace sensitive formats, like
-;; raw and org
-(defn aligned-th [col]
- (align-cell col (:title col) (:title-align col)))
-
-(defn aligned-td [col row]
- (align-cell col (row (:name col)) (:align col)))
-
-;; unalighed-th and td are useful for whitespace immune formats, like
-;; csv and html
-(defn unaligned-th [col]
- (:title col))
+(defn format-rows [cols rows]
+ (for [row rows]
+ (reduce
+ (fn [m {:keys [name format] :as col}]
+ (assoc m name
+ (-> row
+ (get name)
+ format)))
+ {}
+ cols)))
-(defn unaligned-td [col row]
- (row (:name col)))
+;; table formats
+(def renderers {:csv doric.csv/renderer
+ :html doric.html/renderer
+ :org doric.org/renderer
+ :raw doric.raw/renderer
+ :unicode doric.unicode/renderer
+ :json (doric.json/make-renderer)
+ :json-pretty (doric.json/make-renderer true)})
(defn mapify [rows]
(let [example (first rows)]
@@ -140,34 +60,47 @@
(map-indexed (fn [i x] [i x]) row)))
(map? example) rows)))
-(defn table*
- {:arglists '[[rows]
- [opts rows]
- [cols rows]
- [opts cols rows]]}
- [& args]
- (let [rows (mapify (last args))
+(defn conform
+ "Given an optional colspec and a sequence of maps, returns map with
+ keys :cols, :rows"
+ ([rows]
+ (conform nil rows))
+ ([cols rows]
+ (let [rows (mapify rows)
+ cols (filter :when
+ (map columnize (or cols
+ (keys (first rows)))))
+ rows (format-rows cols rows)]
+ {:cols cols, :rows rows})))
+
+(defn -parse-args
+ [args]
+ (let [rows (last args)
[opts cols] (case (count args)
1 [nil nil]
2 (if (map? (first args))
[(first args) nil]
[nil (first args)])
3 [(first args) (second args)])
- cols (or cols (keys (first rows)))
- format (or (:format opts) org)
- _ (require format)
- th (ns-resolve format 'th)
- td (ns-resolve format 'td)
- render (ns-resolve format 'render)
- cols (columns1 cols rows)
- rows (format-rows cols rows)
- cols (columns2 cols rows)]
- (render (cons (header th cols) (body td cols rows)))))
+ format (or (:format opts) :org)
+ renderer (renderers format format)
+ cols-rows (conform cols rows)]
+ (merge {:renderer renderer} cols-rows)))
+
+(defn table*
+ {:arglists '[[rows]
+ [opts rows]
+ [cols rows]
+ [opts cols rows]]}
+ [& args]
+ (let [{:keys [renderer cols rows]} (-parse-args args)]
+ (render-lazy renderer cols rows)))
(defn table
{:arglists '[[rows]
[opts rows]
[cols rows]
- [otps cols rows]]}
+ [opts cols rows]]}
[& args]
- (apply str (join "\n" (apply table* args))))
+ (let [{:keys [renderer cols rows]} (-parse-args args)]
+ (render renderer cols rows)))
diff --git a/src/doric/csv.clj b/src/doric/csv.clj
index 2e396b1..d2693d9 100644
--- a/src/doric/csv.clj
+++ b/src/doric/csv.clj
@@ -1,11 +1,9 @@
(ns doric.csv
- (:refer-clojure :exclude [join])
- (:use [clojure.string :only [join]]
- [doric.core :only [unaligned-th unaligned-td]]))
+ (:require [clojure.string :as str]
+ [doric.tabular :refer [tabular-renderer
+ unaligned-td
+ unaligned-th]]))
-(def th unaligned-th)
-
-(def td unaligned-td)
(defn escape [s]
(let [s (.replaceAll (str s) "\"" "\"\"")]
@@ -13,7 +11,12 @@
(str "\"" s "\"")
s)))
-(defn render [table]
- (cons (join "," (map escape (first table)))
- (for [tr (rest table)]
- (join "," (map escape tr)))))
+(defn assemble [rows]
+ (cons (str/join "," (first rows))
+ (for [tr (rest rows)]
+ (str/join "," tr))))
+
+(def renderer (tabular-renderer {:th (comp escape unaligned-th)
+ :td (comp escape unaligned-td)
+ :assemble assemble
+ :escape escape}))
diff --git a/src/doric/formatting.clj b/src/doric/formatting.clj
new file mode 100644
index 0000000..9bc9aff
--- /dev/null
+++ b/src/doric/formatting.clj
@@ -0,0 +1,44 @@
+(ns doric.formatting
+ (:require [clojure.string :as str]))
+
+(defn string ^String [elem]
+ (str (cond
+ (keyword? elem) (name elem)
+ (symbol? elem) (name elem)
+ :else elem)))
+
+(defn- title-case-word [w]
+ (if (zero? (count w))
+ w
+ (str (Character/toTitleCase (first w))
+ (subs w 1))))
+
+(defn title-case [^String s]
+ (str/join " " (map title-case-word (str/split s #"\s"))))
+
+(defn titleize [n]
+ (title-case
+ (.replaceAll (string n) "-" " ")))
+
+(defn escape [s]
+ (if (string? s)
+ (str/escape s {\newline "\\n"
+ \tab "\\t"})
+ s))
+
+(defn align-cell [col s align]
+ (let [width (:width col)
+ s (str s)
+ s (cond (<= (count s) width) s
+ (:ellipsis col) (str (subs s 0 (- width 3)) "...")
+ :else (subs s 0 width))
+ len (count s)
+ pad #(apply str (take % (repeat " ")))
+ padding (- width len)
+ half-padding (/ (- width len) 2)]
+ (case align
+ :left (str s (pad padding))
+ :right (str (pad padding) s)
+ :center (str (pad (Math/ceil half-padding))
+ s
+ (pad (Math/floor half-padding))))))
diff --git a/src/doric/html.clj b/src/doric/html.clj
index 9eb0d91..ca84e93 100644
--- a/src/doric/html.clj
+++ b/src/doric/html.clj
@@ -1,17 +1,24 @@
(ns doric.html
- (:refer-clojure :exclude [join])
- (:use [clojure.string :only [join]]
- [doric.core :only [unaligned-th unaligned-td]]))
+ (:require [clojure.string :as str]
+ [doric.tabular :refer [tabular-renderer
+ unaligned-td
+ unaligned-th]]))
+(defn escape [^String s]
+ (-> s
+ (str/replace "&" "&")
+ (str/replace "<" "<")
+ (str/replace ">" ">")))
-(def th unaligned-th)
-
-(def td unaligned-td)
-
-(defn render [table]
+(defn assemble [rows]
(concat [""
- (str "" (join (for [c (first table)]
+ (str " " (str/join (for [c (first rows)]
(str "" c " | "))) " ")]
- (for [tr (rest table)]
- (str "" (join (for [c tr]
+ (for [tr (rest rows)]
+ (str " " (str/join (for [c tr]
(str "" c " | "))) " "))
[" "]))
+
+(def renderer (tabular-renderer {:th unaligned-th
+ :td unaligned-td
+ :assemble assemble
+ :escape escape}))
diff --git a/src/doric/json.clj b/src/doric/json.clj
new file mode 100644
index 0000000..4505da2
--- /dev/null
+++ b/src/doric/json.clj
@@ -0,0 +1,16 @@
+(ns doric.json
+ (:require [cheshire.core :as json]
+ [doric.protocols :as proto]))
+
+(defrecord JSONRenderer [cheshire-opts]
+ proto/Render
+ (-render-lazy [_ cols data]
+ (map #(json/generate-string % cheshire-opts) data))
+ (-render [_ cols data]
+ (json/generate-string data cheshire-opts)))
+
+(defn make-renderer
+ ([]
+ (make-renderer false))
+ ([pretty]
+ (->JSONRenderer {:pretty pretty})))
diff --git a/src/doric/org.clj b/src/doric/org.clj
index bf17aab..2005247 100644
--- a/src/doric/org.clj
+++ b/src/doric/org.clj
@@ -1,21 +1,24 @@
(ns doric.org
- (:refer-clojure :exclude [join])
- (:use [clojure.string :only [join]]
- [doric.core :only [aligned-th aligned-td]]))
+ (:require [clojure.string :as str]
+ [doric.formatting :refer [escape]]
+ [doric.tabular :refer [tabular-renderer
+ aligned-th
+ aligned-td]]))
-(def th aligned-th)
-
-(def td aligned-td)
-
-(defn render [table]
+(defn assemble [rows]
(let [spacer (str "|-"
- (join "-+-"
- (map #(apply str (repeat (.length %) "-"))
- (first table)))
+ (str/join "-+-"
+ (map #(apply str (repeat (.length %) "-"))
+ (first rows)))
"-|")]
(concat [spacer
- (str "| " (join " | " (first table)) " |")
+ (str "| " (str/join " | " (first rows)) " |")
spacer]
- (for [tr (rest table)]
- (str "| " (join " | " tr) " |"))
+ (for [tr (rest rows)]
+ (str "| " (str/join " | " tr) " |"))
[spacer])))
+
+(def renderer (tabular-renderer {:th aligned-th
+ :td aligned-td
+ :assemble assemble
+ :escape escape}))
diff --git a/src/doric/protocols.clj b/src/doric/protocols.clj
new file mode 100644
index 0000000..7abf88a
--- /dev/null
+++ b/src/doric/protocols.clj
@@ -0,0 +1,11 @@
+(ns doric.protocols)
+
+(defprotocol Render
+ (-render-lazy [_ cols data])
+ (-render [_ cols data]))
+
+(defn render-lazy [renderer cols data]
+ (-render-lazy renderer cols data))
+
+(defn render [renderer cols data]
+ (-render renderer cols data))
diff --git a/src/doric/raw.clj b/src/doric/raw.clj
index 8d8bd4b..15ba91a 100644
--- a/src/doric/raw.clj
+++ b/src/doric/raw.clj
@@ -1,13 +1,14 @@
(ns doric.raw
- (:refer-clojure :exclude [join])
- (:use [clojure.string :only [join]]
- [doric.core :only [aligned-th aligned-td]]))
+ (:require [clojure.string :as str]
+ [doric.tabular :refer [tabular-renderer
+ aligned-th
+ aligned-td]]))
-(def th aligned-th)
+(defn assemble [rows]
+ (cons (str/join " " (first rows))
+ (for [tr (rest rows)]
+ (str/join " " tr))))
-(def td aligned-td)
-
-(defn render [table]
- (cons (join " " (first table))
- (for [tr (rest table)]
- (join " " tr))))
+(def renderer (tabular-renderer {:th aligned-th
+ :td aligned-td
+ :assemble assemble}))
diff --git a/src/doric/tabular.clj b/src/doric/tabular.clj
new file mode 100644
index 0000000..fdd3f7c
--- /dev/null
+++ b/src/doric/tabular.clj
@@ -0,0 +1,59 @@
+(ns doric.tabular
+ (:require [clojure.string :as str]
+ [doric.protocols :refer :all]
+ [doric.formatting :refer [align-cell]]))
+
+(defn calculate-width
+ ([col rows]
+ (calculate-width col rows identity))
+ ([{:keys [title name]} rows escape]
+ (->> rows
+ (map (comp escape str #(get % name)))
+ (cons (escape title))
+ (map count)
+ (apply max))))
+
+(defn columns-with-widths [escape cols rows]
+ (for [{:keys [width] :as col} cols]
+ (assoc col
+ :width (or width
+ (calculate-width col rows escape)))))
+
+(defrecord TabularRender [th td assemble escape]
+ Render
+ (-render [this cols data]
+ (str/join "\n"
+ (-render-lazy this cols data)))
+ (-render-lazy [_ cols data]
+ (let [cols (columns-with-widths escape cols data)]
+ (assemble
+ (cons (for [col cols]
+ (th col (escape (:title col))))
+ (for [row data]
+ (for [col cols]
+ (td col (escape (get row (:name col)))))))))))
+
+;; table format helpers
+
+;; unalighed-th and td are useful for whitespace immune formats, like
+;; csv and html
+(defn unaligned-th [_ data] data)
+(defn unaligned-td [_ data] data)
+
+;; aligned th and td are useful for whitespace sensitive formats, like
+;; raw and org
+(defn aligned-th [col cell-data]
+ (align-cell col
+ cell-data
+ (:title-align col)))
+
+(defn aligned-td [col cell-data]
+ (align-cell col
+ cell-data
+ (:align col)))
+
+(defn tabular-renderer [{:keys [td th assemble escape]}]
+ (map->TabularRender {:td (or td unaligned-td)
+ :th (or th unaligned-th)
+ :escape (or escape identity)
+ :assemble assemble}))
diff --git a/src/doric/unicode.clj b/src/doric/unicode.clj
new file mode 100644
index 0000000..5537728
--- /dev/null
+++ b/src/doric/unicode.clj
@@ -0,0 +1,25 @@
+(ns doric.unicode
+ (:require [clojure.string :as str]
+ [doric.formatting :refer [escape]]
+ [doric.tabular :refer [tabular-renderer
+ aligned-th
+ aligned-td]]))
+
+(defn assemble [rows]
+ (let [spacer (fn [l c r]
+ (str l
+ (str/join c
+ (map #(apply str (repeat (.length %) "─"))
+ (first rows)))
+ r))]
+ (concat [(spacer "┌─" "─┬─" "─┐")
+ (str "│ " (str/join " │ " (first rows)) " │")
+ (spacer "├─" "─┼─" "─┤")]
+ (for [tr (rest rows)]
+ (str "│ " (str/join " │ " tr) " │"))
+ [(spacer "└─" "─┴─" "─┘")])))
+
+(def renderer (tabular-renderer {:th aligned-th
+ :td aligned-td
+ :assemble assemble
+ :escape escape}))
diff --git a/test/doric/test/core.clj b/test/doric/test/core.clj
index 43e75ed..240d63f 100644
--- a/test/doric/test/core.clj
+++ b/test/doric/test/core.clj
@@ -1,34 +1,14 @@
(ns doric.test.core
- (:refer-clojure :exclude [format name when])
- (:use [doric.core]
- [clojure.test]
- [doric.org :only [th td render]]))
+ (:require [clojure.test :refer :all]
+ [cheshire.core :as json]
+ [doric.core :refer :all]
+ [doric.org :refer [assemble]]))
-(deftest test-title-case
- (is (= "Foo" (title-case "foo")))
- (is (= "Foo-bar" (title-case "foo-bar")))
- (is (= "Foo Bar" (title-case "foo bar")))
- (is (= "Foo Bar" (title-case "foo bar"))))
-
-(deftest test-align
- (is (= :left (align {})))
- (is (= :right (align {:align :right}))))
-
-(deftest test-format
- (is (= identity (format {})))
- (is (= str (format {:format str}))))
-
-(deftest test-title
- (is (= "foo" (title {:title "foo"})))
- (is (= "Foo" (title {:name "foo"}))))
-
-(deftest test-title-align
- (is (= :center (title-align {})))
- (is (= :left (title-align {:align :left})))
- (is (= :left (title-align {:align 'left})))
- (is (= :left (title-align {:align "left"})))
- (is (= :right (title-align {:align :left :title-align :right})))
- (is (= :right (title-align {:align :left :title-align :right}))))
+(deftest test-column-defaults
+ (is (= "foo" (:title (columnize {:title "foo"}))))
+ (is (= "Foo" (:title (columnize {:name "foo"}))))
+ (is (= "Foo Bar" (:title (columnize {:name "foo bar"}))))
+ (is (= "Foo" (:title (columnize :foo)))))
(deftest test-when
(is (re-find #"Foo" (table [{:name :foo}] [{:foo :bar}])))
@@ -38,51 +18,66 @@
(is (not (re-find #"Foo" (table [{:name :foo :when false}] [{:foo :bar}]))))
(is (not (re-find #"bar" (table [{:name :foo :when false}] [{:foo :bar}])))))
-(deftest test-width
- (is (= 5 (width {:width 5})))
- (is (= 5 (width {:width 5 :name :foobar})))
- (is (= 7 (width {:name :foobar} ["foobar2"]))))
-
-(deftest test-format-cell
- (is (= 2 (format-cell {:format inc} 1))))
-
-(deftest test-align-cell
- (is (= "." (align-cell {:width 1} "." :left)))
- (is (= "." (align-cell {:width 1} "." :center)))
- (is (= "." (align-cell {:width 1} "." :right)))
- (is (= ". " (align-cell {:width 3} "." :left)))
- (is (= " . " (align-cell {:width 3} "." :center)))
- (is (= " ." (align-cell {:width 3} "." :right)))
- (is (= ". " (align-cell {:width 4} "." :left)))
- (is (= " . " (align-cell {:width 4} "." :center)))
- (is (= " ." (align-cell {:width 4} "." :right))))
-
-(deftest test-th
- (is (= "Title " (th {:title "Title" :width 7 :title-align :left})))
- (is (= " Title " (th {:title "Title" :width 7 :title-align :center})))
- (is (= " Title" (th {:title "Title" :width 7 :title-align :right}))))
-
-(deftest test-td
- (is (= ". " (td {:name :t :width 3 :align :left} {:t "."})))
- (is (= " . " (td {:name :t :width 3 :align :center} {:t "."})))
- (is (= " ." (td {:name :t :width 3 :align :right} {:t "."}))))
-
;; TODO (deftest test-header)
-;; TODO (deftest test-body)
-
-(deftest test-render
- (let [rendered (render [["1" "2"]["3" "4"]])]
+(deftest test-assemble
+ (let [rendered (assemble [["1" "2"]["3" "4"]])]
(is (.contains rendered "| 1 | 2 |"))
(is (.contains rendered "| 3 | 4 |"))
(is (.contains rendered "|---+---|"))))
;; TODO embiggen these tests
(deftest test-table
- (let [rendered (table [{:1 3 :2 4}])]
- (is (.contains rendered "| 1 | 2 |"))
- (is (.contains rendered "| 3 | 4 |"))
- (is (.contains rendered "|---+---|"))))
+ (let [rendered (table* [{:1 3 :2 4}])]
+ (is (= rendered
+ ["|---+---|"
+ "| 1 | 2 |"
+ "|---+---|"
+ "| 3 | 4 |"
+ "|---+---|"]))))
+
+(deftest test-render-vectors
+ (let [rendered (table* [[1 2] ["3" "4"] [:a :b]])]
+ (is (= rendered
+ ["|----+----|"
+ "| 0 | 1 |"
+ "|----+----|"
+ "| 1 | 2 |"
+ "| 3 | 4 |"
+ "| :a | :b |"
+ "|----+----|"]))))
+
+(deftest test-unicode-table
+ (let [rendered (table* {:format :unicode}
+ [{:name :foo} {:name :bar :width 9}]
+ [{:foo "what" :bar 4}
+ {:foo "who" :bar 87}])]
+ (is (= rendered
+ ["┌──────┬───────────┐"
+ "│ Foo │ Bar │"
+ "├──────┼───────────┤"
+ "│ what │ 4 │"
+ "│ who │ 87 │"
+ "└──────┴───────────┘"]))))
+
+(deftest test-escaping
+ (let [rendered (table* [:a :b] [{:a "foo\nbar" :b "what\tever"}])]
+ (is (= rendered
+ ["|----------+------------|"
+ "| A | B |"
+ "|----------+------------|"
+ "| foo\\nbar | what\\tever |" ;; lines up when printed
+ "|----------+------------|"]))))
+
+(deftest test-escaping-html
+ (let [rendered (table* {:format :html}
+ [:a :b]
+ [{:a "foo < bar" :b "what & ever"}])]
+ (is (= rendered
+ [""
+ "A | B | "
+ "foo < bar | what & ever | "
+ " "]))))
(deftest test-table*-laziness
(let [calls (atom 0)
@@ -100,12 +95,30 @@
(is (= 0 @calls))))
(reset! calls 0)
(testing "even for formats that should be automatically lazy, like csv"
- (let [seq (table* ^{:format csv}
+ (let [seq (table* {:format :csv}
[{:name :1 :format inc :width 0}
{:name :2 :format inc :width 0}]
[{:1 3 :2 4}])]
(is (= 0 @calls))))))
+(deftest test-json
+ (let [data [{:a 1 :b "2"} {:a 2 :b "42"}]
+ out (table {:format :json} data)]
+ (is (= data (json/parse-string out true)))))
+
+(deftest test-json-format-and-when
+ (let [data [{:a "1" :b 2} {:a "2" :b 42}]
+ out (table {:format :json}
+ [{:name :a :when false} {:name :b :format inc}]
+ data)]
+ (is (= [{:b 3} {:b 43}]
+ (json/parse-string out true)))))
+
+(deftest test-json-table*
+ (let [data [{:a 1 :b "2"} {:a 2 :b "42"}]
+ out (table* {:format :json} data)]
+ (is (= data (map #(json/parse-string % true) out)))))
+
(deftest test-empty-table
(let [empty-table "|--|\n| |\n|--|\n|--|"]
(is (= empty-table (table [])))
diff --git a/test/doric/test/formatting.clj b/test/doric/test/formatting.clj
new file mode 100644
index 0000000..a225b6a
--- /dev/null
+++ b/test/doric/test/formatting.clj
@@ -0,0 +1,20 @@
+(ns doric.test.formatting
+ (:require [doric.formatting :refer :all]
+ [clojure.test :refer :all]))
+
+(deftest test-title-case
+ (is (= "Foo" (title-case "foo")))
+ (is (= "Foo-bar" (title-case "foo-bar")))
+ (is (= "Foo Bar" (title-case "foo bar")))
+ (is (= "Foo Bar" (title-case "foo bar"))))
+
+(deftest test-align-cell
+ (is (= "." (align-cell {:width 1} "." :left)))
+ (is (= "." (align-cell {:width 1} "." :center)))
+ (is (= "." (align-cell {:width 1} "." :right)))
+ (is (= ". " (align-cell {:width 3} "." :left)))
+ (is (= " . " (align-cell {:width 3} "." :center)))
+ (is (= " ." (align-cell {:width 3} "." :right)))
+ (is (= ". " (align-cell {:width 4} "." :left)))
+ (is (= " . " (align-cell {:width 4} "." :center)))
+ (is (= " ." (align-cell {:width 4} "." :right))))
diff --git a/test/doric/test/tabular.clj b/test/doric/test/tabular.clj
new file mode 100644
index 0000000..ee5278d
--- /dev/null
+++ b/test/doric/test/tabular.clj
@@ -0,0 +1,20 @@
+(ns doric.test.tabular
+ (:require [doric.tabular :refer :all]
+ [clojure.test :refer :all]))
+
+(deftest test-aligned-th
+ (is (= "Title " (aligned-th {:width 7 :title-align :left} "Title")))
+ (is (= " Title " (aligned-th {:width 7 :title-align :center} "Title")))
+ (is (= " Title" (aligned-th {:width 7 :title-align :right} "Title"))))
+
+(deftest test-aligned-td
+ (is (= ". " (aligned-td {:width 3 :align :left} ".")))
+ (is (= " . " (aligned-td {:width 3 :align :center} ".")))
+ (is (= " ." (aligned-td {:width 3 :align :right} "."))))
+
+(deftest test-calculate-width
+ (is (= 9 (calculate-width {:title "TitleCase" :name :a} ["hi"])))
+ (is (= 8 (calculate-width {:title "Title" :name :a} [{:a "whatever"}
+ {:a "is"}
+ {:a "largest"}])))
+ (is (= 7 (calculate-width {:name :foobar} [{:foobar "foobar2"}]))))
|