options.go 4.1 KB

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