mirror of
https://github.com/rclone/rclone.git
synced 2024-11-28 19:24:15 +08:00
lib/encoder: add string rendering and parsing for encodings
This commit is contained in:
parent
1ba5e99152
commit
0a5c83ece1
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user