Josh SorefSpelling fixesd0888ed6 years agoHistory
1159 lines · 26.8 KB · Go
RawDownload
/*Translate file names for usage on restrictive storage systems The restricted set of characters are mapped to a unicode equivalent version(most to their FULLWIDTH variant) to increase compatibility with otherstorage systems.See: http://unicode-search.net/unicode-namesearch.pl?term=FULLWIDTH Encoders will also quote reserved characters to differentiate betweenthe raw and encoded forms.*/ package encoder import (	"bytes"	"fmt"	"io"	"sort"	"strconv"	"strings"	"unicode/utf8") const (	// adding this to any printable ASCII character turns it into the	// FULLWIDTH variant	fullOffset = 0xFEE0	// the first rune of the SYMBOL FOR block for control characters	symbolOffset = '␀' // SYMBOL FOR NULL	// QuoteRune is the rune used for quoting reserved characters	QuoteRune = '‛' // SINGLE HIGH-REVERSED-9 QUOTATION MARK) // 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 MultiEncoderconst (	EncodeZero          MultiEncoder = 0         // NUL(0x00)	EncodeSlash         MultiEncoder = 1 << iota // /	EncodeLtGt                                   // <>	EncodeDoubleQuote                            // "	EncodeSingleQuote                            // '	EncodeBackQuote                              // `	EncodeDollar                                 // $	EncodeColon                                  // :	EncodeQuestion                               // ?	EncodeAsterisk                               // *	EncodePipe                                   // |	EncodeHash                                   // #	EncodePercent                                // %	EncodeBackSlash                              // \	EncodeCrLf                                   // CR(0x0D), LF(0x0A)	EncodeDel                                    // DEL(0x7F)	EncodeCtl                                    // CTRL(0x01-0x1F)	EncodeLeftSpace                              // Leading SPACE	EncodeLeftPeriod                             // Leading .	EncodeLeftTilde                              // Leading ~	EncodeLeftCrLfHtVt                           // Leading CR LF HT VT	EncodeRightSpace                             // Trailing SPACE	EncodeRightPeriod                            // Trailing .	EncodeRightCrLfHtVt                          // Trailing CR LF HT VT	EncodeInvalidUtf8                            // Invalid UTF-8 bytes	EncodeDot                                    // . and .. names 	// Synthetic	EncodeWin         = EncodeColon | EncodeQuestion | EncodeDoubleQuote | EncodeAsterisk | EncodeLtGt | EncodePipe // :?"*<>|	EncodeHashPercent = EncodeHash | EncodePercent                                                                  // #%) // Has returns true if flag is contained in maskfunc (mask MultiEncoder) Has(flag MultiEncoder) bool {	return mask&flag != 0} // Encoder can transform names to and from the original and translated version.type Encoder interface {	// Encode takes a raw name and substitutes any reserved characters and	// patterns in it	Encode(string) string	// Decode takes a name and undoes any substitutions made by Encode	Decode(string) string 	// FromStandardPath takes a / separated path in Standard encoding	// and converts it to a / separated path in this encoding.	FromStandardPath(string) string	// FromStandardName takes name in Standard encoding and converts	// it in this encoding.	FromStandardName(string) string	// ToStandardPath takes a / separated path in this encoding	// and converts it to a / separated path in Standard encoding.	ToStandardPath(string) string	// ToStandardName takes name in this encoding and converts	// it in Standard encoding.	ToStandardName(string) string} // MultiEncoder is a configurable Encoder. The Encode* constants in this// package can be combined using bitwise or (|) to enable handling of multiple// character classestype MultiEncoder uint // Aliases maps encodings to names and vice versavar (	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 stringsfunc 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 textfunc (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 MultiEncoderfunc (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 satisfy the pflag.Value interfacefunc (mask MultiEncoder) Type() string {	return "Encoding"} // Scan implements the fmt.Scanner interfacefunc (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 itfunc (mask MultiEncoder) Encode(in string) string {	if in == "" {		return ""	} 	if mask.Has(EncodeDot) {		switch in {		case ".":			return "."		case "..":			return ".."		case ".":			return string(QuoteRune) + "."		case "..":			return string(QuoteRune) + "." + string(QuoteRune) + "."		}	} 	// handle prefix only replacements	prefix := ""	if mask.Has(EncodeLeftSpace) { // Leading SPACE		if in[0] == ' ' {			prefix, in = "␠", in[1:] // SYMBOL FOR SPACE		} else if r, l := utf8.DecodeRuneInString(in); r == '␠' { // SYMBOL FOR SPACE			prefix, in = string(QuoteRune)+"␠", in[l:] // SYMBOL FOR SPACE		}	}	if mask.Has(EncodeLeftPeriod) && prefix == "" { // Leading PERIOD		if in[0] == '.' {			prefix, in = ".", in[1:] // FULLWIDTH FULL STOP		} else if r, l := utf8.DecodeRuneInString(in); r == '.' { // FULLWIDTH FULL STOP			prefix, in = string(QuoteRune)+".", in[l:] //  FULLWIDTH FULL STOP		}	}	if mask.Has(EncodeLeftTilde) && prefix == "" { // Leading ~		if in[0] == '~' {			prefix, in = string('~'+fullOffset), in[1:] // FULLWIDTH TILDE		} else if r, l := utf8.DecodeRuneInString(in); r == '~'+fullOffset {			prefix, in = string(QuoteRune)+string('~'+fullOffset), in[l:] // FULLWIDTH TILDE		}	}	if mask.Has(EncodeLeftCrLfHtVt) && prefix == "" { // Leading CR LF HT VT		switch c := in[0]; c {		case '\t', '\n', '\v', '\r':			prefix, in = string('␀'+rune(c)), in[1:] // SYMBOL FOR NULL		default:			switch r, l := utf8.DecodeRuneInString(in); r {			case '␀' + '\t', '␀' + '\n', '␀' + '\v', '␀' + '\r':				prefix, in = string(QuoteRune)+string(r), in[l:]			}		}	}	// handle suffix only replacements	suffix := ""	if in != "" {		if mask.Has(EncodeRightSpace) { // Trailing SPACE			if in[len(in)-1] == ' ' {				suffix, in = "␠", in[:len(in)-1] // SYMBOL FOR SPACE			} else if r, l := utf8.DecodeLastRuneInString(in); r == '␠' {				suffix, in = string(QuoteRune)+"␠", in[:len(in)-l] // SYMBOL FOR SPACE			}		}		if mask.Has(EncodeRightPeriod) && suffix == "" { // Trailing .			if in[len(in)-1] == '.' {				suffix, in = ".", in[:len(in)-1] // FULLWIDTH FULL STOP			} else if r, l := utf8.DecodeLastRuneInString(in); r == '.' {				suffix, in = string(QuoteRune)+".", in[:len(in)-l] // FULLWIDTH FULL STOP			}		}		if mask.Has(EncodeRightCrLfHtVt) && suffix == "" { // Trailing .			switch c := in[len(in)-1]; c {			case '\t', '\n', '\v', '\r':				suffix, in = string('␀'+rune(c)), in[:len(in)-1] // FULLWIDTH FULL STOP			default:				switch r, l := utf8.DecodeLastRuneInString(in); r {				case '␀' + '\t', '␀' + '\n', '␀' + '\v', '␀' + '\r':					suffix, in = string(QuoteRune)+string(r), in[:len(in)-l]				}			}		}	} 	index := 0	if prefix == "" && suffix == "" {		// find the first rune which (most likely) needs to be replaced		index = strings.IndexFunc(in, func(r rune) bool {			switch r {			case 0, '␀', QuoteRune, utf8.RuneError:				return true			}			if mask.Has(EncodeAsterisk) { // *				switch r {				case '*',					'*':					return true				}			}			if mask.Has(EncodeLtGt) { // <>				switch r {				case '<', '>',					'<', '>':					return true				}			}			if mask.Has(EncodeQuestion) { // ?				switch r {				case '?',					'?':					return true				}			}			if mask.Has(EncodeColon) { // :				switch r {				case ':',					':':					return true				}			}			if mask.Has(EncodePipe) { // |				switch r {				case '|',					'|':					return true				}			}			if mask.Has(EncodeDoubleQuote) { // "				switch r {				case '"',					'"':					return true				}			}			if mask.Has(EncodeSingleQuote) { // '				switch r {				case '\'',					''':					return true				}			}			if mask.Has(EncodeBackQuote) { // `				switch r {				case '`',					'`':					return true				}			}			if mask.Has(EncodeDollar) { // $				switch r {				case '$',					'$':					return true				}			}			if mask.Has(EncodeSlash) { // /				switch r {				case '/',					'/':					return true				}			}			if mask.Has(EncodeBackSlash) { // \				switch r {				case '\\',					'\':					return true				}			}			if mask.Has(EncodeCrLf) { // CR LF				switch r {				case rune(0x0D), rune(0x0A),					'␍', '␊':					return true				}			}			if mask.Has(EncodeHash) { // #				switch r {				case '#',					'#':					return true				}			}			if mask.Has(EncodePercent) { // %				switch r {				case '%',					'%':					return true				}			}			if mask.Has(EncodeDel) { // DEL(0x7F)				switch r {				case rune(0x7F), '␡':					return true				}			}			if mask.Has(EncodeCtl) { // CTRL(0x01-0x1F)				if r >= 1 && r <= 0x1F {					return true				} else if r > symbolOffset && r <= symbolOffset+0x1F {					return true				}			}			return false		})	}	// nothing to replace, return input	if index == -1 {		return in	} 	var out bytes.Buffer	out.Grow(len(in) + len(prefix) + len(suffix))	out.WriteString(prefix)	// copy the clean part of the input and skip it	out.WriteString(in[:index])	in = in[index:] 	for i, r := range in {		switch r {		case 0:			out.WriteRune(symbolOffset)			continue		case '␀', QuoteRune:			out.WriteRune(QuoteRune)			out.WriteRune(r)			continue		case utf8.RuneError:			if mask.Has(EncodeInvalidUtf8) {				// only encode invalid sequences and not utf8.RuneError				if i+3 > len(in) || in[i:i+3] != string(utf8.RuneError) {					_, l := utf8.DecodeRuneInString(in[i:])					appendQuotedBytes(&out, in[i:i+l])					continue				}			} else {				// append the real bytes instead of utf8.RuneError				_, l := utf8.DecodeRuneInString(in[i:])				out.WriteString(in[i : i+l])				continue			}		}		if mask.Has(EncodeAsterisk) { // *			switch r {			case '*':				out.WriteRune(r + fullOffset)				continue			case '*':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodeLtGt) { // <>			switch r {			case '<', '>':				out.WriteRune(r + fullOffset)				continue			case '<', '>':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodeQuestion) { // ?			switch r {			case '?':				out.WriteRune(r + fullOffset)				continue			case '?':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodeColon) { // :			switch r {			case ':':				out.WriteRune(r + fullOffset)				continue			case ':':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodePipe) { // |			switch r {			case '|':

Lines 1–500 of 1159.