diff --git a/.gitignore b/.gitignore
index 98de5f9..c97db96 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,5 @@
go.work
.idea
gob
-*.sql
11.json
target
diff --git a/README.md b/README.md
index ef0d034..8db60a6 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,7 @@ flowchart TD
gob.yaml --> plugin2
gob.yaml --> plugin3
```
-You just need to tell `gob` 3W(where,when and what)
+You just need to tell `gbc` 3W(where,when and what)
1. **Where** : where to download the tool
2. **When** : when to execute to command
@@ -59,40 +59,40 @@ You just need to tell `gob` 3W(where,when and what)
## Quick Start
1. Install `gob` with below command
```shell
- go install github.com/kcmvp/gob
+ go install github.com/kcmvp/gbc
```
2. Initialize project with below command(in the project home directory)
```shell
- gob init
+ gbc init
```
-| Make some changes and comit code | execute `gob deps` |
+| Make some changes and comit code | execute `gbc deps` |
|--------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
-| | |
+| | |
## Commands
Build Commands
-- [gob init](#gob-init)
-- [gob build](#gob-build)
-- [gob clean](#gob-clean)
-- [gob test](#gob-test)
-- [gob lint](#gob-lint)
-- [gob deps](#gob-deps)
+- [gbc init](#gbc-init)
+- [gbc build](#gbc-build)
+- [gbc clean](#gbc-clean)
+- [gbc test](#gbc-test)
+- [gbc lint](#gbc-lint)
+- [gbc deps](#gbc-deps)
Plugin Commands
-- [gob plugin install](#gob-plugin-install)
-- [gob plugin list](#gob-plugin-list)
+- [gbc plugin install](#gbc-plugin-install)
+- [gbc plugin list](#gbc-plugin-list)
Setup Commands
-- [gob setup version](#gob-setup-version)
+- [gbc setup version](#gbc-setup-version)
-### gob init
+### gbc init
```shell
-gob init
+gbc init
```
-Initialize gob for the project, it will do following initializations
+Initialize gbc for the project, it will do following initializations
1. generate file `gob.yaml`
2. generate file `.golangci.yaml`, which is the configuration for [golangci-lint](https://github.com/golangci/golangci-lint)
3. setup `git hooks` if project in the source control.
@@ -113,7 +113,7 @@ exec:
- test
plugins:
golangci-lint:
- alias: lint #When : when issue `gob lint`
+ alias: lint #When : when issue `gbc lint`
args: run ./... #What: execute `golangci-lint run ./...`
url: github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 #Where: where to download the plugin
gotestsum:
@@ -121,41 +121,41 @@ plugins:
args: --format testname -- -coverprofile=target/cover.out ./...
url: gotest.tools/gotestsum@v1.11.0
```
-in most cases you don't need to edit the configuration manually. you can achieve this by [plugin commands](#gob-plugin-install)
+in most cases you don't need to edit the configuration manually. you can achieve this by [plugin commands](#gbc-plugin-install)
-### gob build
+### gbc build
```shell
-gob build
+gbc build
```
This command would build all the candidate binaries(main methods in main packages) to the `target` folder.
1. Final binary name is same as go source file name which contains `main method`
2. Would fail if there are same name go main surce file
-### gob clean
+### gbc clean
```shell
-gob clean
+gbc clean
```
This command would clean `target` folder
-### gob test
+### gbc test
```shell
-gob test
+gbc test
```
This command would run all tests for the project and generate coverage report at `target/cover.html`
-### gob lint
+### gbc lint
```shell
-gob lint
+gbc lint
```
Run `golangci-lint` against project based on the configuration, a report named `target/lint.log` will be generated if there are any violations
-### gob deps
+### gbc deps
```shell
-gob deps
+gbc deps
```
List project dependencies tree and indicate there are updates for a specific dependency
-### gob plugin install
+### gbc plugin install
```shell
-gob plugin install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 lint run ./...
+gbc plugin install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 lint run ./...
```
It is an advanced version of `go install`, which supports multi-version.(eg:`golangci-lint-v1.55.2`, `golangci-lint-v1.55.1`)
1. Install the versioned tool(just the same as `go install`)
diff --git a/application.yaml b/application.yaml
new file mode 100644
index 0000000..92284e1
--- /dev/null
+++ b/application.yaml
@@ -0,0 +1,19 @@
+# "postgres://username:password@localhost:5432/database_name"
+# username:password@protocol(address)/dbname?param=value
+datasource:
+ ds1:
+ driver: sqlite3
+ user: usera
+ password: passwd1
+ Host: localhost
+ url: file:test1.db?cache=shared&mode=memory
+ ds2:
+ driver: sqlite3
+ user: userb
+ password: passwd2
+ url: file:test2.db?cache=shared&mode=memory
+ scripts:
+ - sqlite3-schema.sql
+
+
+
diff --git a/application_test.yaml b/application_test.yaml
new file mode 100644
index 0000000..e7930d4
--- /dev/null
+++ b/application_test.yaml
@@ -0,0 +1,12 @@
+# "postgres://username:password@localhost:5432/database_name"
+# username:password@protocol(address)/dbname?param=value
+datasource:
+ ds1:
+ driver: sqlite3
+ user: abc
+ password: 123
+ url: file:test3.db?cache=shared&mode=memory
+ scripts:
+ - sqlite3-schema.sql
+ - sqlite3-init.sql
+
diff --git a/boot/application.go b/boot/application.go
new file mode 100644
index 0000000..09fb0ba
--- /dev/null
+++ b/boot/application.go
@@ -0,0 +1,57 @@
+package boot
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "github.com/kcmvp/gob/internal"
+ "github.com/kcmvp/gob/utils"
+
+ "github.com/samber/do/v2"
+ "github.com/spf13/viper"
+)
+
+const (
+ DefaultCfg = "application"
+)
+
+var (
+ cfg *viper.Viper
+ once sync.Once
+)
+
+func RootDir() string {
+ return internal.RootDir
+}
+
+func Container() *do.RootScope {
+ return internal.Container
+}
+
+func InitApp() {
+ InitAppWith(DefaultCfg)
+}
+
+func InitAppWith(cfgName string) {
+ if cfg == nil {
+ once.Do(func() {
+ cfg = viper.New()
+ cfg.SetConfigName(cfgName) // name of cfg file (without extension)
+ cfg.SetConfigType("yaml") // REQUIRED if the cfg file does not have the extension in the name
+ cfg.AddConfigPath(internal.RootDir) // optionally look for cfg in the working directory
+ if err := cfg.ReadInConfig(); err != nil { // Find and read the cfg file
+ panic(fmt.Errorf("fatal error cfg file: %w", err))
+ }
+ if test, _ := utils.TestCaller(); test {
+ if testCfg, err := os.Open(filepath.Join(internal.RootDir, fmt.Sprintf("%s_test.yaml", cfgName))); err == nil {
+ if err = cfg.MergeConfig(testCfg); err != nil {
+ panic(fmt.Errorf("failed to merge test configuration file: %w", err))
+ }
+ }
+ }
+ setupDb()
+ })
+ }
+}
diff --git a/boot/db.go b/boot/db.go
new file mode 100644
index 0000000..5211159
--- /dev/null
+++ b/boot/db.go
@@ -0,0 +1,86 @@
+package boot
+
+import (
+ "database/sql"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/kcmvp/gob/internal"
+
+ //"github.com/kcmvp/gob/internal"
+
+ "github.com/samber/do/v2"
+ typetostring "github.com/samber/go-type-to-string"
+ "github.com/samber/lo"
+)
+
+const (
+ DSKey = "datasource"
+ UserKey = "${user}"
+ PasswordKey = "${password}"
+ HostKey = "${host}"
+ DefaultDS = "DefaultDS"
+)
+
+type dataSource struct {
+ Driver string `mapstructure:"driver"`
+ User string `mapstructure:"user"`
+ Password string `mapstructure:"password"`
+ Host string `mapstructure:"host"`
+ URL string `mapstructure:"url"`
+ Scripts []string `mapstructure:"scripts"`
+}
+
+func (ds dataSource) DSN() string {
+ dsn := strings.ReplaceAll(ds.URL, UserKey, ds.User)
+ dsn = strings.ReplaceAll(dsn, PasswordKey, ds.Password)
+ return strings.ReplaceAll(dsn, HostKey, ds.Host)
+}
+
+func dsMap() map[string]dataSource {
+ // single data source
+ if v := cfg.Get(fmt.Sprintf("%s.%s", DSKey, "driver")); v != nil {
+ var ds dataSource
+ if err := cfg.UnmarshalKey(DSKey, &ds); err != nil {
+ panic(fmt.Errorf("failed parse datasource: %w", err))
+ }
+ return map[string]dataSource{DefaultDS: ds}
+ // multiple data sources
+ } else if v = cfg.Get(DSKey); v != nil {
+ dss := v.(map[string]any)
+ return lo.MapValues(dss, func(_ any, key string) dataSource {
+ var ds dataSource
+ key = fmt.Sprintf("%s.%s", DSKey, key)
+ if err := cfg.UnmarshalKey(key, &ds); err != nil {
+ panic(fmt.Errorf("failed parse datasource: %w", err))
+ }
+ return ds
+ })
+ }
+ return map[string]dataSource{}
+}
+
+func setupDb() {
+ for name, ds := range dsMap() {
+ if db, err := sql.Open(ds.Driver, ds.DSN()); err == nil {
+ if err = db.Ping(); err != nil {
+ _ = db.Close()
+ panic(fmt.Errorf("failed to initialize %s: %w", name, err))
+ }
+ lo.ForEach(ds.Scripts, func(script string, _ int) {
+ if data, err := os.ReadFile(filepath.Join(internal.RootDir, script)); err == nil {
+ if _, err = db.Exec(string(data)); err != nil {
+ panic(fmt.Errorf("failed to execute %s: %w", script, err))
+ }
+ } else {
+ panic(fmt.Errorf("failed to read %s: %w", script, err))
+ }
+ })
+ do.ProvideNamedValue[*sql.DB](internal.Container, fmt.Sprintf("%s_%s", name, typetostring.GetType[*sql.DB]()), db)
+ } else {
+ panic(fmt.Errorf("failed to connect to datasource %s: %w", name, err))
+ }
+ }
+}
diff --git a/boot/db_test.go b/boot/db_test.go
new file mode 100644
index 0000000..3ce8d82
--- /dev/null
+++ b/boot/db_test.go
@@ -0,0 +1,42 @@
+package boot
+
+import (
+ "database/sql"
+ "fmt"
+ "github.com/kcmvp/gob/internal"
+ _ "github.com/mattn/go-sqlite3"
+ "github.com/samber/do/v2"
+ typetostring "github.com/samber/go-type-to-string"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
+ "testing"
+)
+
+type DBTestSuite struct {
+ suite.Suite
+}
+
+func TestDBTestSuite(t *testing.T) {
+ suite.Run(t, &DBTestSuite{})
+}
+
+func (dbs *DBTestSuite) SetupSuite() {
+ InitApp()
+}
+
+func (dbs *DBTestSuite) TestMultipleDB() {
+ ds := dsMap()
+ assert.Equal(dbs.T(), 2, len(ds))
+ db := do.MustInvokeNamed[*sql.DB](internal.Container, fmt.Sprintf("%s_%s", "ds1", typetostring.GetType[*sql.DB]()))
+ assert.NotNil(dbs.T(), db)
+ rs, err := db.Exec("select * from Product")
+ assert.NoError(dbs.T(), err)
+ cnt, _ := rs.RowsAffected()
+ assert.Equal(dbs.T(), int64(2), cnt)
+ db = do.MustInvokeNamed[*sql.DB](internal.Container, fmt.Sprintf("%s_%s", "ds2", typetostring.GetType[*sql.DB]()))
+ assert.NotNil(dbs.T(), db)
+ rs, err = db.Exec("select * from Product")
+ assert.NoError(dbs.T(), err)
+ cnt, _ = rs.RowsAffected()
+ assert.Equal(dbs.T(), int64(0), cnt)
+}
diff --git a/cmd/builder.go b/cmd/builder.go
deleted file mode 100644
index e138644..0000000
--- a/cmd/builder.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Package cmd /*
-package cmd
-
-import (
- "context"
- "embed"
- "errors"
- "fmt"
- "github.com/fatih/color"
- "github.com/kcmvp/gob/internal"
- "github.com/samber/lo"
- "github.com/spf13/cobra"
- "os"
-)
-
-//go:embed resources/*
-var resources embed.FS
-
-const resourceDir = "resources"
-
-// builderCmd represents the base command when called without any subcommands
-var builderCmd = &cobra.Command{
- Use: "gob",
- Short: "Go project boot",
- Long: `Go pluggable toolchain and best practice`,
- ValidArgs: validBuilderArgs(),
- Args: func(cmd *cobra.Command, args []string) error {
- if !lo.Every(validBuilderArgs(), args) {
- return fmt.Errorf(color.RedString("valid args are : %s", validBuilderArgs()))
- }
- if err := cobra.MinimumNArgs(1)(cmd, args); err != nil {
- return fmt.Errorf(color.RedString(err.Error()))
- }
- return nil
- },
- PersistentPreRun: func(cmd *cobra.Command, args []string) {
- internal.CurProject().Validate()
- },
- RunE: func(cmd *cobra.Command, args []string) error {
- for _, arg := range lo.Uniq(args) {
- if err := execute(cmd, arg); err != nil {
- return errors.New(color.RedString("%s \n", err.Error()))
- }
- }
- return nil
- },
-}
-
-func Execute() error {
- currentDir, _ := os.Getwd()
- if internal.CurProject().Root() != currentDir {
- return fmt.Errorf(color.RedString("Please execute the command in the project root dir"))
- }
- ctx := context.Background()
- if err := builderCmd.ExecuteContext(ctx); err != nil {
- return fmt.Errorf(color.RedString(err.Error()))
- }
- return nil
-}
-
-func init() {
- builderCmd.SetErrPrefix(color.RedString("Error:"))
- builderCmd.SetFlagErrorFunc(func(command *cobra.Command, err error) error {
- return lo.IfF(err != nil, func() error {
- return fmt.Errorf(color.RedString(err.Error()))
- }).Else(nil)
- })
- builderCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}
diff --git a/cmd/builder_test.go b/cmd/builder_test.go
deleted file mode 100644
index 3d46a01..0000000
--- a/cmd/builder_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-package cmd
-
-import (
- "github.com/fatih/color"
- "github.com/kcmvp/gob/internal"
- "github.com/samber/lo"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/suite"
- "os"
- "path/filepath"
- "strings"
- "testing"
-)
-
-type BuilderTestSuit struct {
- suite.Suite
-}
-
-func TestBuilderTestSuit(t *testing.T) {
- suite.Run(t, &BuilderTestSuit{})
-}
-
-func (suite *BuilderTestSuit) TearDownSuite() {
- TearDownSuite("cmd_builder_test_")
-}
-
-func (suite *BuilderTestSuit) TestValidArgs() {
- assert.Equal(suite.T(), []string{"build", "clean", "test", "lint"}, builderCmd.ValidArgs)
-}
-
-func (suite *BuilderTestSuit) TestArgs() {
- tests := []struct {
- name string
- args []string
- wantErr bool
- }{
- {
- name: "not in valid args list",
- args: []string{"def"},
- wantErr: true,
- },
- {
- name: "partial valid args",
- args: []string{"test", "def"},
- wantErr: true,
- },
- {
- name: "no args",
- args: []string{},
- wantErr: true,
- },
- {
- name: "empty args",
- args: []string{""},
- wantErr: true,
- },
- {
- name: "positive case",
- args: []string{"clean", "test"},
- wantErr: false,
- },
- }
- for _, test := range tests {
- err := builderCmd.Args(nil, test.args)
- assert.True(suite.T(), test.wantErr == (err != nil))
- }
-
-}
-
-func (suite *BuilderTestSuit) TestExecute() {
- builderCmd.SetArgs([]string{"cd"})
- os.Chdir(internal.CurProject().Root())
- err := Execute()
- assert.Equal(suite.T(), "valid args are : [build clean test lint]", err.Error())
- os.Chdir(internal.GoPath())
- err = Execute()
- assert.Equal(suite.T(), "Please execute the command in the project root dir", err.Error())
- builderCmd.SetArgs([]string{"build"})
- os.Chdir(internal.CurProject().Root())
- err = Execute()
- assert.NoError(suite.T(), err)
-}
-
-func (suite *BuilderTestSuit) TestBuild() {
- tests := []struct {
- name string
- args []string
- wantErr bool
- }{
- {
- name: "invalid",
- args: []string{"cd"},
- wantErr: true,
- },
- {
- name: "valid",
- args: []string{"build"},
- wantErr: false,
- },
- }
- for _, test := range tests {
- builderCmd.SetArgs(test.args)
- err := execute(builderCmd, test.args[0])
- assert.True(suite.T(), test.wantErr == (err != nil))
- if test.wantErr {
- assert.True(suite.T(), strings.Contains(err.Error(), color.RedString("")))
- }
- }
-}
-
-func (suite *BuilderTestSuit) TestPersistentPreRun() {
- builderCmd.PersistentPreRun(nil, nil)
- hooks := lo.MapToSlice(internal.HookScripts(), func(key string, _ string) string {
- return key
- })
- for _, hook := range hooks {
- _, err := os.Stat(filepath.Join(internal.CurProject().HookDir(), hook))
- assert.Error(suite.T(), err)
- }
- internal.CurProject().SetupHooks(true)
- for _, hook := range hooks {
- _, err := os.Stat(filepath.Join(internal.CurProject().HookDir(), hook))
- assert.NoError(suite.T(), err)
- }
-}
-
-func (suite *BuilderTestSuit) TestBuiltinPlugins() {
- plugins := builtinPlugins()
- assert.Equal(suite.T(), 2, len(plugins))
- plugin, ok := lo.Find(plugins, func(plugin internal.Plugin) bool {
- return plugin.Url == "github.com/golangci/golangci-lint/cmd/golangci-lint"
- })
- assert.True(suite.T(), ok)
- assert.Equal(suite.T(), "v1.56.2", plugin.Version())
- assert.Equal(suite.T(), "golangci-lint", plugin.Name())
- assert.Equal(suite.T(), "github.com/golangci/golangci-lint", plugin.Module())
- assert.Equal(suite.T(), "lint", plugin.Alias)
- plugin, ok = lo.Find(plugins, func(plugin internal.Plugin) bool {
- return plugin.Url == "gotest.tools/gotestsum"
- })
- assert.True(suite.T(), ok)
- assert.Equal(suite.T(), "v1.11.0", plugin.Version())
- assert.Equal(suite.T(), "gotestsum", plugin.Name())
- assert.Equal(suite.T(), "gotest.tools/gotestsum", plugin.Module())
- assert.Equal(suite.T(), "test", plugin.Alias)
-}
-
-func (suite *BuilderTestSuit) TestRunE() {
- target := internal.CurProject().Target()
- err := builderCmd.RunE(builderCmd, []string{"build"})
- assert.NoError(suite.T(), err)
- _, err = os.Stat(filepath.Join(target, lo.If(internal.Windows(), "gob.exe").Else("gob")))
- assert.NoError(suite.T(), err, "binary should be generated")
- err = builderCmd.RunE(builderCmd, []string{"build", "clean"})
- assert.NoError(suite.T(), err)
- assert.NoFileExistsf(suite.T(), filepath.Join(target, lo.If(internal.Windows(), "gob.exe").Else("gob")), "binary should be deleted")
- err = builderCmd.RunE(builderCmd, []string{"def"})
- assert.Errorf(suite.T(), err, "can not find the command def")
-}
diff --git a/cmd/setup_test.go b/cmd/setup_test.go
deleted file mode 100644
index 631117e..0000000
--- a/cmd/setup_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package cmd
-
-import (
- "github.com/kcmvp/gob/internal"
- "github.com/stretchr/testify/assert"
- "os"
- "path/filepath"
- "testing"
-)
-
-func TestValidSetupArgs(t *testing.T) {
- assert.Equal(t, setupCmd.ValidArgs, []string{"version"})
-}
-
-func TestSetupVersion(t *testing.T) {
- version := filepath.Join(internal.CurProject().Root(), "infra", "version.go")
- os.Remove(version)
- _, err := os.Stat(version)
- assert.Error(t, err)
- builderCmd.SetArgs([]string{"setup", "version"})
- err = builderCmd.Execute()
- assert.NoError(t, err)
- _, err = os.Stat(version)
- assert.NoError(t, err)
-}
diff --git a/dbx/clause.go b/dbx/clause.go
new file mode 100644
index 0000000..50c9e81
--- /dev/null
+++ b/dbx/clause.go
@@ -0,0 +1,108 @@
+package dbx
+
+import (
+ "fmt"
+
+ "github.com/kcmvp/gob/internal"
+ "github.com/samber/lo"
+)
+
+type IEntity interface {
+ Table() string
+}
+
+type Key interface {
+ string | int64
+}
+
+type SQL interface {
+ SQLStr() string
+}
+
+type Order string
+
+const (
+ ASC Order = "ASC"
+ DESC Order = "DESC"
+)
+
+type Attr internal.Mapper
+
+func (a Attr) SQLStr() string {
+ return a.B
+}
+
+type Set lo.Tuple2[Attr, any]
+
+// Criteria attribute predicate
+type Criteria struct {
+ expression string
+}
+
+func (criteria Criteria) SQLStr() string {
+ return criteria.expression
+}
+
+func (criteria Criteria) Or(rp Criteria) Criteria {
+ return Criteria{
+ expression: fmt.Sprintf("(%s or %s)", criteria.expression, rp.SQLStr()),
+ }
+}
+
+func (criteria Criteria) Add(rp Criteria) Criteria {
+ return Criteria{
+ expression: fmt.Sprintf("(%s and %s)", criteria.expression, rp.SQLStr()),
+ }
+}
+
+func LT(attr Attr) Criteria {
+ return Criteria{expression: fmt.Sprintf("%s < ?", attr.SQLStr())}
+}
+
+func LTE(attr Attr) Criteria {
+ return Criteria{expression: fmt.Sprintf("%s <= ?", attr.SQLStr())}
+}
+
+func GT(attr Attr) Criteria {
+ return Criteria{expression: fmt.Sprintf("%s > ?", attr.SQLStr())}
+}
+
+func GTE(attr Attr) Criteria {
+ return Criteria{expression: fmt.Sprintf("%s >= ?", attr.SQLStr())}
+}
+
+func Null(attr Attr) Criteria {
+ return Criteria{expression: fmt.Sprintf("%s is null", attr.SQLStr())}
+}
+
+func NotNull(attr Attr) Criteria {
+ return Criteria{expression: fmt.Sprintf("%s is not null", attr.SQLStr())}
+}
+
+func Like(attr Attr) Criteria {
+ return Criteria{expression: fmt.Sprintf("%s like '%%?%%'", attr.SQLStr())}
+}
+
+func NotLike(attr Attr) Criteria {
+ return Criteria{expression: fmt.Sprintf("%s not like '%%?%%'", attr.SQLStr())}
+}
+
+func Prefix(attr Attr) Criteria {
+ return Criteria{expression: fmt.Sprintf("%s like '?%%'", attr.SQLStr())}
+}
+
+func Suffix[E IEntity](attr Attr) Criteria {
+ return Criteria{expression: fmt.Sprintf("%s like '%%?'", attr.SQLStr())}
+}
+
+type OrderBy lo.Tuple2[Attr, Order]
+
+func (orderBy OrderBy) SQLStr() string {
+ return fmt.Sprintf("%s %s", orderBy.A.SQLStr(), orderBy.B)
+}
+
+var (
+ _ SQL = (*Attr)(nil)
+ _ SQL = (*Criteria)(nil)
+ _ SQL = (*OrderBy)(nil)
+)
diff --git a/dbx/clause_test.go b/dbx/clause_test.go
new file mode 100644
index 0000000..f7e054a
--- /dev/null
+++ b/dbx/clause_test.go
@@ -0,0 +1,51 @@
+package dbx
+
+import (
+ "database/sql"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
+ "testing"
+ "time"
+)
+
+type Base struct {
+ CreatedAt time.Time `ds:"autoUpdateTime"`
+ CreatedBy string `ds:"createdBy"`
+ UpdatedAt time.Time `ds:"autoCreateTime"`
+ UpdatedBy string `ds:"updatedBy"`
+}
+
+type Product struct {
+ Base
+ Id string `ds:"pk;"`
+ Name string `ds:"column=name"`
+ FullName string `ds:"ignore"`
+ Grade sql.NullInt32
+ Address sql.NullString
+ ProductDate time.Time
+ comment string
+}
+
+func (p Product) Table() string {
+ return "product"
+}
+
+func TestName(t *testing.T) {
+ //assert.Equal(t, 1, 1)
+}
+
+type BuilderTestSuit struct {
+ //builder *SqlBuilder
+ suite.Suite
+}
+
+func TestBuilderSuite(t *testing.T) {
+ suite.Run(t, &BuilderTestSuit{})
+}
+
+// func (suite *BuilderTestSuit) SetupSuite() {
+// //suite.builder = do.MustInvoke[*SqlBuilder](boot.Container())
+// }
+func (suite *BuilderTestSuit) TestHappyFlow() {
+ assert.Equal(suite.T(), 1, 1)
+}
diff --git a/dbx/dbx.go b/dbx/dbx.go
new file mode 100644
index 0000000..f00286e
--- /dev/null
+++ b/dbx/dbx.go
@@ -0,0 +1,115 @@
+// nolint
+package dbx
+
+import (
+ "context"
+ "database/sql"
+ "strings"
+
+ "github.com/kcmvp/gob/internal"
+ "github.com/samber/do/v2"
+ "github.com/samber/lo"
+)
+
+const DefaultDS = "defaultDS"
+
+// DBX database adapter
+type DBX interface {
+ ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
+ PrepareContext(context.Context, string) (*sql.Stmt, error)
+ QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
+ QueryRowContext(context.Context, string, ...interface{}) *sql.Row
+ Close() error
+}
+
+type Hook func(sql string) string
+
+type DBXImpl struct { //nolint
+ ds DBX
+ beforeQueryHooks []Hook
+ beforeExecHooks []Hook
+}
+
+func (dbxImpl *DBXImpl) PoolSize() int32 {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (dbxImpl *DBXImpl) TotalConns() int32 {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (dbxImpl *DBXImpl) IdleConns() int32 {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (dbxImpl *DBXImpl) MaxIdleDestroyCount() int32 {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (dbxImpl *DBXImpl) Close() error {
+ return dbxImpl.ds.Close()
+}
+
+func (dbxImpl *DBXImpl) Shutdown() {
+ dbxImpl.Close()
+}
+
+func (dbxImpl *DBXImpl) HealthCheck(_ context.Context) error {
+ panic("print pool status")
+}
+
+func (dbxImpl *DBXImpl) PrepareContext(_ context.Context, s string) (*sql.Stmt, error) {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (dbxImpl *DBXImpl) ExecContext(_ context.Context, s string, i ...interface{}) (sql.Result, error) {
+ // TODO implement me
+ for _, hook := range dbxImpl.beforeExecHooks {
+ s = hook(s)
+ }
+ panic("implement me")
+}
+
+func (dbxImpl *DBXImpl) QueryContext(ctx context.Context, s string, i ...interface{}) (*sql.Rows, error) {
+ for _, hook := range dbxImpl.beforeQueryHooks {
+ s = hook(s)
+ }
+ panic("implement me")
+}
+
+func (dbxImpl *DBXImpl) QueryRowContext(ctx context.Context, s string, i ...interface{}) *sql.Row {
+ for _, hook := range dbxImpl.beforeQueryHooks {
+ s = hook(s)
+ }
+ panic("implement me")
+}
+
+func (dbxImpl *DBXImpl) AddQueryHook(hook Hook) {
+ dbxImpl.beforeQueryHooks = append(dbxImpl.beforeQueryHooks, hook)
+}
+
+func (dbxImpl *DBXImpl) AddExecHooks(hook Hook) {
+ dbxImpl.beforeExecHooks = append(dbxImpl.beforeExecHooks, hook)
+}
+
+func init() {
+ lo.ForEach(internal.Container.ListProvidedServices(), func(item do.EdgeService, _ int) {
+ if strings.HasSuffix(item.Service, "_*database/sql.DB") {
+ dsName := strings.TrimSuffix(item.Service, "_*database/sql.DB")
+ do.ProvideNamed[DBX](internal.Container, dsName, func(injector do.Injector) (DBX, error) {
+ return &DBXImpl{ds: do.MustInvokeNamed[*sql.DB](injector, item.Service)}, nil
+ })
+ }
+ })
+}
+
+var (
+ _ DBX = (*DBXImpl)(nil)
+ _ do.HealthcheckerWithContext = (*DBXImpl)(nil)
+ _ do.Shutdowner = (*DBXImpl)(nil)
+)
diff --git a/dbx/query.go b/dbx/query.go
new file mode 100644
index 0000000..e02031c
--- /dev/null
+++ b/dbx/query.go
@@ -0,0 +1,51 @@
+// nolint
+package dbx
+
+import (
+ "github.com/samber/lo"
+)
+
+type Joint lo.Tuple3[Attr, Attr, string]
+
+func (joint Joint) String() string {
+ return ""
+}
+
+type Result struct {
+ raw map[string]any
+ attrs []Attr //nolint
+}
+
+func (result Result) Get(attr Attr) (any, error) {
+ return nil, nil
+}
+
+func (result Result) Count() int {
+ return len(result.raw)
+}
+
+type Query struct {
+ jointStr string
+}
+
+func Select(attrs []Attr) Query {
+ //@todo validate
+ return Query{}
+}
+
+func (query Query) WithJoin(joints []Joint) Query {
+ //@todo validate
+ return Query{}
+}
+
+func (query Query) OrderBy(orders []OrderBy) Query {
+ return Query{}
+}
+
+func (query Query) Where(criteria Criteria, values func() []any) Query {
+ return Query{}
+}
+
+func (query Query) Rows() Result {
+ return Result{}
+}
diff --git a/dbx/repository.go b/dbx/repository.go
new file mode 100644
index 0000000..a28d917
--- /dev/null
+++ b/dbx/repository.go
@@ -0,0 +1,88 @@
+// nolint
+package dbx
+
+import (
+ "fmt"
+
+ "github.com/kcmvp/gob/internal"
+ "github.com/samber/do/v2"
+)
+
+type Repository[E IEntity, K Key] interface {
+ Insert(entity E) (int64, error)
+ BatchInsert(entities []E) (int64, error)
+ Delete(Key K) (int64, error)
+ Update(set []Set, criteria Criteria, values func() []any) (int64, error)
+ Find(Key K) (E, error)
+ FindBy(criteria Criteria, values func() []any) (E, error)
+ DeleteBy(criteria Criteria, values func() []any) (int64, error)
+ SearchBy(criteria Criteria, values func() []any, orderBy ...OrderBy) ([]E, error)
+}
+
+type MustBeStructError struct {
+ msg string
+}
+
+func (e MustBeStructError) Error() string {
+ return fmt.Sprintf("must be struct %s", e.msg)
+}
+
+// defaultRepository default Repository implementation
+type defaultRepository[E IEntity, K Key] struct {
+ zeroK K //nolint
+ zeroE E //nolint
+ // sqlBuilder *SqlBuilder
+ dbx DBX
+}
+
+func (d defaultRepository[E, K]) Insert(entity E) (int64, error) {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (d defaultRepository[E, K]) BatchInsert(entities []E) (int64, error) {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (d defaultRepository[E, K]) Delete(Key K) (int64, error) {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (d defaultRepository[E, K]) Find(Key K) (E, error) {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (d defaultRepository[E, K]) FindBy(criteria Criteria, parameters func() []any) (E, error) {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (d defaultRepository[E, K]) DeleteBy(criteria Criteria, parameters func() []any) (int64, error) {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (d defaultRepository[E, K]) Update(set []Set, criteria Criteria, parameters func() []any) (int64, error) {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (d defaultRepository[E, K]) SearchBy(criteria Criteria, parameters func() []any, orderBy ...OrderBy) ([]E, error) {
+ // TODO implement me
+ panic("implement me")
+}
+
+func NewRepository[E IEntity, K Key]() Repository[E, K] {
+ return NewRepositoryWithDS[E, K](DefaultDS)
+}
+
+func NewRepositoryWithDS[E IEntity, K Key](dsName string) Repository[E, K] {
+ repo := &defaultRepository[E, K]{
+ dbx: do.MustInvokeNamed[DBX](internal.Container, dsName),
+ }
+ //@todo validate with Attr
+ return repo
+}
diff --git a/internal/hook.go b/gbc/artifact/hook.go
similarity index 77%
rename from internal/hook.go
rename to gbc/artifact/hook.go
index 2091268..e56e30f 100644
--- a/internal/hook.go
+++ b/gbc/artifact/hook.go
@@ -1,4 +1,4 @@
-package internal
+package artifact
import (
"bufio"
@@ -10,6 +10,7 @@ import (
)
const (
+ command = "gbc"
execCfgKey = "exec"
//hook script name
CommitMsg = "commit-msg"
@@ -19,9 +20,9 @@ const (
func HookScripts() map[string]string {
return map[string]string{
- CommitMsg: fmt.Sprintf("gob exec %s $1", CommitMsg),
- PreCommit: fmt.Sprintf("gob exec %s", PreCommit),
- PrePush: fmt.Sprintf("gob exec %s $1 $2", PrePush),
+ CommitMsg: fmt.Sprintf("%s exec %s $1", command, CommitMsg),
+ PreCommit: fmt.Sprintf("%s exec %s", command, PreCommit),
+ PrePush: fmt.Sprintf("%s exec %s $1 $2", command, PrePush),
}
}
@@ -58,7 +59,7 @@ func (project *Project) Executions() []Execution {
}
// SetupHooks setup git local hooks for project. force means always update gob.yaml
-func (project *Project) SetupHooks(force bool) {
+func (project *Project) SetupHooks(force bool) error {
if force {
hook := map[string]any{
fmt.Sprintf("%s.%s", execCfgKey, CommitMsg): "^#[0-9]+:\\s*.{10,}$",
@@ -70,10 +71,11 @@ func (project *Project) SetupHooks(force bool) {
}
}
if !InGit() {
- color.Yellow("project is not in the source control")
- return
+ return fmt.Errorf(color.RedString("project is not in the source control"))
+ }
+ if err := project.config().ReadInConfig(); err != nil {
+ return err
}
- _ = project.config().ReadInConfig()
gitHook := CurProject().GitHook()
var hooks []string
if len(gitHook.CommitMsg) > 0 {
@@ -88,9 +90,10 @@ func (project *Project) SetupHooks(force bool) {
shell := lo.IfF(Windows(), func() string {
return "#!/usr/bin/env pwsh\n"
}).Else("#!/bin/sh\n")
+ hookDir := CurProject().HookDir()
for name, script := range HookScripts() {
if lo.Contains(hooks, name) || force {
- msgHook, _ := os.OpenFile(filepath.Join(CurProject().HookDir(), name), os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm)
+ msgHook, _ := os.OpenFile(filepath.Join(hookDir, name), os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm)
writer := bufio.NewWriter(msgHook)
writer.WriteString(shell)
writer.WriteString("\n")
@@ -98,7 +101,8 @@ func (project *Project) SetupHooks(force bool) {
writer.Flush()
msgHook.Close()
} else {
- os.Remove(filepath.Join(CurProject().HookDir(), name))
+ os.Remove(filepath.Join(hookDir, name))
}
}
+ return nil
}
diff --git a/internal/hook_test.go b/gbc/artifact/hook_test.go
similarity index 91%
rename from internal/hook_test.go
rename to gbc/artifact/hook_test.go
index db83e5f..2ff715a 100644
--- a/internal/hook_test.go
+++ b/gbc/artifact/hook_test.go
@@ -1,6 +1,7 @@
-package internal
+package artifact
import (
+ "github.com/kcmvp/gob/utils"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
@@ -31,7 +32,8 @@ func TearDownSuite(prefix string) {
}
func (suite *GitHookTestSuite) TearDownSuite() {
- TearDownSuite("internal_hook_test_")
+ _, method := utils.TestCaller()
+ TearDownSuite(strings.Join(lo.DropRight(strings.Split(method, "_"), 1), "_"))
}
func TestGitHookSuite(t *testing.T) {
diff --git a/internal/multiple_writer.go b/gbc/artifact/multiple_writer.go
similarity index 99%
rename from internal/multiple_writer.go
rename to gbc/artifact/multiple_writer.go
index b49bf82..44ed37a 100644
--- a/internal/multiple_writer.go
+++ b/gbc/artifact/multiple_writer.go
@@ -1,4 +1,4 @@
-package internal
+package artifact
import (
"bufio"
diff --git a/internal/plugin.go b/gbc/artifact/plugin.go
similarity index 91%
rename from internal/plugin.go
rename to gbc/artifact/plugin.go
index bd01ed3..3814fe6 100644
--- a/internal/plugin.go
+++ b/gbc/artifact/plugin.go
@@ -1,4 +1,4 @@
-package internal
+package artifact
import (
"encoding/json"
@@ -17,13 +17,14 @@ import (
const modulePattern = `^[^@]+@?[^@\s]+$`
type Plugin struct {
- Alias string `json:"alias" mapstructure:"alias"`
- Args string `json:"args" mapstructure:"args"`
- Url string `json:"url" mapstructure:"url"` //nolint
- Config string `json:"config" mapstructure:"config"`
- version string
- name string
- module string
+ Alias string `json:"alias" mapstructure:"alias"`
+ Args string `json:"args" mapstructure:"args"`
+ Url string `json:"url" mapstructure:"url"` //nolint
+ Config string `json:"config" mapstructure:"config"`
+ Description string `json:"description" mapstructure:"description"`
+ version string
+ name string
+ module string
}
func (plugin *Plugin) init() error {
diff --git a/internal/plugin_test.go b/gbc/artifact/plugin_test.go
similarity index 91%
rename from internal/plugin_test.go
rename to gbc/artifact/plugin_test.go
index 72ff664..a91fb40 100644
--- a/internal/plugin_test.go
+++ b/gbc/artifact/plugin_test.go
@@ -1,14 +1,16 @@
-package internal
+package artifact
import (
"encoding/json"
"fmt"
+ "github.com/kcmvp/gob/utils"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/tidwall/gjson"
"os"
"path/filepath"
+ "strings"
"testing"
)
@@ -17,7 +19,8 @@ type InternalPluginTestSuit struct {
}
func (suite *InternalPluginTestSuit) TearDownSuite() {
- TearDownSuite("internal_plugin_test_")
+ _, method := utils.TestCaller()
+ TearDownSuite(strings.Trim(method, "TearDownSuite"))
}
func TestInternalPluginSuite(t *testing.T) {
@@ -42,7 +45,7 @@ func (suite *InternalPluginTestSuit) TestNewPlugin() {
url: "github.com/golangci/golangci-lint/cmd/golangci-lint",
module: "github.com/golangci/golangci-lint",
logName: "golangci-lint",
- binary: "golangci-lint-v1.56.2",
+ binary: "golangci-lint-v1.57.2",
wantErr: false,
},
{
@@ -50,7 +53,7 @@ func (suite *InternalPluginTestSuit) TestNewPlugin() {
url: "github.com/golangci/golangci-lint/cmd/golangci-lint@latest",
module: "github.com/golangci/golangci-lint",
logName: "golangci-lint",
- binary: "golangci-lint-v1.56.2",
+ binary: "golangci-lint-v1.57.2",
wantErr: false,
},
{
@@ -102,7 +105,7 @@ func (suite *InternalPluginTestSuit) TestNewPlugin() {
assert.True(t, test.wantErr == (err != nil))
if !test.wantErr {
assert.Equal(t, test.module, plugin.module)
- assert.True(t, lo.Contains([]string{"v1.56.2", "v1.1.1", "v1.11.0"}, plugin.Version()))
+ assert.True(t, lo.Contains([]string{"v1.57.2", "v1.1.1", "v1.11.0"}, plugin.Version()))
}
})
}
@@ -113,7 +116,7 @@ func (suite *InternalPluginTestSuit) TestUnmarshalJSON() {
defer func() {
os.RemoveAll(gopath)
}()
- data, _ := os.ReadFile(filepath.Join(CurProject().Root(), "cmd", "resources", "config.json"))
+ data, _ := os.ReadFile(filepath.Join(CurProject().Root(), "gbc", "cmd", "resources", "config.json"))
v := gjson.GetBytes(data, "plugins")
var plugins []Plugin
err := json.Unmarshal([]byte(v.Raw), &plugins)
@@ -124,7 +127,7 @@ func (suite *InternalPluginTestSuit) TestUnmarshalJSON() {
return plugin.Url == "github.com/golangci/golangci-lint/cmd/golangci-lint"
})
assert.True(t, ok)
- assert.Equal(t, "v1.56.2", plugin.Version())
+ assert.Equal(t, "v1.57.2", plugin.Version())
assert.Equal(t, "golangci-lint", plugin.Name())
assert.Equal(t, "github.com/golangci/golangci-lint", plugin.Module())
assert.Equal(t, "lint", plugin.Alias)
diff --git a/internal/progress.go b/gbc/artifact/progress.go
similarity index 97%
rename from internal/progress.go
rename to gbc/artifact/progress.go
index 8357d43..a2ddab7 100644
--- a/internal/progress.go
+++ b/gbc/artifact/progress.go
@@ -1,4 +1,4 @@
-package internal
+package artifact
import (
"fmt"
diff --git a/internal/project.go b/gbc/artifact/project.go
similarity index 71%
rename from internal/project.go
rename to gbc/artifact/project.go
index d25b7b1..56211c8 100644
--- a/internal/project.go
+++ b/gbc/artifact/project.go
@@ -1,10 +1,11 @@
-package internal
+package artifact
import (
"bufio"
"errors"
"fmt"
"github.com/fatih/color" //nolint
+ "github.com/kcmvp/gob/utils"
"github.com/samber/lo" //nolint
"github.com/spf13/viper" //nolint
"io/fs"
@@ -21,6 +22,7 @@ import (
const (
pluginCfgKey = "plugins"
defaultCfgKey = "_default_"
+ pluginCfgFile = "plugins.yaml"
)
var (
@@ -31,44 +33,18 @@ type Project struct {
root string
module string
deps []string
- cfg sync.Map // store all the configuration
-}
-
-// TestCaller returns true when caller is from _test.go and the full method name
-func TestCaller() (bool, string) {
- var test bool
- var file string
- callers := make([]uintptr, 10)
- n := runtime.Callers(0, callers)
- frames := runtime.CallersFrames(callers[:n])
- for {
- frame, more := frames.Next()
- // fmt.Printf("%s->%s:%d\n", frame.File, frame.Function, frame.Line)
- test = strings.HasSuffix(frame.File, "_test.go") && strings.HasPrefix(frame.Function, project.module)
- if test || !more {
- items := strings.Split(frame.File, "/")
- items = lo.Map(items[len(items)-2:], func(item string, _ int) string {
- return strings.ReplaceAll(item, ".go", "")
- })
- uniqueNames := strings.Split(frame.Function, ".")
- items = append(items, uniqueNames[len(uniqueNames)-1])
- file = strings.Join(items, "_")
- break
- }
- }
- return test, file
+ cfgs sync.Map // store all the configuration
}
func (project *Project) config() *viper.Viper {
- testEnv, file := TestCaller()
+ testEnv, file := utils.TestCaller()
key := lo.If(testEnv, file).Else(defaultCfgKey)
- obj, ok := project.cfg.Load(key)
+ obj, ok := project.cfgs.Load(key)
if ok {
return obj.(*viper.Viper)
}
v := viper.New()
path := lo.If(!testEnv, project.Root()).Else(project.Target())
-
v.SetConfigFile(filepath.Join(path, "gob.yaml"))
if err := v.ReadInConfig(); err != nil {
var configFileNotFoundError viper.ConfigFileNotFoundError
@@ -76,7 +52,7 @@ func (project *Project) config() *viper.Viper {
color.Yellow("Warning: can not find configuration gob.yaml")
}
}
- project.cfg.Store(key, v)
+ project.cfgs.Store(key, v)
return v
}
@@ -89,7 +65,7 @@ func (project *Project) mergeConfig(cfg map[string]any) error {
}
func (project *Project) HookDir() string {
- if ok, _ := TestCaller(); ok {
+ if ok, _ := utils.TestCaller(); ok {
mock := filepath.Join(CurProject().Target(), ".git", "hooks")
if _, err := os.Stat(mock); err != nil {
os.MkdirAll(mock, os.ModePerm) //nolint
@@ -110,7 +86,7 @@ func init() {
project = Project{
root: item[0],
module: item[1],
- cfg: sync.Map{},
+ cfgs: sync.Map{},
}
cmd = exec.Command("go", "list", "-f", "{{if not .Standard}}{{.ImportPath}}{{end}}", "-deps", "./...")
output, err = cmd.Output()
@@ -145,7 +121,7 @@ func (project *Project) Module() string {
func (project *Project) Target() string {
target := filepath.Join(project.Root(), "target")
- if test, method := TestCaller(); test {
+ if test, method := utils.TestCaller(); test {
target = filepath.Join(target, method)
}
if _, err := os.Stat(target); err != nil {
@@ -205,12 +181,13 @@ func (project *Project) MainFiles() []string {
}
func (project *Project) Plugins() []Plugin {
- if v := project.config().Get(pluginCfgKey); v != nil {
+ viper := project.config()
+ if v := viper.Get(pluginCfgKey); v != nil {
plugins := v.(map[string]any)
return lo.MapToSlice(plugins, func(key string, _ any) Plugin {
var plugin Plugin
key = fmt.Sprintf("%s.%s", pluginCfgKey, key)
- if err := project.config().UnmarshalKey(key, &plugin); err != nil {
+ if err := viper.UnmarshalKey(key, &plugin); err != nil {
color.Yellow("failed to parse plugin %s: %s", key, err.Error())
}
if err := plugin.init(); err != nil {
@@ -233,7 +210,7 @@ func (project *Project) SetupPlugin(plugin Plugin) {
return fmt.Sprintf("%s.%s.%s", pluginCfgKey, plugin.Name(), key), value
})
if err := project.mergeConfig(values); err != nil {
- color.Red("faialed to setup plugin %s", err.Error())
+ color.Red("failed to setup plugin %s", err.Error())
return
}
_ = project.config().ReadInConfig()
@@ -247,8 +224,8 @@ func (project *Project) isSetup(plugin Plugin) bool {
return project.config().Get(fmt.Sprintf("plugins.%s.url", plugin.name)) != nil
}
-func (project *Project) Validate() {
- project.SetupHooks(false)
+func (project *Project) Validate() error {
+ return project.SetupHooks(false)
}
func InGit() bool {
@@ -256,30 +233,14 @@ func InGit() bool {
return err == nil
}
-var unknownVersion = "unknown"
+var latestHash = []string{`log`, `-1`, `--abbrev-commit`, `--date=format-local:%Y-%m-%d %H:%M`, `--format=%h(%ad)`}
func Version() string {
- if output, err := exec.Command("git", "rev-parse", "HEAD").CombinedOutput(); err == nil {
- hash := strings.ReplaceAll(string(output), "\n", "")
- output, err = exec.Command("git", "describe", "--tag", hash).CombinedOutput()
- if err != nil {
- return unknownVersion
- }
- tag := strings.ReplaceAll(string(output), "\n", "")
- output, _ = exec.Command("git", "status", "--short").CombinedOutput()
- if lo.ContainsBy(strings.Split(string(output), "\n"), func(line string) bool {
- line = strings.TrimSpace(strings.ToUpper(line))
- return strings.HasPrefix(line, "M ") ||
- strings.HasSuffix(line, "D ") ||
- strings.HasSuffix(line, "?? ")
- }) {
- return fmt.Sprintf("%s@stage", tag)
- }
- if output, err = exec.Command("git", "log", "--format=%ci -n 1", hash).CombinedOutput(); err == nil {
- return fmt.Sprintf("%s@%s", tag, strings.ReplaceAll(string(output), "\n", ""))
- }
+ version := "unknown"
+ if output, err := exec.Command("git", latestHash...).CombinedOutput(); err == nil {
+ version = strings.Trim(string(output), "\n")
}
- return unknownVersion
+ return version
}
func temporaryGoPath() string {
@@ -288,7 +249,7 @@ func temporaryGoPath() string {
}
func GoPath() string {
- if ok, method := TestCaller(); ok {
+ if ok, method := utils.TestCaller(); ok {
dir := filepath.Join(os.TempDir(), method)
_ = os.MkdirAll(dir, os.ModePerm) //nolint
return dir
diff --git a/internal/project_test.go b/gbc/artifact/project_test.go
similarity index 83%
rename from internal/project_test.go
rename to gbc/artifact/project_test.go
index b2e8f0c..78fb3a3 100644
--- a/internal/project_test.go
+++ b/gbc/artifact/project_test.go
@@ -1,7 +1,8 @@
-package internal
+package artifact
import (
"fmt"
+ "github.com/kcmvp/gob/utils"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
@@ -21,8 +22,8 @@ type ProjectTestSuite struct {
}
func (suite *ProjectTestSuite) BeforeTest(_, testName string) {
- s, _ := os.Open(filepath.Join(CurProject().Root(), "testdata", "gob.yaml"))
- root := filepath.Join(CurProject().Root(), "target", fmt.Sprintf("internal_project_test_%s", testName))
+ s, _ := os.Open(filepath.Join(CurProject().Root(), "gbc", "testdata", "gob.yaml"))
+ root := filepath.Join(CurProject().Root(), "target", fmt.Sprintf("artifact_ProjectTestSuite_%s", testName))
os.MkdirAll(root, os.ModePerm)
t, _ := os.Create(filepath.Join(root, "gob.yaml"))
io.Copy(t, s)
@@ -31,7 +32,9 @@ func (suite *ProjectTestSuite) BeforeTest(_, testName string) {
}
func (suite *ProjectTestSuite) TearDownSuite() {
- TearDownSuite("internal_project_test_")
+ _, method := utils.TestCaller()
+ prefix := strings.TrimRight(method, "TearDownSuite")
+ TearDownSuite(prefix)
}
func TestProjectSuite(t *testing.T) {
@@ -114,7 +117,7 @@ func (suite *ProjectTestSuite) TestValidate() {
func (suite *ProjectTestSuite) TestMainFiles() {
mainFiles := CurProject().MainFiles()
assert.Equal(suite.T(), 1, len(mainFiles))
- assert.True(suite.T(), lo.Contains(mainFiles, filepath.Join(CurProject().Root(), "gob.go")))
+ assert.True(suite.T(), lo.Contains(mainFiles, filepath.Join(CurProject().Root(), "gbc", "gbc.go")))
}
func (suite *ProjectTestSuite) TestVersion() {
@@ -122,14 +125,15 @@ func (suite *ProjectTestSuite) TestVersion() {
}
func (suite *ProjectTestSuite) Test_Callee() {
- test, method := TestCaller()
+ test, method := utils.TestCaller()
assert.True(suite.T(), test)
- assert.Equal(suite.T(), "internal_project_test_Test_Callee", method)
+ assert.Equal(suite.T(), "artifact_ProjectTestSuite_Test_Callee", method)
}
func (suite *ProjectTestSuite) TestHookDir() {
hookDir := CurProject().HookDir()
- assert.True(suite.T(), strings.HasSuffix(hookDir, "internal_project_test_TestHookDir/.git/hooks"))
+ _, dir := utils.TestCaller()
+ assert.True(suite.T(), strings.Contains(hookDir, dir))
_, err := os.Stat(hookDir)
assert.NoError(suite.T(), err)
}
@@ -137,8 +141,10 @@ func (suite *ProjectTestSuite) TestHookDir() {
func (suite *ProjectTestSuite) TestSetupPlugin() {
plugin, _ := NewPlugin(v6)
project.SetupPlugin(plugin)
- entry, err := os.ReadDir(GoPath())
- assert.True(suite.T(), strings.HasSuffix(GoPath(), "project_test_TestSetupPlugin"))
+ gopath := GoPath()
+ entry, err := os.ReadDir(gopath)
+ _, suffix := utils.TestCaller()
+ assert.True(suite.T(), strings.HasSuffix(gopath, suffix))
assert.NoErrorf(suite.T(), err, "GOPATH should be created")
assert.True(suite.T(), len(entry) == 1, "plugin should be installed to GOPATH")
}
diff --git a/cmd/action.go b/gbc/cmd/build_action.go
similarity index 67%
rename from cmd/action.go
rename to gbc/cmd/build_action.go
index ec42e56..dfda14d 100644
--- a/cmd/action.go
+++ b/gbc/cmd/build_action.go
@@ -3,30 +3,33 @@ package cmd
import (
"errors"
"fmt"
+ "github.com/kcmvp/gob/gbc/artifact" //nolint
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/fatih/color"
- "github.com/kcmvp/gob/internal"
"github.com/samber/lo" //nolint
"github.com/spf13/cobra"
)
type (
Execution func(cmd *cobra.Command, args ...string) error
- Action lo.Tuple2[string, Execution]
+ Action lo.Tuple3[string, Execution, string]
)
func buildActions() []Action {
return []Action{
- {A: "build", B: buildAction},
- {A: "clean", B: cleanAction},
- {A: "test", B: testAction},
+ {A: "build", B: buildAction, C: "build all main methods which in main package and name the binary as file name"},
+ {A: "clean", B: cleanAction, C: "clean project target folder"},
+ {A: "test", B: testAction, C: "test the project and generate coverage report in target folder"},
{A: "after_test", B: coverReport},
}
}
+func (a Action) String() string {
+ return fmt.Sprintf("%s: %s", a.A, a.A)
+}
func setupActions() []Action {
return []Action{
@@ -54,7 +57,7 @@ func afterExecution(cmd *cobra.Command, arg string) {
func execute(cmd *cobra.Command, arg string) error {
beforeExecution(cmd, arg) //nolint
var err error
- if plugin, ok := lo.Find(internal.CurProject().Plugins(), func(plugin internal.Plugin) bool {
+ if plugin, ok := lo.Find(artifact.CurProject().Plugins(), func(plugin artifact.Plugin) bool {
return plugin.Alias == arg
}); ok {
err = plugin.Execute()
@@ -78,7 +81,7 @@ func validBuilderArgs() []string {
}), func(action Action, _ int) string {
return action.A
})
- lo.ForEach(internal.CurProject().Plugins(), func(item internal.Plugin, _ int) {
+ lo.ForEach(artifact.CurProject().Plugins(), func(item artifact.Plugin, _ int) {
if !lo.Contains(builtIn, item.Alias) {
builtIn = append(builtIn, item.Alias)
}
@@ -88,17 +91,17 @@ func validBuilderArgs() []string {
func buildAction(_ *cobra.Command, _ ...string) error {
bm := map[string]string{}
- for _, mainFile := range internal.CurProject().MainFiles() {
+ for _, mainFile := range artifact.CurProject().MainFiles() {
binary := strings.TrimSuffix(filepath.Base(mainFile), ".go")
if f, exists := bm[binary]; exists {
return fmt.Errorf("file %s has already built as %s, please rename %s", f, binary, mainFile)
}
- output := filepath.Join(internal.CurProject().Target(), binary)
- versionFlag := fmt.Sprintf("-X '%s/infra.buildVersion=%s'", internal.CurProject().Module(), internal.Version())
- if _, err := exec.Command("go", "build", "-ldflags", versionFlag, "-o", output, mainFile).CombinedOutput(); err != nil { //nolint
- return errors.New(color.RedString("failed to build the project: %s", err.Error()))
+ output := filepath.Join(artifact.CurProject().Target(), binary)
+ versionFlag := fmt.Sprintf("-X 'main.buildVersion=%s'", artifact.Version())
+ if data, err := exec.Command("go", "build", "-o", output, "-ldflags", versionFlag, mainFile).CombinedOutput(); err != nil { //nolint
+ return errors.New(color.RedString(string(data)))
}
- fmt.Printf("Build %s to %s successfully\n", mainFile, output)
+ fmt.Printf("Build project successfully %s\n", output)
bm[binary] = output
}
if len(bm) == 0 {
@@ -109,40 +112,39 @@ func buildAction(_ *cobra.Command, _ ...string) error {
func cleanAction(_ *cobra.Command, _ ...string) error {
// clean target folder
- os.RemoveAll(internal.CurProject().Target())
- os.Mkdir(internal.CurProject().Target(), os.ModePerm) //nolint errcheck
+ os.RemoveAll(artifact.CurProject().Target())
+ os.Mkdir(artifact.CurProject().Target(), os.ModePerm) //nolint errcheck
fmt.Println("Clean target folder successfully !")
// clean cache
args := []string{"clean"}
_, err := exec.Command("go", args...).CombinedOutput()
- if err == nil {
- fmt.Println("Clean cache successfully !")
+ if err != nil {
+ color.Red("failed to clean the project cache %s", err.Error())
}
return err
}
-func testAction(_ *cobra.Command, args ...string) error {
- coverProfile := fmt.Sprintf("-coverprofile=%s/cover.out", internal.CurProject().Target())
+func testAction(_ *cobra.Command, _ ...string) error {
+ coverProfile := fmt.Sprintf("-coverprofile=%s/cover.out", artifact.CurProject().Target())
testCmd := exec.Command("go", []string{"test", "-v", coverProfile, "./..."}...) //nolint
- return internal.StreamCmdOutput(testCmd, "test")
+ return artifact.StreamCmdOutput(testCmd, "test")
}
func coverReport(_ *cobra.Command, _ ...string) error {
- target := internal.CurProject().Target()
+ target := artifact.CurProject().Target()
_, err := os.Stat(filepath.Join(target, "cover.out"))
if err == nil {
if _, err = exec.Command("go", []string{"tool", "cover", fmt.Sprintf("-html=%s/cover.out", target), fmt.Sprintf("-o=%s/cover.html", target)}...).CombinedOutput(); err == nil { //nolint
fmt.Printf("Coverage report is generated at %s/cover.html \n", target)
return nil
- } else {
- return fmt.Errorf(color.RedString("Failed to generate coverage report %s", err.Error()))
}
+ return fmt.Errorf(color.RedString("Failed to generate coverage report %s", err.Error()))
}
return fmt.Errorf(color.RedString("Failed to generate coverage report %s", err.Error()))
}
func setupVersion(_ *cobra.Command, _ ...string) error {
- infra := filepath.Join(internal.CurProject().Root(), "infra")
+ infra := filepath.Join(artifact.CurProject().Root(), "infra")
if _, err := os.Stat(infra); err != nil {
os.Mkdir(infra, 0700) // nolint
}
diff --git a/cmd/action_test.go b/gbc/cmd/build_action_test.go
similarity index 69%
rename from cmd/action_test.go
rename to gbc/cmd/build_action_test.go
index 0e1212c..6e9e017 100644
--- a/cmd/action_test.go
+++ b/gbc/cmd/build_action_test.go
@@ -2,12 +2,12 @@ package cmd
import (
"github.com/fatih/color"
- "github.com/kcmvp/gob/internal"
+ "github.com/kcmvp/gob/gbc/artifact"
+ "github.com/kcmvp/gob/utils"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"io"
- "io/fs"
"os"
"path/filepath"
"strings"
@@ -22,29 +22,15 @@ func TestActionSuite(t *testing.T) {
suite.Run(t, &ActionTestSuite{})
}
-func TearDownSuite(prefix string) {
- filepath.WalkDir(os.TempDir(), func(path string, d fs.DirEntry, err error) error {
- if d.IsDir() && strings.HasPrefix(d.Name(), prefix) {
- os.RemoveAll(path)
- }
- return nil
- })
- filepath.WalkDir(filepath.Join(internal.CurProject().Root(), "target"), func(path string, d fs.DirEntry, err error) error {
- if d.IsDir() && strings.HasPrefix(d.Name(), prefix) {
- os.RemoveAll(path)
- }
- return nil
- })
-}
-
func (suite *ActionTestSuite) TearDownSuite() {
- TearDownSuite("cmd_action_test_")
+ _, method := utils.TestCaller()
+ TearDownSuite(strings.TrimRight(method, "TearDownSuite"))
}
func (suite *ActionTestSuite) TestActionBuild() {
err := buildAction(nil)
assert.NoError(suite.T(), err)
- binary := filepath.Join(internal.CurProject().Target(), lo.If(internal.Windows(), "gob.exe").Else("gob"))
+ binary := filepath.Join(artifact.CurProject().Target(), lo.If(artifact.Windows(), "gbc.exe").Else("gbc"))
_, err = os.Stat(binary)
assert.NoError(suite.T(), err)
}
@@ -69,10 +55,10 @@ func (suite *ActionTestSuite) TestBuiltInActions() {
}
func (suite *ActionTestSuite) TestExecute() {
- _ = os.Chdir(internal.CurProject().Root())
- err := execute(builderCmd, "build")
+ _ = os.Chdir(artifact.CurProject().Root())
+ err := execute(rootCmd, "build")
assert.NoError(suite.T(), err)
- err = execute(builderCmd, "build1")
+ err = execute(rootCmd, "build1")
assert.Error(suite.T(), err)
assert.Contains(suite.T(), err.Error(), color.RedString("can not find command %s", "build1"))
}
@@ -80,15 +66,15 @@ func (suite *ActionTestSuite) TestExecute() {
func (suite *ActionTestSuite) TestCoverage() {
err := coverReport(nil, "")
assert.Errorf(suite.T(), err, "no cover.out")
- s, _ := os.Open(filepath.Join(internal.CurProject().Root(), "testdata", "cover.out"))
- t, _ := os.Create(filepath.Join(internal.CurProject().Target(), "cover.out"))
+ s, _ := os.Open(filepath.Join(artifact.CurProject().Root(), "testdata", "cover.out"))
+ t, _ := os.Create(filepath.Join(artifact.CurProject().Target(), "cover.out"))
io.Copy(t, s)
s.Close()
t.Close()
- _, err = os.Stat(filepath.Join(internal.CurProject().Target(), "cover.out"))
+ _, err = os.Stat(filepath.Join(artifact.CurProject().Target(), "cover.out"))
err = coverReport(nil, "")
assert.NoError(suite.T(), err, "should generate test cover report successfully")
- _, err = os.Stat(filepath.Join(internal.CurProject().Target(), "cover.html"))
+ _, err = os.Stat(filepath.Join(artifact.CurProject().Target(), "cover.html"))
assert.NoError(suite.T(), err)
}
@@ -100,7 +86,7 @@ func (suite *ActionTestSuite) TestSetupActions() {
func (suite *ActionTestSuite) TestSetupVersion() {
err := setupVersion(nil, "")
assert.NoError(suite.T(), err)
- version := filepath.Join(internal.CurProject().Root(), "infra", "version.go")
+ version := filepath.Join(artifact.CurProject().Root(), "infra", "version.go")
os.Remove(version)
_, err = os.Stat(version)
assert.Error(suite.T(), err)
@@ -111,7 +97,7 @@ func (suite *ActionTestSuite) TestSetupVersion() {
}
func (suite *ActionTestSuite) TestBuildAndClean() {
- target := internal.CurProject().Target()
+ target := artifact.CurProject().Target()
err := buildAction(nil, "")
assert.NoError(suite.T(), err)
entry, err := os.ReadDir(target)
diff --git a/cmd/deps.go b/gbc/cmd/deps.go
similarity index 91%
rename from cmd/deps.go
rename to gbc/cmd/deps.go
index 58acc04..cedf58e 100644
--- a/cmd/deps.go
+++ b/gbc/cmd/deps.go
@@ -8,16 +8,17 @@ import (
"path/filepath"
"strings"
+ "github.com/kcmvp/gob/gbc/artifact" //nolint
+
"github.com/fatih/color"
- "github.com/kcmvp/gob/internal"
"github.com/samber/lo"
"github.com/spf13/cobra"
"github.com/xlab/treeprint"
)
var (
- bold = color.New(color.Bold)
- yellow = color.New(color.FgYellow, color.Bold)
+ green = color.New(color.FgGreen)
+ yellow = color.New(color.FgYellow)
)
// parseMod return a tuple which the fourth element is the indicator of direct or indirect reference
@@ -47,7 +48,7 @@ func parseMod(mod *os.File) (string, string, []*lo.Tuple4[string, string, string
// dependencyTree build dependency tree of the project, an empty tree returns when runs into error
func dependencyTree() (treeprint.Tree, error) {
- mod, err := os.Open(filepath.Join(internal.CurProject().Root(), "go.mod"))
+ mod, err := os.Open(filepath.Join(artifact.CurProject().Root(), "go.mod"))
if err != nil {
return nil, fmt.Errorf(color.RedString(err.Error()))
}
@@ -63,7 +64,7 @@ func dependencyTree() (treeprint.Tree, error) {
return nil, fmt.Errorf(err.Error())
}
tree := treeprint.New()
- tree.SetValue(bold.Sprintf("%s", module))
+ tree.SetValue(module)
direct := lo.FilterMap(dependencies, func(item *lo.Tuple4[string, string, string, int], _ int) (string, bool) {
return fmt.Sprintf("%s@latest", item.A), item.D == 1
})
@@ -126,14 +127,14 @@ and indicate available updates which take an green * indicator`,
yellow.Println("No dependencies !")
return nil
}
- bold.Print("\nDependencies of the projects:\n")
- yellow.Print("* indicates new versions available\n")
- fmt.Println("")
+ green.Println("Dependencies of the projects:")
fmt.Println(tree.String())
return nil
},
}
func init() {
- builderCmd.AddCommand(depCmd)
+ depCmd.SetUsageTemplate(usageTemplate())
+ depCmd.SetErrPrefix(color.RedString("Error:"))
+ rootCmd.AddCommand(depCmd)
}
diff --git a/cmd/deps_test.go b/gbc/cmd/deps_test.go
similarity index 70%
rename from cmd/deps_test.go
rename to gbc/cmd/deps_test.go
index 3fb816c..a9edbe0 100644
--- a/cmd/deps_test.go
+++ b/gbc/cmd/deps_test.go
@@ -2,7 +2,7 @@ package cmd
import (
"fmt"
- "github.com/kcmvp/gob/internal"
+ "github.com/kcmvp/gob/gbc/artifact"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/xlab/treeprint"
@@ -13,20 +13,20 @@ import (
)
func TestParseMod(t *testing.T) {
- os.Chdir(internal.CurProject().Root())
- mod, _ := os.Open(filepath.Join(internal.CurProject().Root(), "go.mod"))
+ os.Chdir(artifact.CurProject().Root())
+ mod, _ := os.Open(filepath.Join(artifact.CurProject().Root(), "go.mod"))
m, _, deps, err := parseMod(mod)
assert.NoError(t, err)
assert.Equal(t, m, "github.com/kcmvp/gob")
- assert.Equal(t, 10, len(lo.Filter(deps, func(item *lo.Tuple4[string, string, string, int], _ int) bool {
+ assert.Equal(t, 15, len(lo.Filter(deps, func(item *lo.Tuple4[string, string, string, int], _ int) bool {
return item.D == 1
})))
- assert.Equal(t, 43, len(deps))
+ assert.Equal(t, 48, len(deps))
}
func TestDependency(t *testing.T) {
- os.Chdir(internal.CurProject().Root())
- mod, _ := os.Open(filepath.Join(internal.CurProject().Root(), "go.mod"))
+ os.Chdir(artifact.CurProject().Root())
+ mod, _ := os.Open(filepath.Join(artifact.CurProject().Root(), "go.mod"))
_, _, deps, _ := parseMod(mod)
tree, err := dependencyTree()
assert.NoError(t, err)
diff --git a/cmd/exec.go b/gbc/cmd/exec.go
similarity index 76%
rename from cmd/exec.go
rename to gbc/cmd/exec.go
index a391644..f49c6cd 100644
--- a/cmd/exec.go
+++ b/gbc/cmd/exec.go
@@ -7,13 +7,14 @@ import (
"bufio"
"errors"
"fmt"
- "github.com/fatih/color"
- "github.com/kcmvp/gob/internal"
- "github.com/samber/lo"
- "github.com/spf13/cobra"
"os"
"regexp"
"strings"
+
+ "github.com/fatih/color"
+ "github.com/kcmvp/gob/gbc/artifact"
+ "github.com/samber/lo"
+ "github.com/spf13/cobra"
)
const pushDeleteHash = "0000000000000000000000000000000000000000"
@@ -36,13 +37,13 @@ var validateCommitMsg Execution = func(_ *cobra.Command, args ...string) error {
}
func execValidArgs() []string {
- return lo.Map(internal.CurProject().Executions(), func(exec internal.Execution, _ int) string {
+ return lo.Map(artifact.CurProject().Executions(), func(exec artifact.Execution, _ int) string {
return exec.CmdKey
})
}
func pushDelete(cmd string) bool {
- if cmd == internal.PrePush {
+ if cmd == artifact.PrePush {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := scanner.Text()
@@ -54,22 +55,21 @@ func pushDelete(cmd string) bool {
return false
}
-func do(execution internal.Execution, cmd *cobra.Command, args ...string) error {
- if execution.CmdKey == internal.CommitMsg {
+func do(execution artifact.Execution, cmd *cobra.Command, args ...string) error {
+ if execution.CmdKey == artifact.CommitMsg {
args = append(args, execution.Actions...)
return validateCommitMsg(nil, args...)
- } else {
- if pushDelete(execution.CmdKey) {
- return nil
- }
- // process hook
- for _, arg := range execution.Actions {
- if err := execute(cmd, arg); err != nil {
- return errors.New(color.RedString("failed to %s the project \n", arg))
- }
- }
+ }
+ if pushDelete(execution.CmdKey) {
return nil
}
+ // process hook
+ for _, arg := range execution.Actions {
+ if err := execute(cmd, arg); err != nil {
+ return errors.New(color.RedString("failed to %s the project \n", arg))
+ }
+ }
+ return nil
}
// execCmd represents the exec command
@@ -90,7 +90,7 @@ var execCmd = &cobra.Command{
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
- execution, _ := lo.Find(internal.CurProject().Executions(), func(exec internal.Execution) bool {
+ execution, _ := lo.Find(artifact.CurProject().Executions(), func(exec artifact.Execution) bool {
return exec.CmdKey == args[0]
})
return do(execution, cmd, args...)
@@ -98,5 +98,5 @@ var execCmd = &cobra.Command{
}
func init() {
- builderCmd.AddCommand(execCmd)
+ rootCmd.AddCommand(execCmd)
}
diff --git a/cmd/exec_test.go b/gbc/cmd/exec_test.go
similarity index 77%
rename from cmd/exec_test.go
rename to gbc/cmd/exec_test.go
index bb8b24f..6166d6c 100644
--- a/cmd/exec_test.go
+++ b/gbc/cmd/exec_test.go
@@ -2,13 +2,15 @@ package cmd
import (
"fmt"
- "github.com/kcmvp/gob/internal"
+ "github.com/kcmvp/gob/gbc/artifact"
+ "github.com/kcmvp/gob/utils"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"io"
"os"
"path/filepath"
+ "strings"
"testing"
)
@@ -17,8 +19,8 @@ type ExecTestSuite struct {
}
func (suite *ExecTestSuite) BeforeTest(_, testName string) {
- s, _ := os.Open(filepath.Join(internal.CurProject().Root(), "testdata", "gob.yaml"))
- root := filepath.Join(internal.CurProject().Root(), "target", fmt.Sprintf("cmd_exec_test_%s", testName))
+ s, _ := os.Open(filepath.Join(artifact.CurProject().Root(), "gbc", "testdata", "gob.yaml"))
+ root := filepath.Join(artifact.CurProject().Root(), "target", fmt.Sprintf("cmd_ExecTestSuite_%s", testName))
os.MkdirAll(root, os.ModePerm)
t, _ := os.Create(filepath.Join(root, "gob.yaml"))
io.Copy(t, s)
@@ -27,7 +29,8 @@ func (suite *ExecTestSuite) BeforeTest(_, testName string) {
}
func (suite *ExecTestSuite) TearDownSuite() {
- TearDownSuite("cmd_exec_test_")
+ _, method := utils.TestCaller()
+ TearDownSuite(strings.TrimRight(method, "TearDownSuite"))
}
func TestExecSuite(t *testing.T) {
@@ -36,7 +39,7 @@ func TestExecSuite(t *testing.T) {
func (suite *ExecTestSuite) TestActions() {
assert.Equal(suite.T(), 3, len(execValidArgs()))
- assert.True(suite.T(), lo.Every(execValidArgs(), []string{internal.CommitMsg, internal.PreCommit, internal.PreCommit}))
+ assert.True(suite.T(), lo.Every(execValidArgs(), []string{artifact.CommitMsg, artifact.PreCommit, artifact.PreCommit}))
}
func (suite *ExecTestSuite) TestCmdArgs() {
@@ -47,9 +50,9 @@ func (suite *ExecTestSuite) TestCmdArgs() {
}{
{"no args", []string{}, true},
{"no match", []string{lo.RandomString(10, lo.LettersCharset)}, true},
- {"first match", []string{internal.CommitMsg, lo.RandomString(10, lo.LettersCharset)}, false},
+ {"first match", []string{artifact.CommitMsg, lo.RandomString(10, lo.LettersCharset)}, false},
{"second match", []string{lo.RandomString(10, lo.LettersCharset), "msghook"}, true},
- {"more than 3", []string{internal.CommitMsg, lo.RandomString(10, lo.AlphanumericCharset),
+ {"more than 3", []string{artifact.CommitMsg, lo.RandomString(10, lo.AlphanumericCharset),
lo.RandomString(10, lo.AlphanumericCharset),
lo.RandomString(10, lo.AlphanumericCharset)},
true,
@@ -79,7 +82,7 @@ func (suite *ExecTestSuite) TestValidateCommitMsg() {
}
for _, test := range tests {
suite.T().Run(test.name, func(t *testing.T) {
- err := do(internal.Execution{CmdKey: internal.CommitMsg}, nil, test.args...)
+ err := do(artifact.Execution{CmdKey: artifact.CommitMsg}, nil, test.args...)
assert.True(t, test.wantErr == (err != nil))
})
}
@@ -94,13 +97,13 @@ func (suite *ExecTestSuite) TestPushDelete() {
}{
{
name: "valid",
- cmdKey: internal.PrePush,
+ cmdKey: artifact.PrePush,
msg: fmt.Sprintf("delete %s", pushDeleteHash),
result: true,
},
{
name: "invalid",
- cmdKey: internal.PrePush,
+ cmdKey: artifact.PrePush,
msg: pushDeleteHash,
result: false,
},
@@ -123,7 +126,7 @@ func (suite *ExecTestSuite) TestPushDelete() {
defer writer.Close()
io.WriteString(writer, test.msg)
}()
- rs := pushDelete(internal.PrePush)
+ rs := pushDelete(artifact.PrePush)
assert.Equal(t, test.result, rs)
})
}
@@ -132,22 +135,22 @@ func (suite *ExecTestSuite) TestPushDelete() {
func (suite *ExecTestSuite) TestDo() {
tests := []struct {
name string
- execution internal.Execution
+ execution artifact.Execution
wantErr bool
}{
{
name: "pre-push",
- execution: internal.Execution{
- CmdKey: internal.PrePush,
+ execution: artifact.Execution{
+ CmdKey: artifact.PrePush,
Actions: []string{"build"},
},
wantErr: false,
},
{
name: "pre-commit",
- execution: internal.Execution{
- CmdKey: internal.PreCommit,
+ execution: artifact.Execution{
+ CmdKey: artifact.PreCommit,
Actions: []string{"lint"},
},
wantErr: true,
diff --git a/cmd/initializer.go b/gbc/cmd/initialize.go
similarity index 64%
rename from cmd/initializer.go
rename to gbc/cmd/initialize.go
index c311d78..300b801 100644
--- a/cmd/initializer.go
+++ b/gbc/cmd/initialize.go
@@ -4,28 +4,29 @@ Copyright © 2023 kcmvp
package cmd
import (
- _ "embed"
"encoding/json"
"fmt"
+ "os"
+ "path/filepath"
+
"github.com/fatih/color"
- "github.com/kcmvp/gob/internal"
+ "github.com/kcmvp/gob/gbc/artifact"
+ "github.com/kcmvp/gob/utils"
"github.com/samber/lo"
"github.com/spf13/cobra"
"github.com/tidwall/gjson"
- "os"
- "path/filepath"
)
-func builtinPlugins() []internal.Plugin {
+func builtinPlugins() []artifact.Plugin {
var data []byte
var err error
- test, _ := internal.TestCaller()
+ test, _ := utils.TestCaller()
if !test {
data, err = resources.ReadFile("resources/config.json")
} else {
- data, err = os.ReadFile(filepath.Join(internal.CurProject().Root(), "testdata", "config.json"))
+ data, err = os.ReadFile(filepath.Join(artifact.CurProject().Root(), "gbc", "testdata", "config.json"))
}
- var plugins []internal.Plugin
+ var plugins []artifact.Plugin
if err == nil {
v := gjson.GetBytes(data, "plugins")
if err = json.Unmarshal([]byte(v.Raw), &plugins); err != nil {
@@ -35,14 +36,14 @@ func builtinPlugins() []internal.Plugin {
return plugins
}
-func initializerFunc(_ *cobra.Command, _ []string) {
+func initialize(_ *cobra.Command, _ []string) {
fmt.Println("Initialize configuration ......")
- lo.ForEach(builtinPlugins(), func(plugin internal.Plugin, index int) {
- internal.CurProject().SetupPlugin(plugin)
+ lo.ForEach(builtinPlugins(), func(plugin artifact.Plugin, _ int) {
+ artifact.CurProject().SetupPlugin(plugin)
if len(plugin.Config) > 0 {
- if _, err := os.Stat(filepath.Join(internal.CurProject().Root(), plugin.Config)); err != nil {
+ if _, err := os.Stat(filepath.Join(artifact.CurProject().Root(), plugin.Config)); err != nil {
if data, err := resources.ReadFile(filepath.Join(resourceDir, plugin.Config)); err == nil {
- if err = os.WriteFile(filepath.Join(internal.CurProject().Root(), plugin.Config), data, os.ModePerm); err != nil {
+ if err = os.WriteFile(filepath.Join(artifact.CurProject().Root(), plugin.Config), data, os.ModePerm); err != nil {
color.Red("failed to create configuration %s", plugin.Config)
}
} else {
@@ -51,7 +52,7 @@ func initializerFunc(_ *cobra.Command, _ []string) {
}
}
})
- internal.CurProject().SetupHooks(true)
+ artifact.CurProject().SetupHooks(true)
}
// initializerCmd represents the init command
@@ -59,9 +60,9 @@ var initializerCmd = &cobra.Command{
Use: "init",
Short: "Initialize project builder configuration",
Long: `Initialize project builder configuration`,
- Run: initializerFunc,
+ Run: initialize,
}
func init() {
- builderCmd.AddCommand(initializerCmd)
+ rootCmd.AddCommand(initializerCmd)
}
diff --git a/cmd/initializer_test.go b/gbc/cmd/initialize_test.go
similarity index 56%
rename from cmd/initializer_test.go
rename to gbc/cmd/initialize_test.go
index 6ca9eb4..d313116 100644
--- a/cmd/initializer_test.go
+++ b/gbc/cmd/initialize_test.go
@@ -1,7 +1,8 @@
package cmd
import (
- "github.com/kcmvp/gob/internal"
+ "github.com/kcmvp/gob/gbc/artifact"
+ "github.com/kcmvp/gob/utils"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
@@ -14,22 +15,23 @@ import (
const golangCiLinter = "github.com/golangci/golangci-lint/cmd/golangci-lint"
const testsum = "gotest.tools/gotestsum"
-type InitializationTestSuite struct {
+type InitializeTestSuite struct {
suite.Suite
}
-func TestInitializationTestSuit(t *testing.T) {
- suite.Run(t, &InitializationTestSuite{})
+func TestInitializeTestSuit(t *testing.T) {
+ suite.Run(t, &InitializeTestSuite{})
}
-func (suite *InitializationTestSuite) TearDownSuite() {
- TearDownSuite("cmd_initializer_test_")
+func (suite *InitializeTestSuite) TearDownSuite() {
+ _, method := utils.TestCaller()
+ TearDownSuite(strings.TrimRight(method, "TearDownSuite"))
}
-func (suite *InitializationTestSuite) TestBuiltInPlugins() {
+func (suite *InitializeTestSuite) TestBuiltInPlugins() {
plugins := builtinPlugins()
assert.Equal(suite.T(), 2, len(plugins))
- plugin, ok := lo.Find(plugins, func(plugin internal.Plugin) bool {
+ plugin, ok := lo.Find(plugins, func(plugin artifact.Plugin) bool {
return plugin.Module() == "github.com/golangci/golangci-lint"
})
assert.True(suite.T(), ok)
@@ -37,22 +39,22 @@ func (suite *InitializationTestSuite) TestBuiltInPlugins() {
assert.Equal(suite.T(), "lint", plugin.Alias)
}
-func (suite *InitializationTestSuite) TestInitializerFunc() {
- gopath := internal.GoPath()
- target := internal.CurProject().Target()
- initializerFunc(nil, nil)
- plugins := internal.CurProject().Plugins()
+func (suite *InitializeTestSuite) TestInitialization() {
+ gopath := artifact.GoPath()
+ target := artifact.CurProject().Target()
+ initialize(nil, nil)
+ plugins := artifact.CurProject().Plugins()
assert.Equal(suite.T(), 2, len(plugins))
- _, ok := lo.Find(plugins, func(plugin internal.Plugin) bool {
+ _, ok := lo.Find(plugins, func(plugin artifact.Plugin) bool {
return strings.HasPrefix(plugin.Url, golangCiLinter)
})
assert.True(suite.T(), ok)
- _, ok = lo.Find(plugins, func(plugin internal.Plugin) bool {
+ _, ok = lo.Find(plugins, func(plugin artifact.Plugin) bool {
return strings.HasPrefix(plugin.Url, testsum)
})
assert.True(suite.T(), ok)
- lo.ForEach(plugins, func(plugin internal.Plugin, _ int) {
+ lo.ForEach(plugins, func(plugin artifact.Plugin, _ int) {
_, err := os.Stat(filepath.Join(gopath, plugin.Binary()))
assert.NoError(suite.T(), err)
})
diff --git a/cmd/plugin.go b/gbc/cmd/plugin.go
similarity index 64%
rename from cmd/plugin.go
rename to gbc/cmd/plugin.go
index 0b5e69c..e642e91 100644
--- a/cmd/plugin.go
+++ b/gbc/cmd/plugin.go
@@ -6,14 +6,14 @@ package cmd
import (
"errors"
"fmt"
+ "strings"
+
"github.com/fatih/color"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
- "github.com/kcmvp/gob/internal"
+ "github.com/kcmvp/gob/gbc/artifact"
"github.com/samber/lo"
"github.com/spf13/cobra"
- //nolint
- "strings"
)
// alias is the tool alias
@@ -24,21 +24,21 @@ var command string
// Install the specified tool as gob plugin
func install(_ *cobra.Command, args ...string) error {
- plugin, err := internal.NewPlugin(args[0])
+ plugin, err := artifact.NewPlugin(args[0])
if err != nil {
return err
}
plugin.Alias = alias
plugin.Alias = command
- internal.CurProject().SetupPlugin(plugin)
+ artifact.CurProject().SetupPlugin(plugin)
return nil
}
func list(_ *cobra.Command, _ ...string) error {
- plugins := internal.CurProject().Plugins()
+ plugins := artifact.CurProject().Plugins()
ct := table.Table{}
ct.SetTitle("Installed Plugins")
- ct.AppendRow(table.Row{"Command", "Alias", "Method", "URL"})
+ ct.AppendRow(table.Row{"Command", "Plugin"})
style := table.StyleDefault
style.Options.DrawBorder = true
style.Options.SeparateRows = true
@@ -46,8 +46,8 @@ func list(_ *cobra.Command, _ ...string) error {
style.Title.Align = text.AlignCenter
style.HTML.CSSClass = table.DefaultHTMLCSSClass
ct.SetStyle(style)
- rows := lo.Map(plugins, func(plugin internal.Plugin, index int) table.Row {
- return table.Row{plugin.Name(), plugin.Alias, plugin.Args, plugin.Url}
+ rows := lo.Map(plugins, func(plugin artifact.Plugin, index int) table.Row {
+ return table.Row{plugin.Alias, plugin.Url}
})
ct.AppendRows(rows)
fmt.Println(ct.Render())
@@ -58,10 +58,12 @@ var pluginCmdAction = []Action{
{
A: "list",
B: list,
+ C: "list all setup plugins",
},
{
A: "install",
B: install,
+ C: "install a plugin. `gbc plugin install `",
},
}
@@ -69,17 +71,18 @@ var pluginCmdAction = []Action{
var pluginCmd = &cobra.Command{
Use: "plugin",
Short: "Install a new plugin or list installed plugins",
- Long: `Install a new plugin or list installed plugins
+ Long: color.BlueString(`
+Install a new plugin or list installed plugins
you can update the plugin by edit gob.yaml directly
-`,
+`),
Args: func(cmd *cobra.Command, args []string) error {
- if err := cobra.MinimumNArgs(1)(cmd, args); err != nil {
+ if err := MinimumNArgs(1)(cmd, args); err != nil {
return err
}
if !lo.Contains(lo.Map(pluginCmdAction, func(item Action, _ int) string {
return item.A
}), args[0]) {
- return fmt.Errorf("invalid argument %s", args[0])
+ return errors.New(color.RedString("invalid argument %s", args[0]))
}
if "install" == args[0] && (len(args) < 2 || strings.TrimSpace(args[1]) == "") {
return errors.New(color.RedString("miss the plugin url"))
@@ -97,10 +100,24 @@ you can update the plugin by edit gob.yaml directly
},
}
+func pluginExample() string {
+ format := fmt.Sprintf(" %%-%ds %%s", pluginCmd.NamePadding())
+ return strings.Join(lo.Map(pluginCmdAction, func(action Action, index_ int) string {
+ return fmt.Sprintf(format, action.A, action.C)
+ }), "\n")
+}
+
func init() {
// init pluginCmd
- builderCmd.AddCommand(pluginCmd)
- // init installPluginCmd
+ pluginCmd.Example = pluginExample()
+ pluginCmd.SetUsageTemplate(usageTemplate())
+ pluginCmd.SetFlagErrorFunc(func(command *cobra.Command, err error) error {
+ return lo.IfF(err != nil, func() error {
+ return fmt.Errorf(color.RedString(err.Error()))
+ }).Else(nil)
+ })
pluginCmd.Flags().StringVarP(&alias, "alias", "a", "", "alias of the tool")
pluginCmd.Flags().StringVarP(&command, "command", "c", "", "default command of this tool")
+
+ rootCmd.AddCommand(pluginCmd)
}
diff --git a/cmd/plugin_test.go b/gbc/cmd/plugin_test.go
similarity index 82%
rename from cmd/plugin_test.go
rename to gbc/cmd/plugin_test.go
index 739e4e0..cdb665d 100644
--- a/cmd/plugin_test.go
+++ b/gbc/cmd/plugin_test.go
@@ -1,11 +1,13 @@
package cmd
import (
- "github.com/kcmvp/gob/internal"
+ "github.com/kcmvp/gob/gbc/artifact"
+ "github.com/kcmvp/gob/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"os"
"path/filepath"
+ "strings"
"testing"
)
@@ -21,15 +23,15 @@ func TestPluginSuite(t *testing.T) {
}
func (suite *PluginTestSuit) TearDownSuite() {
-
- TearDownSuite("cmd_plugin_test_")
+ _, method := utils.TestCaller()
+ TearDownSuite(strings.TrimRight(method, "TearDownSuite"))
}
func (suite *PluginTestSuit) TestInstallPlugin() {
err := install(nil, v6)
assert.NoError(suite.T(), err)
- plugins := internal.CurProject().Plugins()
- _, err = os.Stat(filepath.Join(internal.GoPath(), plugins[0].Binary()))
+ plugins := artifact.CurProject().Plugins()
+ _, err = os.Stat(filepath.Join(artifact.GoPath(), plugins[0].Binary()))
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), 1, len(plugins))
assert.Equal(suite.T(), "digraph", plugins[0].Name())
@@ -38,7 +40,7 @@ func (suite *PluginTestSuit) TestInstallPlugin() {
err = install(nil, v7)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), 1, len(plugins))
- _, err = os.Stat(filepath.Join(internal.GoPath(), plugins[0].Binary()))
+ _, err = os.Stat(filepath.Join(artifact.GoPath(), plugins[0].Binary()))
assert.NoError(suite.T(), err)
}
@@ -100,3 +102,8 @@ func (suite *PluginTestSuit) TestRunE() {
err := pluginCmd.RunE(pluginCmd, []string{"list"})
assert.NoErrorf(suite.T(), err, "should list iinstalled plugin successfully")
}
+
+func (suite *PluginTestSuit) TestPluginHelpTemplate() {
+ rootCmd.SetArgs([]string{"plugin", "--help"})
+ rootCmd.Execute()
+}
diff --git a/cmd/resources/.golangci.yaml b/gbc/cmd/resources/.golangci.yaml
similarity index 100%
rename from cmd/resources/.golangci.yaml
rename to gbc/cmd/resources/.golangci.yaml
diff --git a/cmd/resources/config.json b/gbc/cmd/resources/config.json
similarity index 100%
rename from cmd/resources/config.json
rename to gbc/cmd/resources/config.json
diff --git a/gbc/cmd/resources/usage.tmpl b/gbc/cmd/resources/usage.tmpl
new file mode 100644
index 0000000..d8ac719
--- /dev/null
+++ b/gbc/cmd/resources/usage.tmpl
@@ -0,0 +1,32 @@
+Usage:{{if .Runnable}}
+ {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
+ {{.CommandPath}} [command]{{end}}{{if gt (len .ValidArgs) 0}}
+ {{.CommandPath}} [argument]{{end}}{{if gt (len .Aliases) 0}}
+
+Aliases:
+ {{.NameAndAliases}}{{end}}{{if gt (len .ValidArgs) 0}}
+
+Available Arguments:
+{{.Example}}{{end}}
+{{if .HasAvailableSubCommands}}{{$cmds := .Commands}}{{if eq (len .Groups) 0}}
+
+Available Commands:{{range $cmds}}{{if (and (ne .Name "exec") (or .IsAvailableCommand (eq .Name "help")))}}
+ {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{else}}{{range $group := .Groups}}
+
+{{.Title}}{{range $cmds}}{{if (and (eq .GroupID $group.ID) (or .IsAvailableCommand (eq .Name "help")))}}
+ {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if not .AllChildCommandsHaveGroup}}
+
+Additional Commands:{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}}
+ {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
+
+Flags:
+{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
+
+Global Flags:
+{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
+
+Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
+ {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
+
+Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
+
diff --git a/cmd/resources/version.tmpl b/gbc/cmd/resources/version.tmpl
similarity index 100%
rename from cmd/resources/version.tmpl
rename to gbc/cmd/resources/version.tmpl
diff --git a/gbc/cmd/root.go b/gbc/cmd/root.go
new file mode 100644
index 0000000..997a63e
--- /dev/null
+++ b/gbc/cmd/root.go
@@ -0,0 +1,100 @@
+// Package cmd /*
+package cmd
+
+import (
+ "context"
+ "embed"
+ "errors"
+ "fmt"
+ "github.com/fatih/color"
+ "github.com/kcmvp/gob/gbc/artifact"
+ "github.com/samber/lo"
+ "github.com/spf13/cobra"
+ "os" //nolint
+ "strings" //nolint
+ "sync" //nolint
+)
+
+//go:embed resources/*
+var resources embed.FS
+
+var (
+ once sync.Once
+ template string
+)
+
+func usageTemplate() string {
+ once.Do(func() {
+ bytes, _ := resources.ReadFile("resources/usage.tmpl")
+ template = color.YellowString(string(bytes))
+ })
+ return template
+}
+
+const resourceDir = "resources"
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+ Use: "gbc",
+ Short: color.GreenString(`Go project boot command line`),
+ Long: color.GreenString(`Go project boot command line`),
+ ValidArgs: validBuilderArgs(),
+ Args: func(cmd *cobra.Command, args []string) error {
+ if err := OnlyValidArgs(cmd, args); err != nil {
+ return err
+ }
+ return MinimumNArgs(1)(cmd, args)
+ },
+ PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+ currentDir, _ := os.Getwd()
+ if artifact.CurProject().Root() != currentDir {
+ return fmt.Errorf(color.RedString("Please execute the command in the project root dir"))
+ }
+ return artifact.CurProject().Validate()
+ },
+ RunE: func(cmd *cobra.Command, args []string) error {
+ for _, arg := range lo.Uniq(args) {
+ if err := execute(cmd, arg); err != nil {
+ return errors.New(color.RedString("%s \n", err.Error()))
+ }
+ }
+ return nil
+ },
+}
+
+func rootExample() string {
+ format := fmt.Sprintf(" %%-%ds %%s", rootCmd.NamePadding())
+ builtIn := lo.Map(lo.Filter(buildActions(), func(item Action, _ int) bool {
+ return !strings.Contains(item.A, "_")
+ }), func(action Action, _ int) string {
+ return fmt.Sprintf(format, action.A, action.C)
+ })
+ lo.ForEach(artifact.CurProject().Plugins(), func(plugin artifact.Plugin, _ int) {
+ if !lo.ContainsBy(builtIn, func(item string) bool {
+ return strings.HasPrefix(strings.TrimSpace(item), strings.TrimSpace(plugin.Alias))
+ }) {
+ builtIn = append(builtIn, fmt.Sprintf(format, plugin.Alias, plugin.Description))
+ }
+ })
+ return strings.Join(builtIn, "\n")
+}
+
+func Execute() error {
+ ctx := context.Background()
+ if err := rootCmd.ExecuteContext(ctx); err != nil {
+ return fmt.Errorf(color.RedString(err.Error()))
+ }
+ return nil
+}
+
+func init() {
+ rootCmd.Example = rootExample()
+ rootCmd.SetUsageTemplate(usageTemplate())
+ rootCmd.SetErrPrefix(color.RedString("Error:"))
+ rootCmd.SetFlagErrorFunc(func(command *cobra.Command, err error) error {
+ return lo.IfF(err != nil, func() error {
+ return fmt.Errorf(color.RedString(err.Error()))
+ }).Else(nil)
+ })
+ rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
diff --git a/gbc/cmd/root_test.go b/gbc/cmd/root_test.go
new file mode 100644
index 0000000..15aca20
--- /dev/null
+++ b/gbc/cmd/root_test.go
@@ -0,0 +1,199 @@
+package cmd
+
+import (
+ "fmt"
+ "github.com/fatih/color"
+ "github.com/kcmvp/gob/gbc/artifact"
+ "github.com/kcmvp/gob/utils"
+ "github.com/samber/lo"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
+ "io"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+type RootTestSuit struct {
+ suite.Suite
+}
+
+func TestRootTestSuit(t *testing.T) {
+ suite.Run(t, &RootTestSuit{})
+}
+
+func (suite *RootTestSuit) BeforeTest(_, testName string) {
+ os.Chdir(artifact.CurProject().Root())
+ s, _ := os.Open(filepath.Join(artifact.CurProject().Root(), "gbc", "testdata", "gob.yaml"))
+ _, method := utils.TestCaller()
+ root := filepath.Join(artifact.CurProject().Root(), "target", strings.ReplaceAll(method, "_BeforeTest", fmt.Sprintf("_%s", testName)))
+ os.MkdirAll(root, os.ModePerm)
+ t, _ := os.Create(filepath.Join(root, "gob.yaml"))
+ io.Copy(t, s)
+ t.Close()
+ s.Close()
+ os.Stat(root)
+}
+
+func (suite *RootTestSuit) TearDownSuite() {
+ _, method := utils.TestCaller()
+ TearDownSuite(strings.TrimRight(method, "TearDownSuite"))
+}
+
+func (suite *RootTestSuit) TestValidArgs() {
+ args := rootCmd.ValidArgs
+ assert.Equal(suite.T(), 4, len(args))
+ assert.True(suite.T(), lo.Every(args, []string{"build", "clean", "test", "lint"}))
+}
+
+func (suite *RootTestSuit) TestArgs() {
+ tests := []struct {
+ name string
+ args []string
+ wantErr bool
+ }{
+ {
+ name: "not in valid args list",
+ args: []string{"def"},
+ wantErr: true,
+ },
+ {
+ name: "partial valid args",
+ args: []string{"build", "def"},
+ wantErr: true,
+ },
+ {
+ name: "no args",
+ args: []string{},
+ wantErr: true,
+ },
+ {
+ name: "positive case",
+ args: []string{"clean", "build"},
+ wantErr: false,
+ },
+ }
+ for _, test := range tests {
+ rootCmd.SetArgs(test.args)
+ err := Execute()
+ assert.True(suite.T(), test.wantErr == (err != nil))
+ }
+
+}
+
+func (suite *RootTestSuit) TestExecute() {
+ os.Chdir(artifact.CurProject().Target())
+ rootCmd.SetArgs([]string{"build"})
+ err := Execute()
+ assert.Equal(suite.T(), "Please execute the command in the project root dir", err.Error())
+ rootCmd.SetArgs([]string{"cd"})
+ err = Execute()
+ lo.ForEach([]string{"build", "clean", "test", "lint", "depth"}, func(item string, _ int) {
+ assert.Equal(suite.T(), "invalid argument \"cd\" for gbc", err.Error())
+ })
+ os.Chdir(artifact.CurProject().Root())
+ rootCmd.SetArgs([]string{"build"})
+ err = Execute()
+ assert.NoError(suite.T(), err)
+}
+
+func (suite *RootTestSuit) TestBuild() {
+ tests := []struct {
+ name string
+ args []string
+ wantErr bool
+ }{
+ {
+ name: "no args",
+ wantErr: true,
+ },
+ {
+ name: "invalid",
+ args: []string{"cd"},
+ wantErr: true,
+ },
+ {
+ name: "valid",
+ args: []string{"build"},
+ wantErr: false,
+ },
+ }
+ for _, test := range tests {
+ rootCmd.SetArgs(test.args)
+ err := rootCmd.Execute()
+ assert.True(suite.T(), test.wantErr == (err != nil))
+ if test.wantErr {
+ assert.True(suite.T(), strings.Contains(err.Error(), color.RedString("")))
+ }
+ }
+}
+
+func (suite *RootTestSuit) TestPersistentPreRun() {
+ rootCmd.SetArgs([]string{"build"})
+ Execute()
+ hooks := lo.MapToSlice(artifact.HookScripts(), func(key string, _ string) string {
+ return key
+ })
+ for _, hook := range hooks {
+ _, err := os.Stat(filepath.Join(artifact.CurProject().HookDir(), hook))
+ assert.NoError(suite.T(), err)
+ }
+}
+
+func (suite *RootTestSuit) TestBuiltinPlugins() {
+ plugins := builtinPlugins()
+ assert.Equal(suite.T(), 2, len(plugins))
+ plugin, ok := lo.Find(plugins, func(plugin artifact.Plugin) bool {
+ return plugin.Url == "github.com/golangci/golangci-lint/cmd/golangci-lint"
+ })
+ assert.True(suite.T(), ok)
+ assert.Equal(suite.T(), "v1.57.2", plugin.Version())
+ assert.Equal(suite.T(), "golangci-lint", plugin.Name())
+ assert.Equal(suite.T(), "github.com/golangci/golangci-lint", plugin.Module())
+ assert.Equal(suite.T(), "lint", plugin.Alias)
+ plugin, ok = lo.Find(plugins, func(plugin artifact.Plugin) bool {
+ return plugin.Url == "gotest.tools/gotestsum"
+ })
+ assert.True(suite.T(), ok)
+ assert.Equal(suite.T(), "v1.11.0", plugin.Version())
+ assert.Equal(suite.T(), "gotestsum", plugin.Name())
+ assert.Equal(suite.T(), "gotest.tools/gotestsum", plugin.Module())
+ assert.Equal(suite.T(), "test", plugin.Alias)
+}
+
+func (suite *RootTestSuit) TestRunE() {
+ target := artifact.CurProject().Target()
+ err := rootCmd.RunE(rootCmd, []string{"build"})
+ assert.NoError(suite.T(), err)
+ _, err = os.Stat(filepath.Join(target, lo.If(artifact.Windows(), "gbc.exe").Else("gbc")))
+ assert.NoError(suite.T(), err, "binary should be generated")
+ err = rootCmd.RunE(rootCmd, []string{"build", "clean"})
+ assert.NoError(suite.T(), err)
+ assert.NoFileExistsf(suite.T(), filepath.Join(target, lo.If(artifact.Windows(), "gob.exe").Else("gob")), "binary should be deleted")
+ err = rootCmd.RunE(rootCmd, []string{"def"})
+ assert.Errorf(suite.T(), err, "can not find the command def")
+}
+
+func (suite *RootTestSuit) TestOutOfRoot() {
+ os.Chdir(artifact.CurProject().Target())
+ err := Execute()
+ assert.Error(suite.T(), err)
+ assert.True(suite.T(), strings.Contains(err.Error(), "Please execute the command in the project root dir"))
+}
+
+func TearDownSuite(prefix string) {
+ filepath.WalkDir(os.TempDir(), func(path string, d fs.DirEntry, err error) error {
+ if d.IsDir() && strings.HasPrefix(d.Name(), prefix) {
+ os.RemoveAll(path)
+ }
+ return nil
+ })
+ filepath.WalkDir(filepath.Join(artifact.CurProject().Root(), "target"), func(path string, d fs.DirEntry, err error) error {
+ if d.IsDir() && strings.HasPrefix(d.Name(), prefix) {
+ os.RemoveAll(path)
+ }
+ return nil
+ })
+}
diff --git a/cmd/setup.go b/gbc/cmd/setup.go
similarity index 95%
rename from cmd/setup.go
rename to gbc/cmd/setup.go
index a1b1626..4c407cb 100644
--- a/cmd/setup.go
+++ b/gbc/cmd/setup.go
@@ -29,5 +29,5 @@ var setupCmd = &cobra.Command{
}
func init() {
- builderCmd.AddCommand(setupCmd)
+ rootCmd.AddCommand(setupCmd)
}
diff --git a/gbc/cmd/setup_test.go b/gbc/cmd/setup_test.go
new file mode 100644
index 0000000..e588827
--- /dev/null
+++ b/gbc/cmd/setup_test.go
@@ -0,0 +1,23 @@
+package cmd
+
+import (
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestValidSetupArgs(t *testing.T) {
+ assert.Equal(t, setupCmd.ValidArgs, []string{"version"})
+}
+
+//
+//func TestSetupVersion(t *testing.T) {
+// version := filepath.Join(artifact.CurProject().Root(), "infra", "version.go")
+// os.Remove(version)
+// _, err := os.Stat(version)
+// assert.Error(t, err)
+// rootCmd.SetArgs([]string{"setup", "version"})
+// err = rootCmd.Execute()
+// assert.NoError(t, err)
+// _, err = os.Stat(version)
+// assert.NoError(t, err)
+//}
diff --git a/gbc/cmd/validator.go b/gbc/cmd/validator.go
new file mode 100644
index 0000000..ef1d364
--- /dev/null
+++ b/gbc/cmd/validator.go
@@ -0,0 +1,27 @@
+package cmd
+
+import (
+ "errors"
+
+ "github.com/fatih/color"
+ "github.com/samber/lo"
+ "github.com/spf13/cobra"
+)
+
+func MinimumNArgs(n int) cobra.PositionalArgs {
+ return func(cmd *cobra.Command, args []string) error {
+ if len(args) < n {
+ return errors.New(color.RedString("requires at least %d arg(s), only received %d", n, len(args)))
+ }
+ return nil
+ }
+}
+
+func OnlyValidArgs(cmd *cobra.Command, args []string) error {
+ for _, arg := range args {
+ if !lo.Contains(cmd.ValidArgs, arg) {
+ return errors.New(color.RedString("invalid argument %q for %s", arg, cmd.CommandPath()))
+ }
+ }
+ return nil
+}
diff --git a/gob.go b/gbc/gbc.go
similarity index 64%
rename from gob.go
rename to gbc/gbc.go
index a97e0c9..f8249d4 100644
--- a/gob.go
+++ b/gbc/gbc.go
@@ -4,12 +4,12 @@ Copyright © 2023 kcheng.mvp@gmail.com
package main
import (
- "github.com/kcmvp/gob/cmd"
+ "github.com/kcmvp/gob/gbc/cmd"
"os" //nolint
)
func main() {
- if cmd.Execute() != nil {
+ if err := cmd.Execute(); err != nil {
os.Exit(1)
}
os.Exit(0)
diff --git a/testdata/config.json b/gbc/testdata/config.json
similarity index 100%
rename from testdata/config.json
rename to gbc/testdata/config.json
diff --git a/testdata/cover.out b/gbc/testdata/cover.out
similarity index 100%
rename from testdata/cover.out
rename to gbc/testdata/cover.out
diff --git a/testdata/gob.yaml b/gbc/testdata/gob.yaml
similarity index 100%
rename from testdata/gob.yaml
rename to gbc/testdata/gob.yaml
diff --git a/go.mod b/go.mod
index 5930fb9..609b890 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,12 @@ go 1.21.4
require (
github.com/creack/pty v1.1.21
github.com/fatih/color v1.16.0
+ github.com/iancoleman/strcase v0.3.0
github.com/jedib0t/go-pretty/v6 v6.5.4
+ github.com/kcmvp/structs v1.1.0
+ github.com/mattn/go-sqlite3 v1.14.22
+ github.com/samber/do/v2 v2.0.0-beta.5
+ github.com/samber/go-type-to-string v1.2.0
github.com/samber/lo v1.39.0
github.com/schollz/progressbar/v3 v3.14.1
github.com/spf13/cobra v1.8.0
diff --git a/go.sum b/go.sum
index bbc0d05..bffb49b 100644
--- a/go.sum
+++ b/go.sum
@@ -15,11 +15,15 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
+github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jedib0t/go-pretty/v6 v6.5.4 h1:gOGo0613MoqUcf0xCj+h/V3sHDaZasfv152G6/5l91s=
github.com/jedib0t/go-pretty/v6 v6.5.4/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
+github.com/kcmvp/structs v1.1.0 h1:SwJ8JQ2CWnPIJ3ncn19zE7nBQmPG5oxVTw2eKY64g2U=
+github.com/kcmvp/structs v1.1.0/go.mod h1:JBFThRiqYYNjAgfWKaFJGXHecQEwaSQfyp+ACFfRJSY=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -36,6 +40,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
+github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@@ -55,6 +61,10 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/samber/do/v2 v2.0.0-beta.5 h1:KpQhVkkzDlsLSDC5WXgyCL8Q3SqOYoInFJIbvntPazM=
+github.com/samber/do/v2 v2.0.0-beta.5/go.mod h1:FNMy1RSKMX11Ag8v4KW95n9k+ZkCXn8GuvDKufVKN9E=
+github.com/samber/go-type-to-string v1.2.0 h1:Pvdqx3r/EHn9/DTKoW6RoHz/850s5yV1vA6MqKKG5Ys=
+github.com/samber/go-type-to-string v1.2.0/go.mod h1:jpU77vIDoIxkahknKDoEx9C8bQ1ADnh2sotZ8I4QqBU=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/schollz/progressbar/v3 v3.14.1 h1:VD+MJPCr4s3wdhTc7OEJ/Z3dAeBzJ7yKH/P4lC5yRTI=
@@ -93,6 +103,8 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
+go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
diff --git a/gob.yaml b/gob.yaml
index 94819c5..6292f79 100644
--- a/gob.yaml
+++ b/gob.yaml
@@ -10,7 +10,9 @@ plugins:
alias: lint
args: run ./...
url: github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
+ description: run golangci-lint against project
gotestsum:
alias: test
args: --format testname -- -coverprofile=target/cover.out ./...
url: gotest.tools/gotestsum@v1.11.0
+ description: test project with gotest
diff --git a/internal/container.go b/internal/container.go
new file mode 100644
index 0000000..2104942
--- /dev/null
+++ b/internal/container.go
@@ -0,0 +1,33 @@
+package internal
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+
+ "github.com/kcmvp/gob/utils"
+ "github.com/samber/do/v2"
+)
+
+var (
+ Container *do.RootScope
+ RootDir string
+)
+
+func init() {
+ Container = do.NewWithOpts(&do.InjectorOpts{
+ HookAfterRegistration: func(scope *do.Scope, serviceName string) {
+ fmt.Printf("scope is %s, name is %s \n", scope.Name(), serviceName)
+ //@todo, parse the mapping once
+ },
+ Logf: func(format string, args ...any) {
+ log.Printf(format, args...)
+ },
+ })
+ if output, err := exec.Command("go", "list", "-f", "{{.Root}}").CombinedOutput(); err == nil {
+ RootDir = utils.CleanStr(string(output))
+ } else {
+ RootDir, _ = os.Executable()
+ }
+}
diff --git a/internal/parser.go b/internal/parser.go
new file mode 100644
index 0000000..a1cb571
--- /dev/null
+++ b/internal/parser.go
@@ -0,0 +1,72 @@
+package internal
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/iancoleman/strcase"
+ "github.com/kcmvp/structs"
+ "github.com/samber/lo"
+)
+
+type Mapper lo.Tuple3[string, string, []string]
+
+func (mapper Mapper) String() string {
+ return fmt.Sprintf("%s_%s_%s", mapper.A, mapper.B, strings.Join(mapper.C, "_"))
+}
+
+func (mapper Mapper) IsPK() bool {
+ return lo.ContainsBy(mapper.C, func(item string) bool {
+ return string(PK) == item
+ })
+}
+
+type Attribute string
+
+const (
+ // DBTag struct tag name
+ DBTag = "db"
+ // AutoUpdateTime attribute tag with `aut` will be set to time.Now() for update
+ AutoUpdateTime Attribute = "aut"
+ // AutoCreateTime attribute tag with `act` will be set to time.Now() for creation
+ AutoCreateTime Attribute = "act"
+ // PK identify a column is primary key
+ PK Attribute = "pk"
+ // Ignore identify this attribute would not map to database column
+ Ignore Attribute = "ignore"
+ // Name database column name
+ Name Attribute = "name"
+ // Type database column type
+ Type Attribute = "type"
+)
+
+func Parse(str any) []Mapper {
+ var mappers []Mapper
+ return append(mappers, lo.FilterMap(structs.Fields(str), func(f *structs.Field, _ int) (Mapper, bool) {
+ if !f.IsExported() {
+ return Mapper{}, false
+ }
+ if f.IsEmbedded() && f.Kind().String() == "struct" {
+ mappers = append(mappers, Parse(f.Value())...)
+ return Mapper{}, false
+ }
+ colName := strcase.ToSnake(f.Name())
+ var attrs []string
+ ignore := false
+ if tag := f.Tag(DBTag); tag != "" {
+ prefix := fmt.Sprintf("%s=", Name)
+ attrs = strings.Split(tag, ";")
+ ignore = lo.ContainsBy(attrs, func(attr string) bool {
+ return string(Ignore) == strings.TrimSpace(attr)
+ })
+ if !ignore {
+ lo.ForEach(attrs, func(item string, _ int) {
+ if strings.HasPrefix(item, prefix) {
+ colName = strings.TrimLeft(item, prefix)
+ }
+ })
+ }
+ }
+ return Mapper{A: f.Name(), B: colName, C: attrs}, !ignore
+ })...)
+}
diff --git a/internal/parser_test.go b/internal/parser_test.go
new file mode 100644
index 0000000..69bb2a0
--- /dev/null
+++ b/internal/parser_test.go
@@ -0,0 +1,83 @@
+package internal
+
+import (
+ "database/sql"
+ "github.com/samber/lo"
+ "github.com/stretchr/testify/assert"
+ "testing"
+ "time"
+)
+
+type Base struct {
+ CreatedAt time.Time `db:"act;name=ct_time"`
+ CreatedBy string `db:"type=varchar(20)"`
+ UpdatedAt time.Time `db:"aut"`
+ UpdatedBy string `db:"type=varchar(20)"`
+}
+type Base1 struct {
+ Id int64 `db:"pk;type=integer"`
+ CreatedAt time.Time
+ UpdatedAt time.Time `db:"ignore"`
+}
+type Base2 struct {
+ CreatedAt time.Time
+ UpdatedAt time.Time `db:"ignore;name=abc;type=varchar(20)"`
+}
+
+type Product struct {
+ Base1
+ Address sql.NullString `db:"name=full_address"`
+ comment string
+}
+
+func TestParse(t *testing.T) {
+
+ tests := []struct {
+ name string
+ arg any
+ want []string
+ }{
+ {
+ name: "Simple",
+ arg: Base{},
+ want: []string{
+ Mapper{"CreatedAt", "ct_time", []string{"act", "name=ct_time"}}.String(),
+ Mapper{"CreatedBy", "created_by", []string{"type=varchar(20)"}}.String(),
+ Mapper{"UpdatedAt", "updated_at", []string{"aut"}}.String(),
+ Mapper{"UpdatedBy", "updated_by", []string{"type=varchar(20)"}}.String(),
+ },
+ },
+ {
+ name: "Ignore_case1",
+ arg: Base1{},
+ want: []string{
+ Mapper{A: "CreatedAt", B: "created_at"}.String(),
+ Mapper{A: "Id", B: "id", C: []string{"pk", "type=integer"}}.String(),
+ },
+ },
+ {
+ name: "Ignore_case2",
+ arg: Base2{},
+ want: []string{
+ Mapper{A: "CreatedAt", B: "created_at"}.String(),
+ },
+ },
+ {
+ name: "embedded",
+ arg: Product{},
+ want: []string{
+ Mapper{A: "CreatedAt", B: "created_at"}.String(),
+ Mapper{A: "Id", B: "id", C: []string{"pk", "type=integer"}}.String(),
+ Mapper{A: "Address", B: "full_address", C: []string{"name=full_address"}}.String(),
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := lo.Map(Parse(tt.arg), func(item Mapper, _ int) string {
+ return item.String()
+ })
+ assert.True(t, lo.Every(got, tt.want))
+ })
+ }
+}
diff --git a/internal/type.go b/internal/type.go
new file mode 100644
index 0000000..de2c31b
--- /dev/null
+++ b/internal/type.go
@@ -0,0 +1,136 @@
+package internal
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/samber/lo"
+)
+
+const (
+ MySQL = "mysql"
+ PostgreSQL = "postgreSQL"
+ SQLite = "sqlite"
+)
+
+type (
+ GoType string
+ DBType lo.Tuple3[string, []string, []string]
+)
+
+func ParseDBType() (DBType, error) {
+ var dbName string
+ db, ok := lo.Find([]DBType{
+ {A: MySQL, B: []string{"auto_increment"}, C: []string{"github.com/go-sql-driver/mysql"}},
+ {A: PostgreSQL, B: []string{"smallserial", "serial", "bigserial"}, C: []string{"github.com/lib/pq", "github.com/jackc/pgx"}},
+ {A: SQLite, B: []string{"autoincrement"}, C: []string{"github.com/mattn/go-sqlite3"}},
+ }, func(item DBType) bool {
+ return strings.Contains(dbName, item.A)
+ })
+ if !ok {
+ return DBType{}, fmt.Errorf("can not find database driver in go.mod")
+ }
+ return db, nil
+}
+
+func (p DBType) PrimaryStr() []string {
+ return p.B
+}
+
+var TypeMappings = []lo.Tuple4[GoType, string, string, string]{
+ {
+ A: "string",
+ B: "varchar", // MySQL
+ C: "varchar", // PostgreSQL
+ D: "text", // SQLite
+ },
+ {
+ A: "bool",
+ B: "boolean",
+ C: "boolean",
+ D: "integer",
+ },
+ {
+ A: "int8",
+ B: "tinyint",
+ C: "int8",
+ D: "integer",
+ },
+ // unsigned, not a SQL standard
+ {
+ A: "uint8",
+ B: "tinyint unsigned",
+ C: "int8",
+ D: "integer",
+ },
+ // unsigned, not a SQL standard
+ {
+ A: "byte",
+ B: "tinyint unsigned",
+ C: "int8",
+ D: "integer",
+ },
+ {
+ A: "int16",
+ B: "smallint",
+ C: "smallint",
+ D: "integer",
+ },
+ // unsigned, not a SQL standard
+ {
+ A: "uint16",
+ B: "smallint",
+ C: "smallint",
+ D: "integer",
+ },
+ {
+ A: "int32",
+ B: "int",
+ C: "integer",
+ D: "integer",
+ },
+ {
+ A: "rune",
+ B: "int",
+ C: "integer",
+ D: "integer",
+ },
+ // unsigned, not a SQL standard
+ {
+ A: "uint32",
+ B: "int",
+ C: "integer",
+ D: "integer",
+ },
+ {
+ A: "int64",
+ B: "bigint",
+ C: "bigint",
+ D: "integer",
+ },
+ // unsigned, not a SQL standard
+ {
+ A: "uint64",
+ B: "bigint",
+ C: "bigint",
+ D: "integer",
+ },
+ {
+ A: "float32",
+ B: "float",
+ C: "double precision",
+ D: "double precision",
+ },
+ {
+ A: "float64",
+ B: "decimal",
+ C: "decimal",
+ D: "decimal",
+ },
+ {
+ A: "time",
+ B: "timestamp",
+ C: "timestamp",
+ D: "datetime",
+ },
+}
diff --git a/sqlite3-init.sql b/sqlite3-init.sql
new file mode 100644
index 0000000..1244f3a
--- /dev/null
+++ b/sqlite3-init.sql
@@ -0,0 +1 @@
+insert into Product(Name) values ('Apple'),('Peach');
diff --git a/sqlite3-schema.sql b/sqlite3-schema.sql
new file mode 100644
index 0000000..64c5093
--- /dev/null
+++ b/sqlite3-schema.sql
@@ -0,0 +1,5 @@
+CREATE TABLE Product
+(
+ id INTEGER PRIMARY KEY,
+ Name text
+);
diff --git a/utils/utils.go b/utils/utils.go
new file mode 100644
index 0000000..23f402a
--- /dev/null
+++ b/utils/utils.go
@@ -0,0 +1,46 @@
+package utils
+
+import (
+ "regexp"
+ "runtime"
+ "strings"
+
+ "github.com/samber/lo"
+)
+
+// CleanStr Function to remove non-printable characters
+func CleanStr(str string) string {
+ cleanStr := func(r rune) rune {
+ if r >= 32 && r != 127 {
+ return r
+ }
+ return -1
+ }
+ return strings.Map(cleanStr, str)
+}
+
+func TestCaller() (bool, string) {
+ var test bool
+ var frame runtime.Frame
+ more := true
+ callers := make([]uintptr, 20)
+ for {
+ size := runtime.Callers(0, callers)
+ if size == len(callers) {
+ callers = make([]uintptr, 2*len(callers))
+ continue
+ }
+ frames := runtime.CallersFrames(callers[:size])
+ for !test && more {
+ frame, more = frames.Next()
+ // fmt.Printf("%s: %s\size", frame.Function, frame.File)
+ test = strings.HasSuffix(frame.File, "_test.go")
+ }
+ break
+ }
+ fqn, _ := lo.Last(strings.Split(frame.Function, "/"))
+ re := regexp.MustCompile(`\(\*|\)`)
+ fqn = re.ReplaceAllString(fqn, "")
+ fqn = strings.ReplaceAll(fqn, ".", "_")
+ return test, fqn
+}