From 19c6c084a723416f58ee8f03eb8cb17ed2fe60d8 Mon Sep 17 00:00:00 2001 From: Markku Rontu Date: Wed, 12 Oct 2022 16:50:01 +0300 Subject: [PATCH] feat: pretty printing without color (#16) Introduce a new flag `--color` that decides whether colors are printed while pretty-printing is on. Valid values are `on`, `off` and `auto`, which is the default. In the future, there is the possibility of autodetecting from TTY settings, but at the time of writing `auto` means `on`. The flags `-c` and `-C` can also be used to toggle color on/off respectively. NB: Currently, only applies to EDN printing. Additionally tests the pretty-printing with multiple line case, and ANSI color escape sequences. --- README.md | 3 +++ src/cq/formats.clj | 6 ++++-- src/cq/main.clj | 23 ++++++++++++++++++++++- test/cq/formats_test.clj | 20 +++++++++++++++++++- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c23692a..53c3c60 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,9 @@ Options: -i, --in FORMAT yaml Input format: csv, edn, json, lines, msgpack, text, transit, yaml -o, --out FORMAT edn Output format: csv, edn, json, lines, msgpack, text, transit, yaml -p, --[no-]pretty Pretty print output - default is true + --color COLOR auto When pretty printing, whether to use colors: auto, off, on - default is auto + -c Same as --color=on + -C Same as --color=off -k, --key-fn FN keyword Function used to transform keys - currently only supported for JSON and CSV --yaml-unsafe Enables unsafe mode in clj-yaml / SnakeYAML --[no-]yaml-keywords Turn map keys into keywords in clj-yaml - default is true diff --git a/src/cq/formats.clj b/src/cq/formats.clj index 1972c49..6568b57 100644 --- a/src/cq/formats.clj +++ b/src/cq/formats.clj @@ -35,11 +35,13 @@ (edn/read (PushbackReader. (io/reader in))))) (defn ->edn-writer - [{:keys [pretty]}] + [{:keys [pretty color]}] (if pretty (fn [x out] (binding [*out* (io/writer out)] - (puget/cprint x))) + (if color + (puget/cprint x) + (puget/pprint x)))) (fn [x out] (binding [*out* (io/writer out)] (pr x) diff --git a/src/cq/main.clj b/src/cq/main.clj index 9176037..97209fb 100644 --- a/src/cq/main.clj +++ b/src/cq/main.clj @@ -7,6 +7,10 @@ (require 'cq.readers) +(def colors #{:auto :on :off}) + +(def colors-str (str/join ", " (sort (map name colors)))) + (def formats (set (keys fmt/formats))) (def formats-str (str/join ", " (sort formats))) @@ -24,6 +28,16 @@ :validate [formats]] ["-p" "--[no-]pretty" "Pretty print output - default is true" :default true] + [nil "--color COLOR" (str "When pretty printing, whether to use colors: " colors-str " - default is auto") + :default "auto" + :parse-fn keyword + :validate [colors]] + ["-c" nil "Same as --color=on" + :id :color + :assoc-fn (fn [m _ _] (assoc m :color :on))] + ["-C" nil "Same as --color=off" + :id :color + :assoc-fn (fn [m _ _] (assoc m :color :off))] ["-k" "--key-fn FN" "Function used to transform keys - currently only supported for JSON and CSV" :default "keyword"] [nil "--yaml-unsafe" "Enables unsafe mode in clj-yaml / SnakeYAML"] @@ -96,11 +110,18 @@ (def ^:dynamic *stdin* System/in) (def ^:dynamic *stdout* System/out) +(defn- handle-auto-options [opts] + (update opts :color #(case % + :auto true ; would be nice to detect + :on true + false))) + (defn main [args] (let [{:keys [arguments exit-message ok?] {:keys [in out] :as opts} :options} - (validate-args args)] + (validate-args args) + opts (handle-auto-options opts)] (if exit-message (exit (if ok? 0 1) exit-message) (let [expressions (args->exprs arguments) diff --git a/test/cq/formats_test.clj b/test/cq/formats_test.clj index 2457f0a..6254452 100644 --- a/test/cq/formats_test.clj +++ b/test/cq/formats_test.clj @@ -60,7 +60,25 @@ (is (= {:a {:b [1 2 3]}} (test-reader-str sut/->edn-reader nil "{:a {:b [1 2 3]}}")))) (testing "writer" - (is (= "{:a {:b [1 2 3]}}\n" (test-writer-str sut/->edn-writer nil {:a {:b [1 2 3]}}))))) + (is (= "{:a {:b [1 2 3]}}\n" (test-writer-str sut/->edn-writer nil {:a {:b [1 2 3]}}))) + + (testing "pretty" + (is (= "{:a {:b [1 2 3],\n :c [2 3 {:something :long}],\n :d [1 2 {:something :even/longer}]}}\n" + (test-writer-str sut/->edn-writer {:pretty true :color false} {:a {:b [1 2 3] :c [2 3 {:something :long}] :d [1 2 {:something :even/longer}]}}))) + + (testing "color" + (is (= "\u001B[1;31m{\u001B[0m\u001B[1;33m:a\u001B[0m \u001B[1;31m{\u001B[0m\u001B[1;33m:b\u001B[0m \u001B[1;31m[\u001B[0m\u001B[36m1\u001B[0m \u001B[36m2\u001B[0m \u001B[36m3\u001B[0m\u001B[1;31m]\u001B[0m,\n \u001B[1;33m:c\u001B[0m \u001B[1;31m[\u001B[0m\u001B[36m2\u001B[0m \u001B[36m3\u001B[0m \u001B[1;31m{\u001B[0m\u001B[1;33m:something\u001B[0m \u001B[1;33m:long\u001B[0m\u001B[1;31m}\u001B[0m\u001B[1;31m]\u001B[0m,\n \u001B[1;33m:d\u001B[0m \u001B[1;31m[\u001B[0m\u001B[36m1\u001B[0m \u001B[36m2\u001B[0m \u001B[1;31m{\u001B[0m\u001B[1;33m:something\u001B[0m \u001B[1;33m:even/longer\u001B[0m\u001B[1;31m}\u001B[0m\u001B[1;31m]\u001B[0m\u001B[1;31m}\u001B[0m\u001B[1;31m}\u001B[0m\n" + (test-writer-str sut/->edn-writer {:pretty true :color true} {:a {:b [1 2 3] :c [2 3 {:something :long}] :d [1 2 {:something :even/longer}]}}))))))) + +(comment + ;; simple utility for printing an ANSI escaped sequence for a test constant + ;; ANSI uses the ESC character for escape codes and it is lost in printing + (let [x {:a {:b [1 2 3] :c [2 3 {:something :long}] :d [1 2 {:something :even/longer}]}} + s (test-writer-str sut/->edn-writer {:pretty true :color true} x)] + (str/join (for [c s] + (if (= 27 (int c)) + "ESC" ; replace with \u001B and you get a test constant + c))))) (defn resource->bytes [file] (with-open [xin (io/input-stream (io/resource file))