mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-08 23:56:15 +02:00
refactor
This commit is contained in:
parent
bc044c64b2
commit
258eedb38c
63
pkg/gui/mergeconflicts/find_conflicts.go
Normal file
63
pkg/gui/mergeconflicts/find_conflicts.go
Normal file
@ -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
|
||||||
|
}
|
57
pkg/gui/mergeconflicts/find_conflicts_test.go
Normal file
57
pkg/gui/mergeconflicts/find_conflicts_test.go
Normal file
@ -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))
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,6 @@
|
|||||||
package mergeconflicts
|
package mergeconflicts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/golang-collections/collections/stack"
|
"github.com/golang-collections/collections/stack"
|
||||||
@ -19,7 +16,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// mergeConflict : A git conflict with a start middle and end corresponding to line
|
// 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 {
|
type mergeConflict struct {
|
||||||
start int
|
start int
|
||||||
middle int
|
middle int
|
||||||
@ -88,36 +85,6 @@ func (s *State) SetConflictsFromCat(cat string) {
|
|||||||
s.setConflicts(findConflicts(cat))
|
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) {
|
func (s *State) setConflicts(conflicts []*mergeConflict) {
|
||||||
s.conflicts = conflicts
|
s.conflicts = conflicts
|
||||||
|
|
||||||
@ -158,32 +125,29 @@ func (s *State) ContentAfterConflictResolve(path string, selection Selection) (b
|
|||||||
return false, "", nil
|
return false, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
reader := bufio.NewReader(file)
|
|
||||||
content := ""
|
content := ""
|
||||||
for i := 0; true; i++ {
|
err := utils.ForEachLineInFile(path, func(line string, i int) {
|
||||||
line, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !isIndexToDelete(i, conflict, selection) {
|
if !isIndexToDelete(i, conflict, selection) {
|
||||||
content += line
|
content += line
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, content, nil
|
return true, content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isIndexToDelete(i int, conflict *mergeConflict, selection Selection) bool {
|
func isIndexToDelete(i int, conflict *mergeConflict, selection Selection) bool {
|
||||||
return i == conflict.middle ||
|
isMarkerLine :=
|
||||||
|
i == conflict.middle ||
|
||||||
i == conflict.start ||
|
i == conflict.start ||
|
||||||
i == conflict.end ||
|
i == conflict.end
|
||||||
selection != BOTH &&
|
|
||||||
(selection == BOTTOM && i > conflict.start && i < conflict.middle) ||
|
isUnwantedContent :=
|
||||||
(selection == TOP && i > conflict.middle && i < conflict.end)
|
(selection == BOTTOM && conflict.start < i && i < conflict.middle) ||
|
||||||
|
(selection == TOP && conflict.middle < i && i < conflict.end)
|
||||||
|
|
||||||
|
return isMarkerLine || isUnwantedContent
|
||||||
}
|
}
|
||||||
|
49
pkg/utils/color.go
Normal file
49
pkg/utils/color.go
Normal file
@ -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
|
||||||
|
}
|
39
pkg/utils/formatting.go
Normal file
39
pkg/utils/formatting.go
Normal file
@ -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
|
||||||
|
}
|
81
pkg/utils/formatting_test.go
Normal file
81
pkg/utils/formatting_test.go
Normal file
@ -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))
|
||||||
|
}
|
||||||
|
}
|
25
pkg/utils/io.go
Normal file
25
pkg/utils/io.go
Normal file
@ -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
|
||||||
|
}
|
34
pkg/utils/lines.go
Normal file
34
pkg/utils/lines.go
Normal file
@ -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
|
||||||
|
}
|
94
pkg/utils/lines_test.go
Normal file
94
pkg/utils/lines_test.go
Normal file
@ -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)))
|
||||||
|
}
|
||||||
|
}
|
118
pkg/utils/slice.go
Normal file
118
pkg/utils/slice.go
Normal file
@ -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
|
||||||
|
}
|
135
pkg/utils/slice_test.go
Normal file
135
pkg/utils/slice_test.go
Normal file
@ -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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
30
pkg/utils/template.go
Normal file
30
pkg/utils/template.go
Normal file
@ -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
|
||||||
|
}
|
61
pkg/utils/template_test.go
Normal file
61
pkg/utils/template_test.go
Normal file
@ -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))
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@ -11,50 +10,11 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/jesseduffield/gocui"
|
"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
|
// GetCurrentRepoName gets the repo's base name
|
||||||
func GetCurrentRepoName() string {
|
func GetCurrentRepoName() string {
|
||||||
pwd, err := os.Getwd()
|
pwd, err := os.Getwd()
|
||||||
@ -64,22 +24,6 @@ func GetCurrentRepoName() string {
|
|||||||
return filepath.Base(pwd)
|
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
|
// 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
|
// in testing contexts, as with binaries it's unlikely this path will exist on
|
||||||
// the machine
|
// the machine
|
||||||
@ -100,15 +44,6 @@ func Loader() string {
|
|||||||
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)
|
|
||||||
str = strings.Replace(str, "{{."+key+"}}", value, -1)
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
// Min returns the minimum of two integers
|
// Min returns the minimum of two integers
|
||||||
func Min(x, y int) int {
|
func Min(x, y int) int {
|
||||||
if x < y {
|
if x < y {
|
||||||
@ -117,156 +52,11 @@ func Min(x, y int) int {
|
|||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
|
|
||||||
func RenderDisplayStrings(displayStringsArr [][]string) string {
|
|
||||||
padWidths := getPadWidths(displayStringsArr)
|
|
||||||
paddedDisplayStrings := getPaddedDisplayStrings(displayStringsArr, padWidths)
|
|
||||||
|
|
||||||
return strings.Join(paddedDisplayStrings, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// displayArraysAligned returns true if every string array returned from our
|
|
||||||
// list of displayables has the same length
|
|
||||||
func displayArraysAligned(stringArrays [][]string) bool {
|
|
||||||
for _, strings := range stringArrays {
|
|
||||||
if len(strings) != len(stringArrays[0]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func AsJson(i interface{}) string {
|
func AsJson(i interface{}) string {
|
||||||
bytes, _ := json.MarshalIndent(i, "", " ")
|
bytes, _ := json.MarshalIndent(i, "", " ")
|
||||||
return string(bytes)
|
return string(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// used to keep a number n between 0 and max, allowing for wraparounds
|
// used to keep a number n between 0 and max, allowing for wraparounds
|
||||||
func ModuloWithWrap(n, max int) int {
|
func ModuloWithWrap(n, max int) int {
|
||||||
if n >= max {
|
if n >= max {
|
||||||
@ -278,32 +68,6 @@ func ModuloWithWrap(n, max int) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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]
|
|
||||||
}
|
|
||||||
|
|
||||||
// TruncateWithEllipsis returns a string, truncated to a certain length, with an ellipsis
|
// TruncateWithEllipsis returns a string, truncated to a certain length, with an ellipsis
|
||||||
func TruncateWithEllipsis(str string, limit int) string {
|
func TruncateWithEllipsis(str string, limit int) string {
|
||||||
if limit == 1 && len(str) > 1 {
|
if limit == 1 && len(str) > 1 {
|
||||||
@ -329,18 +93,6 @@ func FindStringSubmatch(str string, regexpStr string) (bool, []string) {
|
|||||||
return len(match) > 0, match
|
return len(match) > 0, match
|
||||||
}
|
}
|
||||||
|
|
||||||
func StringArraysOverlap(strArrA []string, strArrB []string) bool {
|
|
||||||
for _, first := range strArrA {
|
|
||||||
for _, second := range strArrB {
|
|
||||||
if first == second {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustConvertToInt(s string) int {
|
func MustConvertToInt(s string) int {
|
||||||
i, err := strconv.Atoi(s)
|
i, err := strconv.Atoi(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -349,20 +101,6 @@ func MustConvertToInt(s string) int {
|
|||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safe will close tcell if a panic occurs so that we don't end up in a malformed
|
// Safe will close tcell if a panic occurs so that we don't end up in a malformed
|
||||||
// terminal state
|
// terminal state
|
||||||
func Safe(f func()) {
|
func Safe(f func()) {
|
||||||
|
@ -6,244 +6,6 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestDisplayArraysAligned is a function.
|
|
||||||
func TestDisplayArraysAligned(t *testing.T) {
|
|
||||||
type scenario struct {
|
|
||||||
input [][]string
|
|
||||||
expected bool
|
|
||||||
}
|
|
||||||
|
|
||||||
scenarios := []scenario{
|
|
||||||
{
|
|
||||||
[][]string{{"", ""}, {"", ""}},
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[][]string{{""}, {"", ""}},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range scenarios {
|
|
||||||
assert.EqualValues(t, s.expected, displayArraysAligned(s.input))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestMin is a function.
|
// TestMin is a function.
|
||||||
func TestMin(t *testing.T) {
|
func TestMin(t *testing.T) {
|
||||||
type scenario struct {
|
type scenario struct {
|
||||||
@ -275,134 +37,6 @@ func TestMin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAsJson(t *testing.T) {
|
func TestAsJson(t *testing.T) {
|
||||||
type myStruct struct {
|
type myStruct struct {
|
||||||
a string
|
a string
|
||||||
|
Loading…
x
Reference in New Issue
Block a user