feat(cli): add qmd query (3)

This commit is contained in:
rcmerci
2026-05-10 14:58:47 +08:00
parent 5ec5b258c9
commit b06eaa48a9
4 changed files with 193 additions and 47 deletions

View File

@@ -335,6 +335,48 @@
comment-text
(str content " " comment-text))))
(defn- content-first-line
[content]
(-> (or content "")
string/split-lines
first
(or "")
string/trim))
(defn- block-first-line-fragment
[block]
(content-first-line (:block/title block)))
(defn- code-fence-block-line?
[content]
(string/starts-with? (string/trim (or content "")) "```"))
(defn- normalize-rendered-match-text
[content]
(-> (or content "")
string/lower-case
(string/replace ref-or-tag-re "$1[[]]")
(string/replace simple-hashtag-re "$1#[[]]")
(string/replace #"\s+" " ")
string/trim))
(defn- rendered-line-matches-block?
[block-info content]
(when block-info
(let [content (or content "")
fragment (:first-line-fragment block-info)
content* (normalize-rendered-match-text content)
fragment* (normalize-rendered-match-text fragment)]
(cond
(:code-block? block-info)
(code-fence-block-line? content)
(string/blank? fragment)
(string/blank? (string/trim content))
:else
(string/includes? content* fragment*)))))
(defn- code-block?
[block]
(or (= :code (:logseq.property.node/display-type block))
@@ -344,6 +386,7 @@
(defn- block-line-info
[db block marker]
{:db/id (:db/id block)
:first-line-fragment (block-first-line-fragment block)
:code-block? (code-block? block)
:status-marker (when (seq (d/datoms db :eavt (:db/id block) :logseq.property/status))
(some-> (:logseq.property/status block) status-marker))
@@ -433,31 +476,54 @@
[block-line-info' & more-block-line-infos] line-infos
lines initial-lines
seen-block? false
property-indent nil]
property-indent nil
in-code-block? false]
(if (nil? line)
(string/join "\n" lines)
(if (property-value-line? line property-indent)
(cond
in-code-block?
(recur more
(cons block-line-info' more-block-line-infos)
(conj lines line)
seen-block?
property-indent)
(if (re-matches markdown-block-line-re line)
(let [lines' (cond-> lines
(and insert-blank-before-first-block?
(not seen-block?)) (conj "")
true (into (decorate-block-line db block-line-info' line options)))]
property-indent
(not (code-fence-block-line? line)))
(property-value-line? line property-indent)
(recur more
(cons block-line-info' more-block-line-infos)
(conj lines line)
seen-block?
property-indent
false)
:else
(if-let [[_ _ title] (re-matches markdown-block-line-re line)]
(if (rendered-line-matches-block? block-line-info' title)
(let [lines' (cond-> lines
(and insert-blank-before-first-block?
(not seen-block?)) (conj "")
true (into (decorate-block-line db block-line-info' line options)))]
(recur more
more-block-line-infos
lines'
true
nil
(and (:code-block? block-line-info')
(code-fence-block-line? title))))
(recur more
more-block-line-infos
lines'
true
nil))
(cons block-line-info' more-block-line-infos)
(conj lines line)
seen-block?
property-indent
false))
(let [property-indent' (property-line-indent line)]
(recur more
(cons block-line-info' more-block-line-infos)
(conj lines line)
seen-block?
property-indent'))))))))
property-indent'
false))))))))
(defn- add-page-id-to-rendered-content
[db page content options]

View File

@@ -257,6 +257,12 @@
(js->clj parsed :keywordize-keys true)
(recur (string/index-of output "[" (inc start)))))))))
(defn- qmd-json-parse-failed
[result]
(qmd-error :qmd-json-parse-failed
"Unable to parse QMD JSON output"
result))
(defn extract-block-ids
[results]
(->> (or results [])
@@ -472,39 +478,43 @@
qmd-result (<run-qmd (qsearch-args action))]
(if-not (zero? (:exit qmd-result))
(qmd-command-failed "qmd query failed" qmd-result)
(let [results (vec (or (parse-qmd-json-output (:out qmd-result)) []))
result-count (count results)]
(p/let [results (<expand-qmd-result-snippets action results)]
(let [ids (extract-block-ids results)]
(cond
(empty? results)
(qsearch-ok-data action 0 [] [])
(if-let [parsed-results (parse-qmd-json-output (:out qmd-result))]
(let [results (vec parsed-results)
result-count (count results)]
(p/let [results (<expand-qmd-result-snippets action results)]
(let [ids (extract-block-ids results)]
(cond
(empty? results)
(qsearch-ok-data action 0 [] [])
(not (seq ids))
{:status :error
:error {:code :qmd-no-block-ids
:message "QMD results did not include Markdown Mirror block ids"
:hint "Run `logseq qmd init [--graph <graph>]` and retry"}}
(not (seq ids))
{:status :error
:error {:code :qmd-no-block-ids
:message "QMD results did not include Markdown Mirror block ids"
:hint "Run `logseq qmd init [--graph <graph>]` and retry"}}
:else
(let [result-by-id (qmd-result-by-id results)]
(p/let [entities (p/all
(map (fn [id]
(transport/invoke cfg :thread-api/pull
[(:repo action) qsearch-pull-selector id]))
ids))
pairs (mapv vector ids entities)
items (->> pairs
(keep (fn [[id entity]]
(when (qsearch-entity-present? entity)
(normalize-qsearch-item entity
(get result-by-id id)))))
vec)
missing-ids (->> pairs
(keep (fn [[id entity]]
(when-not (qsearch-entity-present? entity) id)))
vec)
items (<normalize-qsearch-item-refs cfg (:repo action) items)
human-data (when (human-output? config)
(<qsearch-human-data cfg action entities))]
(qsearch-ok-data action result-count items missing-ids human-data))))))))))
:else
(let [result-by-id (qmd-result-by-id results)]
(p/let [entities (p/all
(map (fn [id]
(transport/invoke cfg :thread-api/pull
[(:repo action) qsearch-pull-selector id]))
ids))
pairs (mapv vector ids entities)
items (->> pairs
(keep (fn [[id entity]]
(when (qsearch-entity-present? entity)
(normalize-qsearch-item entity
(get result-by-id id)))))
vec)
missing-ids (->> pairs
(keep (fn [[id entity]]
(when-not (qsearch-entity-present? entity) id)))
vec)
items (<normalize-qsearch-item-refs cfg (:repo action) items)
human-data (when (human-output? config)
(<qsearch-human-data cfg action entities))]
(qsearch-ok-data action result-count items missing-ids human-data)))))))
(if (string/blank? (:out qmd-result))
(qsearch-ok-data action 0 [] [])
(qmd-json-parse-failed qmd-result))))))

View File

@@ -204,6 +204,28 @@
(p/catch (fn [e] (is false (str "unexpected error: " e))))
(p/finally done)))))
(deftest multiline-markdown-list-lines-do-not-consume-next-block-db-id-test
(async done
(let [{:keys [platform files]} (fake-platform)
page-uuid #uuid "33333333-3333-4333-8333-333333333340"
conn (db-test/create-conn-with-blocks
{:pages-and-blocks [{:page {:block/title "Multiline List"
:block/uuid page-uuid}
:blocks [{:block/title "first line\n- not a child"}
{:block/title "after multiline"}]}]})
page (db-test/find-page-by-title @conn "Multiline List")
first-block (db-test/find-block-by-content @conn "first line\n- not a child")
after-multiline (db-test/find-block-by-content @conn "after multiline")]
(-> (markdown-mirror/<mirror-page! test-repo @conn (:db/id page) {:platform platform})
(p/then (fn [_]
(is (= (str (page-marker page-uuid) "\n\n"
"- first line " (block-id-comment first-block) "\n"
" - not a child\n"
"- after multiline " (block-id-comment after-multiline))
(get @files (page-path "pages/Multiline List.md"))))))
(p/catch (fn [e] (is false (str "unexpected error: " e))))
(p/finally done)))))
(deftest nested-block-db-id-comments-preserve-indent-test
(async done
(let [{:keys [platform files]} (fake-platform)
@@ -250,6 +272,32 @@
(p/catch (fn [e] (is false (str "unexpected error: " e))))
(p/finally done)))))
(deftest code-block-markdown-list-lines-do-not-consume-block-db-ids-test
(async done
(let [{:keys [platform files]} (fake-platform)
page-uuid #uuid "33333333-3333-4333-8333-333333333339"
conn (db-test/create-conn-with-blocks
{:pages-and-blocks [{:page {:block/title "Code List"
:block/uuid page-uuid}
:blocks [{:block/title "- not an outline block\n(+ 1 2)"
:build/tags [:logseq.class/Code-block]
:build/properties {:logseq.property.node/display-type :code
:logseq.property.code/lang "clojure"}}
{:block/title "after code"}]}]})
page (db-test/find-page-by-title @conn "Code List")
after-code (db-test/find-block-by-content @conn "after code")]
(-> (markdown-mirror/<mirror-page! test-repo @conn (:db/id page) {:platform platform})
(p/then (fn [_]
(is (= (str (page-marker page-uuid) "\n\n"
"- ```clojure\n"
" - not an outline block\n"
" (+ 1 2)\n"
" ```\n"
"- after code " (block-id-comment after-code))
(get @files (page-path "pages/Code List.md"))))))
(p/catch (fn [e] (is false (str "unexpected error: " e))))
(p/finally done)))))
(deftest property-value-lines-do-not-consume-block-db-id-comments-test
(async done
(let [{:keys [platform files]} (fake-platform)

View File

@@ -752,6 +752,28 @@
(p/catch (fn [e] (is false (str "unexpected error: " e))))
(p/finally done))))
(deftest test-execute-qsearch-errors-on-malformed-qmd-json
(async done
(-> (p/with-redefs [qmd-command/<run-qmd
(fn [_]
(p/resolved {:exit 0
:out "QMD finished without JSON output"
:err ""}))
cli-server/ensure-server! (fn [config _repo] config)]
(qmd-command/execute-qsearch
{:type :qsearch
:repo "logseq_db_demo"
:query "alpha"
:collection "custom"}
{}))
(p/then (fn [result]
(is (= :error (:status result)))
(is (= :qmd-json-parse-failed (get-in result [:error :code])))
(is (string/includes? (or (get-in result [:error :message]) "")
"parse QMD JSON"))))
(p/catch (fn [e] (is false (str "unexpected error: " e))))
(p/finally done))))
(deftest test-execute-qsearch-errors-when-qmd-results-have-no-block-ids
(async done
(-> (p/with-redefs [qmd-command/<run-qmd