lib/encoder: add string rendering and parsing for encodings

This commit is contained in:
Nick Craig-Wood 2020-01-07 19:11:34 +00:00
parent 1ba5e99152
commit 0a5c83ece1
2 changed files with 166 additions and 0 deletions

View File

@ -16,6 +16,7 @@ import (
"bytes"
"fmt"
"io"
"sort"
"strconv"
"strings"
"unicode/utf8"
@ -32,6 +33,7 @@ const (
)
// NB keep the tests in fstests/fstests/fstests.go FsEncoding up to date with this
// NB keep the aliases up to date below also
// Possible flags for the MultiEncoder
const (
@ -99,6 +101,114 @@ type Encoder interface {
// character classes
type MultiEncoder uint
// Aliases maps encodings to names and vice versa
var (
encodingToName = map[MultiEncoder]string{}
nameToEncoding = map[string]MultiEncoder{}
)
// alias adds an alias for MultiEncoder.String() and MultiEncoder.Set()
func alias(name string, mask MultiEncoder) {
nameToEncoding[name] = mask
// don't overwrite existing reverse translations
if _, ok := encodingToName[mask]; !ok {
encodingToName[mask] = name
}
}
func init() {
alias("None", EncodeZero)
alias("Slash", EncodeSlash)
alias("LtGt", EncodeLtGt)
alias("DoubleQuote", EncodeDoubleQuote)
alias("SingleQuote", EncodeSingleQuote)
alias("BackQuote", EncodeBackQuote)
alias("Dollar", EncodeDollar)
alias("Colon", EncodeColon)
alias("Question", EncodeQuestion)
alias("Asterisk", EncodeAsterisk)
alias("Pipe", EncodePipe)
alias("Hash", EncodeHash)
alias("Percent", EncodePercent)
alias("BackSlash", EncodeBackSlash)
alias("CrLf", EncodeCrLf)
alias("Del", EncodeDel)
alias("Ctl", EncodeCtl)
alias("LeftSpace", EncodeLeftSpace)
alias("LeftPeriod", EncodeLeftPeriod)
alias("LeftTilde", EncodeLeftTilde)
alias("LeftCrLfHtVt", EncodeLeftCrLfHtVt)
alias("RightSpace", EncodeRightSpace)
alias("RightPeriod", EncodeRightPeriod)
alias("RightCrLfHtVt", EncodeRightCrLfHtVt)
alias("InvalidUtf8", EncodeInvalidUtf8)
alias("Dot", EncodeDot)
}
// validStrings returns all the valid MultiEncoder strings
func validStrings() string {
var out []string
for k := range nameToEncoding {
out = append(out, k)
}
sort.Strings(out)
return strings.Join(out, ", ")
}
// String converts the MultiEncoder into text
func (mask MultiEncoder) String() string {
// See if there is an exact translation - if so return that
if name, ok := encodingToName[mask]; ok {
return name
}
var out []string
// Otherwise decompose bit by bit
for bit := MultiEncoder(1); bit != 0; bit *= 2 {
if (mask & bit) != 0 {
if name, ok := encodingToName[bit]; ok {
out = append(out, name)
} else {
out = append(out, fmt.Sprintf("0x%X", uint(bit)))
}
}
}
return strings.Join(out, ",")
}
// Set converts a string into a MultiEncoder
func (mask *MultiEncoder) Set(in string) error {
var out MultiEncoder
parts := strings.Split(in, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if bits, ok := nameToEncoding[part]; ok {
out |= bits
} else {
i, err := strconv.ParseInt(part, 0, 64)
if err != nil {
return fmt.Errorf("bad encoding %q: possible values are: %s", part, validStrings())
}
out |= MultiEncoder(i)
}
}
*mask = out
return nil
}
// Type returns a textual type of the MultiEncoder to satsify the pflag.Value interface
func (mask MultiEncoder) Type() string {
return "Encoding"
}
// Scan implements the fmt.Scanner interface
func (mask *MultiEncoder) Scan(s fmt.ScanState, ch rune) error {
token, err := s.Token(true, nil)
if err != nil {
return err
}
return mask.Set(string(token))
}
// Encode takes a raw name and substitutes any reserved characters and
// patterns in it
func (mask MultiEncoder) Encode(in string) string {

View File

@ -1,12 +1,68 @@
package encoder
import (
"fmt"
"regexp"
"strconv"
"strings"
"testing"
"github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
)
// Check it satisfies the interfaces
var (
_ pflag.Value = (*MultiEncoder)(nil)
_ fmt.Scanner = (*MultiEncoder)(nil)
)
func TestEncodeString(t *testing.T) {
for _, test := range []struct {
mask uint
want string
}{
{0, "None"},
{EncodeZero, "None"},
{EncodeDoubleQuote, "DoubleQuote"},
{EncodeDot, "Dot"},
{EncodeWin, "LtGt,DoubleQuote,Colon,Question,Asterisk,Pipe"},
{EncodeHashPercent, "Hash,Percent"},
{EncodeSlash | EncodeDollar | EncodeColon, "Slash,Dollar,Colon"},
{EncodeSlash | (1 << 31), "Slash,0x80000000"},
} {
got := MultiEncoder(test.mask).String()
assert.Equal(t, test.want, got)
}
}
func TestEncodeSet(t *testing.T) {
for _, test := range []struct {
in string
want uint
wantErr bool
}{
{"", 0, true},
{"None", 0, false},
{"None", EncodeZero, false},
{"DoubleQuote", EncodeDoubleQuote, false},
{"Dot", EncodeDot, false},
{"LtGt,DoubleQuote,Colon,Question,Asterisk,Pipe", EncodeWin, false},
{"Hash,Percent", EncodeHashPercent, false},
{"Slash,Dollar,Colon", EncodeSlash | EncodeDollar | EncodeColon, false},
{"Slash,0x80000000", EncodeSlash | (1 << 31), false},
{"Blerp", 0, true},
{"0xFGFFF", 0, true},
} {
var got MultiEncoder
err := got.Set(test.in)
assert.Equal(t, test.wantErr, err != nil, err)
assert.Equal(t, MultiEncoder(test.want), got, test.in)
}
}
type testCase struct {
mask uint
in string