mirror of
https://github.com/logseq/logseq.git
synced 2026-04-24 14:14:55 +00:00
enhancement: add offline CLI search and predefined query
to address https://github.com/logseq/logseq/pull/12000#issuecomment-3177406045. Also bump version for initial release
This commit is contained in:
11
deps/cli/README.md
vendored
11
deps/cli/README.md
vendored
@@ -12,7 +12,7 @@ This section assumes you have installed the CLI from npm or via the [dev
|
||||
setup](#setup). If you haven't, substitute `node cli.mjs` for `logseq` e.g.
|
||||
`node.cli.mjs -h`.
|
||||
|
||||
All commands excepts for `search` can be used offline or on CI. The `search` command and any command that has an api-query-token option require the [HTTP Server](https://docs.logseq.com/#/page/local%20http%20server) to be turned on.
|
||||
All commands can be used offline or on CI. The `search` command and any command that has an api-query-token option require the [HTTP API Server](https://docs.logseq.com/#/page/local%20http%20server) to be turned on.
|
||||
|
||||
Now let's use it!
|
||||
|
||||
@@ -54,13 +54,20 @@ $ logseq show db-test
|
||||
| Graph created by commit | https://github.com/logseq/logseq/commit/3c93fd2637 |
|
||||
| Graph imported by | :cli/create-graph |
|
||||
|
||||
# Search your current graph and print results one per line like grep
|
||||
# Search your current graph and print highlighted results one per line like grep
|
||||
$ logseq search woot -a my-token
|
||||
Search found 100 results:
|
||||
dev:db-export woot woot.edn && dev:db-create woot2 woot.edn
|
||||
dev:db-diff woot woot2
|
||||
...
|
||||
|
||||
# Search a local graph
|
||||
$ logseq search woot page
|
||||
Search found 23 results:
|
||||
Node page
|
||||
Annotation page
|
||||
...
|
||||
|
||||
# Query a graph locally using `d/entity` id(s) like an integer or a :db/ident
|
||||
# Can also specify a uuid string to fetch an entity
|
||||
$ logseq query woot 10 :logseq.class/Tag
|
||||
|
||||
2
deps/cli/package.json
vendored
2
deps/cli/package.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@logseq/cli",
|
||||
"version": "0.1.0-alpha.1",
|
||||
"version": "0.1.0",
|
||||
"description": "Logseq CLI",
|
||||
"bin": {
|
||||
"logseq": "cli.mjs"
|
||||
|
||||
6
deps/cli/src/logseq/cli.cljs
vendored
6
deps/cli/src/logseq/cli.cljs
vendored
@@ -69,12 +69,12 @@
|
||||
:args->opts [:graphs] :coerce {:graphs []} :require [:graphs]}
|
||||
{:cmds ["search"]
|
||||
:fn (lazy-load-fn 'logseq.cli.commands.search/search)
|
||||
:desc "Search current DB graph"
|
||||
:args->opts [:search-terms] :coerce {:search-terms []} :require [:search-terms]
|
||||
:desc "Search DB graph"
|
||||
:args->opts [:graph :search-terms] :coerce {:search-terms []} :require [:graph]
|
||||
:spec cli-spec/search}
|
||||
{:cmds ["query"] :desc "Query DB graph(s)"
|
||||
:fn (lazy-load-fn 'logseq.cli.commands.query/query)
|
||||
:args->opts [:graph :args] :coerce {:args []} :no-keyword-opts true :require [:graph :args]
|
||||
:args->opts [:graph :args] :coerce {:args []} :no-keyword-opts true :require [:graph]
|
||||
:spec cli-spec/query}
|
||||
{:cmds ["export-edn"] :desc "Export DB graph as EDN"
|
||||
:fn (lazy-load-fn 'logseq.cli.commands.export-edn/export)
|
||||
|
||||
26
deps/cli/src/logseq/cli/commands/query.cljs
vendored
26
deps/cli/src/logseq/cli/commands/query.cljs
vendored
@@ -64,20 +64,34 @@
|
||||
m)))
|
||||
args))
|
||||
|
||||
(defn- local-datalog-query [db query*]
|
||||
(let [query (into query* [:in '$ '%]) ;; assumes no :in are in queries
|
||||
res (d/q query db (rules/extract-rules rules/db-query-dsl-rules))]
|
||||
;; Remove nesting for most queries which just have one :find binding
|
||||
(if (= 1 (count (first res))) (mapv first res) res)))
|
||||
|
||||
(defn- local-query
|
||||
[{{:keys [graph args graphs properties-readable]} :opts}]
|
||||
[{{:keys [graph args graphs properties-readable title-query]} :opts}]
|
||||
(let [graphs' (into [graph] graphs)]
|
||||
(doseq [graph' graphs']
|
||||
(if (fs/existsSync (cli-util/get-graph-dir graph'))
|
||||
(let [conn (apply sqlite-cli/open-db! (cli-util/->open-db-args graph))
|
||||
query* (when (string? (first args)) (common-util/safe-read-string {:log-error? false} (first args)))
|
||||
;; If datalog query detected run it or else default to entity lookups
|
||||
results (if (and (vector? query*) (= :find (first query*)))
|
||||
;; assumes no :in are in queries
|
||||
(let [query' (into query* [:in '$ '%])
|
||||
res (d/q query' @conn (rules/extract-rules rules/db-query-dsl-rules))]
|
||||
results (cond
|
||||
;; Run datalog query if detected
|
||||
(and (vector? query*) (= :find (first query*)))
|
||||
(local-datalog-query @conn query*)
|
||||
;; Runs predefined title query. Predefined queries could better off in a separate command
|
||||
;; since they could be more powerful and have different args than query command
|
||||
title-query
|
||||
(let [query '[:find (pull ?b [*])
|
||||
:in $ % ?search-term
|
||||
:where (block-content ?b ?search-term)]
|
||||
res (d/q query @conn (rules/extract-rules rules/db-query-dsl-rules)
|
||||
(string/join " " args))]
|
||||
;; Remove nesting for most queries which just have one :find binding
|
||||
(if (= 1 (count (first res))) (mapv first res) res))
|
||||
:else
|
||||
(local-entities-query @conn properties-readable args))]
|
||||
(when (> (count graphs') 1)
|
||||
(println "Results for graph" (pr-str graph')))
|
||||
|
||||
54
deps/cli/src/logseq/cli/commands/search.cljs
vendored
54
deps/cli/src/logseq/cli/commands/search.cljs
vendored
@@ -1,9 +1,12 @@
|
||||
(ns logseq.cli.commands.search
|
||||
"Search command"
|
||||
(:require [clojure.pprint :as pprint]
|
||||
(:require ["fs" :as fs]
|
||||
[clojure.pprint :as pprint]
|
||||
[clojure.string :as string]
|
||||
[logseq.cli.util :as cli-util]
|
||||
[datascript.core :as d]
|
||||
[logseq.cli.text-util :as cli-text-util]
|
||||
[logseq.cli.util :as cli-util]
|
||||
[logseq.db.common.sqlite-cli :as sqlite-cli]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defn- highlight
|
||||
@@ -23,21 +26,42 @@
|
||||
(recur new-result)
|
||||
new-result)))))
|
||||
|
||||
(defn search
|
||||
[{{:keys [search-terms api-query-token raw limit]} :opts}]
|
||||
(-> (p/let [resp (cli-util/api-fetch api-query-token
|
||||
"logseq.app.search"
|
||||
[(string/join " " search-terms) {:limit limit}])]
|
||||
(defn- format-results
|
||||
"Results are a list of strings. Handles highlighting search term in results and printing options like :raw"
|
||||
[results search-term {:keys [raw api?]}]
|
||||
(println "Search found" (count results) "results:")
|
||||
(if raw
|
||||
(pprint/pprint results)
|
||||
(let [highlight-fn (if api?
|
||||
highlight-content-query
|
||||
#(string/replace % search-term (highlight search-term)))]
|
||||
(println (string/join "\n"
|
||||
(->> results
|
||||
(map #(string/replace % "\n" "\\\\n"))
|
||||
(map highlight-fn)))))))
|
||||
|
||||
(defn- api-search
|
||||
[search-term {{:keys [api-query-token raw limit]} :opts}]
|
||||
(-> (p/let [resp (cli-util/api-fetch api-query-token "logseq.app.search" [search-term {:limit limit}])]
|
||||
(if (= 200 (.-status resp))
|
||||
(p/let [body (.json resp)]
|
||||
(let [{:keys [blocks]} (js->clj body :keywordize-keys true)]
|
||||
(println "Search found" (count blocks) "results:")
|
||||
(if raw
|
||||
(pprint/pprint blocks)
|
||||
(println (string/join "\n"
|
||||
(->> blocks
|
||||
(map :block/title)
|
||||
(map #(string/replace % "\n" "\\\\n"))
|
||||
(map highlight-content-query)))))))
|
||||
(format-results (map :block/title blocks) search-term {:raw raw :api? true})))
|
||||
(cli-util/api-handle-error-response resp)))
|
||||
(p/catch cli-util/command-catch-handler)))
|
||||
|
||||
(defn- local-search [search-term {{:keys [graph raw limit]} :opts}]
|
||||
(if (fs/existsSync (cli-util/get-graph-dir graph))
|
||||
(let [conn (apply sqlite-cli/open-db! (cli-util/->open-db-args graph))
|
||||
nodes (->> (d/datoms @conn :aevt :block/title)
|
||||
(filter (fn [datom]
|
||||
(string/includes? (:v datom) search-term)))
|
||||
(take limit)
|
||||
(map :v))]
|
||||
(format-results nodes search-term {:raw raw}))
|
||||
(cli-util/error "Graph" (pr-str graph) "does not exist")))
|
||||
|
||||
(defn search [{{:keys [graph search-terms api-query-token]} :opts :as m}]
|
||||
(if api-query-token
|
||||
(api-search (string/join " " (into [graph] search-terms)) m)
|
||||
(local-search (string/join " " search-terms) m)))
|
||||
3
deps/cli/src/logseq/cli/spec.cljs
vendored
3
deps/cli/src/logseq/cli/spec.cljs
vendored
@@ -28,12 +28,13 @@
|
||||
:properties-readable {:alias :p
|
||||
:coerce :boolean
|
||||
:desc "Make properties on entity queries show property values instead of ids"}
|
||||
:title-query {:alias :t
|
||||
:desc "Invokes local query on :block/title"}
|
||||
:api-query-token {:alias :a
|
||||
:desc "Query current graph with api server token"}})
|
||||
|
||||
(def search
|
||||
{:api-query-token {:alias :a
|
||||
:require true
|
||||
:desc "Api server token"}
|
||||
:raw {:alias :r
|
||||
:desc "Print raw response"}
|
||||
|
||||
Reference in New Issue
Block a user