Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

So many changes #4

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
22 changes: 7 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -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,27 +51,27 @@ 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 <tr>s and <td>s here
```

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 ###
12 changes: 6 additions & 6 deletions project.clj
Original file line number Diff line number Diff line change
@@ -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"]})
215 changes: 74 additions & 141 deletions src/doric/core.clj
Original file line number Diff line number Diff line change
@@ -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)))
23 changes: 13 additions & 10 deletions src/doric/csv.clj
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
(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) "\"" "\"\"")]
(if (re-find #"[,\n\"]" s)
(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}))
44 changes: 44 additions & 0 deletions src/doric/formatting.clj
Original file line number Diff line number Diff line change
@@ -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))))))
29 changes: 18 additions & 11 deletions src/doric/html.clj
Original file line number Diff line number Diff line change
@@ -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 "&" "&amp;")
(str/replace "<" "&lt;")
(str/replace ">" "&gt;")))

(def th unaligned-th)

(def td unaligned-td)

(defn render [table]
(defn assemble [rows]
(concat ["<table>"
(str "<tr>" (join (for [c (first table)]
(str "<tr>" (str/join (for [c (first rows)]
(str "<th>" c "</th>"))) "</tr>")]
(for [tr (rest table)]
(str "<tr>" (join (for [c tr]
(for [tr (rest rows)]
(str "<tr>" (str/join (for [c tr]
(str "<td>" c "</td>"))) "</tr>"))
["</table>"]))

(def renderer (tabular-renderer {:th unaligned-th
:td unaligned-td
:assemble assemble
:escape escape}))
16 changes: 16 additions & 0 deletions src/doric/json.clj
Original file line number Diff line number Diff line change
@@ -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})))
31 changes: 17 additions & 14 deletions src/doric/org.clj
Original file line number Diff line number Diff line change
@@ -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}))
11 changes: 11 additions & 0 deletions src/doric/protocols.clj
Original file line number Diff line number Diff line change
@@ -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))
21 changes: 11 additions & 10 deletions src/doric/raw.clj
Original file line number Diff line number Diff line change
@@ -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}))
59 changes: 59 additions & 0 deletions src/doric/tabular.clj
Original file line number Diff line number Diff line change
@@ -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}))
25 changes: 25 additions & 0 deletions src/doric/unicode.clj
Original file line number Diff line number Diff line change
@@ -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}))
147 changes: 80 additions & 67 deletions test/doric/test/core.clj
Original file line number Diff line number Diff line change
@@ -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
["<table>"
"<tr><th>A</th><th>B</th></tr>"
"<tr><td>foo &lt; bar</td><td>what &amp; ever</td></tr>"
"</table>"]))))

(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 [])))
20 changes: 20 additions & 0 deletions test/doric/test/formatting.clj
Original file line number Diff line number Diff line change
@@ -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))))
20 changes: 20 additions & 0 deletions test/doric/test/tabular.clj
Original file line number Diff line number Diff line change
@@ -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"}]))))