wfn/fsb.go (141 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package wfn
import (
"fmt"
"strings"
"unicode"
)
// BindToFmtString binds WFN to formatted string
func (a Attributes) BindToFmtString() string {
parts := make([]string, 11)
for i, s := range []string{
a.Part,
a.Vendor,
a.Product,
a.Version,
a.Update,
a.Edition,
a.Language,
a.SWEdition,
a.TargetSW,
a.TargetHW,
a.Other,
} {
parts[i] = bindValueFS(s)
}
return fsbPrefix + strings.Join(parts, ":")
}
// UnbindFmtString loads WFN from formatted string
func UnbindFmtString(s string) (*Attributes, error) {
if !strings.HasPrefix(s, fsbPrefix) {
return nil, fmt.Errorf("bad prefix in FSB %q", s)
}
attr := &Attributes{}
for i, partN := len(fsbPrefix), 0; i < len(s); i, partN = i+1, partN+1 {
var err error
switch partN {
case 0:
attr.Part, i, err = unbindValueFSAt(s, i)
case 1:
attr.Vendor, i, err = unbindValueFSAt(s, i)
case 2:
attr.Product, i, err = unbindValueFSAt(s, i)
case 3:
attr.Version, i, err = unbindValueFSAt(s, i)
case 4:
attr.Update, i, err = unbindValueFSAt(s, i)
case 5:
attr.Edition, i, err = unbindValueFSAt(s, i)
case 6:
attr.Language, i, err = unbindValueFSAt(s, i)
case 7:
attr.SWEdition, i, err = unbindValueFSAt(s, i)
case 8:
attr.TargetSW, i, err = unbindValueFSAt(s, i)
case 9:
attr.TargetHW, i, err = unbindValueFSAt(s, i)
case 10:
attr.Other, i, err = unbindValueFSAt(s, i)
}
if err != nil {
return nil, fmt.Errorf("unbind formatted string: %v", err)
}
}
return attr, nil
}
// StripSlashes removes escaping of punctuation characters from attribute value
func StripSlashes(s string) string {
out := make([]byte, 0, len(s)) // might be more than we need, but no reallocs
for i := 0; i < len(s); i++ {
if s[i] == '\\' && i < len(s)-1 {
switch s[i+1] {
case '.', '_', '-': // these pass unquoted
continue
}
}
out = append(out, byte(s[i]))
}
return string(out)
}
func bindValueFS(s string) string {
switch s {
case Any:
return "*"
case NA:
return "-"
default:
return StripSlashes(s)
}
}
func unbindValueFSAt(s string, at int) (string, int, error) {
if len(s)-at < 1 || s[at] == ':' {
return Any, at, fmt.Errorf("could not unbind attribute at pos %d", at)
}
if len(s)-at == 1 || s[at+1] == ':' {
switch s[at] {
case '*':
return Any, at + 1, nil
case '-':
return NA, at + 1, nil
default:
return s[at : at+1], at + 1, nil
}
}
return addSlashesAt(s, at)
}
func addSlashesAt(s string, at int) (string, int, error) {
b := make([]byte, 0, len(s)*2) // assume a quote for every character
embedded := false
i := at
for ; i < len(s) && s[i] != ':'; i++ {
c := s[i]
if unicode.IsLetter(rune(c)) || unicode.IsDigit(rune(c)) || c == '_' {
b = append(b, c)
embedded = true
continue
}
switch c {
case '\\':
i++
if i == len(s) {
return "", i, fmt.Errorf("unquoted '\\' at the end of the FSB fragment: %q", s)
}
b = append(b, c, s[i])
embedded = true
case '*':
// An unquoted asterisk must appear at the beginning or end of the string
if i != at && i != len(s)-1 && s[i+1] != ':' {
return Any, i, fmt.Errorf("unquoted '*' inside the FSB fragment: %q", s)
}
b = append(b, c)
embedded = true
case '?':
if !(i == at || i == len(s)-1 || s[i+1] == ':' || // at the beginning or at the end of the string
(!embedded && i > 0 && s[i-1] == c || // not embedded and preceded by the same symbol
(embedded && s[i+1] == c))) { // embedded and followed by the same symbol
return Any, i, fmt.Errorf("unquoted '?' inside the FSB fragment %q (%t, %d)", s, embedded, i)
}
b = append(b, c)
embedded = false
default:
b = append(b, '\\', c)
embedded = true
}
}
return string(append([]byte{}, b...)), i, nil
}