package main import ( "bufio" "errors" "flag" "fmt" "io" "os" "path" "path/filepath" "regexp" "strings" "text/template" "code.thetadev.de/ThetaDev/gotry" "code.thetadev.de/ThetaDev/gotry/try" ) var variablePattern = regexp.MustCompile(`^(\w[\w\d]*)(\+s)?$`) const codeTemplate = `package try // gotry auto-generated type definitions. DO NOT EDIT. {{- if .Imports}} import ( {{- range $pkg, $b := .Imports}} "{{$pkg}}" {{- end}} ) {{- end}} {{range $d := .Definitions }} // {{$d.Name}} is a helper method to handle errors of func() ({{range $i, $t := $d.Types}}{{$t}}, {{end}}error) functions func {{$d.Name}}({{range $i, $t := $d.Types}}v{{if $i}}{{$i}}{{end}} {{$t}}, {{end}}err error) ( {{- range $i, $t := $d.Types}}{{if $i}}, {{end}}{{$t}}{{end}}) { Check(err) return {{range $i, $t := $d.Types}}{{if $i}}, v{{$i}}{{else}}v{{end}}{{end}} } {{- if $d.WithSlice}} // {{$d.Name}}s is a helper method to handle errors of func() ({{range $i, $t := $d.Types}}[]{{$t}}, {{end}}error) functions func {{$d.Name}}s({{range $i, $t := $d.Types}}v{{if $i}}{{$i}}{{end}} []{{$t}}, {{end}}err error) ( {{- range $i, $t := $d.Types}}{{if $i}}, {{end}}[]{{$t}}{{end}}) { Check(err) return {{range $i, $t := $d.Types}}{{if $i}}, v{{$i}}{{else}}v{{end}}{{end}} } {{- end}} {{end}}` type line struct { Name string WithSlice bool Types []string } func parseDefinitions(defFileName string, outFileName string) (err error) { defer try.Annotate("error parsing definitions", &err) defFile := try.File(os.Open(defFileName)) fileScanner := bufio.NewScanner(defFile) fileScanner.Split(bufio.ScanLines) ln := 0 var lines []line imports := map[string]bool{} for fileScanner.Scan() { lineStr := strings.TrimSpace(fileScanner.Text()) ln++ if lineStr == "" || strings.HasPrefix(lineStr, "#") || strings.HasPrefix(lineStr, "/") { continue } lineData, err := parseLine(lineStr, imports) if err != nil { fmt.Printf("[Err] line %d, %s: \"%s\n", ln, err.Error(), lineStr) continue } lines = append(lines, *lineData) } fmt.Printf("Parsed %d definitions\n", len(lines)) outFile := try.File(os.Create(outFileName)) tmpl := template.Must(template.New("templ").Parse(codeTemplate)) try.Check(tmpl.Execute(outFile, struct { Definitions []line Imports map[string]bool }{ Definitions: lines, Imports: imports, })) try.Check(defFile.Close()) try.Check(outFile.Close()) return } func parseLine(lineStr string, imports map[string]bool) (*line, error) { lineParts := strings.Split(lineStr, ";") lineData := line{} if len(lineParts) < 2 { return nil, errors.New("missing type name") } // Parse variable name variableRaw := strings.TrimSpace(lineParts[0]) if match := variablePattern.FindStringSubmatch(variableRaw); match != nil { lineData.Name = match[1] lineData.WithSlice = match[2] != "" } else { return nil, errors.New("invalid variable name") } // Parse type name(s) for _, typeStr := range strings.Split(lineParts[1], ",") { typeStr = strings.TrimSpace(typeStr) if typeStr != "" { lineData.Types = append(lineData.Types, typeStr) } } if len(lineData.Types) == 0 { return nil, errors.New("invalid type name") } // Parse imports for i := 2; i < len(lineParts); i++ { pkgName := strings.TrimSpace(lineParts[i]) imports[pkgName] = true } return &lineData, nil } func main() { defer try.Catch(func(err error) { fmt.Println("Error: " + err.Error()) }) // Read flags var definitionFile string var targetDir string var typeFile string flag.StringVar(&definitionFile, "def", path.Join("try", "types.csv"), "Definition file") flag.StringVar(&targetDir, "o", "try", "Target directory") flag.StringVar(&typeFile, "of", "types", "Name of generated type definition file") flagInit := flag.Bool("init", false, "Copy gotry package") flagNoTests := flag.Bool("no-tests", false, "Dont copy tests") flagNoStds := flag.Bool("no-std", false, "Dont copy std types") flag.Parse() os.MkdirAll(targetDir, 0777) // Copy library if *flagInit { filesToCopy := []string{"try.go"} if !*flagNoTests { filesToCopy = append(filesToCopy, "try_test.go") } if !*flagNoStds { filesToCopy = append(filesToCopy, "std_types.go") if !*flagNoTests { filesToCopy = append(filesToCopy, "std_types_test.go") } } for _, fname := range filesToCopy { f, e := gotry.TryFiles.Open("try/" + fname) try.Check(e) targetPath := filepath.Join(targetDir, fname) dest := try.File(os.Create(targetPath)) try.Empty(io.Copy(dest, f)) fmt.Printf("Copied %s\n", fname) try.Check(f.Close()) try.Check(dest.Close()) } // Add go-generate command gencmd := "\n//go:generate go run code.thetadev.de/ThetaDev/gotry/gotry_generate -def types.csv -o .\n" f := try.File(os.OpenFile(filepath.Join(targetDir, "try.go"), os.O_APPEND|os.O_WRONLY, 0644)) try.Empty(f.WriteString(gencmd)) try.Check(f.Close()) // If not present, add definition file template definitionFile = filepath.Join(targetDir, "types.csv") if _, e := os.Stat(definitionFile); os.IsNotExist(e) { f := try.File(os.Create(definitionFile)) f.WriteString("# Add your type definitions here\n# Example:\n# DB;*gorm.DB;gorm.io/gorm\n") try.Check(f.Close()) } } // Generate types if _, e := os.Stat(definitionFile); os.IsNotExist(e) { fmt.Println("Definition file does not exist") } else { try.Check(parseDefinitions(definitionFile, path.Join(targetDir, typeFile+".go"))) } }