Initial import of LogDarioSeq baseline
Establishes version control for the personal Logseq DB fork before starting the server-client architecture migration and domain features described in docs/agent-guide and the approved implementation plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
517
cli/lib/completion.ml
Normal file
517
cli/lib/completion.ml
Normal file
@@ -0,0 +1,517 @@
|
||||
type opts = { shell : Cli_primitive.shell option }
|
||||
type parsed = Parsed_completion of opts
|
||||
|
||||
type action =
|
||||
| Completion of {
|
||||
shell : Cli_primitive.shell;
|
||||
registry : Command_registry.t option;
|
||||
}
|
||||
|
||||
let join_words words = Vec.string_concat " " words
|
||||
|
||||
let unique_preserve values =
|
||||
let seen = Hashtbl.create 16 in
|
||||
values
|
||||
|> Vec.filter (fun value ->
|
||||
if Hashtbl.mem seen value then false
|
||||
else (
|
||||
Hashtbl.add seen value ();
|
||||
true))
|
||||
|
||||
let top_command_names registry =
|
||||
registry.Command_registry.commands
|
||||
|> Vec.filter_map (fun command -> Vec.nth_opt command.Command_registry.path 0)
|
||||
|> Vec.sort_uniq String.compare
|
||||
|
||||
let is_prefix prefix path =
|
||||
let rec loop index =
|
||||
index = Vec.length prefix
|
||||
|| (Vec.nth prefix index = Vec.nth path index && loop (index + 1))
|
||||
in
|
||||
Vec.length prefix <= Vec.length path && loop 0
|
||||
|
||||
let commands_under registry prefix =
|
||||
registry.Command_registry.commands
|
||||
|> Vec.filter (fun command ->
|
||||
is_prefix prefix command.Command_registry.path
|
||||
&& Vec.length command.Command_registry.path > Vec.length prefix)
|
||||
|
||||
let direct_children registry prefix =
|
||||
let prefix_len = Vec.length prefix in
|
||||
commands_under registry prefix
|
||||
|> Vec.filter_map (fun command ->
|
||||
Vec.nth_opt command.Command_registry.path prefix_len)
|
||||
|> unique_preserve
|
||||
|
||||
let direct_leaf_children registry prefix =
|
||||
let prefix_len = Vec.length prefix in
|
||||
commands_under registry prefix
|
||||
|> Vec.filter (fun command ->
|
||||
Vec.length command.Command_registry.path = prefix_len + 1)
|
||||
|
||||
let nested_child_groups registry prefix =
|
||||
let prefix_len = Vec.length prefix in
|
||||
commands_under registry prefix
|
||||
|> Vec.filter_map (fun command ->
|
||||
if Vec.length command.Command_registry.path > prefix_len + 1 then
|
||||
Vec.nth_opt command.Command_registry.path prefix_len
|
||||
else None)
|
||||
|> unique_preserve
|
||||
|
||||
let is_group registry path = not (Vec.is_empty (commands_under registry path))
|
||||
|
||||
let global_option_names =
|
||||
Vec.of_array
|
||||
[|
|
||||
"--help";
|
||||
"-h";
|
||||
"--version";
|
||||
"--config";
|
||||
"--graph";
|
||||
"-g";
|
||||
"--root-dir";
|
||||
"--timeout-ms";
|
||||
"--output";
|
||||
"-o";
|
||||
"--verbose";
|
||||
"-v";
|
||||
"--profile";
|
||||
|]
|
||||
|
||||
let global_value_option_names =
|
||||
Vec.of_array
|
||||
[|
|
||||
"--config";
|
||||
"--graph";
|
||||
"-g";
|
||||
"--root-dir";
|
||||
"--timeout-ms";
|
||||
"--output";
|
||||
"-o";
|
||||
|]
|
||||
|
||||
let command_desc_for_path registry path =
|
||||
Command_registry.find_by_path path registry
|
||||
|> Option.map (fun (command : Command_registry.command_meta) -> command.doc)
|
||||
|> Option.value ~default:""
|
||||
|
||||
let zsh_quote_entry name desc = "'" ^ name ^ ":" ^ desc ^ "'"
|
||||
|
||||
let zsh_escape_desc doc =
|
||||
let buffer = Buffer.create (String.length doc) in
|
||||
String.iter
|
||||
(fun c ->
|
||||
match c with
|
||||
| '\'' -> Buffer.add_string buffer "'\\''"
|
||||
| '[' -> Buffer.add_string buffer "\\["
|
||||
| ']' -> Buffer.add_string buffer "\\]"
|
||||
| c -> Buffer.add_char buffer c)
|
||||
doc;
|
||||
Buffer.contents buffer
|
||||
|
||||
let zsh_option_action (option : Command_registry.option_meta) value_name =
|
||||
if Vec.mem "--graph" option.names then "{_logseq_graphs}"
|
||||
else if option.multi && not (Vec.is_empty option.choices) then
|
||||
"{_values -s , " ^ value_name ^ " " ^ Vec.string_concat " " option.choices
|
||||
^ "}"
|
||||
else if not (Vec.is_empty option.choices) then
|
||||
"(" ^ Vec.string_concat " " option.choices ^ ")"
|
||||
else if Vec.mem "--root-dir" option.names then "_files -/"
|
||||
else if value_name = "path" then "_files"
|
||||
else if value_name = "bool" then "(true false)"
|
||||
else ""
|
||||
|
||||
let zsh_option_spec (option : Command_registry.option_meta) =
|
||||
let desc = "[" ^ zsh_escape_desc option.doc ^ "]" in
|
||||
let value_spec =
|
||||
match option.arity with
|
||||
| Command_registry.Flag -> None
|
||||
| Required_value name ->
|
||||
Some (":" ^ name ^ ":" ^ zsh_option_action option name)
|
||||
| Optional_value name ->
|
||||
Some ("::" ^ name ^ ":" ^ zsh_option_action option name)
|
||||
in
|
||||
let name_suffix = if Option.is_some value_spec then "=" else "" in
|
||||
let arg = Option.value value_spec ~default:"" in
|
||||
let star = if option.repeatable then "*" else "" in
|
||||
if Vec.length option.names = 1 then
|
||||
"'" ^ star ^ Vec.nth option.names 0 ^ name_suffix ^ desc ^ arg ^ "'"
|
||||
else
|
||||
let exclusion =
|
||||
if option.repeatable then "'" ^ star ^ "'"
|
||||
else "'(" ^ Vec.string_concat " " option.names ^ ")'"
|
||||
in
|
||||
let alternatives =
|
||||
option.names
|
||||
|> Vec.map (fun name -> name ^ name_suffix)
|
||||
|> Vec.string_concat ","
|
||||
in
|
||||
exclusion ^ "{" ^ alternatives ^ "}'" ^ desc ^ arg ^ "'"
|
||||
|
||||
let global_option_named name =
|
||||
Command_registry.global_options
|
||||
|> Vec.find_opt (fun (option : Command_registry.option_meta) ->
|
||||
Vec.mem name option.names)
|
||||
|
||||
let zsh_global_specs names =
|
||||
names |> Vec.filter_map global_option_named |> Vec.map zsh_option_spec
|
||||
|
||||
let zsh_spec_lines specs = " " ^ Vec.string_concat " \\\n " specs
|
||||
|
||||
let zsh_leaf_function command =
|
||||
let func_name = "_logseq_" ^ join_words command.Command_registry.path in
|
||||
let func_name = String.map (function ' ' -> '_' | c -> c) func_name in
|
||||
let specs =
|
||||
Vec.append
|
||||
(zsh_global_specs (Vec.of_array [| "--help"; "--graph"; "--output" |]))
|
||||
(Vec.map zsh_option_spec command.Command_registry.options)
|
||||
in
|
||||
String.concat "\n"
|
||||
[ func_name ^ "() {"; " _arguments \\"; zsh_spec_lines specs; "}"; "" ]
|
||||
|
||||
let zsh_group_specs registry path =
|
||||
let own_option_specs =
|
||||
match Command_registry.find_by_path path registry with
|
||||
| Some command -> Vec.map zsh_option_spec command.Command_registry.options
|
||||
| None -> Vec.empty
|
||||
in
|
||||
Vec.append
|
||||
(Vec.append
|
||||
(zsh_global_specs (Vec.of_array [| "--help"; "--graph" |]))
|
||||
own_option_specs)
|
||||
(Vec.of_array [| "'(-)1:subcommand:->subcmd'"; "'(-)*::args:->args'" |])
|
||||
|
||||
let zsh_subgroup_function registry parent subgroup =
|
||||
let prefix = Vec.push_back parent subgroup in
|
||||
let child_entries =
|
||||
direct_leaf_children registry prefix
|
||||
|> Vec.map (fun command ->
|
||||
" "
|
||||
^ zsh_quote_entry
|
||||
(Vec.nth command.Command_registry.path (Vec.length prefix))
|
||||
command.Command_registry.doc)
|
||||
|> Vec.string_concat "\n"
|
||||
in
|
||||
let dispatches =
|
||||
direct_leaf_children registry prefix
|
||||
|> Vec.map (fun command ->
|
||||
let leaf = Vec.nth command.Command_registry.path (Vec.length prefix) in
|
||||
" " ^ leaf ^ ") _logseq_"
|
||||
^ Vec.string_concat "_" command.Command_registry.path
|
||||
^ " ;;")
|
||||
|> Vec.string_concat "\n"
|
||||
in
|
||||
String.concat "\n"
|
||||
[
|
||||
"_logseq_" ^ Vec.string_concat "_" prefix ^ "() {";
|
||||
" local curcontext=\"$curcontext\" state line";
|
||||
" typeset -A opt_args";
|
||||
" _arguments -C \\";
|
||||
zsh_spec_lines (zsh_group_specs registry prefix);
|
||||
" case $state in";
|
||||
" subcmd)";
|
||||
" local -a subcmds";
|
||||
" subcmds=(";
|
||||
child_entries;
|
||||
" )";
|
||||
" _describe 'subcommand' subcmds";
|
||||
" ;;";
|
||||
" args)";
|
||||
" case $line[1] in";
|
||||
dispatches;
|
||||
" esac";
|
||||
" ;;";
|
||||
" esac";
|
||||
"}";
|
||||
"";
|
||||
]
|
||||
|
||||
let zsh_group_function registry group =
|
||||
let prefix = Vec.singleton group in
|
||||
let leaf_entries =
|
||||
direct_leaf_children registry prefix
|
||||
|> Vec.map (fun command ->
|
||||
" "
|
||||
^ zsh_quote_entry
|
||||
(Vec.nth command.Command_registry.path 1)
|
||||
command.Command_registry.doc)
|
||||
in
|
||||
let subgroup_entries =
|
||||
nested_child_groups registry prefix
|
||||
|> Vec.map (fun name ->
|
||||
" " ^ zsh_quote_entry name (name ^ " commands"))
|
||||
in
|
||||
let dispatches =
|
||||
direct_leaf_children registry prefix
|
||||
|> Vec.map (fun command ->
|
||||
let subcmd = Vec.nth command.Command_registry.path 1 in
|
||||
" " ^ subcmd ^ ") _logseq_"
|
||||
^ Vec.string_concat "_" command.Command_registry.path
|
||||
^ " ;;")
|
||||
|> fun leaf_dispatches ->
|
||||
Vec.append leaf_dispatches
|
||||
(nested_child_groups registry prefix
|
||||
|> Vec.map (fun name ->
|
||||
" " ^ name ^ ") _logseq_" ^ group ^ "_" ^ name ^ " ;;"))
|
||||
in
|
||||
let subgroup_functions =
|
||||
nested_child_groups registry prefix
|
||||
|> Vec.map (zsh_subgroup_function registry prefix)
|
||||
|> Vec.string_concat "\n"
|
||||
in
|
||||
subgroup_functions
|
||||
^ String.concat "\n"
|
||||
[
|
||||
"_logseq_" ^ group ^ "() {";
|
||||
" local curcontext=\"$curcontext\" state line";
|
||||
" typeset -A opt_args";
|
||||
" _arguments -C \\";
|
||||
zsh_spec_lines (zsh_group_specs registry prefix);
|
||||
" case $state in";
|
||||
" subcmd)";
|
||||
" local -a subcmds";
|
||||
" subcmds=(";
|
||||
Vec.string_concat "\n" (Vec.append leaf_entries subgroup_entries);
|
||||
" )";
|
||||
" _describe 'subcommand' subcmds";
|
||||
" ;;";
|
||||
" args)";
|
||||
" case $line[1] in";
|
||||
Vec.string_concat "\n" dispatches;
|
||||
" esac";
|
||||
" ;;";
|
||||
" esac";
|
||||
"}";
|
||||
"";
|
||||
]
|
||||
|
||||
let generate_zsh_completion registry =
|
||||
let top_entries =
|
||||
top_command_names registry
|
||||
|> Vec.map (fun top ->
|
||||
let desc =
|
||||
let path = Vec.singleton top in
|
||||
if is_group registry path then top ^ " commands"
|
||||
else command_desc_for_path registry path
|
||||
in
|
||||
" " ^ zsh_quote_entry top desc)
|
||||
|> Vec.string_concat "\n"
|
||||
in
|
||||
let dispatches =
|
||||
top_command_names registry
|
||||
|> Vec.map (fun top -> " " ^ top ^ ") _logseq_" ^ top ^ " ;;")
|
||||
|> Vec.string_concat "\n"
|
||||
in
|
||||
let leaf_functions =
|
||||
registry.Command_registry.commands
|
||||
|> Vec.filter (fun command ->
|
||||
(* commands that are also groups (e.g. `query`) are completed by
|
||||
their group dispatcher, which includes their options *)
|
||||
not (is_group registry command.Command_registry.path))
|
||||
|> Vec.map zsh_leaf_function |> Vec.string_concat "\n"
|
||||
in
|
||||
let group_functions =
|
||||
top_command_names registry
|
||||
|> Vec.filter (fun top -> is_group registry (Vec.singleton top))
|
||||
|> Vec.map (zsh_group_function registry)
|
||||
|> Vec.string_concat "\n"
|
||||
in
|
||||
String.concat "\n"
|
||||
[
|
||||
"#compdef logseq";
|
||||
"# Auto-generated by `logseq completion zsh` - do not edit manually.";
|
||||
"";
|
||||
"_logseq_graphs() {";
|
||||
" local -a graphs";
|
||||
" graphs=( ${(f)\"$(logseq graph list --output json 2>/dev/null | node \
|
||||
-e 'const fs = require(\"fs\"); const input = fs.readFileSync(0, \
|
||||
\"utf8\"); if (!input.trim()) process.exit(0); const data = \
|
||||
JSON.parse(input); const graphs = data && data.data && \
|
||||
data.data.graphs; if (Array.isArray(graphs)) \
|
||||
console.log(graphs.join(\"\\n\"));')\"} )";
|
||||
" compadd -a graphs";
|
||||
"}";
|
||||
"";
|
||||
"# --- per-command functions ---";
|
||||
"";
|
||||
leaf_functions;
|
||||
"# --- group dispatchers ---";
|
||||
"";
|
||||
group_functions;
|
||||
"# --- top-level dispatcher ---";
|
||||
"";
|
||||
"_logseq() {";
|
||||
" local curcontext=\"$curcontext\" state line";
|
||||
" typeset -A opt_args";
|
||||
" _arguments -C \\";
|
||||
zsh_spec_lines
|
||||
(Vec.append
|
||||
(Vec.map zsh_option_spec Command_registry.global_options)
|
||||
(Vec.of_array
|
||||
[| "'(-)1:command:->cmds'"; "'(-)*::args:->args'" |]));
|
||||
" case $state in";
|
||||
" cmds)";
|
||||
" local -a cmds";
|
||||
" cmds=(";
|
||||
top_entries;
|
||||
" )";
|
||||
" _describe 'command' cmds";
|
||||
" ;;";
|
||||
" args)";
|
||||
" case $line[1] in";
|
||||
dispatches;
|
||||
" esac";
|
||||
" ;;";
|
||||
" esac";
|
||||
"}";
|
||||
"";
|
||||
"compdef _logseq logseq";
|
||||
]
|
||||
|
||||
let bash_case_pattern names = Vec.string_concat "|" names
|
||||
|
||||
let bash_subcommand_cases registry =
|
||||
top_command_names registry
|
||||
|> Vec.filter_map (fun top ->
|
||||
let children = direct_children registry (Vec.singleton top) in
|
||||
if Vec.is_empty children then None
|
||||
else
|
||||
Some
|
||||
(" " ^ top ^ ") COMPREPLY=( $(compgen -W '"
|
||||
^ Vec.string_concat " " children
|
||||
^ "' -- \"$cur\") ) ;;"))
|
||||
|> Vec.string_concat "\n"
|
||||
|
||||
let bash_sub_subcommand_cases registry =
|
||||
top_command_names registry
|
||||
|> Vec.concat_map (fun top ->
|
||||
nested_child_groups registry (Vec.singleton top)
|
||||
|> Vec.map (fun subgroup ->
|
||||
let leaves =
|
||||
direct_children registry (Vec.of_array [| top; subgroup |])
|
||||
in
|
||||
" " ^ top ^ ":" ^ subgroup ^ ") COMPREPLY=( $(compgen -W '"
|
||||
^ Vec.string_concat " " leaves
|
||||
^ "' -- \"$cur\") ) ;;"))
|
||||
|> Vec.string_concat "\n"
|
||||
|
||||
let generate_bash_completion registry =
|
||||
let top_commands = Vec.string_concat " " (top_command_names registry) in
|
||||
String.concat "\n"
|
||||
[
|
||||
"# Auto-generated by `logseq completion bash` - do not edit manually.";
|
||||
"";
|
||||
"_logseq_is_value_opt() {";
|
||||
" case \"$1\" in";
|
||||
" " ^ bash_case_pattern global_value_option_names ^ ")";
|
||||
" return 0 ;;";
|
||||
" *) return 1 ;;";
|
||||
" esac";
|
||||
"}";
|
||||
"";
|
||||
"_logseq_cmd_and_subcmd() {";
|
||||
" local i skip=0";
|
||||
" __cmd='' __subcmd='' __subsubcmd=''";
|
||||
" for (( i = 1; i < COMP_CWORD; i++ )); do";
|
||||
" local w=\"${COMP_WORDS[i]}\"";
|
||||
" if (( skip )); then skip=0; continue; fi";
|
||||
" if [[ \"$w\" == -* ]]; then";
|
||||
" _logseq_is_value_opt \"$w\" && skip=1";
|
||||
" continue";
|
||||
" fi";
|
||||
" if [[ -z \"$__cmd\" ]]; then __cmd=\"$w\"";
|
||||
" elif [[ -z \"$__subcmd\" ]]; then __subcmd=\"$w\"";
|
||||
" elif [[ -z \"$__subsubcmd\" ]]; then __subsubcmd=\"$w\"";
|
||||
" fi";
|
||||
" done";
|
||||
"}";
|
||||
"";
|
||||
"_logseq_json_names_bash() {";
|
||||
" logseq graph list --output json 2>/dev/null | node -e 'const fs = \
|
||||
require(\"fs\"); const input = fs.readFileSync(0, \"utf8\"); if \
|
||||
(!input.trim()) process.exit(0); const data = JSON.parse(input); const \
|
||||
graphs = data && data.data && data.data.graphs; if \
|
||||
(Array.isArray(graphs)) console.log(graphs.join(\" \"));'";
|
||||
"}";
|
||||
"";
|
||||
"_logseq_opts_for() {";
|
||||
" printf '%s' \"" ^ Vec.string_concat " " global_option_names ^ "\"";
|
||||
"}";
|
||||
"";
|
||||
"_logseq() {";
|
||||
" local cur prev";
|
||||
" cur=\"${COMP_WORDS[COMP_CWORD]}\"";
|
||||
" prev=\"${COMP_WORDS[COMP_CWORD-1]}\"";
|
||||
" COMPREPLY=()";
|
||||
" local __cmd __subcmd __subsubcmd";
|
||||
" _logseq_cmd_and_subcmd";
|
||||
" if [[ \"$cur\" == -* ]]; then";
|
||||
" COMPREPLY=( $(compgen -W \"$(_logseq_opts_for \"$__cmd\" \
|
||||
\"$__subcmd\" \"$__subsubcmd\")\" -- \"$cur\") )";
|
||||
" return";
|
||||
" fi";
|
||||
" if [[ -z \"$__cmd\" ]]; then";
|
||||
" COMPREPLY=( $(compgen -W '" ^ top_commands ^ "' -- \"$cur\") )";
|
||||
" return";
|
||||
" fi";
|
||||
" if [[ -z \"$__subcmd\" ]]; then";
|
||||
" case \"$__cmd\" in";
|
||||
bash_subcommand_cases registry;
|
||||
" esac";
|
||||
" return";
|
||||
" fi";
|
||||
" if [[ -z \"$__subsubcmd\" ]]; then";
|
||||
" case \"$__cmd:$__subcmd\" in";
|
||||
bash_sub_subcommand_cases registry;
|
||||
" esac";
|
||||
" return";
|
||||
" fi";
|
||||
"}";
|
||||
"";
|
||||
"complete -F _logseq logseq";
|
||||
]
|
||||
|
||||
let generate shell registry =
|
||||
match shell with
|
||||
| Cli_primitive.Zsh -> generate_zsh_completion registry
|
||||
| Bash -> generate_bash_completion registry
|
||||
|
||||
let command_id _ = Command_id.Completion
|
||||
let validate_parsed _ = Ok ()
|
||||
|
||||
let build ?registry _config _ = function
|
||||
| Parsed_completion { shell = Some shell } ->
|
||||
Ok (Completion { shell; registry })
|
||||
| Parsed_completion { shell = None } ->
|
||||
Error
|
||||
(Error.invalid_options
|
||||
"completion shell is required; expected zsh or bash")
|
||||
|
||||
let execute_with_mode (Completion { shell; registry }) _config mode =
|
||||
Cli_effect.pure
|
||||
(Cli_result.ok ~command:Command_id.Completion mode
|
||||
(Message
|
||||
(generate shell
|
||||
(Option.value registry ~default:Command_registry.empty))))
|
||||
|
||||
let metadata () =
|
||||
Vec.of_array
|
||||
[|
|
||||
{
|
||||
Command_registry.id = Command_id.Completion;
|
||||
path = Command_id.to_path Command_id.Completion;
|
||||
doc = "Generate shell completion script";
|
||||
long_doc = None;
|
||||
examples = Vec.empty;
|
||||
options = Vec.empty;
|
||||
category = Command_registry.Utilities;
|
||||
requires_graph = Command_id.requires_graph Command_id.Completion;
|
||||
requires_auth = Command_id.requires_auth Command_id.Completion;
|
||||
write_command = Command_id.is_write Command_id.Completion;
|
||||
human_table_headers_order = Vec.empty;
|
||||
};
|
||||
|]
|
||||
|
||||
let execute action config =
|
||||
let (Output.Mode.Packed mode) = Output_mode.for_config config in
|
||||
execute_with_mode action config mode
|
||||
Reference in New Issue
Block a user