043-logseq-cli-tag-property-management.md

This commit is contained in:
rcmerci
2026-03-01 20:46:20 +08:00
parent dfa80b084e
commit 85ebeb976e
10 changed files with 1319 additions and 276 deletions

View File

@@ -34,13 +34,9 @@
:tags {:desc "EDN vector of tags. Identifiers can be id, :db/ident, or :block/title."}
:properties {:desc "EDN map of built-in properties. Identifiers can be id, :db/ident, or :block/title."}})
(def ^:private add-tag-spec
{:name {:desc "Tag name"}})
(def entries
[(core/command-entry ["add" "block"] :add-block "Add blocks" content-add-spec)
(core/command-entry ["add" "page"] :add-page "Create page" add-page-spec)
(core/command-entry ["add" "tag"] :add-tag "Create tag" add-tag-spec)])
(core/command-entry ["add" "page"] :add-page "Create page" add-page-spec)])
(defn- today-page-title
[config repo]
@@ -1011,43 +1007,6 @@
:error {:code :missing-page-name
:message "page name is required"}}))))
(defn- normalize-tag-name-option
[value]
(let [normalized (normalize-tag-value value)]
(when (string? normalized)
(let [name (string/trim normalized)]
(when (seq name)
name)))))
(defn build-add-tag-action
[options repo]
(if-not (seq repo)
{:ok? false
:error {:code :missing-repo
:message "repo is required for add"}}
(let [name (normalize-tag-name-option (:name options))]
(if (seq name)
{:ok? true
:action {:type :add-tag
:repo repo
:graph (core/repo->graph repo)
:name name}}
{:ok? false
:error {:code :missing-tag-name
:message "tag name is required"}}))))
(defn- pull-page-by-name
[config repo page-name]
(pull-entity config repo
[:db/id :block/name :block/title :block/uuid
{:block/tags [:db/id :db/ident :block/name :block/title]}]
[:block/name (common-util/page-name-sanity-lc page-name)]))
(defn- tag-entity?
[entity]
(some #(= :logseq.class/Tag (:db/ident %))
(:block/tags entity)))
(defn execute-add-block
[action config]
(-> (p/let [cfg (cli-server/ensure-server! config (:repo action))
@@ -1134,33 +1093,3 @@
created-ids (resolve-created-page-ids cfg (:repo action) (:page action) create-result)]
{:status :ok
:data {:result created-ids}})))
(defn execute-add-tag
[action config]
(-> (p/let [cfg (cli-server/ensure-server! config (:repo action))
existing (pull-page-by-name cfg (:repo action) (:name action))
existing-id (:db/id existing)
_ (when (and existing-id (not (tag-entity? existing)))
(throw (ex-info "tag already exists as a page and is not a tag"
{:code :tag-name-conflict
:name (:name action)})))
_ (when-not existing-id
(transport/invoke cfg :thread-api/apply-outliner-ops false
[(:repo action)
[[:create-page [(:name action) {:class? true}]]]
{}]))
page (or (when existing-id existing)
(pull-page-by-name cfg (:repo action) (:name action)))
page-id (:db/id page)
_ (when-not page-id
(throw (ex-info "tag not found after create"
{:code :tag-not-found
:name (:name action)})))
_ (when-not (tag-entity? page)
(throw (ex-info "created entity is not tagged as :logseq.class/Tag"
{:code :tag-create-not-tag
:name (:name action)
:id page-id})))
created-ids (normalize-created-ids [page-id])]
{:status :ok
:data {:result created-ids}})))

View File

@@ -93,7 +93,7 @@
(defn top-level-summary
[table]
(let [groups [{:title "Graph Inspect and Edit"
:commands #{"list" "add" "remove" "update" "query" "show"}}
:commands #{"list" "add" "upsert" "remove" "update" "query" "show"}}
{:title "Graph Management"
:commands #{"graph" "server" "doctor"}}]
render-group (fn [{:keys [title commands]}]

View File

@@ -8,27 +8,66 @@
[logseq.common.util :as common-util]
[promesa.core :as p]))
(def ^:private remove-spec
(def ^:private remove-block-spec
{:id {:desc "Block db/id or EDN vector of ids"}
:uuid {:desc "Block UUID"}
:page {:desc "Page name"}})
:uuid {:desc "Block UUID"}})
(def ^:private remove-page-spec
{:name {:desc "Page name"}})
(def ^:private remove-entity-spec
{:id {:desc "Entity db/id"
:coerce :long}
:name {:desc "Entity name"}})
(def entries
[(core/command-entry ["remove"] :remove "Remove blocks or pages" remove-spec)])
[(core/command-entry ["remove" "block"] :remove-block "Remove blocks" remove-block-spec)
(core/command-entry ["remove" "page"] :remove-page "Remove page" remove-page-spec)
(core/command-entry ["remove" "tag"] :remove-tag "Remove tag" remove-entity-spec)
(core/command-entry ["remove" "property"] :remove-property "Remove property" remove-entity-spec)])
(defn invalid-options?
[opts]
(let [id-result (id-command/parse-id-option (:id opts))]
(cond
(and (some? (:id opts)) (not (:ok? id-result)))
(:message id-result)
[command opts]
(case command
:remove-block
(let [id-result (id-command/parse-id-option (:id opts))
selectors (filter some? [(:id opts) (some-> (:uuid opts) string/trim)])]
(cond
(and (some? (:id opts)) (not (:ok? id-result)))
(:message id-result)
:else
nil)))
(> (count selectors) 1)
"only one of --id or --uuid is allowed"
:else
nil))
(:remove-tag :remove-property)
(let [name (some-> (:name opts) string/trim)
selectors (filter some? [(:id opts) name])]
(cond
(> (count selectors) 1)
"only one of --id or --name is allowed"
(and (contains? opts :name) (string/blank? (or (:name opts) "")))
"name must be non-empty"
:else
nil))
nil))
(def ^:private block-id-selector
[:db/id :block/uuid])
(def ^:private page-id-selector
[:db/id :block/uuid :block/name :block/title])
(def ^:private entity-selector
[:db/id :db/ident :block/uuid :block/name :block/title
:logseq.property/type :logseq.property/public? :logseq.property/built-in?
{:block/tags [:db/id :db/ident :block/title :block/name]}])
(defn- fetch-block-by-id
[config repo id]
(transport/invoke config :thread-api/pull false
@@ -48,6 +87,11 @@
(transport/invoke config :thread-api/apply-outliner-ops false
[repo [[:delete-blocks [ids {}]]] {}]))
(defn- delete-page-by-uuid
[config repo page-uuid]
(transport/invoke config :thread-api/apply-outliner-ops false
[repo [[:delete-page [page-uuid]]] {}]))
(defn- remove-block-id
[config repo id]
(p/let [entity (fetch-block-by-id config repo id)]
@@ -74,8 +118,8 @@
:missing-ids missing-ids
:result result}))
(defn- perform-remove
[config {:keys [repo ids multi-id? uuid page]}]
(defn- perform-remove-block
[config {:keys [repo ids multi-id? uuid]}]
(cond
(and (seq ids) multi-id?)
(remove-block-ids-best-effort config repo ids)
@@ -91,59 +135,288 @@
(delete-block-ids config repo [id])
(throw (ex-info "block not found" {:code :block-not-found})))))
(seq page)
(p/let [entity (transport/invoke config :thread-api/pull false
[repo [:db/id :block/uuid] [:block/name page]])]
(if-let [page-uuid (:block/uuid entity)]
(transport/invoke config :thread-api/apply-outliner-ops false
[repo [[:delete-page [page-uuid]]] {}])
(throw (ex-info "page not found" {:code :page-not-found}))))
:else
(p/rejected (ex-info "block is required" {:code :missing-target}))))
(defn- resolve-page-by-name
[config repo name]
(transport/invoke config :thread-api/pull false
[repo page-id-selector [:block/name (common-util/page-name-sanity-lc name)]]))
(defn- item-id
[item]
(or (:db/id item) (:id item)))
(defn- item-name
[item]
(or (:block/title item) (:title item) (:name item) (:block/name item)))
(defn- normalize-name
[value]
(common-util/page-name-sanity-lc (or value "")))
(defn- tag-entity?
[entity]
(some #(= :logseq.class/Tag (:db/ident %))
(:block/tags entity)))
(defn- property-entity?
[entity]
(some? (:logseq.property/type entity)))
(defn- list-matches-by-name
[config repo method name]
(let [normalized (normalize-name name)]
(p/let [items (transport/invoke config method false [repo {:include-built-in true :expand true}])
matches (->> (or items [])
(filter (fn [item]
(= normalized (normalize-name (item-name item)))))
vec)]
matches)))
(defn- ambiguous-error
[code label name matches]
(let [candidates (->> matches
(map (fn [item]
{:id (item-id item)
:name (item-name item)}))
(filter :id)
vec)]
{:code code
:message (str "multiple " label "s match name: " name "; rerun with --id")
:candidates candidates}))
(defn- resolve-target
[config repo {:keys [id name]} {:keys [list-method not-found-code ambiguous-code label]}]
(cond
(some? id)
(p/resolved {:ok? true
:lookup id
:id id})
(seq name)
(p/let [matches (list-matches-by-name config repo list-method name)]
(cond
(empty? matches)
{:ok? false
:error {:code not-found-code
:message (str label " not found")}}
(> (count matches) 1)
{:ok? false
:error (ambiguous-error ambiguous-code label name matches)}
:else
{:ok? true
:lookup [:block/name (normalize-name (or (item-name (first matches)) name))]
:id (item-id (first matches))
:name (item-name (first matches))}))
:else
(p/rejected (ex-info "block or page required" {:code :missing-target}))))
(p/resolved {:ok? false
:error {:code :missing-target
:message (str label " name or id is required")}})))
(defn- validate-tag-target
[entity]
(cond
(nil? (:db/id entity))
{:ok? false
:error {:code :tag-not-found
:message "tag not found"}}
(not (tag-entity? entity))
{:ok? false
:error {:code :invalid-tag-target
:message "target is not a tag"}}
(true? (:logseq.property/built-in? entity))
{:ok? false
:error {:code :tag-built-in
:message "built-in tag cannot be removed"}}
(false? (:logseq.property/public? entity))
{:ok? false
:error {:code :tag-hidden
:message "hidden tag cannot be removed"}}
(nil? (:block/uuid entity))
{:ok? false
:error {:code :tag-not-found
:message "tag uuid not found"}}
:else
{:ok? true
:entity entity}))
(defn- validate-property-target
[entity]
(cond
(nil? (:db/id entity))
{:ok? false
:error {:code :property-not-found
:message "property not found"}}
(not (property-entity? entity))
{:ok? false
:error {:code :invalid-property-target
:message "target is not a property"}}
(true? (:logseq.property/built-in? entity))
{:ok? false
:error {:code :property-built-in
:message "built-in property cannot be removed"}}
(false? (:logseq.property/public? entity))
{:ok? false
:error {:code :property-hidden
:message "hidden property cannot be removed"}}
(nil? (:block/uuid entity))
{:ok? false
:error {:code :property-not-found
:message "property uuid not found"}}
:else
{:ok? true
:entity entity}))
(defn- perform-remove-entity
[config action {:keys [list-method not-found-code ambiguous-code label validate-fn]}]
(p/let [resolved (resolve-target config (:repo action) action
{:list-method list-method
:not-found-code not-found-code
:ambiguous-code ambiguous-code
:label label})]
(if-not (:ok? resolved)
{:status :error
:error (:error resolved)}
(p/let [entity (transport/invoke config :thread-api/pull false
[(:repo action) entity-selector (:lookup resolved)])
validation (validate-fn entity)]
(if-not (:ok? validation)
{:status :error
:error (:error validation)}
(p/let [result (delete-page-by-uuid config (:repo action) (:block/uuid entity))]
{:status :ok
:data {:result result
:id (:db/id entity)
:name (or (:name resolved) (:block/title entity) (:block/name entity))}}))))))
(defn build-action
[options repo]
[command options repo]
(if-not (seq repo)
{:ok? false
:error {:code :missing-repo
:message "repo is required for remove"}}
(let [id-result (id-command/parse-id-option (:id options))
ids (:value id-result)
multi-id? (:multi? id-result)
uuid (some-> (:uuid options) string/trim)
page (some-> (:page options) string/trim)
selectors (filter some? [(:id options) uuid page])]
(cond
(empty? selectors)
{:ok? false
:error {:code :missing-target
:message "block or page is required"}}
(case command
:remove-block
(let [id-result (id-command/parse-id-option (:id options))
ids (:value id-result)
multi-id? (:multi? id-result)
uuid (some-> (:uuid options) string/trim)
selectors (filter some? [(:id options) uuid])]
(cond
(empty? selectors)
{:ok? false
:error {:code :missing-target
:message "block is required"}}
(> (count selectors) 1)
{:ok? false
:error {:code :invalid-options
:message "only one of --id, --uuid, or --page is allowed"}}
(> (count selectors) 1)
{:ok? false
:error {:code :invalid-options
:message "only one of --id or --uuid is allowed"}}
(and (some? (:id options)) (not (:ok? id-result)))
{:ok? false
:error {:code :invalid-options
:message (:message id-result)}}
(and (some? (:id options)) (not (:ok? id-result)))
{:ok? false
:error {:code :invalid-options
:message (:message id-result)}}
:else
{:ok? true
:action {:type :remove
:repo repo
:id (when (and (seq ids) (not multi-id?)) (first ids))
:ids ids
:multi-id? multi-id?
:uuid uuid
:page page}}))))
:else
{:ok? true
:action {:type :remove-block
:repo repo
:graph (core/repo->graph repo)
:id (when (and (seq ids) (not multi-id?)) (first ids))
:ids ids
:multi-id? multi-id?
:uuid uuid}}))
(defn execute-remove
:remove-page
(let [name (some-> (:name options) string/trim)]
(if (seq name)
{:ok? true
:action {:type :remove-page
:repo repo
:graph (core/repo->graph repo)
:name name}}
{:ok? false
:error {:code :missing-page-name
:message "page name is required"}}))
(:remove-tag :remove-property)
(let [name (some-> (:name options) string/trim)
id (:id options)
selectors (filter some? [id name])]
(cond
(empty? selectors)
{:ok? false
:error {:code :missing-target
:message "name or id is required"}}
(> (count selectors) 1)
{:ok? false
:error {:code :invalid-options
:message "only one of --id or --name is allowed"}}
:else
{:ok? true
:action {:type command
:repo repo
:graph (core/repo->graph repo)
:id id
:name name}}))
{:ok? false
:error {:code :unknown-command
:message (str "unknown remove command: " command)}})))
(defn execute-remove-block
[action config]
(-> (p/let [cfg (cli-server/ensure-server! config (:repo action))
result (perform-remove cfg action)]
result (perform-remove-block cfg action)]
{:status :ok
:data (cond-> {:result result}
(map? result) (merge (dissoc result :result)))})))
(defn execute-remove-page
[action config]
(-> (p/let [cfg (cli-server/ensure-server! config (:repo action))
entity (resolve-page-by-name cfg (:repo action) (:name action))]
(if-let [page-uuid (:block/uuid entity)]
(p/let [result (delete-page-by-uuid cfg (:repo action) page-uuid)]
{:status :ok
:data {:result result}})
{:status :error
:error {:code :page-not-found
:message "page not found"}}))))
(defn execute-remove-tag
[action config]
(-> (p/let [cfg (cli-server/ensure-server! config (:repo action))]
(perform-remove-entity cfg action
{:list-method :thread-api/api-list-tags
:not-found-code :tag-not-found
:ambiguous-code :ambiguous-tag-name
:label "tag"
:validate-fn validate-tag-target}))))
(defn execute-remove-property
[action config]
(-> (p/let [cfg (cli-server/ensure-server! config (:repo action))]
(perform-remove-entity cfg action
{:list-method :thread-api/api-list-properties
:not-found-code :property-not-found
:ambiguous-code :ambiguous-property-name
:label "property"
:validate-fn validate-property-target}))))

View File

@@ -0,0 +1,227 @@
(ns logseq.cli.command.upsert
"Upsert-related CLI commands."
(:require [clojure.string :as string]
[logseq.cli.command.core :as core]
[logseq.cli.server :as cli-server]
[logseq.cli.transport :as transport]
[logseq.common.util :as common-util]
[promesa.core :as p]))
(def ^:private upsert-tag-spec
{:name {:desc "Tag name"}})
(def ^:private upsert-property-spec
{:name {:desc "Property name"}
:type {:desc "Property type (default, number, date, datetime, checkbox, url, node, json, string)"}
:cardinality {:desc "Property cardinality (one, many)"}
:hide {:desc "Hide property"
:coerce :boolean}
:public {:desc "Set property public visibility"
:coerce :boolean}})
(def entries
[(core/command-entry ["upsert" "tag"] :upsert-tag "Upsert tag" upsert-tag-spec)
(core/command-entry ["upsert" "property"] :upsert-property "Upsert property" upsert-property-spec)])
(def ^:private property-types
#{"default" "number" "date" "datetime" "checkbox" "url" "node" "json" "string"})
(def ^:private property-cardinalities
#{"one" "many"})
(defn- normalize-tag-name
[value]
(let [text (some-> value string/trim (string/replace #"^#+" ""))]
(when (seq text)
text)))
(defn- normalize-property-name
[value]
(let [text (some-> value string/trim)]
(when (seq text)
text)))
(defn- normalize-property-type
[value]
(some-> value string/trim string/lower-case))
(defn- normalize-property-cardinality
[value]
(let [v (some-> value string/trim string/lower-case)]
(case v
"db.cardinality/one" "one"
"db.cardinality/many" "many"
v)))
(defn invalid-options?
[command opts]
(case command
:upsert-property
(let [type' (normalize-property-type (:type opts))
cardinality' (normalize-property-cardinality (:cardinality opts))]
(cond
(and (seq (:type opts)) (not (contains? property-types type')))
(str "invalid type: " (:type opts))
(and (seq (:cardinality opts)) (not (contains? property-cardinalities cardinality')))
(str "invalid cardinality: " (:cardinality opts))
:else
nil))
nil))
(defn build-tag-action
[options repo]
(if-not (seq repo)
{:ok? false
:error {:code :missing-repo
:message "repo is required for upsert"}}
(let [name (normalize-tag-name (:name options))]
(if (seq name)
{:ok? true
:action {:type :upsert-tag
:repo repo
:graph (core/repo->graph repo)
:name name}}
{:ok? false
:error {:code :missing-tag-name
:message "tag name is required"}}))))
(defn- cardinality->db
[value]
(when-let [v (normalize-property-cardinality value)]
(case v
"many" :db.cardinality/many
"one" :db.cardinality/one
nil)))
(defn- property-schema
[options]
(cond-> {}
(seq (:type options))
(assoc :logseq.property/type (keyword (normalize-property-type (:type options))))
(seq (:cardinality options))
(assoc :db/cardinality (cardinality->db (:cardinality options)))
(contains? options :hide)
(assoc :logseq.property/hide? (boolean (:hide options)))
(contains? options :public)
(assoc :logseq.property/public? (boolean (:public options)))))
(defn build-property-action
[options repo]
(if-not (seq repo)
{:ok? false
:error {:code :missing-repo
:message "repo is required for upsert"}}
(let [name (normalize-property-name (:name options))
invalid-message (invalid-options? :upsert-property options)]
(cond
(not (seq name))
{:ok? false
:error {:code :missing-property-name
:message "property name is required"}}
(seq invalid-message)
{:ok? false
:error {:code :invalid-options
:message invalid-message}}
:else
{:ok? true
:action {:type :upsert-property
:repo repo
:graph (core/repo->graph repo)
:name name
:schema (property-schema options)}}))))
(defn- pull-page-by-name
[config repo page-name selector]
(transport/invoke config :thread-api/pull false
[repo selector [:block/name (common-util/page-name-sanity-lc page-name)]]))
(defn- tag-entity?
[entity]
(some #(= :logseq.class/Tag (:db/ident %))
(:block/tags entity)))
(defn execute-upsert-tag
[action config]
(-> (p/let [cfg (cli-server/ensure-server! config (:repo action))
existing (pull-page-by-name cfg (:repo action) (:name action)
[:db/id :block/name :block/title
{:block/tags [:db/ident]}])
existing-id (:db/id existing)]
(cond
(and existing-id (not (tag-entity? existing)))
{:status :error
:error {:code :tag-name-conflict
:message "tag already exists as a page and is not a tag"}}
:else
(p/let [_ (when-not existing-id
(transport/invoke cfg :thread-api/apply-outliner-ops false
[(:repo action)
[[:create-page [(:name action) {:class? true}]]]
{}]))
page (or (when existing-id existing)
(pull-page-by-name cfg (:repo action) (:name action)
[:db/id :block/name :block/title
{:block/tags [:db/ident]}]))
page-id (:db/id page)]
(cond
(not page-id)
{:status :error
:error {:code :tag-not-found
:message "tag not found after upsert"}}
(not (tag-entity? page))
{:status :error
:error {:code :tag-create-not-tag
:message "created entity is not tagged as :logseq.class/Tag"}}
:else
{:status :ok
:data {:result [page-id]}}))))))
(def ^:private property-selector
[:db/id :db/ident :block/name :block/title :logseq.property/type])
(defn- property-entity?
[entity]
(some? (:logseq.property/type entity)))
(defn execute-upsert-property
[action config]
(-> (p/let [cfg (cli-server/ensure-server! config (:repo action))
existing (pull-page-by-name cfg (:repo action) (:name action) property-selector)
existing-id (:db/id existing)]
(cond
(and existing-id (not (property-entity? existing)))
{:status :error
:error {:code :property-name-conflict
:message "property already exists as a page and is not a property"}}
:else
(p/let [property-ident (when (property-entity? existing)
(:db/ident existing))
property-opts (cond-> {}
(nil? property-ident)
(assoc :property-name (:name action)))
_ (transport/invoke cfg :thread-api/apply-outliner-ops false
[(:repo action)
[[:upsert-property [property-ident
(:schema action)
property-opts]]]
{}])
property (pull-page-by-name cfg (:repo action) (:name action) property-selector)
property-id (:db/id property)]
(if property-id
{:status :ok
:data {:result [property-id]}}
{:status :error
:error {:code :property-not-found
:message "property not found after upsert"}}))))))

View File

@@ -11,6 +11,7 @@
[logseq.cli.command.remove :as remove-command]
[logseq.cli.command.server :as server-command]
[logseq.cli.command.show :as show-command]
[logseq.cli.command.upsert :as upsert-command]
[logseq.cli.command.update :as update-command]
[logseq.cli.server :as cli-server]
[promesa.core :as p]))
@@ -69,6 +70,13 @@
:message "tag name is required"}
:summary summary})
(defn- missing-property-name-result
[summary]
{:ok? false
:error {:code :missing-property-name
:message "property name is required"}
:summary summary})
(defn- missing-type-result
[summary]
{:ok? false
@@ -106,6 +114,7 @@
server-command/entries
list-command/entries
add-command/entries
upsert-command/entries
remove-command/entries
update-command/entries
query-command/entries
@@ -153,9 +162,6 @@
(seq (:blocks-file opts))
has-args?)
show-targets (filter some? [(:id opts) (:uuid opts) (:page opts)])
remove-targets (filter some? [(:id opts)
(some-> (:uuid opts) string/trim)
(some-> (:page opts) string/trim)])
update-sources (filter some? [(:id opts) (some-> (:uuid opts) string/trim)])]
(cond
(:help opts)
@@ -174,17 +180,24 @@
(and (= command :add-page) (not (seq (:page opts))))
(missing-page-name-result summary)
(and (= command :add-tag) (not (seq (some-> (:name opts) string/trim))))
(and (= command :upsert-tag) (not (seq (some-> (:name opts) string/trim))))
(missing-tag-name-result summary)
(and (= command :remove) (seq args))
(command-core/invalid-options-result summary "remove does not accept subcommands")
(and (= command :upsert-property) (not (seq (some-> (:name opts) string/trim))))
(missing-property-name-result summary)
(and (= command :remove) (empty? remove-targets))
(and (= command :upsert-property) (upsert-command/invalid-options? command opts))
(command-core/invalid-options-result summary (upsert-command/invalid-options? command opts))
(and (= command :remove-block) (empty? (filter some? [(:id opts) (some-> (:uuid opts) string/trim)])))
(missing-target-result summary)
(and (= command :remove) (> (count remove-targets) 1))
(command-core/invalid-options-result summary "only one of --id, --uuid, or --page is allowed")
(and (= command :remove-page) (not (seq (some-> (:name opts) string/trim))))
(missing-page-name-result summary)
(and (#{:remove-tag :remove-property} command)
(empty? (filter some? [(:id opts) (some-> (:name opts) string/trim)])))
(missing-target-result summary)
(and (= command :update-block) (update-command/invalid-options? opts))
(command-core/invalid-options-result summary (update-command/invalid-options? opts))
@@ -207,8 +220,9 @@
(list-command/invalid-options? command opts))
(command-core/invalid-options-result summary (list-command/invalid-options? command opts))
(and (= command :remove) (remove-command/invalid-options? opts))
(command-core/invalid-options-result summary (remove-command/invalid-options? opts))
(and (#{:remove-block :remove-page :remove-tag :remove-property} command)
(remove-command/invalid-options? command opts))
(command-core/invalid-options-result summary (remove-command/invalid-options? command opts))
(and (= command :show) (show-command/invalid-options? opts))
(command-core/invalid-options-result summary (show-command/invalid-options? opts))
@@ -268,7 +282,7 @@
:message "missing command"}
:summary summary})
(and (= 1 (count args)) (#{"graph" "server" "list" "add" "query"} (first args)))
(and (= 1 (count args)) (#{"graph" "server" "list" "add" "upsert" "remove" "query"} (first args)))
(command-core/help-result (command-core/group-summary (first args) table))
:else
@@ -372,14 +386,17 @@
:add-page
(add-command/build-add-page-action options repo)
:add-tag
(add-command/build-add-tag-action options repo)
:upsert-tag
(upsert-command/build-tag-action options repo)
:upsert-property
(upsert-command/build-property-action options repo)
:update-block
(update-command/build-action options repo)
:remove
(remove-command/build-action options repo)
(:remove-block :remove-page :remove-tag :remove-property)
(remove-command/build-action command options repo)
:query
(query-command/build-action options repo config)
@@ -423,9 +440,13 @@
:list-property (list-command/execute-list-property action config)
:add-block (add-command/execute-add-block action config)
:add-page (add-command/execute-add-page action config)
:add-tag (add-command/execute-add-tag action config)
:upsert-tag (upsert-command/execute-upsert-tag action config)
:upsert-property (upsert-command/execute-upsert-property action config)
:update-block (update-command/execute-update action config)
:remove (remove-command/execute-remove action config)
:remove-block (remove-command/execute-remove-block action config)
:remove-page (remove-command/execute-remove-page action config)
:remove-tag (remove-command/execute-remove-tag action config)
:remove-property (remove-command/execute-remove-property action config)
:query (query-command/execute-query action config)
:query-list (query-command/execute-query-list action config)
:show (show-command/execute-show action config)
@@ -440,8 +461,8 @@
:message "unknown action"}}))]
(assoc result
:command (or (:command action) (:type action))
:context (select-keys action [:repo :graph :page :id :ids :uuid :block :blocks
:name
:context (select-keys action [:repo :graph :page :name :id :ids :uuid :block :blocks
:schema
:source :target :update-tags :update-properties
:remove-tags :remove-properties
:export-type :output :import-type :input])))))

View File

@@ -93,17 +93,30 @@
:missing-tag-name "Use --name <tag-name>"
:missing-query "Use --query <edn>"
:unknown-query "Use `logseq query list` to see available queries"
:ambiguous-tag-name "Retry with --id <tag-id>"
:ambiguous-property-name "Retry with --id <property-id>"
:data-dir-permission "Check filesystem permissions or set LOGSEQ_CLI_DATA_DIR"
:server-owned-by-other "Retry from the process owner that started the server"
:server-start-timeout-orphan "Check and stop lingering db-worker-node processes, then retry"
nil))
(defn- format-candidates
[candidates]
(when (seq candidates)
(str "\nCandidates:\n"
(string/join "\n"
(map (fn [{:keys [id name]}]
(str " " id " " (or name "-")))
candidates)))))
(defn- format-error
[error]
(let [{:keys [code message]} error
(let [{:keys [code message candidates]} error
hint (error-hint error)
message* (style/bold-keywords message ["option" "command" "argument"])]
message* (style/bold-keywords message ["option" "command" "argument"])
candidates* (format-candidates candidates)]
(cond-> (str "Error (" (name (or code :error)) "): " message*)
candidates* (str candidates*)
hint (str "\nHint: " hint))))
(defn- maybe-ident-header
@@ -248,14 +261,37 @@
[_context ids]
(str "Added tag:\n" (pr-str (vec (or ids [])))))
(defn- format-remove
[{:keys [repo page uuid id ids]}]
(defn- format-upsert-tag
[_context ids]
(str "Upserted tag:\n" (pr-str (vec (or ids [])))))
(defn- format-upsert-property
[_context ids]
(str "Upserted property:\n" (pr-str (vec (or ids [])))))
(defn- format-remove-block
[{:keys [repo uuid id ids]}]
(cond
(seq page) (str "Removed page: " page " (repo: " repo ")")
(seq uuid) (str "Removed block: " uuid " (repo: " repo ")")
(seq ids) (str "Removed blocks: " (count ids) " (repo: " repo ")")
(some? id) (str "Removed block: " id " (repo: " repo ")")
:else (str "Removed item (repo: " repo ")")))
:else (str "Removed block (repo: " repo ")")))
(defn- format-remove-page
[{:keys [repo name]}]
(str "Removed page: " name " (repo: " repo ")"))
(defn- format-remove-tag
[{:keys [repo name id]}]
(if (seq name)
(str "Removed tag: " name " (repo: " repo ")")
(str "Removed tag: " id " (repo: " repo ")")))
(defn- format-remove-property
[{:keys [repo name id]}]
(if (seq name)
(str "Removed property: " name " (repo: " repo ")")
(str "Removed property: " id " (repo: " repo ")")))
(defn- format-update-block
[{:keys [repo source target update-tags update-properties remove-tags remove-properties]}]
@@ -319,7 +355,12 @@
:add-block (format-add-block context (:result data))
:add-page (format-add-page context (:result data))
:add-tag (format-add-tag context (:result data))
:remove (format-remove context)
:upsert-tag (format-upsert-tag context (:result data))
:upsert-property (format-upsert-property context (:result data))
:remove-block (format-remove-block context)
:remove-page (format-remove-page context)
:remove-tag (format-remove-tag context)
:remove-property (format-remove-property context)
:update-block (format-update-block context)
:graph-export (format-graph-export context)
:graph-import (format-graph-import context)