yq/pkg/yqlib/encoder_sh.go

80 lines
1.8 KiB
Go

package yqlib
import (
"fmt"
"io"
"regexp"
"strings"
yaml "gopkg.in/yaml.v3"
)
var unsafeChars = regexp.MustCompile(`[^\w@%+=:,./-]`)
type shEncoder struct {
quoteAll bool
}
func NewShEncoder() Encoder {
return &shEncoder{false}
}
func (e *shEncoder) CanHandleAliases() bool {
return false
}
func (e *shEncoder) PrintDocumentSeparator(writer io.Writer) error {
return nil
}
func (e *shEncoder) PrintLeadingContent(writer io.Writer, content string) error {
return nil
}
func (e *shEncoder) Encode(writer io.Writer, originalNode *yaml.Node) error {
node := unwrapDoc(originalNode)
if guessTagFromCustomType(node) != "!!str" {
return fmt.Errorf("cannot encode %v as URI, can only operate on strings. Please first pipe through another encoding operator to convert the value to a string", node.Tag)
}
return writeString(writer, e.encode(originalNode.Value))
}
// put any (shell-unsafe) characters into a single-quoted block, close the block lazily
func (e *shEncoder) encode(input string) string {
const quote = '\''
var inQuoteBlock = false
var encoded strings.Builder
encoded.Grow(len(input))
for _, ir := range input {
// open or close a single-quote block
if ir == quote {
if inQuoteBlock {
// get out of a quote block for an input quote
encoded.WriteRune(quote)
inQuoteBlock = !inQuoteBlock
}
// escape the quote with a backslash
encoded.WriteRune('\\')
} else {
if e.shouldQuote(ir) && !inQuoteBlock {
// start a quote block for any (unsafe) characters
encoded.WriteRune(quote)
inQuoteBlock = !inQuoteBlock
}
}
// pass on the input character
encoded.WriteRune(ir)
}
// close any pending quote block
if inQuoteBlock {
encoded.WriteRune(quote)
}
return encoded.String()
}
func (e *shEncoder) shouldQuote(ir rune) bool {
return e.quoteAll || unsafeChars.MatchString(string(ir))
}