GithubHelp home page GithubHelp logo

enumer's Introduction

Enumer GoDoc Go Report Card cover.run go

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.

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:

  • The following basic methods/functions:

    • 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.
    • Function <Type>String(s string): returns the enum value from its string representation. This is useful when you need to read enum values from command line arguments, from a configuration file, or from a REST API request... In short, from those places where using the real enum value (an integer) would be almost meaningless or hard to trace or use by a human.
    • Function <Type>Values(): returns a slice with all the values of the enum
    • Method IsA<Type>(): returns true only if the current value is among the values of the enum. Useful for validations.
  • 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.

  • 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. 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

  • When the flag yaml is provided, two additional methods will be generated, MarshalYAML() and UnmarshalYAML(). These make the enum conform to the gopkg.in/yaml.v2.Marshaler and gopkg.in/yaml.v2.Unmarshaler interfaces.

  • When the flag sql is provided, the methods for implementing the Scanner and Valuer interfaces will be also generated. Useful when storing the enum in a database.

For example, if we have an enum type called Pill,

type Pill int

const (
	Placebo Pill = iota
	Aspirin
	Ibuprofen
	Paracetamol
	Acetaminophen = Paracetamol
)

executing enumer -type=Pill -json will generate a new file with four basic methods and two extra for JSON:

func (i Pill) String() string { 
	//...
}

func PillString(s string) (Pill, error) { 
	//...
}

func PillValues() []Pill { 
	//...
}

func (i Pill) IsAPill() bool { 
	//...
}

func (i Pill) MarshalJSON() ([]byte, error) {
	//...
}

func (i *Pill) UnmarshalJSON(data []byte) error {
	//...
}

From now on, we can:

// Convert any Pill value to string
var aspirinString string = Aspirin.String()
// (or use it in any place where a Stringer is accepted)
fmt.Println("I need ", Paracetamol) // Will print "I need Paracetamol"

// Convert a string with the enum name to the corresponding enum value
pill, err := PillString("Ibuprofen")
if err != nil {
    fmt.Println("Unrecognized pill: ", err)
    return
}
// Now pill == Ibuprofen

// Get all the values of the string
allPills := PillValues()
fmt.Println(allPills) // Will print [Placebo Aspirin Ibuprofen Paracetamol]

// Check if a value belongs to the Pill enum values
var notAPill Pill = 42
if (notAPill.IsAPill()) {
	fmt.Println(notAPill, "is not a value of the Pill enum")
}

// Marshal/unmarshal to/from json strings, either directly or automatically when
// the enum is a field of a struct
pillJSON := Aspirin.MarshalJSON()
// Now pillJSON == `"Aspirin"`

The generated code is exactly the same as the Stringer tool plus the mentioned additions, so you can use Enumer where you are already using Stringer without any code change.

Transforming the string representation of the enum value

By default, Enumer uses the same name of the enum value for generating the string representation (usually CamelCase in Go).

type MyType int

 ...

name := MyTypeValue.String() // name => "MyTypeValue"

Sometimes you need to use some other string representation format than CamelCase (i.e. in JSON).

To transform it from CamelCase to snake_case or kebab-case, you can use the transform flag.

For example, the command enumer -type=MyType -json -transform=snake would generate the following string representation:

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.

How to use

The usage of Enumer is the same as Stringer, so you can refer to the Stringer docs for more information.

There are four boolean flags: json, text, yaml and sql. You can use any combination of them (i.e. enumer -type=Pill -json -text),

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. 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 it is transformed). If a name doesn't have the prefix it will be passed unchanged.

Inspiring projects

enumer's People

Contributors

alvaroloes avatar boromisp avatar carlsverre avatar dterei avatar godsboss avatar ltpquang avatar marcobeierer avatar mrgossett avatar pascaldekloe avatar pdf avatar prashantv avatar sekky0905 avatar techmexdev avatar wlbr avatar wttw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

enumer's Issues

Improvement: Whitespace transform?

Hi Alvaro! I have a use case where I'm trying to model enum types from other systems. Some of these already use snake_case which is great because you've already included that transform here. However one or two of these models use strings which are whitespace separated. Would you agree that that transformer could be added to this library? Thanks so much!

Support prefix subtraction from variable name when generating string method

Since the variable name becomes the enum string, it would be nice to be able to define a Prefix or Suffix string that should be removed.

For example, while adding enums for Cloudfront log parsing.

// EdgeResultType represents a specific result type that is represented in the Logline
type EdgeResultType int

const (
	// Hit means the object was served to the viewer from the edge cache.
	Hit EdgeResultType = iota
	// RefreshHit means the object was in the edge cache but it had expired, so CloudFront contacted the origin to verify that the cache has the latest version of the object.
	RefreshHit
	// Miss means the request could not be satisfied by an object in the edge cache, so CloudFront forwarded the request to the origin server and returned the result to the viewer.
	Miss
	// LimitExceeded means the request was denied because a CloudFront limit was exceeded.
	LimitExceeded
	// CapacityExceeded returns a 503 error because the edge location didn't have enough capacity at the time of the request to serve the object
	CapacityExceeded
	// Error means the request resulted in a client error (sc-status is 4xx) or a server error (sc-status is 5xx).
	Error
	// Redirect means that we redirected from HTTP to HTTPS. Alternatively, if the status is 403 and the distribution is configured CloudFront to restrict the geographic distribution of your content, the request might have come from a restricted location.
	// If the value of x-edge-result-type is Error and the value of x-edge-response-result-type is not Error, the client disconnected before finishing the download.
	Redirect
)

It would be useful to be able to Prefix these enums to be EdgeResultHit, EdgeResultRefreshHit, but keep the generated string as Hit, RefreshHit, for actual parsing.

Doesn't work with go modules

With Go 1.11 release and go mod introduction I moved some projects that uses enumer out of $GOPATH and so now it can't generate code when package has some imports either because of built-in dependencies failing or because it just can't find import out of $GOPATH.

E.g. when I try to generate from this MWE:

Click to expand
package mwe

import (
	"log"
)

type Type uint8

//go:generate enumer -type=Type -json -transform=snake
const (
	ConstOne Type = iota
	ConstTwo
)

func init() {
	var a = ConstOne

	log.Println("a", a)
}

You'll get that:

enumer: checking package: mwe.go:4:2: could not import log (type-checking package "log" failed ($GOPATH/src/log/log.go:18:2: could not import fmt (type-checking package "fmt" failed ($GOPATH/src/fmt/format.go:8:2: could not import strconv (type-checking package "strconv" failed ($GOPATH/src/strconv/atof.go:13:8: could not import math (type-checking package "math" failed ($GOPATH/src/math/exp_asm.go:9:8: could not import internal/cpu (type-checking package "internal/cpu" failed ($GOPATH/src/internal/cpu/cpu_x86.go:9:7: CacheLineSize redeclared in this block))))))))))
mwe.go:9: running "enumer": exit status 1

could not import sync

Hi everyone. Can u help me with my problem?
enumer: checking package: report.go:4:2: could not import sync (type-checking package "sync" failed (/usr/local/go/src/sync/pool.go:9:2: could not import runtime (type-checking package "runtime" failed (/usr/local/go/src/runtime/mem_linux.go:21:6: sysAlloc redeclared in this block))))

Make marshaling unknown values error

Hi. I'm using

type Action int

const (
	ActionFoo       Action = iota + 1
	ActionBar
)

to make the default zero value be an undefined value. I just noticed that some JSON marshaled as

{"action": "Action(0)", ...}

I would much prefer that marshaling enums that are not a valid value produce an error. It would expose my programming errors sooner. What do you think?

can't find import

Seems like I cannot use any imports with enumer. I have to uncomment all for a successful run.
The project builds fine otherwise.

enumer: checking package: main.go:5:2: could not import gopkg.in/yaml.v2 (can't find import: "foo/vendor/gopkg.in/yaml.v2")

My setup:

$ go version
go version go1.9.2 darwin/amd64
$ ls -l -d $GOPATH/src/foo/vendor/gopkg.in/yaml.v2/
drwxr-xr-x  16 tcurdt  staff  544 Dec 12 03:06 .../src/foo/vendor/gopkg.in/yaml.v2/

Missing License

Hi
Do you mind adding a license? Preferably something similar to GO's license?

Thanks.

enumer runtime crash due to x/tools being broken in go 1.22.0 (with workaround)

the x/tools package used by enumer causes it to generate the following error when it is built with go 1.22.0 on Mac OSX silicon.

panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x104847480]

goroutine 169 [running]:
go/types.(*Checker).handleBailout(0x14000826000, 0x14000249c78)
	/usr/local/go/src/go/types/check.go:367 +0x9c
panic({0x10492b840?, 0x104ab2c30?})
	/usr/local/go/src/runtime/panic.go:770 +0x124
go/types.(*StdSizes).Sizeof(0x0, {0x1049670f8, 0x104ab5a60})
	/usr/local/go/src/go/types/sizes.go:228 +0x320
go/types.(*Config).sizeof(...)
	/usr/local/go/src/go/types/sizes.go:333
go/types.representableConst.func1({0x1049670f8?, 0x104ab5a60?})
	/usr/local/go/src/go/types/const.go:76 +0x9c
go/types.representableConst({0x104968b98, 0x104aab660}, 0x14000826000, 0x104ab5a60, 0x0)
	/usr/local/go/src/go/types/const.go:92 +0x138
go/types.(*Checker).arrayLength(0x14000826000, {0x104968730, 0x14000320180?})
	/usr/local/go/src/go/types/typexpr.go:510 +0x238
module github.com/alvaroloes/enumer
go/types.(*Checker).typInternal(0x14000826000, {0x104967fe0, 0x1400031c0c0}, 0x0)
	/usr/local/go/src/go/types/typexpr.go:299 +0x3bc
go/types.(*Checker).definedType(0x14000826000, {0x104967fe0, 0x1400031c0c0}, 0x14000249238?)
	/usr/local/go/src/go/types/typexpr.go:180 +0x2c
go/types.(*Checker).varType(0x14000826000, {0x104967fe0, 0x1400031c0c0})
	/usr/local/go/src/go/types/typexpr.go:145 +0x2c
go/types.(*Checker).structType(0x14000826000, 0x14000818270, 0x14000818270?)
	/usr/local/go/src/go/types/struct.go:113 +0x128
module github.com/alvaroloes/enumer
go/types.(*Checker).typInternal(0x14000826000, {0x104968010, 0x1400031a9f0}, 0x14000822320)
	/usr/local/go/src/go/types/typexpr.go:316 +0xed0
go/types.(*Checker).definedType(0x14000826000, {0x104968010, 0x1400031a9f0}, 0x1046ac1e4?)
	/usr/local/go/src/go/types/typexpr.go:180 +0x2c
go/types.(*Checker).typeDecl(0x14000826000, 0x14000822320, 0x1400037c080, 0x0)
	/usr/local/go/src/go/types/decl.go:615 +0x39c
go/types.(*Checker).objDecl(0x14000826000, {0x10496abb8, 0x14000822320}, 0x0)
	/usr/local/go/src/go/types/decl.go:197 +0x880
go/types.(*Checker).packageObjects(0x14000826000)
	/usr/local/go/src/go/types/resolver.go:681 +0x3c0
go/types.(*Checker).checkFiles(0x14000826000, {0x1400030e008, 0x1, 0x1})
	/usr/local/go/src/go/types/check.go:408 +0x164
go/types.(*Checker).Files(...)
	/usr/local/go/src/go/types/check.go:372
golang.org/x/tools/go/packages.(*loader).loadPackage(0x140000c22c0, 0x1400040b8a0)
	/Users/johnpaulhumphrey/go/pkg/mod/golang.org/x/[email protected]/go/packages/packages.go:825 +0x4f4
golang.org/x/tools/go/packages.(*loader).loadRecursive.func1()
	/Users/johnpaulhumphrey/go/pkg/mod/golang.org/x/[email protected]/go/packages/packages.go:683 +0x178
sync.(*Once).doSlow(0x0?, 0x0?)
	/usr/local/go/src/sync/once.go:74 +0x100
sync.(*Once).Do(...)
	/usr/local/go/src/sync/once.go:65
golang.org/x/tools/go/packages.(*loader).loadRecursive(0x0?, 0x0?)
	/Users/johnpaulhumphrey/go/pkg/mod/golang.org/x/[email protected]/go/packages/packages.go:670 +0x50
golang.org/x/tools/go/packages.(*loader).loadRecursive.func1.1(0x0?)
	/Users/johnpaulhumphrey/go/pkg/mod/golang.org/x/[email protected]/go/packages/packages.go:677 +0x30
created by golang.org/x/tools/go/packages.(*loader).loadRecursive.func1 in goroutine 40
	/Users/johnpaulhumphrey/go/pkg/mod/golang.org/x/[email protected]/go/packages/packages.go:676 +0x84

As you can see these errors come from x/tools not enumer

the workaround

per temporalio/sdk-go#1382 we need to update x/tools in go mod to a later version

I edited enumer/go.mod to

module github.com/alvaroloes/enumer

go 1.17 // bumped to later version

require (
	github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1
	golang.org/x/tools v0.17.0 //was v0.0.0-20190525145741-7be61e1b0e51
)

require golang.org/x/mod v0.14.0 // indirect

Request List function

Request List Function:

func (...) List() []Type {}
func (...) ListString() []string {}

Request support ENUM list for SQL Check function

I would like to request a function which returns all the ENUMS as comma separated list.

Introduction Information:

See the following example from stackoverflow: http://stackoverflow.com/a/17203007

CREATE TABLE prices (
 id INTEGER PRIMARY KEY,
 pName TEXT CHECK( LENGTH(pName) <= 100 ) NOT NULL DEFAULT '',
 pType TEXT CHECK( pType IN ('M','R','H') ) NOT NULL DEFAULT 'M',
 pField TEXT CHECK( LENGTH(pField) <= 50 ) NULL DEFAULT NULL,
 pFieldExt TEXT CHECK( LENGTH(pFieldExt) <= 50 ) NULL DEFAULT NULL,
 cmp_id INTEGER NOT NULL DEFAULT '0'
)

Regards of database system, (MySQL, PostgreSQL, SQLite, Microsoft SQL Server) it is quite common to use a statement involving IN.

Request:

I would like to do the following.
Generate a ENUM for a const with enumer, with -json, -sql. Then I would like to have a function which returns the ENUMS as a comma separated list so I can place it dynamically within SQL statements with Sprintf. This way I only have to re-generate the ENUM and the SQL statement will also include the new ENUM within either a CHECK statement like above or as a IN statement.

Example (ENDUSER):

func GetUser() interface{} {
    return db.Exec(fmt.Sprintf(`
        SELECT * 
        FROM USERS 
        WHERE 
            USERNAME=$1 
        AND 
        TYPE IN ('%s')`, enumer.SQLList()), username)
}

Function name for this request can be chosen better :-)

P.S. Above code is just a example.

Add -linecomment flag to enumer based on x/tools/cmd/stringer

Hi Álvaro,

I just started looking at enumer and it looks great for my use case. Basically I want to automatically generate strings for enums, and convert strings back to enums.

However, the strings will not always be the same as the enum name. This was solved by the stringer tool by adding a -linecomment flag. With this flag, the line comment after an enum will be used for the string value, instead of the name of the enum.

https://github.com/golang/tools/blob/4fd33079060a7ef7bffe3a1c2369620f4f21ad06/cmd/stringer/stringer.go#L84

Please consider adding it to enumer. That would be very useful!

For an example usage of -linecomment see https://github.com/llir/l/blob/f6ae116b371f89c51d52b496de3487149b30335a/ir/ll/enums.go#L15

//go:generate stringer -linecomment -type Linkage

// Linkage specifies the linkage of a global identifier.
type Linkage uint8

// Linkage kinds.
const (
	LinkageNone                Linkage = iota // none
	LinkageAppending                          // appending
	LinkageAvailableExternally                // available_externally
	LinkageCommon                             // common
	LinkageInternal                           // internal
	LinkageLinkOnce                           // linkonce
	LinkageLinkOnceODR                        // linkonce_odr
	LinkagePrivate                            // private
	LinkageWeak                               // weak
	LinkageWeakODR                            // weak_odr
	// External linkage.
	LinkageExternal   // external
	LinkageExternWeak // extern_weak
)

Cheers,
Robin

suggestion: Use constant names rather than literals

When using enumer, it's possible to end up with something where the only places that a constant would be directly used would be in the conversion to/from string, but enumer doesn't use the constant, it uses a literal value.

See examples here:
dominikh/go-tools#660

So for instance

var _DayNameToValueMap = map[string]Day{
	_DayName[0:6]:   0,
	_DayName[6:13]:  1,
	_DayName[13:22]: 2,
	_DayName[22:30]: 3,
	_DayName[30:36]: 4,
	_DayName[36:44]: 5,
	_DayName[44:50]: 6,
}

This could use Monday instead of the literal 0, and then it would be easier for analysis tools to detect that the constant value is in some way used. It might also be easier to read the resulting code, although I recognize that's not especially important with generated code.

You should update `/x/tools` dependency

the revision tag in your go.mod for golang.org/x/tools is too old. Enumer will fail with go1.14+.
update with : golang.org/x/tools v0.0.0-20200725200936-102e7d357031
thanks.

Failure w/ go 1.12.1

Not using modules!

enumer: checking package: loglevel_enumer.go:6:2: could not import fmt (type-checking package "fmt" failed (/golangdev/go/src/fmt/print.go:9:2: could not import internal/fmtsort (type-checking package "internal/fmtsort" failed (/golangdev/go/src/internal/fmtsort/sort.go:12:2: could not import reflect (type-checking package "reflect" failed (/golangdev/go/src/reflect/type.go:19:2: could not import runtime (type-checking package "runtime" failed /golangdev/go/src/runtime/mem_linux.go:21:6: sysAlloc redeclared in this block))))))))
.gopath~/src/github.com/influxdata/telegraf/plugins/parsers/sefofmt/loglevel.go:1: running "enumer": exit status 1

I see the same error when using stringer, so perhaps this error is upstream.

Go 1.19: conflict with doc comments

Go 1.19 changed how the go tool handles doc comments. This leads to gofmt now reformatting enumer-generated files from previous versions like this:

  // Code generated by "enumer -type ObservationID"; DO NOT EDIT.

+ //
  package easee

Use Semantic Versioning

Hi!

I would like to suggest using Semantic Versioning for enumer's releases.

What do you think about it?

Thanks.

Go 1.18 compatibility: internal error

Seems that there are some problems with 1.18 in conjunction with the old x/tools library:

go generate ./...
enumer: internal error: package "fmt" without types was imported from "github.com/evcc-io/evcc/charger/easee"
charger/easee/signalr.go:30: running "enumer": exit status 1

I could not track it down yet; the package in question above does not make use of generics. Code is publicly available if anyone wants to take a stab.

This is actually a duplicate of #62

Enum Values with value Zero are not marshalled as Json

I have an enum like

type Fruit byte

const (
	APPLE         Fruit = iota
	PEAR
)

when serialising a value APPLE to json I would expect the value to appear in the output json. However, the value does not appear, presumably because it is 0 under the hood

Replacing Fruit = iota with Fruit = iota + 1 means APPLE gets outputted as expected

I'm new to Go from java, so there may be good reason why this is so, but it was surprising for me, so, if you don't think it should be fixed, perhaps it's worth a mention in the documentation

Support for multiple transform targets.

For example:
JSON would transform to snake_case
String() would transform to kebab-case

Therefore "cs-uri-stem" would be defined as var CsUriStem and run with a transform of kebab-case, but would marshal as cs_uri_stem.

enumer does not convert enum keys of map to strings on JSON Marshal

Eg (pseudocode):

enums/myenum.go:

//go:generate enumer -type=MyEnum -json -sql

// MyEnum:
type MyEnum int

const (
  MyEnumA MyEnum = iota + 1
  MyEnumB
  MyEnumC
)

main.go:


func main() {
  myMap := make(map[enums.MyEnum]string)
  myMap[enums.MyEnumA] = "A"
  myMap[enums.MyEnumB] = "B"

  jsonBytes, _ := json.MarshalIndent(HostRecommendationConfig,"", "  ")
  fmt.Println(string(jsonBytes))
}

The above code will print the output:

{
  "1": "A",
  "2": "B"
}

Expected output:

{
  "A": "A",
  "B": "B"
}

invalid cross-device link

When using enumer in a CI environment I encounter this issue.

enumer: moving tempfile to output file: rename /tmp/SomeType_enumer_457916248 sometype_enumer.go: invalid cross-device link

Due to how the container is sandboxed, this is not possible in my case.

In my opinion this should be a setting?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.