From e2002764dee88b1da42a40154fe11cc627dac00c Mon Sep 17 00:00:00 2001 From: Danny Hadley Date: Fri, 10 Nov 2017 11:09:44 -0500 Subject: [PATCH 1/7] chore(refactor) removing feature sub package - moving to marlow --- marlow/{features => }/blueprint.go | 2 +- marlow/{features => }/blueprint_test.go | 2 +- marlow/constants/config.go | 10 +++++-- marlow/{features => }/createable.go | 2 +- marlow/{features => }/createable_test.go | 2 +- marlow/{features => }/deleteable.go | 2 +- marlow/{features => }/deleteable_test.go | 2 +- marlow/exhaust.go | 9 +++++++ marlow/exhaust_test.go | 28 +++++++++++++++++++ marlow/{features => }/queryable.go | 2 +- marlow/{features => }/queryable_test.go | 2 +- marlow/record_exhaust.go | 29 ++++++++++++++++++++ marlow/record_reader.go | 34 ++++++++++-------------- marlow/{features => }/store.go | 2 +- marlow/{features => }/store_test.go | 2 +- marlow/{features => }/updateable.go | 2 +- marlow/{features => }/updateable_test.go | 2 +- 17 files changed, 100 insertions(+), 34 deletions(-) rename marlow/{features => }/blueprint.go (99%) rename marlow/{features => }/blueprint_test.go (99%) rename marlow/{features => }/createable.go (99%) rename marlow/{features => }/createable_test.go (99%) rename marlow/{features => }/deleteable.go (99%) rename marlow/{features => }/deleteable_test.go (99%) create mode 100644 marlow/exhaust.go create mode 100644 marlow/exhaust_test.go rename marlow/{features => }/queryable.go (99%) rename marlow/{features => }/queryable_test.go (99%) create mode 100644 marlow/record_exhaust.go rename marlow/{features => }/store.go (97%) rename marlow/{features => }/store_test.go (99%) rename marlow/{features => }/updateable.go (99%) rename marlow/{features => }/updateable_test.go (99%) diff --git a/marlow/features/blueprint.go b/marlow/blueprint.go similarity index 99% rename from marlow/features/blueprint.go rename to marlow/blueprint.go index b3f36e7..bad3fcf 100644 --- a/marlow/features/blueprint.go +++ b/marlow/blueprint.go @@ -1,4 +1,4 @@ -package features +package marlow import "io" import "fmt" diff --git a/marlow/features/blueprint_test.go b/marlow/blueprint_test.go similarity index 99% rename from marlow/features/blueprint_test.go rename to marlow/blueprint_test.go index 562ce17..0e22f72 100644 --- a/marlow/features/blueprint_test.go +++ b/marlow/blueprint_test.go @@ -1,4 +1,4 @@ -package features +package marlow import "io" import "fmt" diff --git a/marlow/constants/config.go b/marlow/constants/config.go index e7474f7..9e5b440 100644 --- a/marlow/constants/config.go +++ b/marlow/constants/config.go @@ -37,8 +37,14 @@ const ( ColumnConfigOption = "column" // QueryableConfigOption boolean value, true/false based on fields ability to be updated. - QueryableConfigOption = "query" + QueryableConfigOption = "queryable" // UpdateableConfigOption boolean value, true/false based on fields ability to be updated. - UpdateableConfigOption = "updates" + UpdateableConfigOption = "updateable" + + // DeleteableConfigOption boolean record config option for generating the deletion api methods. + DeleteableConfigOption = "deletable" + + // CreateableConfigOption boolean record config option for generating the creation api methods. + CreateableConfigOption = "createable" ) diff --git a/marlow/features/createable.go b/marlow/createable.go similarity index 99% rename from marlow/features/createable.go rename to marlow/createable.go index 4557aae..a710f07 100644 --- a/marlow/features/createable.go +++ b/marlow/createable.go @@ -1,4 +1,4 @@ -package features +package marlow import "io" import "fmt" diff --git a/marlow/features/createable_test.go b/marlow/createable_test.go similarity index 99% rename from marlow/features/createable_test.go rename to marlow/createable_test.go index 7b3deda..d482165 100644 --- a/marlow/features/createable_test.go +++ b/marlow/createable_test.go @@ -1,4 +1,4 @@ -package features +package marlow import "io" import "sync" diff --git a/marlow/features/deleteable.go b/marlow/deleteable.go similarity index 99% rename from marlow/features/deleteable.go rename to marlow/deleteable.go index c1e1edb..3070654 100644 --- a/marlow/features/deleteable.go +++ b/marlow/deleteable.go @@ -1,4 +1,4 @@ -package features +package marlow import "io" import "fmt" diff --git a/marlow/features/deleteable_test.go b/marlow/deleteable_test.go similarity index 99% rename from marlow/features/deleteable_test.go rename to marlow/deleteable_test.go index d85db24..a31c862 100644 --- a/marlow/features/deleteable_test.go +++ b/marlow/deleteable_test.go @@ -1,4 +1,4 @@ -package features +package marlow import "io" import "sync" diff --git a/marlow/exhaust.go b/marlow/exhaust.go new file mode 100644 index 0000000..dbf9b29 --- /dev/null +++ b/marlow/exhaust.go @@ -0,0 +1,9 @@ +package marlow + +import "go/ast" + +// FeatureExhaust defines an interface that is used by generators to comunicate dependencies during their code gen. +type FeatureExhaust interface { + RegisterImports(...string) + RegisterStoreMethods(...*ast.FuncDecl) +} diff --git a/marlow/exhaust_test.go b/marlow/exhaust_test.go new file mode 100644 index 0000000..76d2b83 --- /dev/null +++ b/marlow/exhaust_test.go @@ -0,0 +1,28 @@ +package marlow + +import "go/ast" + +type testExhaust struct { + imports map[string]bool + methods map[string]bool +} + +func (e *testExhaust) RegisterStoreMethods(methods ...*ast.FuncDecl) { + if e.methods == nil { + e.methods = make(map[string]bool) + } + + for _, k := range methods { + e.methods[k.Name.Name] = true + } +} + +func (e *testExhaust) RegisterImports(imports ...string) { + if e.imports == nil { + e.imports = make(map[string]bool) + } + + for _, k := range imports { + e.imports[k] = true + } +} diff --git a/marlow/features/queryable.go b/marlow/queryable.go similarity index 99% rename from marlow/features/queryable.go rename to marlow/queryable.go index d782a1e..9c782c3 100644 --- a/marlow/features/queryable.go +++ b/marlow/queryable.go @@ -1,4 +1,4 @@ -package features +package marlow import "io" import "fmt" diff --git a/marlow/features/queryable_test.go b/marlow/queryable_test.go similarity index 99% rename from marlow/features/queryable_test.go rename to marlow/queryable_test.go index 5fbcb8d..b816f82 100644 --- a/marlow/features/queryable_test.go +++ b/marlow/queryable_test.go @@ -1,4 +1,4 @@ -package features +package marlow import "fmt" import "io" diff --git a/marlow/record_exhaust.go b/marlow/record_exhaust.go new file mode 100644 index 0000000..386e04a --- /dev/null +++ b/marlow/record_exhaust.go @@ -0,0 +1,29 @@ +package marlow + +import "go/ast" + +type recordExhaust struct { + imports chan<- string + methods chan<- *ast.FuncDecl + registeredImports map[string]bool +} + +func (e *recordExhaust) RegisterStoreMethods(imports ...*ast.FuncDecl) { +} + +func (e *recordExhaust) RegisterImports(imports ...string) { + if e.registeredImports == nil { + e.registeredImports = make(map[string]bool) + } + + for _, i := range imports { + _, dupe := e.registeredImports[i] + + if dupe { + continue + } + + e.registeredImports[i] = true + e.imports <- i + } +} diff --git a/marlow/record_reader.go b/marlow/record_reader.go index 5a02db2..3f2ced7 100644 --- a/marlow/record_reader.go +++ b/marlow/record_reader.go @@ -9,7 +9,6 @@ import "reflect" import "net/url" import "strings" import "github.com/gedex/inflector" -import "github.com/dadleyy/marlow/marlow/features" import "github.com/dadleyy/marlow/marlow/constants" const ( @@ -150,28 +149,23 @@ func newRecordReader(root ast.Decl, imports chan<- string) (io.Reader, bool) { func readRecord(writer io.Writer, config url.Values, fields map[string]url.Values, imports chan<- string) error { buffer := new(bytes.Buffer) - enabled := make(map[string]bool) - readers := []io.Reader{ - features.NewCreateableGenerator(config, fields, imports), - features.NewDeleteableGenerator(config, fields, imports), + readers := make([]io.Reader, 0, 4) + + if v := config.Get(constants.CreateableConfigOption); v != "false" { + readers = append(readers, NewCreateableGenerator(config, fields, imports)) } - for _, fieldConfig := range fields { - queryable := fieldConfig.Get(constants.QueryableConfigOption) - updateable := fieldConfig.Get(constants.UpdateableConfigOption) + if v := config.Get(constants.DeleteableConfigOption); v != "false" { + readers = append(readers, NewDeleteableGenerator(config, fields, imports)) + } - if _, e := enabled[constants.QueryableConfigOption]; queryable != "false" && !e { - generator := features.NewQueryableGenerator(config, fields, imports) - readers = append(readers, generator) - enabled[constants.QueryableConfigOption] = true - } + if v := config.Get(constants.UpdateableConfigOption); v != "false" { + readers = append(readers, NewUpdateableGenerator(config, fields, imports)) + } - if _, e := enabled[constants.UpdateableConfigOption]; updateable != "false" && !e { - generator := features.NewUpdateableGenerator(config, fields, imports) - readers = append(readers, generator) - enabled[constants.UpdateableConfigOption] = true - } + if v := config.Get(constants.QueryableConfigOption); v != "false" { + readers = append(readers, NewQueryableGenerator(config, fields, imports)) } if len(readers) == 0 { @@ -186,8 +180,8 @@ func readRecord(writer io.Writer, config url.Values, fields map[string]url.Value // If we had any features enabled, we need to also generate the blue print API. readers = append( readers, - features.NewStoreGenerator(config, imports), - features.NewBlueprintGenerator(config, fields, imports), + NewStoreGenerator(config, imports), + NewBlueprintGenerator(config, fields, imports), ) // Iterate over all our collected features, copying them into the buffer diff --git a/marlow/features/store.go b/marlow/store.go similarity index 97% rename from marlow/features/store.go rename to marlow/store.go index e42abbb..7f97273 100644 --- a/marlow/features/store.go +++ b/marlow/store.go @@ -1,4 +1,4 @@ -package features +package marlow import "io" import "net/url" diff --git a/marlow/features/store_test.go b/marlow/store_test.go similarity index 99% rename from marlow/features/store_test.go rename to marlow/store_test.go index 35140bb..8034d6c 100644 --- a/marlow/features/store_test.go +++ b/marlow/store_test.go @@ -1,4 +1,4 @@ -package features +package marlow import "fmt" import "io" diff --git a/marlow/features/updateable.go b/marlow/updateable.go similarity index 99% rename from marlow/features/updateable.go rename to marlow/updateable.go index d4f3f74..7df39fc 100644 --- a/marlow/features/updateable.go +++ b/marlow/updateable.go @@ -1,4 +1,4 @@ -package features +package marlow import "io" import "fmt" diff --git a/marlow/features/updateable_test.go b/marlow/updateable_test.go similarity index 99% rename from marlow/features/updateable_test.go rename to marlow/updateable_test.go index 9a77bd9..7f06470 100644 --- a/marlow/features/updateable_test.go +++ b/marlow/updateable_test.go @@ -1,4 +1,4 @@ -package features +package marlow import "io" import "sync" From d5a1d8dc2e5d417ddd1e7b21fab4ab30824d7527 Mon Sep 17 00:00:00 2001 From: Danny Hadley Date: Fri, 10 Nov 2017 12:04:30 -0500 Subject: [PATCH 2/7] chore(refactor) renaming generator methods to be un-exported --- marlow/blueprint.go | 2 +- marlow/blueprint_test.go | 10 +++++----- marlow/createable.go | 4 ++-- marlow/createable_test.go | 4 ++-- marlow/deleteable.go | 4 ++-- marlow/deleteable_test.go | 4 ++-- marlow/queryable.go | 4 ++-- marlow/queryable_test.go | 4 ++-- marlow/record.go | 9 +++++++++ marlow/record_reader.go | 23 +++++++++++++++-------- marlow/store.go | 4 ++-- marlow/store_test.go | 4 ++-- marlow/updateable.go | 4 ++-- marlow/updateable_test.go | 4 ++-- marlow/writing/types_test.go | 10 +--------- marlow/writing/writer_test.go | 2 +- 16 files changed, 52 insertions(+), 44 deletions(-) create mode 100644 marlow/record.go diff --git a/marlow/blueprint.go b/marlow/blueprint.go index bad3fcf..ed245a4 100644 --- a/marlow/blueprint.go +++ b/marlow/blueprint.go @@ -408,7 +408,7 @@ func numericalMethods(print blueprint, name string, config url.Values, methods c } // NewBlueprintGenerator returns a reader that will generate the basic query struct type used for record lookups. -func NewBlueprintGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { +func newBlueprintGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { pr, pw := io.Pipe() go func() { diff --git a/marlow/blueprint_test.go b/marlow/blueprint_test.go index 0e22f72..43cf789 100644 --- a/marlow/blueprint_test.go +++ b/marlow/blueprint_test.go @@ -36,7 +36,7 @@ func Test_Blueprint(t *testing.T) { }) }) - g.Describe("NewBlueprintGenerator", func() { + g.Describe("blueprint generator test suite", func() { var inputs chan string var receivedInputs map[string]bool @@ -80,13 +80,13 @@ func Test_Blueprint(t *testing.T) { }) g.It("returns an error if a field does not have a type", func() { - reader := NewBlueprintGenerator(r, f, inputs) + reader := newBlueprintGenerator(r, f, inputs) _, e := io.Copy(b, reader) g.Assert(e == nil).Equal(false) }) g.It("did not send any imports over the channel", func() { - reader := NewBlueprintGenerator(r, f, inputs) + reader := newBlueprintGenerator(r, f, inputs) io.Copy(b, reader) close(inputs) wg.Wait() @@ -115,7 +115,7 @@ func Test_Blueprint(t *testing.T) { }) g.It("injected the strings library to the import stream", func() { - io.Copy(b, NewBlueprintGenerator(r, f, inputs)) + io.Copy(b, newBlueprintGenerator(r, f, inputs)) closed = true close(inputs) wg.Wait() @@ -124,7 +124,7 @@ func Test_Blueprint(t *testing.T) { g.It("produced valid a golang struct", func() { fmt.Fprintln(b, "package marlowt") - _, e := io.Copy(b, NewBlueprintGenerator(r, f, inputs)) + _, e := io.Copy(b, newBlueprintGenerator(r, f, inputs)) g.Assert(e).Equal(nil) _, e = parser.ParseFile(token.NewFileSet(), "", b, parser.AllErrors) g.Assert(e).Equal(nil) diff --git a/marlow/createable.go b/marlow/createable.go index a710f07..3fba4d0 100644 --- a/marlow/createable.go +++ b/marlow/createable.go @@ -24,8 +24,8 @@ type createableSymbolList struct { AffectedError string } -// NewCreateableGenerator returns a reader that will generate a record store's creation api. -func NewCreateableGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { +// newCreateableGenerator returns a reader that will generate a record store's creation api. +func newCreateableGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { pr, pw := io.Pipe() storeName := record.Get(constants.StoreNameConfigOption) diff --git a/marlow/createable_test.go b/marlow/createable_test.go index d482165..be5fbe7 100644 --- a/marlow/createable_test.go +++ b/marlow/createable_test.go @@ -21,7 +21,7 @@ type createableTestScaffold struct { } func (s *createableTestScaffold) g() io.Reader { - return NewCreateableGenerator(s.record, s.fields, s.imports) + return newCreateableGenerator(s.record, s.fields, s.imports) } func Test_Createable(t *testing.T) { @@ -29,7 +29,7 @@ func Test_Createable(t *testing.T) { var scaffold *createableTestScaffold - g.Describe("createable test suite", func() { + g.Describe("createable feature generator test suite", func() { g.BeforeEach(func() { scaffold = &createableTestScaffold{ diff --git a/marlow/deleteable.go b/marlow/deleteable.go index 3070654..3d1e028 100644 --- a/marlow/deleteable.go +++ b/marlow/deleteable.go @@ -18,8 +18,8 @@ type deleteableSymbols struct { StatementError string } -// NewDeleteableGenerator is responsible for creating a generator that will write out the Delete api methods. -func NewDeleteableGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { +// newDeleteableGenerator is responsible for creating a generator that will write out the Delete api methods. +func newDeleteableGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { pr, pw := io.Pipe() storeName := record.Get(constants.StoreNameConfigOption) diff --git a/marlow/deleteable_test.go b/marlow/deleteable_test.go index a31c862..0e15735 100644 --- a/marlow/deleteable_test.go +++ b/marlow/deleteable_test.go @@ -21,7 +21,7 @@ type deleteableTestScaffold struct { } func (s *deleteableTestScaffold) g() io.Reader { - return NewDeleteableGenerator(s.record, s.fields, s.imports) + return newDeleteableGenerator(s.record, s.fields, s.imports) } func Test_Deleteable(t *testing.T) { @@ -29,7 +29,7 @@ func Test_Deleteable(t *testing.T) { var scaffold *deleteableTestScaffold - g.Describe("Deleteable feature test suite", func() { + g.Describe("deleteable feature generator test suite", func() { g.BeforeEach(func() { scaffold = &deleteableTestScaffold{ diff --git a/marlow/queryable.go b/marlow/queryable.go index 9c782c3..2f009b7 100644 --- a/marlow/queryable.go +++ b/marlow/queryable.go @@ -467,8 +467,8 @@ func selector(record url.Values, name string, config url.Values, imports chan<- return pr } -// NewQueryableGenerator is responsible for returning a reader that will generate lookup functions for a given record. -func NewQueryableGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { +// newQueryableGenerator is responsible for returning a reader that will generate lookup functions for a given record. +func newQueryableGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { pr, pw := io.Pipe() table := record.Get(constants.TableNameConfigOption) diff --git a/marlow/queryable_test.go b/marlow/queryable_test.go index b816f82..5dc92c6 100644 --- a/marlow/queryable_test.go +++ b/marlow/queryable_test.go @@ -24,7 +24,7 @@ type queryableTestScaffold struct { } func (s *queryableTestScaffold) g() io.Reader { - return NewQueryableGenerator(s.record, s.fields, s.imports) + return newQueryableGenerator(s.record, s.fields, s.imports) } func (s *queryableTestScaffold) parsed() (*ast.File, error) { @@ -36,7 +36,7 @@ func Test_QueryableGenerator(t *testing.T) { var scaffold *queryableTestScaffold - g.Describe("NewQueryableGenerator", func() { + g.Describe("queryable feature test suite", func() { g.BeforeEach(func() { scaffold = &queryableTestScaffold{ diff --git a/marlow/record.go b/marlow/record.go new file mode 100644 index 0000000..4ba296c --- /dev/null +++ b/marlow/record.go @@ -0,0 +1,9 @@ +package marlow + +import "net/url" + +// marlowRecord structs represent both the field-level and record-level configuration options for gerating the marlow stores. +type marlowRecord struct { + config url.Values + fields map[string]url.Values +} diff --git a/marlow/record_reader.go b/marlow/record_reader.go index 3f2ced7..1c3fdbd 100644 --- a/marlow/record_reader.go +++ b/marlow/record_reader.go @@ -140,32 +140,39 @@ func newRecordReader(root ast.Decl, imports chan<- string) (io.Reader, bool) { } go func() { - e := readRecord(pw, recordConfig, recordFields, imports) + record := marlowRecord{ + config: recordConfig, + fields: recordFields, + } + + e := readRecord(pw, record, imports) pw.CloseWithError(e) }() return pr, true } -func readRecord(writer io.Writer, config url.Values, fields map[string]url.Values, imports chan<- string) error { +func readRecord(writer io.Writer, record marlowRecord, imports chan<- string) error { buffer := new(bytes.Buffer) + config, fields := record.config, record.fields + readers := make([]io.Reader, 0, 4) if v := config.Get(constants.CreateableConfigOption); v != "false" { - readers = append(readers, NewCreateableGenerator(config, fields, imports)) + readers = append(readers, newCreateableGenerator(config, fields, imports)) } if v := config.Get(constants.DeleteableConfigOption); v != "false" { - readers = append(readers, NewDeleteableGenerator(config, fields, imports)) + readers = append(readers, newDeleteableGenerator(config, fields, imports)) } if v := config.Get(constants.UpdateableConfigOption); v != "false" { - readers = append(readers, NewUpdateableGenerator(config, fields, imports)) + readers = append(readers, newUpdateableGenerator(config, fields, imports)) } if v := config.Get(constants.QueryableConfigOption); v != "false" { - readers = append(readers, NewQueryableGenerator(config, fields, imports)) + readers = append(readers, newQueryableGenerator(config, fields, imports)) } if len(readers) == 0 { @@ -180,8 +187,8 @@ func readRecord(writer io.Writer, config url.Values, fields map[string]url.Value // If we had any features enabled, we need to also generate the blue print API. readers = append( readers, - NewStoreGenerator(config, imports), - NewBlueprintGenerator(config, fields, imports), + newStoreGenerator(config, imports), + newBlueprintGenerator(config, fields, imports), ) // Iterate over all our collected features, copying them into the buffer diff --git a/marlow/store.go b/marlow/store.go index 7f97273..ec8eaad 100644 --- a/marlow/store.go +++ b/marlow/store.go @@ -22,8 +22,8 @@ func writeStore(destination io.Writer, record url.Values, imports chan<- string) return nil } -// NewStoreGenerator returns a reader that will generate the centralized record store for a given record. -func NewStoreGenerator(record url.Values, imports chan<- string) io.Reader { +// newStoreGenerator returns a reader that will generate the centralized record store for a given record. +func newStoreGenerator(record url.Values, imports chan<- string) io.Reader { pr, pw := io.Pipe() go func() { diff --git a/marlow/store_test.go b/marlow/store_test.go index 8034d6c..aa00ad5 100644 --- a/marlow/store_test.go +++ b/marlow/store_test.go @@ -21,7 +21,7 @@ type storeTestScaffold struct { } func (s *storeTestScaffold) g() io.Reader { - return NewStoreGenerator(s.record, s.imports) + return newStoreGenerator(s.record, s.imports) } func (s *storeTestScaffold) parsed() (*ast.File, error) { @@ -39,7 +39,7 @@ func Test_StoreGenerator(t *testing.T) { var scaffold *storeTestScaffold - g.Describe("NewStoreGenerator", func() { + g.Describe("store generator test suite", func() { g.BeforeEach(func() { scaffold = &storeTestScaffold{ diff --git a/marlow/updateable.go b/marlow/updateable.go index 7df39fc..0b55958 100644 --- a/marlow/updateable.go +++ b/marlow/updateable.go @@ -139,8 +139,8 @@ func updater(record url.Values, name string, config url.Values, imports chan<- s return pr } -// NewUpdateableGenerator is responsible for generating updating store methods. -func NewUpdateableGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { +// newUpdateableGenerator is responsible for generating updating store methods. +func newUpdateableGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { readers := make([]io.Reader, 0, len(fields)) for name, config := range fields { diff --git a/marlow/updateable_test.go b/marlow/updateable_test.go index 7f06470..ffd4588 100644 --- a/marlow/updateable_test.go +++ b/marlow/updateable_test.go @@ -19,7 +19,7 @@ type updateableTestScaffold struct { } func (s *updateableTestScaffold) g() io.Reader { - return NewUpdateableGenerator(s.record, s.fields, s.imports) + return newUpdateableGenerator(s.record, s.fields, s.imports) } func Test_Updateable(t *testing.T) { @@ -27,7 +27,7 @@ func Test_Updateable(t *testing.T) { var scaffold *updateableTestScaffold - g.Describe("Updateable test suite", func() { + g.Describe("updateable feature generator test suite", func() { g.BeforeEach(func() { scaffold = &updateableTestScaffold{ diff --git a/marlow/writing/types_test.go b/marlow/writing/types_test.go index 1215580..7d4031f 100644 --- a/marlow/writing/types_test.go +++ b/marlow/writing/types_test.go @@ -4,7 +4,7 @@ import "fmt" import "testing" import "github.com/franela/goblin" -func Test_mapListToWrappedCommaList(t *testing.T) { +func Test_Types(t *testing.T) { g := goblin.Goblin(t) g.Describe("mapListToWrappedCommaList", func() { @@ -23,10 +23,6 @@ func Test_mapListToWrappedCommaList(t *testing.T) { g.Assert(v).Equal("") }) }) -} - -func Test_StringSliceLiteral(t *testing.T) { - g := goblin.Goblin(t) g.Describe("StringSliceLiteral", func() { var l StringSliceLiteral @@ -37,10 +33,6 @@ func Test_StringSliceLiteral(t *testing.T) { g.Assert(v).Equal("[]string{\"hello\",\"world\"}") }) }) -} - -func Test_SingleQuotedStringList(t *testing.T) { - g := goblin.Goblin(t) g.Describe("SingleQuotedStringList", func() { var l SingleQuotedStringList diff --git a/marlow/writing/writer_test.go b/marlow/writing/writer_test.go index cc28f1a..2e48e5b 100644 --- a/marlow/writing/writer_test.go +++ b/marlow/writing/writer_test.go @@ -59,7 +59,7 @@ func Test_goWriter(t *testing.T) { }) }) - g.Describe("New", func() { + g.Describe("writer test suite", func() { var b *parseableBuffer var w GoWriter From 0ef6682854357c103b4b69c53289b4109386b397 Mon Sep 17 00:00:00 2001 From: Danny Hadley Date: Fri, 10 Nov 2017 12:07:26 -0500 Subject: [PATCH 3/7] chore(refactor) using map to dry up feature registration --- marlow/record_reader.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/marlow/record_reader.go b/marlow/record_reader.go index 1c3fdbd..0388838 100644 --- a/marlow/record_reader.go +++ b/marlow/record_reader.go @@ -159,20 +159,22 @@ func readRecord(writer io.Writer, record marlowRecord, imports chan<- string) er readers := make([]io.Reader, 0, 4) - if v := config.Get(constants.CreateableConfigOption); v != "false" { - readers = append(readers, newCreateableGenerator(config, fields, imports)) + features := map[string]func(url.Values, map[string]url.Values, chan<- string) io.Reader{ + constants.CreateableConfigOption: newCreateableGenerator, + constants.UpdateableConfigOption: newUpdateableGenerator, + constants.DeleteableConfigOption: newDeleteableGenerator, + constants.QueryableConfigOption: newQueryableGenerator, } - if v := config.Get(constants.DeleteableConfigOption); v != "false" { - readers = append(readers, newDeleteableGenerator(config, fields, imports)) - } + for flag, generator := range features { + v := config.Get(flag) - if v := config.Get(constants.UpdateableConfigOption); v != "false" { - readers = append(readers, newUpdateableGenerator(config, fields, imports)) - } + if v == "false" { + continue + } - if v := config.Get(constants.QueryableConfigOption); v != "false" { - readers = append(readers, newQueryableGenerator(config, fields, imports)) + g := generator(config, fields, imports) + readers = append(readers, g) } if len(readers) == 0 { From 7b0e57b1cc746c801ba065d57f5918b1690f6896 Mon Sep 17 00:00:00 2001 From: Danny Hadley Date: Sat, 11 Nov 2017 20:07:29 -0500 Subject: [PATCH 4/7] chore(cleanup) using marlowRecord in generator signatures --- marlow/blueprint.go | 95 ++++++++++++++++++-------------------- marlow/blueprint_test.go | 36 ++++++--------- marlow/constants/config.go | 6 +++ marlow/createable.go | 16 +++---- marlow/createable_test.go | 7 ++- marlow/deleteable.go | 12 +++-- marlow/deleteable_test.go | 6 ++- marlow/queryable.go | 82 ++++++++++++++++---------------- marlow/queryable_test.go | 7 ++- marlow/record.go | 7 ++- marlow/record_reader.go | 18 ++++---- marlow/updateable.go | 38 +++++++++------ marlow/updateable_test.go | 6 ++- 13 files changed, 179 insertions(+), 157 deletions(-) diff --git a/marlow/blueprint.go b/marlow/blueprint.go index ed245a4..60c2466 100644 --- a/marlow/blueprint.go +++ b/marlow/blueprint.go @@ -5,25 +5,15 @@ import "fmt" import "sync" import "strings" import "net/url" -import "github.com/gedex/inflector" import "github.com/dadleyy/marlow/marlow/writing" import "github.com/dadleyy/marlow/marlow/constants" -type blueprint struct { - record url.Values - fields map[string]url.Values -} - -func (p blueprint) Name() string { - singular := inflector.Singularize(p.record.Get(constants.RecordNameConfigOption)) - return fmt.Sprintf("%sBlueprint", singular) -} - -func writeBlueprint(destination io.Writer, bp blueprint, imports chan<- string) error { +func writeBlueprint(destination io.Writer, record marlowRecord, imports chan<- string) error { out := writing.NewGoWriter(destination) + blueprintName := record.config.Get(constants.BlueprintNameConfigOption) - e := out.WithStruct(bp.Name(), func(url.Values) error { - for name, config := range bp.fields { + e := out.WithStruct(blueprintName, func(url.Values) error { + for name, config := range record.fields { fieldType := config.Get("type") if fieldType == "" { @@ -32,12 +22,12 @@ func writeBlueprint(destination io.Writer, bp blueprint, imports chan<- string) // Support IN lookup on string fields. if fieldType == "int" { - out.Println("%s%s []int", name, bp.record.Get(constants.BlueprintRangeFieldSuffixConfigOption)) + out.Println("%s%s []int", name, record.config.Get(constants.BlueprintRangeFieldSuffixConfigOption)) } // Support LIKE lookup on string fields. if fieldType == "string" { - out.Println("%s%s []string", name, bp.record.Get(constants.BlueprintLikeFieldSuffixConfigOption)) + out.Println("%s%s []string", name, record.config.Get(constants.BlueprintLikeFieldSuffixConfigOption)) } if fieldImport := config.Get("import"); fieldImport != "" { @@ -75,8 +65,8 @@ func writeBlueprint(destination io.Writer, bp blueprint, imports chan<- string) wg.Done() }() - for name, config := range bp.fields { - fieldGenerators := fieldMethods(bp, name, config, methodReceiver) + for name, config := range record.fields { + fieldGenerators := fieldMethods(record, name, config, methodReceiver) if len(fieldGenerators) == 0 { continue @@ -100,7 +90,7 @@ func writeBlueprint(destination io.Writer, bp blueprint, imports chan<- string) // With all of our fields having generated non-exported clause generation methods on our struct, we can create the // final 'String' method which iterates over all of these, calling them and adding the non-empty string clauses to // a list, which eventually is returned as a joined string. - e = out.WithMethod("String", bp.Name(), nil, []string{"string"}, func(scope url.Values) error { + e = out.WithMethod("String", blueprintName, nil, []string{"string"}, func(scope url.Values) error { out.Println("%s := make([]string, 0, %d)", symbols.ClauseSlice, len(clauseMethods)) for _, method := range clauseMethods { @@ -123,7 +113,7 @@ func writeBlueprint(destination io.Writer, bp blueprint, imports chan<- string) return e } - return out.WithMethod("Values", bp.Name(), nil, []string{"[]interface{}"}, func(scope url.Values) error { + return out.WithMethod("Values", blueprintName, nil, []string{"[]interface{}"}, func(scope url.Values) error { out.Println("%s := make([]interface{}, 0, %d)", symbols.ClauseSlice, len(clauseMethods)) out.WithIf("%s == nil", func(url.Values) error { @@ -144,24 +134,24 @@ func writeBlueprint(destination io.Writer, bp blueprint, imports chan<- string) }) } -func fieldMethods(print blueprint, name string, config url.Values, methods chan<- string) []io.Reader { +func fieldMethods(record marlowRecord, name string, config url.Values, methods chan<- string) []io.Reader { fieldType := config.Get("type") - results := make([]io.Reader, 0, len(print.fields)) + results := make([]io.Reader, 0, len(record.fields)) if fieldType == "string" || fieldType == "int" { - results = append(results, simpleTypeIn(print, name, config, methods)) + results = append(results, simpleTypeIn(record, name, config, methods)) } if fieldType == "string" { - results = append(results, stringMethods(print, name, config, methods)) + results = append(results, stringMethods(record, name, config, methods)) } if fieldType == "int" { - results = append(results, numericalMethods(print, name, config, methods)) + results = append(results, numericalMethods(record, name, config, methods)) } if fieldType == "sql.NullInt64" { - results = append(results, nullableIntMethods(print, name, config, methods)) + results = append(results, nullableIntMethods(record, name, config, methods)) } if len(results) == 0 { @@ -173,10 +163,10 @@ func fieldMethods(print blueprint, name string, config url.Values, methods chan< return results } -func nullableIntMethods(print blueprint, fieldName string, config url.Values, methods chan<- string) io.Reader { +func nullableIntMethods(record marlowRecord, fieldName string, config url.Values, methods chan<- string) io.Reader { pr, pw := io.Pipe() columnName := config.Get(constants.ColumnConfigOption) - tableName := print.record.Get(constants.TableNameConfigOption) + tableName := record.config.Get(constants.TableNameConfigOption) methodName := fmt.Sprintf("%sInString", columnName) symbols := struct { @@ -187,12 +177,15 @@ func nullableIntMethods(print blueprint, fieldName string, config url.Values, me }{"_placeholders", "_values", "_v", "_joined"} columnReference := fmt.Sprintf("%s.%s", tableName, columnName) + blueprintName := record.config.Get(constants.BlueprintNameConfigOption) + + returns := []string{"string", "[]interface{}"} write := func() { writer := writing.NewGoWriter(pw) writer.Comment("[marlow] nullable clause gen for \"%s\"", columnReference) - e := writer.WithMethod(methodName, print.Name(), nil, []string{"string", "[]interface{}"}, func(scope url.Values) error { + e := writer.WithMethod(methodName, blueprintName, nil, returns, func(scope url.Values) error { fieldReference := fmt.Sprintf("%s.%s", scope.Get("receiver"), fieldName) // Add conditional check for length presence on lookup slice. @@ -244,12 +237,13 @@ func nullableIntMethods(print blueprint, fieldName string, config url.Values, me return pr } -func simpleTypeIn(print blueprint, fieldName string, config url.Values, methods chan<- string) io.Reader { +func simpleTypeIn(record marlowRecord, fieldName string, fieldConfig url.Values, methods chan<- string) io.Reader { pr, pw := io.Pipe() - columnName := config.Get(constants.ColumnConfigOption) - tableName := print.record.Get(constants.TableNameConfigOption) + columnName := fieldConfig.Get(constants.ColumnConfigOption) + tableName := record.config.Get(constants.TableNameConfigOption) methodName := fmt.Sprintf("%sInString", columnName) columnReference := fmt.Sprintf("%s.%s", tableName, columnName) + blueprintName := record.config.Get(constants.BlueprintNameConfigOption) symbols := struct { PlaceholderSlice string @@ -258,11 +252,13 @@ func simpleTypeIn(print blueprint, fieldName string, config url.Values, methods JoinedValues string }{"_placeholder", "_values", "_v", "_joined"} + returns := []string{"string", "[]interface{}"} + write := func() { writer := writing.NewGoWriter(pw) writer.Comment("[marlow] type IN clause for \"%s\"", columnReference) - e := writer.WithMethod(methodName, print.Name(), nil, []string{"string", "[]interface{}"}, func(scope url.Values) error { + e := writer.WithMethod(methodName, blueprintName, nil, returns, func(scope url.Values) error { fieldReference := fmt.Sprintf("%s.%s", scope.Get("receiver"), fieldName) // Add conditional check for length presence on lookup slice. @@ -302,11 +298,14 @@ func simpleTypeIn(print blueprint, fieldName string, config url.Values, methods return pr } -func stringMethods(print blueprint, name string, config url.Values, methods chan<- string) io.Reader { - columnName := config.Get(constants.ColumnConfigOption) +func stringMethods(record marlowRecord, fieldName string, fieldConfig url.Values, methods chan<- string) io.Reader { + columnName := fieldConfig.Get(constants.ColumnConfigOption) methodName := fmt.Sprintf("%sLikeString", columnName) - likeFieldName := fmt.Sprintf("%s%s", name, print.record.Get(constants.BlueprintLikeFieldSuffixConfigOption)) - columnReference := fmt.Sprintf("%s.%s", print.record.Get(constants.TableNameConfigOption), columnName) + likeSuffix := record.config.Get(constants.BlueprintLikeFieldSuffixConfigOption) + likeFieldName := fmt.Sprintf("%s%s", fieldName, likeSuffix) + tableName := record.config.Get(constants.TableNameConfigOption) + columnReference := fmt.Sprintf("%s.%s", tableName, columnName) + blueprintName := record.config.Get(constants.BlueprintNameConfigOption) symbols := struct { PlaceholderSlice string @@ -323,7 +322,7 @@ func stringMethods(print blueprint, name string, config url.Values, methods chan writer := writing.NewGoWriter(pw) writer.Comment("[marlow] string LIKE clause for \"%s\"", columnReference) - e := writer.WithMethod(methodName, print.Name(), nil, returns, func(scope url.Values) error { + e := writer.WithMethod(methodName, blueprintName, nil, returns, func(scope url.Values) error { likeSlice := fmt.Sprintf("%s.%s", scope.Get("receiver"), likeFieldName) writer.WithIf("%s == nil || %s == nil || len(%s) == 0", func(url.Values) error { @@ -358,12 +357,13 @@ func stringMethods(print blueprint, name string, config url.Values, methods chan return pr } -func numericalMethods(print blueprint, name string, config url.Values, methods chan<- string) io.Reader { - tableName := print.record.Get(constants.TableNameConfigOption) - columnName := config.Get(constants.ColumnConfigOption) +func numericalMethods(record marlowRecord, fieldName string, fieldConfig url.Values, methods chan<- string) io.Reader { + tableName := record.config.Get(constants.TableNameConfigOption) + columnName := fieldConfig.Get(constants.ColumnConfigOption) rangeMethodName := fmt.Sprintf("%sRangeString", columnName) - rangeFieldName := fmt.Sprintf("%s%s", name, print.record.Get(constants.BlueprintRangeFieldSuffixConfigOption)) + rangeFieldName := fmt.Sprintf("%s%s", fieldName, record.config.Get(constants.BlueprintRangeFieldSuffixConfigOption)) columnReference := fmt.Sprintf("%s.%s", tableName, columnName) + blueprintName := record.config.Get(constants.BlueprintNameConfigOption) pr, pw := io.Pipe() @@ -377,7 +377,7 @@ func numericalMethods(print blueprint, name string, config url.Values, methods c writer := writing.NewGoWriter(pw) writer.Comment("[marlow] range clause methods for %s", columnReference) - e := writer.WithMethod(rangeMethodName, print.Name(), nil, returns, func(scope url.Values) error { + e := writer.WithMethod(rangeMethodName, blueprintName, nil, returns, func(scope url.Values) error { receiver := scope.Get("receiver") rangeArray := fmt.Sprintf("%s.%s", receiver, rangeFieldName) @@ -408,16 +408,11 @@ func numericalMethods(print blueprint, name string, config url.Values, methods c } // NewBlueprintGenerator returns a reader that will generate the basic query struct type used for record lookups. -func newBlueprintGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { +func newBlueprintGenerator(record marlowRecord, imports chan<- string) io.Reader { pr, pw := io.Pipe() go func() { - bp := blueprint{ - record: record, - fields: fields, - } - - e := writeBlueprint(pw, bp, imports) + e := writeBlueprint(pw, record, imports) pw.CloseWithError(e) }() diff --git a/marlow/blueprint_test.go b/marlow/blueprint_test.go index 43cf789..05ff2bc 100644 --- a/marlow/blueprint_test.go +++ b/marlow/blueprint_test.go @@ -9,32 +9,15 @@ import "net/url" import "go/token" import "go/parser" import "github.com/franela/goblin" +import "github.com/dadleyy/marlow/marlow/constants" func Test_Blueprint(t *testing.T) { g := goblin.Goblin(t) - var p blueprint var b *bytes.Buffer var r url.Values var f map[string]url.Values - - g.Describe("blueprint", func() { - - g.BeforeEach(func() { - f = make(map[string]url.Values) - r = make(url.Values) - p = blueprint{ - record: r, - fields: f, - } - }) - - g.It("generates the correct blueprint name based on the record's recordName config", func() { - r.Set("recordName", "Books") - n := p.Name() - g.Assert(n).Equal("BookBlueprint") - }) - }) + var record marlowRecord g.Describe("blueprint generator test suite", func() { @@ -53,6 +36,11 @@ func Test_Blueprint(t *testing.T) { f = make(map[string]url.Values) r = make(url.Values) + record = marlowRecord{ + config: r, + fields: f, + } + wg.Add(1) go func() { @@ -80,13 +68,13 @@ func Test_Blueprint(t *testing.T) { }) g.It("returns an error if a field does not have a type", func() { - reader := newBlueprintGenerator(r, f, inputs) + reader := newBlueprintGenerator(record, inputs) _, e := io.Copy(b, reader) g.Assert(e == nil).Equal(false) }) g.It("did not send any imports over the channel", func() { - reader := newBlueprintGenerator(r, f, inputs) + reader := newBlueprintGenerator(record, inputs) io.Copy(b, reader) close(inputs) wg.Wait() @@ -112,10 +100,12 @@ func Test_Blueprint(t *testing.T) { "type": []string{"sql.NullInt64"}, "column": []string{"company_id"}, } + + r.Set(constants.BlueprintNameConfigOption, "SomeBlueprint") }) g.It("injected the strings library to the import stream", func() { - io.Copy(b, newBlueprintGenerator(r, f, inputs)) + io.Copy(b, newBlueprintGenerator(record, inputs)) closed = true close(inputs) wg.Wait() @@ -124,7 +114,7 @@ func Test_Blueprint(t *testing.T) { g.It("produced valid a golang struct", func() { fmt.Fprintln(b, "package marlowt") - _, e := io.Copy(b, newBlueprintGenerator(r, f, inputs)) + _, e := io.Copy(b, newBlueprintGenerator(record, inputs)) g.Assert(e).Equal(nil) _, e = parser.ParseFile(token.NewFileSet(), "", b, parser.AllErrors) g.Assert(e).Equal(nil) diff --git a/marlow/constants/config.go b/marlow/constants/config.go index 9e5b440..11e1ed4 100644 --- a/marlow/constants/config.go +++ b/marlow/constants/config.go @@ -21,6 +21,12 @@ const ( // searching by the queryable interface. BlueprintLikeFieldSuffixConfigOption = "blueprintLikeFieldSuffix" + // BlueprintNameSuffix is added after the record name for the type that can be stringifyed into valid sql code. + BlueprintNameSuffix = "Blueprint" + + // BlueprintNameConfigOption holds the blueprint name on the record config. + BlueprintNameConfigOption = "blueprintName" + // StoreFindMethodPrefixConfigOption determines the prefix used when adding the main find/lookup method to the store. StoreFindMethodPrefixConfigOption = "storeFindMethodPrefix" diff --git a/marlow/createable.go b/marlow/createable.go index 3fba4d0..b96f451 100644 --- a/marlow/createable.go +++ b/marlow/createable.go @@ -25,13 +25,13 @@ type createableSymbolList struct { } // newCreateableGenerator returns a reader that will generate a record store's creation api. -func newCreateableGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { +func newCreateableGenerator(record marlowRecord, imports chan<- string) io.Reader { pr, pw := io.Pipe() - storeName := record.Get(constants.StoreNameConfigOption) - recordName := record.Get(constants.RecordNameConfigOption) + storeName := record.config.Get(constants.StoreNameConfigOption) + recordName := record.config.Get(constants.RecordNameConfigOption) methodName := fmt.Sprintf("Create%s", inflector.Pluralize(recordName)) - tableName := record.Get(constants.TableNameConfigOption) + tableName := record.config.Get(constants.TableNameConfigOption) symbols := createableSymbolList{ RecordParam: "_records", @@ -70,11 +70,11 @@ func newCreateableGenerator(record url.Values, fields map[string]url.Values, imp return nil }, symbols.RecordParam) - columnList := make([]string, 0, len(fields)) - placeholders := make([]string, 0, len(fields)) - fieldLookup := make(map[string]string, len(fields)) + columnList := make([]string, 0, len(record.fields)) + placeholders := make([]string, 0, len(record.fields)) + fieldLookup := make(map[string]string, len(record.fields)) - for field, c := range fields { + for field, c := range record.fields { columnName := c.Get(constants.ColumnConfigOption) if c.Get(constants.ColumnAutoIncrementFlag) != "" { diff --git a/marlow/createable_test.go b/marlow/createable_test.go index be5fbe7..edb187c 100644 --- a/marlow/createable_test.go +++ b/marlow/createable_test.go @@ -21,7 +21,12 @@ type createableTestScaffold struct { } func (s *createableTestScaffold) g() io.Reader { - return newCreateableGenerator(s.record, s.fields, s.imports) + record := marlowRecord{ + fields: s.fields, + config: s.record, + } + + return newCreateableGenerator(record, s.imports) } func Test_Createable(t *testing.T) { diff --git a/marlow/deleteable.go b/marlow/deleteable.go index 3d1e028..29e7027 100644 --- a/marlow/deleteable.go +++ b/marlow/deleteable.go @@ -19,13 +19,13 @@ type deleteableSymbols struct { } // newDeleteableGenerator is responsible for creating a generator that will write out the Delete api methods. -func newDeleteableGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { +func newDeleteableGenerator(record marlowRecord, imports chan<- string) io.Reader { pr, pw := io.Pipe() - storeName := record.Get(constants.StoreNameConfigOption) - recordName := record.Get(constants.RecordNameConfigOption) + storeName := record.config.Get(constants.StoreNameConfigOption) + recordName := record.config.Get(constants.RecordNameConfigOption) methodName := fmt.Sprintf("Delete%s", inflector.Pluralize(recordName)) - tableName := record.Get(constants.TableNameConfigOption) + tableName := record.config.Get(constants.TableNameConfigOption) symbols := deleteableSymbols{ Error: "_e", @@ -38,8 +38,10 @@ func newDeleteableGenerator(record url.Values, fields map[string]url.Values, imp ExecError: "_ee", } + blueprintName := record.config.Get(constants.BlueprintNameConfigOption) + params := []writing.FuncParam{ - {Type: fmt.Sprintf("*%s", blueprint{record: record}.Name()), Symbol: symbols.BlueprintParam}, + {Type: fmt.Sprintf("*%s", blueprintName), Symbol: symbols.BlueprintParam}, } returns := []string{ diff --git a/marlow/deleteable_test.go b/marlow/deleteable_test.go index 0e15735..e579260 100644 --- a/marlow/deleteable_test.go +++ b/marlow/deleteable_test.go @@ -21,7 +21,11 @@ type deleteableTestScaffold struct { } func (s *deleteableTestScaffold) g() io.Reader { - return newDeleteableGenerator(s.record, s.fields, s.imports) + record := marlowRecord{ + config: s.record, + fields: s.fields, + } + return newDeleteableGenerator(record, s.imports) } func Test_Deleteable(t *testing.T) { diff --git a/marlow/queryable.go b/marlow/queryable.go index 2f009b7..0c6842a 100644 --- a/marlow/queryable.go +++ b/marlow/queryable.go @@ -23,25 +23,22 @@ type finderSymbols struct { offset string } -func finder(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { - table := record.Get(constants.TableNameConfigOption) - recordName := record.Get(constants.RecordNameConfigOption) - store := record.Get(constants.StoreNameConfigOption) +func finder(record marlowRecord, imports chan<- string) io.Reader { + table := record.config.Get(constants.TableNameConfigOption) + recordName := record.config.Get(constants.RecordNameConfigOption) + store := record.config.Get(constants.StoreNameConfigOption) methodName := fmt.Sprintf("%s%s", - record.Get(constants.StoreFindMethodPrefixConfigOption), + record.config.Get(constants.StoreFindMethodPrefixConfigOption), inflector.Pluralize(recordName), ) pr, pw := io.Pipe() - if len(fields) == 0 { + if len(record.fields) == 0 { pw.CloseWithError(nil) return pr } - bp := blueprint{ - record: record, - fields: fields, - } + blueprintName := record.config.Get(constants.BlueprintNameConfigOption) symbols := finderSymbols{ blueprint: "_blueprint", @@ -62,14 +59,14 @@ func finder(record url.Values, fields map[string]url.Values, imports chan<- stri gosrc.Comment("[marlow feature]: finder on table[%s]", table) params := []writing.FuncParam{ - {Symbol: symbols.blueprint, Type: fmt.Sprintf("*%s", bp.Name())}, + {Symbol: symbols.blueprint, Type: fmt.Sprintf("*%s", blueprintName)}, } returns := []string{symbols.recordSlice, "error"} - fieldList := make([]string, 0, len(fields)) + fieldList := make([]string, 0, len(record.fields)) - for name, config := range fields { + for name, config := range record.fields { colName := config.Get(constants.ColumnConfigOption) if colName == "" { @@ -80,7 +77,7 @@ func finder(record url.Values, fields map[string]url.Values, imports chan<- stri fieldList = append(fieldList, expanded) } - defaultLimit := record.Get(constants.DefaultLimitConfigOption) + defaultLimit := record.config.Get(constants.DefaultLimitConfigOption) if defaultLimit == "" { pw.CloseWithError(fmt.Errorf("invalid defaultLimit for record %s", recordName)) @@ -176,9 +173,9 @@ func finder(record url.Values, fields map[string]url.Values, imports chan<- stri return gosrc.WithIter("%s.Next()", func(url.Values) error { gosrc.Println("var %s %s", symbols.rowItem, recordName) - references := make([]string, 0, len(fields)) + references := make([]string, 0, len(record.fields)) - for name := range fields { + for name := range record.fields { references = append(references, fmt.Sprintf("&%s.%s", symbols.rowItem, name)) } @@ -224,15 +221,15 @@ type counterSymbols struct { ScanError string } -func counter(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { - table := record.Get(constants.TableNameConfigOption) - recordName := record.Get(constants.RecordNameConfigOption) - store := record.Get(constants.StoreNameConfigOption) - blueprint := blueprint{record: record, fields: fields} +func counter(record marlowRecord, imports chan<- string) io.Reader { + table := record.config.Get(constants.TableNameConfigOption) + recordName := record.config.Get(constants.RecordNameConfigOption) + store := record.config.Get(constants.StoreNameConfigOption) + blueprintName := record.config.Get(constants.BlueprintNameConfigOption) pr, pw := io.Pipe() - methodPrefix := record.Get(constants.StoreCountMethodPrefixConfigOption) + methodPrefix := record.config.Get(constants.StoreCountMethodPrefixConfigOption) - if len(fields) == 0 { + if len(record.fields) == 0 { pw.CloseWithError(nil) return pr } @@ -254,7 +251,7 @@ func counter(record url.Values, fields map[string]url.Values, imports chan<- str gosrc.Comment("[marlow feature]: counter on table[%s]", table) params := []writing.FuncParam{ - {Symbol: symbols.BlueprintParamName, Type: fmt.Sprintf("*%s", blueprint.Name())}, + {Symbol: symbols.BlueprintParamName, Type: fmt.Sprintf("*%s", blueprintName)}, } returns := []string{ @@ -265,7 +262,7 @@ func counter(record url.Values, fields map[string]url.Values, imports chan<- str e := gosrc.WithMethod(symbols.CountMethodName, store, params, returns, func(scope url.Values) error { receiver := scope.Get("receiver") gosrc.WithIf("%s == nil", func(url.Values) error { - gosrc.Println("%s = &%s{}", params[0].Symbol, blueprint.Name()) + gosrc.Println("%s = &%s{}", params[0].Symbol, blueprintName) return nil }, symbols.BlueprintParamName) @@ -345,16 +342,16 @@ type selectorSymbols struct { ScanError string } -func selector(record url.Values, name string, config url.Values, imports chan<- string) io.Reader { +func selector(record marlowRecord, fieldName string, fieldConfig url.Values, imports chan<- string) io.Reader { pr, pw := io.Pipe() - blueprint := blueprint{record: record} - methodName := fmt.Sprintf("Select%s", inflector.Pluralize(name)) + blueprintName := record.config.Get(constants.BlueprintNameConfigOption) + methodName := fmt.Sprintf("Select%s", inflector.Pluralize(fieldName)) - tableName := record.Get(constants.TableNameConfigOption) - columnName := config.Get(constants.ColumnConfigOption) - storeName := record.Get(constants.StoreNameConfigOption) + tableName := record.config.Get(constants.TableNameConfigOption) + columnName := fieldConfig.Get(constants.ColumnConfigOption) + storeName := record.config.Get(constants.StoreNameConfigOption) - returnItemType := config.Get("type") + returnItemType := fieldConfig.Get("type") returnArrayType := fmt.Sprintf("[]%s", returnItemType) returns := []string{ @@ -375,7 +372,7 @@ func selector(record url.Values, name string, config url.Values, imports chan<- } params := []writing.FuncParam{ - {Type: fmt.Sprintf("*%s", blueprint.Name()), Symbol: symbols.BlueprintParam}, + {Type: fmt.Sprintf("*%s", blueprintName), Symbol: symbols.BlueprintParam}, } columnReference := fmt.Sprintf("%s.%s", tableName, columnName) @@ -383,7 +380,7 @@ func selector(record url.Values, name string, config url.Values, imports chan<- go func() { gosrc := writing.NewGoWriter(pw) - gosrc.Comment("[marlow] field selector for %s (%s) [print: %s]", name, methodName, blueprint.Name()) + gosrc.Comment("[marlow] field selector for %s (%s) [print: %s]", fieldName, methodName, blueprintName) e := gosrc.WithMethod(methodName, storeName, params, returns, func(scope url.Values) error { gosrc.Println("%s := make(%s, 0)", symbols.ReturnSlice, returnArrayType) @@ -468,12 +465,12 @@ func selector(record url.Values, name string, config url.Values, imports chan<- } // newQueryableGenerator is responsible for returning a reader that will generate lookup functions for a given record. -func newQueryableGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { +func newQueryableGenerator(record marlowRecord, imports chan<- string) io.Reader { pr, pw := io.Pipe() - table := record.Get(constants.TableNameConfigOption) - recordName := record.Get(constants.RecordNameConfigOption) - store := record.Get(constants.StoreNameConfigOption) + table := record.config.Get(constants.TableNameConfigOption) + recordName := record.config.Get(constants.RecordNameConfigOption) + store := record.config.Get(constants.StoreNameConfigOption) if len(table) == 0 || len(recordName) == 0 || len(store) == 0 { pw.CloseWithError(fmt.Errorf("invalid record config")) @@ -481,12 +478,13 @@ func newQueryableGenerator(record url.Values, fields map[string]url.Values, impo } features := []io.Reader{ - finder(record, fields, imports), - counter(record, fields, imports), + finder(record, imports), + counter(record, imports), } - for name, config := range fields { - features = append(features, selector(record, name, config, imports)) + for name, config := range record.fields { + s := selector(record, name, config, imports) + features = append(features, s) } go func() { diff --git a/marlow/queryable_test.go b/marlow/queryable_test.go index 5dc92c6..8369120 100644 --- a/marlow/queryable_test.go +++ b/marlow/queryable_test.go @@ -24,7 +24,11 @@ type queryableTestScaffold struct { } func (s *queryableTestScaffold) g() io.Reader { - return newQueryableGenerator(s.record, s.fields, s.imports) + record := marlowRecord{ + config: s.record, + fields: s.fields, + } + return newQueryableGenerator(record, s.imports) } func (s *queryableTestScaffold) parsed() (*ast.File, error) { @@ -117,6 +121,7 @@ func Test_QueryableGenerator(t *testing.T) { g.BeforeEach(func() { scaffold.record.Set("defaultLimit", "20") scaffold.record.Set("storeName", "BookStore") + scaffold.record.Set("blueprintName", "BookBlueprint") scaffold.record.Set("recordName", "Book") scaffold.record.Set("tableName", "books") scaffold.fields["Title"] = url.Values{ diff --git a/marlow/record.go b/marlow/record.go index 4ba296c..749ff1e 100644 --- a/marlow/record.go +++ b/marlow/record.go @@ -1,9 +1,12 @@ package marlow +import "go/ast" import "net/url" // marlowRecord structs represent both the field-level and record-level configuration options for gerating the marlow stores. type marlowRecord struct { - config url.Values - fields map[string]url.Values + config url.Values + fields map[string]url.Values + imports chan<- string + store chan<- *ast.FuncDecl } diff --git a/marlow/record_reader.go b/marlow/record_reader.go index 0388838..8e16d7d 100644 --- a/marlow/record_reader.go +++ b/marlow/record_reader.go @@ -23,9 +23,13 @@ func newRecordConfig(typeName string) url.Values { config.Set(constants.TableNameConfigOption, tableName) config.Set(constants.DefaultLimitConfigOption, fmt.Sprintf("%d", DefaultBlueprintLimit)) storeName := fmt.Sprintf("%sStore", typeName) + blueprintName := fmt.Sprintf("%s%s", typeName, constants.BlueprintNameSuffix) config.Set(constants.StoreNameConfigOption, storeName) + + config.Set(constants.BlueprintNameConfigOption, blueprintName) config.Set(constants.BlueprintRangeFieldSuffixConfigOption, "Range") config.Set(constants.BlueprintLikeFieldSuffixConfigOption, "Like") + config.Set(constants.StoreFindMethodPrefixConfigOption, "Find") config.Set(constants.StoreCountMethodPrefixConfigOption, "Count") config.Set(constants.UpdateFieldMethodPrefixConfigOption, "Update") @@ -155,11 +159,9 @@ func newRecordReader(root ast.Decl, imports chan<- string) (io.Reader, bool) { func readRecord(writer io.Writer, record marlowRecord, imports chan<- string) error { buffer := new(bytes.Buffer) - config, fields := record.config, record.fields - readers := make([]io.Reader, 0, 4) - features := map[string]func(url.Values, map[string]url.Values, chan<- string) io.Reader{ + features := map[string]func(marlowRecord, chan<- string) io.Reader{ constants.CreateableConfigOption: newCreateableGenerator, constants.UpdateableConfigOption: newUpdateableGenerator, constants.DeleteableConfigOption: newDeleteableGenerator, @@ -167,19 +169,19 @@ func readRecord(writer io.Writer, record marlowRecord, imports chan<- string) er } for flag, generator := range features { - v := config.Get(flag) + v := record.config.Get(flag) if v == "false" { continue } - g := generator(config, fields, imports) + g := generator(record, imports) readers = append(readers, g) } if len(readers) == 0 { comment := strings.NewReader( - fmt.Sprintf("// [marlow no-features]: %s\n", config.Get(constants.RecordNameConfigOption)), + fmt.Sprintf("// [marlow no-features]: %s\n", record.config.Get(constants.RecordNameConfigOption)), ) _, e := io.Copy(writer, comment) @@ -189,8 +191,8 @@ func readRecord(writer io.Writer, record marlowRecord, imports chan<- string) er // If we had any features enabled, we need to also generate the blue print API. readers = append( readers, - newStoreGenerator(config, imports), - newBlueprintGenerator(config, fields, imports), + newStoreGenerator(record.config, imports), + newBlueprintGenerator(record, imports), ) // Iterate over all our collected features, copying them into the buffer diff --git a/marlow/updateable.go b/marlow/updateable.go index 0b55958..e9cdf1e 100644 --- a/marlow/updateable.go +++ b/marlow/updateable.go @@ -19,13 +19,20 @@ type updaterSymbols struct { valueSlice string } -func updater(record url.Values, name string, config url.Values, imports chan<- string) io.Reader { +func updater(record marlowRecord, fieldName string, fieldConfig url.Values, imports chan<- string) io.Reader { pr, pw := io.Pipe() - blueprint := blueprint{record: record} - recordName := record.Get(constants.RecordNameConfigOption) - methodName := fmt.Sprintf("%s%s%s", record.Get(constants.UpdateFieldMethodPrefixConfigOption), recordName, name) - tableName, columnName := record.Get(constants.TableNameConfigOption), config.Get(constants.ColumnConfigOption) - storeName := record.Get(constants.StoreNameConfigOption) + recordName := record.config.Get(constants.RecordNameConfigOption) + + methodName := fmt.Sprintf( + "%s%s%s", + record.config.Get(constants.UpdateFieldMethodPrefixConfigOption), + recordName, + fieldName, + ) + + tableName := record.config.Get(constants.TableNameConfigOption) + columnName := fieldConfig.Get(constants.ColumnConfigOption) + storeName := record.config.Get(constants.StoreNameConfigOption) symbols := updaterSymbols{ valueParam: "_updates", @@ -41,12 +48,12 @@ func updater(record url.Values, name string, config url.Values, imports chan<- s } params := []writing.FuncParam{ - {Type: config.Get("type"), Symbol: symbols.valueParam}, - {Type: fmt.Sprintf("*%s", blueprint.Name()), Symbol: symbols.blueprintParam}, + {Type: fieldConfig.Get("type"), Symbol: symbols.valueParam}, + {Type: fmt.Sprintf("*%s", record.config.Get(constants.BlueprintNameConfigOption)), Symbol: symbols.blueprintParam}, } - if config.Get("type") == "sql.NullInt64" { - params[0].Type = fmt.Sprintf("*%s", config.Get("type")) + if fieldConfig.Get("type") == "sql.NullInt64" { + params[0].Type = fmt.Sprintf("*%s", fieldConfig.Get("type")) } returns := []string{ @@ -57,7 +64,7 @@ func updater(record url.Values, name string, config url.Values, imports chan<- s go func() { gosrc := writing.NewGoWriter(pw) - gosrc.Comment("[marlow] updater method for %s", name) + gosrc.Comment("[marlow] updater method for %s", fieldName) e := gosrc.WithMethod(methodName, storeName, params, returns, func(scope url.Values) error { gosrc.Println( @@ -140,11 +147,12 @@ func updater(record url.Values, name string, config url.Values, imports chan<- s } // newUpdateableGenerator is responsible for generating updating store methods. -func newUpdateableGenerator(record url.Values, fields map[string]url.Values, imports chan<- string) io.Reader { - readers := make([]io.Reader, 0, len(fields)) +func newUpdateableGenerator(record marlowRecord, imports chan<- string) io.Reader { + readers := make([]io.Reader, 0, len(record.fields)) - for name, config := range fields { - readers = append(readers, updater(record, name, config, imports)) + for name, config := range record.fields { + u := updater(record, name, config, imports) + readers = append(readers, u) } return io.MultiReader(readers...) diff --git a/marlow/updateable_test.go b/marlow/updateable_test.go index ffd4588..e75ad1d 100644 --- a/marlow/updateable_test.go +++ b/marlow/updateable_test.go @@ -19,7 +19,11 @@ type updateableTestScaffold struct { } func (s *updateableTestScaffold) g() io.Reader { - return newUpdateableGenerator(s.record, s.fields, s.imports) + record := marlowRecord{ + config: s.record, + fields: s.fields, + } + return newUpdateableGenerator(record, s.imports) } func Test_Updateable(t *testing.T) { From f9d992a1fdbbdf3cdb972a6a4d3ed6a6c6e45859 Mon Sep 17 00:00:00 2001 From: Danny Hadley Date: Sat, 11 Nov 2017 20:57:51 -0500 Subject: [PATCH 5/7] moving import channel - wrapping in registerImports method save --- Makefile | 1 - marlow/blueprint.go | 44 ++++++++------------- marlow/blueprint_test.go | 35 +++++++++-------- marlow/createable.go | 23 +++-------- marlow/createable_test.go | 7 ++-- marlow/deleteable.go | 18 +++------ marlow/deleteable_test.go | 7 ++-- marlow/queryable.go | 75 ++++++++++++++--------------------- marlow/queryable_test.go | 7 ++-- marlow/record.go | 46 +++++++++++++++++++--- marlow/record_exhaust.go | 29 -------------- marlow/record_reader.go | 19 ++++----- marlow/record_reader_test.go | 76 ++++++++++++++++++------------------ marlow/record_test.go | 46 ++++++++++++++++++++++ marlow/store.go | 12 +++--- marlow/store_test.go | 7 +++- marlow/updateable.go | 20 ++++------ marlow/updateable_test.go | 7 ++-- 18 files changed, 243 insertions(+), 236 deletions(-) delete mode 100644 marlow/record_exhaust.go create mode 100644 marlow/record_test.go diff --git a/Makefile b/Makefile index cecdffa..bf337ab 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,6 @@ example: $(LIBRARY_EXAMPLE_SRC) $(LIBRARY_EXAMPLE_MAIN) $(EXE) test-example: example mkdir -p $(LIBRARY_COVERAGE_OUTPUT_DIR) - $(EXE) -input=$(LIBRARY_EXAMPLE_MODEL_DIR) $(GO) test $(LIBRARY_EXAMPLE_TEST_FLAGS) -p=$(MAX_TEST_CONCURRENCY) $(LIBRARY_EXAMPLE_MODEL_DIR) $(GO) tool cover -html $(LIBRARY_EXAMPLE_COVERAGE_REPORT) -o $(LIBRARY_EXAMPLE_COVERAGE_DISTRIBUTABLE) $(VET) $(VET_FLAGS) $(LIBRARY_EXAMPLE_MODEL_DIR) diff --git a/marlow/blueprint.go b/marlow/blueprint.go index 60c2466..9d7046e 100644 --- a/marlow/blueprint.go +++ b/marlow/blueprint.go @@ -8,11 +8,10 @@ import "net/url" import "github.com/dadleyy/marlow/marlow/writing" import "github.com/dadleyy/marlow/marlow/constants" -func writeBlueprint(destination io.Writer, record marlowRecord, imports chan<- string) error { +func writeBlueprint(destination io.Writer, record marlowRecord) error { out := writing.NewGoWriter(destination) - blueprintName := record.config.Get(constants.BlueprintNameConfigOption) - e := out.WithStruct(blueprintName, func(url.Values) error { + e := out.WithStruct(record.blueprint(), func(url.Values) error { for name, config := range record.fields { fieldType := config.Get("type") @@ -31,7 +30,7 @@ func writeBlueprint(destination io.Writer, record marlowRecord, imports chan<- s } if fieldImport := config.Get("import"); fieldImport != "" { - imports <- fieldImport + record.registerImports(fieldImport) } out.Println("%s []%s", name, fieldType) @@ -49,8 +48,7 @@ func writeBlueprint(destination io.Writer, record marlowRecord, imports chan<- s return e } - imports <- "fmt" - imports <- "strings" + record.registerImports("fmt", "strings") var readers []io.Reader methodReceiver := make(chan string) @@ -90,7 +88,7 @@ func writeBlueprint(destination io.Writer, record marlowRecord, imports chan<- s // With all of our fields having generated non-exported clause generation methods on our struct, we can create the // final 'String' method which iterates over all of these, calling them and adding the non-empty string clauses to // a list, which eventually is returned as a joined string. - e = out.WithMethod("String", blueprintName, nil, []string{"string"}, func(scope url.Values) error { + e = out.WithMethod("String", record.blueprint(), nil, []string{"string"}, func(scope url.Values) error { out.Println("%s := make([]string, 0, %d)", symbols.ClauseSlice, len(clauseMethods)) for _, method := range clauseMethods { @@ -113,7 +111,7 @@ func writeBlueprint(destination io.Writer, record marlowRecord, imports chan<- s return e } - return out.WithMethod("Values", blueprintName, nil, []string{"[]interface{}"}, func(scope url.Values) error { + return out.WithMethod("Values", record.blueprint(), nil, []string{"[]interface{}"}, func(scope url.Values) error { out.Println("%s := make([]interface{}, 0, %d)", symbols.ClauseSlice, len(clauseMethods)) out.WithIf("%s == nil", func(url.Values) error { @@ -166,7 +164,6 @@ func fieldMethods(record marlowRecord, name string, config url.Values, methods c func nullableIntMethods(record marlowRecord, fieldName string, config url.Values, methods chan<- string) io.Reader { pr, pw := io.Pipe() columnName := config.Get(constants.ColumnConfigOption) - tableName := record.config.Get(constants.TableNameConfigOption) methodName := fmt.Sprintf("%sInString", columnName) symbols := struct { @@ -176,8 +173,7 @@ func nullableIntMethods(record marlowRecord, fieldName string, config url.Values JoinedValues string }{"_placeholders", "_values", "_v", "_joined"} - columnReference := fmt.Sprintf("%s.%s", tableName, columnName) - blueprintName := record.config.Get(constants.BlueprintNameConfigOption) + columnReference := fmt.Sprintf("%s.%s", record.table(), columnName) returns := []string{"string", "[]interface{}"} @@ -185,7 +181,7 @@ func nullableIntMethods(record marlowRecord, fieldName string, config url.Values writer := writing.NewGoWriter(pw) writer.Comment("[marlow] nullable clause gen for \"%s\"", columnReference) - e := writer.WithMethod(methodName, blueprintName, nil, returns, func(scope url.Values) error { + e := writer.WithMethod(methodName, record.blueprint(), nil, returns, func(scope url.Values) error { fieldReference := fmt.Sprintf("%s.%s", scope.Get("receiver"), fieldName) // Add conditional check for length presence on lookup slice. @@ -217,7 +213,7 @@ func nullableIntMethods(record marlowRecord, fieldName string, config url.Values writer.Println("%s := strings.Join(%s, \",\")", symbols.JoinedValues, symbols.PlaceholderSlice) writer.Println( "return fmt.Sprintf(\"%s.%s IN (%%s)\", %s), %s", - tableName, + record.table(), columnName, symbols.JoinedValues, symbols.ValueSlice, @@ -240,10 +236,8 @@ func nullableIntMethods(record marlowRecord, fieldName string, config url.Values func simpleTypeIn(record marlowRecord, fieldName string, fieldConfig url.Values, methods chan<- string) io.Reader { pr, pw := io.Pipe() columnName := fieldConfig.Get(constants.ColumnConfigOption) - tableName := record.config.Get(constants.TableNameConfigOption) methodName := fmt.Sprintf("%sInString", columnName) - columnReference := fmt.Sprintf("%s.%s", tableName, columnName) - blueprintName := record.config.Get(constants.BlueprintNameConfigOption) + columnReference := fmt.Sprintf("%s.%s", record.table(), columnName) symbols := struct { PlaceholderSlice string @@ -258,7 +252,7 @@ func simpleTypeIn(record marlowRecord, fieldName string, fieldConfig url.Values, writer := writing.NewGoWriter(pw) writer.Comment("[marlow] type IN clause for \"%s\"", columnReference) - e := writer.WithMethod(methodName, blueprintName, nil, returns, func(scope url.Values) error { + e := writer.WithMethod(methodName, record.blueprint(), nil, returns, func(scope url.Values) error { fieldReference := fmt.Sprintf("%s.%s", scope.Get("receiver"), fieldName) // Add conditional check for length presence on lookup slice. @@ -303,9 +297,7 @@ func stringMethods(record marlowRecord, fieldName string, fieldConfig url.Values methodName := fmt.Sprintf("%sLikeString", columnName) likeSuffix := record.config.Get(constants.BlueprintLikeFieldSuffixConfigOption) likeFieldName := fmt.Sprintf("%s%s", fieldName, likeSuffix) - tableName := record.config.Get(constants.TableNameConfigOption) - columnReference := fmt.Sprintf("%s.%s", tableName, columnName) - blueprintName := record.config.Get(constants.BlueprintNameConfigOption) + columnReference := fmt.Sprintf("%s.%s", record.table(), columnName) symbols := struct { PlaceholderSlice string @@ -322,7 +314,7 @@ func stringMethods(record marlowRecord, fieldName string, fieldConfig url.Values writer := writing.NewGoWriter(pw) writer.Comment("[marlow] string LIKE clause for \"%s\"", columnReference) - e := writer.WithMethod(methodName, blueprintName, nil, returns, func(scope url.Values) error { + e := writer.WithMethod(methodName, record.blueprint(), nil, returns, func(scope url.Values) error { likeSlice := fmt.Sprintf("%s.%s", scope.Get("receiver"), likeFieldName) writer.WithIf("%s == nil || %s == nil || len(%s) == 0", func(url.Values) error { @@ -358,12 +350,10 @@ func stringMethods(record marlowRecord, fieldName string, fieldConfig url.Values } func numericalMethods(record marlowRecord, fieldName string, fieldConfig url.Values, methods chan<- string) io.Reader { - tableName := record.config.Get(constants.TableNameConfigOption) columnName := fieldConfig.Get(constants.ColumnConfigOption) rangeMethodName := fmt.Sprintf("%sRangeString", columnName) rangeFieldName := fmt.Sprintf("%s%s", fieldName, record.config.Get(constants.BlueprintRangeFieldSuffixConfigOption)) - columnReference := fmt.Sprintf("%s.%s", tableName, columnName) - blueprintName := record.config.Get(constants.BlueprintNameConfigOption) + columnReference := fmt.Sprintf("%s.%s", record.table(), columnName) pr, pw := io.Pipe() @@ -377,7 +367,7 @@ func numericalMethods(record marlowRecord, fieldName string, fieldConfig url.Val writer := writing.NewGoWriter(pw) writer.Comment("[marlow] range clause methods for %s", columnReference) - e := writer.WithMethod(rangeMethodName, blueprintName, nil, returns, func(scope url.Values) error { + e := writer.WithMethod(rangeMethodName, record.blueprint(), nil, returns, func(scope url.Values) error { receiver := scope.Get("receiver") rangeArray := fmt.Sprintf("%s.%s", receiver, rangeFieldName) @@ -408,11 +398,11 @@ func numericalMethods(record marlowRecord, fieldName string, fieldConfig url.Val } // NewBlueprintGenerator returns a reader that will generate the basic query struct type used for record lookups. -func newBlueprintGenerator(record marlowRecord, imports chan<- string) io.Reader { +func newBlueprintGenerator(record marlowRecord) io.Reader { pr, pw := io.Pipe() go func() { - e := writeBlueprint(pw, record, imports) + e := writeBlueprint(pw, record) pw.CloseWithError(e) }() diff --git a/marlow/blueprint_test.go b/marlow/blueprint_test.go index 05ff2bc..3a9f2df 100644 --- a/marlow/blueprint_test.go +++ b/marlow/blueprint_test.go @@ -21,14 +21,14 @@ func Test_Blueprint(t *testing.T) { g.Describe("blueprint generator test suite", func() { - var inputs chan string - var receivedInputs map[string]bool + var imports chan string + var receivedImports map[string]bool var wg *sync.WaitGroup var closed bool g.BeforeEach(func() { - inputs = make(chan string, 10) - receivedInputs = make(map[string]bool) + imports = make(chan string, 10) + receivedImports = make(map[string]bool) wg = &sync.WaitGroup{} closed = false @@ -37,15 +37,16 @@ func Test_Blueprint(t *testing.T) { r = make(url.Values) record = marlowRecord{ - config: r, - fields: f, + config: r, + fields: f, + importChannel: imports, } wg.Add(1) go func() { - for i := range inputs { - receivedInputs[i] = true + for i := range imports { + receivedImports[i] = true } wg.Done() @@ -56,7 +57,7 @@ func Test_Blueprint(t *testing.T) { g.AfterEach(func() { if closed == false { - close(inputs) + close(imports) wg.Wait() } }) @@ -68,17 +69,17 @@ func Test_Blueprint(t *testing.T) { }) g.It("returns an error if a field does not have a type", func() { - reader := newBlueprintGenerator(record, inputs) + reader := newBlueprintGenerator(record) _, e := io.Copy(b, reader) g.Assert(e == nil).Equal(false) }) g.It("did not send any imports over the channel", func() { - reader := newBlueprintGenerator(record, inputs) + reader := newBlueprintGenerator(record) io.Copy(b, reader) - close(inputs) + close(imports) wg.Wait() - g.Assert(len(receivedInputs)).Equal(0) + g.Assert(len(receivedImports)).Equal(0) closed = true }) }) @@ -105,16 +106,16 @@ func Test_Blueprint(t *testing.T) { }) g.It("injected the strings library to the import stream", func() { - io.Copy(b, newBlueprintGenerator(record, inputs)) + io.Copy(b, newBlueprintGenerator(record)) closed = true - close(inputs) + close(imports) wg.Wait() - g.Assert(receivedInputs["strings"]).Equal(true) + g.Assert(receivedImports["strings"]).Equal(true) }) g.It("produced valid a golang struct", func() { fmt.Fprintln(b, "package marlowt") - _, e := io.Copy(b, newBlueprintGenerator(record, inputs)) + _, e := io.Copy(b, newBlueprintGenerator(record)) g.Assert(e).Equal(nil) _, e = parser.ParseFile(token.NewFileSet(), "", b, parser.AllErrors) g.Assert(e).Equal(nil) diff --git a/marlow/createable.go b/marlow/createable.go index b96f451..0b1c759 100644 --- a/marlow/createable.go +++ b/marlow/createable.go @@ -25,13 +25,9 @@ type createableSymbolList struct { } // newCreateableGenerator returns a reader that will generate a record store's creation api. -func newCreateableGenerator(record marlowRecord, imports chan<- string) io.Reader { +func newCreateableGenerator(record marlowRecord) io.Reader { pr, pw := io.Pipe() - - storeName := record.config.Get(constants.StoreNameConfigOption) - recordName := record.config.Get(constants.RecordNameConfigOption) - methodName := fmt.Sprintf("Create%s", inflector.Pluralize(recordName)) - tableName := record.config.Get(constants.TableNameConfigOption) + methodName := fmt.Sprintf("Create%s", inflector.Pluralize(record.name())) symbols := createableSymbolList{ RecordParam: "_records", @@ -49,7 +45,7 @@ func newCreateableGenerator(record marlowRecord, imports chan<- string) io.Reade } params := []writing.FuncParam{ - {Symbol: symbols.RecordParam, Type: fmt.Sprintf("...%s", recordName)}, + {Symbol: symbols.RecordParam, Type: fmt.Sprintf("...%s", record.name())}, } returns := []string{ @@ -62,9 +58,7 @@ func newCreateableGenerator(record marlowRecord, imports chan<- string) io.Reade gosrc.Comment("[marlow] createable") - // gosrc.Println("/* %s %s %s %s", storeName, recordName, methodName, tableName) - - e := gosrc.WithMethod(methodName, storeName, params, returns, func(scope url.Values) error { + e := gosrc.WithMethod(methodName, record.store(), params, returns, func(scope url.Values) error { gosrc.WithIf("len(%s) == 0", func(url.Values) error { gosrc.Println("return 0, nil") return nil @@ -121,7 +115,7 @@ func newCreateableGenerator(record marlowRecord, imports chan<- string) io.Reade gosrc.Println( "fmt.Fprintf(%s, \"INSERT INTO %s ( %s ) VALUES %%s;\", strings.Join(%s, \", \"))\n", symbols.QueryBuffer, - tableName, + record.table(), strings.Join(columnList, ", "), symbols.StatementPlaceholderList, ) @@ -165,13 +159,8 @@ func newCreateableGenerator(record marlowRecord, imports chan<- string) io.Reade return nil }) - gosrc.Println("/* %s %s %s %s", storeName, recordName, methodName, tableName) - gosrc.Println("*/") - if e == nil { - imports <- "fmt" - imports <- "bytes" - imports <- "strings" + record.registerImports("fmt", "bytes", "strings") } pw.CloseWithError(e) diff --git a/marlow/createable_test.go b/marlow/createable_test.go index edb187c..de1fc18 100644 --- a/marlow/createable_test.go +++ b/marlow/createable_test.go @@ -22,11 +22,12 @@ type createableTestScaffold struct { func (s *createableTestScaffold) g() io.Reader { record := marlowRecord{ - fields: s.fields, - config: s.record, + fields: s.fields, + config: s.record, + importChannel: s.imports, } - return newCreateableGenerator(record, s.imports) + return newCreateableGenerator(record) } func Test_Createable(t *testing.T) { diff --git a/marlow/deleteable.go b/marlow/deleteable.go index 29e7027..384df37 100644 --- a/marlow/deleteable.go +++ b/marlow/deleteable.go @@ -19,13 +19,9 @@ type deleteableSymbols struct { } // newDeleteableGenerator is responsible for creating a generator that will write out the Delete api methods. -func newDeleteableGenerator(record marlowRecord, imports chan<- string) io.Reader { +func newDeleteableGenerator(record marlowRecord) io.Reader { pr, pw := io.Pipe() - - storeName := record.config.Get(constants.StoreNameConfigOption) - recordName := record.config.Get(constants.RecordNameConfigOption) - methodName := fmt.Sprintf("Delete%s", inflector.Pluralize(recordName)) - tableName := record.config.Get(constants.TableNameConfigOption) + methodName := fmt.Sprintf("Delete%s", inflector.Pluralize(record.name())) symbols := deleteableSymbols{ Error: "_e", @@ -38,10 +34,8 @@ func newDeleteableGenerator(record marlowRecord, imports chan<- string) io.Reade ExecError: "_ee", } - blueprintName := record.config.Get(constants.BlueprintNameConfigOption) - params := []writing.FuncParam{ - {Type: fmt.Sprintf("*%s", blueprintName), Symbol: symbols.BlueprintParam}, + {Type: fmt.Sprintf("*%s", record.blueprint()), Symbol: symbols.BlueprintParam}, } returns := []string{ @@ -54,13 +48,13 @@ func newDeleteableGenerator(record marlowRecord, imports chan<- string) io.Reade gosrc.Comment("[marlow] deleteable") - e := gosrc.WithMethod(methodName, storeName, params, returns, func(scope url.Values) error { + e := gosrc.WithMethod(methodName, record.store(), params, returns, func(scope url.Values) error { gosrc.WithIf("%s == nil || %s.String() == \"\"", func(url.Values) error { gosrc.Println("return -1, fmt.Errorf(\"%s\")", constants.InvalidDeletionBlueprint) return nil }, symbols.BlueprintParam, symbols.BlueprintParam) - deleteString := fmt.Sprintf("DELETE FROM %s", tableName) + deleteString := fmt.Sprintf("DELETE FROM %s", record.table()) gosrc.Println( "%s := fmt.Sprintf(\"%s %%s\", %s)", @@ -110,7 +104,7 @@ func newDeleteableGenerator(record marlowRecord, imports chan<- string) io.Reade }) if e == nil { - imports <- "fmt" + record.registerImports("fmt") } pw.CloseWithError(e) diff --git a/marlow/deleteable_test.go b/marlow/deleteable_test.go index e579260..eafa121 100644 --- a/marlow/deleteable_test.go +++ b/marlow/deleteable_test.go @@ -22,10 +22,11 @@ type deleteableTestScaffold struct { func (s *deleteableTestScaffold) g() io.Reader { record := marlowRecord{ - config: s.record, - fields: s.fields, + config: s.record, + fields: s.fields, + importChannel: s.imports, } - return newDeleteableGenerator(record, s.imports) + return newDeleteableGenerator(record) } func Test_Deleteable(t *testing.T) { diff --git a/marlow/queryable.go b/marlow/queryable.go index 0c6842a..c96e915 100644 --- a/marlow/queryable.go +++ b/marlow/queryable.go @@ -23,13 +23,10 @@ type finderSymbols struct { offset string } -func finder(record marlowRecord, imports chan<- string) io.Reader { - table := record.config.Get(constants.TableNameConfigOption) - recordName := record.config.Get(constants.RecordNameConfigOption) - store := record.config.Get(constants.StoreNameConfigOption) +func finder(record marlowRecord) io.Reader { methodName := fmt.Sprintf("%s%s", record.config.Get(constants.StoreFindMethodPrefixConfigOption), - inflector.Pluralize(recordName), + inflector.Pluralize(record.name()), ) pr, pw := io.Pipe() @@ -51,12 +48,12 @@ func finder(record marlowRecord, imports chan<- string) io.Reader { queryError: "_qe", limit: "_limit", offset: "_offset", - recordSlice: fmt.Sprintf("[]*%s", recordName), + recordSlice: fmt.Sprintf("[]*%s", record.name()), } go func() { gosrc := writing.NewGoWriter(pw) - gosrc.Comment("[marlow feature]: finder on table[%s]", table) + gosrc.Comment("[marlow feature]: finder on table[%s]", record.table()) params := []writing.FuncParam{ {Symbol: symbols.blueprint, Type: fmt.Sprintf("*%s", blueprintName)}, @@ -73,20 +70,20 @@ func finder(record marlowRecord, imports chan<- string) io.Reader { colName = strings.ToLower(name) } - expanded := fmt.Sprintf("%s.%s", table, colName) + expanded := fmt.Sprintf("%s.%s", record.table(), colName) fieldList = append(fieldList, expanded) } defaultLimit := record.config.Get(constants.DefaultLimitConfigOption) if defaultLimit == "" { - pw.CloseWithError(fmt.Errorf("invalid defaultLimit for record %s", recordName)) + pw.CloseWithError(fmt.Errorf("invalid defaultLimit for record %s", record.name())) return } sort.Strings(fieldList) - e := gosrc.WithMethod(methodName, store, params, returns, func(scope url.Values) error { + e := gosrc.WithMethod(methodName, record.store(), params, returns, func(scope url.Values) error { // Prepare the array that will be returned. gosrc.Println("%s := make(%s, 0)\n", symbols.results, symbols.recordSlice) defer gosrc.Println("return %s, nil", symbols.results) @@ -96,7 +93,7 @@ func finder(record marlowRecord, imports chan<- string) io.Reader { "%s := bytes.NewBufferString(\"SELECT %s FROM %s\")", symbols.queryString, strings.Join(fieldList, ","), - table, + record.table(), ) // Write our where clauses @@ -172,7 +169,7 @@ func finder(record marlowRecord, imports chan<- string) io.Reader { }, symbols.queryError) return gosrc.WithIter("%s.Next()", func(url.Values) error { - gosrc.Println("var %s %s", symbols.rowItem, recordName) + gosrc.Println("var %s %s", symbols.rowItem, record.name()) references := make([]string, 0, len(record.fields)) for name := range record.fields { @@ -199,9 +196,7 @@ func finder(record marlowRecord, imports chan<- string) io.Reader { return } - imports <- "fmt" - imports <- "bytes" - imports <- "strings" + record.registerImports("fmt", "bytes", "strings") pw.Close() }() @@ -221,11 +216,7 @@ type counterSymbols struct { ScanError string } -func counter(record marlowRecord, imports chan<- string) io.Reader { - table := record.config.Get(constants.TableNameConfigOption) - recordName := record.config.Get(constants.RecordNameConfigOption) - store := record.config.Get(constants.StoreNameConfigOption) - blueprintName := record.config.Get(constants.BlueprintNameConfigOption) +func counter(record marlowRecord) io.Reader { pr, pw := io.Pipe() methodPrefix := record.config.Get(constants.StoreCountMethodPrefixConfigOption) @@ -235,7 +226,7 @@ func counter(record marlowRecord, imports chan<- string) io.Reader { } symbols := counterSymbols{ - CountMethodName: fmt.Sprintf("%s%s", methodPrefix, inflector.Pluralize(recordName)), + CountMethodName: fmt.Sprintf("%s%s", methodPrefix, inflector.Pluralize(record.name())), BlueprintParamName: "_blueprint", StatementQuery: "_raw", StatementError: "_statementError", @@ -248,10 +239,10 @@ func counter(record marlowRecord, imports chan<- string) io.Reader { go func() { gosrc := writing.NewGoWriter(pw) - gosrc.Comment("[marlow feature]: counter on table[%s]", table) + gosrc.Comment("[marlow feature]: counter on table[%s]", record.table()) params := []writing.FuncParam{ - {Symbol: symbols.BlueprintParamName, Type: fmt.Sprintf("*%s", blueprintName)}, + {Symbol: symbols.BlueprintParamName, Type: fmt.Sprintf("*%s", record.blueprint())}, } returns := []string{ @@ -259,17 +250,17 @@ func counter(record marlowRecord, imports chan<- string) io.Reader { "error", } - e := gosrc.WithMethod(symbols.CountMethodName, store, params, returns, func(scope url.Values) error { + e := gosrc.WithMethod(symbols.CountMethodName, record.store(), params, returns, func(scope url.Values) error { receiver := scope.Get("receiver") gosrc.WithIf("%s == nil", func(url.Values) error { - gosrc.Println("%s = &%s{}", params[0].Symbol, blueprintName) + gosrc.Println("%s = &%s{}", params[0].Symbol, record.blueprint()) return nil }, symbols.BlueprintParamName) gosrc.Println( "%s := fmt.Sprintf(\"SELECT COUNT(*) FROM %s %%s;\", %s)", symbols.StatementQuery, - table, + record.table(), symbols.BlueprintParamName, ) @@ -321,7 +312,7 @@ func counter(record marlowRecord, imports chan<- string) io.Reader { }) if e == nil { - imports <- "fmt" + record.registerImports("fmt") } pw.CloseWithError(e) @@ -342,14 +333,10 @@ type selectorSymbols struct { ScanError string } -func selector(record marlowRecord, fieldName string, fieldConfig url.Values, imports chan<- string) io.Reader { +func selector(record marlowRecord, fieldName string, fieldConfig url.Values) io.Reader { pr, pw := io.Pipe() - blueprintName := record.config.Get(constants.BlueprintNameConfigOption) methodName := fmt.Sprintf("Select%s", inflector.Pluralize(fieldName)) - - tableName := record.config.Get(constants.TableNameConfigOption) columnName := fieldConfig.Get(constants.ColumnConfigOption) - storeName := record.config.Get(constants.StoreNameConfigOption) returnItemType := fieldConfig.Get("type") returnArrayType := fmt.Sprintf("[]%s", returnItemType) @@ -372,24 +359,24 @@ func selector(record marlowRecord, fieldName string, fieldConfig url.Values, imp } params := []writing.FuncParam{ - {Type: fmt.Sprintf("*%s", blueprintName), Symbol: symbols.BlueprintParam}, + {Type: fmt.Sprintf("*%s", record.blueprint()), Symbol: symbols.BlueprintParam}, } - columnReference := fmt.Sprintf("%s.%s", tableName, columnName) + columnReference := fmt.Sprintf("%s.%s", record.table(), columnName) go func() { gosrc := writing.NewGoWriter(pw) - gosrc.Comment("[marlow] field selector for %s (%s) [print: %s]", fieldName, methodName, blueprintName) + gosrc.Comment("[marlow] field selector for %s (%s) [print: %s]", fieldName, methodName, record.blueprint()) - e := gosrc.WithMethod(methodName, storeName, params, returns, func(scope url.Values) error { + e := gosrc.WithMethod(methodName, record.store(), params, returns, func(scope url.Values) error { gosrc.Println("%s := make(%s, 0)", symbols.ReturnSlice, returnArrayType) gosrc.Println( "%s := bytes.NewBufferString(\"SELECT %s FROM %s\")", symbols.QueryString, columnReference, - tableName, + record.table(), ) // Write our where clauses @@ -465,25 +452,21 @@ func selector(record marlowRecord, fieldName string, fieldConfig url.Values, imp } // newQueryableGenerator is responsible for returning a reader that will generate lookup functions for a given record. -func newQueryableGenerator(record marlowRecord, imports chan<- string) io.Reader { +func newQueryableGenerator(record marlowRecord) io.Reader { pr, pw := io.Pipe() - table := record.config.Get(constants.TableNameConfigOption) - recordName := record.config.Get(constants.RecordNameConfigOption) - store := record.config.Get(constants.StoreNameConfigOption) - - if len(table) == 0 || len(recordName) == 0 || len(store) == 0 { + if len(record.table()) == 0 || len(record.name()) == 0 || len(record.store()) == 0 { pw.CloseWithError(fmt.Errorf("invalid record config")) return pr } features := []io.Reader{ - finder(record, imports), - counter(record, imports), + finder(record), + counter(record), } for name, config := range record.fields { - s := selector(record, name, config, imports) + s := selector(record, name, config) features = append(features, s) } diff --git a/marlow/queryable_test.go b/marlow/queryable_test.go index 8369120..f954722 100644 --- a/marlow/queryable_test.go +++ b/marlow/queryable_test.go @@ -25,10 +25,11 @@ type queryableTestScaffold struct { func (s *queryableTestScaffold) g() io.Reader { record := marlowRecord{ - config: s.record, - fields: s.fields, + config: s.record, + fields: s.fields, + importChannel: s.imports, } - return newQueryableGenerator(record, s.imports) + return newQueryableGenerator(record) } func (s *queryableTestScaffold) parsed() (*ast.File, error) { diff --git a/marlow/record.go b/marlow/record.go index 749ff1e..32f80f6 100644 --- a/marlow/record.go +++ b/marlow/record.go @@ -1,12 +1,48 @@ package marlow -import "go/ast" import "net/url" +import "github.com/dadleyy/marlow/marlow/constants" // marlowRecord structs represent both the field-level and record-level configuration options for gerating the marlow stores. type marlowRecord struct { - config url.Values - fields map[string]url.Values - imports chan<- string - store chan<- *ast.FuncDecl + config url.Values + fields map[string]url.Values + importChannel chan<- string + importRegistry map[string]bool +} + +func (r *marlowRecord) registerImports(imports ...string) { + registry := r.importRegistry + + if registry == nil { + r.importRegistry = make(map[string]bool) + registry = r.importRegistry + } + + for _, name := range imports { + _, dupe := registry[name] + + if dupe { + continue + } + + r.importChannel <- name + registry[name] = true + } +} + +func (r *marlowRecord) name() string { + return r.config.Get(constants.RecordNameConfigOption) +} + +func (r *marlowRecord) store() string { + return r.config.Get(constants.StoreNameConfigOption) +} + +func (r *marlowRecord) table() string { + return r.config.Get(constants.TableNameConfigOption) +} + +func (r *marlowRecord) blueprint() string { + return r.config.Get(constants.BlueprintNameConfigOption) } diff --git a/marlow/record_exhaust.go b/marlow/record_exhaust.go deleted file mode 100644 index 386e04a..0000000 --- a/marlow/record_exhaust.go +++ /dev/null @@ -1,29 +0,0 @@ -package marlow - -import "go/ast" - -type recordExhaust struct { - imports chan<- string - methods chan<- *ast.FuncDecl - registeredImports map[string]bool -} - -func (e *recordExhaust) RegisterStoreMethods(imports ...*ast.FuncDecl) { -} - -func (e *recordExhaust) RegisterImports(imports ...string) { - if e.registeredImports == nil { - e.registeredImports = make(map[string]bool) - } - - for _, i := range imports { - _, dupe := e.registeredImports[i] - - if dupe { - continue - } - - e.registeredImports[i] = true - e.imports <- i - } -} diff --git a/marlow/record_reader.go b/marlow/record_reader.go index 8e16d7d..8e4b00b 100644 --- a/marlow/record_reader.go +++ b/marlow/record_reader.go @@ -145,23 +145,24 @@ func newRecordReader(root ast.Decl, imports chan<- string) (io.Reader, bool) { go func() { record := marlowRecord{ - config: recordConfig, - fields: recordFields, + config: recordConfig, + fields: recordFields, + importChannel: imports, } - e := readRecord(pw, record, imports) + e := readRecord(pw, record) pw.CloseWithError(e) }() return pr, true } -func readRecord(writer io.Writer, record marlowRecord, imports chan<- string) error { +func readRecord(writer io.Writer, record marlowRecord) error { buffer := new(bytes.Buffer) readers := make([]io.Reader, 0, 4) - features := map[string]func(marlowRecord, chan<- string) io.Reader{ + features := map[string]func(marlowRecord) io.Reader{ constants.CreateableConfigOption: newCreateableGenerator, constants.UpdateableConfigOption: newUpdateableGenerator, constants.DeleteableConfigOption: newDeleteableGenerator, @@ -175,7 +176,7 @@ func readRecord(writer io.Writer, record marlowRecord, imports chan<- string) er continue } - g := generator(record, imports) + g := generator(record) readers = append(readers, g) } @@ -189,11 +190,7 @@ func readRecord(writer io.Writer, record marlowRecord, imports chan<- string) er } // If we had any features enabled, we need to also generate the blue print API. - readers = append( - readers, - newStoreGenerator(record.config, imports), - newBlueprintGenerator(record, imports), - ) + readers = append(readers, newBlueprintGenerator(record), newStoreGenerator(record)) // Iterate over all our collected features, copying them into the buffer if _, e := io.Copy(buffer, io.MultiReader(readers...)); e != nil { diff --git a/marlow/record_reader_test.go b/marlow/record_reader_test.go index 9bac966..1673d77 100644 --- a/marlow/record_reader_test.go +++ b/marlow/record_reader_test.go @@ -83,10 +83,10 @@ func Test_RecordReader(t *testing.T) { g.It("with a valid source struct including explicit column names", func() { scaffold.source = strings.NewReader(` - package marlowt - type Author struct { - Title string ` + "`marlow:\"column=title\"`" + ` - }`) + package marlowt + type Author struct { + Title string ` + "`marlow:\"column=title\"`" + ` + }`) reader, ok := newRecordReader(scaffold.root(), scaffold.imports) g.Assert(ok).Equal(true) _, e := io.Copy(scaffold.output, reader) @@ -96,11 +96,11 @@ func Test_RecordReader(t *testing.T) { g.It("with a valid source struct including explicit table name from field", func() { scaffold.source = strings.NewReader(` - package marlowt - type Author struct { - table string ` + "`marlow:\"tableName=authors\"`" + ` - Title string - }`) + package marlowt + type Author struct { + table string ` + "`marlow:\"tableName=authors\"`" + ` + Title string + }`) reader, ok := newRecordReader(scaffold.root(), scaffold.imports) g.Assert(ok).Equal(true) _, e := io.Copy(scaffold.output, reader) @@ -110,11 +110,11 @@ func Test_RecordReader(t *testing.T) { g.It("with a valid source struct with empty marlow field column config", func() { scaffold.source = strings.NewReader(` - package marlowt - type Author struct { - Title string - IgnoredColumn string ` + "`marlow:\"\"`" + ` - }`) + package marlowt + type Author struct { + Title string + IgnoredColumn string ` + "`marlow:\"\"`" + ` + }`) reader, ok := newRecordReader(scaffold.root(), scaffold.imports) g.Assert(ok).Equal(true) _, e := io.Copy(scaffold.output, reader) @@ -124,11 +124,11 @@ func Test_RecordReader(t *testing.T) { g.It("with a valid source struct with explicit exclusions of certain columns", func() { scaffold.source = strings.NewReader(` - package marlowt - type Author struct { - Title string - IgnoredColumn string ` + "`marlow:\"column=-\"`" + ` - }`) + package marlowt + type Author struct { + Title string + IgnoredColumn string ` + "`marlow:\"column=-\"`" + ` + }`) reader, ok := newRecordReader(scaffold.root(), scaffold.imports) g.Assert(ok).Equal(true) _, e := io.Copy(scaffold.output, reader) @@ -138,12 +138,12 @@ func Test_RecordReader(t *testing.T) { g.It("errors during copy if duplicate column names", func() { scaffold.source = strings.NewReader(` - package marlowt - type Author struct { - Title string - IgnoredColumn string ` + "`marlow:\"column=dupe\"`" + ` - OtherColumn string ` + "`marlow:\"column=dupe\"`" + ` - }`) + package marlowt + type Author struct { + Title string + IgnoredColumn string ` + "`marlow:\"column=dupe\"`" + ` + OtherColumn string ` + "`marlow:\"column=dupe\"`" + ` + }`) reader, ok := newRecordReader(scaffold.root(), scaffold.imports) g.Assert(ok).Equal(true) _, e := io.Copy(scaffold.output, reader) @@ -153,12 +153,12 @@ func Test_RecordReader(t *testing.T) { g.It("errors during copy if slice field type", func() { scaffold.source = strings.NewReader(` - package marlowt - type Author struct { - Title string - MiddleName string - SliceColumn []string ` + "`marlow:\"column=dupe\"`" + ` - }`) + package marlowt + type Author struct { + Title string + MiddleName string + SliceColumn []string ` + "`marlow:\"column=dupe\"`" + ` + }`) reader, ok := newRecordReader(scaffold.root(), scaffold.imports) g.Assert(ok).Equal(true) _, e := io.Copy(scaffold.output, reader) @@ -168,13 +168,13 @@ func Test_RecordReader(t *testing.T) { g.It("errors during copy if duplicate column names (with other valid fields)", func() { scaffold.source = strings.NewReader(` - package marlowt - type Author struct { - Title string - MiddleName string - IgnoredColumn string ` + "`marlow:\"column=dupe\"`" + ` - OtherColumn string ` + "`marlow:\"column=dupe\"`" + ` - }`) + package marlowt + type Author struct { + Title string + MiddleName string + IgnoredColumn string ` + "`marlow:\"column=dupe\"`" + ` + OtherColumn string ` + "`marlow:\"column=dupe\"`" + ` + }`) reader, ok := newRecordReader(scaffold.root(), scaffold.imports) g.Assert(ok).Equal(true) _, e := io.Copy(scaffold.output, reader) diff --git a/marlow/record_test.go b/marlow/record_test.go new file mode 100644 index 0000000..d545706 --- /dev/null +++ b/marlow/record_test.go @@ -0,0 +1,46 @@ +package marlow + +import "sync" +import "testing" +import "github.com/franela/goblin" + +func Test_Record(t *testing.T) { + g := goblin.Goblin(t) + + g.Describe("marlowRecord test suite", func() { + var record *marlowRecord + var imports chan string + var received map[string]int + var wg *sync.WaitGroup + + g.BeforeEach(func() { + imports = make(chan string) + record = &marlowRecord{importChannel: imports} + received = make(map[string]int) + wg = &sync.WaitGroup{} + + wg.Add(1) + + go func() { + for i := range imports { + _, dupe := received[i] + + if !dupe { + received[i] = 1 + continue + } + + received[i]++ + } + wg.Done() + }() + }) + + g.It("only registers received imports once", func() { + record.registerImports("fmt", "fmt") + close(imports) + wg.Wait() + g.Assert(received["fmt"]).Equal(1) + }) + }) +} diff --git a/marlow/store.go b/marlow/store.go index ec8eaad..a419ae7 100644 --- a/marlow/store.go +++ b/marlow/store.go @@ -3,13 +3,11 @@ package marlow import "io" import "net/url" import "github.com/dadleyy/marlow/marlow/writing" -import "github.com/dadleyy/marlow/marlow/constants" -func writeStore(destination io.Writer, record url.Values, imports chan<- string) error { +func writeStore(destination io.Writer, record marlowRecord) error { out := writing.NewGoWriter(destination) - storeName := record.Get(constants.StoreNameConfigOption) - e := out.WithStruct(storeName, func(url.Values) error { + e := out.WithStruct(record.store(), func(url.Values) error { out.Println("*sql.DB") return nil }) @@ -18,16 +16,16 @@ func writeStore(destination io.Writer, record url.Values, imports chan<- string) return e } - imports <- "database/sql" + record.registerImports("database/sql") return nil } // newStoreGenerator returns a reader that will generate the centralized record store for a given record. -func newStoreGenerator(record url.Values, imports chan<- string) io.Reader { +func newStoreGenerator(record marlowRecord) io.Reader { pr, pw := io.Pipe() go func() { - e := writeStore(pw, record, imports) + e := writeStore(pw, record) pw.CloseWithError(e) }() diff --git a/marlow/store_test.go b/marlow/store_test.go index aa00ad5..a8e63b0 100644 --- a/marlow/store_test.go +++ b/marlow/store_test.go @@ -21,7 +21,12 @@ type storeTestScaffold struct { } func (s *storeTestScaffold) g() io.Reader { - return newStoreGenerator(s.record, s.imports) + record := marlowRecord{ + importChannel: s.imports, + config: s.record, + } + + return newStoreGenerator(record) } func (s *storeTestScaffold) parsed() (*ast.File, error) { diff --git a/marlow/updateable.go b/marlow/updateable.go index e9cdf1e..de9119e 100644 --- a/marlow/updateable.go +++ b/marlow/updateable.go @@ -19,20 +19,15 @@ type updaterSymbols struct { valueSlice string } -func updater(record marlowRecord, fieldName string, fieldConfig url.Values, imports chan<- string) io.Reader { +func updater(record marlowRecord, fieldName string, fieldConfig url.Values) io.Reader { pr, pw := io.Pipe() - recordName := record.config.Get(constants.RecordNameConfigOption) - methodName := fmt.Sprintf( "%s%s%s", record.config.Get(constants.UpdateFieldMethodPrefixConfigOption), - recordName, + record.name(), fieldName, ) - - tableName := record.config.Get(constants.TableNameConfigOption) columnName := fieldConfig.Get(constants.ColumnConfigOption) - storeName := record.config.Get(constants.StoreNameConfigOption) symbols := updaterSymbols{ valueParam: "_updates", @@ -66,11 +61,11 @@ func updater(record marlowRecord, fieldName string, fieldConfig url.Values, impo gosrc := writing.NewGoWriter(pw) gosrc.Comment("[marlow] updater method for %s", fieldName) - e := gosrc.WithMethod(methodName, storeName, params, returns, func(scope url.Values) error { + e := gosrc.WithMethod(methodName, record.store(), params, returns, func(scope url.Values) error { gosrc.Println( "%s := bytes.NewBufferString(\"UPDATE %s set %s = ?\")", symbols.queryString, - tableName, + record.table(), columnName, ) @@ -136,8 +131,7 @@ func updater(record marlowRecord, fieldName string, fieldConfig url.Values, impo }) if e == nil { - imports <- "fmt" - imports <- "bytes" + record.registerImports("fmt", "bytes") } pw.CloseWithError(e) @@ -147,11 +141,11 @@ func updater(record marlowRecord, fieldName string, fieldConfig url.Values, impo } // newUpdateableGenerator is responsible for generating updating store methods. -func newUpdateableGenerator(record marlowRecord, imports chan<- string) io.Reader { +func newUpdateableGenerator(record marlowRecord) io.Reader { readers := make([]io.Reader, 0, len(record.fields)) for name, config := range record.fields { - u := updater(record, name, config, imports) + u := updater(record, name, config) readers = append(readers, u) } diff --git a/marlow/updateable_test.go b/marlow/updateable_test.go index e75ad1d..bcaf4f4 100644 --- a/marlow/updateable_test.go +++ b/marlow/updateable_test.go @@ -20,10 +20,11 @@ type updateableTestScaffold struct { func (s *updateableTestScaffold) g() io.Reader { record := marlowRecord{ - config: s.record, - fields: s.fields, + config: s.record, + fields: s.fields, + importChannel: s.imports, } - return newUpdateableGenerator(record, s.imports) + return newUpdateableGenerator(record) } func Test_Updateable(t *testing.T) { From ff3a142c8b1ad3fbbec98a10936d97e5494ca3b5 Mon Sep 17 00:00:00 2001 From: Danny Hadley Date: Sat, 11 Nov 2017 22:30:54 -0500 Subject: [PATCH 6/7] finishing stores-as-interface refactor [GH-59] --- examples/library/main.go | 4 +-- examples/library/models/author_test.go | 4 +-- examples/library/models/book_test.go | 4 +-- marlow/createable.go | 5 +++ marlow/createable_test.go | 43 ++++++++++++++++------- marlow/deleteable.go | 5 +++ marlow/deleteable_test.go | 13 ++++++- marlow/queryable.go | 21 +++++++++-- marlow/queryable_test.go | 37 ++++++++++++++------ marlow/record.go | 25 ++++++++++++-- marlow/record_reader.go | 26 ++++++++++++-- marlow/store.go | 48 +++++++++++++++++++++++--- marlow/store_test.go | 5 ++- marlow/updateable.go | 5 +++ marlow/updateable_test.go | 28 ++++++++++++--- marlow/writing/go_writer.go | 8 +++++ marlow/writing/writer.go | 10 +++++- 17 files changed, 242 insertions(+), 49 deletions(-) diff --git a/examples/library/main.go b/examples/library/main.go index b6ee0d1..75edbc0 100644 --- a/examples/library/main.go +++ b/examples/library/main.go @@ -119,7 +119,7 @@ func main() { NameLike: []string{"danny"}, }) - authorStore := models.AuthorStore{DB: db} + authorStore := models.NewAuthorStore(db) a, e := authorStore.FindAuthors(&models.AuthorBlueprint{ ID: []int{1, 2, 3}, @@ -133,7 +133,7 @@ func main() { log.Printf("found author name[%s]", author.Name) } - bookStore := models.BookStore{DB: db} + bookStore := models.NewBookStore(db) b, e := bookStore.FindBooks(&models.BookBlueprint{ ID: []int{1, 2}, }) diff --git a/examples/library/models/author_test.go b/examples/library/models/author_test.go index d2f031e..adc8bd1 100644 --- a/examples/library/models/author_test.go +++ b/examples/library/models/author_test.go @@ -36,7 +36,7 @@ func addAuthorRow(db *sql.DB, values ...[]string) error { func Test_Author(t *testing.T) { g := goblin.Goblin(t) var db *sql.DB - var store *AuthorStore + var store AuthorStore dbFile := "author-testing.db" generatedAuthorCount := 150 @@ -120,7 +120,7 @@ func Test_Author(t *testing.T) { }) g.BeforeEach(func() { - store = &AuthorStore{DB: db} + store = NewAuthorStore(db) }) g.After(func() { diff --git a/examples/library/models/book_test.go b/examples/library/models/book_test.go index f7f1b1a..3bc6766 100644 --- a/examples/library/models/book_test.go +++ b/examples/library/models/book_test.go @@ -34,7 +34,7 @@ func addBookRow(db *sql.DB, values ...[]string) error { func Test_Book(t *testing.T) { var db *sql.DB - var store *BookStore + var store BookStore g := goblin.Goblin(t) testBookCount := 150 @@ -67,7 +67,7 @@ func Test_Book(t *testing.T) { }) g.BeforeEach(func() { - store = &BookStore{DB: db} + store = NewBookStore(db) }) g.After(func() { diff --git a/marlow/createable.go b/marlow/createable.go index 0b1c759..867c1a5 100644 --- a/marlow/createable.go +++ b/marlow/createable.go @@ -161,6 +161,11 @@ func newCreateableGenerator(record marlowRecord) io.Reader { if e == nil { record.registerImports("fmt", "bytes", "strings") + record.registerStoreMethod(writing.FuncDecl{ + Name: methodName, + Params: params, + Returns: returns, + }) } pw.CloseWithError(e) diff --git a/marlow/createable_test.go b/marlow/createable_test.go index de1fc18..f9b5b9f 100644 --- a/marlow/createable_test.go +++ b/marlow/createable_test.go @@ -6,25 +6,40 @@ import "bytes" import "testing" import "net/url" import "github.com/franela/goblin" +import "github.com/dadleyy/marlow/marlow/writing" import "github.com/dadleyy/marlow/marlow/constants" type createableTestScaffold struct { buffer *bytes.Buffer imports chan string - record url.Values - fields map[string]url.Values + methods chan writing.FuncDecl + + record url.Values + fields map[string]url.Values received map[string]bool closed bool wg *sync.WaitGroup } +func (s *createableTestScaffold) close() { + if s == nil || s.closed { + return + } + + s.closed = true + close(s.imports) + close(s.methods) + s.wg.Wait() +} + func (s *createableTestScaffold) g() io.Reader { record := marlowRecord{ fields: s.fields, config: s.record, importChannel: s.imports, + storeChannel: s.methods, } return newCreateableGenerator(record) @@ -39,16 +54,25 @@ func Test_Createable(t *testing.T) { g.BeforeEach(func() { scaffold = &createableTestScaffold{ - buffer: new(bytes.Buffer), - wg: &sync.WaitGroup{}, - imports: make(chan string), + buffer: new(bytes.Buffer), + wg: &sync.WaitGroup{}, + + imports: make(chan string), + methods: make(chan writing.FuncDecl), + record: make(url.Values), fields: make(map[string]url.Values), received: make(map[string]bool), closed: false, } - scaffold.wg.Add(1) + scaffold.wg.Add(2) + + go func() { + for range scaffold.methods { + } + scaffold.wg.Done() + }() go func() { for i := range scaffold.imports { @@ -59,12 +83,7 @@ func Test_Createable(t *testing.T) { }) g.AfterEach(func() { - if scaffold.closed != false { - return - } - - close(scaffold.imports) - scaffold.wg.Wait() + scaffold.close() }) g.Describe("with a valid record config", func() { diff --git a/marlow/deleteable.go b/marlow/deleteable.go index 384df37..c8f6bdd 100644 --- a/marlow/deleteable.go +++ b/marlow/deleteable.go @@ -105,6 +105,11 @@ func newDeleteableGenerator(record marlowRecord) io.Reader { if e == nil { record.registerImports("fmt") + record.registerStoreMethod(writing.FuncDecl{ + Name: methodName, + Returns: returns, + Params: params, + }) } pw.CloseWithError(e) diff --git a/marlow/deleteable_test.go b/marlow/deleteable_test.go index eafa121..84f96f8 100644 --- a/marlow/deleteable_test.go +++ b/marlow/deleteable_test.go @@ -6,12 +6,14 @@ import "bytes" import "net/url" import "testing" import "github.com/franela/goblin" +import "github.com/dadleyy/marlow/marlow/writing" import "github.com/dadleyy/marlow/marlow/constants" type deleteableTestScaffold struct { buffer *bytes.Buffer imports chan string + methods chan writing.FuncDecl record url.Values fields map[string]url.Values @@ -25,6 +27,7 @@ func (s *deleteableTestScaffold) g() io.Reader { config: s.record, fields: s.fields, importChannel: s.imports, + storeChannel: s.methods, } return newDeleteableGenerator(record) } @@ -40,6 +43,7 @@ func Test_Deleteable(t *testing.T) { scaffold = &deleteableTestScaffold{ buffer: new(bytes.Buffer), imports: make(chan string), + methods: make(chan writing.FuncDecl), record: make(url.Values), fields: make(map[string]url.Values), received: make(map[string]bool), @@ -47,7 +51,7 @@ func Test_Deleteable(t *testing.T) { wg: &sync.WaitGroup{}, } - scaffold.wg.Add(1) + scaffold.wg.Add(2) go func() { for i := range scaffold.imports { @@ -55,11 +59,18 @@ func Test_Deleteable(t *testing.T) { } scaffold.wg.Done() }() + + go func() { + for range scaffold.methods { + } + scaffold.wg.Done() + }() }) g.AfterEach(func() { if scaffold.closed == false { close(scaffold.imports) + close(scaffold.methods) scaffold.wg.Wait() } }) diff --git a/marlow/queryable.go b/marlow/queryable.go index c96e915..e8cd642 100644 --- a/marlow/queryable.go +++ b/marlow/queryable.go @@ -196,6 +196,11 @@ func finder(record marlowRecord) io.Reader { return } + record.registerStoreMethod(writing.FuncDecl{ + Name: methodName, + Params: params, + Returns: returns, + }) record.registerImports("fmt", "bytes", "strings") pw.Close() @@ -205,7 +210,7 @@ func finder(record marlowRecord) io.Reader { } type counterSymbols struct { - CountMethodName string + countMethodName string BlueprintParamName string StatementQuery string StatementResult string @@ -226,7 +231,7 @@ func counter(record marlowRecord) io.Reader { } symbols := counterSymbols{ - CountMethodName: fmt.Sprintf("%s%s", methodPrefix, inflector.Pluralize(record.name())), + countMethodName: fmt.Sprintf("%s%s", methodPrefix, inflector.Pluralize(record.name())), BlueprintParamName: "_blueprint", StatementQuery: "_raw", StatementError: "_statementError", @@ -250,7 +255,7 @@ func counter(record marlowRecord) io.Reader { "error", } - e := gosrc.WithMethod(symbols.CountMethodName, record.store(), params, returns, func(scope url.Values) error { + e := gosrc.WithMethod(symbols.countMethodName, record.store(), params, returns, func(scope url.Values) error { receiver := scope.Get("receiver") gosrc.WithIf("%s == nil", func(url.Values) error { gosrc.Println("%s = &%s{}", params[0].Symbol, record.blueprint()) @@ -313,6 +318,11 @@ func counter(record marlowRecord) io.Reader { if e == nil { record.registerImports("fmt") + record.registerStoreMethod(writing.FuncDecl{ + Name: symbols.countMethodName, + Params: params, + Returns: returns, + }) } pw.CloseWithError(e) @@ -441,6 +451,11 @@ func selector(record marlowRecord, fieldName string, fieldConfig url.Values) io. return e } + record.registerStoreMethod(writing.FuncDecl{ + Name: methodName, + Params: params, + Returns: returns, + }) gosrc.Println("return %s, nil", symbols.ReturnSlice) return nil }) diff --git a/marlow/queryable_test.go b/marlow/queryable_test.go index f954722..58d3790 100644 --- a/marlow/queryable_test.go +++ b/marlow/queryable_test.go @@ -11,10 +11,13 @@ import "go/token" import "go/parser" import "github.com/franela/goblin" +import "github.com/dadleyy/marlow/marlow/writing" + type queryableTestScaffold struct { output *bytes.Buffer imports chan string + methods chan writing.FuncDecl record url.Values fields map[string]url.Values @@ -28,10 +31,22 @@ func (s *queryableTestScaffold) g() io.Reader { config: s.record, fields: s.fields, importChannel: s.imports, + storeChannel: s.methods, } return newQueryableGenerator(record) } +func (s *queryableTestScaffold) close() { + if s == nil || s.closed { + return + } + + s.closed = true + close(s.methods) + close(s.imports) + s.wg.Wait() +} + func (s *queryableTestScaffold) parsed() (*ast.File, error) { return parser.ParseFile(token.NewFileSet(), "", s.output, parser.AllErrors) } @@ -47,6 +62,7 @@ func Test_QueryableGenerator(t *testing.T) { scaffold = &queryableTestScaffold{ output: new(bytes.Buffer), imports: make(chan string), + methods: make(chan writing.FuncDecl), record: make(url.Values), fields: make(map[string]url.Values), received: make(map[string]bool), @@ -54,7 +70,13 @@ func Test_QueryableGenerator(t *testing.T) { wg: &sync.WaitGroup{}, } - scaffold.wg.Add(1) + scaffold.wg.Add(2) + + go func() { + for range scaffold.methods { + } + scaffold.wg.Done() + }() go func() { for i := range scaffold.imports { @@ -65,10 +87,7 @@ func Test_QueryableGenerator(t *testing.T) { }) g.AfterEach(func() { - if scaffold.closed == false { - close(scaffold.imports) - scaffold.wg.Wait() - } + scaffold.close() }) g.Describe("with invalid record config", func() { @@ -110,9 +129,7 @@ func Test_QueryableGenerator(t *testing.T) { _, e := io.Copy(scaffold.output, scaffold.g()) g.Assert(e).Equal(nil) g.Assert(scaffold.output.Len()).Equal(0) - scaffold.closed = true - close(scaffold.imports) - scaffold.wg.Wait() + scaffold.close() g.Assert(len(scaffold.received)).Equal(0) }) }) @@ -142,9 +159,7 @@ func Test_QueryableGenerator(t *testing.T) { g.It("injected the fmt, bytes and strings packages to the import channel", func() { fmt.Fprintln(scaffold.output, "package marlowt") io.Copy(scaffold.output, scaffold.g()) - scaffold.closed = true - close(scaffold.imports) - scaffold.wg.Wait() + scaffold.close() g.Assert(scaffold.received["fmt"]).Equal(true) g.Assert(scaffold.received["strings"]).Equal(true) diff --git a/marlow/record.go b/marlow/record.go index 32f80f6..dba3cb6 100644 --- a/marlow/record.go +++ b/marlow/record.go @@ -1,14 +1,23 @@ package marlow +import "strings" import "net/url" +import "github.com/dadleyy/marlow/marlow/writing" import "github.com/dadleyy/marlow/marlow/constants" // marlowRecord structs represent both the field-level and record-level configuration options for gerating the marlow stores. type marlowRecord struct { - config url.Values - fields map[string]url.Values + config url.Values + fields map[string]url.Values + importChannel chan<- string importRegistry map[string]bool + + storeChannel chan writing.FuncDecl +} + +func (r *marlowRecord) registerStoreMethod(method writing.FuncDecl) { + r.storeChannel <- method } func (r *marlowRecord) registerImports(imports ...string) { @@ -31,12 +40,22 @@ func (r *marlowRecord) registerImports(imports ...string) { } } +func (r *marlowRecord) external() string { + return r.config.Get(constants.StoreNameConfigOption) +} + func (r *marlowRecord) name() string { return r.config.Get(constants.RecordNameConfigOption) } func (r *marlowRecord) store() string { - return r.config.Get(constants.StoreNameConfigOption) + storeName := r.external() + + if storeName == "" { + return "" + } + + return strings.ToLower(storeName[0:1]) + storeName[1:] } func (r *marlowRecord) table() string { diff --git a/marlow/record_reader.go b/marlow/record_reader.go index 8e4b00b..757495a 100644 --- a/marlow/record_reader.go +++ b/marlow/record_reader.go @@ -3,12 +3,14 @@ package marlow import "io" import "fmt" import "bytes" +import "sync" import "go/ast" import "regexp" import "reflect" import "net/url" import "strings" import "github.com/gedex/inflector" +import "github.com/dadleyy/marlow/marlow/writing" import "github.com/dadleyy/marlow/marlow/constants" const ( @@ -148,6 +150,7 @@ func newRecordReader(root ast.Decl, imports chan<- string) (io.Reader, bool) { config: recordConfig, fields: recordFields, importChannel: imports, + storeChannel: make(chan writing.FuncDecl), } e := readRecord(pw, record) @@ -190,13 +193,32 @@ func readRecord(writer io.Writer, record marlowRecord) error { } // If we had any features enabled, we need to also generate the blue print API. - readers = append(readers, newBlueprintGenerator(record), newStoreGenerator(record)) + readers = append(readers, newBlueprintGenerator(record)) + + methods := make(map[string]writing.FuncDecl) + wg := &sync.WaitGroup{} + wg.Add(1) + + go func() { + for method := range record.storeChannel { + if _, d := methods[method.Name]; d { + continue + } + + methods[method.Name] = method + } + wg.Done() + }() // Iterate over all our collected features, copying them into the buffer if _, e := io.Copy(buffer, io.MultiReader(readers...)); e != nil { return e } - _, e := io.Copy(writer, buffer) + close(record.storeChannel) + wg.Wait() + + store := newStoreGenerator(record, methods) + _, e := io.Copy(writer, io.MultiReader(buffer, store)) return e } diff --git a/marlow/store.go b/marlow/store.go index a419ae7..bf50038 100644 --- a/marlow/store.go +++ b/marlow/store.go @@ -1,10 +1,12 @@ package marlow import "io" +import "fmt" +import "strings" import "net/url" import "github.com/dadleyy/marlow/marlow/writing" -func writeStore(destination io.Writer, record marlowRecord) error { +func writeStore(destination io.Writer, record marlowRecord, storeMethods map[string]writing.FuncDecl) error { out := writing.NewGoWriter(destination) e := out.WithStruct(record.store(), func(url.Values) error { @@ -16,16 +18,54 @@ func writeStore(destination io.Writer, record marlowRecord) error { return e } + symbols := struct { + dbParam string + }{"_db"} + + params := []writing.FuncParam{ + {Type: "*sql.DB", Symbol: symbols.dbParam}, + } + + returns := []string{record.external()} + + e = out.WithFunc(fmt.Sprintf("New%s", record.external()), params, returns, func(url.Values) error { + out.Println("return &%s{%s}", record.store(), symbols.dbParam) + return nil + }) + + if e != nil { + return e + } + + e = out.WithInterface(record.external(), func(url.Values) error { + for _, method := range storeMethods { + params := make([]string, 0) + returns := strings.Join(method.Returns, ",") + + for _, p := range method.Params { + params = append(params, fmt.Sprintf("%s", p.Type)) + } + + if len(method.Returns) > 0 { + returns = fmt.Sprintf("(%s)", returns) + } + + definition := fmt.Sprintf("%s(%s) %s", method.Name, strings.Join(params, ","), returns) + out.Println(definition) + } + return nil + }) + record.registerImports("database/sql") - return nil + return e } // newStoreGenerator returns a reader that will generate the centralized record store for a given record. -func newStoreGenerator(record marlowRecord) io.Reader { +func newStoreGenerator(record marlowRecord, methods map[string]writing.FuncDecl) io.Reader { pr, pw := io.Pipe() go func() { - e := writeStore(pw, record) + e := writeStore(pw, record, methods) pw.CloseWithError(e) }() diff --git a/marlow/store_test.go b/marlow/store_test.go index a8e63b0..06e779d 100644 --- a/marlow/store_test.go +++ b/marlow/store_test.go @@ -10,10 +10,12 @@ import "go/ast" import "go/token" import "go/parser" import "github.com/franela/goblin" +import "github.com/dadleyy/marlow/marlow/writing" type storeTestScaffold struct { output *bytes.Buffer imports chan string + methods map[string]writing.FuncDecl record url.Values received map[string]bool closed bool @@ -26,7 +28,7 @@ func (s *storeTestScaffold) g() io.Reader { config: s.record, } - return newStoreGenerator(record) + return newStoreGenerator(record, s.methods) } func (s *storeTestScaffold) parsed() (*ast.File, error) { @@ -51,6 +53,7 @@ func Test_StoreGenerator(t *testing.T) { output: new(bytes.Buffer), imports: make(chan string), record: make(url.Values), + methods: make(map[string]writing.FuncDecl), received: make(map[string]bool), closed: false, wg: &sync.WaitGroup{}, diff --git a/marlow/updateable.go b/marlow/updateable.go index de9119e..a93fc00 100644 --- a/marlow/updateable.go +++ b/marlow/updateable.go @@ -132,6 +132,11 @@ func updater(record marlowRecord, fieldName string, fieldConfig url.Values) io.R if e == nil { record.registerImports("fmt", "bytes") + record.registerStoreMethod(writing.FuncDecl{ + Name: methodName, + Params: params, + Returns: returns, + }) } pw.CloseWithError(e) diff --git a/marlow/updateable_test.go b/marlow/updateable_test.go index bcaf4f4..d3822e1 100644 --- a/marlow/updateable_test.go +++ b/marlow/updateable_test.go @@ -6,11 +6,13 @@ import "bytes" import "testing" import "net/url" import "github.com/franela/goblin" +import "github.com/dadleyy/marlow/marlow/writing" import "github.com/dadleyy/marlow/marlow/constants" type updateableTestScaffold struct { buffer *bytes.Buffer imports chan string + methods chan writing.FuncDecl record url.Values fields map[string]url.Values received map[string]bool @@ -18,11 +20,23 @@ type updateableTestScaffold struct { wg *sync.WaitGroup } +func (s *updateableTestScaffold) close() { + if s == nil || s.closed == true { + return + } + + s.closed = true + close(s.methods) + close(s.imports) + s.wg.Wait() +} + func (s *updateableTestScaffold) g() io.Reader { record := marlowRecord{ config: s.record, fields: s.fields, importChannel: s.imports, + storeChannel: s.methods, } return newUpdateableGenerator(record) } @@ -38,6 +52,7 @@ func Test_Updateable(t *testing.T) { scaffold = &updateableTestScaffold{ buffer: new(bytes.Buffer), imports: make(chan string), + methods: make(chan writing.FuncDecl), record: make(url.Values), fields: make(map[string]url.Values), received: make(map[string]bool), @@ -45,7 +60,13 @@ func Test_Updateable(t *testing.T) { wg: &sync.WaitGroup{}, } - scaffold.wg.Add(1) + scaffold.wg.Add(2) + + go func() { + for range scaffold.methods { + } + scaffold.wg.Done() + }() go func() { for i := range scaffold.imports { @@ -56,10 +77,7 @@ func Test_Updateable(t *testing.T) { }) g.AfterEach(func() { - if scaffold.closed == false { - close(scaffold.imports) - scaffold.wg.Wait() - } + scaffold.close() }) g.Describe("with a record config that has nullable fields", func() { diff --git a/marlow/writing/go_writer.go b/marlow/writing/go_writer.go index bbffa3a..c91b0ef 100644 --- a/marlow/writing/go_writer.go +++ b/marlow/writing/go_writer.go @@ -2,6 +2,13 @@ package writing import "net/url" +// FuncDecl provides the structure of a golang function definition. ast.FuncDecl was too complex. +type FuncDecl struct { + Name string + Params []FuncParam + Returns []string +} + // FuncParam represents the golang function parameter syntax type FuncParam struct { Type string @@ -20,6 +27,7 @@ type GoWriter interface { WithIf(string, Block, ...interface{}) error WithIter(string, Block, ...interface{}) error WithStruct(string, Block) error + WithInterface(string, Block) error Println(string, ...interface{}) Comment(string, ...interface{}) } diff --git a/marlow/writing/writer.go b/marlow/writing/writer.go index 989a441..9185d28 100644 --- a/marlow/writing/writer.go +++ b/marlow/writing/writer.go @@ -77,12 +77,20 @@ func (w *goWriter) WithIf(condition string, block Block, symbols ...interface{}) return w.withBlock(fmt.Sprintf("if %s", formattedCondition), block, make(url.Values)) } +func (w *goWriter) WithInterface(name string, block Block) error { + return w.withType(name, "interface", block) +} + func (w *goWriter) WithStruct(name string, block Block) error { + return w.withType(name, "struct", block) +} + +func (w *goWriter) withType(name string, typename string, block Block) error { if len(name) == 0 { return fmt.Errorf("invalid-name") } - return w.withBlock(fmt.Sprintf("type %s struct", name), block, make(url.Values)) + return w.withBlock(fmt.Sprintf("type %s %s", name, typename), block, make(url.Values)) } func (w *goWriter) formatReturns(returns []string) (returnList string) { From 6c1b2d1a5810407df914b651d7d8d6ff86f3aac1 Mon Sep 17 00:00:00 2001 From: Danny Hadley Date: Sat, 11 Nov 2017 22:33:38 -0500 Subject: [PATCH 7/7] chore(hound) adding capacity during store method param building --- marlow/store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marlow/store.go b/marlow/store.go index bf50038..e78cb01 100644 --- a/marlow/store.go +++ b/marlow/store.go @@ -39,7 +39,7 @@ func writeStore(destination io.Writer, record marlowRecord, storeMethods map[str e = out.WithInterface(record.external(), func(url.Values) error { for _, method := range storeMethods { - params := make([]string, 0) + params := make([]string, 0, len(method.Params)) returns := strings.Join(method.Returns, ",") for _, p := range method.Params {