├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example ├── shirtsize.go ├── shirtsize_jsonenums.go └── weekday_jsonenums.go ├── jsonenums.go ├── parser ├── parser.go └── parser_test.go ├── server ├── app.yaml ├── main.go ├── server.go └── static │ └── home.html └── template.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.7 5 | - 1.8 6 | - master 7 | script: 8 | # The GOPATH is for testing #21 9 | - GOPATH="$GOPATH:/tmp/jsonenums-test/go1:/tmp/jsonenums-test/go2" go test ./... 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsonenums 2 | 3 | jsonenums is a tool to automate the creation of methods that satisfy the 4 | `json.Marshaler` and `json.Unmarshaler` interfaces. 5 | Given the name of a (signed or unsigned) integer type T that has constants 6 | defined, jsonenums will create a new self-contained Go source file implementing 7 | 8 | ``` 9 | func (t T) MarshalJSON() ([]byte, error) 10 | func (t *T) UnmarshalJSON([]byte) error 11 | ``` 12 | 13 | The file is created in the same package and directory as the package that 14 | defines T. It has helpful defaults designed for use with go generate. 15 | 16 | jsonenums is a simple implementation of a concept and the code might not be the 17 | most performant or beautiful to read. 18 | 19 | For example, given this snippet, 20 | 21 | ```Go 22 | package painkiller 23 | 24 | type Pill int 25 | 26 | const ( 27 | Placebo Pill = iota 28 | Aspirin 29 | Ibuprofen 30 | Paracetamol 31 | Acetaminophen = Paracetamol 32 | ) 33 | ``` 34 | 35 | running this command 36 | 37 | ``` 38 | jsonenums -type=Pill 39 | ``` 40 | 41 | in the same directory will create the file `pill_jsonenums.go`, in package 42 | `painkiller`, containing a definition of 43 | 44 | ``` 45 | func (r Pill) MarshalJSON() ([]byte, error) 46 | func (r *Pill) UnmarshalJSON([]byte) error 47 | ``` 48 | 49 | `MarshalJSON` will translate the value of a `Pill` constant to the `[]byte` 50 | representation of the respective constant name, so that the call 51 | `json.Marshal(painkiller.Aspirin)` will return the bytes `[]byte("\"Aspirin\"")`. 52 | 53 | `UnmarshalJSON` performs the opposite operation; given the `[]byte` 54 | representation of a `Pill` constant it will change the receiver to equal the 55 | corresponding constant. So given `[]byte("\"Aspirin\"")` the receiver will 56 | change to `Aspirin` and the returned error will be `nil`. 57 | 58 | Typically this process would be run using go generate, like this: 59 | 60 | ``` 61 | //go:generate jsonenums -type=Pill 62 | ``` 63 | 64 | If multiple constants have the same value, the lexically first matching name 65 | will be used (in the example, Acetaminophen will print as "Paracetamol"). 66 | 67 | With no arguments, it processes the package in the current directory. Otherwise, 68 | the arguments must name a single directory holding a Go package or a set of Go 69 | source files that represent a single Go package. 70 | 71 | The `-type` flag accepts a comma-separated list of types so a single run can 72 | generate methods for multiple types. The default output file is t_jsonenums.go, 73 | where t is the lower-cased name of the first type listed. The suffix can be 74 | overridden with the `-suffix` flag and a prefix may be added with the `-prefix` 75 | flag. 76 | 77 | This is not an official Google product (experimental or otherwise), it is just code that happens to be owned by Google. 78 | -------------------------------------------------------------------------------- /example/shirtsize.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | "log" 20 | "os" 21 | "strings" 22 | ) 23 | 24 | //go:generate jsonenums -type=ShirtSize 25 | 26 | type ShirtSize byte 27 | 28 | const ( 29 | NA ShirtSize = iota 30 | XS 31 | S 32 | M 33 | L 34 | XL 35 | ) 36 | 37 | //go:generate jsonenums -type=WeekDay 38 | 39 | type WeekDay int 40 | 41 | const ( 42 | Monday WeekDay = iota 43 | Tuesday 44 | Wednesday 45 | Thursday 46 | Friday 47 | Saturday 48 | Sunday 49 | ) 50 | 51 | func (d WeekDay) String() string { 52 | switch d { 53 | case Monday: 54 | return "Dilluns" 55 | case Tuesday: 56 | return "Dimarts" 57 | case Wednesday: 58 | return "Dimecres" 59 | case Thursday: 60 | return "Dijous" 61 | case Friday: 62 | return "Divendres" 63 | case Saturday: 64 | return "Dissabte" 65 | case Sunday: 66 | return "Diumenge" 67 | default: 68 | return "invalid WeekDay" 69 | } 70 | } 71 | 72 | func main() { 73 | v := struct { 74 | Size ShirtSize 75 | Day WeekDay 76 | }{M, Friday} 77 | if err := json.NewEncoder(os.Stdout).Encode(v); err != nil { 78 | log.Fatal(err) 79 | } 80 | 81 | input := `{"Size":"XL", "Day":"Dimarts"}` 82 | if err := json.NewDecoder(strings.NewReader(input)).Decode(&v); err != nil { 83 | log.Fatal(err) 84 | } 85 | fmt.Printf("decoded %s as %+v\n", input, v) 86 | } 87 | -------------------------------------------------------------------------------- /example/shirtsize_jsonenums.go: -------------------------------------------------------------------------------- 1 | // Code generated by jsonenums -type=ShirtSize; DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | ) 9 | 10 | var ( 11 | _ShirtSizeNameToValue = map[string]ShirtSize{ 12 | "NA": NA, 13 | "XS": XS, 14 | "S": S, 15 | "M": M, 16 | "L": L, 17 | "XL": XL, 18 | } 19 | 20 | _ShirtSizeValueToName = map[ShirtSize]string{ 21 | NA: "NA", 22 | XS: "XS", 23 | S: "S", 24 | M: "M", 25 | L: "L", 26 | XL: "XL", 27 | } 28 | ) 29 | 30 | func init() { 31 | var v ShirtSize 32 | if _, ok := interface{}(v).(fmt.Stringer); ok { 33 | _ShirtSizeNameToValue = map[string]ShirtSize{ 34 | interface{}(NA).(fmt.Stringer).String(): NA, 35 | interface{}(XS).(fmt.Stringer).String(): XS, 36 | interface{}(S).(fmt.Stringer).String(): S, 37 | interface{}(M).(fmt.Stringer).String(): M, 38 | interface{}(L).(fmt.Stringer).String(): L, 39 | interface{}(XL).(fmt.Stringer).String(): XL, 40 | } 41 | } 42 | } 43 | 44 | // MarshalJSON is generated so ShirtSize satisfies json.Marshaler. 45 | func (r ShirtSize) MarshalJSON() ([]byte, error) { 46 | if s, ok := interface{}(r).(fmt.Stringer); ok { 47 | return json.Marshal(s.String()) 48 | } 49 | s, ok := _ShirtSizeValueToName[r] 50 | if !ok { 51 | return nil, fmt.Errorf("invalid ShirtSize: %d", r) 52 | } 53 | return json.Marshal(s) 54 | } 55 | 56 | // UnmarshalJSON is generated so ShirtSize satisfies json.Unmarshaler. 57 | func (r *ShirtSize) UnmarshalJSON(data []byte) error { 58 | var s string 59 | if err := json.Unmarshal(data, &s); err != nil { 60 | return fmt.Errorf("ShirtSize should be a string, got %s", data) 61 | } 62 | v, ok := _ShirtSizeNameToValue[s] 63 | if !ok { 64 | return fmt.Errorf("invalid ShirtSize %q", s) 65 | } 66 | *r = v 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /example/weekday_jsonenums.go: -------------------------------------------------------------------------------- 1 | // Code generated by jsonenums -type=WeekDay; DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | ) 9 | 10 | var ( 11 | _WeekDayNameToValue = map[string]WeekDay{ 12 | "Monday": Monday, 13 | "Tuesday": Tuesday, 14 | "Wednesday": Wednesday, 15 | "Thursday": Thursday, 16 | "Friday": Friday, 17 | "Saturday": Saturday, 18 | "Sunday": Sunday, 19 | } 20 | 21 | _WeekDayValueToName = map[WeekDay]string{ 22 | Monday: "Monday", 23 | Tuesday: "Tuesday", 24 | Wednesday: "Wednesday", 25 | Thursday: "Thursday", 26 | Friday: "Friday", 27 | Saturday: "Saturday", 28 | Sunday: "Sunday", 29 | } 30 | ) 31 | 32 | func init() { 33 | var v WeekDay 34 | if _, ok := interface{}(v).(fmt.Stringer); ok { 35 | _WeekDayNameToValue = map[string]WeekDay{ 36 | interface{}(Monday).(fmt.Stringer).String(): Monday, 37 | interface{}(Tuesday).(fmt.Stringer).String(): Tuesday, 38 | interface{}(Wednesday).(fmt.Stringer).String(): Wednesday, 39 | interface{}(Thursday).(fmt.Stringer).String(): Thursday, 40 | interface{}(Friday).(fmt.Stringer).String(): Friday, 41 | interface{}(Saturday).(fmt.Stringer).String(): Saturday, 42 | interface{}(Sunday).(fmt.Stringer).String(): Sunday, 43 | } 44 | } 45 | } 46 | 47 | // MarshalJSON is generated so WeekDay satisfies json.Marshaler. 48 | func (r WeekDay) MarshalJSON() ([]byte, error) { 49 | if s, ok := interface{}(r).(fmt.Stringer); ok { 50 | return json.Marshal(s.String()) 51 | } 52 | s, ok := _WeekDayValueToName[r] 53 | if !ok { 54 | return nil, fmt.Errorf("invalid WeekDay: %d", r) 55 | } 56 | return json.Marshal(s) 57 | } 58 | 59 | // UnmarshalJSON is generated so WeekDay satisfies json.Unmarshaler. 60 | func (r *WeekDay) UnmarshalJSON(data []byte) error { 61 | var s string 62 | if err := json.Unmarshal(data, &s); err != nil { 63 | return fmt.Errorf("WeekDay should be a string, got %s", data) 64 | } 65 | v, ok := _WeekDayNameToValue[s] 66 | if !ok { 67 | return fmt.Errorf("invalid WeekDay %q", s) 68 | } 69 | *r = v 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /jsonenums.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // JSONenums is a tool to automate the creation of methods that satisfy the 15 | // fmt.Stringer, json.Marshaler and json.Unmarshaler interfaces. 16 | // Given the name of a (signed or unsigned) integer type T that has constants 17 | // defined, jsonenums will create a new self-contained Go source file implementing 18 | // 19 | // func (t T) String() string 20 | // func (t T) MarshalJSON() ([]byte, error) 21 | // func (t *T) UnmarshalJSON([]byte) error 22 | // 23 | // The file is created in the same package and directory as the package that defines T. 24 | // It has helpful defaults designed for use with go generate. 25 | // 26 | // JSONenums is a simple implementation of a concept and the code might not be 27 | // the most performant or beautiful to read. 28 | // 29 | // For example, given this snippet, 30 | // 31 | // package painkiller 32 | // 33 | // type Pill int 34 | // 35 | // const ( 36 | // Placebo Pill = iota 37 | // Aspirin 38 | // Ibuprofen 39 | // Paracetamol 40 | // Acetaminophen = Paracetamol 41 | // ) 42 | // 43 | // running this command 44 | // 45 | // jsonenums -type=Pill 46 | // 47 | // in the same directory will create the file pill_jsonenums.go, in package painkiller, 48 | // containing a definition of 49 | // 50 | // func (r Pill) String() string 51 | // func (r Pill) MarshalJSON() ([]byte, error) 52 | // func (r *Pill) UnmarshalJSON([]byte) error 53 | // 54 | // That method will translate the value of a Pill constant to the string representation 55 | // of the respective constant name, so that the call fmt.Print(painkiller.Aspirin) will 56 | // print the string "Aspirin". 57 | // 58 | // Typically this process would be run using go generate, like this: 59 | // 60 | // //go:generate jsonenums -type=Pill 61 | // 62 | // If multiple constants have the same value, the lexically first matching name will 63 | // be used (in the example, Acetaminophen will print as "Paracetamol"). 64 | // 65 | // With no arguments, it processes the package in the current directory. 66 | // Otherwise, the arguments must name a single directory holding a Go package 67 | // or a set of Go source files that represent a single Go package. 68 | // 69 | // The -type flag accepts a comma-separated list of types so a single run can 70 | // generate methods for multiple types. The default output file is 71 | // t_jsonenums.go, where t is the lower-cased name of the first type listed. 72 | // The suffix can be overridden with the -suffix flag and a prefix may be added 73 | // with the -prefix flag. 74 | // 75 | package main 76 | 77 | import ( 78 | "bytes" 79 | "flag" 80 | "go/format" 81 | "io/ioutil" 82 | "log" 83 | "os" 84 | "path/filepath" 85 | "strings" 86 | 87 | "github.com/campoy/jsonenums/parser" 88 | ) 89 | 90 | var ( 91 | typeNames = flag.String("type", "", "comma-separated list of type names; must be set") 92 | outputPrefix = flag.String("prefix", "", "prefix to be added to the output file") 93 | outputSuffix = flag.String("suffix", "_jsonenums", "suffix to be added to the output file") 94 | ) 95 | 96 | func main() { 97 | flag.Parse() 98 | if len(*typeNames) == 0 { 99 | log.Fatalf("the flag -type must be set") 100 | } 101 | types := strings.Split(*typeNames, ",") 102 | 103 | // Only one directory at a time can be processed, and the default is ".". 104 | dir := "." 105 | if args := flag.Args(); len(args) == 1 { 106 | dir = args[0] 107 | } else if len(args) > 1 { 108 | log.Fatalf("only one directory at a time") 109 | } 110 | dir, err := filepath.Abs(dir) 111 | if err != nil { 112 | log.Fatalf("unable to determine absolute filepath for requested path %s: %v", 113 | dir, err) 114 | } 115 | 116 | pkg, err := parser.ParsePackage(dir) 117 | if err != nil { 118 | log.Fatalf("parsing package: %v", err) 119 | } 120 | 121 | var analysis = struct { 122 | Command string 123 | PackageName string 124 | TypesAndValues map[string][]string 125 | }{ 126 | Command: strings.Join(os.Args[1:], " "), 127 | PackageName: pkg.Name, 128 | TypesAndValues: make(map[string][]string), 129 | } 130 | 131 | // Run generate for each type. 132 | for _, typeName := range types { 133 | values, err := pkg.ValuesOfType(typeName) 134 | if err != nil { 135 | log.Fatalf("finding values for type %v: %v", typeName, err) 136 | } 137 | analysis.TypesAndValues[typeName] = values 138 | 139 | var buf bytes.Buffer 140 | if err := generatedTmpl.Execute(&buf, analysis); err != nil { 141 | log.Fatalf("generating code: %v", err) 142 | } 143 | 144 | src, err := format.Source(buf.Bytes()) 145 | if err != nil { 146 | // Should never happen, but can arise when developing this code. 147 | // The user can compile the output to see the error. 148 | log.Printf("warning: internal error: invalid Go generated: %s", err) 149 | log.Printf("warning: compile the package to analyze the error") 150 | src = buf.Bytes() 151 | } 152 | 153 | output := strings.ToLower(*outputPrefix + typeName + 154 | *outputSuffix + ".go") 155 | outputPath := filepath.Join(dir, output) 156 | if err := ioutil.WriteFile(outputPath, src, 0644); err != nil { 157 | log.Fatalf("writing output: %s", err) 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /parser/parser.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // Package parser parses Go code and keeps track of all the types defined 15 | // and provides access to all the constants defined for an int type. 16 | package parser 17 | 18 | import ( 19 | "fmt" 20 | "go/ast" 21 | "go/build" 22 | "go/constant" 23 | "go/token" 24 | "go/types" 25 | "log" 26 | "strings" 27 | 28 | "golang.org/x/tools/go/loader" 29 | ) 30 | 31 | // A Package contains all the information related to a parsed package. 32 | type Package struct { 33 | Name string 34 | files []*ast.File 35 | 36 | defs map[*ast.Ident]types.Object 37 | } 38 | 39 | // ParsePackage parses the package in the given directory and returns it. 40 | func ParsePackage(directory string) (*Package, error) { 41 | p, err := build.ImportDir(directory, build.FindOnly) 42 | if err != nil { 43 | return nil, fmt.Errorf("provided directory (%s) may not under GOPATH (%s): %v", 44 | directory, build.Default.GOPATH, err) 45 | } 46 | 47 | conf := loader.Config{TypeChecker: types.Config{FakeImportC: true}} 48 | conf.Import(p.ImportPath) 49 | program, err := conf.Load() 50 | if err != nil { 51 | return nil, fmt.Errorf("couldn't load package: %v", err) 52 | } 53 | 54 | pkgInfo := program.Package(p.ImportPath) 55 | return &Package{ 56 | Name: pkgInfo.Pkg.Name(), 57 | files: pkgInfo.Files, 58 | defs: pkgInfo.Defs, 59 | }, nil 60 | } 61 | 62 | // generate produces the String method for the named type. 63 | func (pkg *Package) ValuesOfType(typeName string) ([]string, error) { 64 | var values, inspectErrs []string 65 | for _, file := range pkg.files { 66 | ast.Inspect(file, func(node ast.Node) bool { 67 | decl, ok := node.(*ast.GenDecl) 68 | if !ok || decl.Tok != token.CONST { 69 | // We only care about const declarations. 70 | return true 71 | } 72 | 73 | if vs, err := pkg.valuesOfTypeIn(typeName, decl); err != nil { 74 | inspectErrs = append(inspectErrs, err.Error()) 75 | } else { 76 | values = append(values, vs...) 77 | } 78 | return false 79 | }) 80 | } 81 | if len(inspectErrs) > 0 { 82 | return nil, fmt.Errorf("inspecting code:\n\t%v", strings.Join(inspectErrs, "\n\t")) 83 | } 84 | if len(values) == 0 { 85 | return nil, fmt.Errorf("no values defined for type %s", typeName) 86 | } 87 | return values, nil 88 | } 89 | 90 | func (pkg *Package) valuesOfTypeIn(typeName string, decl *ast.GenDecl) ([]string, error) { 91 | var values []string 92 | 93 | // The name of the type of the constants we are declaring. 94 | // Can change if this is a multi-element declaration. 95 | typ := "" 96 | // Loop over the elements of the declaration. Each element is a ValueSpec: 97 | // a list of names possibly followed by a type, possibly followed by values. 98 | // If the type and value are both missing, we carry down the type (and value, 99 | // but the "go/types" package takes care of that). 100 | for _, spec := range decl.Specs { 101 | vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST. 102 | if vspec.Type == nil && len(vspec.Values) > 0 { 103 | // "X = 1". With no type but a value, the constant is untyped. 104 | // Skip this vspec and reset the remembered type. 105 | typ = "" 106 | continue 107 | } 108 | if vspec.Type != nil { 109 | // "X T". We have a type. Remember it. 110 | ident, ok := vspec.Type.(*ast.Ident) 111 | if !ok { 112 | continue 113 | } 114 | typ = ident.Name 115 | } 116 | if typ != typeName { 117 | // This is not the type we're looking for. 118 | continue 119 | } 120 | 121 | // We now have a list of names (from one line of source code) all being 122 | // declared with the desired type. 123 | // Grab their names and actual values and store them in f.values. 124 | for _, name := range vspec.Names { 125 | if name.Name == "_" { 126 | continue 127 | } 128 | // This dance lets the type checker find the values for us. It's a 129 | // bit tricky: look up the object declared by the name, find its 130 | // types.Const, and extract its value. 131 | obj, ok := pkg.defs[name] 132 | if !ok { 133 | return nil, fmt.Errorf("no value for constant %s", name) 134 | } 135 | info := obj.Type().Underlying().(*types.Basic).Info() 136 | if info&types.IsInteger == 0 { 137 | return nil, fmt.Errorf("can't handle non-integer constant type %s", typ) 138 | } 139 | value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST. 140 | if value.Kind() != constant.Int { 141 | log.Fatalf("can't happen: constant is not an integer %s", name) 142 | } 143 | values = append(values, name.Name) 144 | } 145 | } 146 | return values, nil 147 | } 148 | -------------------------------------------------------------------------------- /parser/parser_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package parser 15 | 16 | import ( 17 | "go/build" 18 | "io/ioutil" 19 | "os" 20 | "path/filepath" 21 | "testing" 22 | ) 23 | 24 | func must(t *testing.T, err error) { 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | } 29 | 30 | var fakeCode = ` 31 | package main 32 | import "fmt" 33 | func main() { 34 | fmt.Println("Hello world!") 35 | } 36 | ` 37 | 38 | func TestParseFromMultipleGopath(t *testing.T) { 39 | gopaths := filepath.SplitList(build.Default.GOPATH) 40 | if len(gopaths) < 2 { 41 | t.Skipf("No multiple GOPATH (%s) exists, skiping..", build.Default.GOPATH) 42 | } 43 | gopath := gopaths[len(gopaths)-1] 44 | dir := filepath.Join(gopath, "src", "foo") 45 | defer func() { must(t, os.RemoveAll(dir)) }() 46 | must(t, os.MkdirAll(dir, 0755)) 47 | must(t, ioutil.WriteFile(filepath.Join(dir, "main.go"), []byte(fakeCode), 0644)) 48 | 49 | if _, err := ParsePackage(dir); err != nil { 50 | t.Fatalf("Parse package (%v): %v", dir, err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /server/app.yaml: -------------------------------------------------------------------------------- 1 | # Unless required by applicable law or agreed to writing, software distributed 2 | # under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 3 | # CONDITIONS OF ANY KIND, either express or implied. 4 | 5 | # See the License for the specific language governing permissions and 6 | # limitations under the License. 7 | 8 | version: gae 9 | runtime: go 10 | api_version: go1 11 | 12 | handlers: 13 | - url: /generate 14 | script: _go_app 15 | 16 | - url: / 17 | static_files: static/home.html 18 | upload: static/home.html 19 | 20 | - url: / 21 | static_dir: static 22 | -------------------------------------------------------------------------------- /server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // +build !appengine 15 | 16 | package main 17 | 18 | import ( 19 | "flag" 20 | "net/http" 21 | ) 22 | 23 | func main() { 24 | port := flag.String("http", "127.0.0.1:8080", "ip and port to listen to") 25 | flag.Parse() 26 | http.HandleFunc("/", homeHandler) 27 | http.ListenAndServe(*port, nil) 28 | } 29 | 30 | func homeHandler(w http.ResponseWriter, r *http.Request) { 31 | http.ServeFile(w, r, "static/home.html") 32 | } 33 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // Server is an http server that provides an alternative way of generating code 15 | // based on int types and the constants defined with it. 16 | // 17 | // Use the http flag to change the address on which the server will listen for 18 | // requests. The default is 127.0.0.1:8080. 19 | package main 20 | 21 | import ( 22 | "bytes" 23 | "fmt" 24 | "io/ioutil" 25 | "log" 26 | "net/http" 27 | "os" 28 | "path/filepath" 29 | "text/template" 30 | 31 | "go/format" 32 | 33 | "github.com/campoy/jsonenums/parser" 34 | ) 35 | 36 | func init() { 37 | http.Handle("/generate", handler(generateHandler)) 38 | } 39 | 40 | func generateHandler(w http.ResponseWriter, r *http.Request) error { 41 | if r.Method != "GET" { 42 | return codeError{fmt.Errorf("only GET accepted"), http.StatusMethodNotAllowed} 43 | } 44 | code := r.FormValue("code") 45 | if code == "" { 46 | return codeError{fmt.Errorf("no code to be parsed"), http.StatusBadRequest} 47 | } 48 | typ := r.FormValue("type") 49 | if typ == "" { 50 | return codeError{fmt.Errorf("no type to be analyzed"), http.StatusBadRequest} 51 | } 52 | 53 | dir, err := createDir(code) 54 | if err != nil { 55 | return err 56 | } 57 | defer os.RemoveAll(dir) 58 | 59 | pkg, err := parser.ParsePackage(dir) 60 | if err != nil { 61 | return fmt.Errorf("parse package: %v", err) 62 | } 63 | 64 | values, err := pkg.ValuesOfType(typ) 65 | if err != nil { 66 | return fmt.Errorf("find values: %v", err) 67 | } 68 | 69 | t, err := template.New("code").Parse(r.FormValue("template")) 70 | if err != nil { 71 | return codeError{fmt.Errorf("parse template: %v", err), http.StatusBadRequest} 72 | } 73 | var data = struct { 74 | PackageName string 75 | TypeName string 76 | Values []string 77 | }{pkg.Name, typ, values} 78 | 79 | var buf bytes.Buffer 80 | if err := t.Execute(&buf, data); err != nil { 81 | return codeError{fmt.Errorf("execute template: %v", err), http.StatusBadRequest} 82 | } 83 | src, err := format.Source(buf.Bytes()) 84 | if err != nil { 85 | return codeError{fmt.Errorf("code generated is not valid: %v\n%v", err, buf.String()), http.StatusBadRequest} 86 | } 87 | w.Write(src) 88 | return nil 89 | } 90 | 91 | func createDir(content string) (string, error) { 92 | dir, err := ioutil.TempDir("", "jsonenums") 93 | if err != nil { 94 | return "", fmt.Errorf("create tmp dir: %v", err) 95 | } 96 | f, err := os.Create(filepath.Join(dir, "main.go")) 97 | if err != nil { 98 | os.RemoveAll(dir) 99 | return "", fmt.Errorf("create tmp file: %v", err) 100 | } 101 | f.WriteString(content) 102 | f.Close() 103 | return dir, err 104 | } 105 | 106 | type handler func(http.ResponseWriter, *http.Request) error 107 | 108 | func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 109 | err := h(w, r) 110 | if err != nil { 111 | code := http.StatusInternalServerError 112 | if cErr, ok := err.(codeError); ok { 113 | code = cErr.code 114 | } else { 115 | log.Printf("%v: %v", r.URL, code) 116 | } 117 | http.Error(w, err.Error(), code) 118 | } 119 | } 120 | 121 | type codeError struct { 122 | error 123 | code int 124 | } 125 | -------------------------------------------------------------------------------- /server/static/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | jsonenums 18 | 30 | 31 | 32 | 47 | 48 | 49 | 50 |
51 | 52 | 67 | 80 |
81 | 82 | 83 | 84 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /template.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // Added as a .go file to avoid embedding issues of the template. 15 | 16 | package main 17 | 18 | import "text/template" 19 | 20 | var generatedTmpl = template.Must(template.New("generated").Parse(` 21 | // Code generated by jsonenums {{.Command}}; DO NOT EDIT. 22 | 23 | package {{.PackageName}} 24 | 25 | import ( 26 | "encoding/json" 27 | "fmt" 28 | ) 29 | 30 | {{range $typename, $values := .TypesAndValues}} 31 | 32 | var ( 33 | _{{$typename}}NameToValue = map[string]{{$typename}} { 34 | {{range $values}}"{{.}}": {{.}}, 35 | {{end}} 36 | } 37 | 38 | _{{$typename}}ValueToName = map[{{$typename}}]string { 39 | {{range $values}}{{.}}: "{{.}}", 40 | {{end}} 41 | } 42 | ) 43 | 44 | func init() { 45 | var v {{$typename}} 46 | if _, ok := interface{}(v).(fmt.Stringer); ok { 47 | _{{$typename}}NameToValue = map[string]{{$typename}} { 48 | {{range $values}}interface{}({{.}}).(fmt.Stringer).String(): {{.}}, 49 | {{end}} 50 | } 51 | } 52 | } 53 | 54 | // MarshalJSON is generated so {{$typename}} satisfies json.Marshaler. 55 | func (r {{$typename}}) MarshalJSON() ([]byte, error) { 56 | if s, ok := interface{}(r).(fmt.Stringer); ok { 57 | return json.Marshal(s.String()) 58 | } 59 | s, ok := _{{$typename}}ValueToName[r] 60 | if !ok { 61 | return nil, fmt.Errorf("invalid {{$typename}}: %d", r) 62 | } 63 | return json.Marshal(s) 64 | } 65 | 66 | // UnmarshalJSON is generated so {{$typename}} satisfies json.Unmarshaler. 67 | func (r *{{$typename}}) UnmarshalJSON(data []byte) error { 68 | var s string 69 | if err := json.Unmarshal(data, &s); err != nil { 70 | return fmt.Errorf("{{$typename}} should be a string, got %s", data) 71 | } 72 | v, ok := _{{$typename}}NameToValue[s] 73 | if !ok { 74 | return fmt.Errorf("invalid {{$typename}} %q", s) 75 | } 76 | *r = v 77 | return nil 78 | } 79 | 80 | {{end}} 81 | `)) 82 | --------------------------------------------------------------------------------