Compare commits

...

16 Commits

Author SHA1 Message Date
Adam Bouqdib b59fe7c6e6 Add transform to MACRO_CASE and phrase 2019-08-21 14:35:35 +01:00
Álvaro López Espinosa d27f80ae12
Merge pull request #48 from ltpquang/master
Fix runsThreshold limit condition
2019-07-18 11:50:21 +01:00
Quang Le 275c2734c9 Fix error when number of runs reaches the run threshold 2019-07-18 10:39:11 +07:00
Quang Le 814e4e4ad4 Add test cases that make the endtoend test failed 2019-07-18 10:37:23 +07:00
Quang Le 89dcacfe6f Fix typos 2019-07-18 09:53:12 +07:00
Álvaro cbdc8d0944 Minimal changes in Readme 2019-05-25 17:21:47 +01:00
Álvaro 642f2c0436 Add support for -linecomment 2019-05-25 17:02:36 +01:00
Álvaro 0f1dc27ec9 Add support for go modules 2019-05-25 16:05:55 +01:00
Álvaro 7820f3770a Remove specific version importers and use the default one 2019-05-25 14:58:46 +01:00
Álvaro c0f685c6bf Merge remote-tracking branch 'origin/master' 2019-05-25 14:30:39 +01:00
Álvaro b6edbe712f Use go modules 2019-05-25 14:30:28 +01:00
Álvaro López Espinosa ddc705194a
Merge pull request #47 from osctobe/master
Create tempfile in the output directory
2019-05-25 14:29:03 +01:00
Michał Mirosław 0ef6de982e Create tempfile in the output directory 2019-05-23 14:01:08 +02:00
Álvaro López Espinosa 0fa5a42e5f
Merge pull request #45 from sekky0905/add-how-to-install-to-readme
Add Install section to README
2019-05-20 17:25:57 +01:00
Álvaro López Espinosa 9875c3a8c3
Merge pull request #33 from dterei/master
Add comment support + temporary file before writing
2019-05-20 16:53:50 +01:00
sekky0905 5d94885c07 Add Install section to README 2019-05-20 12:48:56 +09:00
19 changed files with 438 additions and 374 deletions

View File

@ -2,6 +2,15 @@
Enumer is a tool to generate Go code that adds useful methods to Go enums (constants with a specific type). Enumer is a tool to generate Go code that adds useful methods to Go enums (constants with a specific type).
It started as a fork of [Rob Pikes Stringer tool](https://godoc.org/golang.org/x/tools/cmd/stringer). It started as a fork of [Rob Pikes Stringer tool](https://godoc.org/golang.org/x/tools/cmd/stringer).
## Install
Enumer can be installed as any other go command:
```
go get github.com/alvaroloes/enumer
```
After that, the `enumer` executable will be in "$GOPATH/bin" folder and you can use it with `go generate`
## Generated functions and methods ## Generated functions and methods
When Enumer is applied to a type, it will generate: When Enumer is applied to a type, it will generate:
@ -119,7 +128,7 @@ For example, the command `enumer -type=MyType -json -transform=snake` would gene
```go ```go
name := MyTypeValue.String() // name => "my_type_value" name := MyTypeValue.String() // name => "my_type_value"
``` ```
**Note**: The transformation only works form CamelCase to snake_case or kebab-case, not the other way around. **Note**: The transformation only works from CamelCase to snake_case or kebab-case, not the other way around.
## How to use ## How to use
The usage of Enumer is the same as Stringer, so you can refer to the [Stringer docs](https://godoc.org/golang.org/x/tools/cmd/stringer) The usage of Enumer is the same as Stringer, so you can refer to the [Stringer docs](https://godoc.org/golang.org/x/tools/cmd/stringer)
@ -130,7 +139,8 @@ There are four boolean flags: `json`, `text`, `yaml` and `sql`. You can use any
For enum string representation transformation the `transform` and `trimprefix` flags For enum string representation transformation the `transform` and `trimprefix` flags
were added (i.e. `enumer -type=MyType -json -transform=snake`). were added (i.e. `enumer -type=MyType -json -transform=snake`).
Possible transform values are `snake` and `kebab` for transformation to snake_case and kebab-case accordingly. Possible transform values are `snake`, `kebab` `macro` and `phrase` for transformation
to snake_case, kebab-case, MACRO_CASE or a phrase (i.e. space-separated words) accordingly.
The default value for `transform` flag is `noop` which means no transformation will be performed. The default value for `transform` flag is `noop` which means no transformation will be performed.
If a prefix is provided via the `trimprefix` flag, it will be trimmed from the start of each name (before If a prefix is provided via the `trimprefix` flag, it will be trimmed from the start of each name (before

View File

@ -111,7 +111,14 @@ func copy(to, from string) error {
// run runs a single command and returns an error if it does not succeed. // run runs a single command and returns an error if it does not succeed.
// os/exec should have this function, to be honest. // os/exec should have this function, to be honest.
func run(name string, arg ...string) error { func run(name string, arg ...string) error {
return runInDir(".", name, arg...)
}
// runInDir runs a single command in directory dir and returns an error if
// it does not succeed.
func runInDir(dir, name string, arg ...string) error {
cmd := exec.Command(name, arg...) cmd := exec.Command(name, arg...)
cmd.Dir = dir
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
return cmd.Run() return cmd.Run()

View File

@ -79,7 +79,7 @@ func (g *Generator) buildBasicExtras(runs [][]Value, typeName string, runsThresh
// Print the basic extra methods // Print the basic extra methods
g.Printf(stringNameToValueMethod, typeName) g.Printf(stringNameToValueMethod, typeName)
g.Printf(stringValuesMethod, typeName) g.Printf(stringValuesMethod, typeName)
if len(runs) < runsThreshold { if len(runs) <= runsThreshold {
g.Printf(stringBelongsMethodLoop, typeName) g.Printf(stringBelongsMethodLoop, typeName)
} else { // There is a map of values, the code is simpler then } else { // There is a map of values, the code is simpler then
g.Printf(stringBelongsMethodSet, typeName) g.Printf(stringBelongsMethodSet, typeName)

8
go.mod Normal file
View File

@ -0,0 +1,8 @@
module github.com/alvaroloes/enumer
go 1.12
require (
github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1
golang.org/x/tools v0.0.0-20190525145741-7be61e1b0e51
)

9
go.sum Normal file
View File

@ -0,0 +1,9 @@
github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1 h1:/I3lTljEEDNYLho3/FUB7iD/oc2cEFgVmbHzV+O0PtU=
github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1/go.mod h1:eD5JxqMiuNYyFNmyY9rkJ/slN8y59oEu4Ei7F8OoKWQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190525145741-7be61e1b0e51 h1:RhYYBLDB5MoVkvoNGMNk+DSj7WoGhySvIvtEjTyiP74=
golang.org/x/tools v0.0.0-20190525145741-7be61e1b0e51/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=

View File

@ -10,6 +10,9 @@
package main package main
import ( import (
"io/ioutil"
"os"
"path/filepath"
"strings" "strings"
"testing" "testing"
) )
@ -18,7 +21,7 @@ import (
type Golden struct { type Golden struct {
name string name string
input string // input; the package clause is provided when running the test. input string // input; the package clause is provided when running the test.
output string // exected output. output string // expected output.
} }
var golden = []Golden{ var golden = []Golden{
@ -31,28 +34,32 @@ var golden = []Golden{
} }
var goldenJSON = []Golden{ var goldenJSON = []Golden{
{"prime", primeJsonIn, primeJsonOut}, {"prime with JSON", primeJsonIn, primeJsonOut},
} }
var goldenText = []Golden{ var goldenText = []Golden{
{"prime", primeTextIn, primeTextOut}, {"prime with Text", primeTextIn, primeTextOut},
} }
var goldenYAML = []Golden{ var goldenYAML = []Golden{
{"prime", primeYamlIn, primeYamlOut}, {"prime with YAML", primeYamlIn, primeYamlOut},
} }
var goldenSQL = []Golden{ var goldenSQL = []Golden{
{"prime", primeSqlIn, primeSqlOut}, {"prime with SQL", primeSqlIn, primeSqlOut},
} }
var goldenJSONAndSQL = []Golden{ var goldenJSONAndSQL = []Golden{
{"prime", primeJsonAndSqlIn, primeJsonAndSqlOut}, {"prime with JSONAndSQL", primeJsonAndSqlIn, primeJsonAndSqlOut},
} }
var goldenPrefix = []Golden{ var goldenPrefix = []Golden{
{"prefix", prefixIn, dayOut}, {"prefix", prefixIn, dayOut},
} }
var goldenWithLineComments = []Golden{
{"primer with line Comments", primeWithLineCommentIn, primeWithLineCommentOut},
}
// Each example starts with "type XXX [u]int", with a single space separating them. // Each example starts with "type XXX [u]int", with a single space separating them.
// Simple test: enumeration of type int starting at 0. // Simple test: enumeration of type int starting at 0.
@ -1022,6 +1029,90 @@ const (
) )
` `
const primeWithLineCommentIn = `type Prime int
const (
p2 Prime = 2
p3 Prime = 3
p5 Prime = 5
p7 Prime = 7
p77 Prime = 7 // Duplicate; note that p77 doesn't appear below.
p11 Prime = 11
p13 Prime = 13
p17 Prime = 17
p19 Prime = 19
p23 Prime = 23
p29 Prime = 29
p37 Prime = 31
p41 Prime = 41
p43 Prime = 43
)
`
const primeWithLineCommentOut = `
const _PrimeName = "p2p3GoodPrimep7p11p13p17p19p23p29p37TwinPrime41Twin prime 43"
var _PrimeMap = map[Prime]string{
2: _PrimeName[0:2],
3: _PrimeName[2:4],
5: _PrimeName[4:13],
7: _PrimeName[13:15],
11: _PrimeName[15:18],
13: _PrimeName[18:21],
17: _PrimeName[21:24],
19: _PrimeName[24:27],
23: _PrimeName[27:30],
29: _PrimeName[30:33],
31: _PrimeName[33:36],
41: _PrimeName[36:47],
43: _PrimeName[47:60],
}
func (i Prime) String() string {
if str, ok := _PrimeMap[i]; ok {
return str
}
return fmt.Sprintf("Prime(%d)", i)
}
var _PrimeValues = []Prime{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 41, 43}
var _PrimeNameToValueMap = map[string]Prime{
_PrimeName[0:2]: 2,
_PrimeName[2:4]: 3,
_PrimeName[4:13]: 5,
_PrimeName[13:15]: 7,
_PrimeName[15:18]: 11,
_PrimeName[18:21]: 13,
_PrimeName[21:24]: 17,
_PrimeName[24:27]: 19,
_PrimeName[27:30]: 23,
_PrimeName[30:33]: 29,
_PrimeName[33:36]: 31,
_PrimeName[36:47]: 41,
_PrimeName[47:60]: 43,
}
// PrimeString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func PrimeString(s string) (Prime, error) {
if val, ok := _PrimeNameToValueMap[s]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to Prime values", s)
}
// PrimeValues returns all values of the enum
func PrimeValues() []Prime {
return _PrimeValues
}
// IsAPrime returns "true" if the value is listed in the enum definition. "false" otherwise
func (i Prime) IsAPrime() bool {
_, ok := _PrimeMap[i]
return ok
}
`
func TestGolden(t *testing.T) { func TestGolden(t *testing.T) {
for _, test := range golden { for _, test := range golden {
runGoldenTest(t, test, false, false, false, false, "") runGoldenTest(t, test, false, false, false, false, "")
@ -1050,13 +1141,30 @@ func runGoldenTest(t *testing.T, test Golden, generateJSON, generateYAML, genera
var g Generator var g Generator
input := "package test\n" + test.input input := "package test\n" + test.input
file := test.name + ".go" file := test.name + ".go"
g.parsePackage(".", []string{file}, input)
dir, err := ioutil.TempDir("", "stringer")
if err != nil {
t.Error(err)
}
defer func() {
err = os.RemoveAll(dir)
if err != nil {
t.Error(err)
}
}()
absFile := filepath.Join(dir, file)
err = ioutil.WriteFile(absFile, []byte(input), 0644)
if err != nil {
t.Error(err)
}
g.parsePackage([]string{absFile})
// Extract the name and type of the constant from the first line. // Extract the name and type of the constant from the first line.
tokens := strings.SplitN(test.input, " ", 3) tokens := strings.SplitN(test.input, " ", 3)
if len(tokens) != 3 { if len(tokens) != 3 {
t.Fatalf("%s: need type declaration on first line", test.name) t.Fatalf("%s: need type declaration on first line", test.name)
} }
g.generate(tokens[1], generateJSON, generateYAML, generateSQL, generateText, "noop", prefix) g.generate(tokens[1], generateJSON, generateYAML, generateSQL, generateText, "noop", prefix, false)
got := string(g.format()) got := string(g.format())
if got != test.output { if got != test.output {
t.Errorf("%s: got\n====\n%s====\nexpected\n====%s", test.name, got, test.output) t.Errorf("%s: got\n====\n%s====\nexpected\n====%s", test.name, got, test.output)

View File

@ -1,16 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.9
package main
import (
"go/importer"
"go/types"
)
func defaultImporter() types.Importer {
return importer.Default()
}

View File

@ -1,16 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.9
package main
import (
"go/importer"
"go/types"
)
func defaultImporter() types.Importer {
return importer.For("source", nil)
}

View File

@ -15,12 +15,12 @@ import (
"flag" "flag"
"fmt" "fmt"
"go/ast" "go/ast"
"go/build"
exact "go/constant" exact "go/constant"
"go/format" "go/format"
"go/parser" "go/importer"
"go/token" "go/token"
"go/types" "go/types"
"golang.org/x/tools/go/packages"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
@ -51,6 +51,7 @@ var (
output = flag.String("output", "", "output file name; default srcdir/<type>_string.go") output = flag.String("output", "", "output file name; default srcdir/<type>_string.go")
transformMethod = flag.String("transform", "noop", "enum item name transformation method. Default: noop") transformMethod = flag.String("transform", "noop", "enum item name transformation method. Default: noop")
trimPrefix = flag.String("trimprefix", "", "transform each item name by removing a prefix. Default: \"\"") trimPrefix = flag.String("trimprefix", "", "transform each item name by removing a prefix. Default: \"\"")
lineComment = flag.Bool("linecomment", false, "use line comment text as printed text when present")
) )
var comments arrayFlags var comments arrayFlags
@ -96,12 +97,12 @@ func main() {
if len(args) == 1 && isDirectory(args[0]) { if len(args) == 1 && isDirectory(args[0]) {
dir = args[0] dir = args[0]
g.parsePackageDir(args[0])
} else { } else {
dir = filepath.Dir(args[0]) dir = filepath.Dir(args[0])
g.parsePackageFiles(args)
} }
g.parsePackage(args)
// Print the header and package clause. // Print the header and package clause.
g.Printf("// Code generated by \"enumer %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " ")) g.Printf("// Code generated by \"enumer %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " "))
g.Printf("\n") g.Printf("\n")
@ -120,7 +121,7 @@ func main() {
// Run generate for each type. // Run generate for each type.
for _, typeName := range types { for _, typeName := range types {
g.generate(typeName, *json, *yaml, *sql, *text, *transformMethod, *trimPrefix) g.generate(typeName, *json, *yaml, *sql, *text, *transformMethod, *trimPrefix, *lineComment)
} }
// Format the output. // Format the output.
@ -134,7 +135,8 @@ func main() {
} }
// Write to tmpfile first // Write to tmpfile first
tmpFile, err := ioutil.TempFile("", fmt.Sprintf("%s_enumer_", types[0])) tmpName := fmt.Sprintf("%s_enumer_", filepath.Base(types[0]))
tmpFile, err := ioutil.TempFile(filepath.Dir(types[0]), tmpName)
if err != nil { if err != nil {
log.Fatalf("creating temporary file for output: %s", err) log.Fatalf("creating temporary file for output: %s", err)
} }
@ -192,76 +194,111 @@ type Package struct {
typesPkg *types.Package typesPkg *types.Package
} }
// parsePackageDir parses the package residing in the directory. //// parsePackageDir parses the package residing in the directory.
func (g *Generator) parsePackageDir(directory string) { //func (g *Generator) parsePackageDir(directory string) {
pkg, err := build.Default.ImportDir(directory, 0) // pkg, err := build.Default.ImportDir(directory, 0)
// if err != nil {
// log.Fatalf("cannot process directory %s: %s", directory, err)
// }
// var names []string
// names = append(names, pkg.GoFiles...)
// names = append(names, pkg.CgoFiles...)
// // TODO: Need to think about constants in test files. Maybe write type_string_test.go
// // in a separate pass? For later.
// // names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
// names = append(names, pkg.SFiles...)
// names = prefixDirectory(directory, names)
// g.parsePackage(directory, names, nil)
//}
//
//// parsePackageFiles parses the package occupying the named files.
//func (g *Generator) parsePackageFiles(names []string) {
// g.parsePackage(".", names, nil)
//}
//
//// prefixDirectory places the directory name on the beginning of each name in the list.
//func prefixDirectory(directory string, names []string) []string {
// if directory == "." {
// return names
// }
// ret := make([]string, len(names))
// for i, name := range names {
// ret[i] = filepath.Join(directory, name)
// }
// return ret
//}
//// parsePackage analyzes the single package constructed from the named files.
//// If text is non-nil, it is a string to be used instead of the content of the file,
//// to be used for testing. parsePackage exits if there is an error.
//func (g *Generator) parsePackage(directory string, names []string, text interface{}) {
// var files []*File
// var astFiles []*ast.File
// g.pkg = new(Package)
// fs := token.NewFileSet()
// for _, name := range names {
// if !strings.HasSuffix(name, ".go") {
// continue
// }
// parsedFile, err := parser.ParseFile(fs, name, text, 0)
// if err != nil {
// log.Fatalf("parsing package: %s: %s", name, err)
// }
// astFiles = append(astFiles, parsedFile)
// files = append(files, &File{
// file: parsedFile,
// pkg: g.pkg,
// })
// }
// if len(astFiles) == 0 {
// log.Fatalf("%s: no buildable Go files", directory)
// }
// g.pkg.name = astFiles[0].Name.Name
// g.pkg.files = files
// g.pkg.dir = directory
// // Type check the package.
// g.pkg.check(fs, astFiles)
//}
// parsePackage analyzes the single package constructed from the patterns and tags.
// parsePackage exits if there is an error.
func (g *Generator) parsePackage(patterns []string) {
cfg := &packages.Config{
Mode: packages.LoadSyntax,
// TODO: Need to think about constants in test files. Maybe write type_string_test.go
// in a separate pass? For later.
Tests: false,
}
pkgs, err := packages.Load(cfg, patterns...)
if err != nil { if err != nil {
log.Fatalf("cannot process directory %s: %s", directory, err) log.Fatal(err)
} }
var names []string if len(pkgs) != 1 {
names = append(names, pkg.GoFiles...) log.Fatalf("error: %d packages found", len(pkgs))
names = append(names, pkg.CgoFiles...) }
// TODO: Need to think about constants in test files. Maybe write type_string_test.go g.addPackage(pkgs[0])
// in a separate pass? For later.
// names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
names = append(names, pkg.SFiles...)
names = prefixDirectory(directory, names)
g.parsePackage(directory, names, nil)
} }
// parsePackageFiles parses the package occupying the named files. // addPackage adds a type checked Package and its syntax files to the generator.
func (g *Generator) parsePackageFiles(names []string) { func (g *Generator) addPackage(pkg *packages.Package) {
g.parsePackage(".", names, nil) g.pkg = &Package{
} name: pkg.Name,
defs: pkg.TypesInfo.Defs,
// prefixDirectory places the directory name on the beginning of each name in the list. files: make([]*File, len(pkg.Syntax)),
func prefixDirectory(directory string, names []string) []string {
if directory == "." {
return names
} }
ret := make([]string, len(names))
for i, name := range names {
ret[i] = filepath.Join(directory, name)
}
return ret
}
// parsePackage analyzes the single package constructed from the named files. for i, file := range pkg.Syntax {
// If text is non-nil, it is a string to be used instead of the content of the file, g.pkg.files[i] = &File{
// to be used for testing. parsePackage exits if there is an error. file: file,
func (g *Generator) parsePackage(directory string, names []string, text interface{}) {
var files []*File
var astFiles []*ast.File
g.pkg = new(Package)
fs := token.NewFileSet()
for _, name := range names {
if !strings.HasSuffix(name, ".go") {
continue
}
parsedFile, err := parser.ParseFile(fs, name, text, 0)
if err != nil {
log.Fatalf("parsing package: %s: %s", name, err)
}
astFiles = append(astFiles, parsedFile)
files = append(files, &File{
file: parsedFile,
pkg: g.pkg, pkg: g.pkg,
}) }
} }
if len(astFiles) == 0 {
log.Fatalf("%s: no buildable Go files", directory)
}
g.pkg.name = astFiles[0].Name.Name
g.pkg.files = files
g.pkg.dir = directory
// Type check the package.
g.pkg.check(fs, astFiles)
} }
// check type-checks the package. The package must be OK to proceed. // check type-checks the package. The package must be OK to proceed.
func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) { func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) {
pkg.defs = make(map[*ast.Ident]types.Object) pkg.defs = make(map[*ast.Ident]types.Object)
config := types.Config{Importer: defaultImporter(), FakeImportC: true} config := types.Config{Importer: importer.Default(), FakeImportC: true}
info := &types.Info{ info := &types.Info{
Defs: pkg.defs, Defs: pkg.defs,
} }
@ -279,6 +316,14 @@ func (g *Generator) transformValueNames(values []Value, transformMethod string)
sep = '_' sep = '_'
case "kebab": case "kebab":
sep = '-' sep = '-'
case "phrase":
sep = ' '
case "macro":
sep = '_'
for i := range values {
values[i].name = strings.ToUpper(name.Delimit(values[i].name, sep))
}
return
default: default:
return return
} }
@ -295,8 +340,16 @@ func (g *Generator) trimValueNames(values []Value, prefix string) {
} }
} }
func (g *Generator) replaceValuesWithLineComment(values []Value) {
for i, val := range values {
if val.comment != "" {
values[i].name = val.comment
}
}
}
// generate produces the String method for the named type. // generate produces the String method for the named type.
func (g *Generator) generate(typeName string, includeJSON, includeYAML, includeSQL, includeText bool, transformMethod string, trimPrefix string) { func (g *Generator) generate(typeName string, includeJSON, includeYAML, includeSQL, includeText bool, transformMethod string, trimPrefix string, lineComment bool) {
values := make([]Value, 0, 100) values := make([]Value, 0, 100)
for _, file := range g.pkg.files { for _, file := range g.pkg.files {
// Set the state for this run of the walker. // Set the state for this run of the walker.
@ -316,6 +369,10 @@ func (g *Generator) generate(typeName string, includeJSON, includeYAML, includeS
g.transformValueNames(values, transformMethod) g.transformValueNames(values, transformMethod)
if lineComment {
g.replaceValuesWithLineComment(values)
}
runs := splitIntoRuns(values) runs := splitIntoRuns(values)
// The decision of which pattern to use depends on the number of // The decision of which pattern to use depends on the number of
// runs in the numbers. If there's only one, it's easy. For more than // runs in the numbers. If there's only one, it's easy. For more than
@ -407,9 +464,10 @@ type Value struct {
// this matters is when sorting. // this matters is when sorting.
// Much of the time the str field is all we need; it is printed // Much of the time the str field is all we need; it is printed
// by Value.String. // by Value.String.
value uint64 // Will be converted to int64 when needed. value uint64 // Will be converted to int64 when needed.
signed bool // Whether the constant is a signed type. signed bool // Whether the constant is a signed type.
str string // The string representation given by the "go/exact" package. str string // The string representation given by the "go/exact" package.
comment string // The comment on the right of the constant
} }
func (v *Value) String() string { func (v *Value) String() string {
@ -494,11 +552,17 @@ func (f *File) genDecl(node ast.Node) bool {
if !isInt { if !isInt {
u64 = uint64(i64) u64 = uint64(i64)
} }
comment := ""
if c := vspec.Comment; c != nil && len(c.List) == 1 {
comment = strings.TrimSpace(c.Text())
}
v := Value{ v := Value{
name: name.Name, name: name.Name,
value: u64, value: u64,
signed: info&types.IsUnsigned == 0, signed: info&types.IsUnsigned == 0,
str: value.String(), str: value.String(),
comment: comment,
} }
f.values = append(f.values, v) f.values = append(f.values, v)
} }

47
testdata/thresholdeq.go vendored Normal file
View File

@ -0,0 +1,47 @@
package main
import "fmt"
type Thresholdeq int
const (
req1 Thresholdeq = 2
req2 Thresholdeq = 4
req3 Thresholdeq = 6
req4 Thresholdeq = 8
req5 Thresholdeq = 10
req6 Thresholdeq = 12
req7 Thresholdeq = 14
req8 Thresholdeq = 16
req9 Thresholdeq = 18
req10 Thresholdeq = 20
)
func main() {
ck(1, "Thresholdeq(1)")
ck(req1, "req1")
ck(3, "Thresholdeq(3)")
ck(req2, "req2")
ck(5, "Thresholdeq(5)")
ck(req3, "req3")
ck(7, "Thresholdeq(7)")
ck(req4, "req4")
ck(9, "Thresholdeq(9)")
ck(req5, "req5")
ck(11, "Thresholdeq(11)")
ck(req6, "req6")
ck(13, "Thresholdeq(13)")
ck(req7, "req7")
ck(15, "Thresholdeq(15)")
ck(req8, "req8")
ck(17, "Thresholdeq(17)")
ck(req9, "req9")
ck(19, "Thresholdeq(19)")
ck(req10, "req10")
}
func ck(thresholdeq Thresholdeq, str string) {
if fmt.Sprint(thresholdeq) != str {
panic("thresholdeq.go: " + str)
}
}

50
testdata/thresholdgt.go vendored Normal file
View File

@ -0,0 +1,50 @@
package main
import "fmt"
type Thresholdgt int
const (
rgt1 Thresholdgt = 2
rgt2 Thresholdgt = 4
rgt3 Thresholdgt = 6
rgt4 Thresholdgt = 8
rgt5 Thresholdgt = 10
rgt6 Thresholdgt = 12
rgt7 Thresholdgt = 14
rgt8 Thresholdgt = 16
rgt9 Thresholdgt = 18
rgt10 Thresholdgt = 20
rgt11 Thresholdgt = 22
)
func main() {
ck(1, "Thresholdgt(1)")
ck(rgt1, "rgt1")
ck(3, "Thresholdgt(3)")
ck(rgt2, "rgt2")
ck(5, "Thresholdgt(5)")
ck(rgt3, "rgt3")
ck(7, "Thresholdgt(7)")
ck(rgt4, "rgt4")
ck(9, "Thresholdgt(9)")
ck(rgt5, "rgt5")
ck(11, "Thresholdgt(11)")
ck(rgt6, "rgt6")
ck(13, "Thresholdgt(13)")
ck(rgt7, "rgt7")
ck(15, "Thresholdgt(15)")
ck(rgt8, "rgt8")
ck(17, "Thresholdgt(17)")
ck(rgt9, "rgt9")
ck(19, "Thresholdgt(19)")
ck(rgt10, "rgt10")
ck(21, "Thresholdgt(21)")
ck(rgt11, "rgt11")
}
func ck(thresholdgt Thresholdgt, str string) {
if fmt.Sprint(thresholdgt) != str {
panic("thresholdgt.go: " + str)
}
}

44
testdata/thresholdlt.go vendored Normal file
View File

@ -0,0 +1,44 @@
package main
import "fmt"
type Thresholdlt int
const (
rlt1 Thresholdlt = 2
rlt2 Thresholdlt = 4
rlt3 Thresholdlt = 6
rlt4 Thresholdlt = 8
rlt5 Thresholdlt = 10
rlt6 Thresholdlt = 12
rlt7 Thresholdlt = 14
rlt8 Thresholdlt = 16
rlt9 Thresholdlt = 18
)
func main() {
ck(1, "Thresholdlt(1)")
ck(rlt1, "rlt1")
ck(3, "Thresholdlt(3)")
ck(rlt2, "rlt2")
ck(5, "Thresholdlt(5)")
ck(rlt3, "rlt3")
ck(7, "Thresholdlt(7)")
ck(rlt4, "rlt4")
ck(9, "Thresholdlt(9)")
ck(rlt5, "rlt5")
ck(11, "Thresholdlt(11)")
ck(rlt6, "rlt6")
ck(13, "Thresholdlt(13)")
ck(rlt7, "rlt7")
ck(15, "Thresholdlt(15)")
ck(rlt8, "rlt8")
ck(17, "Thresholdlt(17)")
ck(rlt9, "rlt9")
}
func ck(thresholdlt Thresholdlt, str string) {
if fmt.Sprint(thresholdlt) != str {
panic("thresholdlt.go: " + str)
}
}

View File

@ -54,7 +54,7 @@ Outer:
for n, test := range splitTests { for n, test := range splitTests {
values := make([]Value, len(test.input)) values := make([]Value, len(test.input))
for i, v := range test.input { for i, v := range test.input {
values[i] = Value{"", v, test.signed, fmt.Sprint(v)} values[i] = Value{"", v, test.signed, fmt.Sprint(v), ""}
} }
runs := splitIntoRuns(values) runs := splitIntoRuns(values)
if len(runs) != len(test.output) { if len(runs) != len(test.output) {

View File

@ -1,3 +0,0 @@
To the extent possible under law, Pascal S. de Kloe has waived all
copyright and related or neighboring rights to Colfer. This work is
published from The Netherlands.

View File

@ -1,6 +0,0 @@
[![GoDoc](https://godoc.org/github.com/pascaldekloe/name?status.svg)](https://godoc.org/github.com/pascaldekloe/name)
Naming convention library for the Go programming language (golang).
This is free and unencumbered software released into the
[public domain](http://creativecommons.org/publicdomain/zero/1.0).

View File

@ -1,112 +0,0 @@
// Package name implements naming conventions.
package name
import "unicode"
// CamelCase returns the medial capitals form of word sequence s.
// The input can be any case or even just a bunch of words.
// Upper case abbreviations are preserved.
// Argument upper sets the casing for the first rune.
func CamelCase(s string, upper bool) string {
if s == "" {
return ""
}
out := make([]rune, 1, len(s)+5)
for i, r := range s {
if i == 0 {
if upper {
r = unicode.ToUpper(r)
}
out[0] = r
continue
}
if i == 1 {
if !upper && unicode.Is(unicode.Lower, r) {
out[0] = unicode.ToLower(out[0])
}
upper = false
}
switch {
case unicode.IsLetter(r):
if upper {
r = unicode.ToUpper(r)
}
fallthrough
case unicode.IsNumber(r):
upper = false
out = append(out, r)
default:
upper = true
}
}
return string(out)
}
// SnakeCase is an alias for Delimit(s, '_').
func SnakeCase(s string) string {
return Delimit(s, '_')
}
// DotSeparated is an alias for Delimit(s, '.').
func DotSeparated(s string) string {
return Delimit(s, '.')
}
// Delimit returns word sequence s delimited with sep.
// The input can be any case or even just a bunch of words.
// Upper case abbreviations are preserved. Use strings.ToLower
// and strings.ToUpper to enforce a letter case.
func Delimit(s string, sep rune) string {
out := make([]rune, 0, len(s)+5)
for _, r := range s {
switch {
case unicode.IsUpper(r):
if last := len(out) - 1; last >= 0 && unicode.IsLower(out[last]) {
out = append(out, sep)
}
case unicode.IsLetter(r):
if i := len(out) - 1; i >= 0 {
if last := out[i]; unicode.IsUpper(last) {
out = out[:i]
if i > 0 && out[i-1] != sep {
out = append(out, sep)
}
out = append(out, unicode.ToLower(last))
}
}
case !unicode.IsNumber(r):
if i := len(out); i != 0 && out[i-1] != sep {
out = append(out, sep)
}
continue
}
out = append(out, r)
}
if len(out) == 0 {
return ""
}
// trim tailing separator
if i := len(out) - 1; out[i] == sep {
out = out[:i]
}
if len(out) == 1 {
out[0] = unicode.ToLower(out[0])
}
return string(out)
}

View File

@ -1,103 +0,0 @@
package name
import (
"testing"
)
type goldenCase struct {
snake, lowerCamel, upperCamel string
}
var goldenCases = []goldenCase{
{"", "", ""},
{"i", "i", "I"},
{"name", "name", "Name"},
{"ID", "ID", "ID"},
{"wi_fi", "wiFi", "WiFi"},
// single outer abbreviation
{"vitamin_C", "vitaminC", "VitaminC"},
{"T_cell", "TCell", "TCell"},
// double outer abbreviation
{"master_DB", "masterDB", "MasterDB"},
{"IO_bounds", "IOBounds", "IOBounds"},
// tripple outer abbreviation
{"main_API", "mainAPI", "MainAPI"},
{"TCP_conn", "TCPConn", "TCPConn"},
// inner abbreviation
{"raw_URL_query", "rawURLQuery", "RawURLQuery"},
// numbers
{"4x4", "4x4", "4x4"},
{"no5", "no5", "No5"},
{"DB2", "DB2", "DB2"},
{"3M", "3M", "3M"},
{"7_up", "7Up", "7Up"},
{"20th", "20th", "20th"},
}
func TestSnakeToSnake(t *testing.T) {
for _, golden := range goldenCases {
s := golden.snake
if got := SnakeCase(s); got != s {
t.Errorf("%q: got %q", s, got)
}
}
}
func TestLowerCamelToLowerCamel(t *testing.T) {
for _, golden := range goldenCases {
s := golden.lowerCamel
if got := CamelCase(s, false); got != s {
t.Errorf("%q: got %q", s, got)
}
}
}
func TestUpperCamelToUpperCamel(t *testing.T) {
for _, golden := range goldenCases {
s := golden.upperCamel
if got := CamelCase(s, true); got != s {
t.Errorf("%q: got %q", s, got)
}
}
}
func TestSnakeToLowerCamel(t *testing.T) {
for _, golden := range goldenCases {
snake, want := golden.snake, golden.lowerCamel
if got := CamelCase(snake, false); got != want {
t.Errorf("%q: got %q, want %q", snake, got, want)
}
}
}
func TestSnakeToUpperCamel(t *testing.T) {
for _, golden := range goldenCases {
snake, want := golden.snake, golden.upperCamel
if got := CamelCase(snake, true); got != want {
t.Errorf("%q: got %q, want %q", snake, got, want)
}
}
}
func TestLowerCamelToSnake(t *testing.T) {
for _, golden := range goldenCases {
camel, want := golden.lowerCamel, golden.snake
if got := SnakeCase(camel); got != want {
t.Errorf("%q: got %q, want %q", camel, got, want)
}
}
}
func TestUpperCamelToSnake(t *testing.T) {
for _, golden := range goldenCases {
camel, want := golden.upperCamel, golden.snake
if got := SnakeCase(camel); got != want {
t.Errorf("%q: got %q, want %q", camel, got, want)
}
}
}

View File

@ -1,27 +0,0 @@
package name_test
import (
"fmt"
"github.com/pascaldekloe/name"
)
func ExampleCamelCase() {
fmt.Println(name.CamelCase("pascal case", true))
fmt.Println(name.CamelCase("snake_to_camel AND CamelToCamel?", false))
// Output:
// PascalCase
// snakeToCamelANDCamelToCamel
}
func ExampleDelimit() {
// Garbage to Lisp-case:
fmt.Println(name.Delimit("* All Hype is aGoodThing (TM)", '-'))
// Builds a Java property key:
fmt.Println(name.DotSeparated("WebCrawler#socketTimeout"))
// Output:
// all-hype-is-a-good-thing-TM
// web.crawler.socket.timeout
}