Module: SpaceArchitect::ShellIntegration

Defined in:
lib/space_architect/shell_integration.rb

Constant Summary collapse

FISH_TEMPLATE =
<<~'FISH'
  # Generated by space-architect. Do not edit by hand.

  function __space_architect_command
      set -l __ps_index 1

      while test $__ps_index -le (count $argv)
          set -l __ps_arg $argv[$__ps_index]

          switch "$__ps_arg"
              case "--"
                  set __ps_index (math $__ps_index + 1)
                  if test $__ps_index -le (count $argv)
                      echo $argv[$__ps_index]
                  end
                  return 0
              case "--color" "--colors"
                  set __ps_index (math $__ps_index + 2)
                  continue
              case "--color=*" "--colors=*"
                  set __ps_index (math $__ps_index + 1)
                  continue
              case "-*"
                  return 0
              case "*"
                  echo $__ps_arg
                  return 0
          end
      end
  end

  function __space_architect_has_color_option
      set -l __ps_index 1

      while test $__ps_index -le (count $argv)
          set -l __ps_arg $argv[$__ps_index]

          switch "$__ps_arg"
              case "--color" "--colors" "--color=*" "--colors=*"
                  return 0
              case "--"
                  return 1
          end

          set __ps_index (math $__ps_index + 1)
      end

      return 1
  end

  function space --wraps space --description "Create and manage project spaces"
      if not set -q __space_architect_compat_checked
          set -g __space_architect_compat_checked 1
          set -l __space_architect_installed_version __SPACE_ARCHITECT_VERSION__
          set -l __space_architect_binary_version (command space --version 2>/dev/null)
          if test "$__space_architect_binary_version" != "$__space_architect_installed_version"
              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
          end
      end

      set -l __space_command (__space_architect_command $argv)
      set -l __space_args $argv

      if test -t 1; and not __space_architect_has_color_option $argv
          set __space_args --color=always $__space_args
      end

      switch "$__space_command"
          case new use
              set -l __space_output (command space $__space_args)
              set -l __space_status $status

              if test (count $__space_output) -gt 0
                  printf "%s\n" $__space_output
              end

              if test $__space_status -eq 0
                  set -l __space_target $__space_output[-1]
                  set __space_target (string replace -r "^~(?=/|\$)" $HOME -- $__space_target)
                  if test -d "$__space_target"
                      cd "$__space_target"
                  end
              end

              return $__space_status
          case "*"
              command space $__space_args
              return $status
      end
  end
FISH
FISH_COMPLETIONS =
<<~'FISH'
  # Generated by space-architect. Do not edit by hand.

  function __space_architect_complete_command
      set -l tokens (commandline -opc)
      set -l index 2

      while test $index -le (count $tokens)
          set -l token $tokens[$index]

          switch "$token"
              case "--"
                  set index (math $index + 1)
                  if test $index -le (count $tokens)
                      echo $tokens[$index]
                  end
                  return 0
              case "--color" "--colors"
                  set index (math $index + 2)
                  continue
              case "--color=*" "--colors=*"
                  set index (math $index + 1)
                  continue
              case "-*"
                  set index (math $index + 1)
                  continue
              case "*"
                  echo $token
                  return 0
          end
      end
  end

  function __space_architect_complete_needs_command
      test -z "$(__space_architect_complete_command)"
  end

  function __space_architect_complete_using_command
      contains -- (__space_architect_complete_command) $argv
  end

  function __space_architect_complete_first_argument_after
      set -l commands $argv
      set -l tokens (commandline -opc)
      set -l index 2
      set -l matched_command 0

      while test $index -le (count $tokens)
          set -l token $tokens[$index]

          switch "$token"
              case "--color" "--colors"
                  set index (math $index + 2)
                  continue
              case "--color=*" "--colors=*" "-*"
                  set index (math $index + 1)
                  continue
          end

          if test $matched_command -eq 0
              if contains -- "$token" $commands
                  set matched_command 1
              else
                  return 1
              end
          else
              echo $token
              return 0
          end

          set index (math $index + 1)
      end

      return 1
  end

  function __space_architect_complete_has_first_argument_after
      set -q argv[1]; or return 1
      set -l first_argument (__space_architect_complete_first_argument_after $argv)
      test -n "$first_argument"
  end

  function __space_architect_complete_second_argument_after
      set -q argv[1]; or return 1
      set -l command $argv[1]
      set -l tokens (commandline -opc)
      set -l index 2
      set -l matched_command 0
      set -l matched_first_argument 0

      while test $index -le (count $tokens)
          set -l token $tokens[$index]

          switch "$token"
              case "--color" "--colors"
                  set index (math $index + 2)
                  continue
              case "--color=*" "--colors=*" "-*"
                  set index (math $index + 1)
                  continue
          end

          if test $matched_command -eq 0
              test "$token" = "$command"; or return 1
              set matched_command 1
          else if test $matched_first_argument -eq 0
              set matched_first_argument 1
          else
              echo $token
              return 0
          end

          set index (math $index + 1)
      end

      return 1
  end

  function __space_architect_complete_has_second_argument_after
      set -q argv[1]; or return 1
      set -l second_argument (__space_architect_complete_second_argument_after $argv)
      test -n "$second_argument"
  end

  function __space_architect_complete_first_argument_is
      set -q argv[1]; or return 1
      set -l expected $argv[1]
      set -e argv[1]
      test "$(__space_architect_complete_first_argument_after $argv)" = "$expected"
  end

  function __space_architect_complete_spaces
      command space shell complete spaces 2>/dev/null
  end

  function __space_architect_complete_statuses
      command space shell complete statuses 2>/dev/null
  end

  function __space_architect_complete_config_keys
      command space shell complete config-keys 2>/dev/null
  end

  function __space_architect_complete_config_set_key
      set -l tokens (commandline -opc)
      set -l index 2
      set -l matched_config 0
      set -l matched_set 0

      while test $index -le (count $tokens)
          set -l token $tokens[$index]

          switch "$token"
              case "--color" "--colors"
                  set index (math $index + 2)
                  continue
              case "--color=*" "--colors=*" "-*"
                  set index (math $index + 1)
                  continue
          end

          if test $matched_config -eq 0
              test "$token" = "config"; or return 1
              set matched_config 1
          else if test $matched_set -eq 0
              test "$token" = "set"; or return 1
              set matched_set 1
          else
              echo $token
              return 0
          end

          set index (math $index + 1)
      end

      return 1
  end

  function __space_architect_complete_config_set_has_key
      test -n "$(__space_architect_complete_config_set_key)"
  end

  function __space_architect_complete_config_set_key_is
      test "$(__space_architect_complete_config_set_key)" = "$argv[1]"
  end

  complete -c space -f -l color -x -a "auto always never" -d "Color output"
  complete -c space -f -l colors -x -a "auto always never" -d "Color output"
  complete -c space -f -n "__space_architect_complete_using_command init shell" -l force -d "Overwrite existing files"
  complete -c space -f -n "__space_architect_complete_using_command new" -s r -l repo -x -d "Clone a repo into the new space"

  complete -c space -f -n "__space_architect_complete_needs_command" -a init -d "Create default XDG config and state files"
  complete -c space -f -n "__space_architect_complete_needs_command" -a new -d "Create a new project space"
  complete -c space -f -n "__space_architect_complete_needs_command" -a list -d "List spaces"
  complete -c space -f -n "__space_architect_complete_needs_command" -a ls -d "List spaces"
  complete -c space -f -n "__space_architect_complete_needs_command" -a show -d "Show space metadata"
  complete -c space -f -n "__space_architect_complete_needs_command" -a path -d "Print a space path"
  complete -c space -f -n "__space_architect_complete_needs_command" -a use -d "Select and cd to a space with fish integration"
  complete -c space -f -n "__space_architect_complete_needs_command" -a current -d "Show the current space"
  complete -c space -f -n "__space_architect_complete_needs_command" -a status -d "Set a space status"
  complete -c space -f -n "__space_architect_complete_needs_command" -a config -d "Show or update config"
  complete -c space -f -n "__space_architect_complete_needs_command" -a repo -d "Manage repos in the current space"
  complete -c space -f -n "__space_architect_complete_needs_command" -a repos -d "Manage repos in the current space"
  complete -c space -f -n "__space_architect_complete_needs_command" -a shell -d "Manage shell integration"

  complete -c space -f -n "__space_architect_complete_using_command show path use" -a "(__space_architect_complete_spaces)" -d "Space"
  complete -c space -f -n "__space_architect_complete_using_command status" -a "(__space_architect_complete_spaces)" -d "Space"
  complete -c space -f -n "__space_architect_complete_using_command status" -a "(__space_architect_complete_statuses)" -d "Status"
  complete -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"

  complete -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"
  complete -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"
  complete -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"
  complete -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"

  complete -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"
  complete -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"
  complete -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"
  complete -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"
  complete -c space -f -n "__space_architect_complete_config_set_key_is git_clone_protocol" -a "ssh https" -d "Clone protocol"
  complete -c space -f -n "__space_architect_complete_config_set_key_is default_provider" -a "github.com gitlab.com" -d "Git provider"

  complete -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"
  complete -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"
  complete -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"
  complete -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"
  complete -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"
  complete -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"
  complete -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"
FISH

Class Method Summary collapse

Class Method Details

.completions_for(shell) ⇒ Object



339
340
341
342
343
344
345
346
# File 'lib/space_architect/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_architect/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_architect/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_architect/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_architect/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_architect/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_architect/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_architect/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_architect/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