feat(golang-rewrite): version file parsing

* Create toolversions package
* Address linter errors for toolversions package
* Write tests
This commit is contained in:
Trevor Brown 2024-07-21 18:56:59 -04:00
parent dc9521be61
commit a0cd0319cb
2 changed files with 189 additions and 0 deletions

View File

@ -0,0 +1,83 @@
// Package toolversions handles reading and writing tools and versions from
// asdf's .tool-versions files
package toolversions
import (
"os"
"strings"
)
// ToolVersions represents a tool along with versions specified for it
type ToolVersions struct {
Name string
Versions []string
}
// FindToolVersions looks up a tool version in a tool versions file and if found
// returns a slice of versions for it.
func FindToolVersions(filepath, toolName string) (versions []string, found bool, err error) {
content, err := os.ReadFile(filepath)
if err != nil {
return versions, false, err
}
versions, found = findToolVersionsInContent(string(content), toolName)
return versions, found, nil
}
func findToolVersionsInContent(content, toolName string) (versions []string, found bool) {
toolVersions := getAllToolsAndVersionsInContent(content)
for _, tool := range toolVersions {
if tool.Name == toolName {
return tool.Versions, true
}
}
return versions, found
}
// GetAllToolsAndVersions returns a list of all tools and associated versions
// contained in a .tool-versions file
func GetAllToolsAndVersions(filepath string) (toolVersions []ToolVersions, err error) {
content, err := os.ReadFile(filepath)
if err != nil {
return toolVersions, err
}
toolVersions = getAllToolsAndVersionsInContent(string(content))
return toolVersions, nil
}
func getAllToolsAndVersionsInContent(content string) (toolVersions []ToolVersions) {
for _, line := range readLines(content) {
tokens := parseLine(line)
newTool := ToolVersions{Name: tokens[0], Versions: tokens[1:]}
toolVersions = append(toolVersions, newTool)
}
return toolVersions
}
// readLines reads all the lines in a given file
// removing spaces and comments which are marked by '#'
func readLines(content string) (lines []string) {
for _, line := range strings.Split(content, "\n") {
line = strings.SplitN(line, "#", 2)[0]
line = strings.TrimSpace(line)
if len(line) > 0 {
lines = append(lines, line)
}
}
return
}
func parseLine(line string) (tokens []string) {
for _, token := range strings.Split(line, " ") {
token = strings.TrimSpace(token)
if len(token) > 0 {
tokens = append(tokens, token)
}
}
return tokens
}

View File

@ -0,0 +1,106 @@
package toolversions
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetAllToolsAndVersions(t *testing.T) {
t.Run("returns error when non-existant file", func(t *testing.T) {
toolVersions, err := GetAllToolsAndVersions("non-existant-file")
assert.Error(t, err)
assert.Empty(t, toolVersions)
})
t.Run("returns list of tool versions when populated file", func(t *testing.T) {
toolVersionsPath := filepath.Join(t.TempDir(), ".tool-versions")
file, err := os.Create(toolVersionsPath)
assert.Nil(t, err)
defer file.Close()
file.WriteString("ruby 2.0.0")
toolVersions, err := GetAllToolsAndVersions(toolVersionsPath)
assert.Nil(t, err)
expected := []ToolVersions{{Name: "ruby", Versions: []string{"2.0.0"}}}
assert.Equal(t, expected, toolVersions)
})
}
func TestFindToolVersions(t *testing.T) {
t.Run("returns error when non-existant file", func(t *testing.T) {
versions, found, err := FindToolVersions("non-existant-file", "nonexistant-tool")
assert.Error(t, err)
assert.False(t, found)
assert.Empty(t, versions)
})
t.Run("returns list of versions and found true when file contains tool versions", func(t *testing.T) {
toolVersionsPath := filepath.Join(t.TempDir(), ".tool-versions")
file, err := os.Create(toolVersionsPath)
assert.Nil(t, err)
defer file.Close()
file.WriteString("ruby 2.0.0")
versions, found, err := FindToolVersions(toolVersionsPath, "ruby")
assert.Nil(t, err)
assert.True(t, found)
assert.Equal(t, []string{"2.0.0"}, versions)
})
}
func TestfindToolVersionsInContent(t *testing.T) {
t.Run("returns empty list with found false when empty content", func(t *testing.T) {
versions, found := findToolVersionsInContent("", "ruby")
assert.False(t, found)
assert.Empty(t, versions)
})
t.Run("returns empty list with found false when tool not found", func(t *testing.T) {
versions, found := findToolVersionsInContent("lua 5.4.5", "ruby")
assert.False(t, found)
assert.Empty(t, versions)
})
t.Run("returns list of versions with found true when tool found", func(t *testing.T) {
versions, found := findToolVersionsInContent("lua 5.4.5 5.4.6\nruby 2.0.0", "lua")
assert.True(t, found)
assert.Equal(t, []string{"5.4.5", "5.4.6"}, versions)
})
}
func TestgetAllToolsAndVersionsInContent(t *testing.T) {
tests := []struct {
desc string
input string
want []ToolVersions
}{
{
desc: "returns empty list with found true and no error when empty content",
input: "",
want: []ToolVersions{},
},
{
desc: "returns list with one tool when single tool in content",
input: "lua 5.4.5 5.4.6",
want: []ToolVersions{{Name: "lua", Versions: []string{"5.4.5", "5.4.6"}}},
},
{
desc: "returns list with multiple tools when multiple tools in content",
input: "lua 5.4.5 5.4.6\nruby 2.0.0",
want: []ToolVersions{
{Name: "lua", Versions: []string{"5.4.5", "5.4.6"}},
{Name: "ruby", Versions: []string{"2.0.0"}},
},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
toolsAndVersions := getAllToolsAndVersionsInContent(tt.input)
assert.Equal(t, tt.want, toolsAndVersions)
})
}
}