lib: Add helper functions to envvar to manipulate token lists.

Renames {Prepend,Append}UsingSeparator to
{Prepend,Append}UniqueToken, and changes the semantics to also
filter away duplicate tokens.  The previous functions didn't seem
to provide much value over manually calling {Split,Join}Tokens.
The new functions provide a bit more functionalty wrapped into a
one-line call.

Also added UniqueTokens and FilterTokens, which help manipulate
slices of tokens.

MultiPart: 2/2

Change-Id: Ice2c126615af17ff43804c472826d738e5724608
diff --git a/envvar/.api b/envvar/.api
index 569d0b4..866ee03 100644
--- a/envvar/.api
+++ b/envvar/.api
@@ -1,16 +1,18 @@
-pkg envvar, func AppendUsingSeparator(string, string, string) string
+pkg envvar, func AppendUniqueToken(string, string, string) string
 pkg envvar, func CopyMap(map[string]string) map[string]string
 pkg envvar, func CopySlice([]string) []string
+pkg envvar, func FilterToken([]string, string) []string
 pkg envvar, func JoinKeyValue(string, string) string
 pkg envvar, func JoinTokens([]string, string) string
 pkg envvar, func MapToSlice(map[string]string) []string
 pkg envvar, func MergeMaps(...map[string]string) map[string]string
 pkg envvar, func MergeSlices(...[]string) []string
-pkg envvar, func PrependUsingSeparator(string, string, string) string
+pkg envvar, func PrependUniqueToken(string, string, string) string
 pkg envvar, func SliceToMap([]string) map[string]string
 pkg envvar, func SortByKey([]string)
 pkg envvar, func SplitKeyValue(string) (string, string)
 pkg envvar, func SplitTokens(string, string) []string
+pkg envvar, func UniqueTokens([]string) []string
 pkg envvar, func VarsFromMap(map[string]string) *Vars
 pkg envvar, func VarsFromOS() *Vars
 pkg envvar, func VarsFromSlice([]string) *Vars
diff --git a/envvar/envvar.go b/envvar/envvar.go
index 788f948..b8a9c3d 100644
--- a/envvar/envvar.go
+++ b/envvar/envvar.go
@@ -145,22 +145,51 @@
 	return value
 }
 
-// PrependUsingSeparator prepends the parameter val to parameter existing using
-// separator to split the tokens in existing  and to prepend val.
-// PrependUsingSeparator uses SplitTokens on the existing string
-// and hence filters out empty tokens, so "A::B:" becomes "A:B".
-func PrependUsingSeparator(val, existing, separator string) string {
-	tmp := SplitTokens(existing, separator)
-	return JoinTokens(append([]string{val}, tmp...), separator)
+// UniqueTokens returns a new slice containing tokens that are not empty or
+// duplicated, and in the same relative order as the original slice.
+func UniqueTokens(tokens []string) []string {
+	var unique []string
+	seen := make(map[string]bool)
+	for _, token := range tokens {
+		if token == "" || seen[token] {
+			continue
+		}
+		seen[token] = true
+		unique = append(unique, token)
+	}
+	return unique
 }
 
-// AppendUsingSeparator appends the parameter val to parameter existing using
-// separator to split the tokens in existing string and to append val.
-// AppendUsingSeparator uses SplitTokens on the existing string
-// and hence filters out empty tokens, so "A::B:" becomes "A:B".
-func AppendUsingSeparator(val, existing, separator string) string {
-	tmp := SplitTokens(existing, separator)
-	return JoinTokens(append(tmp, val), separator)
+// FilterToken returns a new slice containing tokens that are not empty or match
+// the target, and in the same relative order as the original slice.
+func FilterToken(tokens []string, target string) []string {
+	var filtered []string
+	for _, token := range tokens {
+		if token == "" || token == target {
+			continue
+		}
+		filtered = append(filtered, token)
+	}
+	return filtered
+}
+
+// PrependUniqueToken prepends token to value, which is separated by separator,
+// removing all empty and duplicate tokens.  Returns a string where token only
+// occurs once, and is first.
+func PrependUniqueToken(value, separator, token string) string {
+	result := SplitTokens(value, separator)
+	result = append([]string{token}, result...)
+	return JoinTokens(UniqueTokens(result), separator)
+}
+
+// AppendUniqueToken appends token to value, which is separated by separator,
+// and removes all empty and duplicate tokens.  Returns a string where token
+// only occurs once, and is last.
+func AppendUniqueToken(value, separator, token string) string {
+	result := SplitTokens(value, separator)
+	result = FilterToken(result, token)
+	result = append(result, token)
+	return JoinTokens(UniqueTokens(result), separator)
 }
 
 // SortByKey sorts vars into ascending key order, where vars is expected to be
diff --git a/envvar/envvar_test.go b/envvar/envvar_test.go
index e2f9cc9..163cd34 100644
--- a/envvar/envvar_test.go
+++ b/envvar/envvar_test.go
@@ -161,36 +161,95 @@
 	}
 }
 
-func TestAppendPrepend(t *testing.T) {
+func TestUniqueTokens(t *testing.T) {
 	tests := []struct {
-		Sep, Value, Existing, Result string
+		Tokens, Want []string
 	}{
-		{":", "Z", "", "Z"},
-		{":", "", "Z", "Z"},
-		{":", "", "", ""},
-		{":", "X", ":A:B", "X:A:B"},
-		{":", "Y", "A:B", "Y:A:B"},
-		{":", "Z", "A:::B", "Z:A:B"},
-		{":", "Z", "A:::B:", "Z:A:B"},
+		{nil, nil},
+		{[]string{""}, nil},
+		{[]string{"A"}, []string{"A"}},
+		{[]string{"A", "A"}, []string{"A"}},
+		{[]string{"A", "B"}, []string{"A", "B"}},
+		{[]string{"A", "B", "A", "B"}, []string{"A", "B"}},
 	}
-	for i, test := range tests {
-		if got, want := PrependUsingSeparator(test.Value, test.Existing, test.Sep), test.Result; got != want {
-			t.Errorf("SplitTokens(%d) got %v, want %v", i, got, want)
+	for _, test := range tests {
+		if got, want := UniqueTokens(test.Tokens), test.Want; !reflect.DeepEqual(got, want) {
+			t.Errorf("UniqueTokens(%q) got %q, want %q", test.Tokens, got, want)
 		}
 	}
-	tests = []struct {
-		Sep, Value, Existing, Result string
+}
+
+func TestFilterToken(t *testing.T) {
+	tests := []struct {
+		Tokens []string
+		Target string
+		Want   []string
 	}{
-		{":", "Z", "", "Z"},
-		{":", "", "Z", "Z"},
-		{":", "", "", ""},
-		{":", "X", ":A:B:", "A:B:X"},
-		{":", "Y", "A:B", "A:B:Y"},
-		{":", "Z", "A:::B", "A:B:Z"},
+		{nil, "", nil},
+		{nil, "A", nil},
+		{[]string{""}, "", nil},
+		{[]string{""}, "A", nil},
+		{[]string{"A"}, "", []string{"A"}},
+		{[]string{"A"}, "A", nil},
+		{[]string{"A"}, "B", []string{"A"}},
+		{[]string{"A", "A"}, "", []string{"A", "A"}},
+		{[]string{"A", "A"}, "A", nil},
+		{[]string{"A", "A"}, "B", []string{"A", "A"}},
+		{[]string{"A", "B"}, "", []string{"A", "B"}},
+		{[]string{"A", "B"}, "A", []string{"B"}},
+		{[]string{"A", "B"}, "B", []string{"A"}},
+		{[]string{"A", "B", "A", "B"}, "", []string{"A", "B", "A", "B"}},
+		{[]string{"A", "B", "A", "B"}, "A", []string{"B", "B"}},
+		{[]string{"A", "B", "A", "B"}, "B", []string{"A", "A"}},
 	}
-	for i, test := range tests {
-		if got, want := AppendUsingSeparator(test.Value, test.Existing, test.Sep), test.Result; got != want {
-			t.Errorf("SplitTokens(%d) got %v, want %v", i, got, want)
+	for _, test := range tests {
+		if got, want := FilterToken(test.Tokens, test.Target), test.Want; !reflect.DeepEqual(got, want) {
+			t.Errorf("FilterToken(%q, %q) got %q, want %q", test.Tokens, test.Target, got, want)
+		}
+	}
+}
+
+func TestPrependUniqueToken(t *testing.T) {
+	tests := []struct {
+		Sep, Value, Token, Want string
+	}{
+		{":", "", "", ""},
+		{":", "", "Z", "Z"},
+		{":", "Z", "", "Z"},
+		{":", "Z", "Z", "Z"},
+		{":", ":Z:Z:", "Z", "Z"},
+		{":", ":A:B", "Z", "Z:A:B"},
+		{":", "A:B", "Z", "Z:A:B"},
+		{":", "A:::B", "Z", "Z:A:B"},
+		{":", "A:::B:", "Z", "Z:A:B"},
+		{":", "Z:A:Z:B:Z", "Z", "Z:A:B"},
+		{":", "Z:A:Z:B:Z:A:Z:B:Z", "Z", "Z:A:B"},
+	}
+	for _, test := range tests {
+		if got, want := PrependUniqueToken(test.Value, test.Sep, test.Token), test.Want; got != want {
+			t.Errorf("PrependUniqueToken(%q, %q, %q) got %v, want %v", test.Value, test.Sep, test.Token, got, want)
+		}
+	}
+}
+
+func TestAppendUniqueToken(t *testing.T) {
+	tests := []struct {
+		Sep, Value, Token, Want string
+	}{
+		{":", "", "", ""},
+		{":", "", "Z", "Z"},
+		{":", "Z", "", "Z"},
+		{":", "Z", "Z", "Z"},
+		{":", ":Z:Z:", "Z", "Z"},
+		{":", ":A:B:", "Z", "A:B:Z"},
+		{":", "A:B", "Z", "A:B:Z"},
+		{":", "A:::B", "Z", "A:B:Z"},
+		{":", "Z:A:Z:B:Z", "Z", "A:B:Z"},
+		{":", "Z:A:Z:B:Z:A:Z:B:Z", "Z", "A:B:Z"},
+	}
+	for _, test := range tests {
+		if got, want := AppendUniqueToken(test.Value, test.Sep, test.Token), test.Want; got != want {
+			t.Errorf("AppendUniqueToken(%q, %q, %q) got %v, want %v", test.Value, test.Sep, test.Token, got, want)
 		}
 	}
 }