enhance(cli): add --ref-id-footer in show cmd

This commit is contained in:
rcmerci
2026-03-24 21:38:02 +08:00
parent a7bf7fc6ef
commit 8c93d3c9da
4 changed files with 423 additions and 13 deletions

View File

@@ -20,6 +20,8 @@
:complete :pages}
:linked-references {:desc "Include linked references (default true)"
:coerce :boolean}
:ref-id-footer {:desc "Show referenced entity id footer (default true)"
:coerce :boolean}
:level {:desc "Limit tree depth (default 10)"
:coerce :long}})
@@ -454,12 +456,19 @@
distinct
vec)))
(defn- fetch-uuid-labels
(defn- uuid-entity-label
[entity]
(let [uuid-str (some-> (:block/uuid entity) str)]
(or (:block/title entity)
(:block/name entity)
uuid-str)))
(defn- fetch-uuid-entities
[config repo uuid-strings]
(if (seq uuid-strings)
(p/let [blocks (p/all (map (fn [uuid-str]
(transport/invoke config :thread-api/pull false
[repo [:block/uuid :block/title :block/name]
[repo [:db/id :block/uuid :block/title :block/name]
[:block/uuid (uuid uuid-str)]]))
uuid-strings))]
(->> blocks
@@ -467,7 +476,8 @@
(map (fn [block]
(let [uuid-str (some-> (:block/uuid block) str)]
[(string/lower-case uuid-str)
(or (:block/title block) (:block/name block) uuid-str)])))
{:id (:db/id block)
:label (uuid-entity-label block)}])))
(into {})))
(p/resolved {})))
@@ -847,6 +857,20 @@
(linked-refs->text refs uuid->label property-titles property-value-labels))
tree-text)))
(defn- referenced-entity-row
[uuid uuid->entity]
(let [{:keys [id label]} (get uuid->entity (string/lower-case uuid))
id* (or id "-")
label* (or label uuid)]
(str id* " -> " label*)))
(defn- render-referenced-entities-footer
[ordered-uuids uuid->entity]
(let [ordered-uuids (vec (distinct (remove string/blank? ordered-uuids)))]
(when (seq ordered-uuids)
(str "Referenced Entities (" (count ordered-uuids) ")\n"
(string/join "\n" (map #(referenced-entity-row % uuid->entity) ordered-uuids))))))
(defn build-action
[options repo]
(if-not (seq repo)
@@ -875,6 +899,9 @@
:linked-references? (if (contains? options :linked-references)
(:linked-references options)
true)
:ref-id-footer? (if (contains? options :ref-id-footer)
(:ref-id-footer options)
true)
:uuid (:uuid options)
:page (:page options)
:level (:level options)}})))))
@@ -890,8 +917,16 @@
(or linked-refs {:count 0 :blocks []})
{:count 0 :blocks []})
uuid-refs (collect-uuid-refs tree-data linked-refs*)
uuid->label (fetch-uuid-labels config (:repo action) uuid-refs)
tree-data (cond-> (assoc tree-data :uuid->label uuid->label)
uuid->entity (fetch-uuid-entities config (:repo action) uuid-refs)
uuid->label (->> uuid->entity
(keep (fn [[uuid-key {:keys [label]}]]
(when (seq label)
[uuid-key label])))
(into {}))
tree-data (cond-> (assoc tree-data
:referenced-uuids uuid-refs
:uuid->entity uuid->entity
:uuid->label uuid->label)
linked-enabled? (assoc :linked-references linked-refs*))
tree-data (resolve-uuid-refs-in-tree-data tree-data uuid->label)]
tree-data))
@@ -938,11 +973,25 @@
entry))
tree-data))
(defn- strip-show-internal-data
[tree-data]
(dissoc tree-data :referenced-uuids :uuid->entity))
(defn- render-tree-text
[tree-data action]
(if (false? (:linked-references? action))
(tree->text tree-data)
(tree->text-with-linked-refs tree-data)))
(let [tree-text (if (false? (:linked-references? action))
(tree->text tree-data)
(tree->text-with-linked-refs tree-data))
footer-enabled? (not= false (:ref-id-footer? action))
linked-refs (when (not= false (:linked-references? action))
(:linked-references tree-data))
ordered-uuids (or (:referenced-uuids tree-data)
(collect-uuid-refs tree-data linked-refs))
footer (when footer-enabled?
(render-referenced-entities-footer ordered-uuids (:uuid->entity tree-data)))]
(if (seq footer)
(str tree-text "\n\n" footer)
tree-text)))
(defn- render-tree-text-with-properties
[config action tree-data]
@@ -981,7 +1030,9 @@
(and ok? (contained? id)))
results))
sanitize-tree (fn [tree]
(strip-block-uuid 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)
@@ -1011,13 +1062,17 @@
(p/let [tree-data (build-tree-data cfg action)]
(case format
:edn
(let [tree-data (strip-block-uuid tree-data)]
(let [tree-data (-> tree-data
strip-show-internal-data
strip-block-uuid)]
{:status :ok
:data tree-data
:output-format :edn})
:json
(let [tree-data (strip-block-uuid tree-data)]
(let [tree-data (-> tree-data
strip-show-internal-data
strip-block-uuid)]
{:status :ok
:data tree-data
:output-format :json})

View File

@@ -3,6 +3,8 @@
[cljs.test :refer [async deftest is testing]]
[clojure.string :as string]
[logseq.cli.command.show :as show-command]
[logseq.cli.server :as cli-server]
[logseq.cli.style :as style]
[logseq.cli.transport :as transport]
[promesa.core :as p]))
@@ -120,3 +122,184 @@
(is (= "hello" (get-in result [10 :user.property/title]))))))
(p/catch (fn [e] (is false (str "unexpected error: " e))))
(p/finally done)))))
(defn- call-private
[sym & args]
(when-let [v (get (ns-interns 'logseq.cli.command.show) sym)]
(apply @v args)))
(defn- make-show-invoke-mock
[{:keys [entities-by-id children-by-page-id uuid-entities linked-refs-by-root-id]}]
(fn [_ method _ args]
(case method
:thread-api/pull
(let [[_repo _selector target] args]
(cond
(number? target)
(p/resolved (get entities-by-id target))
(and (vector? target) (= :block/uuid (first target)))
(let [uuid-str (some-> (second target) str string/lower-case)]
(p/resolved (get uuid-entities uuid-str)))
:else
(p/resolved nil)))
:thread-api/q
(let [[_repo query-args] args
[_query & inputs] query-args]
(if (= 1 (count inputs))
(let [page-id (first inputs)
blocks (get children-by-page-id page-id [])]
(p/resolved (mapv vector blocks)))
(p/resolved [])))
:thread-api/get-block-refs
(let [[_repo root-id] args]
(p/resolved (get linked-refs-by-root-id root-id [])))
(p/resolved nil))))
(deftest test-render-referenced-entities-footer
(let [render-footer (fn [ordered-uuids uuid->entity]
(call-private 'render-referenced-entities-footer ordered-uuids uuid->entity))
u1 "11111111-1111-1111-1111-111111111111"
u2 "22222222-2222-2222-2222-222222222222"
u3 "33333333-3333-3333-3333-333333333333"]
(testing "returns nil when no refs"
(is (nil? (render-footer [] {}))))
(testing "renders ordered refs with id and label"
(is (= (str "Referenced Entities (2)\n"
"181 -> First child\n"
"179 -> Root task")
(render-footer [u1 u2]
{(string/lower-case u1) {:id 181 :label "First child"}
(string/lower-case u2) {:id 179 :label "Root task"}}))))
(testing "renders fallback rows for missing id/label and unresolved refs"
(is (= (str "Referenced Entities (3)\n"
"- -> Broken ref\n"
"88 -> " u2 "\n"
"- -> " u3)
(render-footer [u1 u2 u3]
{(string/lower-case u1) {:label "Broken ref"}
(string/lower-case u2) {:id 88}}))))))
(deftest test-build-action-ref-id-footer
(testing "ref-id-footer defaults to true"
(let [result (show-command/build-action {:id "42"}
"logseq_db_demo")]
(is (true? (:ok? result)))
(is (true? (get-in result [:action :ref-id-footer?])))))
(testing "ref-id-footer false is threaded into action"
(let [result (show-command/build-action {:id "42"
:ref-id-footer false}
"logseq_db_demo")]
(is (true? (:ok? result)))
(is (false? (get-in result [:action :ref-id-footer?]))))))
(deftest test-execute-show-human-ref-id-footer-default-enabled
(async done
(let [resolved-uuid "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
missing-uuid "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
invoke-mock (make-show-invoke-mock
{:entities-by-id {1 {:db/id 1
:block/title (str "Root [[" resolved-uuid "]] [[" missing-uuid "]]")
:block/page {:db/id 100}}}
:children-by-page-id {100 [{:db/id 2
:block/title "Child"
:block/order 0
:block/parent {:db/id 1}}]}
:uuid-entities {(string/lower-case resolved-uuid)
{:db/id 179
:block/uuid (uuid resolved-uuid)
:block/title "Root task"}}})]
(-> (p/with-redefs [cli-server/ensure-server! (fn [config _] config)
transport/invoke invoke-mock]
(p/let [result (show-command/execute-show {:type :show
:repo "demo"
:id 1
:linked-references? false}
{:output-format nil})
plain (-> result :data :message style/strip-ansi)]
(is (= :ok (:status result)))
(is (string/includes? plain "Referenced Entities (2)"))
(is (string/includes? plain "179 -> Root task"))
(is (string/includes? plain (str "- -> " missing-uuid)))))
(p/catch (fn [e] (is false (str "unexpected error: " e))))
(p/finally done)))))
(deftest test-execute-show-human-ref-id-footer-multi-id
(async done
(let [uuid-a "cccccccc-cccc-cccc-cccc-cccccccccccc"
uuid-b "dddddddd-dddd-dddd-dddd-dddddddddddd"
invoke-mock (make-show-invoke-mock
{:entities-by-id {1 {:db/id 1
:block/title "Root A"
:block/page {:db/id 101}}
2 {:db/id 2
:block/title "Root B"
:block/page {:db/id 102}}}
:children-by-page-id {101 [{:db/id 11
:block/title (str "Child A [[" uuid-a "]]")
:block/order 0
:block/parent {:db/id 1}}]
102 [{:db/id 22
:block/title (str "Child B [[" uuid-b "]]")
:block/order 0
:block/parent {:db/id 2}}]}
:uuid-entities {(string/lower-case uuid-a)
{:db/id 501
:block/uuid (uuid uuid-a)
:block/title "Ref A"}
(string/lower-case uuid-b)
{:db/id 502
:block/uuid (uuid uuid-b)
:block/title "Ref B"}}})]
(-> (p/with-redefs [cli-server/ensure-server! (fn [config _] config)
transport/invoke invoke-mock]
(p/let [result (show-command/execute-show {:type :show
:repo "demo"
:ids [1 2]
:multi-id? true
:linked-references? false}
{:output-format nil})
plain (-> result :data :message style/strip-ansi)
footer-count (count (re-seq #"Referenced Entities \(1\)" plain))]
(is (= :ok (:status result)))
(is (= 2 footer-count))
(is (string/includes? plain "501 -> Ref A"))
(is (string/includes? plain "502 -> Ref B"))))
(p/catch (fn [e] (is false (str "unexpected error: " e))))
(p/finally done)))))
(deftest test-execute-show-human-ref-id-footer-disabled
(async done
(let [uuid-a "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee"
invoke-mock (make-show-invoke-mock
{:entities-by-id {1 {:db/id 1
:block/title "Root"
:block/page {:db/id 201}}}
:children-by-page-id {201 [{:db/id 12
:block/title (str "Child [[" uuid-a "]]")
:block/order 0
:block/parent {:db/id 1}}]}
:uuid-entities {(string/lower-case uuid-a)
{:db/id 601
:block/uuid (uuid uuid-a)
:block/title "Ref A"}}})]
(-> (p/with-redefs [cli-server/ensure-server! (fn [config _] config)
transport/invoke invoke-mock]
(p/let [result (show-command/execute-show {:type :show
:repo "demo"
:id 1
:linked-references? false
:ref-id-footer? false}
{:output-format nil})
plain (-> result :data :message style/strip-ansi)]
(is (= :ok (:status result)))
(is (not (string/includes? plain "Referenced Entities (")))))
(p/catch (fn [e] (is false (str "unexpected error: " e))))
(p/finally done)))))

View File

@@ -1303,10 +1303,16 @@
(is (false? (:ok? result)))
(is (= :invalid-options (get-in result [:error :code])))))
(testing "show help lists linked references option"
(testing "show parses ref-id-footer option"
(let [result (commands/parse-args ["show" "--page" "Home" "--ref-id-footer" "false"])]
(is (true? (:ok? result)))
(is (= false (get-in result [:options :ref-id-footer])))))
(testing "show help lists linked references and ref-id-footer options"
(let [summary (:summary (binding [style/*color-enabled?* true]
(commands/parse-args ["show" "--help"])))]
(is (string/includes? (strip-ansi summary) "--linked-references")))))
(is (string/includes? (strip-ansi summary) "--linked-references"))
(is (string/includes? (strip-ansi summary) "--ref-id-footer")))))
(deftest test-verb-subcommand-parse-query
(testing "query shows group help"