forked from TrueCloudLab/rclone
lib/encoder: add string rendering and parsing for encodings
This commit is contained in:
parent
1ba5e99152
commit
0a5c83ece1
2 changed files with 166 additions and 0 deletions
|
@ -16,6 +16,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"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 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
|
// Possible flags for the MultiEncoder
|
||||||
const (
|
const (
|
||||||
|
@ -99,6 +101,114 @@ type Encoder interface {
|
||||||
// character classes
|
// character classes
|
||||||
type MultiEncoder uint
|
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
|
// Encode takes a raw name and substitutes any reserved characters and
|
||||||
// patterns in it
|
// patterns in it
|
||||||
func (mask MultiEncoder) Encode(in string) string {
|
func (mask MultiEncoder) Encode(in string) string {
|
||||||
|
|
|
@ -1,12 +1,68 @@
|
||||||
package encoder
|
package encoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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 {
|
type testCase struct {
|
||||||
mask uint
|
mask uint
|
||||||
in string
|
in string
|
||||||
|
|
Loading…
Add table
Reference in a new issue