Module: Space::Core::ShellIntegration

Defined in:
lib/space_core/shell_integration.rb

Constant Summary collapse

FISH_TEMPLATE =
"# Generated by space-architect. Do not edit by hand.\n\nfunction __space_architect_command\n    set -l __ps_index 1\n\n    while test $__ps_index -le (count $argv)\n        set -l __ps_arg $argv[$__ps_index]\n\n        switch \"$__ps_arg\"\n            case \"--\"\n                set __ps_index (math $__ps_index + 1)\n                if test $__ps_index -le (count $argv)\n                    echo $argv[$__ps_index]\n                end\n                return 0\n            case \"--color\" \"--colors\"\n                set __ps_index (math $__ps_index + 2)\n                continue\n            case \"--color=*\" \"--colors=*\"\n                set __ps_index (math $__ps_index + 1)\n                continue\n            case \"-*\"\n                return 0\n            case \"*\"\n                echo $__ps_arg\n                return 0\n        end\n    end\nend\n\nfunction __space_architect_has_color_option\n    set -l __ps_index 1\n\n    while test $__ps_index -le (count $argv)\n        set -l __ps_arg $argv[$__ps_index]\n\n        switch \"$__ps_arg\"\n            case \"--color\" \"--colors\" \"--color=*\" \"--colors=*\"\n                return 0\n            case \"--\"\n                return 1\n        end\n\n        set __ps_index (math $__ps_index + 1)\n    end\n\n    return 1\nend\n\nfunction space --wraps space --description \"Create and manage project spaces\"\n    if not set -q __space_architect_compat_checked\n        set -g __space_architect_compat_checked 1\n        set -l __space_architect_installed_version __SPACE_ARCHITECT_VERSION__\n        set -l __space_architect_binary_version (command space --version 2>/dev/null)\n        if test \"$__space_architect_binary_version\" != \"$__space_architect_installed_version\"\n            echo \"space-architect: shell integration version $__space_architect_installed_version does not match binary version $__space_architect_binary_version; re-run 'space shell fish install'\" >&2\n        end\n    end\n\n    set -l __space_command (__space_architect_command $argv)\n    set -l __space_args $argv\n\n    if test -t 1; and not __space_architect_has_color_option $argv\n        set __space_args --color=always $__space_args\n    end\n\n    switch \"$__space_command\"\n        case new use\n            set -l __space_output (command space $__space_args)\n            set -l __space_status $status\n\n            if test (count $__space_output) -gt 0\n                printf \"%s\\n\" $__space_output\n            end\n\n            if test $__space_status -eq 0\n                set -l __space_target $__space_output[-1]\n                set __space_target (string replace -r \"^~(?=/|\\$)\" $HOME -- $__space_target)\n                if test -d \"$__space_target\"\n                    cd \"$__space_target\"\n                end\n            end\n\n            return $__space_status\n        case \"*\"\n            command space $__space_args\n            return $status\n    end\nend\n"
FISH_COMPLETIONS =
"# Generated by space-architect. Do not edit by hand.\n\nfunction __space_architect_complete_command\n    set -l tokens (commandline -opc)\n    set -l index 2\n\n    while test $index -le (count $tokens)\n        set -l token $tokens[$index]\n\n        switch \"$token\"\n            case \"--\"\n                set index (math $index + 1)\n                if test $index -le (count $tokens)\n                    echo $tokens[$index]\n                end\n                return 0\n            case \"--color\" \"--colors\"\n                set index (math $index + 2)\n                continue\n            case \"--color=*\" \"--colors=*\"\n                set index (math $index + 1)\n                continue\n            case \"-*\"\n                set index (math $index + 1)\n                continue\n            case \"*\"\n                echo $token\n                return 0\n        end\n    end\nend\n\nfunction __space_architect_complete_needs_command\n    test -z \"$(__space_architect_complete_command)\"\nend\n\nfunction __space_architect_complete_using_command\n    contains -- (__space_architect_complete_command) $argv\nend\n\nfunction __space_architect_complete_first_argument_after\n    set -l commands $argv\n    set -l tokens (commandline -opc)\n    set -l index 2\n    set -l matched_command 0\n\n    while test $index -le (count $tokens)\n        set -l token $tokens[$index]\n\n        switch \"$token\"\n            case \"--color\" \"--colors\"\n                set index (math $index + 2)\n                continue\n            case \"--color=*\" \"--colors=*\" \"-*\"\n                set index (math $index + 1)\n                continue\n        end\n\n        if test $matched_command -eq 0\n            if contains -- \"$token\" $commands\n                set matched_command 1\n            else\n                return 1\n            end\n        else\n            echo $token\n            return 0\n        end\n\n        set index (math $index + 1)\n    end\n\n    return 1\nend\n\nfunction __space_architect_complete_has_first_argument_after\n    set -q argv[1]; or return 1\n    set -l first_argument (__space_architect_complete_first_argument_after $argv)\n    test -n \"$first_argument\"\nend\n\nfunction __space_architect_complete_second_argument_after\n    set -q argv[1]; or return 1\n    set -l command $argv[1]\n    set -l tokens (commandline -opc)\n    set -l index 2\n    set -l matched_command 0\n    set -l matched_first_argument 0\n\n    while test $index -le (count $tokens)\n        set -l token $tokens[$index]\n\n        switch \"$token\"\n            case \"--color\" \"--colors\"\n                set index (math $index + 2)\n                continue\n            case \"--color=*\" \"--colors=*\" \"-*\"\n                set index (math $index + 1)\n                continue\n        end\n\n        if test $matched_command -eq 0\n            test \"$token\" = \"$command\"; or return 1\n            set matched_command 1\n        else if test $matched_first_argument -eq 0\n            set matched_first_argument 1\n        else\n            echo $token\n            return 0\n        end\n\n        set index (math $index + 1)\n    end\n\n    return 1\nend\n\nfunction __space_architect_complete_has_second_argument_after\n    set -q argv[1]; or return 1\n    set -l second_argument (__space_architect_complete_second_argument_after $argv)\n    test -n \"$second_argument\"\nend\n\nfunction __space_architect_complete_first_argument_is\n    set -q argv[1]; or return 1\n    set -l expected $argv[1]\n    set -e argv[1]\n    test \"$(__space_architect_complete_first_argument_after $argv)\" = \"$expected\"\nend\n\nfunction __space_architect_complete_spaces\n    command space shell complete spaces 2>/dev/null\nend\n\nfunction __space_architect_complete_statuses\n    command space shell complete statuses 2>/dev/null\nend\n\nfunction __space_architect_complete_config_keys\n    command space shell complete config-keys 2>/dev/null\nend\n\nfunction __space_architect_complete_config_set_key\n    set -l tokens (commandline -opc)\n    set -l index 2\n    set -l matched_config 0\n    set -l matched_set 0\n\n    while test $index -le (count $tokens)\n        set -l token $tokens[$index]\n\n        switch \"$token\"\n            case \"--color\" \"--colors\"\n                set index (math $index + 2)\n                continue\n            case \"--color=*\" \"--colors=*\" \"-*\"\n                set index (math $index + 1)\n                continue\n        end\n\n        if test $matched_config -eq 0\n            test \"$token\" = \"config\"; or return 1\n            set matched_config 1\n        else if test $matched_set -eq 0\n            test \"$token\" = \"set\"; or return 1\n            set matched_set 1\n        else\n            echo $token\n            return 0\n        end\n\n        set index (math $index + 1)\n    end\n\n    return 1\nend\n\nfunction __space_architect_complete_config_set_has_key\n    test -n \"$(__space_architect_complete_config_set_key)\"\nend\n\nfunction __space_architect_complete_config_set_key_is\n    test \"$(__space_architect_complete_config_set_key)\" = \"$argv[1]\"\nend\n\ncomplete -c space -f -l color -x -a \"auto always never\" -d \"Color output\"\ncomplete -c space -f -l colors -x -a \"auto always never\" -d \"Color output\"\ncomplete -c space -f -n \"__space_architect_complete_using_command init shell\" -l force -d \"Overwrite existing files\"\ncomplete -c space -f -n \"__space_architect_complete_using_command new\" -s r -l repo -x -d \"Clone a repo into the new space\"\n\ncomplete -c space -f -n \"__space_architect_complete_needs_command\" -a init -d \"Create default XDG config and state files\"\ncomplete -c space -f -n \"__space_architect_complete_needs_command\" -a new -d \"Create a new project space\"\ncomplete -c space -f -n \"__space_architect_complete_needs_command\" -a list -d \"List spaces\"\ncomplete -c space -f -n \"__space_architect_complete_needs_command\" -a ls -d \"List spaces\"\ncomplete -c space -f -n \"__space_architect_complete_needs_command\" -a show -d \"Show space metadata\"\ncomplete -c space -f -n \"__space_architect_complete_needs_command\" -a path -d \"Print a space path\"\ncomplete -c space -f -n \"__space_architect_complete_needs_command\" -a use -d \"Select and cd to a space with fish integration\"\ncomplete -c space -f -n \"__space_architect_complete_needs_command\" -a current -d \"Show the current space\"\ncomplete -c space -f -n \"__space_architect_complete_needs_command\" -a status -d \"Report or set a space status\"\ncomplete -c space -f -n \"__space_architect_complete_needs_command\" -a config -d \"Show or update config\"\ncomplete -c space -f -n \"__space_architect_complete_needs_command\" -a repo -d \"Manage repos in the current space\"\ncomplete -c space -f -n \"__space_architect_complete_needs_command\" -a repos -d \"Manage repos in the current space\"\ncomplete -c space -f -n \"__space_architect_complete_needs_command\" -a shell -d \"Manage shell integration\"\n\ncomplete -c space -f -n \"__space_architect_complete_using_command show path use\" -a \"(__space_architect_complete_spaces)\" -d \"Space\"\ncomplete -c space -f -n \"__space_architect_complete_using_command status\" -a \"(__space_architect_complete_spaces)\" -d \"Space\"\ncomplete -c space -f -n \"__space_architect_complete_using_command status\" -a \"(__space_architect_complete_statuses)\" -d \"Status\"\ncomplete -c space -f -n \"__space_architect_complete_first_argument_is init shell; and not __space_architect_complete_has_second_argument_after shell\" -a fish -d \"Fish shell\"\n\ncomplete -c space -f -n \"__space_architect_complete_using_command repo repos; and not __space_architect_complete_has_first_argument_after repo repos\" -a add -d \"Clone repos into the current space\"\ncomplete -c space -f -n \"__space_architect_complete_using_command repo repos; and not __space_architect_complete_has_first_argument_after repo repos\" -a list -d \"List repos in the current space\"\ncomplete -c space -f -n \"__space_architect_complete_using_command repo repos; and not __space_architect_complete_has_first_argument_after repo repos\" -a ls -d \"List repos in the current space\"\ncomplete -c space -f -n \"__space_architect_complete_using_command repo repos; and not __space_architect_complete_has_first_argument_after repo repos\" -a resolve -d \"Resolve repo names without cloning\"\n\ncomplete -c space -f -n \"__space_architect_complete_using_command config; and not __space_architect_complete_has_first_argument_after config\" -a show -d \"Show config\"\ncomplete -c space -f -n \"__space_architect_complete_using_command config; and not __space_architect_complete_has_first_argument_after config\" -a path -d \"Print config path\"\ncomplete -c space -f -n \"__space_architect_complete_using_command config; and not __space_architect_complete_has_first_argument_after config\" -a set -d \"Set a config value\"\ncomplete -c space -f -n \"__space_architect_complete_first_argument_is set config; and not __space_architect_complete_config_set_has_key\" -a \"(__space_architect_complete_config_keys)\" -d \"Config key\"\ncomplete -c space -f -n \"__space_architect_complete_config_set_key_is git_clone_protocol\" -a \"ssh https\" -d \"Clone protocol\"\ncomplete -c space -f -n \"__space_architect_complete_config_set_key_is default_provider\" -a \"github.com gitlab.com\" -d \"Git provider\"\n\ncomplete -c space -f -n \"__space_architect_complete_using_command shell; and not __space_architect_complete_has_first_argument_after shell\" -a init -d \"Print shell integration\"\ncomplete -c space -f -n \"__space_architect_complete_using_command shell; and not __space_architect_complete_has_first_argument_after shell\" -a fish -d \"Manage fish integration and completions\"\ncomplete -c space -f -n \"__space_architect_complete_using_command shell; and not __space_architect_complete_has_first_argument_after shell\" -a complete -d \"Print completion candidates\"\ncomplete -c space -f -n \"__space_architect_complete_first_argument_is fish shell; and not __space_architect_complete_has_second_argument_after shell\" -a install -d \"Install fish integration and completions\"\ncomplete -c space -f -n \"__space_architect_complete_first_argument_is fish shell; and not __space_architect_complete_has_second_argument_after shell\" -a uninstall -d \"Remove fish integration and completions\"\ncomplete -c space -f -n \"__space_architect_complete_first_argument_is fish shell; and not __space_architect_complete_has_second_argument_after shell\" -a path -d \"Print fish integration paths\"\ncomplete -c space -f -n \"__space_architect_complete_first_argument_is complete shell; and not __space_architect_complete_has_second_argument_after shell\" -a \"spaces statuses config-keys config-values shells color-modes repo-subcommands config-subcommands fish-subcommands\" -d \"Completion kind\"\n"

Class Method Summary collapse

Class Method Details

.completions_for(shell) ⇒ Object



339
340
341
342
343
344
345
346
# File 'lib/space_core/shell_integration.rb', line 339

def self.completions_for(shell)
  case shell.to_s
  when "fish"
    FISH_COMPLETIONS
  else
    raise Error, "Unsupported shell '#{shell}'. Expected: fish"
  end
end

.completions_path_for(shell, env: ENV) ⇒ Object



357
358
359
360
361
362
363
364
# File 'lib/space_core/shell_integration.rb', line 357

def self.completions_path_for(shell, env: ENV)
  case shell.to_s
  when "fish"
    XDG.config_home(env: env).join("fish", "completions", "space.fish")
  else
    raise Error, "Unsupported shell '#{shell}'. Expected: fish"
  end
end

.for(shell) ⇒ Object



330
331
332
333
334
335
336
337
# File 'lib/space_core/shell_integration.rb', line 330

def self.for(shell)
  case shell.to_s
  when "fish"
    FISH_TEMPLATE.gsub("__SPACE_ARCHITECT_VERSION__", VERSION)
  else
    raise Error, "Unsupported shell '#{shell}'. Expected: fish"
  end
end

.install(shell, env: ENV, force: false) ⇒ Object



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/space_core/shell_integration.rb', line 366

def self.install(shell, env: ENV, force: false)
  function_result = write_managed_file(
    path: path_for(shell, env:),
    content: self.for(shell),
    force: force,
    description: "fish function"
  )
  completions_result = write_managed_file(
    path: completions_path_for(shell, env:),
    content: completions_for(shell),
    force: force,
    description: "fish completions"
  )

  function_result.merge(
    completions_action: completions_result.fetch(:action),
    completions_path: completions_result.fetch(:path)
  )
end

.managed_fish?(content) ⇒ Boolean

Returns:

  • (Boolean)


406
407
408
409
# File 'lib/space_core/shell_integration.rb', line 406

def self.managed_fish?(content)
  content.include?("Generated by space-architect") ||
    (content.include?("function __space_architect_command") && content.include?("function space --wraps space"))
end

.path_for(shell, env: ENV) ⇒ Object



348
349
350
351
352
353
354
355
# File 'lib/space_core/shell_integration.rb', line 348

def self.path_for(shell, env: ENV)
  case shell.to_s
  when "fish"
    XDG.config_home(env: env).join("fish", "functions", "space.fish")
  else
    raise Error, "Unsupported shell '#{shell}'. Expected: fish"
  end
end

.remove_managed_file(path:, content:, force:, description:) ⇒ Object



426
427
428
429
430
431
432
433
434
435
436
# File 'lib/space_core/shell_integration.rb', line 426

def self.remove_managed_file(path:, content:, force:, description:)
  return { action: :missing, path: path } unless path.exist?

  existing = path.read
  if existing != content && !force && !managed_fish?(existing)
    raise Error, "Refusing to remove existing #{description} at #{path}. Re-run with --force."
  end

  FileUtils.rm_f(path)
  { action: :removed, path: path }
end

.uninstall(shell, env: ENV, force: false) ⇒ Object



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/space_core/shell_integration.rb', line 386

def self.uninstall(shell, env: ENV, force: false)
  function_result = remove_managed_file(
    path: path_for(shell, env:),
    content: self.for(shell),
    force: force,
    description: "fish function"
  )
  completions_result = remove_managed_file(
    path: completions_path_for(shell, env:),
    content: completions_for(shell),
    force: force,
    description: "fish completions"
  )

  function_result.merge(
    completions_action: completions_result.fetch(:action),
    completions_path: completions_result.fetch(:path)
  )
end

.write_managed_file(path:, content:, force:, description:) ⇒ Object



411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/space_core/shell_integration.rb', line 411

def self.write_managed_file(path:, content:, force:, description:)
  existing = path.read if path.exist?

  if existing && existing != content && !force && !managed_fish?(existing)
    raise Error, "Refusing to overwrite existing #{description} at #{path}. Re-run with --force."
  end

  if existing == content
    { action: :unchanged, path: path }
  else
    AtomicWrite.write(path, content)
    { action: existing ? :updated : :installed, path: path }
  end
end