From 22f1e6c867dfd402305339234d884131ad720f9d Mon Sep 17 00:00:00 2001 From: rcmerci Date: Fri, 10 Apr 2026 22:06:20 +0800 Subject: [PATCH] refactor(cli): centralize output mode handling --- src/main/logseq/cli/command/core.cljs | 13 +- src/main/logseq/cli/command/show.cljs | 145 +++++++++------------ src/main/logseq/cli/command/sync.cljs | 4 +- src/main/logseq/cli/config.cljs | 26 ++-- src/main/logseq/cli/format.cljs | 8 +- src/main/logseq/cli/main.cljs | 47 ++++++- src/main/logseq/cli/output_mode.cljs | 29 +++++ src/test/logseq/cli/command/show_test.cljs | 73 ++++++++++- src/test/logseq/cli/command/sync_test.cljs | 10 ++ src/test/logseq/cli/commands_test.cljs | 6 + src/test/logseq/cli/config_test.cljs | 9 ++ src/test/logseq/cli/main_test.cljs | 37 +++++- src/test/logseq/cli/output_mode_test.cljs | 31 +++++ 13 files changed, 322 insertions(+), 116 deletions(-) create mode 100644 src/main/logseq/cli/output_mode.cljs create mode 100644 src/test/logseq/cli/output_mode_test.cljs diff --git a/src/main/logseq/cli/command/core.cljs b/src/main/logseq/cli/command/core.cljs index f4c4f135cb..408161b123 100644 --- a/src/main/logseq/cli/command/core.cljs +++ b/src/main/logseq/cli/command/core.cljs @@ -2,6 +2,7 @@ "Shared CLI parsing utilities." (:require [babashka.cli :as cli] [clojure.string :as string] + [logseq.cli.output-mode :as output-mode] [logseq.cli.style :as style] [logseq.common.config :as common-config])) @@ -22,7 +23,7 @@ :coerce :long} :output {:desc "Output format. Default: human" :alias :o - :validate #{"human" "json" "edn"}} + :validate output-mode/allowed-values} :verbose {:desc "Enable verbose debug logging to stderr" :alias :v :coerce :boolean} @@ -244,6 +245,12 @@ :else nil)) +(defn- valid-leading-global-value? + [opt-key value] + (case opt-key + :output (contains? output-mode/allowed-values value) + true)) + (defn parse-leading-global-opts [args] (loop [remaining args @@ -255,7 +262,9 @@ (if (contains? global-flag-options opt-key) (recur (rest remaining) (assoc opts opt-key true)) (if-let [value (second remaining)] - (recur (drop 2 remaining) (assoc opts opt-key value)) + (if (valid-leading-global-value? opt-key value) + (recur (drop 2 remaining) (assoc opts opt-key value)) + {:opts opts :args remaining}) {:opts opts :args (rest remaining)})) {:opts opts :args remaining}))))) diff --git a/src/main/logseq/cli/command/show.cljs b/src/main/logseq/cli/command/show.cljs index 169a8b7194..a1549e53e6 100644 --- a/src/main/logseq/cli/command/show.cljs +++ b/src/main/logseq/cli/command/show.cljs @@ -6,6 +6,7 @@ [clojure.walk :as walk] [logseq.cli.command.core :as core] [logseq.cli.command.id :as id-command] + [logseq.cli.output-mode :as output-mode] [logseq.cli.server :as cli-server] [logseq.cli.style :as style] [logseq.cli.transport :as transport] @@ -999,85 +1000,69 @@ (p/let [tree-data (attach-property-titles config (:repo action) tree-data)] (render-tree-text tree-data action))) +(defn- sanitize-structured-tree + [tree-data] + (-> tree-data + strip-show-internal-data + strip-block-uuid)) + +(defn- structured-show-result + [mode data] + {:status :ok + :data data + :output-format mode}) + +(defn- multi-id-structured-data + [results] + (mapv (fn [{:keys [ok? tree id error]}] + (if ok? + (sanitize-structured-tree tree) + (multi-id-error-entry id error))) + results)) + (defn execute-show [action config] - (-> (p/let [cfg (cli-server/ensure-server! config (:repo action)) - format (:output-format config) - ids (:ids action) - multi-id? (:multi-id? action)] - (if (and (seq ids) multi-id?) - (p/let [results (p/all (map (fn [id] - (-> (build-tree-data cfg (assoc action :id id)) - (p/then (fn [tree-data] - {:ok? true - :id id - :tree tree-data})) - (p/catch (fn [error] - {:ok? false - :id id - :error error})))) - ids)) - ok-results (filter :ok? results) - id->tree-ids (into {} - (map (fn [{:keys [id tree]}] - [id (collect-tree-ids (:root tree))])) - ok-results) - contained? (fn [id] - (some (fn [[other-id tree-ids]] - (and (not= other-id id) - (contains? tree-ids id))) - id->tree-ids)) - results (vec (remove (fn [{:keys [ok? id]}] - (and ok? (contained? id))) - results)) - sanitize-tree (fn [tree] - (-> tree - strip-show-internal-data - strip-block-uuid)) - render-result (fn [{:keys [ok? tree id error]}] - (if ok? - (render-tree-text-with-properties cfg action tree) - (multi-id-error-message id error)))] - (case format - :edn - {:status :ok - :data (mapv (fn [{:keys [ok? tree id error]}] - (if ok? - (sanitize-tree tree) - (multi-id-error-entry id error))) - results) - :output-format :edn} - - :json - {:status :ok - :data (mapv (fn [{:keys [ok? tree id error]}] - (if ok? - (sanitize-tree tree) - (multi-id-error-entry id error))) - results) - :output-format :json} - - (p/let [messages (p/all (map render-result results))] - {:status :ok - :data {:message (string/join multi-id-delimiter messages)}}))) - (p/let [tree-data (build-tree-data cfg action)] - (case format - :edn - (let [tree-data (-> tree-data - strip-show-internal-data - strip-block-uuid)] - {:status :ok - :data tree-data - :output-format :edn}) - - :json - (let [tree-data (-> tree-data - strip-show-internal-data - strip-block-uuid)] - {:status :ok - :data tree-data - :output-format :json}) - - (p/let [message (render-tree-text-with-properties cfg action tree-data)] - {:status :ok - :data {:message message}}))))))) + (p/let [cfg (cli-server/ensure-server! config (:repo action)) + mode (output-mode/parse (:output-format config)) + ids (:ids action) + multi-id? (:multi-id? action)] + (if (and (seq ids) multi-id?) + (p/let [results (p/all (map (fn [id] + (-> (build-tree-data cfg (assoc action :id id)) + (p/then (fn [tree-data] + {:ok? true + :id id + :tree tree-data})) + (p/catch (fn [error] + {:ok? false + :id id + :error error})))) + ids)) + ok-results (filter :ok? results) + id->tree-ids (into {} + (map (fn [{:keys [id tree]}] + [id (collect-tree-ids (:root tree))])) + ok-results) + contained? (fn [id] + (some (fn [[other-id tree-ids]] + (and (not= other-id id) + (contains? tree-ids id))) + id->tree-ids)) + results (vec (remove (fn [{:keys [ok? id]}] + (and ok? (contained? id))) + results)) + render-result (fn [{:keys [ok? tree id error]}] + (if ok? + (render-tree-text-with-properties cfg action tree) + (multi-id-error-message id error)))] + (if (output-mode/structured? mode) + (structured-show-result mode (multi-id-structured-data results)) + (p/let [messages (p/all (map render-result results))] + {:status :ok + :data {:message (string/join multi-id-delimiter messages)}}))) + (p/let [tree-data (build-tree-data cfg action)] + (if (output-mode/structured? mode) + (structured-show-result mode (sanitize-structured-tree tree-data)) + (p/let [message (render-tree-text-with-properties cfg action tree-data)] + {:status :ok + :data {:message message}})))))) diff --git a/src/main/logseq/cli/command/sync.cljs b/src/main/logseq/cli/command/sync.cljs index cd771f197c..23a04e0175 100644 --- a/src/main/logseq/cli/command/sync.cljs +++ b/src/main/logseq/cli/command/sync.cljs @@ -4,6 +4,7 @@ [logseq.cli.auth :as cli-auth] [logseq.cli.command.core :as core] [logseq.cli.config :as cli-config] + [logseq.cli.output-mode :as output-mode] [logseq.cli.server :as cli-server] [logseq.cli.transport :as transport] [logseq.common.cognito-config :as cognito-config] @@ -76,7 +77,6 @@ (def ^:private sync-start-timeout-ms 10000) (def ^:private sync-start-poll-interval-ms 1000) (def ^:private sync-download-timeout-ms (* 30 60 1000)) -(def ^:private structured-output-formats #{:json :edn}) (def ^:private sync-start-skipped-states #{:inactive :stopped}) @@ -560,7 +560,7 @@ [action config] (if (:progress-explicit? action) (true? (:progress action)) - (not (contains? structured-output-formats (:output-format config))))) + (not (output-mode/structured? (:output-format config))))) (defn- download-progress-message [graph-id event-type payload] diff --git a/src/main/logseq/cli/config.cljs b/src/main/logseq/cli/config.cljs index 875b7dc9e5..6f4d61999b 100644 --- a/src/main/logseq/cli/config.cljs +++ b/src/main/logseq/cli/config.cljs @@ -6,6 +6,7 @@ ["fs" :as fs] ["os" :as os] ["path" :as node-path] + [logseq.cli.output-mode :as output-mode] [logseq.common.graph :as common-graph])) (defn- parse-int @@ -30,17 +31,6 @@ :else nil)) -(def ^:private output-formats - #{:human :json :edn}) - -(defn- parse-output-format - [value] - (let [kw (cond - (keyword? value) value - (string? value) (-> value string/trim string/lower-case keyword) - :else nil)] - (when (output-formats kw) - kw))) (defn- default-config-path [] @@ -105,7 +95,7 @@ (assoc :logout-timeout-ms (parse-int (gobj/get env "LOGSEQ_CLI_LOGOUT_TIMEOUT_MS"))) (seq (gobj/get env "LOGSEQ_CLI_OUTPUT")) - (assoc :output-format (parse-output-format (gobj/get env "LOGSEQ_CLI_OUTPUT"))) + (assoc :output-format (output-mode/parse (gobj/get env "LOGSEQ_CLI_OUTPUT"))) (seq (gobj/get env "LOGSEQ_CLI_CONFIG")) (assoc :config-path (gobj/get env "LOGSEQ_CLI_CONFIG"))))) @@ -126,12 +116,12 @@ (:config-path env) (:config-path defaults)) file-config (or (read-config-file config-path) {}) - output-format (or (parse-output-format (:output-format opts)) - (parse-output-format (:output opts)) - (parse-output-format (:output-format env)) - (parse-output-format (:output env)) - (parse-output-format (:output-format file-config)) - (parse-output-format (:output file-config))) + output-format (or (output-mode/parse (:output-format opts)) + (output-mode/parse (:output opts)) + (output-mode/parse (:output-format env)) + (output-mode/parse (:output env)) + (output-mode/parse (:output-format file-config)) + (output-mode/parse (:output file-config))) merged (merge defaults file-config env opts {:config-path config-path}) list-title-max-display-width (or (parse-positive-int (:list-title-max-display-width merged)) list-title-max-display-width-default)] diff --git a/src/main/logseq/cli/format.cljs b/src/main/logseq/cli/format.cljs index 776e0254cf..898d468629 100644 --- a/src/main/logseq/cli/format.cljs +++ b/src/main/logseq/cli/format.cljs @@ -4,6 +4,7 @@ [clojure.string :as string] [clojure.walk :as walk] [logseq.cli.command.core :as command-core] + [logseq.cli.output-mode :as output-mode] [logseq.cli.style :as style] [logseq.common.util :as common-util] ["string-width" :default string-width])) @@ -954,11 +955,8 @@ (let [result (-> result normalize-graph-result sanitize-result) - format (cond - (= output-format :edn) :edn - (= output-format :json) :json - :else :human)] - (case format + mode (or (output-mode/parse output-format) :human)] + (case mode :json (->json result) :edn (->edn result) (->human result opts)))) diff --git a/src/main/logseq/cli/main.cljs b/src/main/logseq/cli/main.cljs index 0e4e8cfbce..00dcd1d26d 100644 --- a/src/main/logseq/cli/main.cljs +++ b/src/main/logseq/cli/main.cljs @@ -2,11 +2,13 @@ "CLI entrypoint for invoking db-worker-node." (:refer-clojure :exclude [run!]) (:require [lambdaisland.glogi :as log] + [logseq.cli.command.core :as command-core] [logseq.cli.commands :as commands] [logseq.cli.config :as config] [logseq.cli.data-dir :as data-dir] [logseq.cli.format :as format] [logseq.cli.log :as cli-log] + [logseq.cli.output-mode :as output-mode] [logseq.cli.profile :as profile] [logseq.cli.version :as version] [promesa.core :as p])) @@ -44,6 +46,30 @@ :status (if (zero? (:exit-code result)) :ok :error)}))) result)) +(defn- parse-argv-output-format + [args] + (let [{:keys [opts]} (command-core/parse-leading-global-opts args)] + (or (output-mode/parse (:output-format opts)) + (output-mode/parse (:output opts))))) + +(defn- parsed-output-format + [parsed] + (or (output-mode/parse (get-in parsed [:options :output-format])) + (output-mode/parse (get-in parsed [:options :output])))) + +(defn- resolve-output-format + [args parsed cfg result] + (or (output-mode/parse (:output-format result)) + (output-mode/parse (:output-format cfg)) + (parsed-output-format parsed) + (parse-argv-output-format args))) + +(defn- format-opts + [args parsed cfg result] + (if-let [mode (resolve-output-format args parsed cfg result)] + {:output-format mode} + {})) + (defn- handle-unexpected-error "Provide clean, consistent error handling for unexpected errors in run!" [profile-session parsed cfg error] @@ -86,9 +112,18 @@ (cond (:help? parsed) (p/resolved - (attach-profile-lines profile-session parsed - {:exit-code 0 - :output (:summary parsed)})) + (let [mode (resolve-output-format args parsed nil nil)] + (attach-profile-lines + profile-session + parsed + {:exit-code 0 + :output (if (output-mode/structured? mode) + (profile/time! profile-session "cli.format-result" + (fn [] + (format/format-result {:status :ok + :data {:message (:summary parsed)}} + {:output-format mode}))) + (:summary parsed))}))) (not (:ok? parsed)) (p/resolved @@ -101,7 +136,7 @@ (format/format-result {:status :error :error (:error parsed) :command (:command parsed)} - {})))})) + (format-opts args parsed nil nil))))})) (= :version (:command parsed)) (p/resolved @@ -154,9 +189,7 @@ (fn [] (commands/execute (:action action-result) cfg))) (p/then (fn [result] - (let [opts (cond-> cfg - (:output-format result) - (assoc :output-format (:output-format result)))] + (let [opts (merge cfg (format-opts args parsed cfg result))] (attach-profile-lines profile-session parsed diff --git a/src/main/logseq/cli/output_mode.cljs b/src/main/logseq/cli/output_mode.cljs new file mode 100644 index 0000000000..0b6be4b36f --- /dev/null +++ b/src/main/logseq/cli/output_mode.cljs @@ -0,0 +1,29 @@ +(ns logseq.cli.output-mode + "Shared output mode utilities for CLI human/json/edn output handling." + (:require [clojure.string :as string])) + +(def allowed-keywords + #{:human :json :edn}) + +(def allowed-values + (into #{} (map name) allowed-keywords)) + +(def ^:private structured-keywords + #{:json :edn}) + +(defn parse + [value] + (let [mode (cond + (keyword? value) value + (string? value) (some-> value string/trim string/lower-case keyword) + :else nil)] + (when (contains? allowed-keywords mode) + mode))) + +(defn string-value + [value] + (some-> (parse value) name)) + +(defn structured? + [value] + (contains? structured-keywords (parse value))) diff --git a/src/test/logseq/cli/command/show_test.cljs b/src/test/logseq/cli/command/show_test.cljs index 464fa9a7f1..3ee47558fa 100644 --- a/src/test/logseq/cli/command/show_test.cljs +++ b/src/test/logseq/cli/command/show_test.cljs @@ -202,6 +202,14 @@ (p/resolved nil)))) +(defn- contains-block-uuid? + [value] + (cond + (map? value) (or (contains? value :block/uuid) + (some contains-block-uuid? (vals value))) + (sequential? value) (some contains-block-uuid? value) + :else false)) + (deftest test-render-referenced-entities-footer (let [render-footer (fn [ordered-uuids uuid->entity] (call-private 'render-referenced-entities-footer ordered-uuids uuid->entity)) @@ -325,7 +333,7 @@ :block/title "Root" :block/page {:db/id 201}}} :children-by-page-id {201 [{:db/id 12 - :block/title (str "Child [[" uuid-a "]]") + :block/title (str "Child [[" uuid-a "]]" ) :block/order 0 :block/parent {:db/id 1}}]} :uuid-entities {(string/lower-case uuid-a) @@ -345,3 +353,66 @@ (is (not (string/includes? plain "Referenced Entities ("))))) (p/catch (fn [e] (is false (str "unexpected error: " e)))) (p/finally done))))) + +(deftest test-execute-show-structured-output-single-and-multi-id + (async done + (let [invoke-mock (make-show-invoke-mock + {:entities-by-id {1 {:db/id 1 + :block/uuid (uuid "11111111-1111-1111-1111-111111111111") + :block/title "Root A" + :block/page {:db/id 101}} + 2 {:db/id 2 + :block/uuid (uuid "22222222-2222-2222-2222-222222222222") + :block/title "Root B" + :block/page {:db/id 102}}} + :children-by-page-id {101 [{:db/id 11 + :block/uuid (uuid "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") + :block/title "Child A" + :block/order 0 + :block/parent {:db/id 1}}] + 102 [{:db/id 22 + :block/uuid (uuid "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb") + :block/title "Child B" + :block/order 0 + :block/parent {:db/id 2}}]}})] + (-> (p/with-redefs [cli-server/ensure-server! (fn [config _] config) + transport/invoke invoke-mock] + (p/let [single-json (show-command/execute-show {:type :show + :repo "demo" + :id 1 + :linked-references? false} + {:output-format :json}) + single-edn (show-command/execute-show {:type :show + :repo "demo" + :id 1 + :linked-references? false} + {:output-format :edn}) + multi-json (show-command/execute-show {:type :show + :repo "demo" + :ids [1 2] + :multi-id? true + :linked-references? false} + {:output-format :json}) + multi-edn (show-command/execute-show {:type :show + :repo "demo" + :ids [1 2] + :multi-id? true + :linked-references? false} + {:output-format :edn})] + (doseq [result [single-json single-edn multi-json multi-edn]] + (is (= :ok (:status result))) + (is (not (contains-block-uuid? (:data result))))) + + (is (= :json (:output-format single-json))) + (is (= :edn (:output-format single-edn))) + (is (= 1 (get-in single-json [:data :root :db/id]))) + (is (= 1 (get-in single-edn [:data :root :db/id]))) + + (is (= :json (:output-format multi-json))) + (is (= :edn (:output-format multi-edn))) + (is (= [1 2] + (mapv #(get-in % [:root :db/id]) (:data multi-json)))) + (is (= [1 2] + (mapv #(get-in % [:root :db/id]) (:data multi-edn)))))) + (p/catch (fn [e] (is false (str "unexpected error: " e)))) + (p/finally done))))) diff --git a/src/test/logseq/cli/command/sync_test.cljs b/src/test/logseq/cli/command/sync_test.cljs index 6c33e0e338..dd3b08cd08 100644 --- a/src/test/logseq/cli/command/sync_test.cljs +++ b/src/test/logseq/cli/command/sync_test.cljs @@ -688,6 +688,16 @@ _ (is (= [] @subscribe-calls)) _ (is (= [] @printed-lines)) _ (is (= 0 @close-calls)) + _ (execute-with-runtime-auth {:type :sync-download + :repo "logseq_db_demo" + :graph "demo" + :progress-explicit? false} + {:base-url "http://example" + :data-dir "/tmp" + :output-format :edn}) + _ (is (= [] @subscribe-calls)) + _ (is (= [] @printed-lines)) + _ (is (= 0 @close-calls)) _ (execute-with-runtime-auth {:type :sync-download :repo "logseq_db_demo" :graph "demo" diff --git a/src/test/logseq/cli/commands_test.cljs b/src/test/logseq/cli/commands_test.cljs index af5ecd1f72..7cfd2cce53 100644 --- a/src/test/logseq/cli/commands_test.cljs +++ b/src/test/logseq/cli/commands_test.cljs @@ -528,6 +528,12 @@ (is (true? (:ok? result))) (is (= "json" (get-in result [:options :output]))))) + (testing "global output option rejects invalid values" + (let [result (commands/parse-args ["--output" "yaml" "graph" "list"])] + (is (false? (:ok? result))) + (is (= :invalid-options (get-in result [:error :code]))) + (is (string/includes? (string/lower-case (get-in result [:error :message])) "output")))) + (testing "global profile option defaults to absent" (let [result (commands/parse-args ["graph" "list"])] (is (true? (:ok? result))) diff --git a/src/test/logseq/cli/config_test.cljs b/src/test/logseq/cli/config_test.cljs index f8f98981d1..a47a6b5a5b 100644 --- a/src/test/logseq/cli/config_test.cljs +++ b/src/test/logseq/cli/config_test.cljs @@ -89,6 +89,15 @@ :output "json"})] (is (= :edn (:output-format result))))) +(deftest test-output-format-invalid-values-fallback + (let [dir (node-helper/create-tmp-dir) + cfg-path (node-path/join dir "cli.edn") + _ (fs/writeFileSync cfg-path "{:output-format :edn}") + env {"LOGSEQ_CLI_OUTPUT" "yaml"} + result (with-env env #(config/resolve-config {:config-path cfg-path + :output "xml"}))] + (is (= :edn (:output-format result))))) + (deftest test-default-paths (let [result (config/resolve-config {}) expected-config-path (node-path/join (.homedir os) "logseq" "cli.edn")] diff --git a/src/test/logseq/cli/main_test.cljs b/src/test/logseq/cli/main_test.cljs index 14d8a5e9c6..f1f7cb34c0 100644 --- a/src/test/logseq/cli/main_test.cljs +++ b/src/test/logseq/cli/main_test.cljs @@ -1,5 +1,6 @@ (ns logseq.cli.main-test - (:require [cljs.test :refer [async deftest is]] + (:require [cljs.reader :as reader] + [cljs.test :refer [async deftest is]] [clojure.string :as string] [logseq.cli.commands :as commands] [logseq.cli.main :as cli-main] @@ -28,6 +29,40 @@ (done))) (p/finally done)))) +(deftest test-help-output-respects-structured-modes + (async done + (-> (p/let [json-result (cli-main/run! ["--output" "json" "--help"] {:exit? false}) + edn-result (cli-main/run! ["--output" "edn" "--help"] {:exit? false}) + json-output (js->clj (js/JSON.parse (:output json-result)) :keywordize-keys true) + edn-output (reader/read-string (:output edn-result))] + (is (= 0 (:exit-code json-result))) + (is (= 0 (:exit-code edn-result))) + (is (= "ok" (:status json-output))) + (is (= :ok (:status edn-output))) + (is (string/includes? (get-in json-output [:data :message]) "Usage: logseq")) + (is (string/includes? (get-in edn-output [:data :message]) "Usage: logseq"))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)) + (done))) + (p/finally done)))) + +(deftest test-parse-error-output-respects-structured-modes + (async done + (-> (p/let [json-result (cli-main/run! ["--output" "json" "wat"] {:exit? false}) + edn-result (cli-main/run! ["--output" "edn" "wat"] {:exit? false}) + json-output (js->clj (js/JSON.parse (:output json-result)) :keywordize-keys true) + edn-output (reader/read-string (:output edn-result))] + (is (= 1 (:exit-code json-result))) + (is (= 1 (:exit-code edn-result))) + (is (= "error" (:status json-output))) + (is (= :error (:status edn-output))) + (is (some? (get-in json-output [:error :message]))) + (is (some? (get-in edn-output [:error :message])))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)) + (done))) + (p/finally done)))) + (deftest test-result->exit-code (let [result->exit-code #'cli-main/result->exit-code] (is (= 0 (result->exit-code {:status :ok}))) diff --git a/src/test/logseq/cli/output_mode_test.cljs b/src/test/logseq/cli/output_mode_test.cljs new file mode 100644 index 0000000000..0b4b281e96 --- /dev/null +++ b/src/test/logseq/cli/output_mode_test.cljs @@ -0,0 +1,31 @@ +(ns logseq.cli.output-mode-test + (:require [cljs.test :refer [deftest is testing]] + [logseq.cli.output-mode :as output-mode])) + +(deftest test-allowed-output-modes + (is (= #{:human :json :edn} output-mode/allowed-keywords)) + (is (= #{"human" "json" "edn"} output-mode/allowed-values))) + +(deftest test-parse + (testing "parses string and keyword output modes" + (is (= :human (output-mode/parse "human"))) + (is (= :json (output-mode/parse " JSON "))) + (is (= :edn (output-mode/parse :edn)))) + + (testing "returns nil for unknown or blank values" + (is (nil? (output-mode/parse ""))) + (is (nil? (output-mode/parse " "))) + (is (nil? (output-mode/parse "yaml"))) + (is (nil? (output-mode/parse :yaml))) + (is (nil? (output-mode/parse nil))))) + +(deftest test-structured-output + (is (true? (output-mode/structured? :json))) + (is (true? (output-mode/structured? "edn"))) + (is (false? (output-mode/structured? :human))) + (is (false? (output-mode/structured? "yaml")))) + +(deftest test-string-value + (is (= "json" (output-mode/string-value :json))) + (is (= "edn" (output-mode/string-value "EDN"))) + (is (nil? (output-mode/string-value :yaml))))