From 0d00bd7082f6399af7d8742a4fe6e8e4559884ed Mon Sep 17 00:00:00 2001 From: John Southworth Date: Sat, 17 Oct 2020 14:50:00 -0700 Subject: [PATCH] Add the ability to create unexported types from an exported template This allows one to create an exported template type and create a non exported type from that template. Fixes #66 --- main.go | 11 +++++----- parse/parse.go | 30 +++++++++++++++------------ parse/parse_int_test.go | 16 +++++++++++++- parse/parse_test.go | 10 ++++++++- parse/test/queue/int_queue_private.go | 22 ++++++++++++++++++++ 5 files changed, 69 insertions(+), 20 deletions(-) create mode 100644 parse/test/queue/int_queue_private.go diff --git a/main.go b/main.go index 37d2a37..b075679 100644 --- a/main.go +++ b/main.go @@ -37,6 +37,7 @@ func main() { out = flag.String("out", "", "file to save output to instead of stdout") pkgName = flag.String("pkg", "", "package name for generated files") genTag = flag.String("tag", "", "build tag that is stripped from output") + private = flag.Bool("private", false, "don't export the generated type") prefix = "https://github.com/metabition/gennylib/raw/master/" ) flag.Parse() @@ -84,7 +85,7 @@ func main() { } r.Body.Close() br := bytes.NewReader(b) - err = gen(*in, outputFilename, *pkgName, *genTag, br, typeSets, outWriter) + err = gen(*in, outputFilename, *pkgName, *genTag, br, typeSets, outWriter, *private) } else if len(*in) > 0 { var file *os.File file, err = os.Open(*in) @@ -92,7 +93,7 @@ func main() { fatal(exitcodeSourceFileInvalid, err) } defer file.Close() - err = gen(*in, outputFilename, *pkgName, *genTag, file, typeSets, outWriter) + err = gen(*in, outputFilename, *pkgName, *genTag, file, typeSets, outWriter, *private) } else { var source []byte source, err = ioutil.ReadAll(os.Stdin) @@ -100,7 +101,7 @@ func main() { fatal(exitcodeStdinFailed, err) } reader := bytes.NewReader(source) - err = gen("stdin", outputFilename, *pkgName, *genTag, reader, typeSets, outWriter) + err = gen("stdin", outputFilename, *pkgName, *genTag, reader, typeSets, outWriter, *private) } // do the work @@ -144,12 +145,12 @@ func fatal(code int, a ...interface{}) { } // gen performs the generic generation. -func gen(filename, outputFilename, pkgName, tag string, in io.ReadSeeker, typesets []map[string]string, out io.Writer) error { +func gen(filename, outputFilename, pkgName, tag string, in io.ReadSeeker, typesets []map[string]string, out io.Writer, private bool) error { var output []byte var err error - output, err = parse.Generics(filename, outputFilename, pkgName, tag, in, typesets) + output, err = parse.Generics(filename, outputFilename, pkgName, tag, in, typesets, private) if err != nil { return err } diff --git a/parse/parse.go b/parse/parse.go index bbf8a82..d4f6961 100644 --- a/parse/parse.go +++ b/parse/parse.go @@ -38,33 +38,34 @@ var unwantedLinePrefixes = [][]byte{ []byte("//go:generate genny "), } -func subIntoLiteral(lit, typeTemplate, specificType string) string { +func subIntoLiteral(lit, typeTemplate, specificType string, private bool) string { if lit == typeTemplate { return specificType } if !strings.Contains(lit, typeTemplate) { return lit } - specificLg := wordify(specificType, true) - specificSm := wordify(specificType, false) + specificLg := wordify(specificType, true, private) + specificSm := wordify(specificType, false, private) result := strings.Replace(lit, typeTemplate, specificLg, -1) + fmt.Println(result, specificLg, isExported(lit), lit, specificSm) if strings.HasPrefix(result, specificLg) && !isExported(lit) { return strings.Replace(result, specificLg, specificSm, 1) } return result } -func subTypeIntoComment(line, typeTemplate, specificType string) string { +func subTypeIntoComment(line, typeTemplate, specificType string, private bool) string { var subbed string for _, w := range strings.Fields(line) { - subbed = subbed + subIntoLiteral(w, typeTemplate, specificType) + " " + subbed = subbed + subIntoLiteral(w, typeTemplate, specificType, private) + " " } return subbed } // Does the heavy lifting of taking a line of our code and // sbustituting a type into there for our generic type -func subTypeIntoLine(line, typeTemplate, specificType string) string { +func subTypeIntoLine(line, typeTemplate, specificType string, private bool) string { src := []byte(line) var s scanner.Scanner fset := token.NewFileSet() @@ -76,10 +77,10 @@ func subTypeIntoLine(line, typeTemplate, specificType string) string { if tok == token.EOF { break } else if tok == token.COMMENT { - subbed := subTypeIntoComment(lit, typeTemplate, specificType) + subbed := subTypeIntoComment(lit, typeTemplate, specificType, private) output = output + subbed + " " } else if tok.IsLiteral() { - subbed := subIntoLiteral(lit, typeTemplate, specificType) + subbed := subIntoLiteral(lit, typeTemplate, specificType, private) output = output + subbed + " " } else { output = output + tok.String() + " " @@ -89,7 +90,7 @@ func subTypeIntoLine(line, typeTemplate, specificType string) string { } // typeSet looks like "KeyType: int, ValueType: string" -func generateSpecific(filename string, in io.ReadSeeker, typeSet map[string]string) ([]byte, error) { +func generateSpecific(filename string, in io.ReadSeeker, typeSet map[string]string, private bool) ([]byte, error) { // ensure we are at the beginning of the file in.Seek(0, os.SEEK_SET) @@ -143,7 +144,7 @@ func generateSpecific(filename string, in io.ReadSeeker, typeSet map[string]stri for t, specificType := range typeSet { if strings.Contains(line, t) { - newLine := subTypeIntoLine(line, t, specificType) + newLine := subTypeIntoLine(line, t, specificType, private) line = newLine } } @@ -171,7 +172,7 @@ func generateSpecific(filename string, in io.ReadSeeker, typeSet map[string]stri // Generics parses the source file and generates the bytes replacing the // generic types for the keys map with the specific types (its value). -func Generics(filename, outputFilename, pkgName, tag string, in io.ReadSeeker, typeSets []map[string]string) ([]byte, error) { +func Generics(filename, outputFilename, pkgName, tag string, in io.ReadSeeker, typeSets []map[string]string, private bool) ([]byte, error) { var localUnwantedLinePrefixes [][]byte localUnwantedLinePrefixes = append(localUnwantedLinePrefixes, unwantedLinePrefixes...) @@ -184,7 +185,7 @@ func Generics(filename, outputFilename, pkgName, tag string, in io.ReadSeeker, t for _, typeSet := range typeSets { // generate the specifics - parsed, err := generateSpecific(filename, in, typeSet) + parsed, err := generateSpecific(filename, in, typeSet, private) if err != nil { return nil, err } @@ -266,10 +267,13 @@ func isAlphaNumeric(r rune) bool { // wordify turns a type into a nice word for function and type // names etc. -func wordify(s string, exported bool) string { +func wordify(s string, exported, private bool) string { s = strings.TrimRight(s, "{}") s = strings.TrimLeft(s, "*&") s = strings.Replace(s, ".", "", -1) + if private { + return strings.ToLower(string(s[0])) + s[1:] + } if !exported { return s } diff --git a/parse/parse_int_test.go b/parse/parse_int_test.go index d277f4b..67d0c0b 100644 --- a/parse/parse_int_test.go +++ b/parse/parse_int_test.go @@ -29,7 +29,21 @@ func TestWordify(t *testing.T) { "pack.type": "Packtype", "*pack.type": "Packtype", } { - assert.Equal(t, wordified, wordify(word, true)) + assert.Equal(t, wordified, wordify(word, true, false)) + } + + // Test non exported name generation + for word, wordified := range map[string]string{ + "int": "int", + "*int": "int", + "string": "string", + "*MyType": "myType", + "*myType": "myType", + "interface{}": "interface", + "pack.type": "packtype", + "*pack.type": "packtype", + } { + assert.Equal(t, wordified, wordify(word, true, true)) } } diff --git a/parse/parse_test.go b/parse/parse_test.go index 79c4f09..e49019d 100644 --- a/parse/parse_test.go +++ b/parse/parse_test.go @@ -18,6 +18,7 @@ var tests = []struct { in string tag string types []map[string]string + private bool // expectations expectedOut string @@ -36,6 +37,13 @@ var tests = []struct { types: []map[string]string{{"Something": "int"}}, expectedOut: `test/queue/changed/int_queue.go`, }, + { + filename: "generic_queue.go", + in: `test/queue/generic_queue.go`, + types: []map[string]string{{"Something": "int"}}, + expectedOut: `test/queue/int_queue_private.go`, + private: true, + }, { filename: "generic_queue.go", in: `test/queue/generic_queue.go`, @@ -134,7 +142,7 @@ func TestParse(t *testing.T) { test.in = contents(test.in) test.expectedOut = contents(test.expectedOut) - bytes, err := parse.Generics(test.filename, test.outputFilename, test.pkgName, test.tag, strings.NewReader(test.in), test.types) + bytes, err := parse.Generics(test.filename, test.outputFilename, test.pkgName, test.tag, strings.NewReader(test.in), test.types, test.private) // check the error if test.expectedErr == nil { diff --git a/parse/test/queue/int_queue_private.go b/parse/test/queue/int_queue_private.go new file mode 100644 index 0000000..6c25ac5 --- /dev/null +++ b/parse/test/queue/int_queue_private.go @@ -0,0 +1,22 @@ +// This file was automatically generated by genny. +// Any changes will be lost if this file is regenerated. +// see https://github.com/cheekybits/genny + +package queue + +// intQueue is a queue of ints. +type intQueue struct { + items []int +} + +func NewintQueue() *intQueue { + return &intQueue{items: make([]int, 0)} +} +func (q *intQueue) Push(item int) { + q.items = append(q.items, item) +} +func (q *intQueue) Pop() int { + item := q.items[0] + q.items = q.items[1:] + return item +}