From cd0391a53a47fb36bd46e51ee6233aae609c5f0b Mon Sep 17 00:00:00 2001 From: kozmod Date: Wed, 10 Jul 2024 21:17:49 +0300 Subject: [PATCH 1/3] tmp --- internal/config/config.go | 68 +++++++++- internal/config/unmarshaler.go | 2 +- internal/entity/entity.go | 34 +++++ internal/entity/v.go | 13 ++ internal/exec/chain.go | 66 ++++++++-- internal/exec/file.go | 4 +- internal/factory/action_filter.go | 21 +-- internal/factory/chain_exec.go | 206 ++++++++++++------------------ internal/factory/cmd_exec.go | 5 +- internal/factory/file.go | 156 ++++++++++++++++++++++ internal/factory/file_exec.go | 93 -------------- internal/factory/fs_exec.go | 24 +++- main.go | 93 ++++++++++---- pkg/core/engin.go | 144 +++++++++++++++++++++ pkg/core/entity.go | 47 +++++++ pkg/core/t_test.go | 23 ++++ 16 files changed, 725 insertions(+), 274 deletions(-) create mode 100644 internal/entity/v.go create mode 100644 internal/factory/file.go delete mode 100644 internal/factory/file_exec.go create mode 100644 pkg/core/engin.go create mode 100644 pkg/core/entity.go create mode 100644 pkg/core/t_test.go diff --git a/internal/config/config.go b/internal/config/config.go index 8fbea72..a57ecef 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -28,6 +28,72 @@ type Config struct { FS []Section[[]string] `yaml:"fs,flow"` } +func (c Config) CommandActions() []entity.Action[[]entity.Command] { + return toActionsSlice(c.Cmd, func(cmd Command) entity.Command { + return entity.Command{ + Cmd: cmd.Exec, + Args: cmd.Args, + Dir: cmd.Dir, + } + }) +} + +func (c Config) FilesActions() []entity.Action[[]entity.UndefinedFile] { + return toActionsSlice(c.Files, func(file File) entity.UndefinedFile { + uFile := entity.UndefinedFile{ + Path: file.Path, + } + if file.Data != nil { + *uFile.Data = *file.Data + } + if get := file.Get; get != nil { + uFile.Get = &entity.HTTPClientParams{ + URL: get.URL, + Headers: get.Headers, + QueryParams: get.QueryParams, + } + } + if file.Local != nil { + uFile.Local = file.Local + } + return uFile + }) +} + +func (c Config) DirActions() []entity.Action[[]string] { + return toActionsSlice(c.Dirs, func(dir string) string { + return dir + }) +} + +func (c Config) RmActions() []entity.Action[[]string] { + return toActionsSlice(c.Rm, func(rms string) string { + return rms + }) +} + +func (c Config) FsActions() []entity.Action[[]string] { + return toActionsSlice(c.FS, func(fss string) string { + return fss + }) +} + +func toActionsSlice[S any, T any](sections []Section[[]S], mapFn func(s S) T) []entity.Action[[]T] { + actions := make([]entity.Action[[]T], len(sections)) + for i, section := range sections { + action := entity.Action[[]T]{ + Priority: section.Line, + Name: section.Tag, + Val: make([]T, len(section.Val)), + } + for j, val := range section.Val { + action.Val[j] = mapFn(val) + } + actions[i] = action + } + return actions +} + type Settings struct { HTTP *HTTPClient `yaml:"http"` Groups Groups `yaml:"groups"` @@ -76,7 +142,7 @@ type Group struct { } type Section[T any] struct { - Line int32 + Line int Tag string Val T } diff --git a/internal/config/unmarshaler.go b/internal/config/unmarshaler.go index d996f1f..326738d 100644 --- a/internal/config/unmarshaler.go +++ b/internal/config/unmarshaler.go @@ -58,7 +58,7 @@ func (u *YamlUnmarshaler) Unmarshal(rawConfig []byte) (Config, error) { } func decode[T any](target []Section[T], node yaml.Node, tag string) ([]Section[T], error) { - section := Section[T]{Line: int32(node.Line), Tag: tag} + section := Section[T]{Line: node.Line, Tag: tag} err := node.Decode(§ion.Val) target = append(target, section) return target, err diff --git a/internal/entity/entity.go b/internal/entity/entity.go index e3dc746..70d81cb 100644 --- a/internal/entity/entity.go +++ b/internal/entity/entity.go @@ -89,8 +89,42 @@ type ( Debugf(format string, any ...any) Fatalf(format string, any ...any) } + + ActionFilter interface { + MatchString(s string) bool + } ) +type ExecutorBuilder struct { + Action string + Priority int + ProcFn func() (Executor, error) +} + +type Group struct { + Name string + Actions []string + Manual bool +} + +type Action[T any] struct { + Priority int + Name string + Val T +} + +func (a Action[T]) WithPriority(priority int) Action[T] { + a.Priority = priority + return a +} + +type UndefinedFile struct { + Path string + Data *[]byte + Get *HTTPClientParams + Local *string +} + type DataFile struct { FileInfo Data []byte diff --git a/internal/entity/v.go b/internal/entity/v.go new file mode 100644 index 0000000..1198a99 --- /dev/null +++ b/internal/entity/v.go @@ -0,0 +1,13 @@ +package entity + +type AppendVPlusOrV func(s string) string + +func NewAppendVPlusOrV(vPlus bool) AppendVPlusOrV { + return func(s string) string { + const vp, v = "%+v", "%v" + if vPlus { + return s + vp + } + return s + v + } +} diff --git a/internal/exec/chain.go b/internal/exec/chain.go index 503b0cb..3abe97b 100644 --- a/internal/exec/chain.go +++ b/internal/exec/chain.go @@ -2,35 +2,77 @@ package exec import ( "golang.org/x/xerrors" + "sync" "github.com/kozmod/progen/internal/entity" ) +type Chain struct { + executors []entity.Executor +} + +func NewChain(executors []entity.Executor) *Chain { + return &Chain{executors: executors} +} + +func (c *Chain) Exec() error { + for i, executor := range c.executors { + err := executor.Exec() + if err != nil { + return xerrors.Errorf("execute proc [%d]: %w", i, err) + } + } + return nil +} + type PreprocessingChain struct { - preprocessors []entity.Preprocessor - executors []entity.Executor + preprocessors *Preprocessors + chain *Chain } -func NewPreprocessingChain(preprocessors []entity.Preprocessor, executors []entity.Executor) *PreprocessingChain { +func NewPreprocessingChain(preprocessors *Preprocessors, executors []entity.Executor) *PreprocessingChain { return &PreprocessingChain{ preprocessors: preprocessors, - executors: executors, + chain: NewChain(executors), } } func (c *PreprocessingChain) Exec() error { - for i, preprocessor := range c.preprocessors { + for i, preprocessor := range c.preprocessors.Get() { err := preprocessor.Process() if err != nil { - return xerrors.Errorf("preload [%d]: %w", i, err) + return xerrors.Errorf("preprocess [%d]: %w", i, err) } } + return c.chain.Exec() +} + +type Preprocessors struct { + mx sync.RWMutex + val []entity.Preprocessor +} + +func (p *Preprocessors) Add(in ...entity.Preprocessor) { + if p == nil { + return + } - for i, executor := range c.executors { - err := executor.Exec() - if err != nil { - return xerrors.Errorf("execute proc [%d]: %w", i, err) - } + p.mx.Lock() + defer p.mx.Unlock() + p.val = append(p.val, in...) +} + +func (p *Preprocessors) Get() []entity.Preprocessor { + if p == nil { + return nil } - return nil + + p.mx.RLock() + defer p.mx.RUnlock() + if len(p.val) == 0 { + return nil + } + res := make([]entity.Preprocessor, len(p.val), len(p.val)) + copy(res, p.val) + return res } diff --git a/internal/exec/file.go b/internal/exec/file.go index 78ea2d3..21acc6b 100644 --- a/internal/exec/file.go +++ b/internal/exec/file.go @@ -33,8 +33,8 @@ func (e *FilesExecutor) Exec() error { return xerrors.Errorf("execute file: get file: %w", err) } - for _, processor := range e.strategies { - file, err = processor.Apply(file) + for _, strategy := range e.strategies { + file, err = strategy.Apply(file) if err != nil { return xerrors.Errorf("execute file: process file: %w", err) } diff --git a/internal/factory/action_filter.go b/internal/factory/action_filter.go index a1b6489..438de57 100644 --- a/internal/factory/action_filter.go +++ b/internal/factory/action_filter.go @@ -7,14 +7,15 @@ import ( "github.com/kozmod/progen/internal/entity" ) -type ( - actionFilter interface { - MatchString(s string) bool - } -) +type DummyActionFilter struct { +} + +func (f DummyActionFilter) MatchString(_ string) bool { + return true +} -type ActionFilter struct { - skipFilter actionFilter +type FacadeActionFilter struct { + skipFilter entity.ActionFilter selectedGroups map[string]struct{} groupsByAction map[string]map[string]struct{} manualActions map[string]struct{} @@ -29,7 +30,7 @@ func NewActionFilter( manualActionsSet map[string]struct{}, logger entity.Logger, -) *ActionFilter { +) *FacadeActionFilter { selectedGroupsSet := entity.SliceSet(selectedGroups) skipActions = slices.Compact(skipActions) @@ -44,7 +45,7 @@ func NewActionFilter( logger.Infof("manual actions will be skipped: [%s]", strings.Join(manualActions, entity.LogSliceSep)) } - return &ActionFilter{ + return &FacadeActionFilter{ skipFilter: entity.NewRegexpChain(skipActions...), selectedGroups: selectedGroupsSet, groupsByAction: groupsByAction, @@ -53,7 +54,7 @@ func NewActionFilter( } } -func (f *ActionFilter) MatchString(action string) bool { +func (f *FacadeActionFilter) MatchString(action string) bool { if f.skipFilter.MatchString(action) { f.logger.Infof("action will be skipped: [%s]", action) return false diff --git a/internal/factory/chain_exec.go b/internal/factory/chain_exec.go index 52f4fb2..df9709e 100644 --- a/internal/factory/chain_exec.go +++ b/internal/factory/chain_exec.go @@ -5,150 +5,110 @@ import ( "golang.org/x/xerrors" - "github.com/kozmod/progen/internal/config" "github.com/kozmod/progen/internal/entity" - "github.com/kozmod/progen/internal/exec" ) -func NewExecutorChain( - conf config.Config, - actionFilter actionFilter, - templateData map[string]any, - templateOptions []string, +type ( + executorBuilderFactory interface { + Create(logger entity.Logger, dryRun bool) []entity.ExecutorBuilder + } +) + +type ExecutorChainFactory struct { + logger entity.Logger + dryRun bool + + executorBuilderFactories []executorBuilderFactory + createFn func([]entity.Executor) entity.Executor +} + +func NewExecutorChainFactory( logger entity.Logger, - preprocess, dryRun bool, -) (entity.Executor, error) { + createFn func([]entity.Executor) entity.Executor, + executorBuilderFactories ...executorBuilderFactory, - type ( - ExecutorBuilder struct { - action string - line int32 - procFn func() (entity.Executor, error) - } +) *ExecutorChainFactory { + return &ExecutorChainFactory{ + createFn: createFn, + logger: logger, + dryRun: dryRun, + executorBuilderFactories: executorBuilderFactories, + } +} + +func (f ExecutorChainFactory) Create() (entity.Executor, error) { + var ( + allBuilders []entity.ExecutorBuilder ) + for _, factory := range f.executorBuilderFactories { + builder := factory.Create(f.logger, f.dryRun) + allBuilders = append(allBuilders, builder...) + } - builders := make([]ExecutorBuilder, 0, len(conf.Dirs)+len(conf.Files)+len(conf.Cmd)+len(conf.FS)) - for _, dirs := range conf.Dirs { - var ( - d = dirs - action = d.Tag - ) + sort.Slice(allBuilders, func(i, j int) bool { + return allBuilders[i].Priority < allBuilders[j].Priority + }) - if !actionFilter.MatchString(action) { - continue + executors := make([]entity.Executor, 0, len(allBuilders)) + for _, builder := range allBuilders { + e, err := builder.ProcFn() + if err != nil { + return nil, xerrors.Errorf("configure executor [%s]: %w", builder.Action, err) } - builders = append(builders, - ExecutorBuilder{ - action: action, - line: d.Line, - procFn: func() (entity.Executor, error) { - return NewMkdirExecutor(d.Val, logger, dryRun) - }, - }) - } - for _, rm := range conf.Rm { - var ( - r = rm - action = r.Tag - ) - - if !actionFilter.MatchString(action) { + if e == nil { continue } - builders = append(builders, - ExecutorBuilder{ - action: action, - line: r.Line, - procFn: func() (entity.Executor, error) { - return NewRmExecutor(r.Val, logger, dryRun) - }, - }) + executors = append(executors, e) } - var preprocessors []entity.Preprocessor - for _, files := range conf.Files { - var ( - f = files - action = f.Tag - ) - if !actionFilter.MatchString(action) { - continue - } - builders = append(builders, - ExecutorBuilder{ - action: action, - line: f.Line, - procFn: func() (entity.Executor, error) { - executor, l, err := NewFileExecutor( - f.Val, - conf.Settings.HTTP, - templateData, - templateOptions, - logger, - preprocess, - dryRun) - preprocessors = append(preprocessors, l...) - return executor, err - }, - }) - } + //return exec.NewPreprocessingChain(f.preprocessors, executors), nil + return f.createFn(executors), nil +} - for _, commands := range conf.Cmd { - var ( - cmd = commands - action = cmd.Tag - ) - if !actionFilter.MatchString(action) { - continue - } - builders = append(builders, - ExecutorBuilder{ - action: action, - line: cmd.Line, - procFn: func() (entity.Executor, error) { - return NewRunCommandExecutor(cmd.Val, logger, dryRun) - }, - }) - } - for _, path := range conf.FS { +type ( + actionValConsumer[T any] func(vals []T, logger entity.Logger, dryRun bool) (entity.Executor, error) +) + +type ExecutorBuilderFactory[T any] struct { + actionsSupplier []entity.Action[[]T] + actionValConsumer actionValConsumer[T] + actionFilter entity.ActionFilter +} + +func NewExecutorBuilderFactory[T any]( + actionSupplier []entity.Action[[]T], + actionValConsumer actionValConsumer[T], + actionFilter entity.ActionFilter, +) *ExecutorBuilderFactory[T] { + return &ExecutorBuilderFactory[T]{ + actionsSupplier: actionSupplier, + actionValConsumer: actionValConsumer, + actionFilter: actionFilter} +} + +func (y ExecutorBuilderFactory[T]) Create(logger entity.Logger, dryRun bool) []entity.ExecutorBuilder { + var ( + actions = y.actionsSupplier + builders = make([]entity.ExecutorBuilder, 0, len(actions)) + ) + for _, action := range actions { var ( - fs = path - action = fs.Tag + a = action + name = a.Name ) - if actionFilter.MatchString(action) { + if !y.actionFilter.MatchString(name) { continue } builders = append(builders, - ExecutorBuilder{ - action: action, - line: fs.Line, - procFn: func() (entity.Executor, error) { - return NewFSExecutor( - fs.Val, - templateData, - templateOptions, - logger, - dryRun) + entity.ExecutorBuilder{ + Action: name, + Priority: a.Priority, + ProcFn: func() (entity.Executor, error) { + executor, err := y.actionValConsumer(a.Val, logger, dryRun) + return executor, err }, }) } - - sort.Slice(builders, func(i, j int) bool { - return builders[i].line < builders[j].line - }) - - executors := make([]entity.Executor, 0, len(builders)) - for _, builder := range builders { - e, err := builder.procFn() - if err != nil { - return nil, xerrors.Errorf("configure executor [%s]: %w", builder.action, err) - } - if e == nil { - continue - } - executors = append(executors, e) - } - - return exec.NewPreprocessingChain(preprocessors, executors), nil + return builders } diff --git a/internal/factory/cmd_exec.go b/internal/factory/cmd_exec.go index e99173d..291e40d 100644 --- a/internal/factory/cmd_exec.go +++ b/internal/factory/cmd_exec.go @@ -3,13 +3,12 @@ package factory import ( "strings" - "github.com/kozmod/progen/internal/config" "github.com/kozmod/progen/internal/entity" "github.com/kozmod/progen/internal/exec" ) //goland:noinspection SpellCheckingInspection -func NewRunCommandExecutor(cmds []config.Command, logger entity.Logger, dryRun bool) (entity.Executor, error) { +func NewRunCommandExecutor(cmds []entity.Command, logger entity.Logger, dryRun bool) (entity.Executor, error) { if len(cmds) == 0 { logger.Infof("`cmd` section is empty") return nil, nil @@ -26,7 +25,7 @@ func NewRunCommandExecutor(cmds []config.Command, logger entity.Logger, dryRun b args = append(args, cmd.Args...) commands = append(commands, entity.Command{ - Cmd: cmd.Exec, + Cmd: cmd.Cmd, Args: args, Dir: dir, }) diff --git a/internal/factory/file.go b/internal/factory/file.go new file mode 100644 index 0000000..182da2b --- /dev/null +++ b/internal/factory/file.go @@ -0,0 +1,156 @@ +package factory + +import ( + "golang.org/x/xerrors" + + resty "github.com/go-resty/resty/v2" + + "github.com/kozmod/progen/internal/entity" + "github.com/kozmod/progen/internal/exec" +) + +type FileExecutorFactory struct { + templateData map[string]any + templateOptions []string +} + +func NewFileExecutorFactory( + templateData map[string]any, + templateOptions []string, +) *FileExecutorFactory { + return &FileExecutorFactory{ + templateData: templateData, + templateOptions: templateOptions, + } +} + +func (ff *FileExecutorFactory) Create(files []entity.UndefinedFile, logger entity.Logger, dryRun bool) (entity.Executor, error) { + if len(files) == 0 { + logger.Infof("`files` section is empty") + return nil, nil + } + + producers := make([]entity.FileProducer, 0, len(files)) + for _, f := range files { + file := entity.DataFile{ + FileInfo: entity.NewFileInfo(f.Path), + Data: *f.Data, + } + producer := exec.NewDummyProducer(file) + producers = append(producers, producer) + } + + strategies := []entity.FileStrategy{exec.NewTemplateFileStrategy(ff.templateData, entity.TemplateFnsMap, ff.templateOptions)} + + switch { + case dryRun: + strategies = append(strategies, exec.NewDryRunFileStrategy(logger)) + default: + strategies = append(strategies, exec.NewSaveFileStrategy(logger)) + } + executor := exec.NewFilesExecutor(producers, strategies) + + return executor, nil +} + +type PreprocessorsFileExecutorFactory struct { + templateData map[string]any + templateOptions []string + + preprocess bool + preprocessors *exec.Preprocessors + httpClientSupplier func(logger entity.Logger) *resty.Client +} + +func NewPreprocessorsFileExecutorFactory( + templateData map[string]any, + templateOptions []string, + preprocess bool, + preprocessors *exec.Preprocessors, + httpClientSupplier func(logger entity.Logger) *resty.Client, +) *PreprocessorsFileExecutorFactory { + return &PreprocessorsFileExecutorFactory{ + templateData: templateData, + templateOptions: templateOptions, + preprocess: preprocess, + preprocessors: preprocessors, + httpClientSupplier: httpClientSupplier, + } +} + +func (ff *PreprocessorsFileExecutorFactory) Create(files []entity.UndefinedFile, logger entity.Logger, dryRun bool) (entity.Executor, error) { + if len(files) == 0 { + logger.Infof("`files` section is empty") + return nil, nil + } + + producers := make([]entity.FileProducer, 0, len(files)) + + var client *resty.Client + for _, f := range files { + var ( + tmpl = entity.NewFileInfo(f.Path) + ) + + var producer entity.FileProducer + switch { + case f.Data != nil: + file := entity.DataFile{ + FileInfo: tmpl, + Data: *f.Data, + } + producer = exec.NewDummyProducer(file) + case f.Get != nil: + file := entity.RemoteFile{ + FileInfo: tmpl, + HTTPClientParams: entity.HTTPClientParams{ + URL: f.Get.URL, + Headers: f.Get.Headers, + QueryParams: f.Get.QueryParams, + }, + } + + if client == nil { + client = ff.httpClientSupplier(logger) + } + + producer = exec.NewRemoteProducer(file, client) + case f.Local != nil: + file := entity.LocalFile{ + FileInfo: tmpl, + LocalPath: *f.Local, + } + producer = exec.NewLocalProducer(file) + + default: + return nil, xerrors.Errorf("build file executor from config: one of `data`, `get`, `local` must not be empty") + } + + producers = append(producers, producer) + } + + if preprocess := ff.preprocess; preprocess { + if ff.preprocessors == nil { + return nil, xerrors.Errorf("creating file executor - preprocesso is nil [preprocess:%v]", preprocess) + } + preloadProducers := make([]entity.FileProducer, 0, len(producers)) + preloader := exec.NewPreloadProducer(producers, logger) + for i := 0; i < len(producers); i++ { + preloadProducers = append(preloadProducers, preloader) + } + producers = preloadProducers + ff.preprocessors.Add(preloader) + } + + strategies := []entity.FileStrategy{exec.NewTemplateFileStrategy(ff.templateData, entity.TemplateFnsMap, ff.templateOptions)} + + switch { + case dryRun: + strategies = append(strategies, exec.NewDryRunFileStrategy(logger)) + default: + strategies = append(strategies, exec.NewSaveFileStrategy(logger)) + } + executor := exec.NewFilesExecutor(producers, strategies) + + return executor, nil +} diff --git a/internal/factory/file_exec.go b/internal/factory/file_exec.go deleted file mode 100644 index 8399fbd..0000000 --- a/internal/factory/file_exec.go +++ /dev/null @@ -1,93 +0,0 @@ -package factory - -import ( - resty "github.com/go-resty/resty/v2" - "golang.org/x/xerrors" - - "github.com/kozmod/progen/internal/config" - "github.com/kozmod/progen/internal/entity" - "github.com/kozmod/progen/internal/exec" -) - -func NewFileExecutor( - files []config.File, - http *config.HTTPClient, - templateData map[string]any, - templateOptions []string, - logger entity.Logger, - preprocess, - dryRun bool, -) (entity.Executor, []entity.Preprocessor, error) { - if len(files) == 0 { - logger.Infof("`files` section is empty") - return nil, nil, nil - } - - producers := make([]entity.FileProducer, 0, len(files)) - - var client *resty.Client - for _, f := range files { - var ( - tmpl = entity.NewFileInfo(f.Path) - ) - - var producer entity.FileProducer - switch { - case f.Data != nil: - file := entity.DataFile{ - FileInfo: tmpl, - Data: *f.Data, - } - producer = exec.NewDummyProducer(file) - case f.Get != nil: - file := entity.RemoteFile{ - FileInfo: tmpl, - HTTPClientParams: entity.HTTPClientParams{ - URL: f.Get.URL, - Headers: f.Get.Headers, - QueryParams: f.Get.QueryParams, - }, - } - - if client == nil { - client = NewHTTPClient(http, logger) - } - - producer = exec.NewRemoteProducer(file, client) - case f.Local != nil: - file := entity.LocalFile{ - FileInfo: tmpl, - LocalPath: *f.Local, - } - producer = exec.NewLocalProducer(file) - - default: - return nil, nil, xerrors.Errorf("build file executor: one of `data`, `get`, `local` must not be empty") - } - - producers = append(producers, producer) - } - - var preprocessors []entity.Preprocessor - if preprocess { - preloadProducers := make([]entity.FileProducer, 0, len(producers)) - preloader := exec.NewPreloadProducer(producers, logger) - for i := 0; i < len(producers); i++ { - preloadProducers = append(preloadProducers, preloader) - } - producers = preloadProducers - preprocessors = append(preprocessors, preloader) - } - - strategies := []entity.FileStrategy{exec.NewTemplateFileStrategy(templateData, entity.TemplateFnsMap, templateOptions)} - - switch { - case dryRun: - strategies = append(strategies, exec.NewDryRunFileStrategy(logger)) - default: - strategies = append(strategies, exec.NewSaveFileStrategy(logger)) - } - executor := exec.NewFilesExecutor(producers, strategies) - - return executor, preprocessors, nil -} diff --git a/internal/factory/fs_exec.go b/internal/factory/fs_exec.go index ed71d8c..c9f9e17 100644 --- a/internal/factory/fs_exec.go +++ b/internal/factory/fs_exec.go @@ -5,12 +5,26 @@ import ( "github.com/kozmod/progen/internal/exec" ) -func NewFSExecutor( - dirs []string, +type FsExecFactory struct { + templateData map[string]any + templateOptions []string +} + +func NewFsExecFactory( templateData map[string]any, templateOptions []string, +) *FsExecFactory { + return &FsExecFactory{ + templateData: templateData, + templateOptions: templateOptions, + } +} + +func (f FsExecFactory) Create( + dirs []string, logger entity.Logger, - dryRun bool) (entity.Executor, error) { + dryRun bool, +) (entity.Executor, error) { if len(dirs) == 0 { logger.Infof("fs executor: `dir` section is empty") return nil, nil @@ -24,9 +38,9 @@ func NewFSExecutor( return exec.NewDirExecutor(dirSet, []entity.DirStrategy{ exec.NewFileSystemStrategy( - templateData, + f.templateData, entity.TemplateFnsMap, - templateOptions, + f.templateOptions, logger), }), nil } diff --git a/main.go b/main.go index 64de71c..7cc164a 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,9 @@ package main import ( "fmt" + "github.com/go-resty/resty/v2" + "github.com/kozmod/progen/internal/exec" + "github.com/kozmod/progen/internal/factory" "log" "os" "time" @@ -11,7 +14,6 @@ import ( "github.com/kozmod/progen/internal" "github.com/kozmod/progen/internal/config" "github.com/kozmod/progen/internal/entity" - "github.com/kozmod/progen/internal/factory" "github.com/kozmod/progen/internal/flag" ) @@ -24,13 +26,7 @@ func main() { return } - logFatalSuffixFn := func(s string) string { - const pv, v = "%+v", "%v" - if flags.PrintErrorStackTrace { - return s + pv - } - return s + v - } + logFatalSuffixFn := entity.NewAppendVPlusOrV(flags.PrintErrorStackTrace) logger, err := factory.NewLogger(flags.Verbose) if err != nil { @@ -42,13 +38,15 @@ func main() { { if err = os.Chdir(flags.AWD); err != nil { - logger.Fatalf(logFatalSuffixFn("changes the application working directory: "), xerrors.Errorf("%w", err)) + logger.Errorf(logFatalSuffixFn("changes the application working directory: "), xerrors.Errorf("%w", err)) + return } var awd string awd, err = os.Getwd() if err != nil { - logger.Fatalf(logFatalSuffixFn("get the application working directory: "), xerrors.Errorf("%w", err)) + logger.Errorf(logFatalSuffixFn("get the application working directory: "), xerrors.Errorf("%w", err)) + return } logger.Infof("application working directory: %s", awd) } @@ -61,7 +59,8 @@ func main() { data, err := config.NewConfigReader(flags).Read() if err != nil { - logger.Fatalf(logFatalSuffixFn("read config: "), err) + logger.Errorf(logFatalSuffixFn("read config: "), err) + return } rawConfig, templateData, err := config.NewRawPreprocessor( @@ -71,7 +70,8 @@ func main() { []string{flags.MissingKey.String()}, ).Process(data) if err != nil { - logger.Fatalf(logFatalSuffixFn("preprocess raw config: "), err) + logger.Errorf(logFatalSuffixFn("preprocess raw config: "), err) + return } if flags.PrintProcessedConfig { @@ -83,33 +83,78 @@ func main() { ) conf, err = config.NewYamlConfigUnmarshaler().Unmarshal(rawConfig) if err != nil { - logger.Fatalf(logFatalSuffixFn("unmarshal config: "), err) + logger.Errorf(logFatalSuffixFn("unmarshal config: "), err) + return } if err = conf.Validate(); err != nil { - logger.Fatalf(logFatalSuffixFn("validate config: "), err) + logger.Errorf(logFatalSuffixFn("validate config: "), err) + return } - procChain, err := factory.NewExecutorChain( - conf, - factory.NewActionFilter( + var ( + actionFilter = factory.NewActionFilter( flags.Skip, flags.Group, conf.Settings.Groups.GroupByAction(), conf.Settings.Groups.ManualActions(), logger, - ), - templateData, - []string{flags.MissingKey.String()}, + ) + templateOptions = []string{flags.MissingKey.String()} + preprocessors = &exec.Preprocessors{} + ) + + procChain, err := factory.NewExecutorChainFactory( logger, - flags.PreprocessFiles, - flags.DryRun) + flags.DryRun, + func(executors []entity.Executor) entity.Executor { + return exec.NewPreprocessingChain(preprocessors, executors) + }, + factory.NewExecutorBuilderFactory( + conf.DirActions(), + factory.NewMkdirExecutor, + actionFilter, + ), + factory.NewExecutorBuilderFactory( + conf.RmActions(), + factory.NewRmExecutor, + actionFilter, + ), + factory.NewExecutorBuilderFactory( + conf.CommandActions(), + factory.NewRunCommandExecutor, + actionFilter, + ), + factory.NewExecutorBuilderFactory( + conf.FilesActions(), + factory.NewPreprocessorsFileExecutorFactory( + templateData, + templateOptions, + flags.PreprocessFiles, + preprocessors, + func(logger entity.Logger) *resty.Client { + return factory.NewHTTPClient(conf.Settings.HTTP, logger) + }, + ).Create, + actionFilter, + ), + factory.NewExecutorBuilderFactory( + conf.FsActions(), + factory.NewFsExecFactory( + templateData, + templateOptions, + ).Create, + actionFilter, + ), + ).Create() if err != nil { - logger.Fatalf(logFatalSuffixFn("create processors chain: "), err) + logger.Errorf(logFatalSuffixFn("create processors chain: "), err) + return } err = procChain.Exec() if err != nil { - logger.Fatalf(logFatalSuffixFn("execute chain: "), err) + logger.Errorf(logFatalSuffixFn("execute chain: "), err) + return } } diff --git a/pkg/core/engin.go b/pkg/core/engin.go new file mode 100644 index 0000000..dc823f3 --- /dev/null +++ b/pkg/core/engin.go @@ -0,0 +1,144 @@ +package core + +import ( + "github.com/kozmod/progen/internal/entity" + "github.com/kozmod/progen/internal/exec" + "github.com/kozmod/progen/internal/factory" + "golang.org/x/xerrors" + "sync" +) + +type Engin struct { + mx sync.RWMutex + files []entity.Action[[]entity.UndefinedFile] + cmd []entity.Action[[]entity.Command] + dirs []entity.Action[[]string] + fs []entity.Action[[]string] + rm []entity.Action[[]string] + + templateVars map[string]any + templateOptions []string + + logger entity.Logger +} + +func (e *Engin) AddTemplateVars(vars map[string]any) *Engin { + e.mx.Lock() + defer e.mx.Unlock() + e.templateVars = entity.MergeKeys(e.templateVars, vars) + return e +} + +func (e *Engin) AddFilesActions(a ...entity.Action[[]File]) *Engin { + e.mx.Lock() + defer e.mx.Unlock() + actions := toEntityActions(func(f File) entity.UndefinedFile { + return entity.UndefinedFile{ + Path: f.Path, + Data: &f.Data, + } + }, a...) + e.files = append(e.files, actions...) + return e +} + +func (e *Engin) AddExecuteCommandActions(a ...entity.Action[[]Cmd]) *Engin { + e.mx.Lock() + defer e.mx.Unlock() + commands := toEntityActions(func(cmd Cmd) entity.Command { + return entity.Command(cmd) + }, a...) + e.cmd = append(e.cmd, commands...) + return e +} + +func (e *Engin) AddCreateDirsActions(a ...entity.Action[[]string]) *Engin { + e.mx.Lock() + defer e.mx.Unlock() + e.dirs = append(e.dirs, a...) + return e +} + +func (e *Engin) AddRmActions(a ...entity.Action[[]string]) *Engin { + e.mx.Lock() + defer e.mx.Unlock() + e.rm = append(e.rm, a...) + return e +} + +func (e *Engin) AddFsActions(a ...entity.Action[[]string]) *Engin { + e.mx.Lock() + defer e.mx.Unlock() + e.fs = append(e.fs, a...) + return e +} + +func (e *Engin) Run() error { + var ( + logFatalSuffixFn = entity.NewAppendVPlusOrV(true) //todo + actionFilter factory.DummyActionFilter + ) + + e.mx.RLock() + defer e.mx.RUnlock() + + var logger entity.Logger + if e.logger == nil { + l, err := factory.NewLogger(true) //todo + if err != nil { + return xerrors.Errorf("failed to initialize logger: %w", err) + } + logger = l + } + + procChain, err := factory.NewExecutorChainFactory( + logger, + true, //todo + func(executors []entity.Executor) entity.Executor { + return exec.NewChain(executors) + }, + factory.NewExecutorBuilderFactory( + e.dirs, + factory.NewMkdirExecutor, + actionFilter, + ), + factory.NewExecutorBuilderFactory( + e.fs, + factory.NewFsExecFactory( + e.templateVars, + e.templateOptions, + ).Create, + actionFilter, + ), + factory.NewExecutorBuilderFactory( + e.rm, + factory.NewRmExecutor, + actionFilter, + ), + factory.NewExecutorBuilderFactory( + e.files, + factory.NewFileExecutorFactory( + e.templateVars, + e.templateOptions, + ).Create, + actionFilter, + ), + factory.NewExecutorBuilderFactory( + e.cmd, + factory.NewRunCommandExecutor, + actionFilter, + ), + ).Create() + + if err != nil { + logger.Errorf(logFatalSuffixFn("create processors chain: "), err) + return err + } + + err = procChain.Exec() + if err != nil { + logger.Errorf(logFatalSuffixFn("execute chain: "), err) + return err + } + return nil +} diff --git a/pkg/core/entity.go b/pkg/core/entity.go new file mode 100644 index 0000000..b07eee9 --- /dev/null +++ b/pkg/core/entity.go @@ -0,0 +1,47 @@ +package core + +import ( + "github.com/kozmod/progen/internal/entity" +) + +type ( + File struct { + Path string + Data []byte + } + + Cmd entity.Command +) + +func toEntityActions[S ActionVal, T any](convert func(s S) T, in ...entity.Action[[]S]) []entity.Action[[]T] { + res := make([]entity.Action[[]T], len(in)) + for i, action := range in { + target := make([]T, len(in)) + for j, s := range action.Val { + target[j] = convert(s) + } + res[i] = entity.Action[[]T]{ + Priority: action.Priority, + Name: action.Name, + Val: target, + } + } + return res +} + +type ActionVal interface { + string | Cmd | File +} + +func NewAction[T ActionVal](name string, val ...T) entity.Action[[]T] { + return entity.Action[[]T]{ + Name: name, + Priority: 0, + Val: val, + } +} + +func WithPriority[T any](action entity.Action[[]T], priority int) entity.Action[[]T] { + action.Priority = priority + return action +} diff --git a/pkg/core/t_test.go b/pkg/core/t_test.go new file mode 100644 index 0000000..ddbf07c --- /dev/null +++ b/pkg/core/t_test.go @@ -0,0 +1,23 @@ +package core + +import ( + "testing" +) + +func Test(t *testing.T) { + e := Engin{} + e.AddFilesActions( + WithPriority( + NewAction("files", + File{ + Path: "/file_1", + Data: []byte("xxxxxxxxxx"), + }, + ), + 1, + ), + ). + AddCreateDirsActions() + + _ = e.Run() +} From 3d45fbdac306a2101173c08a320a252430fae8cd Mon Sep 17 00:00:00 2001 From: kozmod Date: Fri, 29 Nov 2024 22:00:27 +0300 Subject: [PATCH 2/3] tmp v2 --- internal/factory/chain_exec.go | 1 - internal/flag/flags.go | 110 ++++++++++++++++-------- pkg/core/engin.go | 78 +++++------------ pkg/core/entity.go | 153 ++++++++++++++++++++++++++++----- pkg/core/t_test.go | 23 ----- 5 files changed, 222 insertions(+), 143 deletions(-) delete mode 100644 pkg/core/t_test.go diff --git a/internal/factory/chain_exec.go b/internal/factory/chain_exec.go index df9709e..ea15243 100644 --- a/internal/factory/chain_exec.go +++ b/internal/factory/chain_exec.go @@ -62,7 +62,6 @@ func (f ExecutorChainFactory) Create() (entity.Executor, error) { executors = append(executors, e) } - //return exec.NewPreprocessingChain(f.preprocessors, executors), nil return f.createFn(executors), nil } diff --git a/internal/flag/flags.go b/internal/flag/flags.go index 998f337..8ef1ad7 100644 --- a/internal/flag/flags.go +++ b/internal/flag/flags.go @@ -34,19 +34,24 @@ var ( ErrDashFlagNotLast = fmt.Errorf(`'-' must be the last argument`) ) -type Flags struct { - ConfigPath string +type DefaultFlags struct { Verbose bool DryRun bool + TemplateVars TemplateVarsFlag + MissingKey MissingKeyFlag + PrintErrorStackTrace bool +} + +type Flags struct { + DefaultFlags + + ConfigPath string Version bool ReadStdin bool - TemplateVars TemplateVarsFlag AWD string // AWD application working directory Skip SkipFlag PreprocessFiles bool - MissingKey MissingKeyFlag Group GroupFlag - PrintErrorStackTrace bool PrintProcessedConfig bool } @@ -65,52 +70,92 @@ func (f *Flags) FileLocationMessage() string { func Parse() Flags { args := os.Args - flags, err := parseFlags(flag.NewFlagSet(args[0], flag.ExitOnError), args[1:]) + var ( + dFlags DefaultFlags + ) + + flagSet := flag.NewFlagSet(args[0], flag.ExitOnError) + err := parseDefaultFlags(&dFlags, flagSet) + if err != nil { + log.Fatalf("parse default flags: %v", err) + } + + var ( + flags = Flags{ + DefaultFlags: dFlags, + } + ) + + err = parseAdditionalFlags(&flags, flagSet, args[1:]) if err != nil { - log.Fatalf("parse flags: %v", err) + log.Fatalf("parse additioanl flags: %v", err) } return flags } -func parseFlags(fs *flag.FlagSet, args []string) (Flags, error) { +func ParseDefault() DefaultFlags { + args := os.Args var ( - f Flags + flags DefaultFlags ) - fs.StringVar( - &f.ConfigPath, - flagKeyConfigFile, - defaultConfigFilePath, - "configuration file path") + flagSet := flag.NewFlagSet(args[0], flag.ExitOnError) + err := parseDefaultFlags(&flags, flagSet) + if err != nil { + log.Fatalf("parse default flags: %v", err) + } + return flags +} + +func parseDefaultFlags(f *DefaultFlags, fs *flag.FlagSet) error { fs.BoolVar( &f.Verbose, flagKeyVarbose, false, "verbose output") - //goland:noinspection SpellCheckingInspection fs.BoolVar( &f.PrintErrorStackTrace, flagKeyErrorStackTrace, false, "output errors stacktrace") - fs.BoolVar( - &f.PrintProcessedConfig, - flagKeyPrintConfig, - false, - "output processed config") fs.BoolVar( &f.DryRun, flagKeyDryRun, false, "dry run mode (can be combine with `-v`)") + fs.Var( + &f.MissingKey, + flagKeyMissingKey, + fmt.Sprintf( + "`missingkey` template option: %v, %v, %v, %v", + entity.MissingKeyDefault, + entity.MissingKeyInvalid, + entity.MissingKeyZero, + entity.MissingKeyError, + ), + ) + fs.Var( + &f.TemplateVars, + flagKeyTemplateVariables, + "template variables") + return nil +} + +func parseAdditionalFlags(f *Flags, fs *flag.FlagSet, args []string) error { + fs.StringVar( + &f.ConfigPath, + flagKeyConfigFile, + defaultConfigFilePath, + "configuration file path") + fs.BoolVar( + &f.PrintProcessedConfig, + flagKeyPrintConfig, + false, + "output processed config") fs.BoolVar( &f.Version, flagKeyVersion, false, "output version") - fs.Var( - &f.TemplateVars, - flagKeyTemplateVariables, - "template variables (override config variables tree)") fs.StringVar( &f.AWD, flagKeyApplicationWorkingDirectory, @@ -125,17 +170,6 @@ func parseFlags(fs *flag.FlagSet, args []string) (Flags, error) { flagKeyPreprocessingAllFiles, true, "preprocessing all files before saving") - fs.Var( - &f.MissingKey, - flagKeyMissingKey, - fmt.Sprintf( - "`missingkey` template option: %v, %v, %v, %v", - entity.MissingKeyDefault, - entity.MissingKeyInvalid, - entity.MissingKeyZero, - entity.MissingKeyError, - ), - ) fs.Var( &f.Group, flagKeyGroup, @@ -143,7 +177,7 @@ func parseFlags(fs *flag.FlagSet, args []string) (Flags, error) { ) err := fs.Parse(args) if err != nil { - return f, err + return xerrors.Errorf("parse args: %w", err) } for i, arg := range args { @@ -152,9 +186,9 @@ func parseFlags(fs *flag.FlagSet, args []string) (Flags, error) { f.ReadStdin = true break } - return f, xerrors.Errorf("%w", ErrDashFlagNotLast) + return xerrors.Errorf("%w", ErrDashFlagNotLast) } } - return f, nil + return nil } diff --git a/pkg/core/engin.go b/pkg/core/engin.go index dc823f3..a7506ab 100644 --- a/pkg/core/engin.go +++ b/pkg/core/engin.go @@ -1,11 +1,14 @@ package core import ( + "sync" + + "golang.org/x/xerrors" + "github.com/kozmod/progen/internal/entity" "github.com/kozmod/progen/internal/exec" "github.com/kozmod/progen/internal/factory" - "golang.org/x/xerrors" - "sync" + "github.com/kozmod/progen/internal/flag" ) type Engin struct { @@ -16,66 +19,23 @@ type Engin struct { fs []entity.Action[[]string] rm []entity.Action[[]string] - templateVars map[string]any - templateOptions []string - logger entity.Logger } -func (e *Engin) AddTemplateVars(vars map[string]any) *Engin { - e.mx.Lock() - defer e.mx.Unlock() - e.templateVars = entity.MergeKeys(e.templateVars, vars) - return e -} - -func (e *Engin) AddFilesActions(a ...entity.Action[[]File]) *Engin { - e.mx.Lock() - defer e.mx.Unlock() - actions := toEntityActions(func(f File) entity.UndefinedFile { - return entity.UndefinedFile{ - Path: f.Path, - Data: &f.Data, - } - }, a...) - e.files = append(e.files, actions...) - return e -} - -func (e *Engin) AddExecuteCommandActions(a ...entity.Action[[]Cmd]) *Engin { +func (e *Engin) AddActions(actions ...Action) *Engin { e.mx.Lock() defer e.mx.Unlock() - commands := toEntityActions(func(cmd Cmd) entity.Command { - return entity.Command(cmd) - }, a...) - e.cmd = append(e.cmd, commands...) - return e -} - -func (e *Engin) AddCreateDirsActions(a ...entity.Action[[]string]) *Engin { - e.mx.Lock() - defer e.mx.Unlock() - e.dirs = append(e.dirs, a...) - return e -} - -func (e *Engin) AddRmActions(a ...entity.Action[[]string]) *Engin { - e.mx.Lock() - defer e.mx.Unlock() - e.rm = append(e.rm, a...) - return e -} - -func (e *Engin) AddFsActions(a ...entity.Action[[]string]) *Engin { - e.mx.Lock() - defer e.mx.Unlock() - e.fs = append(e.fs, a...) + for _, action := range actions { + action.add(e) + } return e } func (e *Engin) Run() error { var ( - logFatalSuffixFn = entity.NewAppendVPlusOrV(true) //todo + flags = flag.ParseDefault() + + logFatalSuffixFn = entity.NewAppendVPlusOrV(flags.PrintErrorStackTrace) actionFilter factory.DummyActionFilter ) @@ -84,7 +44,7 @@ func (e *Engin) Run() error { var logger entity.Logger if e.logger == nil { - l, err := factory.NewLogger(true) //todo + l, err := factory.NewLogger(flags.Verbose) if err != nil { return xerrors.Errorf("failed to initialize logger: %w", err) } @@ -93,7 +53,7 @@ func (e *Engin) Run() error { procChain, err := factory.NewExecutorChainFactory( logger, - true, //todo + flags.DryRun, func(executors []entity.Executor) entity.Executor { return exec.NewChain(executors) }, @@ -105,8 +65,8 @@ func (e *Engin) Run() error { factory.NewExecutorBuilderFactory( e.fs, factory.NewFsExecFactory( - e.templateVars, - e.templateOptions, + flags.TemplateVars.Vars, + []string{flags.MissingKey.String()}, ).Create, actionFilter, ), @@ -118,8 +78,8 @@ func (e *Engin) Run() error { factory.NewExecutorBuilderFactory( e.files, factory.NewFileExecutorFactory( - e.templateVars, - e.templateOptions, + flags.TemplateVars.Vars, + []string{flags.MissingKey.String()}, ).Create, actionFilter, ), @@ -131,12 +91,14 @@ func (e *Engin) Run() error { ).Create() if err != nil { + //goland:noinspection ALL logger.Errorf(logFatalSuffixFn("create processors chain: "), err) return err } err = procChain.Exec() if err != nil { + //goland:noinspection ALL logger.Errorf(logFatalSuffixFn("execute chain: "), err) return err } diff --git a/pkg/core/entity.go b/pkg/core/entity.go index b07eee9..e4999bd 100644 --- a/pkg/core/entity.go +++ b/pkg/core/entity.go @@ -5,6 +5,10 @@ import ( ) type ( + Action interface { + add(engin *Engin) + } + File struct { Path string Data []byte @@ -13,35 +17,138 @@ type ( Cmd entity.Command ) -func toEntityActions[S ActionVal, T any](convert func(s S) T, in ...entity.Action[[]S]) []entity.Action[[]T] { - res := make([]entity.Action[[]T], len(in)) - for i, action := range in { - target := make([]T, len(in)) - for j, s := range action.Val { - target[j] = convert(s) - } - res[i] = entity.Action[[]T]{ - Priority: action.Priority, - Name: action.Name, - Val: target, - } +type Files entity.Action[[]File] + +func (c Files) add(e *Engin) { + if e != nil { + files := convert(c.Val, func(s File) entity.UndefinedFile { + return entity.UndefinedFile{ + Path: s.Path, + Data: &s.Data, + } + }) + e.files = append(e.files, entity.Action[[]entity.UndefinedFile]{ + Name: c.Name, + Val: files, + Priority: c.Priority, + }) + } +} + +func FilesAction(name string, files ...File) Files { + return Files{ + Name: name, + Val: files, + } +} + +type Command entity.Action[[]Cmd] + +func (c Command) add(e *Engin) { + if e != nil { + commands := convert(c.Val, func(s Cmd) entity.Command { + return entity.Command{ + Args: s.Args, + Dir: s.Dir, + Cmd: s.Cmd, + } + }) + e.cmd = append(e.cmd, entity.Action[[]entity.Command]{ + Name: c.Name, + Val: commands, + Priority: c.Priority, + }) + } +} + +func (c Command) WithPriority(priority int) Command { + c.Priority = priority + return c +} + +func CmdAction(name string, commands ...Cmd) Command { + return Command{ + Name: name, + Val: commands, + } +} + +type Dirs entity.Action[[]string] + +func (d Dirs) add(e *Engin) { + if e != nil { + e.dirs = append(e.dirs, entity.Action[[]string]{ + Name: d.Name, + Val: d.Val, + Priority: d.Priority, + }) } - return res } -type ActionVal interface { - string | Cmd | File +func (d Dirs) WithPriority(priority int) Dirs { + d.Priority = priority + return d } -func NewAction[T ActionVal](name string, val ...T) entity.Action[[]T] { - return entity.Action[[]T]{ - Name: name, - Priority: 0, - Val: val, +func DirsAction(name string, dirs ...string) Dirs { + return Dirs{ + Name: name, + Val: dirs, } } -func WithPriority[T any](action entity.Action[[]T], priority int) entity.Action[[]T] { - action.Priority = priority - return action +type Rm entity.Action[[]string] + +func (r Rm) add(e *Engin) { + if e != nil { + e.rm = append(e.rm, entity.Action[[]string]{ + Name: r.Name, + Val: r.Val, + Priority: r.Priority, + }) + } +} + +func (r Rm) WithPriority(priority int) Rm { + r.Priority = priority + return r +} + +func RmAction(name string, rm ...string) Rm { + return Rm{ + Name: name, + Val: rm, + } +} + +type Fs entity.Action[[]string] + +func (f Fs) add(e *Engin) { + if e != nil { + e.fs = append(e.fs, entity.Action[[]string]{ + Name: f.Name, + Val: f.Val, + Priority: f.Priority, + }) + } +} + +func (f Fs) WithPriority(priority int) Fs { + f.Priority = priority + return f +} + +func FsAction(name string, fs ...string) Fs { + return Fs{ + Name: name, + Val: fs, + } +} + +func convert[S any, T any](s []S, fn func(s S) T) []T { + res := make([]T, len(s)) + for i, val := range s { + res[i] = fn(val) + } + return res } diff --git a/pkg/core/t_test.go b/pkg/core/t_test.go deleted file mode 100644 index ddbf07c..0000000 --- a/pkg/core/t_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package core - -import ( - "testing" -) - -func Test(t *testing.T) { - e := Engin{} - e.AddFilesActions( - WithPriority( - NewAction("files", - File{ - Path: "/file_1", - Data: []byte("xxxxxxxxxx"), - }, - ), - 1, - ), - ). - AddCreateDirsActions() - - _ = e.Run() -} From 3ccd20f643222b934caa3b32fb4ded51290d4063 Mon Sep 17 00:00:00 2001 From: kozmod Date: Mon, 9 Dec 2024 22:53:45 +0300 Subject: [PATCH 3/3] [#98] move core functionality to shared `lib` --- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 2 +- .golangci.yml | 20 +-- go.mod | 14 +-- go.sum | 71 +++-------- internal/entity/entity.go | 7 ++ internal/entity/util.go | 14 --- internal/entity/util_test.go | 61 --------- internal/exec/chain.go | 5 +- internal/exec/fs.go | 32 ++--- internal/exec/fs_test.go | 10 +- internal/exec/fss.go | 136 +++++++++++++++++++++ internal/exec/fss_test.go | 135 ++++++++++++++++++++ internal/exec/helper_test.go | 1 + internal/factory/dir_exec.go | 4 +- internal/factory/{file.go => file_exec.go} | 0 internal/factory/fs_exec.go | 18 +-- internal/factory/fss_exec.go | 65 ++++++++++ internal/factory/rm_exec.go | 4 +- internal/flag/flags.go | 53 ++++---- internal/flag/flags_test.go | 123 ++++++++++++++----- main.go | 8 +- pkg/core/engin.go | 61 ++++++--- pkg/core/entity.go | 48 ++++++-- 25 files changed, 622 insertions(+), 274 deletions(-) create mode 100644 internal/exec/fss.go create mode 100644 internal/exec/fss_test.go rename internal/factory/{file.go => file_exec.go} (100%) create mode 100644 internal/factory/fss_exec.go diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 48050cb..9f2349a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/setup-go@v3 with: - go-version: 1.21.6 + go-version: 1.22.9 - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 238fc5a..25b3833 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.21.6 + go-version: 1.22.9 - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5e336f2..000a449 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.21.6 + go-version: 1.22.9 - name: Test run: go test -v ./... -cover -coverprofile cover.out && go tool cover -func cover.out diff --git a/.golangci.yml b/.golangci.yml index 3cdd5a0..9f5a016 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,21 +4,15 @@ run: timeout: 5m issues-exit-code: 1 tests: true - skip-dirs: - - bin - - vendor - - var - - tmp - - .github output: - format: colored-line-number + formats: colored-line-number print-issued-lines: true print-linter-name: true linters-settings: govet: - check-shadowing: true + shadow: true dupl: threshold: 100 goconst: @@ -59,12 +53,20 @@ linters: - gosec - govet - ineffassign - - megacheck + - gosimple + - staticcheck + - unused # - revive # comment check - typecheck - unused # will be used insted of varcheck + deadcode + structcheck. More info https://github.com/golangci/golangci-lint/issues/1841 issues: + exclude-dirs: + - bin + - vendor + - var + - tmp + - .github exclude-use-default: false exclude: # _ instead of err checks diff --git a/go.mod b/go.mod index 4f4d723..06293b1 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/kozmod/progen -go 1.21.1 +go 1.22 require ( - github.com/go-resty/resty/v2 v2.11.0 - github.com/stretchr/testify v1.8.4 - go.uber.org/zap v1.26.0 - golang.org/x/sync v0.6.0 - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 + github.com/go-resty/resty/v2 v2.16.2 + github.com/stretchr/testify v1.10.0 + go.uber.org/zap v1.27.0 + golang.org/x/sync v0.10.0 + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da gopkg.in/yaml.v3 v3.0.1 ) @@ -15,5 +15,5 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.20.0 // indirect + golang.org/x/net v0.32.0 // indirect ) diff --git a/go.sum b/go.sum index cc0efad..ae23ab7 100644 --- a/go.sum +++ b/go.sum @@ -1,64 +1,25 @@ 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/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= -github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= +github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg= +github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= 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/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 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= diff --git a/internal/entity/entity.go b/internal/entity/entity.go index 70d81cb..2a1835b 100644 --- a/internal/entity/entity.go +++ b/internal/entity/entity.go @@ -1,6 +1,7 @@ package entity import ( + "io/fs" "path/filepath" "regexp" @@ -47,6 +48,7 @@ const ( LogSliceSep = Comma + Space ) +//goland:noinspection SpellCheckingInspection type ( FileProducer interface { Get() (DataFile, error) @@ -118,6 +120,11 @@ func (a Action[T]) WithPriority(priority int) Action[T] { return a } +type TargetFs struct { + TargetDir string + Fs fs.FS +} + type UndefinedFile struct { Path string Data *[]byte diff --git a/internal/entity/util.go b/internal/entity/util.go index d4d1446..b5c34d9 100644 --- a/internal/entity/util.go +++ b/internal/entity/util.go @@ -2,20 +2,6 @@ package entity import "reflect" -func Unique[T comparable](in []T) []T { - set := make(map[T]struct{}, len(in)) - out := make([]T, 0, len(in)) - for _, val := range in { - _, ok := set[val] - if ok { - continue - } - out = append(out, val) - set[val] = struct{}{} - } - return out -} - func MergeKeys(dst, src map[string]any) map[string]any { if dst == nil { return src diff --git a/internal/entity/util_test.go b/internal/entity/util_test.go index d66f08b..dec664e 100644 --- a/internal/entity/util_test.go +++ b/internal/entity/util_test.go @@ -8,67 +8,6 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_Unique(t *testing.T) { - t.Parallel() - - type useCase[T comparable] struct { - in []T - exp []T - } - - stringCases := []useCase[string]{ - { - in: []string{"a", "a", "a"}, - exp: []string{"a"}, - }, - { - in: []string{"a", "b", "c"}, - exp: []string{"a", "b", "c"}, - }, - { - in: []string{}, - exp: []string{}, - }, - { - in: nil, - exp: []string{}, - }, - } - - for i, tc := range stringCases { - t.Run(fmt.Sprintf("str_case_%d", i), func(t *testing.T) { - res := Unique(tc.in) - assert.Equal(t, tc.exp, res) - }) - } - - intCases := []useCase[int32]{ - { - in: []int32{1, 1, 1}, - exp: []int32{1}, - }, - { - in: []int32{1, 2, 3}, - exp: []int32{1, 2, 3}, - }, - { - in: []int32{}, - exp: []int32{}, - }, - { - in: nil, - exp: []int32{}, - }, - } - - for i, tc := range intCases { - t.Run(fmt.Sprintf("int_case_%d", i), func(t *testing.T) { - res := Unique(tc.in) - assert.Equal(t, tc.exp, res) - }) - } -} - func Test_MergeKeys(t *testing.T) { t.Parallel() diff --git a/internal/exec/chain.go b/internal/exec/chain.go index 3abe97b..dc9f090 100644 --- a/internal/exec/chain.go +++ b/internal/exec/chain.go @@ -1,9 +1,10 @@ package exec import ( - "golang.org/x/xerrors" "sync" + "golang.org/x/xerrors" + "github.com/kozmod/progen/internal/entity" ) @@ -72,7 +73,7 @@ func (p *Preprocessors) Get() []entity.Preprocessor { if len(p.val) == 0 { return nil } - res := make([]entity.Preprocessor, len(p.val), len(p.val)) + res := make([]entity.Preprocessor, len(p.val)) copy(res, p.val) return res } diff --git a/internal/exec/fs.go b/internal/exec/fs.go index 8e23311..876b3ce 100644 --- a/internal/exec/fs.go +++ b/internal/exec/fs.go @@ -10,7 +10,7 @@ import ( "github.com/kozmod/progen/internal/entity" ) -type FileSystemStrategy struct { +type FileSystemModifyStrategy struct { logger entity.Logger strategiesFn func(paths map[string]string) []entity.FileStrategy templateProcFn func() entity.TemplateProc @@ -19,12 +19,12 @@ type FileSystemStrategy struct { removeAllFn func(path string) error } -func NewFileSystemStrategy( +func NewFileSystemModifyStrategy( templateData, templateFns map[string]any, templateOptions []string, - logger entity.Logger) *FileSystemStrategy { - return &FileSystemStrategy{ + logger entity.Logger) *FileSystemModifyStrategy { + return &FileSystemModifyStrategy{ logger: logger, strategiesFn: func(paths map[string]string) []entity.FileStrategy { return []entity.FileStrategy{ @@ -46,7 +46,7 @@ func NewFileSystemStrategy( } } -func (e *FileSystemStrategy) Apply(dir string) (string, error) { +func (e *FileSystemModifyStrategy) Apply(dir string) (string, error) { type ( Entity struct { Path string @@ -67,7 +67,7 @@ func (e *FileSystemStrategy) Apply(dir string) (string, error) { } entPath, err := e.templateProcFn().Process(path, path) if err != nil { - return xerrors.Errorf("fs: process template to path [%s]: %w", path, err) + return xerrors.Errorf("fs modify: process template to path [%s]: %w", path, err) } filePaths[path] = entPath entitySet[path] = Entity{ @@ -77,7 +77,7 @@ func (e *FileSystemStrategy) Apply(dir string) (string, error) { return err }) if err != nil { - return entity.Empty, xerrors.Errorf("fs: walk dir [%s]: %w", dir, err) + return entity.Empty, xerrors.Errorf("fs modify: walk dir [%s]: %w", dir, err) } var ( @@ -102,13 +102,13 @@ func (e *FileSystemStrategy) Apply(dir string) (string, error) { dirExec := e.dirExecutorFn(dirs) err = dirExec.Exec() if err != nil { - return entity.Empty, xerrors.Errorf("fs: dirs execute: %w", err) + return entity.Empty, xerrors.Errorf("fs modify: dirs execute: %w", err) } fileExec := e.fileExecutorFn(fileProducers, e.strategiesFn(filePaths)) err = fileExec.Exec() if err != nil { - return entity.Empty, xerrors.Errorf("fs: files execute: %w", err) + return entity.Empty, xerrors.Errorf("fs modify: files execute: %w", err) } for old, ent := range entitySet { @@ -117,25 +117,25 @@ func (e *FileSystemStrategy) Apply(dir string) (string, error) { } err = e.removeAllFn(old) if err != nil { - return entity.Empty, xerrors.Errorf("fs: remove old: %w", err) + return entity.Empty, xerrors.Errorf("fs modify: remove old: %w", err) } - e.logger.Infof("fs: remove: %s", old) + e.logger.Infof("fs modify: remove: %s", old) } return dir, nil } -type DryRunFileSystemStrategy struct { +type DryRunFileSystemModifyStrategy struct { logger entity.Logger } -func NewDryRunFileSystemStrategy(logger entity.Logger) *DryRunFileSystemStrategy { - return &DryRunFileSystemStrategy{ +func NewDryRunFileSystemModifyStrategy(logger entity.Logger) *DryRunFileSystemModifyStrategy { + return &DryRunFileSystemModifyStrategy{ logger: logger, } } -func (e *DryRunFileSystemStrategy) Apply(dir string) (string, error) { - e.logger.Infof("fs: dir execute: %s", dir) +func (e *DryRunFileSystemModifyStrategy) Apply(dir string) (string, error) { + e.logger.Infof("fs modify: dir execute: %s", dir) return dir, nil } diff --git a/internal/exec/fs_test.go b/internal/exec/fs_test.go index 26f6e08..06616c4 100644 --- a/internal/exec/fs_test.go +++ b/internal/exec/fs_test.go @@ -10,7 +10,7 @@ import ( "github.com/kozmod/progen/internal/entity" ) -func Test_FileSystemStrategy(t *testing.T) { +func Test_FileSystemModifyStrategy(t *testing.T) { SkipSLowTest(t) const ( @@ -56,7 +56,7 @@ func Test_FileSystemStrategy(t *testing.T) { CreateFile(t, pathTempB, dataB) CreateFile(t, pathTempC, dataC) - str := FileSystemStrategy{ + str := FileSystemModifyStrategy{ templateProcFn: func() entity.TemplateProc { return entity.NewTemplateProc(templateData, nil, nil) }, @@ -106,7 +106,7 @@ func Test_FileSystemStrategy(t *testing.T) { CreateFile(t, pathTempB, dataB) CreateFile(t, pathTempC, dataC) - str := NewFileSystemStrategy(templateData, nil, nil, mockLogger) + str := NewFileSystemModifyStrategy(templateData, nil, nil, mockLogger) dir, err := str.Apply(tmpDir) a.NoError(err) @@ -122,7 +122,7 @@ func Test_FileSystemStrategy(t *testing.T) { }) } -func Test_DryRunFileSystemStrategy(t *testing.T) { +func Test_DryRunFileSystemModifyStrategy(t *testing.T) { const ( dir = "some_dir" ) @@ -133,7 +133,7 @@ func Test_DryRunFileSystemStrategy(t *testing.T) { assert.Equal(t, []any{dir}, args) }, } - res, err := NewDryRunFileSystemStrategy(mockLogger).Apply(dir) + res, err := NewDryRunFileSystemModifyStrategy(mockLogger).Apply(dir) assert.NoError(t, err) assert.Equal(t, dir, res) } diff --git a/internal/exec/fss.go b/internal/exec/fss.go new file mode 100644 index 0000000..d4ec660 --- /dev/null +++ b/internal/exec/fss.go @@ -0,0 +1,136 @@ +package exec + +import ( + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + + "golang.org/x/xerrors" + + "github.com/kozmod/progen/internal/entity" +) + +type FileSystemSaveStrategy struct { + fs fs.FS + logger entity.Logger + strategiesFn func() []entity.FileStrategy + templateProcFn func() entity.TemplateProc + dirExecutorFn func(dirs []string) entity.Executor + fileExecutorFn func(producers []entity.FileProducer, strategies []entity.FileStrategy) entity.Executor + removeAllFn func(path string) error +} + +func NewFileSystemSaveStrategy( + fs fs.FS, + templateData, + templateFns map[string]any, + templateOptions []string, + logger entity.Logger) *FileSystemSaveStrategy { + return &FileSystemSaveStrategy{ + fs: fs, + logger: logger, + strategiesFn: func() []entity.FileStrategy { + return []entity.FileStrategy{ + NewTemplateFileStrategy(templateData, templateFns, templateOptions), + NewSaveFileStrategy(logger), + } + }, + templateProcFn: func() entity.TemplateProc { + return entity.NewTemplateProc(templateData, templateFns, templateOptions) + }, + dirExecutorFn: func(dirs []string) entity.Executor { + return NewDirExecutor(dirs, []entity.DirStrategy{NewMkdirAllStrategy(logger)}) + }, + fileExecutorFn: func(producers []entity.FileProducer, strategies []entity.FileStrategy) entity.Executor { + return NewFilesExecutor(producers, strategies) + }, + removeAllFn: os.RemoveAll, + } +} + +func (e *FileSystemSaveStrategy) Apply(targetDir string) (string, error) { + var ( + dirs []string + fileProducers []entity.FileProducer + root = entity.Dot + ) + + err := fs.WalkDir(e.fs, root, func(path string, info fs.DirEntry, err error) error { + if info == nil { + return err + } + + entPath, err := e.templateProcFn().Process(path, path) + if err != nil { + return xerrors.Errorf("fs save: process template to path [%s]: %w", path, err) + } + + entPath = filepath.Join(targetDir, entPath) + + var ( + srcFile fs.File + data []byte + ) + switch { + case info.IsDir(): + dirs = append(dirs, entPath) + default: + srcFile, err = e.fs.Open(path) + if err != nil { + return fmt.Errorf("fs save: open fs file [%s]: %v", path, err) + } + defer func() { + _ = srcFile.Close() + }() + + data, err = io.ReadAll(srcFile) + if err != nil { + return fmt.Errorf("fs save: read fs file [%s]: %v", path, err) + } + + fileProducers = append(fileProducers, + NewDummyProducer( + entity.DataFile{ + FileInfo: entity.NewFileInfo(entPath), + Data: data, + }, + ), + ) + } + return err + }) + if err != nil { + return entity.Empty, xerrors.Errorf("fs save: walk dir: %w", err) + } + + dirExec := e.dirExecutorFn(dirs) + err = dirExec.Exec() + if err != nil { + return entity.Empty, xerrors.Errorf("fs save: dirs execute: %w", err) + } + + fileExec := e.fileExecutorFn(fileProducers, e.strategiesFn()) + err = fileExec.Exec() + if err != nil { + return entity.Empty, xerrors.Errorf("fs save: files execute: %w", err) + } + + return targetDir, nil +} + +type DryRunFileSystemSaveStrategy struct { + logger entity.Logger +} + +func NewDryRunFileSystemSaveStrategy(logger entity.Logger) *DryRunFileSystemSaveStrategy { + return &DryRunFileSystemSaveStrategy{ + logger: logger, + } +} + +func (e *DryRunFileSystemSaveStrategy) Apply(dir string) (string, error) { + e.logger.Infof("fs save: dir execute: %s", dir) + return dir, nil +} diff --git a/internal/exec/fss_test.go b/internal/exec/fss_test.go new file mode 100644 index 0000000..b2365d2 --- /dev/null +++ b/internal/exec/fss_test.go @@ -0,0 +1,135 @@ +package exec + +import ( + "fmt" + "os" + "path/filepath" + "testing" + "testing/fstest" + + "github.com/stretchr/testify/assert" +) + +func Test_FileSystemSaveStrategy(t *testing.T) { + SkipSLowTest(t) + + const ( + pathA = "some_file.txt" + pathB = "{{ .var }}/some_file.txt" + pathC = "c/{{ .var }}.txt" + + varTemplateVariableValue = "DATA" + ) + + var ( + dataA = []byte("{{ .var }}") + dataB = []byte("B") + dataC = []byte("C") + + templateData = map[string]any{ + "var": varTemplateVariableValue, + } + + expectedSubPathB = fmt.Sprintf("%s/some_file.txt", varTemplateVariableValue) + expectedSubPathC = fmt.Sprintf("c/%s.txt", varTemplateVariableValue) + ) + t.Run("real_save_fs", func(t *testing.T) { + WithTempDir(t, func(tmpDir string) { + var ( + tmpDirTarget = filepath.Join(tmpDir, "result") + + expectedPathA = filepath.Join(tmpDirTarget, pathA) + expectedPathB = filepath.Join(tmpDirTarget, expectedSubPathB) + expectedPathC = filepath.Join(tmpDirTarget, expectedSubPathC) + + a = assert.New(t) + mockLogger = MockLogger{ + infof: func(format string, args ...any) { + a.NotEmpty(format) + }, + } + ) + + fs := fstest.MapFS{ + pathA: { + Data: dataA, + }, + pathB: { + Data: dataB, + }, + pathC: { + Data: dataC, + }, + } + + str := NewFileSystemSaveStrategy(fs, templateData, nil, nil, mockLogger) + + dir, err := str.Apply(tmpDirTarget) + a.NoError(err) + a.Equal(tmpDirTarget, dir) + a.FileExists(expectedPathA) + a.FileExists(expectedPathB) + a.FileExists(expectedPathC) + + AssertFileDataEqual(t, expectedPathA, []byte(varTemplateVariableValue)) + AssertFileDataEqual(t, expectedPathB, dataB) + AssertFileDataEqual(t, expectedPathC, dataC) + }) + }) + t.Run("save_from_real_fs", func(t *testing.T) { + WithTempDir(t, func(tmpDir string) { + var ( + pathTempA = filepath.Join(tmpDir, pathA) + pathTempB = filepath.Join(tmpDir, pathB) + pathTempC = filepath.Join(tmpDir, pathC) + + a = assert.New(t) + mockLogger = MockLogger{ + infof: func(format string, args ...any) { + a.NotEmpty(format) + }, + } + tmpDirTarget = filepath.Join(tmpDir, "result") + + expectedPathA = filepath.Join(tmpDirTarget, pathA) + expectedPathB = filepath.Join(tmpDirTarget, expectedSubPathB) + expectedPathC = filepath.Join(tmpDirTarget, expectedSubPathC) + ) + + CreateFile(t, pathTempA, dataA) + CreateFile(t, pathTempB, dataB) + CreateFile(t, pathTempC, dataC) + + fs := os.DirFS(tmpDir) + + str := NewFileSystemSaveStrategy(fs, templateData, nil, nil, mockLogger) + + dir, err := str.Apply(tmpDirTarget) + a.NoError(err) + a.Equal(tmpDirTarget, dir) + a.FileExists(expectedPathA) + a.FileExists(expectedPathB) + a.FileExists(expectedPathC) + + AssertFileDataEqual(t, expectedPathA, []byte(varTemplateVariableValue)) + AssertFileDataEqual(t, expectedPathB, dataB) + AssertFileDataEqual(t, expectedPathC, dataC) + }) + }) +} + +func Test_DryRunFileSystemSaveStrategy(t *testing.T) { + const ( + dir = "some_dir" + ) + mockLogger := MockLogger{ + infof: func(format string, args ...any) { + assert.NotEmpty(t, format) + assert.NotEmpty(t, args) + assert.Equal(t, []any{dir}, args) + }, + } + res, err := NewDryRunFileSystemSaveStrategy(mockLogger).Apply(dir) + assert.NoError(t, err) + assert.Equal(t, dir, res) +} diff --git a/internal/exec/helper_test.go b/internal/exec/helper_test.go index b6afbad..cf2f663 100644 --- a/internal/exec/helper_test.go +++ b/internal/exec/helper_test.go @@ -43,6 +43,7 @@ func CreateFile(t *testing.T, path string, data []byte) { assert.NoError(t, err) } + //nolint:gosec err := os.WriteFile(path, data, os.ModePerm) assert.NoError(t, err) } diff --git a/internal/factory/dir_exec.go b/internal/factory/dir_exec.go index 9a8640d..e85639c 100644 --- a/internal/factory/dir_exec.go +++ b/internal/factory/dir_exec.go @@ -1,6 +1,8 @@ package factory import ( + "slices" + "github.com/kozmod/progen/internal/entity" "github.com/kozmod/progen/internal/exec" ) @@ -11,7 +13,7 @@ func NewMkdirExecutor(dirs []string, logger entity.Logger, dryRun bool) (entity. return nil, nil } - dirSet := entity.Unique(dirs) + dirSet := slices.Compact(dirs) if dryRun { return exec.NewDirExecutor(dirSet, []entity.DirStrategy{exec.NewDryRunMkdirAllStrategy(logger)}), nil diff --git a/internal/factory/file.go b/internal/factory/file_exec.go similarity index 100% rename from internal/factory/file.go rename to internal/factory/file_exec.go diff --git a/internal/factory/fs_exec.go b/internal/factory/fs_exec.go index c9f9e17..0fdda99 100644 --- a/internal/factory/fs_exec.go +++ b/internal/factory/fs_exec.go @@ -1,26 +1,28 @@ package factory import ( + "slices" + "github.com/kozmod/progen/internal/entity" "github.com/kozmod/progen/internal/exec" ) -type FsExecFactory struct { +type FsModifyExecFactory struct { templateData map[string]any templateOptions []string } -func NewFsExecFactory( +func NewFsModifyExecFactory( templateData map[string]any, templateOptions []string, -) *FsExecFactory { - return &FsExecFactory{ +) *FsModifyExecFactory { + return &FsModifyExecFactory{ templateData: templateData, templateOptions: templateOptions, } } -func (f FsExecFactory) Create( +func (f FsModifyExecFactory) Create( dirs []string, logger entity.Logger, dryRun bool, @@ -30,14 +32,14 @@ func (f FsExecFactory) Create( return nil, nil } - dirSet := entity.Unique(dirs) + dirSet := slices.Compact(dirs) if dryRun { - return exec.NewDirExecutor(dirSet, []entity.DirStrategy{exec.NewDryRunFileSystemStrategy(logger)}), nil + return exec.NewDirExecutor(dirSet, []entity.DirStrategy{exec.NewDryRunFileSystemModifyStrategy(logger)}), nil } return exec.NewDirExecutor(dirSet, []entity.DirStrategy{ - exec.NewFileSystemStrategy( + exec.NewFileSystemModifyStrategy( f.templateData, entity.TemplateFnsMap, f.templateOptions, diff --git a/internal/factory/fss_exec.go b/internal/factory/fss_exec.go new file mode 100644 index 0000000..66f0c29 --- /dev/null +++ b/internal/factory/fss_exec.go @@ -0,0 +1,65 @@ +package factory + +import ( + "github.com/kozmod/progen/internal/entity" + "github.com/kozmod/progen/internal/exec" +) + +type FsSaveExecFactory struct { + templateData map[string]any + templateOptions []string +} + +func NewFsSaveExecFactory( + templateData map[string]any, + templateOptions []string, +) *FsSaveExecFactory { + return &FsSaveExecFactory{ + templateData: templateData, + templateOptions: templateOptions, + } +} + +func (f FsSaveExecFactory) Create( + fsList []entity.TargetFs, + logger entity.Logger, + dryRun bool, +) (entity.Executor, error) { + if len(fsList) == 0 { + logger.Infof("fs executor: `fs save` section is empty") + return nil, nil + } + + fsStrategyBydDir := make(map[string][]entity.DirStrategy, len(fsList)) + for _, targetFs := range fsList { + fsStrategyBydDir[targetFs.TargetDir] = append( + fsStrategyBydDir[targetFs.TargetDir], + exec.NewFileSystemSaveStrategy( + targetFs.Fs, + f.templateData, + entity.TemplateFnsMap, + f.templateOptions, + logger), + ) + } + + executors := make([]entity.Executor, 0, len(fsStrategyBydDir)) + for dir, strategy := range fsStrategyBydDir { + dirs := []string{dir} + if dryRun { + executors = append(executors, + exec.NewDirExecutor(dirs, []entity.DirStrategy{exec.NewDryRunFileSystemSaveStrategy(logger)}), + ) + continue + } + executors = append(executors, + exec.NewDirExecutor( + dirs, + strategy, + ), + ) + + } + + return exec.NewChain(executors), nil +} diff --git a/internal/factory/rm_exec.go b/internal/factory/rm_exec.go index 0d02a21..aac8fa2 100644 --- a/internal/factory/rm_exec.go +++ b/internal/factory/rm_exec.go @@ -1,6 +1,8 @@ package factory import ( + "slices" + "github.com/kozmod/progen/internal/entity" "github.com/kozmod/progen/internal/exec" ) @@ -11,7 +13,7 @@ func NewRmExecutor(paths []string, logger entity.Logger, dryRun bool) (entity.Ex return nil, nil } - pathsSet := entity.Unique(paths) + pathsSet := slices.Compact(paths) if dryRun { return exec.NewRmAllExecutor(pathsSet, []entity.RmStrategy{exec.NewDryRmAllStrategy(logger)}), nil diff --git a/internal/flag/flags.go b/internal/flag/flags.go index 8ef1ad7..0adbb48 100644 --- a/internal/flag/flags.go +++ b/internal/flag/flags.go @@ -43,7 +43,7 @@ type DefaultFlags struct { } type Flags struct { - DefaultFlags + *DefaultFlags ConfigPath string Version bool @@ -70,43 +70,23 @@ func (f *Flags) FileLocationMessage() string { func Parse() Flags { args := os.Args - var ( - dFlags DefaultFlags - ) - flagSet := flag.NewFlagSet(args[0], flag.ExitOnError) - err := parseDefaultFlags(&dFlags, flagSet) - if err != nil { - log.Fatalf("parse default flags: %v", err) - } var ( flags = Flags{ - DefaultFlags: dFlags, + DefaultFlags: NewDefaultFlags(flagSet), } ) - err = parseAdditionalFlags(&flags, flagSet, args[1:]) + err := flags.Parse(flagSet, args[1:]) if err != nil { log.Fatalf("parse additioanl flags: %v", err) } return flags } -func ParseDefault() DefaultFlags { - args := os.Args - var ( - flags DefaultFlags - ) - flagSet := flag.NewFlagSet(args[0], flag.ExitOnError) - err := parseDefaultFlags(&flags, flagSet) - if err != nil { - log.Fatalf("parse default flags: %v", err) - } - return flags -} - -func parseDefaultFlags(f *DefaultFlags, fs *flag.FlagSet) error { +func NewDefaultFlags(fs *flag.FlagSet) *DefaultFlags { + var f DefaultFlags fs.BoolVar( &f.Verbose, flagKeyVarbose, @@ -133,14 +113,21 @@ func parseDefaultFlags(f *DefaultFlags, fs *flag.FlagSet) error { entity.MissingKeyError, ), ) - fs.Var( - &f.TemplateVars, - flagKeyTemplateVariables, - "template variables") + return &f +} + +func (f *DefaultFlags) Parse(fs *flag.FlagSet, args []string) error { + err := fs.Parse(args) + if err != nil { + return xerrors.Errorf("parse args: %w", err) + } return nil } -func parseAdditionalFlags(f *Flags, fs *flag.FlagSet, args []string) error { +func NewFlags(fs *flag.FlagSet) *Flags { + var ( + f = Flags{DefaultFlags: NewDefaultFlags(fs)} + ) fs.StringVar( &f.ConfigPath, flagKeyConfigFile, @@ -175,6 +162,11 @@ func parseAdditionalFlags(f *Flags, fs *flag.FlagSet, args []string) error { flagKeyGroup, "list of executing groups", ) + + return &f +} + +func (f *Flags) Parse(fs *flag.FlagSet, args []string) error { err := fs.Parse(args) if err != nil { return xerrors.Errorf("parse args: %w", err) @@ -189,6 +181,5 @@ func parseAdditionalFlags(f *Flags, fs *flag.FlagSet, args []string) error { return xerrors.Errorf("%w", ErrDashFlagNotLast) } } - return nil } diff --git a/internal/flag/flags_test.go b/internal/flag/flags_test.go index 0870bd8..c5565af 100644 --- a/internal/flag/flags_test.go +++ b/internal/flag/flags_test.go @@ -301,59 +301,113 @@ func Test_parseFlags(t *testing.T) { ) t.Run("success", func(t *testing.T) { - testFs := flag.NewFlagSet(fsName, flag.ContinueOnError) - flags, err := parseFlags(testFs, []string{v, printconfig, errstack, dr, f, configPath}) + var ( + testFs = flag.NewFlagSet(fsName, flag.ContinueOnError) + + flags = NewFlags(testFs) + ) + err := flags.Parse(testFs, []string{v, printconfig, errstack, dr, f, configPath}) assert.NoError(t, err) assert.Equal(t, Flags{ - Verbose: true, - PrintErrorStackTrace: true, + DefaultFlags: &DefaultFlags{ + DryRun: true, + Verbose: true, + PrintErrorStackTrace: true, + }, PrintProcessedConfig: true, PreprocessFiles: true, - DryRun: true, ConfigPath: configPath, AWD: dot}, - flags) + *flags) }) t.Run("success_when_dash_last", func(t *testing.T) { - testFs := flag.NewFlagSet(fsName, flag.ContinueOnError) - flags, err := parseFlags(testFs, []string{v, dr, dash}) + var ( + testFs = flag.NewFlagSet(fsName, flag.ContinueOnError) + + flags = NewFlags(testFs) + ) + err := flags.Parse(testFs, []string{v, dr, dash}) assert.NoError(t, err) assert.Equal(t, - Flags{Verbose: true, PreprocessFiles: true, DryRun: true, ConfigPath: configPath, AWD: dot, ReadStdin: true}, - flags) + Flags{ + DefaultFlags: &DefaultFlags{ + Verbose: true, + DryRun: true, + }, + PreprocessFiles: true, + ConfigPath: configPath, + AWD: dot, + ReadStdin: true, + }, + *flags) }) t.Run("success_when_dash_last_and_before_less_than", func(t *testing.T) { - testFs := flag.NewFlagSet(fsName, flag.ContinueOnError) - flags, err := parseFlags(testFs, []string{v, dr, dash, lessThan, configPath}) + var ( + testFs = flag.NewFlagSet(fsName, flag.ContinueOnError) + + flags = NewFlags(testFs) + ) + err := flags.Parse(testFs, []string{v, dr, dash, lessThan, configPath}) assert.NoError(t, err) assert.Equal(t, - Flags{Verbose: true, PreprocessFiles: true, DryRun: true, ConfigPath: configPath, AWD: dot, ReadStdin: true}, - flags) + Flags{ + DefaultFlags: &DefaultFlags{ + Verbose: true, + DryRun: true, + }, + PreprocessFiles: true, + ConfigPath: configPath, + AWD: dot, + ReadStdin: true, + }, + *flags) }) t.Run("error_when_flag_not_specified", func(t *testing.T) { - testFs := flag.NewFlagSet(fsName, flag.ContinueOnError) + var ( + testFs = flag.NewFlagSet(fsName, flag.ContinueOnError) + + flags = NewFlags(testFs) + ) + testFs.SetOutput(MockWriter{ assertWriteFn: func(p []byte) { assert.NotEmpty(t, p) }, }) - _, err := parseFlags(testFs, []string{v, dr, f}) + err := flags.Parse(testFs, []string{v, dr, f}) assert.Error(t, err) }) t.Run("error_when_dash_flag_not_last", func(t *testing.T) { - testFs := flag.NewFlagSet(fsName, flag.ContinueOnError) - _, err := parseFlags(testFs, []string{v, dash, dr, f, configPath}) + var ( + testFs = flag.NewFlagSet(fsName, flag.ContinueOnError) + + flags = NewFlags(testFs) + ) + err := flags.Parse(testFs, []string{v, dash, dr, f, configPath}) assert.Error(t, err) assert.True(t, errors.Is(err, ErrDashFlagNotLast)) }) t.Run("success_with_preprocess_flag_false", func(t *testing.T) { - testFs := flag.NewFlagSet(fsName, flag.ContinueOnError) - flags, err := parseFlags(testFs, []string{fmt.Sprintf("%s=%v", pf, false)}) + var ( + testFs = flag.NewFlagSet(fsName, flag.ContinueOnError) + + flags = NewFlags(testFs) + ) + err := flags.Parse(testFs, []string{fmt.Sprintf("%s=%v", pf, false)}) assert.NoError(t, err) assert.Equal(t, - Flags{Verbose: false, PreprocessFiles: false, DryRun: false, ConfigPath: configPath, AWD: dot, ReadStdin: false}, - flags) + Flags{ + DefaultFlags: &DefaultFlags{ + Verbose: false, + DryRun: false, + }, + PreprocessFiles: false, + ConfigPath: configPath, + AWD: dot, + ReadStdin: false, + }, + *flags) }) t.Run("missingkey", func(t *testing.T) { t.Run("success_with_set_default", func(t *testing.T) { @@ -361,36 +415,45 @@ func Test_parseFlags(t *testing.T) { missingKeyValueDefault = entity.MissingKeyDefault missingKeyValue = fmt.Sprintf("%s=%v", missingkey, missingKeyValueDefault) missingKeyValueExp = missingKeyValue[1:] + + testFs = flag.NewFlagSet(fsName, flag.ContinueOnError) + + flags = NewFlags(testFs) ) - testFs := flag.NewFlagSet(fsName, flag.ContinueOnError) - flags, err := parseFlags(testFs, []string{missingKeyValue}) + err := flags.Parse(testFs, []string{missingKeyValue}) assert.NoError(t, err) assert.Equal(t, Flags{ - Verbose: false, + DefaultFlags: &DefaultFlags{ + Verbose: false, + DryRun: false, + MissingKey: MissingKeyFlag(missingKeyValueDefault), + }, PreprocessFiles: true, - DryRun: false, ConfigPath: configPath, AWD: dot, ReadStdin: false, - MissingKey: MissingKeyFlag(missingKeyValueDefault)}, - flags) + }, + *flags) assert.Equal(t, missingKeyValueExp, flags.MissingKey.String()) }) t.Run("error_with_set_unexpected", func(t *testing.T) { var ( missingKeyValueDefault = "xxx_unexpected_xxx" missingKeyValue = fmt.Sprintf("%s=%v", missingkey, missingKeyValueDefault) + + testFs = flag.NewFlagSet(fsName, flag.ContinueOnError) + + flags = NewFlags(testFs) ) - testFs := flag.NewFlagSet(fsName, flag.ContinueOnError) testFs.SetOutput(MockWriter{ assertWriteFn: func(p []byte) { assert.NotEmpty(t, p) }, }) - _, err := parseFlags(testFs, []string{missingKeyValue}) + err := flags.Parse(testFs, []string{missingKeyValue}) assert.Error(t, err) }) }) diff --git a/main.go b/main.go index 7cc164a..4d2eb03 100644 --- a/main.go +++ b/main.go @@ -2,13 +2,13 @@ package main import ( "fmt" - "github.com/go-resty/resty/v2" - "github.com/kozmod/progen/internal/exec" - "github.com/kozmod/progen/internal/factory" "log" "os" "time" + "github.com/go-resty/resty/v2" + "github.com/kozmod/progen/internal/exec" + "github.com/kozmod/progen/internal/factory" "golang.org/x/xerrors" "github.com/kozmod/progen/internal" @@ -140,7 +140,7 @@ func main() { ), factory.NewExecutorBuilderFactory( conf.FsActions(), - factory.NewFsExecFactory( + factory.NewFsModifyExecFactory( templateData, templateOptions, ).Create, diff --git a/pkg/core/engin.go b/pkg/core/engin.go index a7506ab..f45deb6 100644 --- a/pkg/core/engin.go +++ b/pkg/core/engin.go @@ -1,6 +1,9 @@ package core import ( + "flag" + "log" + "os" "sync" "golang.org/x/xerrors" @@ -8,21 +11,25 @@ import ( "github.com/kozmod/progen/internal/entity" "github.com/kozmod/progen/internal/exec" "github.com/kozmod/progen/internal/factory" - "github.com/kozmod/progen/internal/flag" + engineFlags "github.com/kozmod/progen/internal/flag" ) type Engin struct { - mx sync.RWMutex - files []entity.Action[[]entity.UndefinedFile] - cmd []entity.Action[[]entity.Command] - dirs []entity.Action[[]string] - fs []entity.Action[[]string] - rm []entity.Action[[]string] + mx sync.RWMutex + files []entity.Action[[]entity.UndefinedFile] + cmd []entity.Action[[]entity.Command] + dirs []entity.Action[[]string] + fsModify []entity.Action[[]string] + fsSave []entity.Action[[]entity.TargetFs] + rm []entity.Action[[]string] logger entity.Logger + + flags engineFlags.DefaultFlags } -func (e *Engin) AddActions(actions ...Action) *Engin { +// AddActions adds action to the [Engin]. +func (e *Engin) AddActions(actions ...action) *Engin { e.mx.Lock() defer e.mx.Unlock() for _, action := range actions { @@ -31,11 +38,20 @@ func (e *Engin) AddActions(actions ...Action) *Engin { return e } +// Run all actions. func (e *Engin) Run() error { var ( - flags = flag.ParseDefault() + args = os.Args + flagSet = flag.NewFlagSet(args[0], flag.ExitOnError) + ) + err := e.flags.Parse(flagSet, args) + if err != nil { + log.Printf("parse flags failed: %v", err) + return err + } - logFatalSuffixFn = entity.NewAppendVPlusOrV(flags.PrintErrorStackTrace) + var ( + logFatalSuffixFn = entity.NewAppendVPlusOrV(e.flags.PrintErrorStackTrace) actionFilter factory.DummyActionFilter ) @@ -44,16 +60,15 @@ func (e *Engin) Run() error { var logger entity.Logger if e.logger == nil { - l, err := factory.NewLogger(flags.Verbose) + logger, err = factory.NewLogger(e.flags.Verbose) if err != nil { return xerrors.Errorf("failed to initialize logger: %w", err) } - logger = l } procChain, err := factory.NewExecutorChainFactory( logger, - flags.DryRun, + e.flags.DryRun, func(executors []entity.Executor) entity.Executor { return exec.NewChain(executors) }, @@ -63,10 +78,18 @@ func (e *Engin) Run() error { actionFilter, ), factory.NewExecutorBuilderFactory( - e.fs, - factory.NewFsExecFactory( - flags.TemplateVars.Vars, - []string{flags.MissingKey.String()}, + e.fsModify, + factory.NewFsModifyExecFactory( + e.flags.TemplateVars.Vars, + []string{e.flags.MissingKey.String()}, + ).Create, + actionFilter, + ), + factory.NewExecutorBuilderFactory( + e.fsSave, + factory.NewFsSaveExecFactory( + e.flags.TemplateVars.Vars, + []string{e.flags.MissingKey.String()}, ).Create, actionFilter, ), @@ -78,8 +101,8 @@ func (e *Engin) Run() error { factory.NewExecutorBuilderFactory( e.files, factory.NewFileExecutorFactory( - flags.TemplateVars.Vars, - []string{flags.MissingKey.String()}, + e.flags.TemplateVars.Vars, + []string{e.flags.MissingKey.String()}, ).Create, actionFilter, ), diff --git a/pkg/core/entity.go b/pkg/core/entity.go index e4999bd..25f75c5 100644 --- a/pkg/core/entity.go +++ b/pkg/core/entity.go @@ -5,7 +5,8 @@ import ( ) type ( - Action interface { + // action is the common interface to adding actions to the engine. + action interface { add(engin *Engin) } @@ -14,7 +15,8 @@ type ( Data []byte } - Cmd entity.Command + Cmd entity.Command + TargetFs entity.TargetFs ) type Files entity.Action[[]File] @@ -121,11 +123,11 @@ func RmAction(name string, rm ...string) Rm { } } -type Fs entity.Action[[]string] +type FsModify entity.Action[[]string] -func (f Fs) add(e *Engin) { +func (f FsModify) add(e *Engin) { if e != nil { - e.fs = append(e.fs, entity.Action[[]string]{ + e.fsModify = append(e.fsModify, entity.Action[[]string]{ Name: f.Name, Val: f.Val, Priority: f.Priority, @@ -133,13 +135,43 @@ func (f Fs) add(e *Engin) { } } -func (f Fs) WithPriority(priority int) Fs { +func (f FsModify) WithPriority(priority int) FsModify { f.Priority = priority return f } -func FsAction(name string, fs ...string) Fs { - return Fs{ +func FsModifyAction(name string, fs ...string) FsModify { + return FsModify{ + Name: name, + Val: fs, + } +} + +type FsSave entity.Action[[]TargetFs] + +func (f FsSave) add(e *Engin) { + if e != nil { + fs := convert(f.Val, func(s TargetFs) entity.TargetFs { + return entity.TargetFs{ + TargetDir: s.TargetDir, + Fs: s.Fs, + } + }) + e.fsSave = append(e.fsSave, entity.Action[[]entity.TargetFs]{ + Name: f.Name, + Val: fs, + Priority: f.Priority, + }) + } +} + +func (f FsSave) WithPriority(priority int) FsSave { + f.Priority = priority + return f +} + +func FsSaveAction(name string, fs ...TargetFs) FsSave { + return FsSave{ Name: name, Val: fs, }