From 6dc66c88d0dd99011c62a8f2c873c27ae2506ce6 Mon Sep 17 00:00:00 2001 From: kozmod Date: Thu, 19 Jan 2023 09:03:50 +0300 Subject: [PATCH 1/2] fix Readme.md --- Readme.md | 28 ++++++++++++++-------------- main.go | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Readme.md b/Readme.md index e54ffe4..2b388ab 100644 --- a/Readme.md +++ b/Readme.md @@ -5,7 +5,7 @@ ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/kozmod/progen) Simple projects generator. - +___ ### Installation ```console @@ -17,12 +17,12 @@ go install github.com/kozmod/progen@latest ```console go build -o progen . ``` - +___ ### About -`progen` use `yml` config file to generate directories, files and execute commands - -#### Allowed config file's keys +`progen` use `yml` config file to generate directories, files and execute commands ([actions](#Actions)) +___ +### Args | Name | Type | Description | |:-----|:------:|:------------------------------------------------------------------:| @@ -30,8 +30,8 @@ go build -o progen . | v | bool | verbose output | | dr | bool | `dry run` mode
(to verbose output should be combine with`-v`) | | help | bool | show flags | - -#### Action config file's tags +___ +### Actions | Key | Type | Optional | Description | |:------------------|:-----------------:|:--------:|:---------------------------------------------------------------------------------:| @@ -58,8 +58,8 @@ go build -o progen . ✳️ required one of for parent block **❗Note:** all action execute on declaration order - -#### Example +___ +### Example ```yaml ## preprocessing of "raw" config use `text/template` of golang's stdlib @@ -136,22 +136,22 @@ files: cmd: - curl -H PRIVATE-TOKEN:{{.vars.TOKEN}} {{.vars.REPO_1}}/.editorconfig/raw?ref=master -o .editorconfig ``` +___ +### Generate -#### Generate project structure from configuration file - -use configuration file with default name (`progen.yaml`) +`progen` use `progen.yaml` as default configuration file ```console progen -v ``` -or define custom config location using `-f` +`-f` flag set custom configuration file ```console progen -v -f conf.yml ``` -generated project structure +generated files and directories ```console . diff --git a/main.go b/main.go index f0e20a2..e0ef94e 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,7 @@ var ( flagConfigPath = flag.String( "f", entity.Empty, - fmt.Sprintf("configuration file path (if not set, default configuration is used: %s)", defaultConfigFilePath)) + fmt.Sprintf("configuration file path (default file: %s)", defaultConfigFilePath)) flagVerbose = flag.Bool( "v", false, From ad9fd483b0bc7ac131cb8104a5a918e1d655ae18 Mon Sep 17 00:00:00 2001 From: kozmod Date: Mon, 16 Jan 2023 08:45:56 +0300 Subject: [PATCH 2/2] [3] add files/dirs permissions options to config file --- internal/config/config.go | 53 +++++++++++++++++++++++++++++++++- internal/entity/entity.go | 22 ++++++++++---- internal/factory/file_proc.go | 23 ++++++++++----- internal/factory/mkdir_proc.go | 29 ++++++++++++++----- internal/proc/file.go | 36 +++++++++++------------ internal/proc/mkdir.go | 52 ++++++++++++++++++++++++--------- main.go | 3 +- 7 files changed, 164 insertions(+), 54 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 51f3361..21614f5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,7 +3,9 @@ package config import ( "fmt" "net/url" + "os" "reflect" + "strconv" "strings" "gopkg.in/yaml.v3" @@ -19,9 +21,9 @@ const ( type Config struct { HTTP *HTTPClient `yaml:"http"` - Dirs []string `yaml:"dirs,flow"` Files []File `yaml:"files,flow"` Cmd []string `yaml:"cmd,flow"` + Dirs []Dir `yaml:"dirs,flow"` } type HTTPClient struct { @@ -30,12 +32,43 @@ type HTTPClient struct { Debug bool `yaml:"debug"` } +type Dir struct { + Path string `yaml:"path"` + Perm *Perm `yaml:"perm"` +} + +func (dir *Dir) UnmarshalYAML(unmarshal func(any) error) error { + var raw string + err := unmarshal(&raw) + if err == nil { + *dir = Dir{ + Perm: nil, + Path: raw, + } + return nil + } + + if _, ok := err.(*yaml.TypeError); !ok { + return fmt.Errorf("unmarshal dir: string: %w", err) + } + + type alias Dir + var val alias + err = unmarshal(&val) + if err != nil { + return fmt.Errorf("unmarshal dir: struct: %w", err) + } + *dir = Dir(val) + return nil +} + type File struct { Path string `yaml:"path"` Data *string `yaml:"data"` Get *Get `yaml:"get"` Local *string `yaml:"local"` ExecTmplSkip bool `yaml:"tmpl_skip"` + Perm *Perm `yaml:"perm"` } type Get struct { @@ -60,6 +93,24 @@ func (addr *AddrURL) UnmarshalYAML(unmarshal func(any) error) error { return nil } +type Perm struct { + os.FileMode `yaml:",inline"` +} + +func (perm *Perm) UnmarshalYAML(unmarshal func(any) error) error { + var raw string + if err := unmarshal(&raw); err != nil { + return fmt.Errorf("unmarshal perm: %w", err) + } + fm, err := strconv.ParseUint(raw, 0, 32) + if err != nil { + return fmt.Errorf("unmarshal perm unit: %w", err) + } + + *perm = Perm{FileMode: os.FileMode(fm)} + return nil +} + func UnmarshalYamlConfig(in []byte) (Config, error) { var conf Config err := yaml.Unmarshal(in, &conf) diff --git a/internal/entity/entity.go b/internal/entity/entity.go index 05b3cde..4b849f0 100644 --- a/internal/entity/entity.go +++ b/internal/entity/entity.go @@ -1,5 +1,7 @@ package entity +import "os" + const ( Space = " " Empty = "" @@ -17,23 +19,31 @@ type Logger interface { Debugf(format string, any ...any) } +type Dir struct { + Path string + Perm os.FileMode +} + +type File struct { + Path string + Name string + Perm os.FileMode +} + type DataFile struct { - Path string - Name string + File Data []byte ExecTmpl bool } type LocalFile struct { - Path string - Name string + File LocalPath string ExecTmpl bool } type RemoteFile struct { - Path string - Name string + File URL string Headers map[string]string ExecTmpl bool diff --git a/internal/factory/file_proc.go b/internal/factory/file_proc.go index 4d33e09..75c8a33 100644 --- a/internal/factory/file_proc.go +++ b/internal/factory/file_proc.go @@ -2,6 +2,7 @@ package factory import ( "fmt" + "os" "path/filepath" "github.com/go-resty/resty/v2" @@ -26,25 +27,31 @@ func NewFileProc( var client *resty.Client for _, f := range conf.Files { var ( - name = filepath.Base(f.Path) - path = filepath.Dir(f.Path) + file = entity.File{ + Name: filepath.Base(f.Path), + Path: filepath.Dir(f.Path), + Perm: os.ModePerm, + } template = !f.ExecTmplSkip ) + if f.Perm != nil { + file.Perm = f.Perm.FileMode + } + var producer entity.FileProducer switch { case f.Data != nil: file := entity.DataFile{ - Name: name, - Path: path, + File: file, Data: []byte(*f.Data), ExecTmpl: template, } producer = proc.NewStoredProducer(file) + case f.Get != nil: file := entity.RemoteFile{ - Name: name, - Path: path, + File: file, URL: f.Get.URL, Headers: f.Get.Headers, ExecTmpl: template, @@ -55,10 +62,10 @@ func NewFileProc( } producer = proc.NewRemoteProducer(file, client) + case f.Local != nil: file := entity.LocalFile{ - Name: name, - Path: path, + File: file, LocalPath: *f.Local, ExecTmpl: template, } diff --git a/internal/factory/mkdir_proc.go b/internal/factory/mkdir_proc.go index 60c5748..d3072a7 100644 --- a/internal/factory/mkdir_proc.go +++ b/internal/factory/mkdir_proc.go @@ -1,6 +1,8 @@ package factory import ( + "os" + "github.com/kozmod/progen/internal/config" "github.com/kozmod/progen/internal/entity" "github.com/kozmod/progen/internal/proc" @@ -11,25 +13,38 @@ func NewMkdirProc(conf config.Config, logger entity.Logger, dryRun bool) (proc.P return nil, nil } - dirSet := uniqueVal(conf.Dirs) + dirSet := uniqueVal[config.Dir, string](conf.Dirs, func(dir config.Dir) string { + return dir.Path + }) + + dirs := make([]entity.Dir, 0, len(dirSet)) + for _, dir := range dirSet { + perm := os.ModePerm + if dir.Perm != nil { + perm = dir.Perm.FileMode + } + + dirs = append(dirs, entity.Dir{Path: dir.Path, Perm: perm}) + } if dryRun { - return proc.NewDryRunMkdirAllProc(dirSet, logger), nil + return proc.NewDryRunMkdirAllProc(dirs, logger), nil } - return proc.NewMkdirAllProc(dirSet, logger), nil + return proc.NewMkdirAllProc(dirs, logger), nil } -func uniqueVal[T comparable](in []T) []T { - set := make(map[T]struct{}, len(in)) +func uniqueVal[T any, R comparable](in []T, keyFn func(T) R) []T { + set := make(map[R]struct{}, len(in)) out := make([]T, 0, len(in)) for _, val := range in { - _, ok := set[val] + key := keyFn(val) + _, ok := set[key] if ok { continue } out = append(out, val) - set[val] = struct{}{} + set[key] = struct{}{} } return out } diff --git a/internal/proc/file.go b/internal/proc/file.go index 4ef1a29..37c44f2 100644 --- a/internal/proc/file.go +++ b/internal/proc/file.go @@ -12,18 +12,20 @@ import ( ) type FileProc struct { - fileMode os.FileMode - producers []entity.FileProducer - logger entity.Logger - executor templateExecutor + defaultPerm os.FileMode + producers []entity.FileProducer + logger entity.Logger + templateData map[string]any + executor templateExecutor } func NewFileProc(producers []entity.FileProducer, templateData map[string]any, logger entity.Logger) *FileProc { return &FileProc{ - fileMode: os.ModePerm, - producers: producers, - executor: templateExecutor{templateData: templateData}, - logger: logger, + defaultPerm: os.ModePerm, + producers: producers, + templateData: templateData, + executor: templateExecutor{templateData: templateData}, + logger: logger, } } @@ -36,7 +38,7 @@ func (p *FileProc) Exec() error { fileDir := file.Path if _, err := os.Stat(fileDir); os.IsNotExist(err) { - err = os.MkdirAll(fileDir, p.fileMode) + err = os.MkdirAll(fileDir, p.defaultPerm) if err != nil { return fmt.Errorf("process file: create file dir [%s]: %w", fileDir, err) } @@ -54,11 +56,11 @@ func (p *FileProc) Exec() error { } } - err = os.WriteFile(filePath, file.Data, p.fileMode) + err = os.WriteFile(filePath, file.Data, file.Perm) if err != nil { return fmt.Errorf("process file: create file [%s]: %w", file.Name, err) } - p.logger.Infof("file created (template: %v): %s", file.ExecTmpl, filePath) + p.logger.Infof("file created [template: %v, perm: %v]: %s", file.ExecTmpl, file.Perm, filePath) } return nil } @@ -87,8 +89,8 @@ func (p *DryRunFileProc) Exec() error { } fileDir := file.Path - if _, err := os.Stat(fileDir); os.IsNotExist(err) { - p.logger.Infof("process file: create dir [%s] to store file [%s]", fileDir, file.Name) + if _, err = os.Stat(fileDir); os.IsNotExist(err) { + p.logger.Infof("process file: create dir to store file [%s]: %s", file.Name, fileDir) } filePath := path.Join(file.Path, file.Name) @@ -100,7 +102,7 @@ func (p *DryRunFileProc) Exec() error { } file.Data = data } - p.logger.Infof("file created [template: %v, path: %s]:\n%s", file.ExecTmpl, filePath, string(file.Data)) + p.logger.Infof("file created [template: %v, perm: %v]: %s\n%s", file.ExecTmpl, file.Perm, filePath, string(file.Data)) } return nil } @@ -135,8 +137,7 @@ func (p *LocalProducer) Get() (*entity.DataFile, error) { return nil, fmt.Errorf("read local: %w", err) } return &entity.DataFile{ - Name: p.file.Name, - Path: p.file.Path, + File: p.file.File, Data: data, ExecTmpl: p.file.ExecTmpl, }, nil @@ -169,8 +170,7 @@ func (p *RemoteProducer) Get() (*entity.DataFile, error) { statusCode := rs.StatusCode() if statusCode >= http.StatusOK && statusCode < http.StatusMultipleChoices { return &entity.DataFile{ - Name: p.file.Name, - Path: p.file.Path, + File: p.file.File, Data: rs.Body(), ExecTmpl: p.file.ExecTmpl, }, nil diff --git a/internal/proc/mkdir.go b/internal/proc/mkdir.go index e5c18ff..83bef2c 100644 --- a/internal/proc/mkdir.go +++ b/internal/proc/mkdir.go @@ -1,44 +1,70 @@ package proc import ( + "container/list" "fmt" + "io/fs" "os" + "path/filepath" "github.com/kozmod/progen/internal/entity" ) type MkdirAllProc struct { - fileMode os.FileMode - dirs []string - logger entity.Logger + defaultPerm os.FileMode + dirs []entity.Dir + logger entity.Logger } -func NewMkdirAllProc(dirs []string, logger entity.Logger) *MkdirAllProc { +func NewMkdirAllProc(dirs []entity.Dir, logger entity.Logger) *MkdirAllProc { return &MkdirAllProc{ - fileMode: os.ModePerm, - dirs: dirs, - logger: logger, + defaultPerm: os.ModePerm, + dirs: dirs, + logger: logger, } } func (p *MkdirAllProc) Exec() error { for _, dir := range p.dirs { - err := os.MkdirAll(dir, p.fileMode) + err := os.MkdirAll(dir.Path, p.defaultPerm) if err != nil { - return fmt.Errorf("create dir [%s]: %w", dir, err) + return fmt.Errorf("create dir [%s:%v]: %w", dir.Path, dir.Perm, err) } - p.logger.Infof("dir created: %s", dir) + + if p.defaultPerm != dir.Perm { + l := list.New() + err = filepath.WalkDir(dir.Path, func(path string, d fs.DirEntry, err error) error { + if d.IsDir() { + l.PushFront(path) + } + return err + }) + if err != nil { + return fmt.Errorf("walk dir [%s]: %w", dir.Path, err) + } + + for e := l.Front(); e != nil; e = e.Next() { + path := e.Value.(string) + err = os.Chmod(path, dir.Perm) + if err != nil { + return fmt.Errorf("chmod dir [path:%s, perm:%v]: %w", path, dir.Perm, err) + } + } + + } + + p.logger.Infof("dir created [perm: %v]: %s ", dir.Perm, dir.Path) } return nil } type DryRunMkdirAllProc struct { fileMode os.FileMode - dirs []string + dirs []entity.Dir logger entity.Logger } -func NewDryRunMkdirAllProc(dirs []string, logger entity.Logger) *DryRunMkdirAllProc { +func NewDryRunMkdirAllProc(dirs []entity.Dir, logger entity.Logger) *DryRunMkdirAllProc { return &DryRunMkdirAllProc{ fileMode: os.ModePerm, dirs: dirs, @@ -48,7 +74,7 @@ func NewDryRunMkdirAllProc(dirs []string, logger entity.Logger) *DryRunMkdirAllP func (p *DryRunMkdirAllProc) Exec() error { for _, dir := range p.dirs { - p.logger.Infof("dir created: %s", dir) + p.logger.Infof("dir created [perm: %v]: %s ", dir.Perm, dir.Path) } return nil } diff --git a/main.go b/main.go index e0ef94e..329c082 100644 --- a/main.go +++ b/main.go @@ -3,11 +3,12 @@ package main import ( "flag" "fmt" - "golang.org/x/sync/errgroup" "log" "os" "time" + "golang.org/x/sync/errgroup" + "github.com/kozmod/progen/internal/config" "github.com/kozmod/progen/internal/entity" "github.com/kozmod/progen/internal/factory"