From 530910cd9908294a0da609940cc87d02580d58d3 Mon Sep 17 00:00:00 2001 From: kroexov Date: Thu, 14 May 2026 13:23:58 +0300 Subject: [PATCH 1/3] xml: make -q honor TableMapping when -n is not set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The help for -q states it is ignored only when --namespaces (-n) is set, but the generator collapsed both sources of Packages — explicit -n and implicit TableMapping from mfd — into a single nil-check, so -q new effectively never prompted on real projects whose TableMapping is populated. Split the two sources: Packages stays "from -n", TableMapping is consulted separately and combined with --quiet. -q new now prompts for tables that are absent from TableMapping; -q all still skips them; -n keeps full precedence over TableMapping as the help promises. Add integration tests covering all three combinations using a fake prompt hook injected into the generator. --- generators/xml/generator.go | 62 +++++++++---- generators/xml/generator_test.go | 154 +++++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+), 16 deletions(-) diff --git a/generators/xml/generator.go b/generators/xml/generator.go index c84403f..b7b09e5 100644 --- a/generators/xml/generator.go +++ b/generators/xml/generator.go @@ -38,6 +38,9 @@ type Generator struct { verbose bool printNamespaces bool + + // promptNS overrides the interactive namespace prompt (used in tests). + promptNS func(table string, namespaces []string) (string, error) } // New creates generator @@ -190,15 +193,26 @@ func (g *Generator) Generate() (err error) { return nil } - if g.options.Packages == nil { - // if options.Packages is nil check TableMapping.Packages - g.options.Packages = project.TableMapping.Packages() - // fill tables from namespaces if not set - if len(g.options.Tables) == 0 && len(g.options.Packages) != 0 { + // tableMapping is the namespace mapping from the mfd file (TableMapping section). + // it is consulted only when -n is not set, in combination with --quiet mode. + tableMapping := project.TableMapping.Packages() + + // fill tables from db source when not explicitly set via -t. + if len(g.options.Tables) == 0 { + switch { + case g.options.Packages != nil: + // -n is set: read only listed tables. for table := range g.options.Packages { g.options.Tables = append(g.options.Tables, table) } - } else if len(g.options.Tables) == 0 { + case g.options.Quiet == quietAll && len(tableMapping) > 0: + // quiet=all relies entirely on existing mapping/entities; no point + // reading tables that would be skipped anyway. + for table := range tableMapping { + g.options.Tables = append(g.options.Tables, table) + } + default: + // quiet=new or default: read all tables so the prompt can run for new ones. g.options.Tables = []string{"public.*"} } } @@ -217,6 +231,11 @@ func (g *Generator) Generate() (err error) { set.Append(namespace.Name) } + prompt := g.PromptNS + if g.promptNS != nil { + prompt = g.promptNS + } + for _, entity := range entities { exiting := project.EntityByTable(entity.PGFullName) if exiting != nil { @@ -226,32 +245,43 @@ func (g *Generator) Generate() (err error) { var namespace string if g.options.Packages != nil { - // getting namespace from preset + // -n preset: strict mapping, skip everything not listed. var ok bool if namespace, ok = g.options.Packages[entity.PGFullName]; !ok { continue } } else { + // -n is not set: consult TableMapping from mfd combined with --quiet mode. + mappedNS, mappedOK := tableMapping[entity.PGFullName] + switch g.options.Quiet { case quietAll: - if exiting != nil { + switch { + case mappedOK: + namespace = mappedNS + case exiting != nil: namespace = exiting.Namespace - break // case + default: + continue // loop } - continue // loop case quietNew: - if exiting != nil { + switch { + case mappedOK: + namespace = mappedNS + case exiting != nil: namespace = exiting.Namespace - break // case + default: + if namespace, err = prompt(entity.PGFullName, set.Elements()); err != nil { + return fmt.Errorf("prompt namespace, err=%w", err) + } + if namespace == "skip" { + continue // loop + } } - fallthrough // to default default: - // asking namespace from prompt if namespace, err = g.PromptNS(entity.PGFullName, set.Elements()); err != nil { - // may happen only in ctrl+c return fmt.Errorf("prompt namespace, err=%w", err) } - // if user choose to skip if namespace == "skip" { continue // loop } diff --git a/generators/xml/generator_test.go b/generators/xml/generator_test.go index bb45717..37a6aa0 100644 --- a/generators/xml/generator_test.go +++ b/generators/xml/generator_test.go @@ -64,6 +64,160 @@ func TestGenerator_Generate(t *testing.T) { }) } +// TestGenerator_QuietWithTableMapping checks that --quiet (-q) flag is respected +// when -n is not set but the project already has a TableMapping section. +// Help promises that -q is ignored only when -n is set; with -n absent, +// -q new must still prompt for tables that are missing from the mapping. +func TestGenerator_QuietWithTableMapping(t *testing.T) { + dbdsn, exists := os.LookupEnv("DB_DSN") + if !exists { + dbdsn = "postgres://postgres:postgres@localhost:5432/newsportal?sslmode=disable" + } + + customTypes := model.CustomTypeMapping{"uuid": { + PGType: "uuid", + GoType: "uuid.UUID", + GoImport: "github.com/google/uuid", + }} + + seedMFD := func(t *testing.T, entries ...mfd.Entry) string { + t.Helper() + mfdPath := filepath.Join(t.TempDir(), testdata.FilenameMFD) + project := mfd.NewProject(testdata.FilenameMFD, mfd.GoPG10) + project.TableMapping = mfd.TableMapping{Entries: entries} + So(mfd.SaveMFD(mfdPath, project), ShouldBeNil) + return mfdPath + } + + // fakePrompt mirrors PromptNS behaviour for special tables but routes the rest + // to the supplied namespace, recording every call. + fakePrompt := func(target string, prompted map[string]struct{}) func(string, []string) (string, error) { + return func(table string, _ []string) (string, error) { + prompted[table] = struct{}{} + if table == "statuses" { + return "skip", nil + } + return target, nil + } + } + + Convey("TestGenerator_QuietWithTableMapping", t, func() { + Convey("-q new prompts only for tables outside TableMapping", func() { + mfdPath := seedMFD(t, mfd.Entry{ + XMLName: xml.Name{Local: "portal"}, + Value: "news,categories,tags", + }) + + prompted := map[string]struct{}{} + generator := New() + generator.options.URL = dbdsn + generator.options.Output = mfdPath + generator.options.GoPgVer = mfd.GoPG10 + generator.options.Quiet = quietNew + generator.options.CustomTypes = customTypes + generator.promptNS = fakePrompt("other", prompted) + + t.Log("Generate xml with -q new and a pre-seeded TableMapping") + So(generator.Generate(), ShouldBeNil) + + // mapped tables must be assigned silently — no prompt + So(prompted, ShouldNotContainKey, "news") + So(prompted, ShouldNotContainKey, "categories") + So(prompted, ShouldNotContainKey, "tags") + // new tables must trigger the prompt + So(prompted, ShouldContainKey, "vfsFiles") + So(prompted, ShouldContainKey, "countries") + + generated, err := mfd.LoadProject(mfdPath, false, mfd.GoPG10) + So(err, ShouldBeNil) + + portal := generated.Namespace("portal") + So(portal, ShouldNotBeNil) + So(portal.EntityByTable("news"), ShouldNotBeNil) + So(portal.EntityByTable("categories"), ShouldNotBeNil) + So(portal.EntityByTable("tags"), ShouldNotBeNil) + + other := generated.Namespace("other") + So(other, ShouldNotBeNil) + So(other.EntityByTable("vfsFiles"), ShouldNotBeNil) + So(other.EntityByTable("countries"), ShouldNotBeNil) + }) + + Convey("-q all with TableMapping keeps mapping and skips unmapped tables", func() { + // encryptionKeys has no FK on other regular entities (only on the statuses + // enum-table), so it can be mapped on its own without breaking IsConsistent; + // vfsFolders is read but stays unmapped to exercise the skip path. + mfdPath := seedMFD(t, mfd.Entry{ + XMLName: xml.Name{Local: "card"}, + Value: "encryptionKeys", + }) + + promptCalls := 0 + generator := New() + generator.options.URL = dbdsn + generator.options.Output = mfdPath + generator.options.Tables = []string{"public.encryptionKeys", "public.vfsFolders"} + generator.options.GoPgVer = mfd.GoPG10 + generator.options.Quiet = quietAll + generator.options.CustomTypes = customTypes + generator.promptNS = func(string, []string) (string, error) { + promptCalls++ + return "", nil + } + + t.Log("Generate xml with -q all and a pre-seeded TableMapping") + So(generator.Generate(), ShouldBeNil) + So(promptCalls, ShouldEqual, 0) + + generated, err := mfd.LoadProject(mfdPath, false, mfd.GoPG10) + So(err, ShouldBeNil) + + card := generated.Namespace("card") + So(card, ShouldNotBeNil) + So(card.EntityByTable("encryptionKeys"), ShouldNotBeNil) + + // unmapped table must not leak into the project under -q all + So(generated.EntityByTable("vfsFolders"), ShouldBeNil) + }) + + Convey("-n preset still ignores -q (help contract)", func() { + // seed TableMapping that points encryptionKeys at "portal" — if -q won + // over -n, encryptionKeys would land in "portal". + mfdPath := seedMFD(t, mfd.Entry{ + XMLName: xml.Name{Local: "portal"}, + Value: "encryptionKeys", + }) + + promptCalls := 0 + generator := New() + generator.options.URL = dbdsn + generator.options.Output = mfdPath + generator.options.GoPgVer = mfd.GoPG10 + generator.options.Quiet = quietNew + generator.options.CustomTypes = customTypes + // -n must take precedence and place encryptionKeys into "custom" + generator.options.Packages = parseNamespacesFlag("custom:encryptionKeys") + generator.promptNS = func(string, []string) (string, error) { + promptCalls++ + return "", nil + } + + t.Log("Generate xml with -n set and -q new together") + So(generator.Generate(), ShouldBeNil) + So(promptCalls, ShouldEqual, 0) + + generated, err := mfd.LoadProject(mfdPath, false, mfd.GoPG10) + So(err, ShouldBeNil) + + custom := generated.Namespace("custom") + So(custom, ShouldNotBeNil) + So(custom.EntityByTable("encryptionKeys"), ShouldNotBeNil) + // TableMapping must be overridden by -n + So(generated.Namespace("portal"), ShouldBeNil) + }) + }) +} + func helperLoadBytes(t *testing.T, path string) []byte { bytes, err := os.ReadFile(path) if err != nil { From e535d6bd0cfdfc734d0afc0318d8d61ea96c8281 Mon Sep 17 00:00:00 2001 From: kroexov Date: Sun, 21 Jun 2026 17:54:15 +0300 Subject: [PATCH 2/3] xml: remove test-only promptNS seam from Generator Move namespace selection into a pure decideNamespace function that is tested directly, and split Generate's per-entity loop into fillNamespaces. Drops the promptNS field that existed only for tests; the package now passes golangci-lint (Generate no longer trips gocognit). --- generators/xml/generator.go | 162 +++++++++++--------- generators/xml/generator_test.go | 251 +++++++++++++------------------ 2 files changed, 199 insertions(+), 214 deletions(-) diff --git a/generators/xml/generator.go b/generators/xml/generator.go index b7b09e5..c0a79c2 100644 --- a/generators/xml/generator.go +++ b/generators/xml/generator.go @@ -38,9 +38,6 @@ type Generator struct { verbose bool printNamespaces bool - - // promptNS overrides the interactive namespace prompt (used in tests). - promptNS func(table string, namespaces []string) (string, error) } // New creates generator @@ -171,6 +168,54 @@ func parseNamespacesFlag(v string) map[string]string { return mp } +type nsAction int + +const ( + nsAssign nsAction = iota // namespace is decided — use it as is + nsSkip // table must be skipped + nsPrompt // namespace must be asked interactively +) + +// decideNamespace resolves the target namespace for a table from the configured +// mode (-n / -q), the mfd TableMapping and the existing project state. It performs +// no IO: nsPrompt signals that the caller must ask the user interactively. +func decideNamespace(opts Options, tableMapping map[string]string, table, existingNS string, hasExisting bool) (string, nsAction) { + // -n preset: strict mapping, skip everything not listed. + if opts.Packages != nil { + if ns, ok := opts.Packages[table]; ok { + return ns, nsAssign + } + return "", nsSkip + } + + // -n is not set: consult TableMapping from mfd combined with --quiet mode. + mappedNS, mappedOK := tableMapping[table] + + switch opts.Quiet { + case quietAll: + switch { + case mappedOK: + return mappedNS, nsAssign + case hasExisting: + return existingNS, nsAssign + default: + return "", nsSkip + } + case quietNew: + switch { + case mappedOK: + return mappedNS, nsAssign + case hasExisting: + return existingNS, nsAssign + default: + return "", nsPrompt + } + default: + // no --quiet: prompt for every table. + return "", nsPrompt + } +} + // Generate runs generator func (g *Generator) Generate() (err error) { var logger *log.Logger @@ -225,66 +270,63 @@ func (g *Generator) Generate() (err error) { return fmt.Errorf("read database, err=%w", err) } + if err = g.fillNamespaces(project, entities, addedCustomTypes); err != nil { + return err + } + + // suggesting searches && fk links + project.SuggestArrayLinks() + project.UpdateLinks() + + // validate names + if err := project.ValidateNames(); err != nil { + return err + } + + if err := project.IsConsistent(); err != nil { + return fmt.Errorf("%w. fk table should be either be in project or selected for generatation", err) + } + + // saving mfd file + if err = mfd.SaveMFD(g.options.Output, project); err != nil { + return err + } + + return mfd.SaveProjectXML(g.options.Output, project) +} + +// fillNamespaces resolves a namespace for every entity read from the database +// (prompting the user when decideNamespace requires it) and adds it to the project. +func (g *Generator) fillNamespaces(project *mfd.Project, entities []model.Entity, addedCustomTypes mfd.CustomTypes) (err error) { + tableMapping := project.TableMapping.Packages() + set := mfd.NewSet() // filling set for _, namespace := range project.Namespaces { set.Append(namespace.Name) } - prompt := g.PromptNS - if g.promptNS != nil { - prompt = g.promptNS - } - for _, entity := range entities { exiting := project.EntityByTable(entity.PGFullName) + existingNS := "" if exiting != nil { + existingNS = exiting.Namespace set.Prepend(exiting.Namespace) } - var namespace string - - if g.options.Packages != nil { - // -n preset: strict mapping, skip everything not listed. - var ok bool - if namespace, ok = g.options.Packages[entity.PGFullName]; !ok { - continue + namespace, action := decideNamespace(g.options, tableMapping, entity.PGFullName, existingNS, exiting != nil) + switch action { + case nsSkip: + continue // loop + case nsPrompt: + // asking namespace from prompt + if namespace, err = g.PromptNS(entity.PGFullName, set.Elements()); err != nil { + // may happen only in ctrl+c + return fmt.Errorf("prompt namespace, err=%w", err) } - } else { - // -n is not set: consult TableMapping from mfd combined with --quiet mode. - mappedNS, mappedOK := tableMapping[entity.PGFullName] - - switch g.options.Quiet { - case quietAll: - switch { - case mappedOK: - namespace = mappedNS - case exiting != nil: - namespace = exiting.Namespace - default: - continue // loop - } - case quietNew: - switch { - case mappedOK: - namespace = mappedNS - case exiting != nil: - namespace = exiting.Namespace - default: - if namespace, err = prompt(entity.PGFullName, set.Elements()); err != nil { - return fmt.Errorf("prompt namespace, err=%w", err) - } - if namespace == "skip" { - continue // loop - } - } - default: - if namespace, err = g.PromptNS(entity.PGFullName, set.Elements()); err != nil { - return fmt.Errorf("prompt namespace, err=%w", err) - } - if namespace == "skip" { - continue // loop - } + // if user choose to skip + if namespace == "skip" { + continue // loop } } @@ -295,25 +337,7 @@ func (g *Generator) Generate() (err error) { project.AddEntity(namespace, PackEntity(namespace, entity, exiting, addedCustomTypes)) } - // suggesting searches && fk links - project.SuggestArrayLinks() - project.UpdateLinks() - - // validate names - if err := project.ValidateNames(); err != nil { - return err - } - - if err := project.IsConsistent(); err != nil { - return fmt.Errorf("%w. fk table should be either be in project or selected for generatation", err) - } - - // saving mfd file - if err = mfd.SaveMFD(g.options.Output, project); err != nil { - return err - } - - return mfd.SaveProjectXML(g.options.Output, project) + return nil } // PromptNS prompting namespace in console diff --git a/generators/xml/generator_test.go b/generators/xml/generator_test.go index 37a6aa0..365f569 100644 --- a/generators/xml/generator_test.go +++ b/generators/xml/generator_test.go @@ -64,158 +64,119 @@ func TestGenerator_Generate(t *testing.T) { }) } -// TestGenerator_QuietWithTableMapping checks that --quiet (-q) flag is respected -// when -n is not set but the project already has a TableMapping section. -// Help promises that -q is ignored only when -n is set; with -n absent, -// -q new must still prompt for tables that are missing from the mapping. -func TestGenerator_QuietWithTableMapping(t *testing.T) { - dbdsn, exists := os.LookupEnv("DB_DSN") - if !exists { - dbdsn = "postgres://postgres:postgres@localhost:5432/newsportal?sslmode=disable" - } - - customTypes := model.CustomTypeMapping{"uuid": { - PGType: "uuid", - GoType: "uuid.UUID", - GoImport: "github.com/google/uuid", - }} - - seedMFD := func(t *testing.T, entries ...mfd.Entry) string { - t.Helper() - mfdPath := filepath.Join(t.TempDir(), testdata.FilenameMFD) - project := mfd.NewProject(testdata.FilenameMFD, mfd.GoPG10) - project.TableMapping = mfd.TableMapping{Entries: entries} - So(mfd.SaveMFD(mfdPath, project), ShouldBeNil) - return mfdPath +// TestDecideNamespace checks the pure namespace-resolution logic that drives +// --quiet (-q) and --namespaces (-n). It replaces the previous DB-backed test +// that swapped an interactive-prompt seam on the Generator: every invariant the +// seam used to verify (which tables are mapped, skipped or prompted, and that -n +// overrides -q/TableMapping) is now asserted directly, with no DB and no mocks. +func TestDecideNamespace(t *testing.T) { + // tableMapping mirrors a project where news/categories/tags are pre-mapped to "portal". + tableMapping := map[string]string{ + "news": "portal", + "categories": "portal", + "tags": "portal", } - // fakePrompt mirrors PromptNS behaviour for special tables but routes the rest - // to the supplied namespace, recording every call. - fakePrompt := func(target string, prompted map[string]struct{}) func(string, []string) (string, error) { - return func(table string, _ []string) (string, error) { - prompted[table] = struct{}{} - if table == "statuses" { - return "skip", nil - } - return target, nil - } + tests := []struct { + name string + opts Options + table string + existingNS string + hasExisting bool + wantNS string + wantAction nsAction + }{ + { + name: "-q new: mapped table is assigned silently", + opts: Options{Quiet: quietNew}, + table: "news", + wantNS: "portal", + wantAction: nsAssign, + }, + { + name: "-q new: unmapped table already in project keeps its namespace", + opts: Options{Quiet: quietNew}, + table: "comments", + existingNS: "blog", + hasExisting: true, + wantNS: "blog", + wantAction: nsAssign, + }, + { + name: "-q new: brand new table must be prompted", + opts: Options{Quiet: quietNew}, + table: "vfsFiles", + wantAction: nsPrompt, + }, + { + name: "-q all: mapped table is assigned", + opts: Options{Quiet: quietAll}, + table: "news", + wantNS: "portal", + wantAction: nsAssign, + }, + { + name: "-q all: unmapped table already in project keeps its namespace", + opts: Options{Quiet: quietAll}, + table: "comments", + existingNS: "blog", + hasExisting: true, + wantNS: "blog", + wantAction: nsAssign, + }, + { + name: "-q all: unmapped new table is skipped", + opts: Options{Quiet: quietAll}, + table: "vfsFolders", + wantAction: nsSkip, + }, + { + name: "-n: listed table is assigned", + opts: Options{Packages: map[string]string{"encryptionKeys": "custom"}}, + table: "encryptionKeys", + wantNS: "custom", + wantAction: nsAssign, + }, + { + name: "-n: table missing from preset is skipped", + opts: Options{Packages: map[string]string{"encryptionKeys": "custom"}}, + table: "vfsFolders", + wantAction: nsSkip, + }, + { + name: "-n overrides TableMapping", + opts: Options{Quiet: quietNew, Packages: map[string]string{"news": "custom"}}, + table: "news", + wantNS: "custom", + wantAction: nsAssign, + }, + { + name: "default (no -q/-n): new table is prompted", + opts: Options{}, + table: "news", + wantAction: nsPrompt, + }, + { + name: "default (no -q/-n): even an existing table is prompted", + opts: Options{}, + table: "comments", + existingNS: "blog", + hasExisting: true, + wantAction: nsPrompt, + }, } - Convey("TestGenerator_QuietWithTableMapping", t, func() { - Convey("-q new prompts only for tables outside TableMapping", func() { - mfdPath := seedMFD(t, mfd.Entry{ - XMLName: xml.Name{Local: "portal"}, - Value: "news,categories,tags", - }) - - prompted := map[string]struct{}{} - generator := New() - generator.options.URL = dbdsn - generator.options.Output = mfdPath - generator.options.GoPgVer = mfd.GoPG10 - generator.options.Quiet = quietNew - generator.options.CustomTypes = customTypes - generator.promptNS = fakePrompt("other", prompted) - - t.Log("Generate xml with -q new and a pre-seeded TableMapping") - So(generator.Generate(), ShouldBeNil) - - // mapped tables must be assigned silently — no prompt - So(prompted, ShouldNotContainKey, "news") - So(prompted, ShouldNotContainKey, "categories") - So(prompted, ShouldNotContainKey, "tags") - // new tables must trigger the prompt - So(prompted, ShouldContainKey, "vfsFiles") - So(prompted, ShouldContainKey, "countries") - - generated, err := mfd.LoadProject(mfdPath, false, mfd.GoPG10) - So(err, ShouldBeNil) - - portal := generated.Namespace("portal") - So(portal, ShouldNotBeNil) - So(portal.EntityByTable("news"), ShouldNotBeNil) - So(portal.EntityByTable("categories"), ShouldNotBeNil) - So(portal.EntityByTable("tags"), ShouldNotBeNil) - - other := generated.Namespace("other") - So(other, ShouldNotBeNil) - So(other.EntityByTable("vfsFiles"), ShouldNotBeNil) - So(other.EntityByTable("countries"), ShouldNotBeNil) - }) - - Convey("-q all with TableMapping keeps mapping and skips unmapped tables", func() { - // encryptionKeys has no FK on other regular entities (only on the statuses - // enum-table), so it can be mapped on its own without breaking IsConsistent; - // vfsFolders is read but stays unmapped to exercise the skip path. - mfdPath := seedMFD(t, mfd.Entry{ - XMLName: xml.Name{Local: "card"}, - Value: "encryptionKeys", - }) - - promptCalls := 0 - generator := New() - generator.options.URL = dbdsn - generator.options.Output = mfdPath - generator.options.Tables = []string{"public.encryptionKeys", "public.vfsFolders"} - generator.options.GoPgVer = mfd.GoPG10 - generator.options.Quiet = quietAll - generator.options.CustomTypes = customTypes - generator.promptNS = func(string, []string) (string, error) { - promptCalls++ - return "", nil + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotNS, gotAction := decideNamespace(tt.opts, tableMapping, tt.table, tt.existingNS, tt.hasExisting) + if gotAction != tt.wantAction { + t.Errorf("action = %d, want %d", gotAction, tt.wantAction) } - - t.Log("Generate xml with -q all and a pre-seeded TableMapping") - So(generator.Generate(), ShouldBeNil) - So(promptCalls, ShouldEqual, 0) - - generated, err := mfd.LoadProject(mfdPath, false, mfd.GoPG10) - So(err, ShouldBeNil) - - card := generated.Namespace("card") - So(card, ShouldNotBeNil) - So(card.EntityByTable("encryptionKeys"), ShouldNotBeNil) - - // unmapped table must not leak into the project under -q all - So(generated.EntityByTable("vfsFolders"), ShouldBeNil) - }) - - Convey("-n preset still ignores -q (help contract)", func() { - // seed TableMapping that points encryptionKeys at "portal" — if -q won - // over -n, encryptionKeys would land in "portal". - mfdPath := seedMFD(t, mfd.Entry{ - XMLName: xml.Name{Local: "portal"}, - Value: "encryptionKeys", - }) - - promptCalls := 0 - generator := New() - generator.options.URL = dbdsn - generator.options.Output = mfdPath - generator.options.GoPgVer = mfd.GoPG10 - generator.options.Quiet = quietNew - generator.options.CustomTypes = customTypes - // -n must take precedence and place encryptionKeys into "custom" - generator.options.Packages = parseNamespacesFlag("custom:encryptionKeys") - generator.promptNS = func(string, []string) (string, error) { - promptCalls++ - return "", nil + if gotNS != tt.wantNS { + t.Errorf("namespace = %q, want %q", gotNS, tt.wantNS) } - - t.Log("Generate xml with -n set and -q new together") - So(generator.Generate(), ShouldBeNil) - So(promptCalls, ShouldEqual, 0) - - generated, err := mfd.LoadProject(mfdPath, false, mfd.GoPG10) - So(err, ShouldBeNil) - - custom := generated.Namespace("custom") - So(custom, ShouldNotBeNil) - So(custom.EntityByTable("encryptionKeys"), ShouldNotBeNil) - // TableMapping must be overridden by -n - So(generated.Namespace("portal"), ShouldBeNil) }) - }) + } } func helperLoadBytes(t *testing.T, path string) []byte { From 42d084d0ff09f420d1c1c0f9b64150c27157914a Mon Sep 17 00:00:00 2001 From: kroexov Date: Sun, 21 Jun 2026 18:16:51 +0300 Subject: [PATCH 3/3] xml: document quiet (-q) flag and refresh -h snapshot in README --- generators/xml/README.md | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/generators/xml/README.md b/generators/xml/README.md index b7e7d29..0fe5816 100644 --- a/generators/xml/README.md +++ b/generators/xml/README.md @@ -17,22 +17,36 @@ mfd-generator xml -h Create or update project base with namespaces and entities Usage: - mfd xml [flags] + mfd-generator xml [flags] Flags: - -v, --verbose print sql queries - -c, --conn string connection string to postgres database, e.g. postgres://usr:pwd@localhost:5432/db - -m, --mfd string mfd file path - -t, --tables strings table names for model generation separated by comma - use 'schema_name.*' to generate model for every table in model (default [public.*]) - -n, --namespaces string use this parameter to set table & namespace in format "users=users,projects;shop=orders,prices" - -p, --print print namespace - tables association - -h, --help help for xml + -v, --verbose print sql queries + -c, --conn string connection string to postgres database, e.g. postgres://usr:pwd@localhost:5432/db + -m, --mfd string mfd file path + -t, --tables strings table names for model generation separated by comma + use 'schema_name.*' to generate model for every table in model + -n, --namespaces string use this parameter to set table & namespace in format "users=users,projects;shop=orders,prices" + -g, --gopgver int go-pg version (default 9) + -q, --quiet string quiet mode. ignored when --namespaces (-n) flag is set. possible values: + - all - will use namespace entity mapping from mfd, entities not present in mfd file will be ignored + - new - generator will prompt namespace for entities not present in mfd file + --custom-types strings set custom types separated by comma + format: :. + examples: uuid:github.com/google/uuid.UUID,point:src/model.Point,bytea:string + + -p, --print print namespace - tables association + -h, --help help for xml ``` `-t, --tables` - позволяет вводить исходные таблицы для генератора через запятую, если не указана схема для таблицы, то будет использоваться public. `*` - для генерирования всех таблиц в схеме, например: `public.*,geo.locations,geo.cities` `-n, --namespaces` - сайлент-режим, позволяет задать ассоциацию неймспейс - таблица. Формат; `namespace1=table1,table2;namespace2=table3,table4`, флаг имеет приоритет над внутренней таблицей TableMapping в заполнении Packages +`-q, --quiet` - тихий режим: позволяет запускать генерацию без интерактивного выбора неймспейсов. Игнорируется, если задан `-n, --namespaces`. Если `-n` не задан, режим работает вместе с `TableMapping` из mfd файла. Возможные значения: +- `all` - берёт неймспейсы только из `TableMapping` и из уже существующих в проекте сущностей. Таблицы, которых там нет, пропускаются и в проект не добавляются. Ничего не спрашивает. +- `new` - тоже берёт неймспейсы из `TableMapping` и существующих сущностей, но для новых таблиц (которых нет ни в маппинге, ни в проекте) спрашивает неймспейс интерактивно. + +Если не задан ни `-q`, ни `-n`, генератор спрашивает неймспейс для каждой таблицы. Для `-q new` без явного `-t` читаются все таблицы (`public.*`) — иначе для новых таблиц не у чего было бы спросить неймспейс. + `-p, --print` - на основе загруженного проекта выводит ассоциации неймспейс - таблица в формате, подходящем для флага `-n, --namespaces`. Не запускает генератор ### MFD файл