1049 lines · 23.7 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 compatability 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"	"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 // Possible flags for the MultiEncoderconst (	EncodeZero          uint = 0         // NUL(0x00)	EncodeSlash         uint = 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 uint) bool {	return uint(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 // 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 '|':				out.WriteRune(r + fullOffset)				continue			case '|':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodeDoubleQuote) { // "			switch r {			case '"':				out.WriteRune(r + fullOffset)				continue			case '"':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodeSingleQuote) { // '			switch r {			case '\'':				out.WriteRune(r + fullOffset)				continue			case ''':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodeBackQuote) { // `			switch r {			case '`':				out.WriteRune(r + fullOffset)				continue			case '`':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodeDollar) { // $			switch r {			case '$':				out.WriteRune(r + fullOffset)				continue			case '$':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodeSlash) { // /			switch r {			case '/':				out.WriteRune(r + fullOffset)				continue			case '/':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodeBackSlash) { // \			switch r {			case '\\':				out.WriteRune(r + fullOffset)				continue			case '\':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodeCrLf) { // CR LF			switch r {			case rune(0x0D), rune(0x0A):				out.WriteRune(r + symbolOffset)				continue			case '␍', '␊':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodeHash) { // #			switch r {			case '#':				out.WriteRune(r + fullOffset)				continue			case '#':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodePercent) { // %			switch r {			case '%':				out.WriteRune(r + fullOffset)				continue			case '%':				out.WriteRune(QuoteRune)				out.WriteRune(r)				continue			}		}		if mask.Has(EncodeDel) { // DEL(0x7F)			switch r {			case rune(0x7F):

Lines 1–500 of 1049.