diff --git a/.github/workflows/go-tests.yaml b/.github/workflows/go-tests.yaml index db4fadb..2ea78ed 100644 --- a/.github/workflows/go-tests.yaml +++ b/.github/workflows/go-tests.yaml @@ -2,7 +2,7 @@ name: Go Tests on: push: - branches: [ 'main', 'develop' ] + branches: [ '*', 'develop' ] pull_request: branches: [ '*' ] diff --git a/Makefile b/Makefile index 89512db..368b406 100644 --- a/Makefile +++ b/Makefile @@ -71,3 +71,8 @@ dist: clean: rm -rf gozen* rm -rf build + +generate-mocks: + # Mockery version v2.50.0 + @mockery --name=ShellRepo --dir=cmd/repository --output=cmd/repository --outpkg=repository --filename=shell_repo_mock.go --structname=ShellRepoMock + @mockery --name=FileSystemRepo --dir=cmd/repository --output=cmd/repository --outpkg=repository --filename=file_system_repo_mock.go --structname=FileSystemRepoMock \ No newline at end of file diff --git a/cmd/app.go b/cmd/app.go new file mode 100644 index 0000000..dbc865e --- /dev/null +++ b/cmd/app.go @@ -0,0 +1,76 @@ +package cmd + +import ( + "fmt" + + "github.com/tech-thinker/gozen/cmd/service" + "github.com/tech-thinker/gozen/models" + "github.com/urfave/cli/v2" +) + +type App interface { + CreateProject() *cli.Command +} + +type app struct { + appService service.AppService +} + +func (c *app) CreateProject() *cli.Command { + + var packageName, outputDir, driver string + + return &cli.Command{ + Name: "create", + Usage: "Create new Projects.", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "pkg", + Aliases: []string{"p"}, + Value: "", + Usage: "Package name for new project.", + Destination: &packageName, + }, + &cli.StringFlag{ + Name: "output", + Aliases: []string{"o"}, + Value: ".", + Usage: "Output directory for new project.", + Destination: &outputDir, + }, + &cli.StringFlag{ + Name: "driver", + Aliases: []string{"d"}, + Value: "sqlite", + Usage: "Database driver for new project. eg. [sqlite, mysql, postgres]", + Destination: &driver, + }, + }, + Action: func(ctx *cli.Context) error { + project := models.Project{ + AppName: ctx.Args().Get(0), + PackageName: packageName, + Driver: driver, + WorkingDir: outputDir, + } + + err := project.Validate() + if err != nil { + fmt.Println(err) + return nil + } + + project.AutoFixes() + + return c.appService.CreateApp(project) + }, + } +} + +func NewApp( + appService service.AppService, +) App { + return &app{ + appService: appService, + } +} diff --git a/cmd/helper/common.go b/cmd/helper/common.go index 4438158..d1707d8 100644 --- a/cmd/helper/common.go +++ b/cmd/helper/common.go @@ -3,10 +3,10 @@ package helper import ( "embed" "fmt" - "os/exec" "path/filepath" "strings" + "github.com/tech-thinker/gozen/cmd/repository" "github.com/tech-thinker/gozen/utils" ) @@ -17,13 +17,15 @@ type CommonHelper interface { } type commonHelper struct { - templatesFS embed.FS + templatesFS embed.FS + shellRepo repository.ShellRepo + fileSystemRepo repository.FileSystemRepo } // Write: generate code and write to file func (helper *commonHelper) Write(templatePath string, outputPath string, data interface{}) error { baseDir := filepath.Dir(outputPath) - err := utils.CreateDirectory(baseDir) + err := helper.fileSystemRepo.CreateDirectory(baseDir) if err != nil { return err } @@ -31,14 +33,14 @@ func (helper *commonHelper) Write(templatePath string, outputPath string, data i if err != nil { return err } - return utils.WriteFile(outputPath, tpl) + tpl = utils.ApplyEscapeChar(tpl) + return helper.fileSystemRepo.WriteFile(outputPath, tpl) } // ExecShell: execute shell command and return output as string slice func (helper *commonHelper) ExecShell(command string, args ...string) ([]string, error) { fmt.Printf(`%s %+v\n`, command, args) - cmd := exec.Command(command, args...) - output, err := cmd.Output() + output, err := helper.shellRepo.Exec(command, args...) if err != nil { fmt.Println("Error executing command:", err) return nil, err @@ -50,8 +52,7 @@ func (helper *commonHelper) ExecShell(command string, args ...string) ([]string, // ExecShellRaw: execute shell command and return output as byte array func (helper *commonHelper) ExecShellRaw(command string, args ...string) ([]byte, error) { fmt.Printf(`%s %+v\n`, command, args) - cmd := exec.Command(command, args...) - output, err := cmd.Output() + output, err := helper.shellRepo.Exec(command, args...) if err != nil { fmt.Println("Error executing command:", err) return nil, err @@ -60,8 +61,14 @@ func (helper *commonHelper) ExecShellRaw(command string, args ...string) ([]byte } // NewCommonHelper returns a new CommonHelper -func NewCommonHelper(tpl embed.FS) CommonHelper { +func NewCommonHelper( + tpl embed.FS, + shellRepo repository.ShellRepo, + fileSystemRepo repository.FileSystemRepo, +) CommonHelper { return &commonHelper{ - templatesFS: tpl, + templatesFS: tpl, + shellRepo: shellRepo, + fileSystemRepo: fileSystemRepo, } } diff --git a/cmd/helper/common_test.go b/cmd/helper/common_test.go new file mode 100644 index 0000000..e04ce23 --- /dev/null +++ b/cmd/helper/common_test.go @@ -0,0 +1,257 @@ +package helper + +import ( + "embed" + "errors" + "reflect" + "testing" + + "github.com/stretchr/testify/mock" + "github.com/tech-thinker/gozen/cmd/repository" + "github.com/tech-thinker/gozen/utils" +) + +func Test_commonHelper_Write(t *testing.T) { + type fields struct { + templatesFS embed.FS + shellRepo *repository.ShellRepoMock + fileSystemRepo *repository.FileSystemRepoMock + } + type args struct { + templatePath string + outputPath string + data interface{} + } + tests := []struct { + name string + fields fields + prepare func(f *fields) + args args + wantErr bool + }{ + { + name: "with valid data should success", + fields: fields{}, + prepare: func(f *fields) { + f.fileSystemRepo.On("CreateDirectory", mock.Anything).Return(nil) + f.fileSystemRepo.On("WriteFile", mock.Anything, mock.Anything).Return(nil) + }, + args: args{ + templatePath: "test.tmpl", + outputPath: "./test.go", + data: struct{ Name string }{Name: "Test"}, + }, + wantErr: false, + }, + { + name: "with directory creation fail should fail", + fields: fields{}, + prepare: func(f *fields) { + f.fileSystemRepo.On("CreateDirectory", mock.Anything).Return(errors.New("error")) + }, + args: args{ + templatePath: "test.tmpl", + outputPath: "./test.go", + data: struct{ Name string }{Name: "Test"}, + }, + wantErr: true, + }, + { + name: "with file write fail should fail", + fields: fields{}, + prepare: func(f *fields) { + f.fileSystemRepo.On("CreateDirectory", mock.Anything).Return(nil) + f.fileSystemRepo.On("WriteFile", mock.Anything, mock.Anything).Return(errors.New("error")) + }, + args: args{ + templatePath: "test.tmpl", + outputPath: "./test.go", + data: struct{ Name string }{Name: "Test"}, + }, + wantErr: true, + }, + { + name: "with code generation fail should fail", + fields: fields{}, + prepare: func(f *fields) { + f.fileSystemRepo.On("CreateDirectory", mock.Anything).Return(nil) + }, + args: args{ + templatePath: "test.tmpl", + outputPath: "./test.go", + data: struct{ name string }{name: "Test"}, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Preparing mocks + tt.fields = fields{ + templatesFS: utils.GetMockTmpl(), + shellRepo: repository.NewShellRepoMock(t), + fileSystemRepo: repository.NewFileSystemRepoMock(t), + } + + if tt.prepare != nil { + tt.prepare(&tt.fields) + } + + helper := NewCommonHelper( + tt.fields.templatesFS, + tt.fields.shellRepo, + tt.fields.fileSystemRepo, + ) + if err := helper.Write(tt.args.templatePath, tt.args.outputPath, tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("commonHelper.Write() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_commonHelper_ExecShell(t *testing.T) { + type fields struct { + templatesFS embed.FS + shellRepo *repository.ShellRepoMock + fileSystemRepo *repository.FileSystemRepoMock + } + type args struct { + command string + args []string + } + tests := []struct { + name string + fields fields + prepare func(f *fields) + args args + want []string + wantErr bool + }{ + { + name: "with valid command should success", + fields: fields{}, + prepare: func(f *fields) { + f.shellRepo.On("Exec", mock.Anything).Return([]byte("test.txt"), nil) + }, + args: args{ + command: "ls", + }, + want: []string{"test.txt"}, + wantErr: false, + }, + { + name: "with invalid command should success", + fields: fields{}, + prepare: func(f *fields) { + f.shellRepo.On("Exec", mock.Anything).Return([]byte(""), errors.New("ls no such command found")) + }, + args: args{ + command: "ls", + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + // Preparing mocks + tt.fields = fields{ + templatesFS: utils.GetMockTmpl(), + shellRepo: repository.NewShellRepoMock(t), + fileSystemRepo: repository.NewFileSystemRepoMock(t), + } + + if tt.prepare != nil { + tt.prepare(&tt.fields) + } + + helper := NewCommonHelper( + tt.fields.templatesFS, + tt.fields.shellRepo, + tt.fields.fileSystemRepo, + ) + got, err := helper.ExecShell(tt.args.command, tt.args.args...) + if (err != nil) != tt.wantErr { + t.Errorf("commonHelper.ExecShell() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("commonHelper.ExecShell() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_commonHelper_ExecShellRaw(t *testing.T) { + type fields struct { + templatesFS embed.FS + shellRepo *repository.ShellRepoMock + fileSystemRepo *repository.FileSystemRepoMock + } + type args struct { + command string + args []string + } + tests := []struct { + name string + fields fields + prepare func(f *fields) + args args + want []byte + wantErr bool + }{ + { + name: "with valid command should success", + fields: fields{}, + prepare: func(f *fields) { + f.shellRepo.On("Exec", mock.Anything).Return([]byte("test.txt"), nil) + }, + args: args{ + command: "ls", + }, + want: []byte("test.txt"), + wantErr: false, + }, + { + name: "with invalid command should success", + fields: fields{}, + prepare: func(f *fields) { + f.shellRepo.On("Exec", mock.Anything).Return([]byte(""), errors.New("ls no such command found")) + }, + args: args{ + command: "ls", + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Preparing mocks + tt.fields = fields{ + templatesFS: utils.GetMockTmpl(), + shellRepo: repository.NewShellRepoMock(t), + fileSystemRepo: repository.NewFileSystemRepoMock(t), + } + + if tt.prepare != nil { + tt.prepare(&tt.fields) + } + + helper := NewCommonHelper( + tt.fields.templatesFS, + tt.fields.shellRepo, + tt.fields.fileSystemRepo, + ) + got, err := helper.ExecShellRaw(tt.args.command, tt.args.args...) + if (err != nil) != tt.wantErr { + t.Errorf("commonHelper.ExecShellRaw() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("commonHelper.ExecShellRaw() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/cmd/repository/file_system_repo.go b/cmd/repository/file_system_repo.go new file mode 100644 index 0000000..1ec4d9f --- /dev/null +++ b/cmd/repository/file_system_repo.go @@ -0,0 +1,36 @@ +package repository + +import ( + "fmt" + "os" +) + +// FileSystemRepo is an abstructtion of file handling system library, no test cases required for this +type FileSystemRepo interface { + CreateDirectory(path string) error + WriteFile(path, data string) error +} + +type fileSystemRepo struct { +} + +func (repo *fileSystemRepo) CreateDirectory(path string) error { + return os.MkdirAll(path, os.ModePerm) +} + +func (repo *fileSystemRepo) WriteFile(path, data string) error { + file, err := os.Create(path) + if err != nil { + return err + } + defer file.Close() + _, err = fmt.Fprintf(file, `%s`, data) + if err != nil { + return err + } + return nil +} + +func NewFileSystemRepo() FileSystemRepo { + return &fileSystemRepo{} +} diff --git a/cmd/repository/file_system_repo_mock.go b/cmd/repository/file_system_repo_mock.go new file mode 100644 index 0000000..a12aa71 --- /dev/null +++ b/cmd/repository/file_system_repo_mock.go @@ -0,0 +1,60 @@ +// Code generated by mockery v2.50.0. DO NOT EDIT. + +package repository + +import mock "github.com/stretchr/testify/mock" + +// FileSystemRepoMock is an autogenerated mock type for the FileSystemRepo type +type FileSystemRepoMock struct { + mock.Mock +} + +// CreateDirectory provides a mock function with given fields: path +func (_m *FileSystemRepoMock) CreateDirectory(path string) error { + ret := _m.Called(path) + + if len(ret) == 0 { + panic("no return value specified for CreateDirectory") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(path) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// WriteFile provides a mock function with given fields: path, data +func (_m *FileSystemRepoMock) WriteFile(path string, data string) error { + ret := _m.Called(path, data) + + if len(ret) == 0 { + panic("no return value specified for WriteFile") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(path, data) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewFileSystemRepoMock creates a new instance of FileSystemRepoMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewFileSystemRepoMock(t interface { + mock.TestingT + Cleanup(func()) +}) *FileSystemRepoMock { + mock := &FileSystemRepoMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/cmd/repository/shell_repo.go b/cmd/repository/shell_repo.go new file mode 100644 index 0000000..a557e7d --- /dev/null +++ b/cmd/repository/shell_repo.go @@ -0,0 +1,20 @@ +package repository + +import "os/exec" + +// ShellRepo is an abstructtion of shell command execution system library, no test cases required for this +type ShellRepo interface { + Exec(name string, args ...string) ([]byte, error) +} + +type shellRepo struct { +} + +func (*shellRepo) Exec(name string, args ...string) ([]byte, error) { + cmd := exec.Command(name, args...) + return cmd.Output() +} + +func NewShellRepo() ShellRepo { + return &shellRepo{} +} diff --git a/cmd/repository/shell_repo_mock.go b/cmd/repository/shell_repo_mock.go new file mode 100644 index 0000000..2ef0ca3 --- /dev/null +++ b/cmd/repository/shell_repo_mock.go @@ -0,0 +1,61 @@ +// Code generated by mockery v2.50.0. DO NOT EDIT. + +package repository + +import mock "github.com/stretchr/testify/mock" + +// ShellRepoMock is an autogenerated mock type for the ShellRepo type +type ShellRepoMock struct { + mock.Mock +} + +// Exec provides a mock function with given fields: name, args +func (_m *ShellRepoMock) Exec(name string, args ...string) ([]byte, error) { + _va := make([]interface{}, len(args)) + for _i := range args { + _va[_i] = args[_i] + } + var _ca []interface{} + _ca = append(_ca, name) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Exec") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(string, ...string) ([]byte, error)); ok { + return rf(name, args...) + } + if rf, ok := ret.Get(0).(func(string, ...string) []byte); ok { + r0 = rf(name, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(string, ...string) error); ok { + r1 = rf(name, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewShellRepoMock creates a new instance of ShellRepoMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewShellRepoMock(t interface { + mock.TestingT + Cleanup(func()) +}) *ShellRepoMock { + mock := &ShellRepoMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/cmd/create_app.go b/cmd/service/create_app.go similarity index 57% rename from cmd/create_app.go rename to cmd/service/create_app.go index 5f63ec5..92ad9f9 100644 --- a/cmd/create_app.go +++ b/cmd/service/create_app.go @@ -1,89 +1,89 @@ -package cmd +package service import ( "fmt" "github.com/tech-thinker/gozen/cmd/helper" + "github.com/tech-thinker/gozen/cmd/repository" "github.com/tech-thinker/gozen/constants" "github.com/tech-thinker/gozen/models" - "github.com/tech-thinker/gozen/utils" ) -type AppCmd interface { - CreateApp() error +type AppService interface { + CreateApp(project models.Project) error } -type appCmd struct { - project models.Project - helper helper.CommonHelper +type appService struct { + fileSystemRepo repository.FileSystemRepo + helper helper.CommonHelper } -func (cmd *appCmd) CreateApp() error { +func (cmd *appService) CreateApp(project models.Project) error { workDir := constants.CURRENT_WORKING_DIRECTORY - if len(cmd.project.WorkingDir) > 1 { - workDir = cmd.project.WorkingDir + if len(project.WorkingDir) > 1 { + workDir = project.WorkingDir } - appPath := fmt.Sprintf(`%s/%s`, workDir, cmd.project.AppName) + appPath := fmt.Sprintf(`%s/%s`, workDir, project.AppName) fmt.Println("Project created at location: " + appPath) - utils.CreateDirectory(appPath) + cmd.fileSystemRepo.CreateDirectory(appPath) // Write project config into file - if err := cmd.project.WriteToJsonFile(); err != nil { + if err := project.WriteToJsonFile(); err != nil { return err } // Generating basic codes - cmd.helper.Write("templates/env.sample.tmpl", appPath+"/.env", cmd.project) - cmd.helper.Write("templates/env.sample.tmpl", appPath+"/.env.sample", cmd.project) - cmd.helper.Write("templates/env.sample.tmpl", appPath+"/docker/.env", cmd.project) - cmd.helper.Write("templates/env.sample.tmpl", appPath+"/docker/.env.sample", cmd.project) - cmd.helper.Write("templates/docker/Dockerfile.debug", appPath+"/docker/Dockerfile.debug", cmd.project) - cmd.helper.Write("templates/docker/Dockerfile.dev", appPath+"/docker/Dockerfile.dev", cmd.project) - cmd.helper.Write("templates/docker/Dockerfile.prod", appPath+"/docker/Dockerfile.prod", cmd.project) - cmd.helper.Write("templates/docker/docker-compose-debug.yml", appPath+"/docker/docker-compose-debug.yml", cmd.project) - cmd.helper.Write("templates/docker/docker-compose.yml", appPath+"/docker/docker-compose.yml", cmd.project) - cmd.helper.Write("templates/docker/modd-debug.conf", appPath+"/docker/modd-debug.conf", cmd.project) - cmd.helper.Write("templates/docker/modd-dev.conf", appPath+"/docker/modd-dev.conf", cmd.project) + cmd.helper.Write("templates/env.sample.tmpl", appPath+"/.env", project) + cmd.helper.Write("templates/env.sample.tmpl", appPath+"/.env.sample", project) + cmd.helper.Write("templates/env.sample.tmpl", appPath+"/docker/.env", project) + cmd.helper.Write("templates/env.sample.tmpl", appPath+"/docker/.env.sample", project) + cmd.helper.Write("templates/docker/Dockerfile.debug", appPath+"/docker/Dockerfile.debug", project) + cmd.helper.Write("templates/docker/Dockerfile.dev", appPath+"/docker/Dockerfile.dev", project) + cmd.helper.Write("templates/docker/Dockerfile.prod", appPath+"/docker/Dockerfile.prod", project) + cmd.helper.Write("templates/docker/docker-compose-debug.yml", appPath+"/docker/docker-compose-debug.yml", project) + cmd.helper.Write("templates/docker/docker-compose.yml", appPath+"/docker/docker-compose.yml", project) + cmd.helper.Write("templates/docker/modd-debug.conf", appPath+"/docker/modd-debug.conf", project) + cmd.helper.Write("templates/docker/modd-dev.conf", appPath+"/docker/modd-dev.conf", project) - cmd.helper.Write("templates/gitignore.tmpl", appPath+"/.gitignore", cmd.project) - cmd.helper.Write("templates/Makefile.tmpl", appPath+"/Makefile", cmd.project) - cmd.helper.Write("templates/go.tmpl", appPath+"/go.mod", cmd.project) - cmd.helper.Write("templates/main.tmpl", appPath+"/main.go", cmd.project) + cmd.helper.Write("templates/gitignore.tmpl", appPath+"/.gitignore", project) + cmd.helper.Write("templates/Makefile.tmpl", appPath+"/Makefile", project) + cmd.helper.Write("templates/go.tmpl", appPath+"/go.mod", project) + cmd.helper.Write("templates/main.tmpl", appPath+"/main.go", project) - cmd.helper.Write("templates/app/init.tmpl", appPath+"/app/init.go", cmd.project) + cmd.helper.Write("templates/app/init.tmpl", appPath+"/app/init.go", project) - cmd.helper.Write("templates/app/rest/controllers/health.tmpl", appPath+"/app/rest/controllers/health.go", cmd.project) - cmd.helper.Write("templates/app/rest/router/router.tmpl", appPath+"/app/rest/router/router.go", cmd.project) + cmd.helper.Write("templates/app/rest/controllers/health.tmpl", appPath+"/app/rest/controllers/health.go", project) + cmd.helper.Write("templates/app/rest/router/router.tmpl", appPath+"/app/rest/router/router.go", project) - cmd.helper.Write("templates/app/grpc/handlers/health.tmpl", appPath+"/app/grpc/handlers/health.go", cmd.project) - cmd.helper.Write("templates/app/grpc/proto/health.proto", appPath+"/app/grpc/proto/health.proto", cmd.project) - cmd.helper.Write("templates/app/grpc/proto/health.pb.tmpl", appPath+"/app/grpc/proto/health.pb.go", cmd.project) - cmd.helper.Write("templates/app/grpc/proto/health_grpc.pb.tmpl", appPath+"/app/grpc/proto/health_hrpc.pb.go", cmd.project) - cmd.helper.Write("templates/app/grpc/router/router.tmpl", appPath+"/app/grpc/router/router.go", cmd.project) + cmd.helper.Write("templates/app/grpc/handlers/health.tmpl", appPath+"/app/grpc/handlers/health.go", project) + cmd.helper.Write("templates/app/grpc/proto/health.proto", appPath+"/app/grpc/proto/health.proto", project) + cmd.helper.Write("templates/app/grpc/proto/health.pb.tmpl", appPath+"/app/grpc/proto/health.pb.go", project) + cmd.helper.Write("templates/app/grpc/proto/health_grpc.pb.tmpl", appPath+"/app/grpc/proto/health_hrpc.pb.go", project) + cmd.helper.Write("templates/app/grpc/router/router.tmpl", appPath+"/app/grpc/router/router.go", project) - cmd.helper.Write("templates/config/config.tmpl", appPath+"/config/config.go", cmd.project) - cmd.helper.Write("templates/constants/app.tmpl", appPath+"/constants/app.go", cmd.project) - cmd.helper.Write("templates/instance/instance.tmpl", appPath+"/instance/instance.go", cmd.project) - cmd.helper.Write("templates/logger/logger.tmpl", appPath+"/logger/logger.go", cmd.project) - cmd.helper.Write("templates/models/model_registry.tmpl", appPath+"/models/model_registry.go", cmd.project) - cmd.helper.Write("templates/models/health.tmpl", appPath+"/models/health.go", cmd.project) - cmd.helper.Write("templates/instance/registry/models.tmpl", appPath+"/instance/registry/models.go", cmd.project) - cmd.helper.Write("templates/repository/health.tmpl", appPath+"/repository/health.go", cmd.project) + cmd.helper.Write("templates/config/config.tmpl", appPath+"/config/config.go", project) + cmd.helper.Write("templates/constants/app.tmpl", appPath+"/constants/app.go", project) + cmd.helper.Write("templates/instance/instance.tmpl", appPath+"/instance/instance.go", project) + cmd.helper.Write("templates/logger/logger.tmpl", appPath+"/logger/logger.go", project) + cmd.helper.Write("templates/models/model_registry.tmpl", appPath+"/models/model_registry.go", project) + cmd.helper.Write("templates/models/health.tmpl", appPath+"/models/health.go", project) + cmd.helper.Write("templates/instance/registry/models.tmpl", appPath+"/instance/registry/models.go", project) + cmd.helper.Write("templates/repository/health.tmpl", appPath+"/repository/health.go", project) - cmd.helper.Write("templates/runner/api.tmpl", appPath+"/runner/api.go", cmd.project) - cmd.helper.Write("templates/runner/grpc.tmpl", appPath+"/runner/grpc.go", cmd.project) + cmd.helper.Write("templates/runner/api.tmpl", appPath+"/runner/api.go", project) + cmd.helper.Write("templates/runner/grpc.tmpl", appPath+"/runner/grpc.go", project) - cmd.helper.Write("templates/service/health.tmpl", appPath+"/service/health.go", cmd.project) - cmd.helper.Write("templates/utils/utils.tmpl", appPath+"/utils/utils.go", cmd.project) + cmd.helper.Write("templates/service/health.tmpl", appPath+"/service/health.go", project) + cmd.helper.Write("templates/utils/utils.tmpl", appPath+"/utils/utils.go", project) return nil } -func NewAppCmd( - project models.Project, +func NewAppService( + fileSystemRepo repository.FileSystemRepo, helper helper.CommonHelper, -) AppCmd { - return &appCmd{ - project: project, - helper: helper, +) AppService { + return &appService{ + fileSystemRepo: fileSystemRepo, + helper: helper, } } diff --git a/go.mod b/go.mod index 5bfd09c..a14e847 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,17 @@ module github.com/tech-thinker/gozen go 1.22 -require github.com/urfave/cli/v2 v2.27.2 +require ( + github.com/stretchr/testify v1.10.0 + github.com/urfave/cli/v2 v2.27.2 +) require ( github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d9aaee3..de08008 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,20 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 0b4dec0..418bd3f 100644 --- a/main.go +++ b/main.go @@ -2,12 +2,12 @@ package main import ( "embed" - "fmt" "os" "github.com/tech-thinker/gozen/cmd" "github.com/tech-thinker/gozen/cmd/helper" - "github.com/tech-thinker/gozen/models" + "github.com/tech-thinker/gozen/cmd/repository" + "github.com/tech-thinker/gozen/cmd/service" "github.com/urfave/cli/v2" ) @@ -21,67 +21,21 @@ var ( var templatesFS embed.FS func main() { - // Declaring flags - var packageName string - var outputDir string - var driver string - // Initialize common helper - helper := helper.NewCommonHelper(templatesFS) + shellRepo := repository.NewShellRepo() + fileSystemRepo := repository.NewFileSystemRepo() - clientApp := cli.NewApp() - clientApp.Name = "gozen" - clientApp.Version = AppVersion - clientApp.Commands = []*cli.Command{ - { - Name: "create", - Usage: "Create new Projects.", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "pkg", - Aliases: []string{"p"}, - Value: "", - Usage: "Package name for new project.", - Destination: &packageName, - }, - &cli.StringFlag{ - Name: "output", - Aliases: []string{"o"}, - Value: ".", - Usage: "Output directory for new project.", - Destination: &outputDir, - }, - &cli.StringFlag{ - Name: "driver", - Aliases: []string{"d"}, - Value: "sqlite", - Usage: "Database driver for new project. eg. [sqlite, mysql, postgres]", - Destination: &driver, - }, - }, - Action: func(ctx *cli.Context) error { - project := models.Project{ - AppName: ctx.Args().Get(0), - PackageName: packageName, - Driver: driver, - WorkingDir: outputDir, - } + commonHelper := helper.NewCommonHelper(templatesFS, shellRepo, fileSystemRepo) + appSvc := service.NewAppService(fileSystemRepo, commonHelper) + app := cmd.NewApp(appSvc) - err := project.Validate() - if err != nil { - fmt.Println(err) - return nil - } - - project.AutoFixes() - - app := cmd.NewAppCmd(project, helper) - - return app.CreateApp() - }, - }, + cliApp := cli.NewApp() + cliApp.Name = "gozen" + cliApp.Version = AppVersion + cliApp.Commands = []*cli.Command{ + app.CreateProject(), } - if err := clientApp.Run(os.Args); err != nil { + if err := cliApp.Run(os.Args); err != nil { panic(err) } } diff --git a/utils/test.tmpl b/utils/test.tmpl new file mode 100644 index 0000000..3504a16 --- /dev/null +++ b/utils/test.tmpl @@ -0,0 +1 @@ +Hello {{.Name}}! \ No newline at end of file diff --git a/utils/utils.go b/utils/utils.go index 9977197..be2b1d0 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -3,9 +3,7 @@ package utils import ( "bytes" "embed" - "fmt" "html/template" - "os" "strings" ) @@ -23,24 +21,9 @@ func GenerateCode(tplFS embed.FS, tplFile string, data interface{}) (string, err return buf.String(), nil } -func WriteFile(path, data string) error { - // Replacing escape characters +func ApplyEscapeChar(data string) string { data = strings.ReplaceAll(data, "<", "<") data = strings.ReplaceAll(data, ">", ">") data = strings.ReplaceAll(data, "&", "&") - - file, err := os.Create(path) - if err != nil { - return err - } - defer file.Close() - _, err = fmt.Fprintf(file, `%s`, data) - if err != nil { - return err - } - return nil -} - -func CreateDirectory(path string) error { - return os.MkdirAll(path, os.ModePerm) + return data } diff --git a/utils/utils_mock.go b/utils/utils_mock.go new file mode 100644 index 0000000..732a797 --- /dev/null +++ b/utils/utils_mock.go @@ -0,0 +1,10 @@ +package utils + +import "embed" + +//go:embed *.tmpl +var tmplFS embed.FS + +func GetMockTmpl() embed.FS { + return tmplFS +} diff --git a/utils/utils_test.go b/utils/utils_test.go new file mode 100644 index 0000000..cbbbca2 --- /dev/null +++ b/utils/utils_test.go @@ -0,0 +1,91 @@ +package utils + +import ( + "embed" + "testing" +) + +func TestGenerateCode(t *testing.T) { + type objData struct { + Name string `json:"Name"` + } + + type args struct { + tplFS embed.FS + tplFile string + data interface{} + } + + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "with valid template should pass", + args: args{ + tplFS: GetMockTmpl(), + tplFile: "test.tmpl", + data: objData{Name: "Test"}, + }, + want: "Hello Test!", + wantErr: false, + }, + { + name: "with invalid template should fail", + args: args{ + tplFS: GetMockTmpl(), + tplFile: "invalid.tmpl", + data: objData{Name: "Test"}, + }, + want: "{}", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GenerateCode(tt.args.tplFS, tt.args.tplFile, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateCode() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GenerateCode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestApplyEscapeChar(t *testing.T) { + type args struct { + data string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "text with escape char entities", + args: args{ + data: "<Hello World & Golang is best!>", + }, + want: "", + }, + { + name: "text with out escape char entities", + args: args{ + data: "Hello World & Golang is best!", + }, + want: "Hello World & Golang is best!", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ApplyEscapeChar(tt.args.data); got != tt.want { + t.Errorf("ApplyEscapeChar() = %v, want %v", got, tt.want) + } + }) + } +}