package precis
import (
"bytes"
"errors"
"unicode/utf8"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"golang.org/x/text/runes"
"golang.org/x/text/secure/bidirule"
"golang.org/x/text/transform"
"golang.org/x/text/width"
)
var (
errDisallowedRune = errors .New ("precis: disallowed rune encountered" )
)
var dpTrie = newDerivedPropertiesTrie (0 )
type Profile struct {
options
class *class
}
func NewIdentifier (opts ...Option ) *Profile {
return &Profile {
options : getOpts (opts ...),
class : identifier ,
}
}
func NewFreeform (opts ...Option ) *Profile {
return &Profile {
options : getOpts (opts ...),
class : freeform ,
}
}
func NewRestrictedProfile (parent *Profile , disallow runes .Set ) *Profile {
p := *parent
Disallow (disallow )(&p .options )
return &p
}
func (p *Profile ) NewTransformer () *Transformer {
var ts []transform .Transformer
r := 1
if p .options .repeat {
r = 4
}
for ; r > 0 ; r -- {
if p .options .foldWidth {
ts = append (ts , width .Fold )
}
for _ , f := range p .options .additional {
ts = append (ts , f ())
}
if p .options .cases != nil {
ts = append (ts , p .options .cases )
}
ts = append (ts , p .options .norm )
if p .options .bidiRule {
ts = append (ts , bidirule .New ())
}
ts = append (ts , &checker {p : p , allowed : p .Allowed ()})
}
return &Transformer {transform .Chain (ts ...)}
}
var errEmptyString = errors .New ("precis: transformation resulted in empty string" )
type buffers struct {
src []byte
buf [2 ][]byte
next int
}
func (b *buffers ) apply (t transform .SpanningTransformer ) (err error ) {
n , err := t .Span (b .src , true )
if err != transform .ErrEndOfSpan {
return err
}
x := b .next & 1
if b .buf [x ] == nil {
b .buf [x ] = make ([]byte , 0 , 8 +len (b .src )+len (b .src )>>2 )
}
span := append (b .buf [x ][:0 ], b .src [:n ]...)
b .src , _, err = transform .Append (t , span , b .src [n :])
b .buf [x ] = b .src
b .next ++
return err
}
var (
foldWidthT transform .SpanningTransformer = width .Fold
lowerCaseT transform .SpanningTransformer = cases .Lower (language .Und , cases .HandleFinalSigma (false ))
)
func (b *buffers ) enforce (p *Profile , src []byte , comparing bool ) (str []byte , err error ) {
b .src = src
ascii := true
for _ , c := range src {
if c >= utf8 .RuneSelf {
ascii = false
break
}
}
if ascii {
for _ , f := range p .options .additional {
if err = b .apply (f ()); err != nil {
return nil , err
}
}
switch {
case p .options .asciiLower || (comparing && p .options .ignorecase ):
for i , c := range b .src {
if 'A' <= c && c <= 'Z' {
b .src [i ] = c ^ 1 <<5
}
}
case p .options .cases != nil :
b .apply (p .options .cases )
}
c := checker {p : p }
if _ , err := c .span (b .src , true ); err != nil {
return nil , err
}
if p .disallow != nil {
for _ , c := range b .src {
if p .disallow .Contains (rune (c )) {
return nil , errDisallowedRune
}
}
}
if p .options .disallowEmpty && len (b .src ) == 0 {
return nil , errEmptyString
}
return b .src , nil
}
r := 1
if p .options .repeat {
r = 4
}
for ; r > 0 ; r -- {
if p .options .foldWidth || (p .options .ignorecase && comparing ) {
b .apply (foldWidthT )
}
for _ , f := range p .options .additional {
if err = b .apply (f ()); err != nil {
return nil , err
}
}
if p .options .cases != nil {
b .apply (p .options .cases )
}
if comparing && p .options .ignorecase {
b .apply (lowerCaseT )
}
b .apply (p .norm )
if p .options .bidiRule && !bidirule .Valid (b .src ) {
return nil , bidirule .ErrInvalid
}
c := checker {p : p }
if _ , err := c .span (b .src , true ); err != nil {
return nil , err
}
if p .disallow != nil {
for i := 0 ; i < len (b .src ); {
r , size := utf8 .DecodeRune (b .src [i :])
if p .disallow .Contains (r ) {
return nil , errDisallowedRune
}
i += size
}
}
if p .options .disallowEmpty && len (b .src ) == 0 {
return nil , errEmptyString
}
}
return b .src , nil
}
func (p *Profile ) Append (dst , src []byte ) ([]byte , error ) {
var buf buffers
b , err := buf .enforce (p , src , false )
if err != nil {
return nil , err
}
return append (dst , b ...), nil
}
func processBytes(p *Profile , b []byte , key bool ) ([]byte , error ) {
var buf buffers
b , err := buf .enforce (p , b , key )
if err != nil {
return nil , err
}
if buf .next == 0 {
c := make ([]byte , len (b ))
copy (c , b )
return c , nil
}
return b , nil
}
func (p *Profile ) Bytes (b []byte ) ([]byte , error ) {
return processBytes (p , b , false )
}
func (p *Profile ) AppendCompareKey (dst , src []byte ) ([]byte , error ) {
var buf buffers
b , err := buf .enforce (p , src , true )
if err != nil {
return nil , err
}
return append (dst , b ...), nil
}
func processString(p *Profile , s string , key bool ) (string , error ) {
var buf buffers
b , err := buf .enforce (p , []byte (s ), key )
if err != nil {
return "" , err
}
return string (b ), nil
}
func (p *Profile ) String (s string ) (string , error ) {
return processString (p , s , false )
}
func (p *Profile ) CompareKey (s string ) (string , error ) {
return processString (p , s , true )
}
func (p *Profile ) Compare (a , b string ) bool {
var buf buffers
akey , err := buf .enforce (p , []byte (a ), true )
if err != nil {
return false
}
buf = buffers {}
bkey , err := buf .enforce (p , []byte (b ), true )
if err != nil {
return false
}
return bytes .Equal (akey , bkey )
}
func (p *Profile ) Allowed () runes .Set {
if p .options .disallow != nil {
return runes .Predicate (func (r rune ) bool {
return p .class .Contains (r ) && !p .options .disallow .Contains (r )
})
}
return p .class
}
type checker struct {
p *Profile
allowed runes .Set
beforeBits catBitmap
termBits catBitmap
acceptBits catBitmap
}
func (c *checker ) Reset () {
c .beforeBits = 0
c .termBits = 0
c .acceptBits = 0
}
func (c *checker ) span (src []byte , atEOF bool ) (n int , err error ) {
for n < len (src ) {
e , sz := dpTrie .lookup (src [n :])
d := categoryTransitions [category (e &catMask )]
if sz == 0 {
if !atEOF {
return n , transform .ErrShortSrc
}
return n , errDisallowedRune
}
doLookAhead := false
if property (e ) < c .p .class .validFrom {
if d .rule == nil {
return n , errDisallowedRune
}
doLookAhead , err = d .rule (c .beforeBits )
if err != nil {
return n , err
}
}
c .beforeBits &= d .keep
c .beforeBits |= d .set
if c .termBits != 0 {
if c .beforeBits &c .termBits != 0 {
c .termBits = 0
c .acceptBits = 0
} else if c .beforeBits &c .acceptBits == 0 {
return n , errContext
}
}
if doLookAhead {
if c .termBits != 0 {
return n , errContext
}
c .termBits = d .term
c .acceptBits = d .accept
}
n += sz
}
if m := c .beforeBits >> finalShift ; c .beforeBits &m != m || c .termBits != 0 {
err = errContext
}
return n , err
}
func (c checker ) Transform (dst , src []byte , atEOF bool ) (nDst , nSrc int , err error ) {
short := false
if len (dst ) < len (src ) {
src = src [:len (dst )]
atEOF = false
short = true
}
nSrc , err = c .span (src , atEOF )
nDst = copy (dst , src [:nSrc ])
if short && (err == transform .ErrShortSrc || err == nil ) {
err = transform .ErrShortDst
}
return nDst , nSrc , err
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .