Skip to content

Commit

Permalink
test: add tests (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmzane authored Aug 26, 2023
1 parent a052808 commit 4f96b79
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 112 deletions.
90 changes: 90 additions & 0 deletions codegen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package main

import (
"fmt"
"io"
"sort"
"strings"
"text/template"

"gopkg.in/yaml.v3"
)

var tmpl = template.Must(template.New("").Funcs(funcs).Parse(
`// Code generated by go-simpler.org/slog-gen. DO NOT EDIT.
package {{.Pkg}}
{{range .Imports -}}
import "{{.}}"
{{end -}}
{{range $_, $attr := .Attrs}}
func {{snakeToCamel $attr.Key}}(value {{$attr.Type}}) slog.Attr {
return slog.{{slogFunc $attr.Type}}("{{$attr.Key}}", value)
}
{{end}}`,
))

type (
config struct {
Pkg string
Imports []string
Attrs []attr
}
attr struct {
Key string
Type string
}
)

func readConfig(r io.Reader) (config, error) {
var cfg struct {
Pkg string `yaml:"pkg"`
Imports []string `yaml:"imports"`
Attrs map[string]string `yaml:"attrs"` // key:type
}
if err := yaml.NewDecoder(r).Decode(&cfg); err != nil {
return config{}, fmt.Errorf("decoding config: %w", err)
}

cfg.Imports = append(cfg.Imports, "log/slog")
sort.Strings(cfg.Imports)

// TODO: rewrite when maps.Keys() is released.
keys := make([]string, 0, len(cfg.Attrs))
for key := range cfg.Attrs {
keys = append(keys, key)
}
sort.Strings(keys)

attrs := make([]attr, len(keys))
for i, key := range keys {
attrs[i] = attr{Key: key, Type: cfg.Attrs[key]}
}

return config{
Pkg: cfg.Pkg,
Imports: cfg.Imports,
Attrs: attrs,
}, nil
}

// nolint:staticcheck // SA1019: strings.Title is deprecated but works just fine here.
var funcs = template.FuncMap{
"snakeToCamel": func(s string) string {
parts := strings.Split(s, "_")
for i := range parts {
parts[i] = strings.Title(parts[i])
}
return strings.Join(parts, "")
},
"slogFunc": func(typ string) string {
switch s := strings.Title(strings.TrimPrefix(typ, "time.")); s {
case "String", "Int64", "Int", "Uint64", "Float64", "Bool", "Time", "Duration":
return s
default:
return "Any"
}
},
}
65 changes: 65 additions & 0 deletions codegen_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package main

import (
"bytes"
"strings"
"testing"

"go-simpler.org/assert"
. "go-simpler.org/assert/dotimport"
)

func Test_tmplExecute(t *testing.T) {
cfg := config{
Pkg: "test",
Imports: []string{"log/slog"},
Attrs: []attr{
{Key: "foo", Type: "int"},
{Key: "bar", Type: "error"},
},
}

const codegen = `// Code generated by go-simpler.org/slog-gen. DO NOT EDIT.
package test
import "log/slog"
func Foo(value int) slog.Attr {
return slog.Int("foo", value)
}
func Bar(value error) slog.Attr {
return slog.Any("bar", value)
}
`

var buf bytes.Buffer
err := tmpl.Execute(&buf, cfg)
assert.NoErr[F](t, err)
assert.Equal[E](t, buf.String(), codegen)
}

func Test_readConfig(t *testing.T) {
r := strings.NewReader(`
pkg: test
imports:
- time
attrs:
foo: time.Time
bar: time.Duration
`)

want := config{
Pkg: "test",
Imports: []string{"log/slog", "time"},
Attrs: []attr{
{Key: "bar", Type: "time.Duration"},
{Key: "foo", Type: "time.Time"},
},
}

got, err := readConfig(r)
assert.NoErr[F](t, err)
assert.Equal[E](t, got, want)
}
18 changes: 0 additions & 18 deletions example_test.go

This file was deleted.

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module go-simpler.org/slog-gen
go 1.21.0

require (
go-simpler.org/assert v0.6.0
go-simpler.org/errorsx v0.7.0
gopkg.in/yaml.v3 v3.0.1
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
go-simpler.org/assert v0.6.0 h1:QxSrXa4oRuo/1eHMXSBFHKvJIpWABayzKldqZyugG7E=
go-simpler.org/assert v0.6.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28=
go-simpler.org/errorsx v0.7.0 h1:KtOhEDzn8WzjEwv0qt/TB44gARqBMm3mRehU2H8YMso=
go-simpler.org/errorsx v0.7.0/go.mod h1:j33G4x0zmHYKTtxq6sX3NJtaOljcDcc2XdK8kp23yTw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
109 changes: 15 additions & 94 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import (
"flag"
"fmt"
"os"
"sort"
"strings"
"text/template"
"path/filepath"

"go-simpler.org/errorsx"
"gopkg.in/yaml.v3"
)

//go:generate go run main.go codegen.go -config=.slog.yml

func main() {
if err := run(); err != nil {
fmt.Println(err)
Expand All @@ -24,113 +23,35 @@ func run() (err error) {
var cfgPath string

fs := flag.NewFlagSet("slog-gen", flag.ContinueOnError)
fs.StringVar(&cfgPath, "config", ".slog.yml", "config path")
fs.StringVar(&cfgPath, "config", ".slog.yml", "path to config")
if err := fs.Parse(os.Args[1:]); err != nil {
return fmt.Errorf("parsing flags: %w", err)
}

cfg, err := loadConfig(cfgPath)
cfgFile, err := os.Open(cfgPath)
if err != nil {
return fmt.Errorf("opening file: %w", err)
}
defer errorsx.Close(cfgFile, &err)

cfg, err := readConfig(cfgFile)
if err != nil {
return err
}

if err := os.Mkdir(cfg.Pkg, 0o755); err != nil && !errors.Is(err, os.ErrExist) {
return fmt.Errorf("mkdir %s: %w", cfg.Pkg, err)
return fmt.Errorf("mkdir: %w", err)
}

f, err := os.Create(cfg.Pkg + "/attr.go")
genFile, err := os.Create(filepath.Join(cfg.Pkg, "attr.go"))
if err != nil {
return fmt.Errorf("creating file: %w", err)
}
defer errorsx.Close(f, &err)
defer errorsx.Close(genFile, &err)

if err := tmpl.Execute(f, cfg); err != nil {
if err := tmpl.Execute(genFile, cfg); err != nil {
return fmt.Errorf("executing template: %w", err)
}

return nil
}

var tmpl = template.Must(template.New("").Funcs(funcs).Parse(
`// Code generated by go-simpler.org/slog-gen. DO NOT EDIT.
package {{.Pkg}}
{{range .Imports -}}
import "{{.}}"
{{end -}}
{{range $_, $attr := .Attrs}}
func {{snakeToCamel $attr.Key}}(value {{$attr.Type}}) slog.Attr {
return slog.{{slogFunc $attr.Type}}("{{$attr.Key}}", value)
}
{{end}}`,
))

type (
config struct {
Pkg string
Imports []string
Attrs []attr
}
attr struct {
Key string
Type string
}
)

func loadConfig(path string) (*config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading config: %w", err)
}

var cfg struct {
Pkg string `yaml:"pkg"`
Imports []string `yaml:"imports"`
Attrs map[string]string `yaml:"attrs"` // key:type
}
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("decoding config: %w", err)
}

cfg.Imports = append(cfg.Imports, "log/slog")
sort.Strings(cfg.Imports)

// TODO: rewrite when maps.Keys() is released.
keys := make([]string, 0, len(cfg.Attrs))
for key := range cfg.Attrs {
keys = append(keys, key)
}
sort.Strings(keys)

attrs := make([]attr, len(keys))
for i, key := range keys {
attrs[i] = attr{Key: key, Type: cfg.Attrs[key]}
}

return &config{
Pkg: cfg.Pkg,
Imports: cfg.Imports,
Attrs: attrs,
}, nil
}

// nolint:staticcheck // SA1019: strings.Title is deprecated but works just fine here.
var funcs = template.FuncMap{
"snakeToCamel": func(s string) string {
parts := strings.Split(s, "_")
for i := range parts {
parts[i] = strings.Title(parts[i])
}
return strings.Join(parts, "")
},
"slogFunc": func(typ string) string {
switch s := strings.Title(strings.TrimPrefix(typ, "time.")); s {
case "String", "Int64", "Int", "Uint64", "Float64", "Bool", "Time", "Duration":
return s
default:
return "Any"
}
},
}

0 comments on commit 4f96b79

Please sign in to comment.