aboutsummaryrefslogtreecommitdiff
path: root/internal/scfg
diff options
context:
space:
mode:
authorRunxi Yu <me@runxiyu.org>2025-04-06 09:26:46 +0800
committerRunxi Yu <me@runxiyu.org>2025-04-06 09:27:53 +0800
commitda1d8f4e7c332c7109427915e6459b10209cedce (patch)
tree280b921be3b51f93d82d916b4eaa89387b7102cc /internal/scfg
parentgit2c, git2d: Rename cmd1 and cmd2 descriptively (diff)
downloadforge-0.1.32.tar.gz
forge-0.1.32.tar.zst
forge-0.1.32.zip
Move the Go stuff to ./forged/v0.1.32
Diffstat (limited to 'internal/scfg')
-rw-r--r--internal/scfg/.golangci.yaml25
-rw-r--r--internal/scfg/reader.go157
-rw-r--r--internal/scfg/scfg.go59
-rw-r--r--internal/scfg/struct.go82
-rw-r--r--internal/scfg/unmarshal.go375
-rw-r--r--internal/scfg/writer.go112
6 files changed, 0 insertions, 810 deletions
diff --git a/internal/scfg/.golangci.yaml b/internal/scfg/.golangci.yaml
deleted file mode 100644
index 7db9793..0000000
--- a/internal/scfg/.golangci.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-linters:
- enable-all: true
- disable:
- - perfsprint
- - wsl
- - varnamelen
- - nlreturn
- - exhaustruct
- - wrapcheck
- - lll
- - exhaustive
- - intrange
- - godox
- - nestif
- - err113
- - staticcheck
- - errorlint
- - cyclop
- - nonamedreturns
- - funlen
- - gochecknoglobals
-
-issues:
- max-issues-per-linter: 0
- max-same-issues: 0
diff --git a/internal/scfg/reader.go b/internal/scfg/reader.go
deleted file mode 100644
index 6a2bedc..0000000
--- a/internal/scfg/reader.go
+++ /dev/null
@@ -1,157 +0,0 @@
-// SPDX-License-Identifier: MIT
-// SPDX-FileCopyrightText: Copyright (c) 2020 Simon Ser <https://emersion.fr>
-
-package scfg
-
-import (
- "bufio"
- "fmt"
- "io"
- "os"
- "strings"
-)
-
-// This limits the max block nesting depth to prevent stack overflows.
-const maxNestingDepth = 1000
-
-// Load loads a configuration file.
-func Load(path string) (Block, error) {
- f, err := os.Open(path)
- if err != nil {
- return nil, err
- }
- defer f.Close()
-
- return Read(f)
-}
-
-// Read parses a configuration file from an io.Reader.
-func Read(r io.Reader) (Block, error) {
- scanner := bufio.NewScanner(r)
-
- dec := decoder{scanner: scanner}
- block, closingBrace, err := dec.readBlock()
- if err != nil {
- return nil, err
- } else if closingBrace {
- return nil, fmt.Errorf("line %v: unexpected '}'", dec.lineno)
- }
-
- return block, scanner.Err()
-}
-
-type decoder struct {
- scanner *bufio.Scanner
- lineno int
- blockDepth int
-}
-
-// readBlock reads a block. closingBrace is true if parsing stopped on '}'
-// (otherwise, it stopped on Scanner.Scan).
-func (dec *decoder) readBlock() (block Block, closingBrace bool, err error) {
- dec.blockDepth++
- defer func() {
- dec.blockDepth--
- }()
-
- if dec.blockDepth >= maxNestingDepth {
- return nil, false, fmt.Errorf("exceeded max block depth")
- }
-
- for dec.scanner.Scan() {
- dec.lineno++
-
- l := dec.scanner.Text()
- words, err := splitWords(l)
- if err != nil {
- return nil, false, fmt.Errorf("line %v: %v", dec.lineno, err)
- } else if len(words) == 0 {
- continue
- }
-
- if len(words) == 1 && l[len(l)-1] == '}' {
- closingBrace = true
- break
- }
-
- var d *Directive
- if words[len(words)-1] == "{" && l[len(l)-1] == '{' {
- words = words[:len(words)-1]
-
- var name string
- params := words
- if len(words) > 0 {
- name, params = words[0], words[1:]
- }
-
- startLineno := dec.lineno
- childBlock, childClosingBrace, err := dec.readBlock()
- if err != nil {
- return nil, false, err
- } else if !childClosingBrace {
- return nil, false, fmt.Errorf("line %v: unterminated block", startLineno)
- }
-
- // Allows callers to tell apart "no block" and "empty block"
- if childBlock == nil {
- childBlock = Block{}
- }
-
- d = &Directive{Name: name, Params: params, Children: childBlock, lineno: dec.lineno}
- } else {
- d = &Directive{Name: words[0], Params: words[1:], lineno: dec.lineno}
- }
- block = append(block, d)
- }
-
- return block, closingBrace, nil
-}
-
-func splitWords(l string) ([]string, error) {
- var (
- words []string
- sb strings.Builder
- escape bool
- quote rune
- wantWSP bool
- )
- for _, ch := range l {
- switch {
- case escape:
- sb.WriteRune(ch)
- escape = false
- case wantWSP && (ch != ' ' && ch != '\t'):
- return words, fmt.Errorf("atom not allowed after quoted string")
- case ch == '\\':
- escape = true
- case quote != 0 && ch == quote:
- quote = 0
- wantWSP = true
- if sb.Len() == 0 {
- words = append(words, "")
- }
- case quote == 0 && len(words) == 0 && sb.Len() == 0 && ch == '#':
- return nil, nil
- case quote == 0 && (ch == '\'' || ch == '"'):
- if sb.Len() > 0 {
- return words, fmt.Errorf("quoted string not allowed after atom")
- }
- quote = ch
- case quote == 0 && (ch == ' ' || ch == '\t'):
- if sb.Len() > 0 {
- words = append(words, sb.String())
- }
- sb.Reset()
- wantWSP = false
- default:
- sb.WriteRune(ch)
- }
- }
- if quote != 0 {
- return words, fmt.Errorf("unterminated quoted string")
- }
- if sb.Len() > 0 {
- words = append(words, sb.String())
- }
- return words, nil
-}
diff --git a/internal/scfg/scfg.go b/internal/scfg/scfg.go
deleted file mode 100644
index 4533e63..0000000
--- a/internal/scfg/scfg.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// SPDX-License-Identifier: MIT
-// SPDX-FileCopyrightText: Copyright (c) 2020 Simon Ser <https://emersion.fr>
-
-// Package scfg parses and formats configuration files.
-// Note that this fork of scfg behaves differently from upstream scfg.
-package scfg
-
-import (
- "fmt"
-)
-
-// Block is a list of directives.
-type Block []*Directive
-
-// GetAll returns a list of directives with the provided name.
-func (blk Block) GetAll(name string) []*Directive {
- l := make([]*Directive, 0, len(blk))
- for _, child := range blk {
- if child.Name == name {
- l = append(l, child)
- }
- }
- return l
-}
-
-// Get returns the first directive with the provided name.
-func (blk Block) Get(name string) *Directive {
- for _, child := range blk {
- if child.Name == name {
- return child
- }
- }
- return nil
-}
-
-// Directive is a configuration directive.
-type Directive struct {
- Name string
- Params []string
-
- Children Block
-
- lineno int
-}
-
-// ParseParams extracts parameters from the directive. It errors out if the
-// user hasn't provided enough parameters.
-func (d *Directive) ParseParams(params ...*string) error {
- if len(d.Params) < len(params) {
- return fmt.Errorf("directive %q: want %v params, got %v", d.Name, len(params), len(d.Params))
- }
- for i, ptr := range params {
- if ptr == nil {
- continue
- }
- *ptr = d.Params[i]
- }
- return nil
-}
diff --git a/internal/scfg/struct.go b/internal/scfg/struct.go
deleted file mode 100644
index 98ec943..0000000
--- a/internal/scfg/struct.go
+++ /dev/null
@@ -1,82 +0,0 @@
-// SPDX-License-Identifier: MIT
-// SPDX-FileCopyrightText: Copyright (c) 2020 Simon Ser <https://emersion.fr>
-
-package scfg
-
-import (
- "fmt"
- "reflect"
- "strings"
- "sync"
-)
-
-// structInfo contains scfg metadata for structs.
-type structInfo struct {
- param int // index of field storing parameters
- children map[string]int // indices of fields storing child directives
-}
-
-var (
- structCacheMutex sync.Mutex
- structCache = make(map[reflect.Type]*structInfo)
-)
-
-func getStructInfo(t reflect.Type) (*structInfo, error) {
- structCacheMutex.Lock()
- defer structCacheMutex.Unlock()
-
- if info := structCache[t]; info != nil {
- return info, nil
- }
-
- info := &structInfo{
- param: -1,
- children: make(map[string]int),
- }
-
- for i := 0; i < t.NumField(); i++ {
- f := t.Field(i)
- if f.Anonymous {
- return nil, fmt.Errorf("scfg: anonymous struct fields are not supported")
- } else if !f.IsExported() {
- continue
- }
-
- tag := f.Tag.Get("scfg")
- parts := strings.Split(tag, ",")
- k, options := parts[0], parts[1:]
- if k == "-" {
- continue
- } else if k == "" {
- k = f.Name
- }
-
- isParam := false
- for _, opt := range options {
- switch opt {
- case "param":
- isParam = true
- default:
- return nil, fmt.Errorf("scfg: invalid option %q in struct tag", opt)
- }
- }
-
- if isParam {
- if info.param >= 0 {
- return nil, fmt.Errorf("scfg: param option specified multiple times in struct tag in %v", t)
- }
- if parts[0] != "" {
- return nil, fmt.Errorf("scfg: name must be empty when param option is specified in struct tag in %v", t)
- }
- info.param = i
- } else {
- if _, ok := info.children[k]; ok {
- return nil, fmt.Errorf("scfg: key %q specified multiple times in struct tag in %v", k, t)
- }
- info.children[k] = i
- }
- }
-
- structCache[t] = info
- return info, nil
-}
diff --git a/internal/scfg/unmarshal.go b/internal/scfg/unmarshal.go
deleted file mode 100644
index e9e1a52..0000000
--- a/internal/scfg/unmarshal.go
+++ /dev/null
@@ -1,375 +0,0 @@
-// SPDX-License-Identifier: MIT
-// SPDX-FileCopyrightText: Copyright (c) 2020 Simon Ser <https://emersion.fr>
-// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-
-package scfg
-
-import (
- "encoding"
- "fmt"
- "io"
- "reflect"
- "strconv"
-)
-
-// Decoder reads and decodes an scfg document from an input stream.
-type Decoder struct {
- r io.Reader
- unknownDirectives []*Directive
-}
-
-// NewDecoder returns a new decoder which reads from r.
-func NewDecoder(r io.Reader) *Decoder {
- return &Decoder{r: r}
-}
-
-// UnknownDirectives returns a slice of all unknown directives encountered
-// during Decode.
-func (dec *Decoder) UnknownDirectives() []*Directive {
- return dec.unknownDirectives
-}
-
-// Decode reads scfg document from the input and stores it in the value pointed
-// to by v.
-//
-// If v is nil or not a pointer, Decode returns an error.
-//
-// Blocks can be unmarshaled to:
-//
-// - Maps. Each directive is unmarshaled into a map entry. The map key must
-// be a string.
-// - Structs. Each directive is unmarshaled into a struct field.
-//
-// Duplicate directives are not allowed, unless the struct field or map value
-// is a slice of values representing a directive: structs or maps.
-//
-// Directives can be unmarshaled to:
-//
-// - Maps. The children block is unmarshaled into the map. Parameters are not
-// allowed.
-// - Structs. The children block is unmarshaled into the struct. Parameters
-// are allowed if one of the struct fields contains the "param" option in
-// its tag.
-// - Slices. Parameters are unmarshaled into the slice. Children blocks are
-// not allowed.
-// - Arrays. Parameters are unmarshaled into the array. The number of
-// parameters must match exactly the length of the array. Children blocks
-// are not allowed.
-// - Strings, booleans, integers, floating-point values, values implementing
-// encoding.TextUnmarshaler. Only a single parameter is allowed and is
-// unmarshaled into the value. Children blocks are not allowed.
-//
-// The decoding of each struct field can be customized by the format string
-// stored under the "scfg" key in the struct field's tag. The tag contains the
-// name of the field possibly followed by a comma-separated list of options.
-// The name may be empty in order to specify options without overriding the
-// default field name. As a special case, if the field name is "-", the field
-// is ignored. The "param" option specifies that directive parameters are
-// stored in this field (the name must be empty).
-func (dec *Decoder) Decode(v interface{}) error {
- block, err := Read(dec.r)
- if err != nil {
- return err
- }
-
- rv := reflect.ValueOf(v)
- if rv.Kind() != reflect.Ptr || rv.IsNil() {
- return fmt.Errorf("scfg: invalid value for unmarshaling")
- }
-
- return dec.unmarshalBlock(block, rv)
-}
-
-func (dec *Decoder) unmarshalBlock(block Block, v reflect.Value) error {
- v = unwrapPointers(v)
- t := v.Type()
-
- dirsByName := make(map[string][]*Directive, len(block))
- for _, dir := range block {
- dirsByName[dir.Name] = append(dirsByName[dir.Name], dir)
- }
-
- switch v.Kind() {
- case reflect.Map:
- if t.Key().Kind() != reflect.String {
- return fmt.Errorf("scfg: map key type must be string")
- }
- if v.IsNil() {
- v.Set(reflect.MakeMap(t))
- } else if v.Len() > 0 {
- clearMap(v)
- }
-
- for name, dirs := range dirsByName {
- mv := reflect.New(t.Elem()).Elem()
- if err := dec.unmarshalDirectiveList(dirs, mv); err != nil {
- return err
- }
- v.SetMapIndex(reflect.ValueOf(name), mv)
- }
-
- case reflect.Struct:
- si, err := getStructInfo(t)
- if err != nil {
- return err
- }
-
- seen := make(map[int]bool)
-
- for name, dirs := range dirsByName {
- fieldIndex, ok := si.children[name]
- if !ok {
- dec.unknownDirectives = append(dec.unknownDirectives, dirs...)
- continue
- }
- fv := v.Field(fieldIndex)
- if err := dec.unmarshalDirectiveList(dirs, fv); err != nil {
- return err
- }
- seen[fieldIndex] = true
- }
-
- for name, fieldIndex := range si.children {
- if fieldIndex == si.param {
- continue
- }
- if _, ok := seen[fieldIndex]; !ok {
- return fmt.Errorf("scfg: missing required directive %q", name)
- }
- }
-
- default:
- return fmt.Errorf("scfg: unsupported type for unmarshaling blocks: %v", t)
- }
-
- return nil
-}
-
-func (dec *Decoder) unmarshalDirectiveList(dirs []*Directive, v reflect.Value) error {
- v = unwrapPointers(v)
- t := v.Type()
-
- if v.Kind() != reflect.Slice || !isDirectiveType(t.Elem()) {
- if len(dirs) > 1 {
- return newUnmarshalDirectiveError(dirs[1], "directive must not be specified more than once")
- }
- return dec.unmarshalDirective(dirs[0], v)
- }
-
- sv := reflect.MakeSlice(t, len(dirs), len(dirs))
- for i, dir := range dirs {
- if err := dec.unmarshalDirective(dir, sv.Index(i)); err != nil {
- return err
- }
- }
- v.Set(sv)
- return nil
-}
-
-// isDirectiveType checks whether a type can only be unmarshaled as a
-// directive, not as a parameter. Accepting too many types here would result in
-// ambiguities, see:
-// https://lists.sr.ht/~emersion/public-inbox/%3C20230629132458.152205-1-contact%40emersion.fr%3E#%3Ch4Y2peS_YBqY3ar4XlmPDPiNBFpYGns3EBYUx3_6zWEhV2o8_-fBQveRujGADWYhVVCucHBEryFGoPtpC3d3mQ-x10pWnFogfprbQTSvtxc=@emersion.fr%3E
-func isDirectiveType(t reflect.Type) bool {
- for t.Kind() == reflect.Ptr {
- t = t.Elem()
- }
-
- textUnmarshalerType := reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
- if reflect.PtrTo(t).Implements(textUnmarshalerType) {
- return false
- }
-
- switch t.Kind() {
- case reflect.Struct, reflect.Map:
- return true
- default:
- return false
- }
-}
-
-func (dec *Decoder) unmarshalDirective(dir *Directive, v reflect.Value) error {
- v = unwrapPointers(v)
- t := v.Type()
-
- if v.CanAddr() {
- if _, ok := v.Addr().Interface().(encoding.TextUnmarshaler); ok {
- if len(dir.Children) != 0 {
- return newUnmarshalDirectiveError(dir, "directive requires zero children")
- }
- return unmarshalParamList(dir, v)
- }
- }
-
- switch v.Kind() {
- case reflect.Map:
- if len(dir.Params) > 0 {
- return newUnmarshalDirectiveError(dir, "directive requires zero parameters")
- }
- if err := dec.unmarshalBlock(dir.Children, v); err != nil {
- return err
- }
- case reflect.Struct:
- si, err := getStructInfo(t)
- if err != nil {
- return err
- }
-
- if si.param >= 0 {
- if err := unmarshalParamList(dir, v.Field(si.param)); err != nil {
- return err
- }
- } else {
- if len(dir.Params) > 0 {
- return newUnmarshalDirectiveError(dir, "directive requires zero parameters")
- }
- }
-
- if err := dec.unmarshalBlock(dir.Children, v); err != nil {
- return err
- }
- default:
- if len(dir.Children) != 0 {
- return newUnmarshalDirectiveError(dir, "directive requires zero children")
- }
- if err := unmarshalParamList(dir, v); err != nil {
- return err
- }
- }
- return nil
-}
-
-func unmarshalParamList(dir *Directive, v reflect.Value) error {
- switch v.Kind() {
- case reflect.Slice:
- t := v.Type()
- sv := reflect.MakeSlice(t, len(dir.Params), len(dir.Params))
- for i, param := range dir.Params {
- if err := unmarshalParam(param, sv.Index(i)); err != nil {
- return newUnmarshalParamError(dir, i, err)
- }
- }
- v.Set(sv)
- case reflect.Array:
- if len(dir.Params) != v.Len() {
- return newUnmarshalDirectiveError(dir, fmt.Sprintf("directive requires exactly %v parameters", v.Len()))
- }
- for i, param := range dir.Params {
- if err := unmarshalParam(param, v.Index(i)); err != nil {
- return newUnmarshalParamError(dir, i, err)
- }
- }
- default:
- if len(dir.Params) != 1 {
- return newUnmarshalDirectiveError(dir, "directive requires exactly one parameter")
- }
- if err := unmarshalParam(dir.Params[0], v); err != nil {
- return newUnmarshalParamError(dir, 0, err)
- }
- }
-
- return nil
-}
-
-func unmarshalParam(param string, v reflect.Value) error {
- v = unwrapPointers(v)
- t := v.Type()
-
- // TODO: improve our logic following:
- // https://cs.opensource.google/go/go/+/refs/tags/go1.21.5:src/encoding/json/decode.go;drc=b9b8cecbfc72168ca03ad586cc2ed52b0e8db409;l=421
- if v.CanAddr() {
- if v, ok := v.Addr().Interface().(encoding.TextUnmarshaler); ok {
- return v.UnmarshalText([]byte(param))
- }
- }
-
- switch v.Kind() {
- case reflect.String:
- v.Set(reflect.ValueOf(param))
- case reflect.Bool:
- switch param {
- case "true":
- v.Set(reflect.ValueOf(true))
- case "false":
- v.Set(reflect.ValueOf(false))
- default:
- return fmt.Errorf("invalid bool parameter %q", param)
- }
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- i, err := strconv.ParseInt(param, 10, t.Bits())
- if err != nil {
- return fmt.Errorf("invalid %v parameter: %v", t, err)
- }
- v.Set(reflect.ValueOf(i).Convert(t))
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- u, err := strconv.ParseUint(param, 10, t.Bits())
- if err != nil {
- return fmt.Errorf("invalid %v parameter: %v", t, err)
- }
- v.Set(reflect.ValueOf(u).Convert(t))
- case reflect.Float32, reflect.Float64:
- f, err := strconv.ParseFloat(param, t.Bits())
- if err != nil {
- return fmt.Errorf("invalid %v parameter: %v", t, err)
- }
- v.Set(reflect.ValueOf(f).Convert(t))
- default:
- return fmt.Errorf("unsupported type for unmarshaling parameter: %v", t)
- }
-
- return nil
-}
-
-func unwrapPointers(v reflect.Value) reflect.Value {
- for v.Kind() == reflect.Ptr {
- if v.IsNil() {
- v.Set(reflect.New(v.Type().Elem()))
- }
- v = v.Elem()
- }
- return v
-}
-
-func clearMap(v reflect.Value) {
- for _, k := range v.MapKeys() {
- v.SetMapIndex(k, reflect.Value{})
- }
-}
-
-type unmarshalDirectiveError struct {
- lineno int
- name string
- msg string
-}
-
-func newUnmarshalDirectiveError(dir *Directive, msg string) *unmarshalDirectiveError {
- return &unmarshalDirectiveError{
- name: dir.Name,
- lineno: dir.lineno,
- msg: msg,
- }
-}
-
-func (err *unmarshalDirectiveError) Error() string {
- return fmt.Sprintf("line %v, directive %q: %v", err.lineno, err.name, err.msg)
-}
-
-type unmarshalParamError struct {
- lineno int
- directive string
- paramIndex int
- err error
-}
-
-func newUnmarshalParamError(dir *Directive, paramIndex int, err error) *unmarshalParamError {
- return &unmarshalParamError{
- directive: dir.Name,
- lineno: dir.lineno,
- paramIndex: paramIndex,
- err: err,
- }
-}
-
-func (err *unmarshalParamError) Error() string {
- return fmt.Sprintf("line %v, directive %q, parameter %v: %v", err.lineno, err.directive, err.paramIndex+1, err.err)
-}
diff --git a/internal/scfg/writer.go b/internal/scfg/writer.go
deleted file mode 100644
index 02a07fe..0000000
--- a/internal/scfg/writer.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// SPDX-License-Identifier: MIT
-// SPDX-FileCopyrightText: Copyright (c) 2020 Simon Ser <https://emersion.fr>
-
-package scfg
-
-import (
- "errors"
- "io"
- "strings"
-)
-
-var errDirEmptyName = errors.New("scfg: directive with empty name")
-
-// Write writes a parsed configuration to the provided io.Writer.
-func Write(w io.Writer, blk Block) error {
- enc := newEncoder(w)
- err := enc.encodeBlock(blk)
- return err
-}
-
-// encoder write SCFG directives to an output stream.
-type encoder struct {
- w io.Writer
- lvl int
- err error
-}
-
-// newEncoder returns a new encoder that writes to w.
-func newEncoder(w io.Writer) *encoder {
- return &encoder{w: w}
-}
-
-func (enc *encoder) push() {
- enc.lvl++
-}
-
-func (enc *encoder) pop() {
- enc.lvl--
-}
-
-func (enc *encoder) writeIndent() {
- for i := 0; i < enc.lvl; i++ {
- enc.write([]byte("\t"))
- }
-}
-
-func (enc *encoder) write(p []byte) {
- if enc.err != nil {
- return
- }
- _, enc.err = enc.w.Write(p)
-}
-
-func (enc *encoder) encodeBlock(blk Block) error {
- for _, dir := range blk {
- if err := enc.encodeDir(*dir); err != nil {
- return err
- }
- }
- return enc.err
-}
-
-func (enc *encoder) encodeDir(dir Directive) error {
- if enc.err != nil {
- return enc.err
- }
-
- if dir.Name == "" {
- enc.err = errDirEmptyName
- return enc.err
- }
-
- enc.writeIndent()
- enc.write([]byte(maybeQuote(dir.Name)))
- for _, p := range dir.Params {
- enc.write([]byte(" "))
- enc.write([]byte(maybeQuote(p)))
- }
-
- if len(dir.Children) > 0 {
- enc.write([]byte(" {\n"))
- enc.push()
- if err := enc.encodeBlock(dir.Children); err != nil {
- return err
- }
- enc.pop()
-
- enc.writeIndent()
- enc.write([]byte("}"))
- }
- enc.write([]byte("\n"))
-
- return enc.err
-}
-
-const specialChars = "\"\\\r\n'{} \t"
-
-func maybeQuote(s string) string {
- if s == "" || strings.ContainsAny(s, specialChars) {
- var sb strings.Builder
- sb.WriteByte('"')
- for _, ch := range s {
- if strings.ContainsRune(`"\`, ch) {
- sb.WriteByte('\\')
- }
- sb.WriteRune(ch)
- }
- sb.WriteByte('"')
- return sb.String()
- }
- return s
-}