From fbe54c54d2960883d2b3962f7ca6e4ee01f8317d Mon Sep 17 00:00:00 2001 From: kozmod Date: Fri, 9 Feb 2024 22:21:32 +0300 Subject: [PATCH] [#91] add `rm` action --- internal/config/config.go | 9 ++- internal/config/unmarshaler.go | 2 + internal/entity/entity.go | 5 ++ internal/exec/rm.go | 84 +++++++++++++++++++++++ internal/exec/rm_test.go | 117 +++++++++++++++++++++++++++++++++ internal/factory/chain_exec.go | 18 +++++ internal/factory/rm_exec.go | 21 ++++++ 7 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 internal/exec/rm.go create mode 100644 internal/exec/rm_test.go create mode 100644 internal/factory/rm_exec.go diff --git a/internal/config/config.go b/internal/config/config.go index d9b77d0..8fbea72 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -12,6 +12,7 @@ import ( const ( TagDirs = "dirs" + TagRm = "rm" TagFiles = "files" TagCmd = "cmd" TagFS = "fs" @@ -21,6 +22,7 @@ const ( type Config struct { Settings Settings `yaml:"settings"` Dirs []Section[[]string] `yaml:"dirs,flow"` + Rm []Section[[]string] `yaml:"rm,flow"` Files []Section[[]File] `yaml:"files,flow"` Cmd []Section[[]Command] `yaml:"cmd,flow"` FS []Section[[]string] `yaml:"fs,flow"` @@ -211,13 +213,14 @@ func validateConfigSections(conf Config) error { var ( files = len(conf.Files) dirs = len(conf.Dirs) + rm = len(conf.Rm) cmd = len(conf.Cmd) fs = len(conf.FS) ) - if files == 0 && dirs == 0 && cmd == 0 && fs == 0 { + if files == 0 && dirs == 0 && rm == 0 && cmd == 0 && fs == 0 { return xerrors.Errorf( - "config not contains executable actions [dirs: %d, files: %d, cms: %d, fs: %d]", - dirs, files, cmd, fs, + "config not contains executable actions [dirs: %d, rm: %d, files: %d, cms: %d, fs: %d]", + dirs, rm, files, cmd, fs, ) } return nil diff --git a/internal/config/unmarshaler.go b/internal/config/unmarshaler.go index 201d992..d996f1f 100644 --- a/internal/config/unmarshaler.go +++ b/internal/config/unmarshaler.go @@ -39,6 +39,8 @@ func (u *YamlUnmarshaler) Unmarshal(rawConfig []byte) (Config, error) { conf.Settings = settings case strings.Index(tag, TagDirs) == 0: conf.Dirs, err = decode(conf.Dirs, node, tag) + case strings.Index(tag, TagRm) == 0: + conf.Rm, err = decode(conf.Rm, node, tag) case strings.Index(tag, TagFiles) == 0: conf.Files, err = decode(conf.Files, node, tag) case strings.Index(tag, TagCmd) == 0: diff --git a/internal/entity/entity.go b/internal/entity/entity.go index f152f87..e3dc746 100644 --- a/internal/entity/entity.go +++ b/internal/entity/entity.go @@ -41,6 +41,7 @@ const ( EqualsSign = "=" LessThan = "<" Tilda = "~" + Astrix = "*" NewLine = "\n" LogSliceSep = Comma + Space @@ -59,6 +60,10 @@ type ( Apply(path string) (string, error) } + RmStrategy interface { + Apply(path string) error + } + TemplateProc interface { Process(name, text string) (string, error) } diff --git a/internal/exec/rm.go b/internal/exec/rm.go new file mode 100644 index 0000000..131ded8 --- /dev/null +++ b/internal/exec/rm.go @@ -0,0 +1,84 @@ +package exec + +import ( + "os" + "path/filepath" + "strings" + + "golang.org/x/xerrors" + + "github.com/kozmod/progen/internal/entity" +) + +type RmAllExecutor struct { + paths []string + strategies []entity.RmStrategy +} + +func NewRmAllExecutor(paths []string, strategies []entity.RmStrategy) *RmAllExecutor { + return &RmAllExecutor{ + paths: paths, + strategies: strategies, + } +} + +func (p *RmAllExecutor) Exec() error { + for _, path := range p.paths { + for _, strategy := range p.strategies { + err := strategy.Apply(path) + if err != nil { + return xerrors.Errorf("execute rm: process rm [%s]: %w", path, err) + } + } + } + return nil +} + +type RmAllStrategy struct { + logger entity.Logger +} + +func NewRmAllStrategy(logger entity.Logger) *RmAllStrategy { + return &RmAllStrategy{ + logger: logger, + } +} + +func (p *RmAllStrategy) Apply(path string) error { + astrixIndex := strings.Index(path, entity.Astrix) + if astrixIndex == len(path)-1 { + contents, err := filepath.Glob(path) + if err != nil { + return xerrors.Errorf("rm [%s]: get names of the all files: %w", path, err) + } + for _, item := range contents { + err = os.RemoveAll(item) + if err != nil { + return xerrors.Errorf("rm content [%s]: %w", item, err) + } + } + return nil + } + + err := os.RemoveAll(path) + if err != nil { + return xerrors.Errorf("rm [%s]: %w", path, err) + } + p.logger.Infof("rm: %s", path) + return nil +} + +type DryRunRmAllStrategy struct { + logger entity.Logger +} + +func NewDryRmAllStrategy(logger entity.Logger) *DryRunRmAllStrategy { + return &DryRunRmAllStrategy{ + logger: logger, + } +} + +func (p *DryRunRmAllStrategy) Apply(path string) error { + p.logger.Infof("rm: %s", path) + return nil +} diff --git a/internal/exec/rm_test.go b/internal/exec/rm_test.go new file mode 100644 index 0000000..8b9fed0 --- /dev/null +++ b/internal/exec/rm_test.go @@ -0,0 +1,117 @@ +package exec + +import ( + "fmt" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "path/filepath" + + "github.com/kozmod/progen/internal/entity" +) + +func Test_RmAllStrategy(t *testing.T) { + SkipSLowTest(t) + + const ( + someDir = "some_dir" + someFile = "file_name.txt" + ) + + t.Run("rm_dir", func(t *testing.T) { + WithTempDir(t, func(tmpDir string) { + var ( + a = assert.New(t) + path = filepath.Join(tmpDir, someDir) + mockLogger = MockLogger{ + infof: func(format string, args ...any) { + assert.NotEmpty(t, format) + assert.ElementsMatch(t, []string{path}, args) + }, + } + ) + + err := os.MkdirAll(path, os.ModePerm) + a.NoError(err) + a.DirExists(path) + + err = NewRmAllStrategy(mockLogger).Apply(path) + a.NoError(err) + a.NoDirExists(path) + }) + }) + + t.Run("rm_file", func(t *testing.T) { + WithTempDir(t, func(tmpDir string) { + var ( + a = assert.New(t) + dir = filepath.Join(tmpDir, someDir, someFile) + filePath = filepath.Join(dir, someFile) + mockLogger = MockLogger{ + infof: func(format string, args ...any) { + assert.NotEmpty(t, format) + assert.ElementsMatch(t, []string{filePath}, args) + }, + } + ) + + err := os.MkdirAll(dir, os.ModePerm) + a.NoError(err) + a.DirExists(dir) + + file, err := os.Create(filePath) + a.NoError(err) + a.FileExists(filePath) + a.Equal(filePath, file.Name()) + + err = NewRmAllStrategy(mockLogger).Apply(filePath) + a.NoError(err) + a.NoFileExists(filePath) + a.DirExists(dir) + }) + }) + t.Run("rm_all_files", func(t *testing.T) { + WithTempDir(t, func(tmpDir string) { + const ( + filesQuantity = 5 + ) + var ( + a = assert.New(t) + dir = filepath.Join(tmpDir, someDir) + rmPath = filepath.Join(dir, entity.Astrix) + filesPath = make([]string, 0, filesQuantity) + mockLogger = MockLogger{ + infof: func(format string, args ...any) { + assert.NotEmpty(t, format) + }, + } + ) + + err := os.MkdirAll(dir, os.ModePerm) + a.NoError(err) + a.DirExists(dir) + + for i := 0; i < filesQuantity; i++ { + var ( + fileExt = filepath.Ext(someFile) + fileName = fmt.Sprintf("%s_%d%s", strings.TrimSuffix(someFile, fileExt), i, fileExt) + filePath = filepath.Join(tmpDir, someDir, fileName) + ) + filesPath = append(filesPath, filePath) + _, err = os.Create(filePath) + a.NoError(err) + a.FileExists(filePath) + } + + err = NewRmAllStrategy(mockLogger).Apply(rmPath) + a.NoError(err) + a.DirExists(dir) + for _, path := range filesPath { + a.NoFileExists(path) + } + }) + }) + +} diff --git a/internal/factory/chain_exec.go b/internal/factory/chain_exec.go index ca90f6b..52f4fb2 100644 --- a/internal/factory/chain_exec.go +++ b/internal/factory/chain_exec.go @@ -47,6 +47,24 @@ func NewExecutorChain( }, }) } + for _, rm := range conf.Rm { + var ( + r = rm + action = r.Tag + ) + + if !actionFilter.MatchString(action) { + continue + } + builders = append(builders, + ExecutorBuilder{ + action: action, + line: r.Line, + procFn: func() (entity.Executor, error) { + return NewRmExecutor(r.Val, logger, dryRun) + }, + }) + } var preprocessors []entity.Preprocessor for _, files := range conf.Files { diff --git a/internal/factory/rm_exec.go b/internal/factory/rm_exec.go new file mode 100644 index 0000000..0d02a21 --- /dev/null +++ b/internal/factory/rm_exec.go @@ -0,0 +1,21 @@ +package factory + +import ( + "github.com/kozmod/progen/internal/entity" + "github.com/kozmod/progen/internal/exec" +) + +func NewRmExecutor(paths []string, logger entity.Logger, dryRun bool) (entity.Executor, error) { + if len(paths) == 0 { + logger.Infof("rm executor: `rm` section is empty") + return nil, nil + } + + pathsSet := entity.Unique(paths) + + if dryRun { + return exec.NewRmAllExecutor(pathsSet, []entity.RmStrategy{exec.NewDryRmAllStrategy(logger)}), nil + } + + return exec.NewRmAllExecutor(pathsSet, []entity.RmStrategy{exec.NewRmAllStrategy(logger)}), nil +}