package main import ( "bufio" "fmt" "log" "os" "sort" "strings" "forgejo.edf-bootstrap.cx.fg1.ffm.osc.live/richardrobertreitz/docudile/lib" ) type field struct { name string enums string kind string required bool description []string fields []field uuid string } type spec struct { uuid string group string kind string version string field []string description []string fields []field enums string specs []spec } type index struct { name string version string key string uuid string group string } func getSpec(level int, specName string) spec { spec := spec{} out, err := lib.ExecNotFatal(`#!/bin/bash kubectl explain "${SPEC}" `, []string{"SPEC=" + specName}) if err != nil { if specName == "events.events.k8s.io" { fmt.Println("warning: skipped " + specName + " intentionally, seems substituted by events") return spec } else { log.Fatal("warning, cant lookup "+specName+": ", err) } } spec.uuid = specName currentField := field{} scanner := bufio.NewScanner(strings.NewReader(out)) step := 0 scans := []string{} for scanner.Scan() { scans = append(scans, scanner.Text()) } //fmt.Println("--- " + specName) for u := range scans { line := scans[u] if step == 0 { if strings.HasPrefix(line, "GROUP:") { word := line[6:] spec.group = strings.Join(strings.Fields(word), " ") } else if strings.HasPrefix(line, "KIND:") { word := line[5:] spec.kind = strings.Join(strings.Fields(word), " ") step++ } step++ } else if step == 1 { if strings.HasPrefix(line, "KIND:") { word := line[5:] spec.kind = strings.Join(strings.Fields(word), " ") step++ } else { log.Fatal("cant find kind type") } } else if step == 2 { if strings.HasPrefix(line, "VERSION:") { word := line[8:] spec.version = strings.Join(strings.Fields(word), " ") step++ } else { log.Fatal("cant find kind type") } } else if step == 3 { if len(strings.TrimSpace(line)) == 0 { step++ } else { log.Fatal("cant find new line 1") } } else if step == 8 { if len(strings.TrimSpace(line)) == 0 { step = 4 } else { spec.enums += ", " + strings.TrimSpace(line) } } else if step == 5 { if len(strings.TrimSpace(line)) == 0 { step = 3 } else { if strings.TrimSpace(line) == "ENUM:" { step = 8 } else { log.Fatal("cant find new line 2 :" + line + ":") } } } else if step == 4 { if strings.HasPrefix(line, "FIELD:") { word := line[6:] spec.field = strings.Fields(word) step = 5 } else if strings.HasPrefix(line, "DESCRIPTION:") { step = 6 } else { log.Fatal("cant find field or description type") } } else if step == 6 { if !strings.HasPrefix(line, "FIELDS:") { if len(line) != 0 { spec.description = append(spec.description, strings.TrimSpace(line)) } } else { step++ } } else if step == 7 { runes := []rune(line) if strings.HasPrefix(strings.TrimSpace(line), "enum:") { currentField.enums = line[8:] } else if len(runes) > 0 && runes[0] == 32 && runes[1] == 32 && runes[2] != 32 { stringSlice := strings.Fields(line) if !(len(stringSlice) == 2 || len(stringSlice) == 3) { log.Fatal("wrong object type: " + line) } currentField.name = stringSlice[0] currentField.kind = stringSlice[1][1 : len(stringSlice[1])-1] if len(stringSlice) == 3 && stringSlice[2] == "-required-" { currentField.required = true } } else { currentField.uuid = spec.uuid currentField.description = append(currentField.description, strings.TrimSpace(line)) if len(strings.TrimSpace(line)) == 0 { if u >= len(scans)-1 { if len(strings.TrimSpace(currentField.name)) == 0 { log.Fatal("field requires a name:" + line) } if level > 0 { subSpec := getSpec(level+1, specName+"."+strings.TrimSpace(currentField.name)) currentField.fields = append(currentField.fields, subSpec.fields...) spec.specs = append(spec.specs, subSpec) } spec.fields = append(spec.fields, currentField) currentField = field{} } else { localRunes := []rune(scans[u+1]) if len(localRunes) > 0 && localRunes[0] == 32 && localRunes[1] == 32 && localRunes[2] != 32 { if len(strings.TrimSpace(currentField.name)) == 0 { log.Fatal("field requires a name:" + line) } if level > 0 { subSpec := getSpec(level+1, specName+"."+strings.TrimSpace(currentField.name)) currentField.fields = append(currentField.fields, subSpec.fields...) spec.specs = append(spec.specs, subSpec) } spec.fields = append(spec.fields, currentField) currentField = field{} } } } } } } if err := scanner.Err(); err != nil { log.Fatal("error: ", err) } if len(spec.field) != 2 && len(spec.field) != 0 { log.Fatal("type field should contain 2 or 0 elements, found " + fmt.Sprint(len(spec.field))) } if len(spec.description) > 0 { if len(strings.TrimSpace(spec.description[len(spec.description)-1])) == 0 { spec.description = spec.description[:len(spec.description)-1] } } return spec } func main() { specs := make(map[string]spec) if len(os.Args) == 1 { out, err := lib.ExecNotFatal(`#!/bin/bash kubectl api-resources --verbs=list -o name | sort `, []string{}) if err != nil { log.Fatal("error: ", err) } scanner := bufio.NewScanner(strings.NewReader(out)) for scanner.Scan() { spec := getSpec(0, scanner.Text()) if len(spec.uuid) > 0 { specs[spec.uuid] = spec } } if err := scanner.Err(); err != nil { log.Fatal("error: ", err) } } else { spec := getSpec(1, os.Args[1]) specs[spec.uuid] = spec } tree := []spec{} for _, spec := range specs { tree = append(tree, spec) } sort.SliceStable(tree, func(i, j int) bool { if tree[i].group == tree[j].group { return tree[i].kind < tree[j].kind } else { groupA := strings.Fields(strings.Replace(tree[i].group, ".", " ", -1)) revGroupA := strings.Join(reverse(groupA), ".") groupB := strings.Fields(strings.Replace(tree[j].group, ".", " ", -1)) revGroupB := strings.Join(reverse(groupB), ".") return revGroupA < revGroupB } }) if len(os.Args) == 1 { fmt.Println("") fmt.Println("tree:") for _, object := range tree { if len(object.group) == 0 { fmt.Println("default, " + object.kind + ", " + object.uuid) } else { group := strings.Fields(strings.Replace(object.group, ".", " ", -1)) revGroup := strings.Join(reverse(group), ".") fmt.Println(revGroup + ", " + object.kind + ", " + object.uuid) } } objects := []index{} for name, spec := range specs { objects = append(objects, index{spec.kind, spec.version, name, spec.uuid, spec.group}) } sort.SliceStable(objects, func(i, j int) bool { groupA := strings.Fields(strings.Replace(objects[i].group, ".", " ", -1)) revGroupA := strings.Join(reverse(groupA), ".") groupB := strings.Fields(strings.Replace(objects[j].group, ".", " ", -1)) revGroupB := strings.Join(reverse(groupB), ".") //fmt.Println(objects[i].key) if objects[i].name == objects[j].name && revGroupA == revGroupB { return objects[i].version < objects[j].version } else if objects[i].name == objects[j].name { return revGroupA < revGroupB } else { return objects[i].name < objects[j].name } }) fmt.Println("") fmt.Println("index:") for _, object := range objects { if len(object.group) == 0 { fmt.Println(object.name + ", default, " + object.uuid) } else { group := strings.Fields(strings.Replace(object.group, ".", " ", -1)) revGroup := strings.Join(reverse(group), ".") fmt.Println(object.name + ", " + revGroup + ", " + object.uuid) } } } if len(os.Args) > 1 { for _, spec := range tree { printSpec(spec, true) } for _, spec := range tree { recurseSpecs(spec) } } } func recurseSpecs(spec spec) { printSpec(spec, false) for _, subSpec := range spec.specs { if len(subSpec.specs) > 0 { recurseSpecs(subSpec) } } } func printSpec(spec spec, recurse bool) { fmt.Println(spec.uuid) fmt.Println("### uuid = " + spec.uuid) if len(spec.group) == 0 { fmt.Println("group = 'default'") } else { fmt.Println("group = '" + spec.group + "'") } fmt.Println("kind = '" + spec.kind + "'") fmt.Println("version = '" + spec.version + "'") fmt.Println("field[" + fmt.Sprint(len(spec.field)) + "]:") for i := 0; i < len(spec.field); i++ { fmt.Println(" " + spec.field[i]) } fmt.Println("description[" + fmt.Sprint(len(spec.description)) + "]:") for i := 0; i < len(spec.description); i++ { fmt.Println(" " + spec.description[i]) } fmt.Println("fields[" + fmt.Sprint(len(spec.fields)) + "]:") printFields(1, spec.fields, len(spec.uuid), strings.ToLower(spec.kind), recurse) } func printFields(level int, fields []field, baseUuidLength int, baseName string, recurse bool) { fill := "" for i := 0; i < level; i++ { fill += " " } for i := range fields { linked := "" if len(fields[i].fields) > 0 { linked = ", link" } fmt.Println(fill + " " + fields[i].name + ", " + fields[i].kind + ", " + fmt.Sprint(fields[i].required) + ", enums[" + fields[i].enums + "]" + linked) if len(fields[i].uuid) > baseUuidLength && len(fields[i].uuid[baseUuidLength+1:]) > 0 { fmt.Println(fill + " " + baseName + "." + fields[i].uuid[baseUuidLength+1:]) } else if len(fields[i].uuid) == baseUuidLength { fmt.Println(fill + " " + baseName) } for j := range fields[i].description { fmt.Println(fill + " " + fields[i].description[j]) } if recurse { printFields(level+1, fields[i].fields, baseUuidLength, baseName, recurse) } } } func reverse(s []string) []string { a := make([]string, len(s)) copy(a, s) for i := len(a)/2 - 1; i >= 0; i-- { opp := len(a) - 1 - i a[i], a[opp] = a[opp], a[i] } return a }