Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for XML and XML Attribute marshalling #78

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ Flags:
if true, alternative string values method will be generated. Default: false
-yaml
if true, yaml marshaling methods will be generated. Default: false
-xml
if true, xml marshaling methods will be generated. Default: false
-xmlattr
if true, xml attribute marshaling methods will be generated. Default: false
```


Expand Down Expand Up @@ -71,7 +75,10 @@ When Enumer is applied to a type, it will generate:
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.
Useful when storing the enum in a database.

- When the flag `xml` is provided two additional methods will be generated, `MarshalXML()` and `UnmarshalXML()`. These make
the enum conform to the `encoding/xml.Marshaler` and `encoding/xml.Unmarshaler` interfaces.
- When the flag `xmlattr` is provided two additional methods will be generated, `MarshalXML()` and `UnmarshalXML()`. These make
the enum conform to the `encoding/xml.MarshalerAttr` and `encoding/xml.UnmarshalerAttr` interfaces.

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

Expand Down Expand Up @@ -201,7 +208,7 @@ For a module-aware repo with `enumer` in the `go.mod` file, generation can be ca
//go:generate go run github.com/dmarkham/enumer -type=YOURTYPE
```

There are four boolean flags: `json`, `text`, `yaml` and `sql`. You can use any combination of them (i.e. `enumer -type=Pill -json -text`),
There are six boolean flags: `json`, `text`, `yaml`, `xml`, `xmlattr` 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`).
Expand All @@ -213,7 +220,7 @@ it is transformed). If a name doesn't have the prefix it will be passed unchange

If a prefix is provided via the `addprefix` flag, it will be added to the start of each name (after trimming and after transforming).

The boolean flag `values` will additionally create an alternative string values method `Values() []string` to fullfill the `EnumValues` interface of [ent](https://entgo.io/docs/schema-fields/#enum-fields).
The boolean flag `values` will additionally create an alternative string values method `Values() []string` to fulfill the `EnumValues` interface of [ent](https://entgo.io/docs/schema-fields/#enum-fields).

## Inspiring projects

Expand Down
49 changes: 49 additions & 0 deletions enumer.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,52 @@ func (i *%[1]s) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (g *Generator) buildYAMLMethods(runs [][]Value, typeName string, runsThreshold int) {
g.Printf(yamlMethods, typeName)
}

// Arguments to format are:
// [1]: type name
const xmlMethods = `
// MarshalXML implements a XML Marshaller for %[1]s
func (i %[1]s) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement(i.String(), start)
}

// UnmarshalXML implements a XML Unmarshaler for %[1]s
func (i *%[1]s) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var s string
var err error
if err = d.DecodeElement(&s, &start); err != nil {
return err
}

*i, err = %[1]sString(s)
return err
}
`

func (g *Generator) buildXMLMethods(runs [][]Value, typeName string, runsThreshold int) {
g.Printf(xmlMethods, typeName)
}

// Arguments to format are:
// [1]: type name
const xmlAttrMethods = `
// MarshalXMLAttr implements a XML Attribute Marshaller for %[1]s
func (i %[1]s) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
return xml.Attr{
Name: name,
Value: i.String(),
}, nil
}

// UnmarshalXMLAttr implements a XML Attribute Unmarshaler for %[1]s
func (i *%[1]s) UnmarshalXMLAttr(attr xml.Attr) error {
var err error

*i, err = %[1]sString(attr.Value)
return err
}
`

func (g *Generator) buildXMLAttrMethods(runs [][]Value, typeName string, runsThreshold int) {
g.Printf(xmlAttrMethods, typeName)
}
83 changes: 68 additions & 15 deletions golden_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package main

import (
"io"
"io/ioutil"
"os"
"path/filepath"
Expand Down Expand Up @@ -44,6 +45,14 @@ var goldenYAML = []Golden{
{"primeYaml", primeYamlIn},
}

var goldenXML = []Golden{
{"primeXml", primeXmlIn},
}

var goldenXMLAttr = []Golden{
{"primeXmlAttr", primeXmlAttrIn},
}

var goldenSQL = []Golden{
{"primeSql", primeSqlIn},
}
Expand Down Expand Up @@ -220,6 +229,44 @@ const (
)
`

const primeXmlIn = `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 primeXmlAttrIn = `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 primeSqlIn = `type Prime int
const (
p2 Prime = 2
Expand Down Expand Up @@ -315,45 +362,51 @@ const (

func TestGolden(t *testing.T) {
for _, test := range golden {
runGoldenTest(t, test, false, false, false, false, false, false, true, "", "")
runGoldenTest(t, test, false, false, false, false, false, false, false, false, true, "", "")
}
for _, test := range goldenJSON {
runGoldenTest(t, test, true, false, false, false, false, false, false, "", "")
runGoldenTest(t, test, true, false, false, false, false, false, false, false, false, "", "")
}
for _, test := range goldenText {
runGoldenTest(t, test, false, false, false, true, false, false, false, "", "")
runGoldenTest(t, test, false, false, false, false, false, true, false, false, false, "", "")
}
for _, test := range goldenYAML {
runGoldenTest(t, test, false, true, false, false, false, false, false, "", "")
runGoldenTest(t, test, false, true, false, false, false, false, false, false, false, "", "")
}
for _, test := range goldenXML {
runGoldenTest(t, test, false, false, true, false, false, false, false, false, false, "", "")
}
for _, test := range goldenXMLAttr {
runGoldenTest(t, test, false, false, false, true, false, false, false, false, false, "", "")
}
for _, test := range goldenSQL {
runGoldenTest(t, test, false, false, true, false, false, false, false, "", "")
runGoldenTest(t, test, false, false, false, false, true, false, false, false, false, "", "")
}
for _, test := range goldenJSONAndSQL {
runGoldenTest(t, test, true, false, true, false, false, false, false, "", "")
runGoldenTest(t, test, true, false, false, false, true, false, false, false, false, "", "")
}
for _, test := range goldenGQLGen {
runGoldenTest(t, test, false, false, false, false, false, true, false, "", "")
runGoldenTest(t, test, false, false, false, false, false, false, false, true, false, "", "")
}
for _, test := range goldenTrimPrefix {
runGoldenTest(t, test, false, false, false, false, false, false, false, "Day", "")
runGoldenTest(t, test, false, false, false, false, false, false, false, false, false, "Day", "")
}
for _, test := range goldenTrimPrefixMultiple {
runGoldenTest(t, test, false, false, false, false, false, false, false, "Day,Night", "")
runGoldenTest(t, test, false, false, false, false, false, false, false, false, false, "Day,Night", "")
}
for _, test := range goldenWithPrefix {
runGoldenTest(t, test, false, false, false, false, false, false, false, "", "Day")
runGoldenTest(t, test, false, false, false, false, false, false, false, false, false, "", "Day")
}
for _, test := range goldenTrimAndAddPrefix {
runGoldenTest(t, test, false, false, false, false, false, false, false, "Day", "Night")
runGoldenTest(t, test, false, false, false, false, false, false, false, false, false, "Day", "Night")
}
for _, test := range goldenLinecomment {
runGoldenTest(t, test, false, false, false, false, true, false, false, "", "")
runGoldenTest(t, test, false, false, false, false, false, false, true, false, false, "", "")
}
}

func runGoldenTest(t *testing.T, test Golden,
generateJSON, generateYAML, generateSQL, generateText, linecomment, generateGQLGen, generateValuesMethod bool,
generateJSON, generateYAML, generateXML, generateXMLAttr, generateSQL, generateText, linecomment, generateGQLGen, generateValuesMethod bool,
trimPrefix string, prefix string) {

var g Generator
Expand Down Expand Up @@ -382,7 +435,7 @@ func runGoldenTest(t *testing.T, test Golden,
if len(tokens) != 3 {
t.Fatalf("%s: need type declaration on first line", test.name)
}
g.generate(tokens[1], generateJSON, generateYAML, generateSQL, generateText, generateGQLGen, "noop", trimPrefix, prefix, linecomment, generateValuesMethod)
g.generate(tokens[1], generateJSON, generateYAML, generateXML, generateXMLAttr, generateSQL, generateText, generateGQLGen, "noop", trimPrefix, prefix, linecomment, generateValuesMethod)
got := string(g.format())
if got != loadGolden(test.name) {
// Use this to help build a golden text when changes are needed
Expand All @@ -401,7 +454,7 @@ func loadGolden(name string) string {
return ""
}
defer fh.Close()
b, err := ioutil.ReadAll(fh)
b, err := io.ReadAll(fh)
if err != nil {
return ""
}
Expand Down
29 changes: 20 additions & 9 deletions stringer.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ var (
sql = flag.Bool("sql", false, "if true, the Scanner and Valuer interface will be implemented.")
json = flag.Bool("json", false, "if true, json marshaling methods will be generated. Default: false")
yaml = flag.Bool("yaml", false, "if true, yaml marshaling methods will be generated. Default: false")
xml = flag.Bool("xml", false, "if true, xml marshaling methods will be generated. Default: false")
xmlAttr = flag.Bool("xmlattr", false, "if true, xml attribute marshaling methods will be generated. Default: false")
text = flag.Bool("text", false, "if true, text marshaling methods will be generated. Default: false")
gqlgen = flag.Bool("gqlgen", false, "if true, GraphQL marshaling methods for gqlgen will be generated. Default: false")
altValuesFunc = flag.Bool("values", false, "if true, alternative string values method will be generated. Default: false")
Expand Down Expand Up @@ -127,6 +129,9 @@ func main() {
if *json {
g.Printf("\t\"encoding/json\"\n")
}
if *xml || *xmlAttr {
g.Printf("\t\"encoding/xml\"\n")
}
if *gqlgen {
g.Printf("\t\"io\"\n")
g.Printf("\t\"strconv\"\n")
Expand All @@ -135,7 +140,7 @@ func main() {

// Run generate for each type.
for _, typeName := range typs {
g.generate(typeName, *json, *yaml, *sql, *text, *gqlgen, *transformMethod, *trimPrefix, *addPrefix, *linecomment, *altValuesFunc)
g.generate(typeName, *json, *yaml, *xml, *xmlAttr, *sql, *text, *gqlgen, *transformMethod, *trimPrefix, *addPrefix, *linecomment, *altValuesFunc)
}

// Format the output.
Expand Down Expand Up @@ -414,7 +419,7 @@ func (g *Generator) prefixValueNames(values []Value, prefix string) {

// generate produces the String method for the named type.
func (g *Generator) generate(typeName string,
includeJSON, includeYAML, includeSQL, includeText, includeGQLGen bool,
includeJSON, includeYAML, includeXML, includeXMLAttr, includeSQL, includeText, includeGQLGen bool,
transformMethod string, trimPrefix string, addPrefix string, lineComment bool, includeValuesMethod bool) {
values := make([]Value, 0, 100)
for _, file := range g.pkg.files {
Expand Down Expand Up @@ -478,6 +483,12 @@ func (g *Generator) generate(typeName string,
if includeYAML {
g.buildYAMLMethods(runs, typeName, runsThreshold)
}
if includeXML {
g.buildXMLMethods(runs, typeName, runsThreshold)
}
if includeXMLAttr {
g.buildXMLAttrMethods(runs, typeName, runsThreshold)
}
if includeSQL {
g.addValueAndScanMethod(typeName)
}
Expand Down Expand Up @@ -774,9 +785,9 @@ func (g *Generator) buildOneRun(runs [][]Value, typeName string) {
}

// Arguments to format are:
// [1]: type name
// [2]: size of index element (8 for uint8 etc.)
// [3]: less than zero check (for signed types)
// [1]: type name
// [2]: size of index element (8 for uint8 etc.)
// [3]: less than zero check (for signed types)
const stringOneRun = `func (i %[1]s) String() string {
if %[3]si >= %[1]s(len(_%[1]sIndex)-1) {
return fmt.Sprintf("%[1]s(%%d)", i)
Expand All @@ -786,10 +797,10 @@ const stringOneRun = `func (i %[1]s) String() string {
`

// Arguments to format are:
// [1]: type name
// [2]: lowest defined value for type, as a string
// [3]: size of index element (8 for uint8 etc.)
// [4]: less than zero check (for signed types)
// [1]: type name
// [2]: lowest defined value for type, as a string
// [3]: size of index element (8 for uint8 etc.)
// [4]: less than zero check (for signed types)
const stringOneRunWithOffset = `func (i %[1]s) String() string {
i -= %[2]s
if %[4]si >= %[1]s(len(_%[1]sIndex)-1) {
Expand Down
Loading