enhance(cli): add examples in --help

This commit is contained in:
rcmerci
2026-03-14 17:58:16 +08:00
parent 3a547c6169
commit 097ddd42f3
14 changed files with 167 additions and 76 deletions

View File

@@ -26,4 +26,6 @@
[(core/command-entry ["completion"] :completion
"Generate shell completion script"
completion-spec
{:long-desc long-desc})])
{:long-desc long-desc
:examples ["logseq completion zsh"
"logseq completion bash"]})])

View File

@@ -39,12 +39,13 @@
(defn command-entry
([cmds command desc spec]
(command-entry cmds command desc spec nil))
([cmds command desc spec {:keys [long-desc]}]
([cmds command desc spec {:keys [long-desc examples]}]
(let [spec* (merge-spec spec)]
{:cmds cmds
:command command
:desc desc
:long-desc long-desc
:examples examples
:spec spec*
:restrict true
:fn (fn [{:keys [opts args]}]
@@ -52,6 +53,7 @@
:cmds cmds
:spec spec*
:long-desc long-desc
:examples examples
:opts opts
:args args})})))
@@ -127,9 +129,21 @@
(str "Command " (style/bold "options") ":")
" See `logseq <command> --help`"])))
(defn- format-examples
[examples]
(->> examples
(keep (fn [example]
(let [line (some-> example str string/trim)]
(when (seq line)
line))))
(take 5)
(map #(str " " %))
(string/join "\n")))
(defn command-summary
[{:keys [cmds spec long-desc]}]
(let [command-spec (apply dissoc spec (keys global-spec*))]
[{:keys [cmds spec long-desc examples]}]
(let [command-spec (apply dissoc spec (keys global-spec*))
formatted-examples (format-examples examples)]
(string/join "\n"
(cond-> [(str "Usage: logseq " (command-usage cmds spec))]
(seq long-desc) (conj "" long-desc)
@@ -138,7 +152,10 @@
(format-opts global-spec*)
""
(str "Command " (style/bold "options") ":")
(format-opts command-spec))))))
(format-opts command-spec))
(seq formatted-examples) (conj ""
(str (style/bold "Examples") ":")
formatted-examples)))))
(defn normalize-opts
[opts]

View File

@@ -13,7 +13,8 @@
:doctor
"Run runtime diagnostics"
{:dev-script {:desc "Check static/db-worker-node.js instead of bundled dist runtime"
:coerce :boolean}})])
:coerce :boolean}}
{:examples ["logseq doctor --dev-script"]})])
(defn build-action
([]

View File

@@ -28,13 +28,21 @@
(def entries
[(core/command-entry ["graph" "list"] :graph-list "List graphs" {})
(core/command-entry ["graph" "create"] :graph-create "Create graph" {})
(core/command-entry ["graph" "switch"] :graph-switch "Switch current graph" {})
(core/command-entry ["graph" "remove"] :graph-remove "Remove graph" {})
(core/command-entry ["graph" "validate"] :graph-validate "Validate graph" graph-validate-spec)
(core/command-entry ["graph" "info"] :graph-info "Graph metadata" {})
(core/command-entry ["graph" "export"] :graph-export "Export graph" graph-export-spec)
(core/command-entry ["graph" "import"] :graph-import "Import graph" graph-import-spec)])
(core/command-entry ["graph" "create"] :graph-create "Create graph" {}
{:examples ["logseq graph create --graph my-graph"]})
(core/command-entry ["graph" "switch"] :graph-switch "Switch current graph" {}
{:examples ["logseq graph switch --graph my-graph"]})
(core/command-entry ["graph" "remove"] :graph-remove "Remove graph" {}
{:examples ["logseq graph remove --graph my-graph"]})
(core/command-entry ["graph" "validate"] :graph-validate "Validate graph" graph-validate-spec
{:examples ["logseq graph validate --graph my-graph"
"logseq graph validate --graph my-graph --fix"]})
(core/command-entry ["graph" "info"] :graph-info "Graph metadata" {}
{:examples ["logseq graph info --graph my-graph"]})
(core/command-entry ["graph" "export"] :graph-export "Export graph" graph-export-spec
{:examples ["logseq graph export --graph my-graph --type edn --file /tmp/my-graph.edn"]})
(core/command-entry ["graph" "import"] :graph-import "Import graph" graph-import-spec
{:examples ["logseq graph import --graph my-graph --type edn --input /tmp/my-graph.edn"]})])
(def ^:private import-export-types*
#{"edn" "sqlite"})

View File

@@ -64,9 +64,13 @@
:fields {:desc "Select output fields (comma separated)"}}))
(def entries
[(core/command-entry ["list" "page"] :list-page "List pages" list-page-spec)
(core/command-entry ["list" "tag"] :list-tag "List tags" list-tag-spec)
(core/command-entry ["list" "property"] :list-property "List properties" list-property-spec)])
[(core/command-entry ["list" "page"] :list-page "List pages" list-page-spec
{:examples ["logseq list page --graph my-graph"
"logseq list page --graph my-graph --journal-only --limit 20"]})
(core/command-entry ["list" "tag"] :list-tag "List tags" list-tag-spec
{:examples ["logseq list tag --graph my-graph --with-properties"]})
(core/command-entry ["list" "property"] :list-property "List properties" list-property-spec
{:examples ["logseq list property --graph my-graph --with-type"]})])
(defn invalid-options?
[command opts]

View File

@@ -17,7 +17,9 @@
{})
(def entries
[(core/command-entry ["query"] :query "Run a Datascript query" query-spec)
[(core/command-entry ["query"] :query "Run a Datascript query" query-spec
{:examples ["logseq query --graph my-graph --name block-search --inputs '[\"daily\"]'"
"logseq query --graph my-graph --query '[:find [?e ...] :where [?e :block/name]]'"]})
(core/command-entry ["query" "list"] :query-list "List available queries" query-list-spec)])
(def ^:private built-in-query-specs

View File

@@ -22,10 +22,15 @@
:name {:desc "Entity name"}})
(def entries
[(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)])
[(core/command-entry ["remove" "block"] :remove-block "Remove blocks" remove-block-spec
{:examples ["logseq remove block --graph my-graph --id 123"
"logseq remove block --graph my-graph --uuid 7f0f4bb3-2e48-4b46-ae0f-18f52ef0f8be"]})
(core/command-entry ["remove" "page"] :remove-page "Remove page" remove-page-spec
{:examples ["logseq remove page --graph my-graph --name Home"]})
(core/command-entry ["remove" "tag"] :remove-tag "Remove tag" remove-entity-spec
{:examples ["logseq remove tag --graph my-graph --name project"]})
(core/command-entry ["remove" "property"] :remove-property "Remove property" remove-entity-spec
{:examples ["logseq remove property --graph my-graph --name status"]})])
(defn invalid-options?
[command opts]

View File

@@ -10,10 +10,14 @@
(def entries
[(core/command-entry ["server" "list"] :server-list "List db-worker-node servers" {})
(core/command-entry ["server" "status"] :server-status "Show server status for a graph" server-spec)
(core/command-entry ["server" "start"] :server-start "Start db-worker-node for a graph" server-spec)
(core/command-entry ["server" "stop"] :server-stop "Stop db-worker-node for a graph" server-spec)
(core/command-entry ["server" "restart"] :server-restart "Restart db-worker-node for a graph" server-spec)])
(core/command-entry ["server" "status"] :server-status "Show server status for a graph" server-spec
{:examples ["logseq server status --graph my-graph"]})
(core/command-entry ["server" "start"] :server-start "Start db-worker-node for a graph" server-spec
{:examples ["logseq server start --graph my-graph"]})
(core/command-entry ["server" "stop"] :server-stop "Stop db-worker-node for a graph" server-spec
{:examples ["logseq server stop --graph my-graph"]})
(core/command-entry ["server" "restart"] :server-restart "Restart db-worker-node for a graph" server-spec
{:examples ["logseq server restart --graph my-graph"]})])
(defn build-action
[command repo]

View File

@@ -22,7 +22,9 @@
:coerce :long}})
(def entries
[(core/command-entry ["show"] :show "Show tree" show-spec)])
[(core/command-entry ["show"] :show "Show tree" show-spec
{:examples ["logseq show --graph my-graph --page Home"
"logseq show --graph my-graph --id 123 --level 3"]})])
(def ^:private multi-id-delimiter "\n================================================================\n")

View File

@@ -17,17 +17,32 @@
:coerce :boolean}})
(def entries
[(core/command-entry ["sync" "status"] :sync-status "Show db-sync runtime status" {})
(core/command-entry ["sync" "start"] :sync-start "Start db-sync client" {})
(core/command-entry ["sync" "stop"] :sync-stop "Stop db-sync client" {})
(core/command-entry ["sync" "upload"] :sync-upload "Upload current graph snapshot" {})
(core/command-entry ["sync" "download"] :sync-download "Download remote graph snapshot" sync-download-spec)
[(core/command-entry ["sync" "status"] :sync-status "Show db-sync runtime status" {}
{:examples ["logseq sync status --graph my-graph"]})
(core/command-entry ["sync" "start"] :sync-start "Start db-sync client" {}
{:examples ["logseq sync start --graph my-graph"]})
(core/command-entry ["sync" "stop"] :sync-stop "Stop db-sync client" {}
{:examples ["logseq sync stop --graph my-graph"]})
(core/command-entry ["sync" "upload"] :sync-upload "Upload current graph snapshot" {}
{:examples ["logseq sync upload --graph my-graph"]})
(core/command-entry ["sync" "download"] :sync-download "Download remote graph snapshot" sync-download-spec
{:examples ["logseq sync download --graph my-graph"
"logseq sync download --graph my-graph --progress"]})
(core/command-entry ["sync" "remote-graphs"] :sync-remote-graphs "List remote graphs" {})
(core/command-entry ["sync" "ensure-keys"] :sync-ensure-keys "Ensure user RSA keys for sync/e2ee" {})
(core/command-entry ["sync" "grant-access"] :sync-grant-access "Grant graph access to an email" sync-grant-access-spec)
(core/command-entry ["sync" "config" "set"] :sync-config-set "Set sync config key" {})
(core/command-entry ["sync" "config" "get"] :sync-config-get "Get sync config key" {})
(core/command-entry ["sync" "config" "unset"] :sync-config-unset "Unset sync config key" {})])
(core/command-entry ["sync" "grant-access"] :sync-grant-access "Grant graph access to an email" sync-grant-access-spec
{:examples ["logseq sync grant-access --graph my-graph --graph-id 8b6ecdd0-1fab-4a9f-b3fb-3069c5f76e95 --email teammate@example.com"]})
(core/command-entry ["sync" "config" "set"] :sync-config-set "Set sync config key" {}
{:examples ["logseq sync config set ws-url wss://sync.logseq.com"
"logseq sync config set http-base https://api.logseq.com"
"logseq sync config set e2ee-password my-secret"
"logseq sync config set ws-url ws://localhost:12315"
"logseq sync config set http-base http://localhost:8080"
"logseq sync config set ws-url wss://example.com/socket"]})
(core/command-entry ["sync" "config" "get"] :sync-config-get "Get sync config key" {}
{:examples ["logseq sync config get ws-url"]})
(core/command-entry ["sync" "config" "unset"] :sync-config-unset "Unset sync config key" {}
{:examples ["logseq sync config unset ws-url"]})])
(def ^:private config-key-map
{"ws-url" :ws-url

View File

@@ -61,10 +61,15 @@
:coerce :boolean}})
(def entries
[(core/command-entry ["upsert" "block"] :upsert-block "Upsert block" upsert-block-spec)
(core/command-entry ["upsert" "page"] :upsert-page "Upsert page" upsert-page-spec)
(core/command-entry ["upsert" "tag"] :upsert-tag "Upsert tag" upsert-tag-spec)
(core/command-entry ["upsert" "property"] :upsert-property "Upsert property" upsert-property-spec)])
[(core/command-entry ["upsert" "block"] :upsert-block "Upsert block" upsert-block-spec
{:examples ["logseq upsert block --graph my-graph --target-page Home --content \"New block\""
"logseq upsert block --graph my-graph --id 123 --content \"Updated content\""]})
(core/command-entry ["upsert" "page"] :upsert-page "Upsert page" upsert-page-spec
{:examples ["logseq upsert page --graph my-graph --page Home --update-tags '[\"project\"]'"]})
(core/command-entry ["upsert" "tag"] :upsert-tag "Upsert tag" upsert-tag-spec
{:examples ["logseq upsert tag --graph my-graph --name project"]})
(core/command-entry ["upsert" "property"] :upsert-property "Upsert property" upsert-property-spec
{:examples ["logseq upsert property --graph my-graph --name status --type default --cardinality one"]})])
(def ^:private property-types
#{"default" "number" "date" "datetime" "checkbox" "url" "node" "json" "string"})

View File

@@ -163,10 +163,13 @@
nil)))
(defn- ^:large-vars/cleanup-todo finalize-command
[summary {:keys [command opts args cmds spec long-desc]}]
[summary {:keys [command opts args cmds spec long-desc examples]}]
(let [opts (command-core/normalize-opts opts)
args (vec args)
cmd-summary (command-core/command-summary {:cmds cmds :spec spec :long-desc long-desc})
cmd-summary (command-core/command-summary {:cmds cmds
:spec spec
:long-desc long-desc
:examples examples})
graph (:graph opts)
has-args? (seq args)
has-content? (or (seq (:content opts))

View File

@@ -14,7 +14,12 @@
(let [entry (first completion-command/entries)]
(is (some? (:long-desc entry)))
(is (string/includes? (:long-desc entry) "autoload -Uz compinit"))
(is (string/includes? (:long-desc entry) "eval \"$(logseq completion zsh)\"")))))
(is (string/includes? (:long-desc entry) "eval \"$(logseq completion zsh)\""))))
(testing "completion entry has examples metadata"
(let [entry (first completion-command/entries)]
(is (= ["logseq completion zsh"
"logseq completion bash"]
(:examples entry))))))
(deftest test-parse-args-completion-shell
(testing "parse-args recognizes completion --shell zsh"
@@ -27,12 +32,14 @@
(is (= :completion (:command result))))))
(deftest test-parse-args-completion-help
(testing "parse-args completion --help returns help with setup instructions"
(testing "parse-args completion --help returns help with setup instructions and examples"
(let [result (commands/parse-args ["completion" "--help"])]
(is (false? (:ok? result)))
(is (true? (:help? result)))
(is (string/includes? (:summary result) "autoload -Uz compinit"))
(is (string/includes? (:summary result) "eval \"$(logseq completion bash)\"")))))
(is (string/includes? (:summary result) "eval \"$(logseq completion bash)\""))
(is (string/includes? (:summary result) "Examples:"))
(is (string/includes? (:summary result) "logseq completion zsh")))))
(deftest test-build-action-completion
(testing "build-action for :completion returns correct action"

View File

@@ -114,7 +114,7 @@
(is (string/includes? plain-summary "Global options:"))
(is (string/includes? plain-summary "Command options:")))))
(deftest test-parse-args-help
(deftest test-parse-args-help-groups
(testing "graph group shows subcommands"
(let [result (binding [style/*color-enabled?* true]
(commands/parse-args ["graph"]))
@@ -156,35 +156,6 @@
(is (contains-bold? summary "upsert tag"))
(is (contains-bold? summary "upsert property"))))
(testing "remove block command shows help"
(let [result (binding [style/*color-enabled?* true]
(commands/parse-args ["remove" "block" "--help"]))
summary (:summary result)
plain-summary (strip-ansi summary)]
(is (true? (:help? result)))
(is (string/includes? plain-summary "Usage: logseq remove block"))
(is (string/includes? plain-summary "Command options:"))
(is (contains-bold? summary "--id"))
(is (contains-bold? summary "--uuid"))))
(testing "upsert block command shows help"
(let [result (binding [style/*color-enabled?* true]
(commands/parse-args ["upsert" "block" "--help"]))
summary (:summary result)
plain-summary (strip-ansi summary)]
(is (true? (:help? result)))
(is (string/includes? plain-summary "Usage: logseq upsert block"))
(is (string/includes? plain-summary "Command options:"))
(is (contains-bold? summary "--id"))
(is (contains-bold? summary "--uuid"))
(is (contains-bold? summary "--content"))
(is (contains-bold? summary "--target-id"))
(is (contains-bold? summary "--target-uuid"))
(is (contains-bold? summary "--update-tags"))
(is (contains-bold? summary "--update-properties"))
(is (contains-bold? summary "--remove-tags"))
(is (contains-bold? summary "--remove-properties"))))
(testing "server group shows subcommands"
(let [result (binding [style/*color-enabled?* true]
(commands/parse-args ["server"]))
@@ -214,6 +185,49 @@
(is (seq lines))
(is (every? #(not (string/includes? % "[options]")) lines)))))
(deftest test-parse-args-help-command-examples
(testing "remove block command shows help"
(let [result (binding [style/*color-enabled?* true]
(commands/parse-args ["remove" "block" "--help"]))
summary (:summary result)
plain-summary (strip-ansi summary)]
(is (true? (:help? result)))
(is (string/includes? plain-summary "Usage: logseq remove block"))
(is (string/includes? plain-summary "Command options:"))
(is (string/includes? plain-summary "Examples:"))
(is (string/includes? plain-summary "logseq remove block --graph my-graph --id 123"))
(is (contains-bold? summary "--id"))
(is (contains-bold? summary "--uuid"))))
(testing "sync config set help limits examples to five lines"
(let [result (binding [style/*color-enabled?* true]
(commands/parse-args ["sync" "config" "set" "--help"]))
plain-summary (strip-ansi (:summary result))]
(is (true? (:help? result)))
(is (string/includes? plain-summary "Examples:"))
(is (string/includes? plain-summary "logseq sync config set ws-url wss://sync.logseq.com"))
(is (string/includes? plain-summary "logseq sync config set http-base http://localhost:8080"))
(is (not (string/includes? plain-summary "logseq sync config set ws-url wss://example.com/socket")))))
(testing "upsert block command shows help"
(let [result (binding [style/*color-enabled?* true]
(commands/parse-args ["upsert" "block" "--help"]))
summary (:summary result)
plain-summary (strip-ansi summary)]
(is (true? (:help? result)))
(is (string/includes? plain-summary "Usage: logseq upsert block"))
(is (string/includes? plain-summary "Command options:"))
(is (string/includes? plain-summary "Examples:"))
(is (contains-bold? summary "--id"))
(is (contains-bold? summary "--uuid"))
(is (contains-bold? summary "--content"))
(is (contains-bold? summary "--target-id"))
(is (contains-bold? summary "--target-uuid"))
(is (contains-bold? summary "--update-tags"))
(is (contains-bold? summary "--update-properties"))
(is (contains-bold? summary "--remove-tags"))
(is (contains-bold? summary "--remove-properties")))))
(deftest test-parse-args-group-help-flags
(testing "all groups show group help with -h and --help"
(doseq [group ["graph" "server" "list" "upsert" "remove" "query" "sync"]
@@ -243,7 +257,8 @@
(is (true? (:help? result)))
(is (string/includes? plain-summary "Usage: logseq login"))
(is (string/includes? plain-summary "Global options:"))
(is (string/includes? plain-summary "Command options:"))))
(is (string/includes? plain-summary "Command options:"))
(is (not (string/includes? plain-summary "Examples:")))))
(testing "logout command shows help"
(let [result (binding [style/*color-enabled?* true]
@@ -253,7 +268,8 @@
(is (true? (:help? result)))
(is (string/includes? plain-summary "Usage: logseq logout"))
(is (string/includes? plain-summary "Global options:"))
(is (string/includes? plain-summary "Command options:")))))
(is (string/includes? plain-summary "Command options:"))
(is (not (string/includes? plain-summary "Examples:"))))))
(deftest test-parse-args-help-sync-group
(testing "sync group shows subcommands"