diff --git a/locale/locale.go b/locale/locale.go new file mode 100644 index 0000000..5f7a99b --- /dev/null +++ b/locale/locale.go @@ -0,0 +1,68 @@ +// Package locale defines a Locale type that represents a POSIX locale value, as +// required by desktop-entry-spec. +package locale + +import "fmt" +import "strings" + +type localeError string +func (err localeError) Error () string { return string(err) } + +const ( + // ErrUnexpectedRune indicates an unexpected rune was encountered while + // parsing a locale string. + ErrUnexpectedRune = localeError("unexpected rune") + // ErrLangEmpty indicates that a lang value was not specified. + ErrLangEmpty = localeError("lang empty") +) + +// Locale represents a POSIX locale value. +type Locale struct { + Lang string + Country string + Encoding string + Modifier string +} + +// String returns the string representation of the Locale. Unspecified values +// are left out. +func (locale Locale) String () string { + str := locale.Lang + if locale.Country != "" { + str = fmt.Sprint(str, "_", locale.Country) + } + if locale.Encoding != "" { + str = fmt.Sprint(str, ".", locale.Encoding) + } + if locale.Modifier != "" { + str = fmt.Sprint(str, "@", locale.Modifier) + } + return str +} + +// Parse parses a formatted locale string and returns the corresponding Locale. +func Parse (value string) (Locale, error) { + locale := Locale { } + value, locale.Modifier, _ = strings.Cut(value, "@") + value, locale.Encoding, _ = strings.Cut(value, ".") + value, locale.Country, _ = strings.Cut(value, "_") + locale.Lang = value + + if hasIllegalRunes(locale.Lang) || + hasIllegalRunes(locale.Country) || + hasIllegalRunes(locale.Encoding) || + hasIllegalRunes(locale.Modifier) { + + return Locale { }, ErrUnexpectedRune + } + + if locale.Lang == "" { + return Locale { }, ErrLangEmpty + } + + return locale, nil +} + +func hasIllegalRunes (value string) bool { + return strings.ContainsAny(value, "@._ ") +} diff --git a/locale/locale_test.go b/locale/locale_test.go new file mode 100644 index 0000000..fbfa66f --- /dev/null +++ b/locale/locale_test.go @@ -0,0 +1,106 @@ +package locale + +import "testing" + +func TestString (test *testing.T) { + testCase := func (correct string, locale Locale) { + got := locale.String() + if got != correct { + test.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", correct, got) + } + } + + testCase("lang_COUNTRY.ENCODING@MODIFIER", Locale { + Lang: "lang", + Country: "COUNTRY", + Encoding: "ENCODING", + Modifier: "MODIFIER", + }) + testCase("lang.ENCODING@MODIFIER", Locale { + Lang: "lang", + Encoding: "ENCODING", + Modifier: "MODIFIER", + }) + testCase("lang_COUNTRY@MODIFIER", Locale { + Lang: "lang", + Country: "COUNTRY", + Modifier: "MODIFIER", + }) + testCase("lang_COUNTRY.ENCODING", Locale { + Lang: "lang", + Country: "COUNTRY", + Encoding: "ENCODING", + }) + testCase("lang_COUNTRY", Locale { + Lang: "lang", + Country: "COUNTRY", + }) + testCase("lang.ENCODING", Locale { + Lang: "lang", + Encoding: "ENCODING", + }) + testCase("lang@MODIFIER", Locale { + Lang: "lang", + Modifier: "MODIFIER", + }) +} + +func TestParse (test *testing.T) { + testCase := func (input string, correct Locale) { + got, err := Parse(input) + if err != nil { + test.Fatal(err) + } + if got != correct { + test.Fatalf("expected:\n\t%v\ngot:\n\t%v\n", correct, got) + } + } + + testCase("lang_COUNTRY.ENCODING@MODIFIER", Locale { + Lang: "lang", + Country: "COUNTRY", + Encoding: "ENCODING", + Modifier: "MODIFIER", + }) + testCase("lang.ENCODING@MODIFIER", Locale { + Lang: "lang", + Encoding: "ENCODING", + Modifier: "MODIFIER", + }) + testCase("lang_COUNTRY@MODIFIER", Locale { + Lang: "lang", + Country: "COUNTRY", + Modifier: "MODIFIER", + }) + testCase("lang_COUNTRY.ENCODING", Locale { + Lang: "lang", + Country: "COUNTRY", + Encoding: "ENCODING", + }) + testCase("lang_COUNTRY", Locale { + Lang: "lang", + Country: "COUNTRY", + }) + testCase("lang.ENCODING", Locale { + Lang: "lang", + Encoding: "ENCODING", + }) + testCase("lang@MODIFIER", Locale { + Lang: "lang", + Modifier: "MODIFIER", + }) +} + +func TestParseErr (test *testing.T) { + testCase := func (input string, correct error) { + _, err := Parse(input) + if err != correct { + test.Fatalf("expected:\n\t%v\ngot:\n\t%v\n", correct, err) + } + } + + testCase("lang.ENCODING.MODIFIER", ErrUnexpectedRune) + testCase("lang@COUNTRY.ENCODING", ErrUnexpectedRune) + testCase("lang.@COUNTRY.ENCODING", ErrUnexpectedRune) + testCase("_COUNTRY.ENCODING@MODIFIER", ErrLangEmpty) +}