1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-11-28 09:08:41 +02:00

Merge branch 'master' into fix/tests

This commit is contained in:
Jesse Duffield 2018-09-03 21:20:17 +10:00 committed by GitHub
commit 796f17eef4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 219 additions and 142 deletions

View File

@ -14,16 +14,44 @@
- white - white
optionsTextColor: optionsTextColor:
- blue - blue
git:
# stuff relating to git
os:
# stuff relating to the OS
update: update:
method: prompt # can be: prompt | background | never method: prompt # can be: prompt | background | never
days: 14 # how often an update is checked for days: 14 # how often an update is checked for
reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined' reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined'
``` ```
## Platform Defaults:
### Windows:
```
os:
openCommand: 'cmd /c "start "" {{filename}}"'
```
### Linux:
```
os:
openCommand: 'bash -c \"xdg-open {{filename}} &>/dev/null &\"'
```
### OSX:
```
os:
openCommand: 'open {{filename}}'
```
### Recommended Config Values:
for users of VSCode
```
os:
openCommand: 'code -r {{filename}}'
```
## Color Attributes: ## Color Attributes:
For color attributes you can choose an array of attributes (with max one color attribute) For color attributes you can choose an array of attributes (with max one color attribute)

View File

@ -54,6 +54,7 @@
<pre> <pre>
<kbd>s</kbd>: squash down (only available for topmost commit) <kbd>s</kbd>: squash down (only available for topmost commit)
<kbd>r</kbd>: rename commit <kbd>r</kbd>: rename commit
<kbd>shift</kbd>+<kbd>R</kbd>: rename commit using git editor
<kbd>g</kbd>: reset to this commit <kbd>g</kbd>: reset to this commit
</pre> </pre>

View File

@ -73,7 +73,7 @@ func NewApp(config config.AppConfigurer) (*App, error) {
} }
var err error var err error
app.Log = newLogger(config) app.Log = newLogger(config)
app.OSCommand = commands.NewOSCommand(app.Log) app.OSCommand = commands.NewOSCommand(app.Log, config)
app.Tr = i18n.NewLocalizer(app.Log) app.Tr = i18n.NewLocalizer(app.Log)

View File

@ -422,6 +422,11 @@ func (c *GitCommand) PrepareCommitSubProcess() *exec.Cmd {
return c.OSCommand.PrepareSubProcess("git", "commit") return c.OSCommand.PrepareSubProcess("git", "commit")
} }
// PrepareCommitAmendSubProcess prepares a subprocess for `git commit --amend --allow-empty`
func (c *GitCommand) PrepareCommitAmendSubProcess() *exec.Cmd {
return c.OSCommand.PrepareSubProcess("git", "commit", "--amend", "--allow-empty")
}
// GetBranchGraph gets the color-formatted graph of the log for the given branch // GetBranchGraph gets the color-formatted graph of the log for the given branch
// Currently it limits the result to 100 commits, but when we get async stuff // Currently it limits the result to 100 commits, but when we get async stuff
// working we can do lazy loading // working we can do lazy loading

View File

@ -189,6 +189,19 @@ func TestGitCommandStashSave(t *testing.T) {
assert.NoError(t, gitCmd.StashSave("A stash message")) assert.NoError(t, gitCmd.StashSave("A stash message"))
} }
func TestGitCommandCommitAmend(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"commit", "--amend", "--allow-empty"}, args)
return exec.Command("echo")
}
_, err := gitCmd.PrepareCommitAmendSubProcess().CombinedOutput()
assert.NoError(t, err)
}
func TestGitCommandMergeStatusFiles(t *testing.T) { func TestGitCommandMergeStatusFiles(t *testing.T) {
type scenario struct { type scenario struct {
oldFiles []File oldFiles []File

View File

@ -6,7 +6,8 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"github.com/davecgh/go-spew/spew" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str" "github.com/mgutz/str"
@ -20,22 +21,25 @@ type Platform struct {
shell string shell string
shellArg string shellArg string
escapedQuote string escapedQuote string
openCommand string
} }
// OSCommand holds all the os commands // OSCommand holds all the os commands
type OSCommand struct { type OSCommand struct {
Log *logrus.Entry Log *logrus.Entry
Platform *Platform Platform *Platform
Config config.AppConfigurer
command func(string, ...string) *exec.Cmd command func(string, ...string) *exec.Cmd
getGlobalGitConfig func(string) (string, error) getGlobalGitConfig func(string) (string, error)
getenv func(string) string getenv func(string) string
} }
// NewOSCommand os command runner // NewOSCommand os command runner
func NewOSCommand(log *logrus.Entry) *OSCommand { func NewOSCommand(log *logrus.Entry, config config.AppConfigurer) *OSCommand {
return &OSCommand{ return &OSCommand{
Log: log, Log: log,
Platform: getPlatform(), Platform: getPlatform(),
Config: config,
command: exec.Command, command: exec.Command,
getGlobalGitConfig: gitconfig.Global, getGlobalGitConfig: gitconfig.Global,
getenv: os.Getenv, getenv: os.Getenv,
@ -47,7 +51,6 @@ func (c *OSCommand) RunCommandWithOutput(command string) (string, error) {
c.Log.WithField("command", command).Info("RunCommand") c.Log.WithField("command", command).Info("RunCommand")
splitCmd := str.ToArgv(command) splitCmd := str.ToArgv(command)
c.Log.Info(splitCmd) c.Log.Info(splitCmd)
return sanitisedCommandOutput( return sanitisedCommandOutput(
c.command(splitCmd[0], splitCmd[1:]...).CombinedOutput(), c.command(splitCmd[0], splitCmd[1:]...).CombinedOutput(),
) )
@ -74,12 +77,9 @@ func (c *OSCommand) FileType(path string) string {
// RunDirectCommand wrapper around direct commands // RunDirectCommand wrapper around direct commands
func (c *OSCommand) RunDirectCommand(command string) (string, error) { func (c *OSCommand) RunDirectCommand(command string) (string, error) {
c.Log.WithField("command", command).Info("RunDirectCommand") c.Log.WithField("command", command).Info("RunDirectCommand")
args := str.ToArgv(c.Platform.shellArg + " " + command)
c.Log.Info(spew.Sdump(args))
return sanitisedCommandOutput( return sanitisedCommandOutput(
exec. c.command(c.Platform.shell, c.Platform.shellArg, command).
Command(c.Platform.shell, args...).
CombinedOutput(), CombinedOutput(),
) )
} }
@ -89,51 +89,24 @@ func sanitisedCommandOutput(output []byte, err error) (string, error) {
if err != nil { if err != nil {
// errors like 'exit status 1' are not very useful so we'll create an error // errors like 'exit status 1' are not very useful so we'll create an error
// from the combined output // from the combined output
if outputString == "" {
return "", err
}
return outputString, errors.New(outputString) return outputString, errors.New(outputString)
} }
return outputString, nil return outputString, nil
} }
// getOpenCommand get open command
func (c *OSCommand) getOpenCommand() (string, string, error) {
//NextStep open equivalents: xdg-open (linux), cygstart (cygwin), open (OSX)
trailMap := map[string]string{
"xdg-open": " &>/dev/null &",
"cygstart": "",
"open": "",
}
for name, trail := range trailMap {
if err := c.RunCommand("which " + name); err == nil {
return name, trail, nil
}
}
return "", "", errors.New("Unsure what command to use to open this file")
}
// VsCodeOpenFile opens the file in code, with the -r flag to open in the
// current window
// each of these open files needs to have the same function signature because
// they're being passed as arguments into another function,
// but only editFile actually returns a *exec.Cmd
func (c *OSCommand) VsCodeOpenFile(filename string) (*exec.Cmd, error) {
return nil, c.RunCommand("code -r " + filename)
}
// SublimeOpenFile opens the filein sublime
// may be deprecated in the future
func (c *OSCommand) SublimeOpenFile(filename string) (*exec.Cmd, error) {
return nil, c.RunCommand("subl " + filename)
}
// OpenFile opens a file with the given // OpenFile opens a file with the given
func (c *OSCommand) OpenFile(filename string) error { func (c *OSCommand) OpenFile(filename string) error {
cmdName, cmdTrail, err := c.getOpenCommand() commandTemplate := c.Config.GetUserConfig().GetString("os.openCommand")
if err != nil { templateValues := map[string]string{
return err "filename": c.Quote(filename),
} }
return c.RunCommand(cmdName + " " + c.Quote(filename) + cmdTrail) // TODO: test on linux command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
err := c.RunCommand(command)
return err
} }
// EditFile opens a file in a subprocess using whatever editor is available, // EditFile opens a file in a subprocess using whatever editor is available,

View File

@ -12,5 +12,6 @@ func getPlatform() *Platform {
shell: "bash", shell: "bash",
shellArg: "-c", shellArg: "-c",
escapedQuote: "\"", escapedQuote: "\"",
openCommand: "open {{filename}}",
} }
} }

View File

@ -5,11 +5,28 @@ import (
"os/exec" "os/exec"
"testing" "testing"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
yaml "gopkg.in/yaml.v2"
) )
func newDummyOSCommand() *OSCommand { func newDummyOSCommand() *OSCommand {
return NewOSCommand(newDummyLog()) return NewOSCommand(newDummyLog(), newDummyAppConfig())
}
func newDummyAppConfig() *config.AppConfig {
appConfig := &config.AppConfig{
Name: "lazygit",
Version: "unversioned",
Commit: "",
BuildDate: "",
Debug: false,
BuildSource: "",
UserConfig: viper.New(),
}
_ = yaml.Unmarshal([]byte{}, appConfig.AppState)
return appConfig
} }
func TestOSCommandRunCommandWithOutput(t *testing.T) { func TestOSCommandRunCommandWithOutput(t *testing.T) {
@ -59,44 +76,6 @@ func TestOSCommandRunCommand(t *testing.T) {
} }
} }
func TestOSCommandGetOpenCommand(t *testing.T) {
type scenario struct {
command func(string, ...string) *exec.Cmd
test func(string, string, error)
}
scenarios := []scenario{
{
func(name string, arg ...string) *exec.Cmd {
return exec.Command("exit", "1")
},
func(name string, trail string, err error) {
assert.EqualError(t, err, "Unsure what command to use to open this file")
},
},
{
func(name string, arg ...string) *exec.Cmd {
assert.Equal(t, "which", name)
assert.Len(t, arg, 1)
assert.Regexp(t, "xdg-open|cygstart|open", arg[0])
return exec.Command("echo")
},
func(name string, trail string, err error) {
assert.NoError(t, err)
assert.Regexp(t, "xdg-open|cygstart|open", name)
assert.Regexp(t, " \\&\\>/dev/null \\&|", trail)
},
},
}
for _, s := range scenarios {
OSCmd := newDummyOSCommand()
OSCmd.command = s.command
s.test(OSCmd.getOpenCommand())
}
}
func TestOSCommandOpenFile(t *testing.T) { func TestOSCommandOpenFile(t *testing.T) {
type scenario struct { type scenario struct {
filename string filename string
@ -111,29 +90,25 @@ func TestOSCommandOpenFile(t *testing.T) {
return exec.Command("exit", "1") return exec.Command("exit", "1")
}, },
func(err error) { func(err error) {
assert.EqualError(t, err, "Unsure what command to use to open this file") assert.Error(t, err)
}, },
}, },
{ {
"test", "test",
func(name string, arg ...string) *exec.Cmd { func(name string, arg ...string) *exec.Cmd {
if name == "which" { assert.Equal(t, "open", name)
return exec.Command("echo") assert.Equal(t, []string{"test"}, arg)
} return exec.Command("echo")
},
switch len(arg) { func(err error) {
case 1: assert.NoError(t, err)
assert.Regexp(t, "open|cygstart", name) },
assert.EqualValues(t, "test", arg[0]) },
case 3: {
assert.Equal(t, "xdg-open", name) "filename with spaces",
assert.EqualValues(t, "test", arg[0]) func(name string, arg ...string) *exec.Cmd {
assert.Regexp(t, " \\&\\>/dev/null \\&|", arg[1]) assert.Equal(t, "open", name)
assert.EqualValues(t, "&", arg[2]) assert.Equal(t, []string{"filename with spaces"}, arg)
default:
assert.Fail(t, "Unexisting command given")
}
return exec.Command("echo") return exec.Command("echo")
}, },
func(err error) { func(err error) {
@ -145,6 +120,7 @@ func TestOSCommandOpenFile(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
OSCmd := newDummyOSCommand() OSCmd := newDummyOSCommand()
OSCmd.command = s.command OSCmd.command = s.command
OSCmd.Config.GetUserConfig().Set("os.openCommand", "open {{filename}}")
s.test(OSCmd.OpenFile(s.filename)) s.test(OSCmd.OpenFile(s.filename))
} }

View File

@ -5,6 +5,6 @@ func getPlatform() *Platform {
os: "windows", os: "windows",
shell: "cmd", shell: "cmd",
shellArg: "/c", shellArg: "/c",
escapedQuote: "\\\"", escapedQuote: `\"`,
} }
} }

View File

@ -40,8 +40,7 @@ type AppConfigurer interface {
// NewAppConfig makes a new app config // NewAppConfig makes a new app config
func NewAppConfig(name, version, commit, date string, buildSource string, debuggingFlag *bool) (*AppConfig, error) { func NewAppConfig(name, version, commit, date string, buildSource string, debuggingFlag *bool) (*AppConfig, error) {
defaultConfig := GetDefaultConfig() userConfig, err := LoadConfig("config", true)
userConfig, err := LoadConfig("config", defaultConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -113,13 +112,16 @@ func newViper(filename string) (*viper.Viper, error) {
} }
// LoadConfig gets the user's config // LoadConfig gets the user's config
func LoadConfig(filename string, defaults []byte) (*viper.Viper, error) { func LoadConfig(filename string, withDefaults bool) (*viper.Viper, error) {
v, err := newViper(filename) v, err := newViper(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if defaults != nil { if withDefaults {
if err = LoadDefaults(v, defaults); err != nil { if err = LoadDefaults(v, GetDefaultConfig()); err != nil {
return nil, err
}
if err = LoadDefaults(v, GetPlatformDefaultConfig()); err != nil {
return nil, err return nil, err
} }
} }
@ -131,7 +133,7 @@ func LoadConfig(filename string, defaults []byte) (*viper.Viper, error) {
// LoadDefaults loads in the defaults defined in this file // LoadDefaults loads in the defaults defined in this file
func LoadDefaults(v *viper.Viper, defaults []byte) error { func LoadDefaults(v *viper.Viper, defaults []byte) error {
return v.ReadConfig(bytes.NewBuffer(defaults)) return v.MergeConfig(bytes.NewBuffer(defaults))
} }
func prepareConfigFile(filename string) (string, error) { func prepareConfigFile(filename string) (string, error) {
@ -166,7 +168,7 @@ func LoadAndMergeFile(v *viper.Viper, filename string) error {
func (c *AppConfig) WriteToUserConfig(key, value string) error { func (c *AppConfig) WriteToUserConfig(key, value string) error {
// reloading the user config directly (without defaults) so that we're not // reloading the user config directly (without defaults) so that we're not
// writing any defaults back to the user's config // writing any defaults back to the user's config
v, err := LoadConfig("config", nil) v, err := LoadConfig("config", false)
if err != nil { if err != nil {
return err return err
} }
@ -220,10 +222,6 @@ func GetDefaultConfig() []byte {
- white - white
optionsTextColor: optionsTextColor:
- blue - blue
git:
# stuff relating to git
os:
# stuff relating to the OS
update: update:
method: prompt # can be: prompt | background | never method: prompt # can be: prompt | background | never
days: 14 # how often a update is checked for days: 14 # how often a update is checked for

View File

@ -0,0 +1,10 @@
// +build !windows,!linux
package config
// GetPlatformDefaultConfig gets the defaults for the platform
func GetPlatformDefaultConfig() []byte {
return []byte(
`os:
openCommand: 'open {{filename}}'`)
}

View File

@ -0,0 +1,8 @@
package config
// GetPlatformDefaultConfig gets the defaults for the platform
func GetPlatformDefaultConfig() []byte {
return []byte(
`os:
openCommand: 'bash -c \"xdg-open {{filename}} &>/dev/null &\"'`)
}

View File

@ -0,0 +1,8 @@
package config
// GetPlatformDefaultConfig gets the defaults for the platform
func GetPlatformDefaultConfig() []byte {
return []byte(
`os:
openCommand: 'cmd /c "start "" {{filename}}"'`)
}

View File

@ -155,6 +155,19 @@ func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error {
if gui.getItemPosition(v) != 0 {
return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit"))
}
gui.SubProcess = gui.GitCommand.PrepareCommitAmendSubProcess()
g.Update(func(g *gocui.Gui) error {
return gui.Errors.ErrSubProcess
})
return nil
}
func (gui *Gui) getSelectedCommit(g *gocui.Gui) (commands.Commit, error) { func (gui *Gui) getSelectedCommit(g *gocui.Gui) (commands.Commit, error) {
v, err := g.View("commits") v, err := g.View("commits")
if err != nil { if err != nil {

View File

@ -7,7 +7,6 @@ import (
// "strings" // "strings"
"os/exec"
"strings" "strings"
"github.com/fatih/color" "github.com/fatih/color"
@ -250,11 +249,10 @@ func (gui *Gui) PrepareSubProcess(g *gocui.Gui, commands ...string) {
}) })
} }
func (gui *Gui) genericFileOpen(g *gocui.Gui, v *gocui.View, filename string, open func(string) (*exec.Cmd, error)) error { func (gui *Gui) editFile(filename string) error {
sub, err := gui.OSCommand.EditFile(filename)
sub, err := open(filename)
if err != nil { if err != nil {
return gui.createErrorPanel(g, err.Error()) return gui.createErrorPanel(gui.g, err.Error())
} }
if sub != nil { if sub != nil {
gui.SubProcess = sub gui.SubProcess = sub
@ -268,7 +266,8 @@ func (gui *Gui) handleFileEdit(g *gocui.Gui, v *gocui.View) error {
if err != nil { if err != nil {
return err return err
} }
return gui.genericFileOpen(g, v, file.Name, gui.OSCommand.EditFile)
return gui.editFile(file.Name)
} }
func (gui *Gui) handleFileOpen(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleFileOpen(g *gocui.Gui, v *gocui.View) error {
@ -279,22 +278,6 @@ func (gui *Gui) handleFileOpen(g *gocui.Gui, v *gocui.View) error {
return gui.openFile(file.Name) return gui.openFile(file.Name)
} }
func (gui *Gui) handleSublimeFileOpen(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
return err
}
return gui.genericFileOpen(g, v, file.Name, gui.OSCommand.SublimeOpenFile)
}
func (gui *Gui) handleVsCodeFileOpen(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
return err
}
return gui.genericFileOpen(g, v, file.Name, gui.OSCommand.VsCodeOpenFile)
}
func (gui *Gui) handleRefreshFiles(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleRefreshFiles(g *gocui.Gui, v *gocui.View) error {
return gui.refreshFiles(g) return gui.refreshFiles(g)
} }

View File

@ -34,8 +34,6 @@ func (gui *Gui) keybindings(g *gocui.Gui) error {
{ViewName: "files", Key: 'm', Modifier: gocui.ModNone, Handler: gui.handleSwitchToMerge}, {ViewName: "files", Key: 'm', Modifier: gocui.ModNone, Handler: gui.handleSwitchToMerge},
{ViewName: "files", Key: 'e', Modifier: gocui.ModNone, Handler: gui.handleFileEdit}, {ViewName: "files", Key: 'e', Modifier: gocui.ModNone, Handler: gui.handleFileEdit},
{ViewName: "files", Key: 'o', Modifier: gocui.ModNone, Handler: gui.handleFileOpen}, {ViewName: "files", Key: 'o', Modifier: gocui.ModNone, Handler: gui.handleFileOpen},
{ViewName: "files", Key: 's', Modifier: gocui.ModNone, Handler: gui.handleSublimeFileOpen},
{ViewName: "files", Key: 'v', Modifier: gocui.ModNone, Handler: gui.handleVsCodeFileOpen},
{ViewName: "files", Key: 'i', Modifier: gocui.ModNone, Handler: gui.handleIgnoreFile}, {ViewName: "files", Key: 'i', Modifier: gocui.ModNone, Handler: gui.handleIgnoreFile},
{ViewName: "files", Key: 'r', Modifier: gocui.ModNone, Handler: gui.handleRefreshFiles}, {ViewName: "files", Key: 'r', Modifier: gocui.ModNone, Handler: gui.handleRefreshFiles},
{ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: gui.handleStashSave}, {ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: gui.handleStashSave},
@ -64,6 +62,7 @@ func (gui *Gui) keybindings(g *gocui.Gui) error {
{ViewName: "branches", Key: 'm', Modifier: gocui.ModNone, Handler: gui.handleMerge}, {ViewName: "branches", Key: 'm', Modifier: gocui.ModNone, Handler: gui.handleMerge},
{ViewName: "commits", Key: 's', Modifier: gocui.ModNone, Handler: gui.handleCommitSquashDown}, {ViewName: "commits", Key: 's', Modifier: gocui.ModNone, Handler: gui.handleCommitSquashDown},
{ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: gui.handleRenameCommit}, {ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: gui.handleRenameCommit},
{ViewName: "commits", Key: 'R', Modifier: gocui.ModNone, Handler: gui.handleRenameCommitEditor},
{ViewName: "commits", Key: 'g', Modifier: gocui.ModNone, Handler: gui.handleResetToCommit}, {ViewName: "commits", Key: 'g', Modifier: gocui.ModNone, Handler: gui.handleResetToCommit},
{ViewName: "commits", Key: 'f', Modifier: gocui.ModNone, Handler: gui.handleCommitFixup}, {ViewName: "commits", Key: 'f', Modifier: gocui.ModNone, Handler: gui.handleCommitFixup},
{ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: gui.handleStashApply}, {ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: gui.handleStashApply},

View File

@ -76,7 +76,7 @@ func (gui *Gui) handleOpenConfig(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) handleEditConfig(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleEditConfig(g *gocui.Gui, v *gocui.View) error {
filename := gui.Config.GetUserConfig().ConfigFileUsed() filename := gui.Config.GetUserConfig().ConfigFileUsed()
return gui.genericFileOpen(g, v, filename, gui.OSCommand.EditFile) return gui.editFile(filename)
} }
func lazygitTitle() string { func lazygitTitle() string {

View File

@ -91,3 +91,11 @@ func Loader() string {
index := nanos / 50000000 % int64(len(characters)) index := nanos / 50000000 % int64(len(characters))
return characters[index : index+1] return characters[index : index+1]
} }
// ResolvePlaceholderString populates a template with values
func ResolvePlaceholderString(str string, arguments map[string]string) string {
for key, value := range arguments {
str = strings.Replace(str, "{{"+key+"}}", value, -1)
}
return str
}

View File

@ -114,3 +114,56 @@ func TestNormalizeLinefeeds(t *testing.T) {
assert.EqualValues(t, string(s.expected), NormalizeLinefeeds(string(s.byteArray))) assert.EqualValues(t, string(s.expected), NormalizeLinefeeds(string(s.byteArray)))
} }
} }
func TestResolvePlaceholderString(t *testing.T) {
type scenario struct {
templateString string
arguments map[string]string
expected string
}
scenarios := []scenario{
{
"",
map[string]string{},
"",
},
{
"hello",
map[string]string{},
"hello",
},
{
"hello {{arg}}",
map[string]string{},
"hello {{arg}}",
},
{
"hello {{arg}}",
map[string]string{"arg": "there"},
"hello there",
},
{
"hello",
map[string]string{"arg": "there"},
"hello",
},
{
"{{nothing}}",
map[string]string{"nothing": ""},
"",
},
{
"{{}} {{ this }} { should not throw}} an {{{{}}}} error",
map[string]string{
"blah": "blah",
"this": "won't match",
},
"{{}} {{ this }} { should not throw}} an {{{{}}}} error",
},
}
for _, s := range scenarios {
assert.EqualValues(t, string(s.expected), ResolvePlaceholderString(s.templateString, s.arguments))
}
}