Skip to content

Commit

Permalink
Merge pull request #50 from asdf-vm/tb/version-file-parsing
Browse files Browse the repository at this point in the history
feat(golang-rewrite): version file parsing
  • Loading branch information
Stratus3D authored Jul 23, 2024
2 parents 30945dd + 13d85b4 commit 7a8de9e
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 0 deletions.
83 changes: 83 additions & 0 deletions internal/toolversions/toolversions.go
Original file line number Diff line number Diff line change
@@ -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
}
106 changes: 106 additions & 0 deletions internal/toolversions/toolversions_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}

0 comments on commit 7a8de9e

Please sign in to comment.