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 |
26
README.md
26
README.md
|
@ -2,10 +2,19 @@
|
||||||
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 Pike’s Stringer tool](https://godoc.org/golang.org/x/tools/cmd/stringer).
|
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
|
## Generated functions and methods
|
||||||
When Enumer is applied to a type, it will generate:
|
When Enumer is applied to a type, it will generate:
|
||||||
|
|
||||||
* The following basic methods/functions:
|
* The following basic methods/functions:
|
||||||
|
|
||||||
* Method `String()`: returns the string representation of the enum value. This makes the enum conform
|
* Method `String()`: returns the string representation of the enum value. This makes the enum conform
|
||||||
the `Stringer` interface, so whenever you print an enum value, you'll get the string name instead of a number.
|
the `Stringer` interface, so whenever you print an enum value, you'll get the string name instead of a number.
|
||||||
|
@ -18,7 +27,7 @@ be almost meaningless or hard to trace or use by a human.
|
||||||
* When the flag `json` is provided, two additional methods will be generated, `MarshalJSON()` and `UnmarshalJSON()`. These make
|
* When the flag `json` is provided, two additional methods will be generated, `MarshalJSON()` and `UnmarshalJSON()`. These make
|
||||||
the enum conform to the `json.Marshaler` and `json.Unmarshaler` interfaces. Very useful to use it in JSON APIs.
|
the enum conform to the `json.Marshaler` and `json.Unmarshaler` interfaces. Very useful to use it in JSON APIs.
|
||||||
* When the flag `text` is provided, two additional methods will be generated, `MarshalText()` and `UnmarshalText()`. These make
|
* When the flag `text` is provided, two additional methods will be generated, `MarshalText()` and `UnmarshalText()`. These make
|
||||||
the enum conform to the `encoding.TextMarshaler` and `encoding.TextUnmarshaler` interfaces.
|
the enum conform to the `encoding.TextMarshaler` and `encoding.TextUnmarshaler` interfaces.
|
||||||
**Note:** If you use your enum values as keys in a map and you encode the map as _JSON_, you need this flag set to true to properly
|
**Note:** If you use your enum values as keys in a map and you encode the map as _JSON_, you need this flag set to true to properly
|
||||||
convert the map keys to json (strings). If not, the numeric values will be used instead
|
convert the map keys to json (strings). If not, the numeric values will be used instead
|
||||||
* When the flag `yaml` is provided, two additional methods will be generated, `MarshalYAML()` and `UnmarshalYAML()`. These make
|
* When the flag `yaml` is provided, two additional methods will be generated, `MarshalYAML()` and `UnmarshalYAML()`. These make
|
||||||
|
@ -40,19 +49,19 @@ const (
|
||||||
```
|
```
|
||||||
executing `enumer -type=Pill -json` will generate a new file with four basic methods and two extra for JSON:
|
executing `enumer -type=Pill -json` will generate a new file with four basic methods and two extra for JSON:
|
||||||
```go
|
```go
|
||||||
func (i Pill) String() string {
|
func (i Pill) String() string {
|
||||||
//...
|
//...
|
||||||
}
|
}
|
||||||
|
|
||||||
func PillString(s string) (Pill, error) {
|
func PillString(s string) (Pill, error) {
|
||||||
//...
|
//...
|
||||||
}
|
}
|
||||||
|
|
||||||
func PillValues() []Pill {
|
func PillValues() []Pill {
|
||||||
//...
|
//...
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i Pill) IsAPill() bool {
|
func (i Pill) IsAPill() bool {
|
||||||
//...
|
//...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -39,7 +39,7 @@ func (i %[1]s) IsA%[1]s() bool {
|
||||||
// [1]: type name
|
// [1]: type name
|
||||||
const stringBelongsMethodSet = `// IsA%[1]s returns "true" if the value is listed in the enum definition. "false" otherwise
|
const stringBelongsMethodSet = `// IsA%[1]s returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||||
func (i %[1]s) IsA%[1]s() bool {
|
func (i %[1]s) IsA%[1]s() bool {
|
||||||
_, ok := _%[1]sMap[i]
|
_, ok := _%[1]sMap[i]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -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
|
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)
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
2
sql.go
2
sql.go
|
@ -26,7 +26,7 @@ const scanMethod = `func (i *%[1]s) Scan(value interface{}) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
*i = val
|
*i = val
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
206
stringer.go
206
stringer.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
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) {
|
||||||
|
|
|
@ -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