From cd0391a53a47fb36bd46e51ee6233aae609c5f0b Mon Sep 17 00:00:00 2001 From: kozmod Date: Wed, 10 Jul 2024 21:17:49 +0300 Subject: [PATCH] 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() +}