diff options
-rw-r--r-- | pkg/gui/mergeconflicts/find_conflicts.go | 63 | ||||
-rw-r--r-- | pkg/gui/mergeconflicts/find_conflicts_test.go | 57 | ||||
-rw-r--r-- | pkg/gui/mergeconflicts/state.go | 68 | ||||
-rw-r--r-- | pkg/utils/color.go | 49 | ||||
-rw-r--r-- | pkg/utils/formatting.go | 39 | ||||
-rw-r--r-- | pkg/utils/formatting_test.go | 81 | ||||
-rw-r--r-- | pkg/utils/io.go | 25 | ||||
-rw-r--r-- | pkg/utils/lines.go | 34 | ||||
-rw-r--r-- | pkg/utils/lines_test.go | 94 | ||||
-rw-r--r-- | pkg/utils/slice.go | 118 | ||||
-rw-r--r-- | pkg/utils/slice_test.go | 135 | ||||
-rw-r--r-- | pkg/utils/template.go | 30 | ||||
-rw-r--r-- | pkg/utils/template_test.go | 61 | ||||
-rw-r--r-- | pkg/utils/utils.go | 262 | ||||
-rw-r--r-- | pkg/utils/utils_test.go | 366 |
15 files changed, 802 insertions, 680 deletions
diff --git a/pkg/gui/mergeconflicts/find_conflicts.go b/pkg/gui/mergeconflicts/find_conflicts.go new file mode 100644 index 000000000..661cc5cc0 --- /dev/null +++ b/pkg/gui/mergeconflicts/find_conflicts.go @@ -0,0 +1,63 @@ +package mergeconflicts + +import ( + "regexp" + "strings" + + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// LineType tells us whether a given line is a start/middle/end marker of a conflict, +// or if it's not a marker at all +type LineType int + +const ( + START LineType = iota + MIDDLE + END + NOT_A_MARKER +) + +func findConflicts(content string) []*mergeConflict { + conflicts := make([]*mergeConflict, 0) + + if content == "" { + return conflicts + } + + var newConflict *mergeConflict + for i, line := range utils.SplitLines(content) { + switch determineLineType(line) { + case START: + newConflict = &mergeConflict{start: i} + case MIDDLE: + newConflict.middle = i + case END: + newConflict.end = i + conflicts = append(conflicts, newConflict) + default: + // line isn't a merge conflict marker so we just continue + } + } + + return conflicts +} + +func determineLineType(line string) LineType { + trimmedLine := strings.TrimPrefix(line, "++") + + mapping := map[string]LineType{ + "^<<<<<<< (HEAD|MERGE_HEAD|Updated upstream|ours)(:.+)?$": START, + "^=======$": MIDDLE, + "^>>>>>>> .*$": END, + } + + for regexp_str, lineType := range mapping { + match, _ := regexp.MatchString(regexp_str, trimmedLine) + if match { + return lineType + } + } + + return NOT_A_MARKER +} diff --git a/pkg/gui/mergeconflicts/find_conflicts_test.go b/pkg/gui/mergeconflicts/find_conflicts_test.go new file mode 100644 index 000000000..c0a8e3e5b --- /dev/null +++ b/pkg/gui/mergeconflicts/find_conflicts_test.go @@ -0,0 +1,57 @@ +package mergeconflicts + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDetermineLineType(t *testing.T) { + type scenario struct { + line string + expected LineType + } + + scenarios := []scenario{ + { + line: "", + expected: NOT_A_MARKER, + }, + { + line: "blah", + expected: NOT_A_MARKER, + }, + { + line: "<<<<<<< HEAD", + expected: START, + }, + { + line: "<<<<<<< HEAD:my_branch", + expected: START, + }, + { + line: "<<<<<<< MERGE_HEAD:my_branch", + expected: START, + }, + { + line: "<<<<<<< Updated upstream:my_branch", + expected: START, + }, + { + line: "<<<<<<< ours:my_branch", + expected: START, + }, + { + line: "=======", + expected: MIDDLE, + }, + { + line: ">>>>>>> blah", + expected: END, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, determineLineType(s.line)) + } +} diff --git a/pkg/gui/mergeconflicts/state.go b/pkg/gui/mergeconflicts/state.go index 383d71a36..9d8320ffb 100644 --- a/pkg/gui/mergeconflicts/state.go +++ b/pkg/gui/mergeconflicts/state.go @@ -1,9 +1,6 @@ package mergeconflicts import ( - "bufio" - "os" - "strings" "sync" "github.com/golang-collections/collections/stack" @@ -19,7 +16,7 @@ const ( ) // mergeConflict : A git conflict with a start middle and end corresponding to line -// numbers in the file where the conflict bars appear +// numbers in the file where the conflict markers appear type mergeConflict struct { start int middle int @@ -88,36 +85,6 @@ func (s *State) SetConflictsFromCat(cat string) { s.setConflicts(findConflicts(cat)) } -func findConflicts(content string) []*mergeConflict { - conflicts := make([]*mergeConflict, 0) - - if content == "" { - return conflicts - } - - var newConflict *mergeConflict - for i, line := range utils.SplitLines(content) { - trimmedLine := strings.TrimPrefix(line, "++") - switch trimmedLine { - case "<<<<<<< HEAD", "<<<<<<< MERGE_HEAD", "<<<<<<< Updated upstream", "<<<<<<< ours": - newConflict = &mergeConflict{start: i} - case "=======": - newConflict.middle = i - default: - // Sometimes these lines look like "<<<<<<< HEAD:foo/bar/baz.go" so handle that case as well. - if strings.HasPrefix(trimmedLine, "<<<<<<< HEAD:") { - newConflict = &mergeConflict{start: i} - } - if strings.HasPrefix(trimmedLine, ">>>>>>> ") { - newConflict.end = i - conflicts = append(conflicts, newConflict) - } - } - - } - return conflicts -} - func (s *State) setConflicts(conflicts []*mergeConflict) { s.conflicts = conflicts @@ -158,32 +125,29 @@ func (s *State) ContentAfterConflictResolve(path string, selection Selection) (b return false, "", nil } - file, err := os.Open(path) - if err != nil { - return false, "", err - } - defer file.Close() - - reader := bufio.NewReader(file) content := "" - for i := 0; true; i++ { - line, err := reader.ReadString('\n') - if err != nil { - break - } + err := utils.ForEachLineInFile(path, func(line string, i int) { if !isIndexToDelete(i, conflict, selection) { content += line } + }) + + if err != nil { + return false, "", err } return true, content, nil } func isIndexToDelete(i int, conflict *mergeConflict, selection Selection) bool { - return i == conflict.middle || - i == conflict.start || - i == conflict.end || - selection != BOTH && - (selection == BOTTOM && i > conflict.start && i < conflict.middle) || - (selection == TOP && i > conflict.middle && i < conflict.end) + isMarkerLine := + i == conflict.middle || + i == conflict.start || + i == conflict.end + + isUnwantedContent := + (selection == BOTTOM && conflict.start < i && i < conflict.middle) || + (selection == TOP && conflict.middle < i && i < conflict.end) + + return isMarkerLine || isUnwantedContent } diff --git a/pkg/utils/color.go b/pkg/utils/color.go new file mode 100644 index 000000000..b38dccf7f --- /dev/null +++ b/pkg/utils/color.go @@ -0,0 +1,49 @@ +package utils + +import ( + "fmt" + "regexp" + + "github.com/fatih/color" +) + +// ColoredString takes a string and a colour attribute and returns a colored +// string with that attribute +func ColoredString(str string, colorAttributes ...color.Attribute) string { + colour := color.New(colorAttributes...) + return ColoredStringDirect(str, colour) +} + +// ColoredStringDirect used for aggregating a few color attributes rather than +// just sending a single one +func ColoredStringDirect(str string, colour *color.Color) string { + return colour.SprintFunc()(fmt.Sprint(str)) +} + +// Decolorise strips a string of color +func Decolorise(str string) string { + re := regexp.MustCompile(`\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]`) + return re.ReplaceAllString(str, "") +} + +func getPadWidths(stringArrays [][]string) []int { + maxWidth := 0 + for _, stringArray := range stringArrays { + if len(stringArray) > maxWidth { + maxWidth = len(stringArray) + } + } + if maxWidth-1 < 0 { + return []int{} + } + padWidths := make([]int, maxWidth-1) + for i := range padWidths { + for _, strings := range stringArrays { + uncoloredString := Decolorise(strings[i]) + if len(uncoloredString) > padWidths[i] { + padWidths[i] = len(uncoloredString) + } + } + } + return padWidths +} diff --git a/pkg/utils/formatting.go b/pkg/utils/formatting.go new file mode 100644 index 000000000..3b4b35bb8 --- /dev/null +++ b/pkg/utils/formatting.go @@ -0,0 +1,39 @@ +package utils + +import "strings" + +// WithPadding pads a string as much as you want +func WithPadding(str string, padding int) string { + uncoloredStr := Decolorise(str) + if padding < len(uncoloredStr) { + return str + } + return str + strings.Repeat(" ", padding-len(uncoloredStr)) +} + +func RenderDisplayStrings(displayStringsArr [][]string) string { + padWidths := getPadWidths(displayStringsArr) + paddedDisplayStrings := getPaddedDisplayStrings(displayStringsArr, padWidths) + + return strings.Join(paddedDisplayStrings, "\n") +} + +func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) []string { + paddedDisplayStrings := make([]string, len(stringArrays)) + for i, stringArray := range stringArrays { + if len(stringArray) == 0 { + continue + } + for j, padWidth := range padWidths { + if len(stringArray)-1 < j { + continue + } + paddedDisplayStrings[i] += WithPadding(stringArray[j], padWidth) + " " + } + if len(stringArray)-1 < len(padWidths) { + continue + } + paddedDisplayStrings[i] += stringArray[len(padWidths)] + } + return paddedDisplayStrings +} diff --git a/pkg/utils/formatting_test.go b/pkg/utils/formatting_test.go new file mode 100644 index 000000000..3fe3f22f0 --- /dev/null +++ b/pkg/utils/formatting_test.go @@ -0,0 +1,81 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestWithPadding is a function. +func TestWithPadding(t *testing.T) { + type scenario struct { + str string + padding int + expected string + } + + scenarios := []scenario{ + { + "hello world !", + 1, + "hello world !", + }, + { + "hello world !", + 14, + "hello world ! ", + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, WithPadding(s.str, s.padding)) + } +} + +// TestGetPaddedDisplayStrings is a function. +func TestGetPaddedDisplayStrings(t *testing.T) { + type scenario struct { + stringArrays [][]string + padWidths []int + expected []string + } + + scenarios := []scenario{ + { + [][]string{{"a", "b"}, {"c", "d"}}, + []int{1}, + []string{"a b", "c d"}, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, getPaddedDisplayStrings(s.stringArrays, s.padWidths)) + } +} + +// TestGetPadWidths is a function. +func TestGetPadWidths(t *testing.T) { + type scenario struct { + stringArrays [][]string + expected []int + } + + scenarios := []scenario{ + { + [][]string{{""}, {""}}, + []int{}, + }, + { + [][]string{{"a"}, {""}}, + []int{}, + }, + { + [][]string{{"aa", "b", "ccc"}, {"c", "d", "e"}}, + []int{2, 1}, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, getPadWidths(s.stringArrays)) + } +} diff --git a/pkg/utils/io.go b/pkg/utils/io.go new file mode 100644 index 000000000..d31c5fc07 --- /dev/null +++ b/pkg/utils/io.go @@ -0,0 +1,25 @@ +package utils + +import ( + "bufio" + "os" +) + +func ForEachLineInFile(path string, f func(string, int)) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + reader := bufio.NewReader(file) + for i := 0; true; i++ { + line, err := reader.ReadString('\n') + if err != nil { + break + } + f(line, i) + } + + return nil +} diff --git a/pkg/utils/lines.go b/pkg/utils/lines.go new file mode 100644 index 000000000..4c654d888 --- /dev/null +++ b/pkg/utils/lines.go @@ -0,0 +1,34 @@ +package utils + +import "strings" + +// SplitLines takes a multiline string and splits it on newlines +// currently we are also stripping \r's which may have adverse effects for +// windows users (but no issues have been raised yet) +func SplitLines(multilineString string) []string { + multilineString = strings.Replace(multilineString, "\r", "", -1) + if multilineString == "" || multilineString == "\n" { + return make([]string, 0) + } + lines := strings.Split(multilineString, "\n") + if lines[len(lines)-1] == "" { + return lines[:len(lines)-1] + } + return lines +} + +// TrimTrailingNewline - Trims the trailing newline +// TODO: replace with `chomp` after refactor +func TrimTrailingNewline(str string) string { + if strings.HasSuffix(str, "\n") { + return str[:len(str)-1] + } + return str +} + +// NormalizeLinefeeds - Removes all Windows and Mac style line feeds +func NormalizeLinefeeds(str string) string { + str = strings.Replace(str, "\r\n", "\n", -1) + str = strings.Replace(str, "\r", "", -1) + return str +} diff --git a/pkg/utils/lines_test.go b/pkg/utils/lines_test.go new file mode 100644 index 000000000..6069b8f93 --- /dev/null +++ b/pkg/utils/lines_test.go @@ -0,0 +1,94 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestSplitLines is a function. +func TestSplitLines(t *testing.T) { + type scenario struct { + multilineString string + expected []string + } + + scenarios := []scenario{ + { + "", + []string{}, + }, + { + "\n", + []string{}, + }, + { + "hello world !\nhello universe !\n", + []string{ + "hello world !", + "hello universe !", + }, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, SplitLines(s.multilineString)) + } +} + +// TestTrimTrailingNewline is a function. +func TestTrimTrailingNewline(t *testing.T) { + type scenario struct { + str string + expected string + } + + scenarios := []scenario{ + { + "hello world !\n", + "hello world !", + }, + { + "hello world !", + "hello world !", + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, TrimTrailingNewline(s.str)) + } +} + +// TestNormalizeLinefeeds is a function. +func TestNormalizeLinefeeds(t *testing.T) { + type scenario struct { + byteArray []byte + expected []byte + } + var scenarios = []scenario{ + { + // \r\n + []byte{97, 115, 100, 102, 13, 10}, + []byte{97, 115, 100, 102, 10}, + }, + { + // bash\r\nblah + []byte{97, 115, 100, 102, 13, 10, 97, 115, 100, 102}, + []byte{97, 115, 100, 102, 10, 97, 115, 100, 102}, + }, + { + // \r + []byte{97, 115, 100, 102, 13}, + []byte{97, 115, 100, 102}, + }, + { + // \n + []byte{97, 115, 100, 102, 10}, + []byte{97, 115, 100, 102, 10}, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, string(s.expected), NormalizeLinefeeds(string(s.byteArray))) + } +} diff --git a/pkg/utils/slice.go b/pkg/utils/slice.go new file mode 100644 index 000000000..f536ea056 --- /dev/null +++ b/pkg/utils/slice.go @@ -0,0 +1,118 @@ +package utils + +// IncludesString if the list contains the string +func IncludesString(list []string, a string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +// IncludesInt if the list contains the Int +func IncludesInt(list []int, a int) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +// NextIndex returns the index of the element that comes after the given number +func NextIndex(numbers []int, currentNumber int) int { + for index, number := range numbers { + if number > currentNumber { + return index + } + } + return len(numbers) - 1 +} + +// PrevIndex returns the index that comes before the given number, cycling if we reach the end +func PrevIndex(numbers []int, currentNumber int) int { + end := len(numbers) - 1 + for i := end; i >= 0; i-- { + if numbers[i] < currentNumber { + return i + } + } + return 0 +} + +// UnionInt returns the union of two int arrays +func UnionInt(a, b []int) []int { + m := make(map[int]bool) + + for _, item := range a { + m[item] = true + } + + for _, item := range b { + if _, ok := m[item]; !ok { + // this does not mutate the original a slice + // though it does mutate the backing array I believe + // but that doesn't matter because if you later want to append to the + // original a it must see that the backing array has been changed + // and create a new one + a = append(a, item) + } + } + return a +} + +// DifferenceInt returns the difference of two int arrays +func DifferenceInt(a, b []int) []int { + result := []int{} + m := make(map[int]bool) + + for _, item := range b { + m[item] = true + } + + for _, item := range a { + if _, ok := m[item]; !ok { + result = append(result, item) + } + } + return result +} + +// NextIntInCycle returns the next int in a slice, returning to the first index if we've reached the end +func NextIntInCycle(sl []int, current int) int { + for i, val := range sl { + if val == current { + if i == len(sl)-1 { + return sl[0] + } + return sl[i+1] + } + } + return sl[0] +} + +// PrevIntInCycle returns the prev int in a slice, returning to the first index if we've reached the end +func PrevIntInCycle(sl []int, current int) int { + for i, val := range sl { + if val == current { + if i > 0 { + return sl[i-1] + } + return sl[len(sl)-1] + } + } + return sl[len(sl)-1] +} + +func StringArraysOverlap(strArrA []string, strArrB []string) bool { + for _, first := range strArrA { + for _, second := range strArrB { + if first == second { + return true + } + } + } + + return false +} diff --git a/pkg/utils/slice_test.go b/pkg/utils/slice_test.go new file mode 100644 index 000000000..858b1b904 --- /dev/null +++ b/pkg/utils/slice_test.go @@ -0,0 +1,135 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestIncludesString is a function. +func TestIncludesString(t *testing.T) { + type scenario struct { + list []string + element string + expected bool + } + + scenarios := []scenario{ + { + []string{"a", "b"}, + "a", + true, + }, + { + []string{"a", "b"}, + "c", + false, + }, + { + []string{"a", "b"}, + "", + false, + }, + { + []string{""}, + "", + true, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, IncludesString(s.list, s.element)) + } +} + +func TestNextIndex(t *testing.T) { + type scenario struct { + testName string + list []int + element int + expected int + } + + scenarios := []scenario{ + { + // I'm not really fussed about how it behaves here + "no elements", + []int{}, + 1, + -1, + }, + { + "one element", + []int{1}, + 1, + 0, + }, + { + "two elements", + []int{1, 2}, + 1, + 1, + }, + { + "two elements, giving second one", + []int{1, 2}, + 2, + 1, + }, + { + "three elements, giving second one", + []int{1, 2, 3}, + 2, + 2, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + assert.EqualValues(t, s.expected, NextIndex(s.list, s.element)) + }) + } +} + +func TestPrevIndex(t *testing.T) { + type scenario struct { + testName string + list []int + element int + expected int + } + + scenarios := []scenario{ + { + // I'm not really fussed about how it behaves here + "no elements", + []int{}, + 1, + 0, + }, + { + "one element", + []int{1}, + 1, + 0, + }, + { + "two elements", + []int{1, 2}, + 1, + 0, + }, + { + "three elements, giving second one", + []int{1, 2, 3}, + 2, + 0, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + assert.EqualValues(t, s.expected, PrevIndex(s.list, s.element)) + }) + } +} diff --git a/pkg/utils/template.go b/pkg/utils/template.go new file mode 100644 index 000000000..6c147cd38 --- /dev/null +++ b/pkg/utils/template.go @@ -0,0 +1,30 @@ +package utils + +import ( + "bytes" + "strings" + "text/template" +) + +func ResolveTemplate(templateStr string, object interface{}) (string, error) { + tmpl, err := template.New("template").Parse(templateStr) + if err != nil { + return "", err + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, object); err != nil { + return "", err + } + + return buf.String(), nil +} + +// 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) + str = strings.Replace(str, "{{."+key+"}}", value, -1) + } + return str +} diff --git a/pkg/utils/template_test.go b/pkg/utils/template_test.go new file mode 100644 index 000000000..f294d115d --- /dev/null +++ b/pkg/utils/template_test.go @@ -0,0 +1,61 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestResolvePlaceholderString is a function. +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, s.expected, ResolvePlaceholderString(s.templateString, s.arguments)) + } +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 69fe711ac..4ec1c624c 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -1,7 +1,6 @@ package utils import ( - "bytes" "encoding/json" "fmt" "log" @@ -11,50 +10,11 @@ import ( "runtime" "strconv" "strings" - "text/template" "time" - "github.com/fatih/color" "github.com/jesseduffield/gocui" ) -// SplitLines takes a multiline string and splits it on newlines -// currently we are also stripping \r's which may have adverse effects for -// windows users (but no issues have been raised yet) -func SplitLines(multilineString string) []string { - multilineString = strings.Replace(multilineString, "\r", "", -1) - if multilineString == "" || multilineString == "\n" { - return make([]string, 0) - } - lines := strings.Split(multilineString, "\n") - if lines[len(lines)-1] == "" { - return lines[:len(lines)-1] - } - return lines -} - -// WithPadding pads a string as much as you want -func WithPadding(str string, padding int) string { - uncoloredStr := Decolorise(str) - if padding < len(uncoloredStr) { - return str - } - return str + strings.Repeat(" ", padding-len(uncoloredStr)) -} - -// ColoredString takes a string and a colour attribute and returns a colored -// string with that attribute -func ColoredString(str string, colorAttributes ...color.Attribute) string { - colour := color.New(colorAttributes...) - return ColoredStringDirect(str, colour) -} - -// ColoredStringDirect used for aggregating a few color attributes rather than -// just sending a single one -func ColoredStringDirect(str string, colour *color.Color) string { - return colour.SprintFunc()(fmt.Sprint(str)) -} - // GetCurrentRepoName gets the repo's base name func GetCurrentRepoName() string { pwd, err := os.Getwd() @@ -64,22 +24,6 @@ func GetCurrentRepoName() string { return filepath.Base(pwd) } -// TrimTrailingNewline - Trims the trailing newline -// TODO: replace with `chomp` after refactor -func TrimTrailingNewline(str string) string { - if strings.HasSuffix(str, "\n") { - return str[:len(str)-1] - } - return str -} - -// NormalizeLinefeeds - Removes all Windows and Mac style line feeds -func NormalizeLinefeeds(str string) string { - str = strings.Replace(str, "\r\n", "\n", -1) - str = strings.Replace(str, "\r", "", -1) - return str -} - // GetProjectRoot returns the path to the root of the project. Only to be used // in testing contexts, as with binaries it's unlikely this path will exist on // the machine @@ -100,15 +44,6 @@ func Loader() string { 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 |