1// Copyright 2015 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package precis
6
7import (
8	"golang.org/x/text/cases"
9	"golang.org/x/text/language"
10	"golang.org/x/text/runes"
11	"golang.org/x/text/transform"
12	"golang.org/x/text/unicode/norm"
13)
14
15// An Option is used to define the behavior and rules of a Profile.
16type Option func(*options)
17
18type options struct {
19	// Preparation options
20	foldWidth bool
21
22	// Enforcement options
23	asciiLower    bool
24	cases         transform.SpanningTransformer
25	disallow      runes.Set
26	norm          transform.SpanningTransformer
27	additional    []func() transform.SpanningTransformer
28	width         transform.SpanningTransformer
29	disallowEmpty bool
30	bidiRule      bool
31	repeat        bool
32
33	// Comparison options
34	ignorecase bool
35}
36
37func getOpts(o ...Option) (res options) {
38	for _, f := range o {
39		f(&res)
40	}
41	// Using a SpanningTransformer, instead of norm.Form prevents an allocation
42	// down the road.
43	if res.norm == nil {
44		res.norm = norm.NFC
45	}
46	return
47}
48
49var (
50	// The IgnoreCase option causes the profile to perform a case insensitive
51	// comparison during the PRECIS comparison step.
52	IgnoreCase Option = ignoreCase
53
54	// The FoldWidth option causes the profile to map non-canonical wide and
55	// narrow variants to their decomposition mapping. This is useful for
56	// profiles that are based on the identifier class which would otherwise
57	// disallow such characters.
58	FoldWidth Option = foldWidth
59
60	// The DisallowEmpty option causes the enforcement step to return an error if
61	// the resulting string would be empty.
62	DisallowEmpty Option = disallowEmpty
63
64	// The BidiRule option causes the Bidi Rule defined in RFC 5893 to be
65	// applied.
66	BidiRule Option = bidiRule
67)
68
69var (
70	ignoreCase = func(o *options) {
71		o.ignorecase = true
72	}
73	foldWidth = func(o *options) {
74		o.foldWidth = true
75	}
76	disallowEmpty = func(o *options) {
77		o.disallowEmpty = true
78	}
79	bidiRule = func(o *options) {
80		o.bidiRule = true
81	}
82	repeat = func(o *options) {
83		o.repeat = true
84	}
85)
86
87// TODO: move this logic to package transform
88
89type spanWrap struct{ transform.Transformer }
90
91func (s spanWrap) Span(src []byte, atEOF bool) (n int, err error) {
92	return 0, transform.ErrEndOfSpan
93}
94
95// TODO: allow different types? For instance:
96//     func() transform.Transformer
97//     func() transform.SpanningTransformer
98//     func([]byte) bool  // validation only
99//
100// Also, would be great if we could detect if a transformer is reentrant.
101
102// The AdditionalMapping option defines the additional mapping rule for the
103// Profile by applying Transformer's in sequence.
104func AdditionalMapping(t ...func() transform.Transformer) Option {
105	return func(o *options) {
106		for _, f := range t {
107			sf := func() transform.SpanningTransformer {
108				return f().(transform.SpanningTransformer)
109			}
110			if _, ok := f().(transform.SpanningTransformer); !ok {
111				sf = func() transform.SpanningTransformer {
112					return spanWrap{f()}
113				}
114			}
115			o.additional = append(o.additional, sf)
116		}
117	}
118}
119
120// The Norm option defines a Profile's normalization rule. Defaults to NFC.
121func Norm(f norm.Form) Option {
122	return func(o *options) {
123		o.norm = f
124	}
125}
126
127// The FoldCase option defines a Profile's case mapping rule. Options can be
128// provided to determine the type of case folding used.
129func FoldCase(opts ...cases.Option) Option {
130	return func(o *options) {
131		o.asciiLower = true
132		o.cases = cases.Fold(opts...)
133	}
134}
135
136// The LowerCase option defines a Profile's case mapping rule. Options can be
137// provided to determine the type of case folding used.
138func LowerCase(opts ...cases.Option) Option {
139	return func(o *options) {
140		o.asciiLower = true
141		if len(opts) == 0 {
142			o.cases = cases.Lower(language.Und, cases.HandleFinalSigma(false))
143			return
144		}
145
146		opts = append([]cases.Option{cases.HandleFinalSigma(false)}, opts...)
147		o.cases = cases.Lower(language.Und, opts...)
148	}
149}
150
151// The Disallow option further restricts a Profile's allowed characters beyond
152// what is disallowed by the underlying string class.
153func Disallow(set runes.Set) Option {
154	return func(o *options) {
155		o.disallow = set
156	}
157}
158