cases: add HandleFinalSigma option

Change-Id: Ifbeb19cdbd07552f92cc11824872bd71f6d46c37
Reviewed-on: https://go-review.googlesource.com/30105
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/cases/cases.go b/cases/cases.go
index bd7bc8c..6c3fa5f 100644
--- a/cases/cases.go
+++ b/cases/cases.go
@@ -97,6 +97,12 @@
 // An Option is used to modify the behavior of a Caser.
 type Option func(o *options)
 
+// TODO: consider these options to take a boolean as well, like FinalSigma.
+// The advantage of using this approach is that other providers of a lower-case
+// algorithm could set different defaults by prefixing a user-provided slice
+// of options with their own. This is handy, for instance, for the precis
+// package which would override the default to not handle the Greek final sigma.
+
 var (
 	// NoLower disables the lowercasing of non-leading letters for a title
 	// caser.
@@ -115,7 +121,7 @@
 
 	// TODO: segmenter, max ignorable, alternative versions, etc.
 
-	noFinalSigma bool // Only used for testing.
+	ignoreFinalSigma bool
 }
 
 func getOpts(o ...Option) (res options) {
@@ -125,10 +131,12 @@
 	return
 }
 
-func noLower(o *options) {
-	o.noLower = true
-}
+func noLower(o *options) { o.noLower = true }
+func compact(o *options) { o.simple = true }
 
-func compact(o *options) {
-	o.simple = true
+// HandleFinalSigma specifies whether the special handling of Greek final sigma
+// should be enabled. Unicode prescribes handling the Greek final sigma for all
+// locales, but standards like IDNA and PRECIS override this default.
+func HandleFinalSigma(enable bool) Option {
+	return func(o *options) { o.ignoreFinalSigma = !enable }
 }
diff --git a/cases/map.go b/cases/map.go
index 68395fc..90c784b 100644
--- a/cases/map.go
+++ b/cases/map.go
@@ -108,7 +108,7 @@
 func makeLower(t language.Tag, o options) transform.SpanningTransformer {
 	_, i, _ := matcher.Match(t)
 	f := lowerFunc[i]
-	if o.noFinalSigma {
+	if o.ignoreFinalSigma {
 		return &simpleCaser{f: f, span: isLower}
 	}
 	return &lowerCaser{
@@ -123,7 +123,7 @@
 	lower := x.lower
 	if o.noLower {
 		lower = (*context).copy
-	} else if !o.noFinalSigma {
+	} else if !o.ignoreFinalSigma {
 		lower = finalSigma(lower)
 	}
 	return &titleCaser{
diff --git a/cases/map_test.go b/cases/map_test.go
index 2f4f40e..7311131 100644
--- a/cases/map_test.go
+++ b/cases/map_test.go
@@ -27,12 +27,6 @@
 	opts  options
 }
 
-// We don't support the NoFinalSigma option, but we use it to test the
-// underlying lower casers and to be able to compare differences in performance.
-func noFinalSigma(o *options) {
-	o.noFinalSigma = true
-}
-
 var testCases = []testCase{
 	0: {
 		lang:  "und",
@@ -40,7 +34,7 @@
 		title: "Abc Abc Abc Abc İsıi Εσάσ",
 		lower: "abc abc abc abc i\u0307sıi εσάσ",
 		upper: "ABC ABC ABC ABC İSII ΕΣΆΣ",
-		opts:  getOpts(noFinalSigma),
+		opts:  getOpts(HandleFinalSigma(false)),
 	},
 
 	1: {
@@ -49,6 +43,7 @@
 		title: "Abc Abc Abc Abc İsıi Εσάς Σ _Σ -Σ",
 		lower: "abc abc abc abc i\u0307sıi εσάς σ _σ -σ",
 		upper: "ABC ABC ABC ABC İSII ΕΣΆΣ Σ _Σ -Σ",
+		opts:  getOpts(HandleFinalSigma(true)),
 	},
 
 	2: { // Title cased runes.
@@ -254,23 +249,23 @@
 		first, second string
 	}{{
 		"title/nosigma/single midword",
-		Caser{makeTitle(language.Und, noSigma)},
+		Title(language.Und, HandleFinalSigma(false)),
 		"A.", "a",
 	}, {
 		"title/nosigma/single midword",
-		Caser{makeTitle(language.Und, noSigma)},
+		Title(language.Und, HandleFinalSigma(false)),
 		"A", ".a",
 	}, {
 		"title/nosigma/double midword",
-		Caser{makeTitle(language.Und, noSigma)},
+		Title(language.Und, HandleFinalSigma(false)),
 		"A..", "a",
 	}, {
 		"title/nosigma/double midword",
-		Caser{makeTitle(language.Und, noSigma)},
+		Title(language.Und, HandleFinalSigma(false)),
 		"A.", ".a",
 	}, {
 		"title/nosigma/double midword",
-		Caser{makeTitle(language.Und, noSigma)},
+		Title(language.Und, HandleFinalSigma(false)),
 		"A", "..a",
 	}, {
 		"title/sigma/single midword",
@@ -384,14 +379,14 @@
 		want:    "οσ οσσ",
 		dstSize: minBufSize,
 		srcSize: minBufSize,
-		t:       makeLower(language.Und, noSigma),
+		t:       Lower(language.Und, HandleFinalSigma(false)),
 	}, {
 		desc:    "und/title/simple (no final sigma)",
 		src:     "ΟΣ ΟΣΣ",
 		want:    "Οσ Οσσ",
 		dstSize: minBufSize,
 		srcSize: minBufSize,
-		t:       makeTitle(language.Und, noSigma),
+		t:       Title(language.Und, HandleFinalSigma(false)),
 	}, {
 		desc:    "und/title/final sigma: no error",
 		src:     "ΟΣ",
@@ -686,13 +681,13 @@
 		src:   "ος οσσ",
 		want:  "οσ οσσ",
 		atEOF: true,
-		t:     Caser{makeLower(language.Und, noSigma)},
+		t:     Lower(language.Und, HandleFinalSigma(false)),
 	}, {
 		desc:  "und/title/simple (no final sigma)",
 		src:   "Οσ Οσσ",
 		want:  "Οσ Οσσ",
 		atEOF: true,
-		t:     Caser{makeTitle(language.Und, noSigma)},
+		t:     Title(language.Und, HandleFinalSigma(false)),
 	}, {
 		desc: "und/lower/final sigma: no error",
 		src:  "οΣ", // Oς
@@ -740,7 +735,7 @@
 		src:   "При",
 		want:  "При",
 		atEOF: true,
-		t:     Caser{makeTitle(language.Und, noSigma)},
+		t:     Title(language.Und, HandleFinalSigma(false)),
 	}, {
 		// Note: the choice to change the final sigma at the end in case of
 		// too many case ignorables is arbitrary. The main reason for this
@@ -884,10 +879,6 @@
 
 // TODO: Improve ASCII performance.
 
-var (
-	noSigma = options{noFinalSigma: true}
-)
-
 func BenchmarkCasers(b *testing.B) {
 	for _, s := range []struct{ name, text string }{
 		{"ascii", txtASCII},
@@ -917,10 +908,10 @@
 		}{
 			{"fold/default", Fold()},
 			{"upper/default", Upper(language.Und)},
-			{"lower/sigma", makeLower(language.Und, options{})},
-			{"lower/simple", makeLower(language.Und, noSigma)},
-			{"title/sigma", makeTitle(language.Und, options{})},
-			{"title/simple", makeTitle(language.Und, noSigma)},
+			{"lower/sigma", Lower(language.Und)},
+			{"lower/simple", Lower(language.Und, HandleFinalSigma(false))},
+			{"title/sigma", Title(language.Und)},
+			{"title/simple", Title(language.Und, HandleFinalSigma(false))},
 		} {
 			c := Caser{t.caser}
 			dst := make([]byte, len(src))