blob: 178f740a7b928ea3c9317084d9d0c040e74146d5 [file]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// 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 res
import (
"fmt"
"strings"
rdpb "src/tools/ak/res/proto/res_data_go_proto"
rmpb "src/tools/ak/res/proto/res_meta_go_proto"
"google.golang.org/protobuf/proto"
)
// FullyQualifiedName represents the components of a name.
type FullyQualifiedName struct {
Package string
Type Type
Name string
}
// ValuesResource represents a resource element.
type ValuesResource struct {
Src *PathInfo
N FullyQualifiedName
Payload []byte
}
// SetResource sets all the name related fields on the top level resource proto.
func (f FullyQualifiedName) SetResource(r *rdpb.Resource) error {
rt, err := f.Type.Enum()
if err != nil {
return err
}
r.ResourceType = rt
r.Name = protoNameSanitizer.Replace(f.Name)
return nil
}
// SetMetaData sets all name related fields for this style on a StyleableMetaData proto
func (f FullyQualifiedName) SetMetaData(md *rmpb.StyleableMetaData) error {
if f.Type != Styleable {
return ErrWrongType
}
md.Name = proto.String(protoNameSanitizer.Replace(f.Name))
return nil
}
var (
protoNameSanitizer = strings.NewReplacer(".", "_")
javaNameSanitizer = strings.NewReplacer(":", "_", ".", "_")
)
// JavaName returns a version of the FullyQualifiedName that should be used for resource identifier fields.
func (f FullyQualifiedName) JavaName() (string, error) {
if !f.Type.IsReal() {
return "", ErrWrongType
}
return javaNameSanitizer.Replace(f.Name), nil
}
// StyleableAttrName creates the java identifier for referencing this attribute in the given
// style.
func StyleableAttrName(styleable, attr FullyQualifiedName) (string, error) {
if styleable.Type != Styleable || attr.Type != Attr {
return "", ErrWrongType
}
js, err := styleable.JavaName()
if err != nil {
return "", err
}
ja, err := attr.JavaName()
if err != nil {
return "", err
}
if attr.Package == "android" {
return fmt.Sprintf("%s_android_%s", js, ja), nil
}
return fmt.Sprintf("%s_%s", js, ja), nil
}
// ParseName is given a name string and optional context about the name (what type the name may be)
// and attempts to extract the local name, Type, and package from the unparsed input. The format of
// unparsed names is flexible and not well specified.
// A FullyQualifiedName's String method will emit pkg:type/name which every tool understands, but
// ParseName will encounter input like ?type:pkg/name - an undocumented, but legal way to specify a
// reference to a style. If unparsed is so mangled that a legal name cannot possibly be determined,
// it will return an error.
func ParseName(unparsed string, resType Type) (FullyQualifiedName, error) {
fqn := removeRef(unparsed)
fqn.Type = resType
pkgIdx := strings.Index(fqn.Name, ":")
typeIdx := strings.Index(fqn.Name, "/")
if pkgIdx == 0 || typeIdx == 0 {
return FullyQualifiedName{}, fmt.Errorf("malformed name %q - can not start with ':' or '/'", unparsed)
}
if typeIdx != -1 {
if pkgIdx != -1 {
if pkgIdx < typeIdx {
// Package, type and name (pkg:type/name)
t, err := ParseType(fqn.Name[pkgIdx+1 : typeIdx])
if err != nil {
// the name has illegal type in it that we'll never be able to scrub out.
return FullyQualifiedName{}, err
}
fqn.Type = t
fqn.Package = fqn.Name[:pkgIdx]
fqn.Name = fqn.Name[typeIdx+1:]
} else {
// Package, type and name, type and package swapped (type:pkg/name)
t, err := ParseType(fqn.Name[:typeIdx])
if err != nil {
// the name has illegal type in it that we'll never be able to scrub out.
return FullyQualifiedName{}, err
}
fqn.Type = t
fqn.Package = fqn.Name[typeIdx+1 : pkgIdx]
fqn.Name = fqn.Name[pkgIdx+1:]
}
} else {
// Only type and name (type/name)
t, err := ParseType(fqn.Name[:typeIdx])
if err != nil {
// the name has illegal type in it that we'll never be able to scrub out.
return FullyQualifiedName{}, err
}
fqn.Type = t
fqn.Name = fqn.Name[typeIdx+1:]
}
} else {
// Only package and name (pkg:name)
if pkgIdx != -1 {
fqn.Package = fqn.Name[:pkgIdx]
fqn.Name = fqn.Name[pkgIdx+1:]
}
}
if fqn.Package == "" {
fqn.Package = "res-auto"
}
if fqn.Type == UnknownType {
return FullyQualifiedName{}, fmt.Errorf("cannot determine type from %q and %v - not a valid name", unparsed, resType)
}
if fqn.Name == "" {
return FullyQualifiedName{}, fmt.Errorf("cannot determine name from %q and %v - not a valid name", unparsed, resType)
}
return fqn, nil
}
func removeRef(unparsed string) (fqn FullyQualifiedName) {
fqn.Name = unparsed
if len(fqn.Name) > 2 && (strings.HasPrefix(fqn.Name, "@") || strings.HasPrefix(fqn.Name, "?")) {
fqn.Name = fqn.Name[1:]
}
return
}
func (f FullyQualifiedName) String() string {
return fmt.Sprintf("%s:%s/%s", f.Package, f.Type, f.Name)
}