// Mgmt // Copyright (C) 2013-2023+ James Shubin and the project contributors // Written by James Shubin and the project contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . package main import ( "bytes" "errors" "fmt" "io/ioutil" "log" "os/exec" "path/filepath" "regexp" "strings" "github.com/iancoleman/strcase" yaml "gopkg.in/yaml.v2" ) var ( validSignature = regexp.MustCompile(`^func (?P[A-Z][a-zA-Z0-9]+)\((?P([a-zA-Z]+( (bool|string|int|int64|float64|\[\]byte))?(, )?){0,})\) (?P(bool|string|int|int64|float64|\[\]byte|)|\((bool|string|int|int64|float64|\[\]byte), error\))$`) errExcluded = errors.New("function is excluded") ) type golangPackages []*golangPackage type golangPackage struct { // Name is the name of the golang package. Name string `yaml:"name"` // Alias is the alias of the package when imported in golang. // e.g. import rand "os.rand" Alias string `yaml:"alias,omitempty"` // MgmtAlias is the name of the package inside mcl. MgmtAlias string `yaml:"mgmtAlias,omitempty"` // Exclude is a list of golang function names that we do not want. Exclude []string `yaml:"exclude,omitempty"` } func parsePkg(path, filename, templates string) error { var c config filePath := filepath.Join(path, filename) log.Printf("Data: %s", filePath) cfgFile, err := ioutil.ReadFile(filePath) if err != nil { return err } err = yaml.UnmarshalStrict(cfgFile, &c) if err != nil { return err } functions, err := parsePackages(c) if err != nil { return err } err = parseFuncs(c, functions, path, templates) if err != nil { return err } return nil } func parsePackages(c config) (functions, error) { var funcs []function for _, golangPackage := range c.Packages { fn, err := golangPackage.parsefuncs() if err != nil { return funcs, err } funcs = append(funcs, fn...) } return funcs, nil } func (obj *golangPackage) parsefuncs() (functions, error) { var funcs []function cmd := exec.Command("go", "doc", obj.Name) var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() if err != nil { return funcs, err } return obj.extractFuncs(out.String(), true) } func (obj *golangPackage) extractFuncs(doc string, getHelp bool) (functions, error) { var funcs []function for _, line := range strings.Split(doc, "\n") { if !validSignature.MatchString(line) { // TODO: improve validSignature regexp for this? fmt.Printf("invalid: %s\n", line) continue } f, err := obj.parseFunctionLine(line, getHelp) if err != nil && err != errExcluded { return funcs, err } if f != nil { funcs = append(funcs, *f) } } return funcs, nil } func (obj *golangPackage) parseFunctionLine(line string, getHelp bool) (*function, error) { match := validSignature.FindStringSubmatch(line) result := make(map[string]string) for i, name := range validSignature.SubexpNames() { if i != 0 && name != "" { result[name] = match[i] } } name := result["name"] for _, e := range obj.Exclude { if e == name { return nil, errExcluded } } errorFul, err := regexp.MatchString(`, error\)$`, result["return"]) if err != nil { return nil, err } returns := parseReturn(result["return"]) if len(returns) == 0 { return nil, errExcluded } mgmtPackage := obj.Name if obj.MgmtAlias != "" { mgmtPackage = obj.MgmtAlias } mgmtPackage = fmt.Sprintf("golang/%s", mgmtPackage) internalName := fmt.Sprintf("%s%s", strcase.ToCamel(strings.Replace(obj.Name, "/", "", -1)), name) internalName = strings.Replace(internalName, "Html", "HTML", -1) var help string if getHelp { help, err = obj.getHelp(name, internalName) if err != nil { return nil, err } } return &function{ MgmtPackage: mgmtPackage, MclName: strcase.ToSnake(name), InternalName: internalName, Help: help, GolangPackage: obj, GolangFunc: name, Errorful: errorFul, Args: parseArgs(result["args"]), Return: returns, }, nil } func reverseArgs(s []arg) { last := len(s) - 1 for i := 0; i < len(s)/2; i++ { s[i], s[last-i] = s[last-i], s[i] } } func reverse(s []string) { last := len(s) - 1 for i := 0; i < len(s)/2; i++ { s[i], s[last-i] = s[last-i], s[i] } } func parseArgs(str string) []arg { var args []arg s := strings.Split(str, ",") reverse(s) var currentType string for _, currentArg := range s { if currentArg == "" { continue } v := strings.Split(strings.TrimSpace(currentArg), " ") if len(v) == 2 { currentType = v[1] } args = append(args, arg{Name: v[0], Type: currentType}) } reverseArgs(args) return args } func parseReturn(str string) []arg { var returns []arg re := regexp.MustCompile(`(bool|string|int|int64|float64|\[\]byte)`) t := string(re.Find([]byte(str))) returns = append(returns, arg{Type: t}) return returns } func (obj *golangPackage) getHelp(function, internalName string) (string, error) { cmd := exec.Command("go", "doc", fmt.Sprintf("%s.%s", obj.Name, function)) var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() if err != nil { return "", err } var doc string for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { if i > 0 { s := strings.TrimSpace(line) if i == 1 { docs := strings.Split(s, " ") docs[0] = internalName s = strings.Join(docs, " ") doc = doc + "// " + s + " is an autogenerated function.\n" } else { doc = doc + "//" if s != "" { doc = doc + " " } doc = doc + s + "\n" } } } return doc, nil }