2017-07-29 04:11:06 +00:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2021-10-30 02:35:13 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2017-07-29 04:11:06 +00:00
|
|
|
"unicode"
|
|
|
|
)
|
|
|
|
|
2018-11-06 22:31:35 +00:00
|
|
|
// SplitQuotedFields is like strings.Fields but ignores spaces inside areas surrounded
|
2017-07-29 04:11:06 +00:00
|
|
|
// by the specified quote character.
|
2022-06-17 17:08:11 +00:00
|
|
|
// To specify a single quote use backslash to escape it: \'
|
2017-07-29 04:11:06 +00:00
|
|
|
func SplitQuotedFields(in string, quote rune) []string {
|
|
|
|
type stateEnum int
|
|
|
|
const (
|
|
|
|
inSpace stateEnum = iota
|
|
|
|
inField
|
|
|
|
inQuote
|
|
|
|
inQuoteEscaped
|
|
|
|
)
|
|
|
|
state := inSpace
|
|
|
|
r := []string{}
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
|
|
|
for _, ch := range in {
|
|
|
|
switch state {
|
|
|
|
case inSpace:
|
|
|
|
if ch == quote {
|
|
|
|
state = inQuote
|
|
|
|
} else if !unicode.IsSpace(ch) {
|
|
|
|
buf.WriteRune(ch)
|
|
|
|
state = inField
|
|
|
|
}
|
|
|
|
|
|
|
|
case inField:
|
|
|
|
if ch == quote {
|
|
|
|
state = inQuote
|
|
|
|
} else if unicode.IsSpace(ch) {
|
|
|
|
r = append(r, buf.String())
|
|
|
|
buf.Reset()
|
2022-08-14 14:01:39 +00:00
|
|
|
state = inSpace
|
2017-07-29 04:11:06 +00:00
|
|
|
} else {
|
|
|
|
buf.WriteRune(ch)
|
|
|
|
}
|
|
|
|
|
|
|
|
case inQuote:
|
2023-11-22 17:07:08 +00:00
|
|
|
switch ch {
|
|
|
|
case quote:
|
2017-07-29 04:11:06 +00:00
|
|
|
state = inField
|
2023-11-22 17:07:08 +00:00
|
|
|
case '\\':
|
2017-07-29 04:11:06 +00:00
|
|
|
state = inQuoteEscaped
|
2023-11-22 17:07:08 +00:00
|
|
|
default:
|
2017-07-29 04:11:06 +00:00
|
|
|
buf.WriteRune(ch)
|
|
|
|
}
|
|
|
|
|
|
|
|
case inQuoteEscaped:
|
|
|
|
buf.WriteRune(ch)
|
|
|
|
state = inQuote
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-14 14:01:39 +00:00
|
|
|
if state == inField || buf.Len() != 0 {
|
2017-07-29 04:11:06 +00:00
|
|
|
r = append(r, buf.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
2021-10-30 02:35:13 +00:00
|
|
|
|
|
|
|
func ConfigureSetSimple(rest string, cfgname string, field reflect.Value) error {
|
|
|
|
simpleArg := func(typ reflect.Type) (reflect.Value, error) {
|
|
|
|
switch typ.Kind() {
|
|
|
|
case reflect.Int:
|
|
|
|
n, err := strconv.Atoi(rest)
|
|
|
|
if err != nil {
|
|
|
|
return reflect.ValueOf(nil), fmt.Errorf("argument to %q must be a number", cfgname)
|
|
|
|
}
|
|
|
|
if n < 0 {
|
|
|
|
return reflect.ValueOf(nil), fmt.Errorf("argument to %q must be a number greater than zero", cfgname)
|
|
|
|
}
|
|
|
|
return reflect.ValueOf(&n), nil
|
|
|
|
case reflect.Bool:
|
*: misc improvements to config command and substitute-path rules (#3335)
A series of interconnected changes to both the terminal command
'config', DAP command 'dlv config', quality of life improvements to how
substitute-path works, and better documentation.
- Let 'config substitute-path' show the current substitute path rules
- Add a -clear command to 'config substitute-path'
- Support 'config-debug-info-directories'
- rewrite SubstitutePath to be platform independent (see below)
- document path substitution more
Regarding the rewrite of SubstitutePath: the previous version used
runtime.GOOS and filepath.IsAbs to determine which filepath separator to use
and if matching should be case insensitive. This is wrong in all situations
where the client and server run on different OSes, when examining core files
and when cross-compilation is involved.
The new version of SubstitutePath checks the rules and the input path to
determine if Windows is involved in the process, if it looks like it is it
switches to case-insensitive matching. It uses a lax version of
filepath.IsAbs to determine if a path is absolute and tries to avoid having
to select a path separator as much as possible
Fixes #2891, #2890, #2889, #3179, #3332, #3343
2023-05-02 19:23:59 +00:00
|
|
|
if rest != "true" && rest != "false" {
|
|
|
|
return reflect.ValueOf(nil), fmt.Errorf("argument to %q must be true or false", cfgname)
|
|
|
|
}
|
2021-10-30 02:35:13 +00:00
|
|
|
v := rest == "true"
|
|
|
|
return reflect.ValueOf(&v), nil
|
|
|
|
case reflect.String:
|
2023-01-04 09:22:19 +00:00
|
|
|
unquoted, err := strconv.Unquote(rest)
|
|
|
|
if err == nil {
|
|
|
|
rest = unquoted
|
|
|
|
}
|
2021-10-30 02:35:13 +00:00
|
|
|
return reflect.ValueOf(&rest), nil
|
2023-01-04 09:22:19 +00:00
|
|
|
case reflect.Interface:
|
|
|
|
// We special case this particular configuration key because historically we accept both a numerical value and a string value for it.
|
|
|
|
if cfgname == "source-list-line-color" {
|
|
|
|
n, err := strconv.Atoi(rest)
|
|
|
|
if err == nil {
|
|
|
|
if n < 0 {
|
|
|
|
return reflect.ValueOf(nil), fmt.Errorf("argument to %q must be a number greater than zero", cfgname)
|
|
|
|
}
|
|
|
|
return reflect.ValueOf(&n), nil
|
|
|
|
}
|
|
|
|
unquoted, err := strconv.Unquote(rest)
|
|
|
|
if err == nil {
|
|
|
|
rest = unquoted
|
|
|
|
}
|
|
|
|
return reflect.ValueOf(&rest), nil
|
|
|
|
}
|
|
|
|
fallthrough
|
2021-10-30 02:35:13 +00:00
|
|
|
default:
|
|
|
|
return reflect.ValueOf(nil), fmt.Errorf("unsupported type for configuration key %q", cfgname)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if field.Kind() == reflect.Ptr {
|
|
|
|
val, err := simpleArg(field.Type().Elem())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
field.Set(val)
|
|
|
|
} else {
|
|
|
|
val, err := simpleArg(field.Type())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
field.Set(val.Elem())
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ConfigureList(w io.Writer, config interface{}, tag string) {
|
|
|
|
it := IterateConfiguration(config, tag)
|
|
|
|
for it.Next() {
|
|
|
|
fieldName, field := it.Field()
|
|
|
|
if fieldName == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
writeField(w, field, fieldName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func writeField(w io.Writer, field reflect.Value, fieldName string) {
|
|
|
|
switch field.Kind() {
|
|
|
|
case reflect.Interface:
|
|
|
|
switch field := field.Interface().(type) {
|
|
|
|
case string:
|
|
|
|
fmt.Fprintf(w, "%s\t%q\n", fieldName, field)
|
|
|
|
default:
|
|
|
|
fmt.Fprintf(w, "%s\t%v\n", fieldName, field)
|
|
|
|
}
|
|
|
|
case reflect.Ptr:
|
|
|
|
if !field.IsNil() {
|
|
|
|
fmt.Fprintf(w, "%s\t%v\n", fieldName, field.Elem())
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, "%s\t<not defined>\n", fieldName)
|
|
|
|
}
|
|
|
|
case reflect.String:
|
|
|
|
fmt.Fprintf(w, "%s\t%q\n", fieldName, field)
|
|
|
|
default:
|
|
|
|
fmt.Fprintf(w, "%s\t%v\n", fieldName, field)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type configureIterator struct {
|
|
|
|
cfgValue reflect.Value
|
|
|
|
cfgType reflect.Type
|
|
|
|
i int
|
|
|
|
tag string
|
|
|
|
}
|
|
|
|
|
|
|
|
func IterateConfiguration(conf interface{}, tag string) *configureIterator {
|
|
|
|
cfgValue := reflect.ValueOf(conf).Elem()
|
|
|
|
cfgType := cfgValue.Type()
|
|
|
|
|
|
|
|
return &configureIterator{cfgValue, cfgType, -1, tag}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (it *configureIterator) Next() bool {
|
|
|
|
it.i++
|
|
|
|
return it.i < it.cfgValue.NumField()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (it *configureIterator) Field() (name string, field reflect.Value) {
|
|
|
|
name = it.cfgType.Field(it.i).Tag.Get(it.tag)
|
|
|
|
if comma := strings.Index(name, ","); comma >= 0 {
|
|
|
|
name = name[:comma]
|
|
|
|
}
|
|
|
|
field = it.cfgValue.Field(it.i)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func ConfigureListByName(conf interface{}, name, tag string) string {
|
|
|
|
if name == "" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
it := IterateConfiguration(conf, tag)
|
|
|
|
for it.Next() {
|
|
|
|
fieldName, field := it.Field()
|
|
|
|
if fieldName == name {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
writeField(&buf, field, fieldName)
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func ConfigureFindFieldByName(conf interface{}, name, tag string) reflect.Value {
|
|
|
|
it := IterateConfiguration(conf, tag)
|
|
|
|
for it.Next() {
|
|
|
|
fieldName, field := it.Field()
|
|
|
|
if fieldName == name {
|
|
|
|
return field
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return reflect.ValueOf(nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func Split2PartsBySpace(s string) []string {
|
|
|
|
v := strings.SplitN(s, " ", 2)
|
|
|
|
for i := range v {
|
|
|
|
v[i] = strings.TrimSpace(v[i])
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|