Add custom path support for cli/tui in terminal config

This commit is contained in:
2026-06-01 11:49:23 +02:00
parent 5e9c6db412
commit b30243f674
12 changed files with 264 additions and 42 deletions

View File

@@ -23,6 +23,7 @@ import {
existsSync,
readFileSync,
appendFileSync,
mkdirSync,
} from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
@@ -193,7 +194,7 @@ export function generateFishCompletions(binName: string): string {
return loadAndProcessTemplate("fish.fish", binName);
}
type ShellType = "bash" | "zsh" | "fish";
export type ShellType = "bash" | "zsh" | "fish";
const generators: Record<ShellType, (binName: string) => string> = {
bash: generateBashCompletions,
@@ -202,51 +203,74 @@ const generators: Record<ShellType, (binName: string) => string> = {
};
/**
* Shell config file paths and eval commands for each shell type.
* Shell config file paths and startup commands for each shell type.
*/
const shellConfigs: Record<
ShellType,
{ configFile: string; evalCommand: (binName: string) => string }
{
configFile: string;
configDirCommand: string;
configDirPattern: RegExp;
evalCommand: (binName: string) => string;
}
> = {
bash: {
configFile: join(homedir(), ".bashrc"),
configDirCommand: 'export XO_CONFIG_DIR="${XO_CONFIG_DIR:-$HOME/.config/xo-cli}"',
configDirPattern: /^\s*(?:export\s+)?XO_CONFIG_DIR=/m,
evalCommand: (binName) => `eval "$(${binName} completions bash)"`,
},
zsh: {
configFile: join(homedir(), ".zshrc"),
configDirCommand: 'export XO_CONFIG_DIR="${XO_CONFIG_DIR:-$HOME/.config/xo-cli}"',
configDirPattern: /^\s*(?:export\s+)?XO_CONFIG_DIR=/m,
evalCommand: (binName) => `eval "$(${binName} completions zsh)"`,
},
fish: {
configFile: join(homedir(), ".config", "fish", "config.fish"),
configDirCommand:
'set -q XO_CONFIG_DIR; or set -gx XO_CONFIG_DIR "$HOME/.config/xo-cli"',
configDirPattern: /^\s*set\b[^\n]*\bXO_CONFIG_DIR\b/m,
evalCommand: (binName) => `${binName} completions fish | source`,
},
};
/**
* Installs completions to the user's shell config file.
* Adds the eval command if not already present.
* Adds a default config directory and the eval command if not already present.
* @param shell - The shell type
* @param binName - The CLI binary name
* @returns true if installed, false if already present
*/
function installCompletions(shell: ShellType, binName: string): boolean {
const config = shellConfigs[shell];
export function installCompletions(
shell: ShellType,
binName: string,
configFile: string = shellConfigs[shell].configFile,
): boolean {
const config = { ...shellConfigs[shell], configFile };
const evalCommand = config.evalCommand(binName);
// Check if config file exists and already has the completion line
let existingContent = "";
if (existsSync(config.configFile)) {
existingContent = readFileSync(config.configFile, "utf8");
if (existingContent.includes(evalCommand)) {
return false; // Already installed
}
}
// Append the completion line
const commands: string[] = [];
if (!config.configDirPattern.test(existingContent)) {
commands.push(config.configDirCommand);
}
if (!existingContent.includes(evalCommand)) {
commands.push(evalCommand);
}
if (commands.length === 0) {
return false;
}
const newLine =
existingContent.endsWith("\n") || existingContent === "" ? "" : "\n";
const completionBlock = `${newLine}\n# ${binName} shell completions\n${evalCommand}\n`;
const completionBlock = `${newLine}\n# ${binName} shell completions\n${commands.join("\n")}\n`;
mkdirSync(dirname(config.configFile), { recursive: true });
appendFileSync(config.configFile, completionBlock);
return true;
}

View File

@@ -26,6 +26,19 @@ __xo_complete() {
[[ -n "${__xo_complete_bin}" ]] && "${__xo_complete_bin}" "$@" 2>/dev/null
}
# @description
# Lists mnemonic aliases directly from the config directory without starting
# the dynamic Node helper.
__xo_complete_mnemonics() {
local config_dir="${XO_CONFIG_DIR:-${HOME}/.config/xo-cli}"
local file mnemonic
for file in "${config_dir}"/mnemonics/mnemonic-*; do
[[ -f "${file}" ]] || continue
mnemonic="${file##*/}"
[[ "${mnemonic}" == "$1"* ]] && printf '%s\n' "${mnemonic}"
done
}
# @description
# Main completion dispatcher invoked by bash's `complete -F`.
# It determines context (command/subcommand/argument position) and then mixes:
@@ -39,10 +52,10 @@ _{{FUNC_NAME}}_completions() {
_init_completion || return
# If the previous token is `-m/--mnemonic-file`, this argument expects a
# mnemonic file alias/path. Ask the helper for mnemonic suggestions.
# mnemonic file alias/path. List mnemonic aliases directly from disk.
if [[ "${prev}" == "-m" || "${prev}" == "--mnemonic-file" ]]; then
local mnemonics
mnemonics=$(__xo_complete mnemonics "${cur}")
mnemonics=$(__xo_complete_mnemonics "${cur}")
if [[ -n "${mnemonics}" ]]; then
while IFS= read -r line; do
COMPREPLY+=("$line")

View File

@@ -28,6 +28,21 @@ function __{{FUNC_NAME}}_complete_dynamic
end
end
# @description
# Lists mnemonic aliases directly from the config directory without starting
# the dynamic Node helper.
function __{{FUNC_NAME}}_complete_mnemonics
set -l config_dir "$XO_CONFIG_DIR"
if test -z "$config_dir"
set config_dir "$HOME/.config/xo-cli"
end
for file in $config_dir/mnemonics/mnemonic-*
if test -f "$file"
string replace -r '.*/' '' "$file"
end
end
end
# Global option flags available across top-level command contexts.
complete -c {{BIN_NAME}} -s h -d "Show help"
complete -c {{BIN_NAME}} -l help -d "Show help"
@@ -37,8 +52,8 @@ complete -c {{BIN_NAME}} -s o -d "Output file"
complete -c {{BIN_NAME}} -l output -d "Output file"
complete -c {{BIN_NAME}} -l currency -d "Set fiat display currency"
# Dynamic completion for `-m/--mnemonic-file`.
complete -c {{BIN_NAME}} -s m -l mnemonic-file -xa '(__{{FUNC_NAME}}_complete_dynamic mnemonics)'
# Shell-native completion for `-m/--mnemonic-file`.
complete -c {{BIN_NAME}} -s m -l mnemonic-file -xa '(__{{FUNC_NAME}}_complete_mnemonics)'
# Top-level command registrations inserted by template expansion.
{{TOP_LEVEL_COMMANDS}}

View File

@@ -25,6 +25,19 @@ __xo_complete() {
[[ -n "${__xo_complete_bin}" ]] && "${__xo_complete_bin}" "$@" 2>/dev/null
}
# @description
# Lists mnemonic aliases directly from the config directory without starting
# the dynamic Node helper.
__xo_complete_mnemonics() {
local config_dir="${XO_CONFIG_DIR:-${HOME}/.config/xo-cli}"
local file mnemonic
for file in "${config_dir}"/mnemonics/mnemonic-*(N); do
[[ -f "${file}" ]] || continue
mnemonic="${file:t}"
[[ "${mnemonic}" == "$1"* ]] && print -r -- "${mnemonic}"
done
}
# @description
# Main zsh completion dispatcher registered via `compdef`.
# It resolves command context from `$words`/`$CURRENT` and serves:
@@ -38,7 +51,7 @@ _{{FUNC_NAME}}_completions() {
# If previous token is `-m/--mnemonic-file`, complete mnemonic sources.
if [[ "${words[CURRENT-1]}" == "-m" || "${words[CURRENT-1]}" == "--mnemonic-file" ]]; then
local mnemonics
mnemonics=("${(@f)$(__xo_complete mnemonics "${words[CURRENT]}")}")
mnemonics=("${(@f)$(__xo_complete_mnemonics "${words[CURRENT]}")}")
if [[ ${#mnemonics[@]} -gt 0 ]]; then
compadd -- "${mnemonics[@]}"
return