Sfoglia il codice sorgente

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
Gabriel Horner 2 mesi fa
parent
commit
cc1093ac85

+ 9 - 2
deps/cli/README.md

@@ -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

+ 1 - 1
deps/cli/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@logseq/cli",
-  "version": "0.1.0-alpha.1",
+  "version": "0.1.0",
   "description": "Logseq CLI",
   "bin": {
     "logseq": "cli.mjs"

+ 3 - 3
deps/cli/src/logseq/cli.cljs

@@ -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)

+ 20 - 6
deps/cli/src/logseq/cli/commands/query.cljs

@@ -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')))

+ 39 - 15
deps/cli/src/logseq/cli/commands/search.cljs

@@ -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)))

+ 2 - 1
deps/cli/src/logseq/cli/spec.cljs

@@ -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"}