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 ""))) "")] - (for [tr (rest table)] - (str "" (join (for [c tr] + (for [tr (rest rows)] + (str "" (str/join (for [c tr] (str ""))) "")) ["
" c "
" 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 + ["" + "" + "" + "
AB
foo < barwhat & 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"}]))))