diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 78144b2..9b52f2e 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -21,6 +21,12 @@ jobs: with: go-version: 1.11 + - name: Build code generation tool + run: go build -v ./cmd/generator + + - name: Generate code + run: go generate ./... + - name: Build run: go build -v ./cmd/gocov-html diff --git a/.gitignore b/.gitignore index c7abeee..0ff6f2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /gocov-html +/generator *.swp /*.css +// Generated Go files +*_gen.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d4ff1f..761eb0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ v 1.3 + - refac: generate Go code to render themes. #38 - doc: fix semver in README. #36 v 1.2 diff --git a/Makefile b/Makefile index ff030f1..6c18c7d 100644 --- a/Makefile +++ b/Makefile @@ -4,5 +4,8 @@ BIN=gocov-html MAIN_CMD=github.com/matm/${BIN}/cmd/${BIN} +GENERATOR_BIN=generator +GENERATOR_CMD=github.com/matm/${BIN}/cmd/${GENERATOR_BIN} + include version.mk include build.mk diff --git a/build.mk b/build.mk index 2146598..7c571e2 100644 --- a/build.mk +++ b/build.mk @@ -71,10 +71,13 @@ cleardist: @rm -rf ${DISTDIR} && mkdir -p ${BINDIR} && mkdir -p ${BUILDDIR} build: + @go build ${GENERATOR_CMD} + @go generate ./... @go build -ldflags "all=$(GO_LDFLAGS)" ${MAIN_CMD} test: @go test ./... clean: - @rm -rf ${BIN} ${BUILDDIR} ${DISTDIR} + @find pkg -name \*_gen.go -delete + @rm -rf ${BIN} ${GENERATOR_BIN} ${BUILDDIR} ${DISTDIR} diff --git a/cmd/generator/main.go b/cmd/generator/main.go new file mode 100644 index 0000000..a0ac54e --- /dev/null +++ b/cmd/generator/main.go @@ -0,0 +1,158 @@ +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "log" + "os" + "path" + "strings" + "text/template" + + "github.com/matm/gocov-html/pkg/types" +) + +const tmpl = `// Code generated by "go run generator.go". DO NOT EDIT. + +package themes + +import ( + "text/template" + "time" + + "github.com/matm/gocov-html/pkg/types" +) + +func (t defaultTheme) Data() *types.TemplateData { + td:= &types.TemplateData{ + When: time.Now().Format(time.RFC822Z), + ProjectURL: types.ProjectURL, + } + {{if .Style}} + td.Style = {{.Style}} + {{end}} + {{if .Script}} + td.Script = {{.Script}} + {{end}} + return td +} + +func (t defaultTheme) Template() *template.Template { + tmpl := {{.Template}} + p := template.Must(template.New("theme").Parse(tmpl)) + return p +} +` + +func inspect(name string, theme *string, assets *types.StaticAssets) error { + fset := token.NewFileSet() + token, err := parser.ParseFile(fset, name, nil, parser.ParseComments) + if err != nil { + return err + } + ast.Inspect(token, func(n ast.Node) bool { + fn, ok := n.(*ast.FuncDecl) + if ok { + switch fn.Name.Name { + case "Name": + *theme = fn.Body.List[0].(*ast.ReturnStmt).Results[0].(*ast.BasicLit).Value + *theme = strings.Replace(*theme, `"`, "", -1) + case "Assets": + es := fn.Body.List[0].(*ast.ReturnStmt).Results[0].(*ast.CompositeLit).Elts + for _, e := range es { + kv := e.(*ast.KeyValueExpr) + id := kv.Key.(*ast.Ident) + switch id.Name { + case "Stylesheets": + elems := kv.Value.(*ast.CompositeLit).Elts + for _, elem := range elems { + sheet := elem.(*ast.BasicLit).Value + assets.Stylesheets = append(assets.Stylesheets, strings.Replace(sheet, `"`, "", -1)) + } + case "Index": + tmplName := kv.Value.(*ast.BasicLit).Value + assets.Index = strings.Replace(tmplName, `"`, "", -1) + case "Scripts": + elems := kv.Value.(*ast.CompositeLit).Elts + for _, elem := range elems { + script := elem.(*ast.BasicLit).Value + assets.Scripts = append(assets.Scripts, strings.Replace(script, `"`, "", -1)) + } + } + } + } + return false + } + return true + }) + return nil +} + +func render(name, theme string, assets types.StaticAssets) error { + baseThemeDir := path.Join("..", "..", "themes", theme) + out := strings.Replace(name, ".go", "_gen.go", 1) + outFile, err := os.Create(out) + if err != nil { + return err + } + defer outFile.Close() + index, err := ioutil.ReadFile(path.Join(baseThemeDir, assets.Index)) + if err != nil { + return err + } + // Contains all stylesheets' data. + var allStyles bytes.Buffer + for _, css := range assets.Stylesheets { + style, err := ioutil.ReadFile(path.Join(baseThemeDir, css)) + if err != nil { + return err + } + fmt.Fprintf(&allStyles, "`%s`", style) + } + + // Contains all scripts' data. + var allScripts bytes.Buffer + for _, script := range assets.Scripts { + js, err := ioutil.ReadFile(path.Join(baseThemeDir, script)) + if err != nil { + return err + } + fmt.Fprintf(&allScripts, "`%s`", js) + } + t, err := template.New("").Parse(tmpl) + if err != nil { + return err + } + type data struct { + Script string + Style string + Template string + } + err = t.Execute(outFile, &data{ + Script: allScripts.String(), + Style: allStyles.String(), + Template: "`" + string(index) + "`"}, + ) + return err +} + +func main() { + name := os.Getenv("GOFILE") + if name == "" { + fmt.Println("Must be run by the \"go generate\" tool, like \"go generate ./...\"") + os.Exit(1) + } + assets := new(types.StaticAssets) + theme := new(string) + err := inspect(name, theme, assets) + if err != nil { + log.Fatal(err) + } + if err := render(name, *theme, *assets); err != nil { + log.Fatal(err) + } +} diff --git a/cmd/gocov-html/main.go b/cmd/gocov-html/main.go index f0aceaa..ea6f785 100644 --- a/cmd/gocov-html/main.go +++ b/cmd/gocov-html/main.go @@ -68,7 +68,7 @@ func main() { } if *showDefaultCSS { - fmt.Println(themes.Current().Data().CSS) + fmt.Println(themes.Current().Data().Style) return } diff --git a/pkg/cov/report.go b/pkg/cov/report.go index 15d9b26..32d1f77 100644 --- a/pkg/cov/report.go +++ b/pkg/cov/report.go @@ -106,7 +106,7 @@ func printReport(w io.Writer, r *report) error { theme := themes.Current() data := theme.Data() - css := data.CSS + css := data.Style if len(r.stylesheet) > 0 { // Inline CSS. f, err := os.Open(r.stylesheet) @@ -124,7 +124,7 @@ func printReport(w io.Writer, r *report) error { reportPackages[i] = buildReportPackage(pkg) } - data.CSS = css + data.Style = css data.Packages = reportPackages if len(reportPackages) > 1 { diff --git a/pkg/themes/default.go b/pkg/themes/default.go index 00b3dd1..62b7d08 100644 --- a/pkg/themes/default.go +++ b/pkg/themes/default.go @@ -1,143 +1,17 @@ package themes -import ( - "text/template" - "time" +//go:generate ../../generator +import ( "github.com/matm/gocov-html/pkg/types" ) type defaultTheme struct{} -func (t defaultTheme) Data() *types.TemplateData { - css := `` - return &types.TemplateData{ - CSS: css, - When: time.Now().Format(time.RFC822Z), - ProjectURL: types.ProjectURL, +func (t defaultTheme) Assets() types.StaticAssets { + return types.StaticAssets{ + Stylesheets: []string{"style.css"}, + Index: "index.html", } } @@ -148,104 +22,3 @@ func (t defaultTheme) Name() string { func (t defaultTheme) Description() string { return "original golang theme (default)" } - -func (t defaultTheme) Template() *template.Template { - tmpl := `{{define "theme"}} - -
-no test files in package.
" - {{else}} -{{$rp.Pkg.Name}} |
- {{printf "%.2f%%" $rp.PercentageReached}} |
- {{printf "%d" $rp.ReachedStatements}}/{{printf "%d" $rp.TotalStatements}} |
-
- This is a coverage report created after analysis of the {{$rp.Pkg.Name}}
package. It
- has been generated with the following command:
-
gocov test {{$rp.Pkg.Name}} | gocov-html-
Here are the stats. Please select a function name to view its implementation and see what's left for testing.
- -
- {{$f.Name}}(...)
- |
-
- {{$rp.Pkg.Name}}/{{$f.ShortFileName}}
- |
-
- {{printf "%.2f%%" $f.CoveragePercent}}
- |
-
- {{$f.StatementsReached}}/{{len $f.Statements}}
- |
-
In {{$f.File}}
:
{{$info.LineNumber}} | -
-
- |
-
no test files in package.
" + {{else}} +{{$rp.Pkg.Name}} |
+ {{printf "%.2f%%" $rp.PercentageReached}} |
+ {{printf "%d" $rp.ReachedStatements}}/{{printf "%d" $rp.TotalStatements}} |
+
+ This is a coverage report created after analysis of the {{$rp.Pkg.Name}}
package. It
+ has been generated with the following command:
+
gocov test {{$rp.Pkg.Name}} | gocov-html+
Here are the stats. Please select a function name to view its implementation and see what's left for testing.
+ +
+ {{$f.Name}}(...)
+ |
+
+ {{$rp.Pkg.Name}}/{{$f.ShortFileName}}
+ |
+
+ {{printf "%.2f%%" $f.CoveragePercent}}
+ |
+
+ {{$f.StatementsReached}}/{{len $f.Statements}}
+ |
+
In {{$f.File}}
:
{{$info.LineNumber}} | +
+
+ |
+