From 13d85b4f9270c5f80288cc380f7f2e2be6bd8e14 Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Sun, 21 Jul 2024 18:56:59 -0400 Subject: [PATCH] feat(golang-rewrite): version file parsing * Create toolversions package * Address linter errors for toolversions package * Write tests --- internal/toolversions/toolversions.go | 83 ++++++++++++++++ internal/toolversions/toolversions_test.go | 106 +++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 internal/toolversions/toolversions.go create mode 100644 internal/toolversions/toolversions_test.go diff --git a/internal/toolversions/toolversions.go b/internal/toolversions/toolversions.go new file mode 100644 index 00000000..3e0e42a4 --- /dev/null +++ b/internal/toolversions/toolversions.go @@ -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 +} diff --git a/internal/toolversions/toolversions_test.go b/internal/toolversions/toolversions_test.go new file mode 100644 index 00000000..b9505cb2 --- /dev/null +++ b/internal/toolversions/toolversions_test.go @@ -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) + }) + } +}