From 6f048c1d944b564331cc6be2c1789ad96f56ccda Mon Sep 17 00:00:00 2001 From: Thomas Karner Date: Sun, 10 Nov 2024 14:35:32 +0100 Subject: [PATCH] Add option to use stdout (#63) * add option to use stdout * update readme and changelog --- analyzer/analyzer.go | 4 +-- changelog.md | 10 +++++-- cmd/root.go | 60 +++++++++++++++++++++++++++++------------- config/config.go | 50 +++++++++++++++++++---------------- config/output_mode.go | 28 ++++++++++++++++++++ diagram/diagram.go | 16 +++-------- exampleRunConfig.yaml | 5 ++-- mocks/Diagram.go | 12 +++++---- mocks/MermerdConfig.go | 14 ++++++++++ presentation/info.go | 14 ++++++++++ presentation/intro.go | 7 ++++- presentation/outro.go | 7 ++++- readme.md | 5 ++++ 13 files changed, 167 insertions(+), 65 deletions(-) create mode 100644 config/output_mode.go create mode 100644 presentation/info.go diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index 957799b..bf48f01 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -123,7 +123,7 @@ func (a analyzer) GetTables(db database.Connector, selectedSchemas []string) ([] return util.Map2(selectedTables, func(value string) database.TableDetail { res, err := database.ParseTableName(value, selectedSchemas) if err != nil { - logrus.Error("Could not parse table name", value) + logrus.Error("Could not parse table name ", value) } return res @@ -165,7 +165,7 @@ func (a analyzer) GetTables(db database.Connector, selectedSchemas []string) ([] return util.Map2(surveyResult, func(value string) database.TableDetail { res, err := database.ParseTableName(value, selectedSchemas) if err != nil { - logrus.Error("Could not parse table name", value) + logrus.Error("Could not parse table name ", value) } return res diff --git a/changelog.md b/changelog.md index ade0303..e3087f6 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (after version 0.0.5). +## [0.11.1] - 2024-12-18 +### Added +- new `--outputMode` option ([PR #63](https://github.com/KarnerTh/mermerd/pull/63)) + ## [0.11.0] - 2024-06-16 ### Added - new `--ignoreTables` option ([PR #59](https://github.com/KarnerTh/mermerd/pull/59)) @@ -11,8 +15,6 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (after version 0.0 ### Changed - go 1.22 is now used -### Fixed - ## [0.10.0] - 2023-11-21 ### Added - Support relationship labels ([PR #50](https://github.com/KarnerTh/mermerd/pull/50)) @@ -155,6 +157,10 @@ by `--showDescriptions enumValues` (for details see [PR #32](https://github.com/ ### Added - Initial release of mermerd +[0.11.1]: https://github.com/cpkurotori/mermerd/releases/tag/v0.11.1 + +[0.11.0]: https://github.com/KarnerTh/mermerd/releases/tag/v0.11.0 + [0.10.0]: https://github.com/KarnerTh/mermerd/releases/tag/v0.10.0 [0.9.0]: https://github.com/KarnerTh/mermerd/releases/tag/v0.9.0 diff --git a/cmd/root.go b/cmd/root.go index 8680c46..897446f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,7 +5,6 @@ import ( "io" "os" - "github.com/fatih/color" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -24,14 +23,18 @@ var rootCmd = &cobra.Command{ Short: "Create Mermaid ERD diagrams from existing tables", Long: "Create Mermaid ERD diagrams from existing tables", Run: func(cmd *cobra.Command, args []string) { - presentation.ShowIntro() - config := config.NewConfig() + conf := config.NewConfig() + if runConfig != "" { + presentation.ShowInfo(conf, fmt.Sprintf("Using run configuration (from %s)", runConfig)) + } + + presentation.ShowIntro(conf) connectorFactory := database.NewConnectorFactory() questioner := analyzer.NewQuestioner() - analyzer := analyzer.NewAnalyzer(config, connectorFactory, questioner) - diagram := diagram.NewDiagram(config) + analyzer := analyzer.NewAnalyzer(conf, connectorFactory, questioner) + diagram := diagram.NewDiagram(conf) - if !config.Debug() { + if !conf.Debug() { logrus.SetOutput(io.Discard) } @@ -42,14 +45,33 @@ var rootCmd = &cobra.Command{ os.Exit(1) } - err = diagram.Create(result) + var wr io.Writer + if conf.OutputMode() == config.File { + f, err := os.Create(conf.OutputFileName()) + defer f.Close() + if err != nil { + logrus.Error(err) + presentation.ShowError() + os.Exit(1) + } + + wr = f + } else if conf.OutputMode() == config.Stdout { + wr = os.Stdout + } else { + logrus.Errorf("Output mode %s not suppported", conf.OutputMode()) + presentation.ShowError() + os.Exit(1) + } + + err = diagram.Create(wr, result) if err != nil { logrus.Error(err) presentation.ShowError() os.Exit(1) } - presentation.ShowSuccess(config.OutputFileName()) + presentation.ShowSuccess(conf, conf.OutputFileName()) }, } @@ -76,24 +98,27 @@ func init() { rootCmd.Flags().StringP(config.SchemaKey, "s", "", "schema that should be used") rootCmd.Flags().StringP(config.OutputFileNameKey, "o", "result.mmd", "output file name") rootCmd.Flags().String(config.SchemaPrefixSeparator, ".", "the separator that should be used between schema and table name") + var outputMode = config.File + rootCmd.Flags().Var(&outputMode, config.OutputMode, `output mode (file, stdout)`) rootCmd.Flags().StringSlice(config.ShowDescriptionsKey, []string{""}, "show 'notNull', 'enumValues' and/or 'columnComments' in the description column") rootCmd.Flags().StringSlice(config.SelectedTablesKey, []string{""}, "tables to include") - bindFlagToViper(config.ShowAllConstraintsKey) - bindFlagToViper(config.UseAllTablesKey) - bindFlagToViper(config.IgnoreTables) - bindFlagToViper(config.UseAllSchemasKey) + bindFlagToViper(config.ConnectionStringKey) bindFlagToViper(config.DebugKey) - bindFlagToViper(config.OmitConstraintLabelsKey) - bindFlagToViper(config.OmitAttributeKeysKey) bindFlagToViper(config.EncloseWithMermaidBackticksKey) - bindFlagToViper(config.ConnectionStringKey) - bindFlagToViper(config.SchemaKey) + bindFlagToViper(config.IgnoreTables) + bindFlagToViper(config.OmitAttributeKeysKey) + bindFlagToViper(config.OmitConstraintLabelsKey) bindFlagToViper(config.OutputFileNameKey) + bindFlagToViper(config.OutputMode) + bindFlagToViper(config.SchemaKey) + bindFlagToViper(config.SchemaPrefixSeparator) bindFlagToViper(config.SelectedTablesKey) + bindFlagToViper(config.ShowAllConstraintsKey) bindFlagToViper(config.ShowDescriptionsKey) bindFlagToViper(config.ShowSchemaPrefix) - bindFlagToViper(config.SchemaPrefixSeparator) + bindFlagToViper(config.UseAllSchemasKey) + bindFlagToViper(config.UseAllTablesKey) } func bindFlagToViper(key string) { @@ -102,7 +127,6 @@ func bindFlagToViper(key string) { func initConfig() { if runConfig != "" { - color.Blue(fmt.Sprintf("Using run configuration (from %s)", runConfig)) viper.SetConfigFile(runConfig) } else { home, err := os.UserHomeDir() diff --git a/config/config.go b/config/config.go index 577ae0b..1b6bfe0 100644 --- a/config/config.go +++ b/config/config.go @@ -3,45 +3,47 @@ package config import "github.com/spf13/viper" const ( - ShowAllConstraintsKey = "showAllConstraints" - UseAllTablesKey = "useAllTables" - IgnoreTables = "ignoreTables" - SelectedTablesKey = "selectedTables" - SchemaKey = "schema" ConnectionStringKey = "connectionString" ConnectionStringSuggestionsKey = "connectionStringSuggestions" - OutputFileNameKey = "outputFileName" - EncloseWithMermaidBackticksKey = "encloseWithMermaidBackticks" DebugKey = "debug" - OmitConstraintLabelsKey = "omitConstraintLabels" + EncloseWithMermaidBackticksKey = "encloseWithMermaidBackticks" + IgnoreTables = "ignoreTables" OmitAttributeKeysKey = "omitAttributeKeys" + OmitConstraintLabelsKey = "omitConstraintLabels" + OutputFileNameKey = "outputFileName" + OutputMode = "outputMode" + RelationshipLabelsKey = "relationshipLabels" + SchemaKey = "schema" + SchemaPrefixSeparator = "schemaPrefixSeparator" + SelectedTablesKey = "selectedTables" + ShowAllConstraintsKey = "showAllConstraints" ShowDescriptionsKey = "showDescriptions" - UseAllSchemasKey = "useAllSchemas" ShowSchemaPrefix = "showSchemaPrefix" - SchemaPrefixSeparator = "schemaPrefixSeparator" - RelationshipLabelsKey = "relationshipLabels" + UseAllSchemasKey = "useAllSchemas" + UseAllTablesKey = "useAllTables" ) type config struct{} type MermerdConfig interface { - ShowAllConstraints() bool - UseAllTables() bool - IgnoreTables() []string - Schemas() []string ConnectionString() string - OutputFileName() string ConnectionStringSuggestions() []string - SelectedTables() []string - EncloseWithMermaidBackticks() bool Debug() bool - OmitConstraintLabels() bool + EncloseWithMermaidBackticks() bool + IgnoreTables() []string OmitAttributeKeys() bool + OmitConstraintLabels() bool + OutputFileName() string + OutputMode() OutputModeType + RelationshipLabels() []RelationshipLabel + SchemaPrefixSeparator() string + Schemas() []string + SelectedTables() []string + ShowAllConstraints() bool ShowDescriptions() []string - UseAllSchemas() bool ShowSchemaPrefix() bool - SchemaPrefixSeparator() string - RelationshipLabels() []RelationshipLabel + UseAllSchemas() bool + UseAllTables() bool } func NewConfig() MermerdConfig { @@ -116,3 +118,7 @@ func (c config) ShowSchemaPrefix() bool { func (c config) SchemaPrefixSeparator() string { return viper.GetString(SchemaPrefixSeparator) } + +func (c config) OutputMode() OutputModeType { + return OutputModeType(viper.GetString(OutputMode)) +} diff --git a/config/output_mode.go b/config/output_mode.go new file mode 100644 index 0000000..5957e18 --- /dev/null +++ b/config/output_mode.go @@ -0,0 +1,28 @@ +package config + +import "errors" + +type OutputModeType string + +const ( + File OutputModeType = "file" + Stdout OutputModeType = "stdout" +) + +func (o *OutputModeType) String() string { + return string(*o) +} + +func (o *OutputModeType) Set(v string) error { + switch v { + case "file", "stdout": + *o = OutputModeType(v) + return nil + } + + return errors.New(`must be one of "file" or "stdout"`) +} + +func (o *OutputModeType) Type() string { + return "OutputModeType" +} diff --git a/diagram/diagram.go b/diagram/diagram.go index 3223935..f04f9bf 100644 --- a/diagram/diagram.go +++ b/diagram/diagram.go @@ -2,7 +2,7 @@ package diagram import ( _ "embed" - "os" + "io" "text/template" "github.com/sirupsen/logrus" @@ -19,22 +19,14 @@ type diagram struct { } type Diagram interface { - Create(result *database.Result) error + Create(wr io.Writer, result *database.Result) error } func NewDiagram(config config.MermerdConfig) Diagram { return diagram{config} } -func (d diagram) Create(result *database.Result) error { - f, err := os.Create(d.config.OutputFileName()) - if err != nil { - logrus.Error("Could not create output file", " | ", err) - return err - } - - defer f.Close() - +func (d diagram) Create(wr io.Writer, result *database.Result) error { tmpl, err := template.New("erd_template").Parse(erdTemplate) if err != nil { logrus.Error("Could not load template file", " | ", err) @@ -74,7 +66,7 @@ func (d diagram) Create(result *database.Result) error { Constraints: constraints, } - if err = tmpl.Execute(f, diagramData); err != nil { + if err = tmpl.Execute(wr, diagramData); err != nil { logrus.Error("Could not create diagram", " | ", err) return err } diff --git a/exampleRunConfig.yaml b/exampleRunConfig.yaml index 32f40ab..3465106 100644 --- a/exampleRunConfig.yaml +++ b/exampleRunConfig.yaml @@ -10,8 +10,8 @@ schema: # Define what tables should be used #useAllTables: true selectedTables: - - article - - label + - public.article + - public.label # Additional flags showAllConstraints: true @@ -20,6 +20,7 @@ encloseWithMermaidBackticks: false debug: false omitConstraintLabels: false omitAttributeKeys: false +outputMode: stdout showDescriptions: - enumValues - columnComments diff --git a/mocks/Diagram.go b/mocks/Diagram.go index 955e45c..6cfee01 100644 --- a/mocks/Diagram.go +++ b/mocks/Diagram.go @@ -5,6 +5,8 @@ package mocks import ( database "github.com/KarnerTh/mermerd/database" + io "io" + mock "github.com/stretchr/testify/mock" ) @@ -13,13 +15,13 @@ type Diagram struct { mock.Mock } -// Create provides a mock function with given fields: result -func (_m *Diagram) Create(result *database.Result) error { - ret := _m.Called(result) +// Create provides a mock function with given fields: wr, result +func (_m *Diagram) Create(wr io.Writer, result *database.Result) error { + ret := _m.Called(wr, result) var r0 error - if rf, ok := ret.Get(0).(func(*database.Result) error); ok { - r0 = rf(result) + if rf, ok := ret.Get(0).(func(io.Writer, *database.Result) error); ok { + r0 = rf(wr, result) } else { r0 = ret.Error(0) } diff --git a/mocks/MermerdConfig.go b/mocks/MermerdConfig.go index a1ee122..9ef713a 100644 --- a/mocks/MermerdConfig.go +++ b/mocks/MermerdConfig.go @@ -128,6 +128,20 @@ func (_m *MermerdConfig) OutputFileName() string { return r0 } +// OutputMode provides a mock function with given fields: +func (_m *MermerdConfig) OutputMode() config.OutputModeType { + ret := _m.Called() + + var r0 config.OutputModeType + if rf, ok := ret.Get(0).(func() config.OutputModeType); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(config.OutputModeType) + } + + return r0 +} + // RelationshipLabels provides a mock function with given fields: func (_m *MermerdConfig) RelationshipLabels() []config.RelationshipLabel { ret := _m.Called() diff --git a/presentation/info.go b/presentation/info.go new file mode 100644 index 0000000..da21279 --- /dev/null +++ b/presentation/info.go @@ -0,0 +1,14 @@ +package presentation + +import ( + "github.com/KarnerTh/mermerd/config" + "github.com/fatih/color" +) + +func ShowInfo(c config.MermerdConfig, value string) { + if c.OutputMode() == config.Stdout { + return + } + + color.Blue(value) +} diff --git a/presentation/intro.go b/presentation/intro.go index 6b11636..7d4577c 100644 --- a/presentation/intro.go +++ b/presentation/intro.go @@ -3,11 +3,16 @@ package presentation import ( "fmt" + "github.com/KarnerTh/mermerd/config" "github.com/fatih/color" "github.com/spf13/viper" ) -func ShowIntro() { +func ShowIntro(c config.MermerdConfig) { + if c.OutputMode() == config.Stdout { + return + } + color.Green(fmt.Sprintf(` . ..___.__ . ..___.__ .__ |\/|[__ [__)|\/|[__ [__)| \ diff --git a/presentation/outro.go b/presentation/outro.go index cbdc931..6b0bfc8 100644 --- a/presentation/outro.go +++ b/presentation/outro.go @@ -3,10 +3,15 @@ package presentation import ( "fmt" + "github.com/KarnerTh/mermerd/config" "github.com/fatih/color" ) -func ShowSuccess(fileName string) { +func ShowSuccess(c config.MermerdConfig, fileName string) { + if c.OutputMode() == config.Stdout { + return + } + color.Green(fmt.Sprintf(` ✓ Diagram was created successfully (%s) diff --git a/readme.md b/readme.md index b9e7296..4acf92c 100644 --- a/readme.md +++ b/readme.md @@ -78,6 +78,7 @@ via `mermerd -h` --omitAttributeKeys omit the attribute keys (PK, FK, UK) --omitConstraintLabels omit the constraint labels -o, --outputFileName string output file name (default "result.mmd") + --outputMode OutputModeType output mode (file, stdout) (default file) --runConfig string run configuration (replaces global configuration) -s, --schema string schema that should be used --schemaPrefixSeparator string the separator that should be used between schema and table name (default ".") @@ -146,6 +147,7 @@ selectedTables: showAllConstraints: true encloseWithMermaidBackticks: true outputFileName: "my-db.mmd" +outputMode: stdout debug: true omitConstraintLabels: true omitAttributeKeys: true @@ -181,6 +183,9 @@ mermerd -c "postgresql://user:password@localhost:5432/yourDb" -s public # same as previous one, but use all available tables without interaction mermerd -c "postgresql://user:password@localhost:5432/yourDb" -s public --useAllTables +# same as previous one, but get the result in stdout instead of a file +mermerd -c "postgresql://user:password@localhost:5432/yourDb" -s public --useAllTables --outputMode stdout + # same as previous one, but use a list of tables without interaction mermerd -c "postgresql://user:password@localhost:5432/yourDb" -s public --selectedTables article,article_label