github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/help/config/config_help.go (about) 1 // Package main implements a parser for our config structure 2 // that emits help topics based on its struct tags. 3 package main 4 5 import ( 6 "encoding/json" 7 "fmt" 8 "reflect" 9 "regexp" 10 "runtime" 11 "strings" 12 13 "core" 14 ) 15 16 type output struct { 17 Preamble string `json:"preamble"` 18 Topics map[string]string `json:"topics"` 19 } 20 21 var urlRegex = regexp.MustCompile("https?://[^ ]+[^.]") 22 23 // ExampleValue returns an example value for a config field based on its type. 24 func ExampleValue(f reflect.Value, name string, t reflect.Type, example, options string) string { 25 if t.Kind() == reflect.Slice { 26 return ExampleValue(f, name, t.Elem(), example, options) + fmt.Sprintf("\n\n%s can be repeated", name) 27 } else if example != "" { 28 return example 29 } else if options != "" { 30 return strings.Replace(options, ",", " | ", -1) 31 } else if name == "version" { 32 return core.PleaseVersion.String() // keep it up to date! 33 } else if t.Kind() == reflect.String { 34 if f.String() != "" { 35 return f.String() 36 } 37 if t.Name() == "URL" { 38 return "https://mydomain.com/somepath" 39 } 40 return "<str>" 41 } else if t.Kind() == reflect.Bool { 42 return "true | false | yes | no | on | off" 43 } else if t.Name() == "Duration" { 44 return "10ms | 20s | 5m" 45 } else if t.Kind() == reflect.Int || t.Kind() == reflect.Int64 { 46 if f.Int() != 0 { 47 return fmt.Sprintf("%d", f.Int()) 48 } 49 return "42" 50 } else if t.Name() == "ByteSize" { 51 return "5K | 10MB | 20GiB" 52 } else if t.Kind() == reflect.Uint64 { 53 return fmt.Sprintf("%d", f.Uint()) 54 } else if t.Name() == "BuildLabel" { 55 return "//src/core:core" 56 } else if t.Name() == "Arch" { 57 return runtime.GOOS + "_" + runtime.GOARCH 58 } 59 panic(fmt.Sprintf("Unknown type: %s", t.Kind())) 60 } 61 62 func main() { 63 o := output{ 64 Preamble: "${BOLD_BLUE}%s${RESET} is a config setting defined in the .plzconfig file. See `plz help plzconfig` for more information.", 65 Topics: map[string]string{}, 66 } 67 config := core.DefaultConfiguration() 68 v := reflect.ValueOf(config).Elem() 69 t := v.Type() 70 for i := 0; i < t.NumField(); i++ { 71 f := v.Field(i) 72 sectname := strings.ToLower(t.Field(i).Name) 73 subfields := []string{} 74 if f.Type().Kind() == reflect.Struct { 75 for j := 0; j < f.Type().NumField(); j++ { 76 subf := f.Field(j) 77 subt := t.Field(i).Type.Field(j) 78 if help := subt.Tag.Get("help"); help != "" { 79 name := strings.ToLower(subt.Name) 80 example := subt.Tag.Get("example") 81 preamble := fmt.Sprintf("${BOLD_YELLOW}[%s]${RESET}\n${YELLOW}%s${RESET} = ${GREEN}%s${RESET}\n\n", sectname, name, ExampleValue(subf, name, subt.Type, example, subt.Tag.Get("options"))) 82 help = strings.Replace(help, "\\n", "\n", -1) + "\n" 83 if v := subt.Tag.Get("var"); v != "" { 84 help += fmt.Sprintf("\nThis variable is exposed to BUILD rules via the variable ${BOLD_CYAN}CONFIG.%s${RESET},\n"+ 85 "and can be overridden package-locally via ${GREEN}package${RESET}(${YELLOW}%s${RESET}='${GREY}<value>${RESET}').\n", v, strings.ToLower(v)) 86 } 87 o.Topics[name] = preamble + help 88 subfields = append(subfields, " "+name) 89 } else if f.CanSet() { 90 panic(fmt.Sprintf("Missing help struct tag on %s.%s", t.Field(i).Name, subt.Name)) 91 } 92 } 93 } 94 if help := t.Field(i).Tag.Get("help"); help != "" { 95 help += "\n" 96 if len(subfields) > 0 { 97 help += "\n${YELLOW}This option has the following sub-fields:${RESET}\n${GREEN}" + strings.Join(subfields, "\n") + "${RESET}\n" 98 } 99 o.Topics[sectname] = urlRegex.ReplaceAllStringFunc(help, func(s string) string { return "${BLUE}" + s + "${RESET}" }) 100 } 101 } 102 b, _ := json.Marshal(o) 103 fmt.Println(string(b)) 104 }