mirror of https://github.com/pulumi/pulumi.git
179 lines
4.3 KiB
Go
179 lines
4.3 KiB
Go
// Copyright 2016-2024, Pulumi Corporation.
|
|
//
|
|
// 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 property
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
propPathStartNameOrIndexErr = fmt.Errorf("expected property path to start with a name or index")
|
|
propPathEndNameOrIndexErr = fmt.Errorf("expected property path to end with a name or index")
|
|
propNameAfterDotErr = fmt.Errorf("expected property name after '.'")
|
|
missingClosingQuoteErr = fmt.Errorf("missing closing quote in property name")
|
|
)
|
|
|
|
type unknownEscapeSequenceError struct {
|
|
found rune
|
|
}
|
|
|
|
func (err unknownEscapeSequenceError) Error() string {
|
|
return fmt.Sprintf(`unknown escape sequence: \%c`, err.found)
|
|
}
|
|
|
|
func pathParse(path []rune) (elements []any, isGlob bool, _ error) {
|
|
if len(path) > 0 && path[0] == '.' {
|
|
return nil, false, propPathStartNameOrIndexErr
|
|
}
|
|
|
|
for len(path) > 0 {
|
|
switch path[0] {
|
|
case '.':
|
|
path = path[1:]
|
|
if len(path) == 0 {
|
|
return nil, false, propPathEndNameOrIndexErr
|
|
}
|
|
if path[0] == '[' {
|
|
return nil, false, propNameAfterDotErr
|
|
}
|
|
case '[':
|
|
path = path[1:]
|
|
if len(path) == 0 {
|
|
return nil, false, errors.New("incomplete index")
|
|
}
|
|
if path[0] == '"' {
|
|
path = path[1:]
|
|
var element strings.Builder
|
|
i := 0
|
|
for {
|
|
if i >= len(path) {
|
|
return nil, false, missingClosingQuoteErr
|
|
} else if path[i] == '"' {
|
|
// We have found the closing element, finish the element and break
|
|
elements = append(elements, element.String())
|
|
path = path[i+1:]
|
|
break
|
|
} else if path[i] == '\\' {
|
|
// An invalid escape:
|
|
//
|
|
// We will return missing closing bracket
|
|
if i+1 >= len(path) {
|
|
break
|
|
}
|
|
switch path[i+1] {
|
|
case '"':
|
|
element.WriteRune(path[i+1])
|
|
default:
|
|
return nil, false, unknownEscapeSequenceError{path[i+1]}
|
|
}
|
|
i += 2
|
|
} else {
|
|
element.WriteRune(path[i])
|
|
i++
|
|
}
|
|
}
|
|
|
|
if len(path) == 0 || path[0] != ']' {
|
|
return nil, false, errors.New("missing closing bracket in property access")
|
|
}
|
|
path = path[1:]
|
|
} else {
|
|
idx := slices.Index(path, ']')
|
|
if idx == -1 {
|
|
return nil, false, errors.New("missing closing bracket in index")
|
|
} else if idx == 0 {
|
|
return nil, false, errors.New("missing index value")
|
|
}
|
|
|
|
segment := string(path[:idx])
|
|
path = path[idx+1:]
|
|
|
|
if segment == "*" {
|
|
isGlob = true
|
|
elements = append(elements, glob)
|
|
} else {
|
|
index, err := strconv.ParseInt(segment, 10, 0)
|
|
if err != nil {
|
|
return nil, false, fmt.Errorf("invalid array index: %w", err)
|
|
}
|
|
elements = append(elements, int(index))
|
|
}
|
|
}
|
|
|
|
default: // A index path (.value)
|
|
i := slices.IndexFunc(path, func(c rune) bool {
|
|
return c == '.' || c == '['
|
|
})
|
|
if i == -1 {
|
|
i = len(path)
|
|
}
|
|
elements = append(elements, string(path[:i]))
|
|
path = path[i:]
|
|
}
|
|
}
|
|
|
|
return elements, isGlob, nil
|
|
}
|
|
|
|
func pathString(p []any) string {
|
|
var b strings.Builder
|
|
for i, e := range p {
|
|
switch e := e.(type) {
|
|
case int:
|
|
b.WriteRune('[')
|
|
b.WriteString(strconv.Itoa(e))
|
|
b.WriteRune(']')
|
|
case string:
|
|
if strings.ContainsAny(e, ".[]\"") {
|
|
b.WriteString(fmt.Sprintf("[%#v]", e))
|
|
} else {
|
|
if i != 0 {
|
|
b.WriteRune('.')
|
|
}
|
|
|
|
b.WriteString(e)
|
|
}
|
|
case Glob:
|
|
b.WriteString("[*]")
|
|
default:
|
|
panic(fmt.Sprintf("Invalid path element of type %T", e))
|
|
}
|
|
}
|
|
return b.String()
|
|
|
|
}
|
|
|
|
func pathGoString(starter string, p []any) string {
|
|
var b strings.Builder
|
|
b.WriteString(starter)
|
|
for _, e := range p {
|
|
switch e := e.(type) {
|
|
case int:
|
|
b.WriteString(fmt.Sprintf(".Index(%#v)", e))
|
|
case string:
|
|
b.WriteString(fmt.Sprintf(".Field(%#v)", e))
|
|
case Glob:
|
|
b.WriteString(".Glob()")
|
|
default:
|
|
panic(fmt.Sprintf("Invalid path element of type %T", e))
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|