forked from mirror/enumer
Compare commits
16 Commits
master
...
macro-case
Author | SHA1 | Date |
---|---|---|
Adam Bouqdib | b59fe7c6e6 | |
Álvaro López Espinosa | d27f80ae12 | |
Quang Le | 275c2734c9 | |
Quang Le | 814e4e4ad4 | |
Quang Le | 89dcacfe6f | |
Álvaro | cbdc8d0944 | |
Álvaro | 642f2c0436 | |
Álvaro | 0f1dc27ec9 | |
Álvaro | 7820f3770a | |
Álvaro | c0f685c6bf | |
Álvaro | b6edbe712f | |
Álvaro López Espinosa | ddc705194a | |
Michał Mirosław | 0ef6de982e | |
Álvaro López Espinosa | 0fa5a42e5f | |
Álvaro López Espinosa | 9875c3a8c3 | |
sekky0905 | 5d94885c07 |
14
README.md
14
README.md
|
@ -2,6 +2,15 @@
|
|||
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 Pike’s 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
|
||||
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
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
If a prefix is provided via the `trimprefix` flag, it will be trimmed from the start of each name (before
|
||||
|
|
|
@ -111,7 +111,14 @@ func copy(to, from string) error {
|
|||
// run runs a single command and returns an error if it does not succeed.
|
||||
// os/exec should have this function, to be honest.
|
||||
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.Dir = dir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
|
|
|
@ -79,7 +79,7 @@ func (g *Generator) buildBasicExtras(runs [][]Value, typeName string, runsThresh
|
|||
// Print the basic extra methods
|
||||
g.Printf(stringNameToValueMethod, typeName)
|
||||
g.Printf(stringValuesMethod, typeName)
|
||||
if len(runs) < runsThreshold {
|
||||
if len(runs) <= runsThreshold {
|
||||
g.Printf(stringBelongsMethodLoop, typeName)
|
||||
} else { // There is a map of values, the code is simpler then
|
||||
g.Printf(stringBelongsMethodSet, typeName)
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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=
|
124
golden_test.go
124
golden_test.go
|
@ -10,6 +10,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
@ -18,7 +21,7 @@ import (
|
|||
type Golden struct {
|
||||
name string
|
||||
input string // input; the package clause is provided when running the test.
|
||||
output string // exected output.
|
||||
output string // expected output.
|
||||
}
|
||||
|
||||
var golden = []Golden{
|
||||
|
@ -31,28 +34,32 @@ var golden = []Golden{
|
|||
}
|
||||
|
||||
var goldenJSON = []Golden{
|
||||
{"prime", primeJsonIn, primeJsonOut},
|
||||
{"prime with JSON", primeJsonIn, primeJsonOut},
|
||||
}
|
||||
var goldenText = []Golden{
|
||||
{"prime", primeTextIn, primeTextOut},
|
||||
{"prime with Text", primeTextIn, primeTextOut},
|
||||
}
|
||||
|
||||
var goldenYAML = []Golden{
|
||||
{"prime", primeYamlIn, primeYamlOut},
|
||||
{"prime with YAML", primeYamlIn, primeYamlOut},
|
||||
}
|
||||
|
||||
var goldenSQL = []Golden{
|
||||
{"prime", primeSqlIn, primeSqlOut},
|
||||
{"prime with SQL", primeSqlIn, primeSqlOut},
|
||||
}
|
||||
|
||||
var goldenJSONAndSQL = []Golden{
|
||||
{"prime", primeJsonAndSqlIn, primeJsonAndSqlOut},
|
||||
{"prime with JSONAndSQL", primeJsonAndSqlIn, primeJsonAndSqlOut},
|
||||
}
|
||||
|
||||
var goldenPrefix = []Golden{
|
||||
{"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.
|
||||
|
||||
// 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) {
|
||||
for _, test := range golden {
|
||||
runGoldenTest(t, test, false, false, false, false, "")
|
||||
|
@ -1050,13 +1141,30 @@ func runGoldenTest(t *testing.T, test Golden, generateJSON, generateYAML, genera
|
|||
var g Generator
|
||||
input := "package test\n" + test.input
|
||||
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.
|
||||
tokens := strings.SplitN(test.input, " ", 3)
|
||||
if len(tokens) != 3 {
|
||||
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())
|
||||
if got != test.output {
|
||||
t.Errorf("%s: got\n====\n%s====\nexpected\n====%s", test.name, got, test.output)
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
206
stringer.go
206
stringer.go
|
@ -15,12 +15,12 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
exact "go/constant"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/importer"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
@ -51,6 +51,7 @@ var (
|
|||
output = flag.String("output", "", "output file name; default srcdir/<type>_string.go")
|
||||
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: \"\"")
|
||||
lineComment = flag.Bool("linecomment", false, "use line comment text as printed text when present")
|
||||
)
|
||||
|
||||
var comments arrayFlags
|
||||
|
@ -96,12 +97,12 @@ func main() {
|
|||
|
||||
if len(args) == 1 && isDirectory(args[0]) {
|
||||
dir = args[0]
|
||||
g.parsePackageDir(args[0])
|
||||
} else {
|
||||
dir = filepath.Dir(args[0])
|
||||
g.parsePackageFiles(args)
|
||||
}
|
||||
|
||||
g.parsePackage(args)
|
||||
|
||||
// Print the header and package clause.
|
||||
g.Printf("// Code generated by \"enumer %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " "))
|
||||
g.Printf("\n")
|
||||
|
@ -120,7 +121,7 @@ func main() {
|
|||
|
||||
// Run generate for each type.
|
||||
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.
|
||||
|
@ -134,7 +135,8 @@ func main() {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
log.Fatalf("creating temporary file for output: %s", err)
|
||||
}
|
||||
|
@ -192,76 +194,111 @@ type Package struct {
|
|||
typesPkg *types.Package
|
||||
}
|
||||
|
||||
// parsePackageDir parses the package residing in the directory.
|
||||
func (g *Generator) parsePackageDir(directory string) {
|
||||
pkg, err := build.Default.ImportDir(directory, 0)
|
||||
//// parsePackageDir parses the package residing in the directory.
|
||||
//func (g *Generator) parsePackageDir(directory string) {
|
||||
// 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 {
|
||||
log.Fatalf("cannot process directory %s: %s", directory, err)
|
||||
log.Fatal(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)
|
||||
if len(pkgs) != 1 {
|
||||
log.Fatalf("error: %d packages found", len(pkgs))
|
||||
}
|
||||
g.addPackage(pkgs[0])
|
||||
}
|
||||
|
||||
// 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
|
||||
// addPackage adds a type checked Package and its syntax files to the generator.
|
||||
func (g *Generator) addPackage(pkg *packages.Package) {
|
||||
g.pkg = &Package{
|
||||
name: pkg.Name,
|
||||
defs: pkg.TypesInfo.Defs,
|
||||
files: make([]*File, len(pkg.Syntax)),
|
||||
}
|
||||
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,
|
||||
for i, file := range pkg.Syntax {
|
||||
g.pkg.files[i] = &File{
|
||||
file: file,
|
||||
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.
|
||||
func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) {
|
||||
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{
|
||||
Defs: pkg.defs,
|
||||
}
|
||||
|
@ -279,6 +316,14 @@ func (g *Generator) transformValueNames(values []Value, transformMethod string)
|
|||
sep = '_'
|
||||
case "kebab":
|
||||
sep = '-'
|
||||
case "phrase":
|
||||
sep = ' '
|
||||
case "macro":
|
||||
sep = '_'
|
||||
for i := range values {
|
||||
values[i].name = strings.ToUpper(name.Delimit(values[i].name, sep))
|
||||
}
|
||||
return
|
||||
default:
|
||||
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.
|
||||
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)
|
||||
for _, file := range g.pkg.files {
|
||||
// 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)
|
||||
|
||||
if lineComment {
|
||||
g.replaceValuesWithLineComment(values)
|
||||
}
|
||||
|
||||
runs := splitIntoRuns(values)
|
||||
// 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
|
||||
|
@ -407,9 +464,10 @@ type Value struct {
|
|||
// this matters is when sorting.
|
||||
// Much of the time the str field is all we need; it is printed
|
||||
// by Value.String.
|
||||
value uint64 // Will be converted to int64 when needed.
|
||||
signed bool // Whether the constant is a signed type.
|
||||
str string // The string representation given by the "go/exact" package.
|
||||
value uint64 // Will be converted to int64 when needed.
|
||||
signed bool // Whether the constant is a signed type.
|
||||
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 {
|
||||
|
@ -494,11 +552,17 @@ func (f *File) genDecl(node ast.Node) bool {
|
|||
if !isInt {
|
||||
u64 = uint64(i64)
|
||||
}
|
||||
comment := ""
|
||||
if c := vspec.Comment; c != nil && len(c.List) == 1 {
|
||||
comment = strings.TrimSpace(c.Text())
|
||||
}
|
||||
|
||||
v := Value{
|
||||
name: name.Name,
|
||||
value: u64,
|
||||
signed: info&types.IsUnsigned == 0,
|
||||
str: value.String(),
|
||||
name: name.Name,
|
||||
value: u64,
|
||||
signed: info&types.IsUnsigned == 0,
|
||||
str: value.String(),
|
||||
comment: comment,
|
||||
}
|
||||
f.values = append(f.values, v)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@ Outer:
|
|||
for n, test := range splitTests {
|
||||
values := make([]Value, len(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)
|
||||
if len(runs) != len(test.output) {
|
||||
|
|
|
@ -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.
|
|
@ -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).
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue