212 lines
5.4 KiB
Go
212 lines
5.4 KiB
Go
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")))
|
|
}
|
|
}
|