Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fish support (closes #8, closes #27) #65

Merged
merged 12 commits into from
Jan 30, 2022
Merged
9 changes: 5 additions & 4 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ FROM mcr.microsoft.com/vscode/devcontainers/ruby:0-${VARIANT}
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi

# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# Install additional OS packages.
# hadolint ignore=DL3009
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends fish

# [Optional] Uncomment this line to install additional gems.
# RUN gem install <your-gem-names-here>

# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
7 changes: 5 additions & 2 deletions .github/workflows/test-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@ jobs:
bundler-cache: true
- name: Build binary
run: rake build
- name: Install ZSH on ubuntu
- name: Install shells on ubuntu
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get -y install zsh
run: sudo apt-get -y install zsh fish
- name: Install shells on macos
if: matrix.os == 'macos-latest'
run: brew install fish
- name: Run integration Tests
run: bundle exec cucumber -s --tags="not @wip" --color
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ To initialize shell functions, add the following to your `~/.bash_profile` or

eval "$(scmpuff init -s)"

or for Fish, add the following to your `~/.config/fish/config.fish` file:

scmpuff init -s --shell=fish | source

This will define the scmpuff shell functions as well as some handy shortcuts.


Expand Down
6 changes: 5 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ end

desc "builds & installs the binary to $GOPATH/bin"
task :install => :build do
cp "bin/scmpuff", "#{ENV['GOPATH']}/bin/scmpuff"
# Don't cp directly over an existing file - it causes problems with Apple code signing.
# https://developer.apple.com/documentation/security/updating_mac_software
destination = "#{ENV['GOPATH']}/bin/scmpuff"
rm destination if File.exist?(destination)
cp "bin/scmpuff", destination
end

desc "run unit tests"
Expand Down
25 changes: 25 additions & 0 deletions commands/inits/data/git_wrapper.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Based on https://github.com/arbelt/fish-plugin-scmpuff,
# with scmpuff-exec support (https://github.com/mroth/scmpuff/pull/49)
functions -e git

set -q SCMPUFF_GIT_CMD; or set -x SCMPUFF_GIT_CMD (which git)

function git
if test (count $argv) -eq 0
eval $SCMPUFF_GIT_CMD
set -l s $status
return $s
end

switch $argv[1]
case commit blame log rebase merge
scmpuff exec -- "$SCMPUFF_GIT_CMD" $argv
case checkout diff rm reset restore
scmpuff exec --relative -- "$SCMPUFF_GIT_CMD" $argv
case add
scmpuff exec -- "$SCMPUFF_GIT_CMD" $argv
scmpuff_status
case '*'
eval command "$SCMPUFF_GIT_CMD" (string escape -- $argv)
end
end
32 changes: 32 additions & 0 deletions commands/inits/data/status_shortcuts.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Based on https://github.com/arbelt/fish-plugin-scmpuff,
# with fish3 fix https://github.com/arbelt/fish-plugin-scmpuff/pull/3
function scmpuff_status
scmpuff_clear_vars
set -lx scmpuff_env_char "e"
set -l cmd_output (/usr/bin/env scmpuff status --filelist $argv)
set -l es "$status"

if test $es -ne 0
return $es
end

set -l files (string split \t $cmd_output[1])
if test (count $files) -gt 0
for e in (seq (count $files))
set -gx "$scmpuff_env_char""$e" "$files[$e]"
end
end

for line in $cmd_output[2..-1]
echo $line
end
end

function scmpuff_clear_vars
set -l scmpuff_env_char "e"
set -l scmpuff_env_vars (set -x | awk '{print $1}' | grep -E '^'$scmpuff_env_char'[0-9]+')

for v in $scmpuff_env_vars
set -e $v
end
end
89 changes: 67 additions & 22 deletions commands/inits/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,80 @@ package inits

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"
)

// Since the flags are defined and used in different locations, we need to
// define a variable outside with the correct scope to assign the flag to work
// with.
var includeAliases bool
var outputScript bool
var wrapGit bool

// CommandInit generates the command handler for `scmpuff init`
func CommandInit() *cobra.Command {
var (
shellType string
includeAliases bool
wrapGit bool
legacyShow bool
)

var InitCmd = &cobra.Command{
Use: "init",
Short: "Output initialization script",
Long: `
Outputs the bash/zsh initialization script for scmpuff.
Outputs the shell initialization script for scmpuff.

Initialize scmpuff by adding the following to your ~/.bash_profile or ~/.zshrc:

eval "$(scmpuff init --shell=sh)"

This should probably be evaluated in your shell startup.
For fish shell, add the following to ~/.config/fish/config.fish instead:

scmpuff init --shell=fish | source

There are a number of flags to customize the shell integration.
`,
Run: func(cmd *cobra.Command, args []string) {
if outputScript {
printScript()
} else {
fmt.Println(helpString())
// If someone's using the old ---show flag, opt-in to the newer --shell defaults
if legacyShow {
shellType = defaultShellType()
}

switch strings.ToLower(shellType) {
case "":
cmd.Help()
os.Exit(0)

case "sh", "bash", "zsh":
fmt.Println(bashCollection.Output(wrapGit, includeAliases))
os.Exit(0)

case "fish":
fmt.Println(fishCollection.Output(wrapGit, includeAliases))
os.Exit(0)

default:
fmt.Fprintf(os.Stderr, "Unrecognized shell '%s'\n", shellType)
os.Exit(1)
}
},
// Watch out for accidental args caused by NoOptDefVal (https://github.com/spf13/cobra/issues/866)
Args: cobra.NoArgs,
}

// --aliases
InitCmd.Flags().BoolVarP(
&includeAliases,
"aliases", "a", true,
"Include short aliases for convenience",
"Include short git aliases",
)

// --show
InitCmd.Flags().BoolVarP(
&outputScript,
"show", "s", false,
// --show (deprecated in favor of --shell)
InitCmd.Flags().BoolVar(
&legacyShow,
"show", false,
"Output scmpuff initialization scripts",
)
InitCmd.Flags().MarkHidden("show")

// --wrap
InitCmd.Flags().BoolVarP(
Expand All @@ -54,12 +84,27 @@ This should probably be evaluated in your shell startup.
"Wrap standard git commands",
)

// --shell
InitCmd.Flags().StringVarP(
&shellType,
"shell", "s", "",
"Output shell type: sh | bash | zsh | fish",
)
InitCmd.Flag("shell").NoOptDefVal = defaultShellType()

return InitCmd
}

// TODO: check for proper shell version
func helpString() string {
return `# Initialize scmpuff by adding the following to ~/.bash_profile or ~/.zshrc:
// defaultShell returns the shellType assumed if user does not specify.
// in the future, we may wish to customize this based on the $SHELL variable.
func defaultShellType() string {
if shellenv, ok := os.LookupEnv("SHELL"); ok {
base := filepath.Base(shellenv)
switch base {
case "sh", "bash", "zsh", "fish":
return base
}
}

eval "$(scmpuff init -s)"`
return "sh"
}
27 changes: 27 additions & 0 deletions commands/inits/init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package inits

import "testing"

func Test_defaultShellType(t *testing.T) {
tests := []struct {
shellenv string
want string
}{
// supported shells at a bunch of different locations
{"/bin/zsh", "zsh"},
{"/usr/bin/zsh", "zsh"},
{"/usr/local/bin/zsh", "zsh"},
{"/bin/bash", "bash"},
{"/usr/local/bin/fish", "fish"},

// edge cases
{"", "sh"},
{"/bin/unsupported", "sh"},
}
for _, tt := range tests {
t.Setenv("SHELL", tt.shellenv)
if got := defaultShellType(); got != tt.want {
t.Errorf("defaultShellType(%v) = %v, want %v", tt.shellenv, got, tt.want)
}
}
}
42 changes: 33 additions & 9 deletions commands/inits/scripts.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,52 @@ package inits

import (
_ "embed"
"fmt"
"strings"
)

//go:embed data/status_shortcuts.sh
var scriptStatusShortcuts string

//go:embed data/status_shortcuts.fish
var scriptStatusShortcutsFish string

//go:embed data/aliases.sh
var scriptAliases string

//go:embed data/git_wrapper.sh
var scriptGitWrapper string

func printScript() {
if outputScript {
fmt.Println(scriptStatusShortcuts)
}
//go:embed data/git_wrapper.fish
var scriptGitWrapperFish string

if includeAliases {
fmt.Println(scriptAliases)
}
type scriptCollection struct {
statusShortcuts string
gitWrapper string
aliases string
}

var bashCollection = scriptCollection{
statusShortcuts: scriptStatusShortcuts,
gitWrapper: scriptGitWrapper,
aliases: scriptAliases,
}

var fishCollection = scriptCollection{
statusShortcuts: scriptStatusShortcutsFish,
gitWrapper: scriptGitWrapperFish,
aliases: scriptAliases,
}

func (sc scriptCollection) Output(wrapGit, aliases bool) string {
var b strings.Builder
b.WriteString(sc.statusShortcuts)
if wrapGit {
fmt.Println(scriptGitWrapper)
b.WriteRune('\n')
b.WriteString(sc.gitWrapper)
}
if aliases {
b.WriteRune('\n')
b.WriteString(sc.aliases)
}
return b.String()
}
11 changes: 9 additions & 2 deletions features/command_init.feature
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ Feature: init command
When I successfully run `scmpuff init -s`
Then the output should contain "scmpuff_status()"

Scenario: init with an unrecognized shell should produce an error
When I run `scmpuff init --shell=oil`
Then the exit status should be 1
Then the output should contain "Unrecognized shell 'oil'"

Scenario Outline: --aliases controls short aliases in output (default: yes)
When I successfully run `scmpuff init <flags>`
Then the output <should?> contain "alias gs='scmpuff_status'"
Expand All @@ -32,12 +37,14 @@ Feature: init command

Scenario Outline: Evaling init -s defines status shortcuts in environment
When I run `<shell>` interactively
And I type `eval "$(scmpuff init -s)"`
And I initialize scmpuff in `<shell>`
And I type "type scmpuff_status"
And I type "type scmpuff_clear_vars"
And I type "exit"
And I close the shell `<shell>`
Then the output should not contain "not found"
Examples:
| shell |
| bash |
| zsh |
| fish |

Loading