From 0890041ee9984f88aa170c722aacb3c7cf136845 Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Sat, 13 Sep 2025 07:12:38 +0100 Subject: [PATCH] refactor(siren): streamline command handling and improve help output This update reorganizes the command parsing logic for better clarity and maintains backward compatibility. The help output has been enhanced to provide clearer usage instructions, including support for specifying the editor in either argument position. Additionally, error handling has been improved for missing editor specifications and command requirements. --- siren | 326 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 185 insertions(+), 141 deletions(-) diff --git a/siren b/siren index 4df7977..11eb590 100755 --- a/siren +++ b/siren @@ -50,97 +50,49 @@ define_settings() { fi } -# Get extensions lockfile path for current editor. -# -# Returns: Path to extensions lock file via `STDOUT`. -get_extensions_lock() { - echo "${SCRIPT_DIR}/extensions.${SETUP_EDITOR}.lock" -} - -# Get extensions lockfile path for a specific editor name. -# Does not rely on SETUP_EDITOR. -extensions_lock_for_editor() { - local editor_name="$1" - echo "${SCRIPT_DIR}/extensions.${editor_name}.lock" -} - -# Normalize editor name/synonyms to canonical editor id. -# Returns canonical id via STDOUT or empty string if unknown. -normalize_editor_name() { - local name - name="$(echo "${1:-}" | tr '[:upper:]' '[:lower:]')" - case "${name}" in - cursor | c) - echo "cursor" - ;; - vscode | code | vsc | v) - echo "vscode" - ;; - vscode-insiders | code-insiders | insiders | vsci | i) - echo "vscode-insiders" - ;; - windsurf | wind | surf | w) - echo "windsurf" - ;; - kiro | k) - echo "kiro" - ;; - *) - echo "" - ;; - esac -} - -# Read a lockfile and output sorted unique extension ids without versions. -extensions_ids_from_lock() { - local lock_path="$1" - grep -Ev '^\s*(#|$)' "${lock_path}" | - cut -d@ -f1 | - sort -u -} - # ============================================================================== # Help # ============================================================================== show_help() { cat << EOF -Usage: $(basename "$0") EDITOR COMMAND [OPTIONS] - $(basename "$0") config - $(basename "$0") shared-extensions [--json] [EDITORS...] +Usage: + $(basename "$0") EDITOR COMMAND [OPTIONS] + $(basename "$0") COMMAND EDITOR [OPTIONS] + $(basename "$0") config + $(basename "$0") shared-extensions [--json] [EDITORS...] Editors: cursor, c Cursor editor - kiro, k Kiro editor + kiro, k Kiro editor (uses OpenVSX by default) vscode, code, vsc, v Visual Studio Code vscode-insiders, vsci, i Visual Studio Code Insiders windsurf, surf, w Windsurf editor Commands: - config, conf Create symlinks for editor config files - dump-extensions, dump Export installed editor extensions to a lock file. - extensions, ext Install editor extensions from a lock file. - install Install a specific extension by identifier (e.g., ms-python.python). - If no version is specified, installs the latest version. + config, conf Create symlinks. + With editor: full editor + static config. + Without editor: static-only symlinks. + dump-extensions, dump Export installed extensions to lock file + for the specified editor. + extensions, ext Install extensions from lock file for the + specified editor. + install Install a specific extension id for the + specified editor (e.g. ms-python.python). shared-extensions, shared Print extensions present in all specified editors. - Defaults to comparing: cursor, vscode - Use --json to output a JSON array + Defaults: cursor, vscode. Use --json for JSON. Options: - --latest When used with the extensions command, installs the - latest version of each extension instead of the - exact version from the lock file. + --latest With 'extensions': install latest versions instead + of exact lockfile versions. With 'install': install + the latest version of the specified extension. + --force-latest Force latest behavior where applicable. -Special Usage: - config, conf When used without an editor, creates only static - symlinks (CLAUDE.md, dictionaries, etc.) - -Description: - This script manages editor configuration files and extensions. - It can create symlinks for settings, keybindings, and snippets, - as well as dump extension lock files and install extensions from them. - It prefers OpenVSX for Kiro, falling back to VS Marketplace. +Notes: + - For 'dump', 'extensions', and 'install', the editor may be given as + arg 1 or arg 2; both orders are supported. + - Kiro prefers OpenVSX, falling back to VS Marketplace. EOF } @@ -180,6 +132,70 @@ check_dependencies() { fi } +# Get extensions lockfile path for current editor. +# +# Returns: Path to extensions lock file via `STDOUT`. +get_extensions_lock() { + echo "${SCRIPT_DIR}/extensions.${SETUP_EDITOR}.lock" +} + +# Get extensions lockfile path for a specific editor name. +# Does not rely on SETUP_EDITOR. +extensions_lock_for_editor() { + local editor_name="$1" + echo "${SCRIPT_DIR}/extensions.${editor_name}.lock" +} + +# Normalize editor name/synonyms to canonical editor id. +# +# Returns: canonical id via `STDOUT` or empty string if unknown. +normalize_editor_name() { + local name + name="$(echo "${1:-}" | tr '[:upper:]' '[:lower:]')" + case "${name}" in + cursor | c) + echo "cursor" + ;; + vscode | code | vsc | v) + echo "vscode" + ;; + vscode-insiders | code-insiders | insiders | vsci | i) + echo "vscode-insiders" + ;; + windsurf | wind | surf | w) + echo "windsurf" + ;; + kiro | k) + echo "kiro" + ;; + *) + echo "" + ;; + esac +} + +# Require an editor to be specified for a command. +# +# Returns: If no editor is specified, exits program with error. +require_editor() { + local command="$1" + + if [[ -z "${SETUP_EDITOR}" ]]; then + error "No editor specified for command '${command}'" + echo >&2 + show_help + exit 1 + fi +} + +# Read a lockfile and output sorted unique extension ids without versions. +extensions_ids_from_lock() { + local lock_path="$1" + grep -Ev '^\s*(#|$)' "${lock_path}" | + cut -d@ -f1 | + sort -u +} + # Determine current platform for OpenVSX downloads. # # Returns: Platform string compatible with OpenVSX via `STDOUT`. @@ -1221,7 +1237,9 @@ do_install_extension() { if [[ -z "${extension_id}" ]]; then error "Extension identifier required" - info "Usage: siren EDITOR install EXTENSION_ID" + info "Usage:" + info " siren EDITOR install EXTENSION_ID" + info " siren install EDITOR EXTENSION_ID" exit 1 fi @@ -1353,99 +1371,125 @@ main() { exit 1 fi + # Set command or SETUP_EDITOR based on first argument. + local command="" + if [[ $# -ge 1 ]]; then + SETUP_EDITOR="$(normalize_editor_name "$1")" + if [[ -z "${SETUP_EDITOR}" ]]; then + command="$(echo "$1" | tr '[:upper:]' '[:lower:]')" + fi + shift 1 + fi + + # Set command or SETUP_EDITOR based on second argument. + if [[ $# -ge 1 ]]; then + if [[ -z "${command}" ]]; then + command="$(echo "$1" | tr '[:upper:]' '[:lower:]')" + shift 1 + else + SETUP_EDITOR="$(normalize_editor_name "$1")" + if [[ -n "${SETUP_EDITOR}" ]]; then + shift 1 + fi + fi + fi + # Handle help command. - if [[ "$1" == "help" || " $* " == *" --help "* || " $* " == *" -h "* ]]; then + if [[ " $* " == *" --help "* || " $* " == *" -h "* ]]; then + command="help" + fi + + # Handle help command early. + if [[ "${command}" == "help" ]]; then show_help exit 0 fi - # Normalize first argument. - local first_arg - first_arg="$(echo "${1}" | tr '[:upper:]' '[:lower:]')" - - # Handle standalone top-level commands. - case "${first_arg}" in - config | conf) - define_settings - do_static_config - exit 0 - ;; - shared-extensions | shared) - shift 1 - # Remaining arguments are optional editor names - do_shared_extensions "$@" - exit 0 - ;; - esac - - # Set editor from first argument using normalization. - SETUP_EDITOR="$(normalize_editor_name "${first_arg}")" - if [[ -z "${SETUP_EDITOR}" ]]; then - error "Unsupported editor '${first_arg}'" + # If still no command, exit with error. + if [[ -z "${command}" ]]; then + error "No command provided" + echo >&2 show_help exit 1 fi + + # Set PREFER_OPENVSX to true for Kiro. if [[ "${SETUP_EDITOR}" == "kiro" ]]; then PREFER_OPENVSX="true" fi - # Require at least two arguments from this point on (editor and command). - if [[ $# -lt 2 ]]; then - error "No command specified for editor '${1}'" - show_help - exit 1 - fi - - # Initialize settings now that SETUP_EDITOR is known. define_settings - # Get command and shift arguments. - local command="${2}" - shift 2 - - # Parse options. - local use_latest="false" - local extension_id="" - local extra_args=() - while [[ $# -gt 0 ]]; do - case "$1" in - --latest) - use_latest="true" - shift - ;; - --force-latest) - use_latest="force" - shift - ;; - *) - extra_args+=("$1") - shift - ;; - esac - done - - # Handle commands. case "${command}" in - "help" | "h") - show_help + config | conf) + if [[ -n "${SETUP_EDITOR}" ]]; then + do_config + else + do_static_config + fi exit 0 ;; - "config" | "conf") - do_config - ;; - "dump-extensions" | "dump") + dump-extensions | dump) + require_editor "dump" do_dump_extensions + exit 0 ;; - "extensions" | "ext") + shared-extensions | shared) + do_shared_extensions "$@" + exit 0 + ;; + extensions | ext) + require_editor "extensions" + local use_latest="false" + while [[ $# -gt 0 ]]; do + case "$1" in + --latest) + use_latest="true" + shift + ;; + --force-latest) + use_latest="force" + shift + ;; + *) + break + ;; + esac + done do_install_extensions "${use_latest}" + exit 0 ;; - "install") - if [[ ${#extra_args[@]} -ne 1 ]]; then + install) + require_editor "install" + local use_latest_i="false" + local extension_arg="" + while [[ $# -gt 0 ]]; do + case "$1" in + --latest) + use_latest_i="true" + shift + ;; + --force-latest) + use_latest_i="force" + shift + ;; + *) + if [[ -z "${extension_arg}" ]]; then + extension_arg="$1" + shift + else + break + fi + ;; + esac + done + if [[ -z "${extension_arg}" ]]; then error "The 'install' command requires exactly one argument: the extension ID." show_help exit 1 fi - do_install_extension "${extra_args[0]}" + do_install_extension "${extension_arg}" "${use_latest_i}" + exit 0 ;; "") error "No command provided"