sqlboiler/output.go
2016-06-27 00:00:19 -07:00

251 lines
6.2 KiB
Go

package main
import (
"bufio"
"bytes"
"fmt"
"go/format"
"io"
"os"
"path/filepath"
"regexp"
"strconv"
"text/template"
"github.com/pkg/errors"
)
var testHarnessStdout io.Writer = os.Stdout
var testHarnessFileOpen = func(filename string) (io.WriteCloser, error) {
file, err := os.Create(filename)
return file, err
}
// generateOutput builds the file output and sends it to outHandler for saving
func generateOutput(state *State, data *templateData) error {
return executeTemplates(executeTemplateData{
state: state,
data: data,
templates: state.Templates,
importSet: defaultTemplateImports,
combineImportsOnType: true,
fileSuffix: ".go",
})
}
// generateTestOutput builds the test file output and sends it to outHandler for saving
func generateTestOutput(state *State, data *templateData) error {
return executeTemplates(executeTemplateData{
state: state,
data: data,
templates: state.TestTemplates,
importSet: defaultTestTemplateImports,
combineImportsOnType: false,
fileSuffix: "_test.go",
})
}
// generateSingletonOutput processes the templates that should only be run
// one time.
func generateSingletonOutput(state *State, data *templateData) error {
return executeSingletonTemplates(executeTemplateData{
state: state,
data: data,
templates: state.SingletonTemplates,
importNamedSet: defaultSingletonTemplateImports,
fileSuffix: ".go",
})
}
// generateSingletonTestOutput processes the templates that should only be run
// one time.
func generateSingletonTestOutput(state *State, data *templateData) error {
return executeSingletonTemplates(executeTemplateData{
state: state,
data: data,
templates: state.SingletonTestTemplates,
importNamedSet: defaultSingletonTestTemplateImports,
fileSuffix: "_test.go",
})
}
type executeTemplateData struct {
state *State
data *templateData
templates templateList
importSet imports
importNamedSet map[string]imports
combineImportsOnType bool
fileSuffix string
}
func executeTemplates(e executeTemplateData) error {
if e.data.Table.IsJoinTable {
return nil
}
var out [][]byte
var imps imports
imps.standard = e.importSet.standard
imps.thirdParty = e.importSet.thirdParty
for _, template := range e.templates {
if e.combineImportsOnType {
imps = combineTypeImports(imps, importsBasedOnType, e.data.Table.Columns)
}
resp, err := executeTemplate(template, e.data)
if err != nil {
return fmt.Errorf("Error generating template %s: %s", template.Name(), err)
}
out = append(out, resp)
}
fName := e.data.Table.Name + e.fileSuffix
err := outHandler(e.state.Config.OutFolder, fName, e.state.Config.PkgName, imps, out)
if err != nil {
return err
}
return nil
}
func executeSingletonTemplates(e executeTemplateData) error {
if e.data.Table.IsJoinTable {
return nil
}
rgxRemove := regexp.MustCompile(`[0-9]+_`)
for _, template := range e.templates {
resp, err := executeTemplate(template, e.data)
if err != nil {
return fmt.Errorf("Error generating template %s: %s", template.Name(), err)
}
fName := template.Name()
ext := filepath.Ext(fName)
fName = rgxRemove.ReplaceAllString(fName[:len(fName)-len(ext)], "")
imps := imports{
standard: e.importNamedSet[fName].standard,
thirdParty: e.importNamedSet[fName].thirdParty,
}
err = outHandler(
e.state.Config.OutFolder,
fName+e.fileSuffix,
e.state.Config.PkgName,
imps,
[][]byte{resp},
)
if err != nil {
return err
}
}
return nil
}
func generateTestMainOutput(state *State, data *templateData) error {
if state.TestMainTemplate == nil {
return errors.New("No TestMain template located for generation")
}
var out [][]byte
var imps imports
imps.standard = defaultTestMainImports[state.Config.DriverName].standard
imps.thirdParty = defaultTestMainImports[state.Config.DriverName].thirdParty
resp, err := executeTemplate(state.TestMainTemplate, data)
if err != nil {
return err
}
out = append(out, resp)
err = outHandler(state.Config.OutFolder, "main_test.go", state.Config.PkgName, imps, out)
if err != nil {
return err
}
return nil
}
func outHandler(outFolder string, fileName string, pkgName string, imps imports, contents [][]byte) error {
out := testHarnessStdout
path := filepath.Join(outFolder, fileName)
outFile, err := testHarnessFileOpen(path)
if err != nil {
return fmt.Errorf("Unable to create output file %s: %s", path, err)
}
defer outFile.Close()
out = outFile
if _, err := fmt.Fprintf(out, "package %s\n\n", pkgName); err != nil {
return fmt.Errorf("Unable to write package name %s to file: %s", pkgName, path)
}
impStr := buildImportString(imps)
if len(impStr) > 0 {
if _, err := fmt.Fprintf(out, "%s\n", impStr); err != nil {
return fmt.Errorf("Unable to write imports to file handle: %v", err)
}
}
for _, templateOutput := range contents {
if _, err := fmt.Fprintf(out, "%s\n", templateOutput); err != nil {
return fmt.Errorf("Unable to write template output to file handle: %v", err)
}
}
return nil
}
var rgxSyntaxError = regexp.MustCompile(`(\d+):\d+: `)
// executeTemplate takes a template and returns the output of the template
// execution.
func executeTemplate(t *template.Template, data *templateData) ([]byte, error) {
var buf bytes.Buffer
if err := t.Execute(&buf, data); err != nil {
return nil, errors.Wrap(err, "failed to execute template")
}
output, err := format.Source(buf.Bytes())
if err != nil {
matches := rgxSyntaxError.FindStringSubmatch(err.Error())
if matches == nil {
return nil, errors.Wrap(err, "failed to format template")
}
lineNum, _ := strconv.Atoi(matches[1])
scanner := bufio.NewScanner(&buf)
errBuf := &bytes.Buffer{}
line := 0
for ; scanner.Scan(); line++ {
if delta := line - lineNum; delta < -5 || delta > 5 {
continue
}
if line == lineNum {
errBuf.WriteString(">>> ")
} else {
fmt.Fprintf(errBuf, "% 3d ", line)
}
errBuf.Write(scanner.Bytes())
errBuf.WriteByte('\n')
}
return nil, errors.Wrapf(err, "failed to format template\n\n%s\n", errBuf.Bytes())
}
return output, nil
}