diff --git a/cmd/os_release.go b/cmd/os_release.go index 06d8e4630cf..aa5e9fb1ff5 100644 --- a/cmd/os_release.go +++ b/cmd/os_release.go @@ -7,10 +7,19 @@ import ( "os" "strconv" "strings" + "unicode" vfs "github.com/twpayne/go-vfs" ) +var ( + wellKnownAbbreviations = map[string]struct{}{ + "ANSI": struct{}{}, + "CPE": struct{}{}, + "URL": struct{}{}, + } +) + // getOSRelease returns the operating system identification data as defined by // https://www.freedesktop.org/software/systemd/man/os-release.html. func getOSRelease(fs vfs.FS) (map[string]string, error) { @@ -22,11 +31,21 @@ func getOSRelease(fs vfs.FS) (map[string]string, error) { return nil, err } defer f.Close() - return parseOSRelease(f) + m, err := parseOSRelease(f) + if err != nil { + return nil, err + } + return upperSnakeCaseToCamelCaseMap(m), nil } return nil, os.ErrNotExist } +// isWellKnownAbbreviation returns true if word is a well known abbreviation. +func isWellKnownAbbreviation(word string) bool { + _, ok := wellKnownAbbreviations[word] + return ok +} + // maybeUnquote removes quotation marks around s. func maybeUnquote(s string) string { // Try to unquote. @@ -53,3 +72,32 @@ func parseOSRelease(r io.Reader) (map[string]string, error) { } return result, s.Err() } + +// titilize returns s, titilized. +func titilize(s string) string { + runes := []rune(s) + return string(append([]rune{unicode.ToTitle(runes[0])}, runes[1:]...)) +} + +// upperSnakeCaseToCamelCase converts a string in UPPER_SNAKE_CASE to +// camelCase. +func upperSnakeCaseToCamelCase(s string) string { + words := strings.Split(s, "_") + for i, word := range words { + if i == 0 { + words[i] = strings.ToLower(word) + } else if !isWellKnownAbbreviation(word) { + words[i] = titilize(strings.ToLower(word)) + } + } + return strings.Join(words, "") +} + +// upperSnakeCaseToCamelCaseKeys returns m with all keys converted from UPPER_SNAKE_CASE to camelCase. +func upperSnakeCaseToCamelCaseMap(m map[string]string) map[string]string { + result := make(map[string]string) + for k, v := range m { + result[upperSnakeCaseToCamelCase(k)] = v + } + return result +} diff --git a/cmd/os_release_test.go b/cmd/os_release_test.go index 2043a5f82bb..6e4268c5733 100644 --- a/cmd/os_release_test.go +++ b/cmd/os_release_test.go @@ -5,8 +5,87 @@ import ( "testing" "github.com/d4l3k/messagediff" + "github.com/twpayne/go-vfs/vfst" ) +func TestGetOSRelease(t *testing.T) { + for _, tc := range []struct { + name string + root map[string]interface{} + want map[string]string + }{ + { + name: "fedora", + root: map[string]interface{}{ + "/etc/os-release": `NAME=Fedora +VERSION="17 (Beefy Miracle)" +ID=fedora +VERSION_ID=17 +PRETTY_NAME="Fedora 17 (Beefy Miracle)" +ANSI_COLOR="0;34" +CPE_NAME="cpe:/o:fedoraproject:fedora:17" +HOME_URL="https://fedoraproject.org/" +BUG_REPORT_URL="https://bugzilla.redhat.com/"`, + }, + want: map[string]string{ + "name": "Fedora", + "version": "17 (Beefy Miracle)", + "id": "fedora", + "versionId": "17", + "prettyName": "Fedora 17 (Beefy Miracle)", + "ansiColor": "0;34", + "cpeName": "cpe:/o:fedoraproject:fedora:17", + "homeURL": "https://fedoraproject.org/", + "bugReportURL": "https://bugzilla.redhat.com/", + }, + }, + { + name: "ubuntu", + root: map[string]interface{}{ + "/usr/lib/os-release": `NAME="Ubuntu" +VERSION="18.04.1 LTS (Bionic Beaver)" +ID=ubuntu +ID_LIKE=debian +PRETTY_NAME="Ubuntu 18.04.1 LTS" +VERSION_ID="18.04" +HOME_URL="https://www.ubuntu.com/" +SUPPORT_URL="https://help.ubuntu.com/" +BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" +PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" +VERSION_CODENAME=bionic +UBUNTU_CODENAME=bionic`, + }, + want: map[string]string{ + "name": "Ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)", + "id": "ubuntu", + "idLike": "debian", + "prettyName": "Ubuntu 18.04.1 LTS", + "versionId": "18.04", + "homeURL": "https://www.ubuntu.com/", + "supportURL": "https://help.ubuntu.com/", + "bugReportURL": "https://bugs.launchpad.net/ubuntu/", + "privacyPolicyURL": "https://www.ubuntu.com/legal/terms-and-policies/privacy-policy", + "versionCodename": "bionic", + "ubuntuCodename": "bionic", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + fs, cleanup, err := vfst.NewTestFS(tc.root) + if err != nil { + t.Fatalf("vfst.NewTestFS(_) == _, _, %v, want _, _, ", err) + } + defer cleanup() + got, gotErr := getOSRelease(fs) + diff, equal := messagediff.PrettyDiff(tc.want, got) + if gotErr != nil || !equal { + t.Errorf("getOSRelease(fs) == %v, %v, want %v, \n%s", got, gotErr, tc.want, diff) + } + }) + } +} + func TestParseOSRelease(t *testing.T) { for _, tc := range []struct { s string @@ -70,3 +149,21 @@ UBUNTU_CODENAME=bionic`, } } } + +func TestUppercaseSnakeToCamelCase(t *testing.T) { + for _, tc := range []struct { + s string + want string + }{ + {s: "ID", want: "id"}, + {s: "NAME", want: "name"}, + {s: "ID_LIKE", want: "idLike"}, + {s: "PRETTY_NAME", want: "prettyName"}, + {s: "ANSI_COLOR", want: "ansiColor"}, + {s: "BUG_REPORT_URL", want: "bugReportURL"}, + } { + if got := upperSnakeCaseToCamelCase(tc.s); got != tc.want { + t.Errorf("uppercaseSnakeToCamelCase(%q) == %q, want %q", tc.s, got, tc.want) + } + } +}