package streams import ( "fmt" "sort" "strings" "unicode" ) func quote(text string) string { text = strings.Replace(text, "\\", "\\\\", -1) text = strings.Replace(text, "'", "\\'", -1) text = "'" + text + "'" return text } func unquote(text string) string { if strings.HasPrefix(text, "'") && strings.HasSuffix(text, "'") { text = text[1 : len(text)-1] text = strings.Replace(text, "\\'", "'", -1) text = strings.Replace(text, "\\\\", "\\", -1) } return text } type Metadata map[string]string func (meta Metadata) sortedKeys() []string { keys := make([]string, len(meta)) i := 0 for key, _ := range meta { keys[i] = key i++ } sort.Strings(keys) return keys } func decodeMetadataItem(text string, metadata *Metadata) (err error) { parts := strings.SplitN(text, "=", 2) if len(parts) < 2 { err = fmt.Errorf("expected key=value but only got key (in %q)", text) return } parts[1] = unquote(parts[1]) (*metadata)[parts[0]] = parts[1] return } func DecodeMetadata(source string) (meta Metadata, err error) { // name='value'; name='value';name='value'; meta = make(Metadata) escape := false quoted := false fieldsFunc := func(c rune) (retval bool) { retval = false if escape { escape = false } else { switch { case unicode.IsSpace(c) && !quoted: retval = true return case c == '\'': quoted = !quoted case c == '\\' && quoted: escape = true case c == ';' && !quoted: retval = true return } } return } for _, field := range strings.FieldsFunc(source, fieldsFunc) { if len(field) == 0 { continue } if err = decodeMetadataItem(field, &meta); err != nil { return } } return } func (meta Metadata) String() string { mstr := "" if meta != nil { for _, key := range meta.sortedKeys() { value := meta[key] mstr += fmt.Sprintf("%s=%s;", key, quote(value)) } } return mstr } func (meta Metadata) Bytes() (buf []byte) { return []byte(meta.String()) }