- Изменен пакет blueprint
- Пример шаблона начал приводить к нашему дефолтному коду

Added:
- Добавлены модули: логер (zap), окружение
This commit is contained in:
Danil Solovyov 2023-12-24 12:38:10 +05:00
parent 49466a901f
commit ee3accd198
13 changed files with 397 additions and 62 deletions

@ -19,12 +19,12 @@ var (
// BlueprintFile - описание модели файла.
type BlueprintFile struct {
ProjectName string `yaml:"ProjectName"`
Description string `yaml:"Description,omitempty"`
Author string `yaml:"Author,omitempty"`
ModulesSettings map[string]any `yaml:"Modules"`
Template TemplateSettings `yaml:"Template,omitempty"`
Vars map[string]any `yaml:"Vars,omitempty"`
ProjectName string `yaml:"ProjectName"`
Description string `yaml:"Description,omitempty"`
Author string `yaml:"Author,omitempty"`
ModulesSettings map[string]any `yaml:"Modules"`
Template Settings `yaml:"Template,omitempty"`
Vars map[string]any `yaml:"Vars,omitempty"`
}
func readFile(blueprintFilePath string) (blueprint *BlueprintFile, err error) {

@ -3,10 +3,10 @@ package blueprint
import (
"fmt"
"path/filepath"
)
type Deps struct {
}
"penahub.gitlab.yandexcloud.net/pena-services/bluepint/blueprint/modules/env"
"penahub.gitlab.yandexcloud.net/pena-services/bluepint/blueprint/modules/logger"
)
type BlueprintCreator struct {
file *BlueprintFile
@ -17,7 +17,6 @@ type BlueprintCreator struct {
type BlueprintModule interface {
Name() string
SetSettings(settings any) error
GetFunctions() any
Execute() error
}
@ -28,13 +27,19 @@ func NewBlueprintCreator(blueprintPath string) (*BlueprintCreator, error) {
return nil, err
}
blueprintModules := []BlueprintModule{}
blueprintModules := []BlueprintModule{
logger.NewLoggerModule(),
env.NewEnvModule(),
}
templateModules := make(map[string]BlueprintModule, 0)
for _, module := range blueprintModules {
if file.ModulesSettings[module.Name()] != nil {
if err = module.SetSettings(file.ModulesSettings[module.Name()]); err != nil {
return nil, fmt.Errorf("blueprintCreator: cannot set settings for module %v. %v", module.Name(), err)
}
templateModules[module.Name()] = module
}
}
@ -44,11 +49,11 @@ func NewBlueprintCreator(blueprintPath string) (*BlueprintCreator, error) {
return nil, fmt.Errorf("blueprintCreator: cannot get absolute path for execPath. %v", err)
}
template := NewTemplate(TemplateSettings{
template := NewTemplate(Settings{
ExecPath: absExecPath,
TemplatePath: file.Template.TemplatePath,
Vars: file.Template.Vars,
Functions: nil,
Modules: templateModules,
})
template.SetVar("ProjectName", file.ProjectName)
@ -63,15 +68,15 @@ func NewBlueprintCreator(blueprintPath string) (*BlueprintCreator, error) {
}
func (r *BlueprintCreator) Create() error {
if err := r.template.Execute(); err != nil {
return fmt.Errorf("template execute. %v", err)
}
for _, module := range r.modules {
if err := module.Execute(); err != nil {
return fmt.Errorf("module execute (%v). %v", module.Name(), err)
}
}
if err := r.template.Execute(); err != nil {
return fmt.Errorf("template execute. %v", err)
}
return nil
}

90
blueprint/modules/env/env.go vendored Normal file

@ -0,0 +1,90 @@
package env
import (
"fmt"
"os/exec"
"strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"gopkg.in/yaml.v3"
)
type EnvModule struct {
settings Settings
}
type Settings struct {
Vars []Var `yaml:"vars"`
}
type Var struct {
Name string `yaml:"name"`
Type string `yaml:"type"`
Required bool `yaml:"required,omitempty"`
Default string `yaml:"default"`
}
func (r Var) code() string {
var fieldName, req, def string
if r.Required {
req = ",required"
}
if r.Default != "" {
def = fmt.Sprintf(` envDefault:"%v"`, r.Default)
}
caser := cases.Title(language.English)
for _, item := range strings.Split(r.Name, "_") {
fieldName += caser.String(item)
}
return fmt.Sprintf(" %v %v `env:\"%v%v\"%v`", fieldName, r.Type, r.Name, req, def)
}
func NewEnvModule() *EnvModule {
return &EnvModule{}
}
func (r *EnvModule) Name() string {
return "env"
}
func (r *EnvModule) SetSettings(settings any) error {
out, err := yaml.Marshal(settings)
if err != nil {
return err
}
return yaml.Unmarshal(out, &r.settings)
}
func (r *EnvModule) Execute() error {
cmd := exec.Command("go", "get", r.Import())
return cmd.Run()
}
func (r *EnvModule) Import() string {
return `"github.com/caarlos0/env/v8"`
}
func (r *EnvModule) Declaration(v string) string {
return fmt.Sprintf(`err = env.Parse(%v)
if err != nil {
panic(err)
}`, v)
}
func (r *EnvModule) Struct() string {
var varsString string
for _, v := range r.settings.Vars {
varsString += fmt.Sprintf("%v\r\n", v.code())
}
return fmt.Sprintf("type Config struct {\r\n%v}", varsString)
}

29
blueprint/modules/env/env_test.go vendored Normal file

@ -0,0 +1,29 @@
package env
import "testing"
func TestVar_code(t *testing.T) {
tests := []struct {
name string
r Var
want string
}{
{
name: "default",
r: Var{
Name: "VAR_EXAMPLE",
Type: "string",
Required: true,
Default: "default value",
},
want: " VarExample string `env:\"VAR_EXAMPLE,required\" envDefault:\"default value\"`",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.r.code(); got != tt.want {
t.Errorf("Var.code() = %v, want %v", got, tt.want)
}
})
}
}

@ -0,0 +1,95 @@
package logger
import (
"fmt"
"os/exec"
"gopkg.in/yaml.v3"
)
type LoggerModule struct {
settings Settings
logger Logger
}
type Settings struct {
Name LoggerType `yaml:"name"`
}
type LoggerType string
const (
LoggerTypeZap = "zap"
)
func (r LoggerType) Valid() bool {
return r == LoggerTypeZap
}
func NewLoggerModule() *LoggerModule {
return &LoggerModule{}
}
func (r *LoggerModule) Name() string {
return "logger"
}
func (r *LoggerModule) SetSettings(settings any) error {
out, err := yaml.Marshal(settings)
if err != nil {
return err
}
if err = yaml.Unmarshal(out, &r.settings); err != nil {
return err
}
if !r.settings.Name.Valid() {
return fmt.Errorf("invalid logger type")
}
switch r.settings.Name {
case LoggerTypeZap:
r.logger = NewZapLogger()
}
return nil
}
func (r *LoggerModule) GetFunctions() any {
return nil
}
func (r *LoggerModule) Execute() error {
cmd := exec.Command("go", "get", r.Import())
return cmd.Run()
}
type Logger interface {
Import() string
ImportCore() string
Declaration() string
Type() string
Message(messageType string, message string, fields ...any) string
}
func (r *LoggerModule) Import() string {
return r.logger.Import()
}
func (r *LoggerModule) ImportCore() string {
return r.logger.ImportCore()
}
func (r *LoggerModule) Init() string {
return r.logger.Declaration()
}
func (r *LoggerModule) Type() string {
return r.logger.Type()
}
func (r *LoggerModule) Message(messageType, message string, fields ...any) string {
return r.logger.Message(messageType, message, fields...)
}

@ -0,0 +1,59 @@
package logger
import (
"fmt"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
type ZapLogger struct {
}
func NewZapLogger() *ZapLogger {
return &ZapLogger{}
}
func (r *ZapLogger) Import() string {
return `"go.uber.org/zap"`
}
func (r *ZapLogger) ImportCore() string {
return `"go.uber.org/zap/zapcore"`
}
func (r *ZapLogger) Declaration() string {
return `cfgLogger := zap.NewDevelopmentConfig()
cfgLogger.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
cfgLogger.EncoderConfig.ConsoleSeparator = " "
logger, err := cfgLogger.Build()
if err != nil {
panic(err)
}`
}
func (r *ZapLogger) Type() string {
return "*zap.Logger"
}
func (r *ZapLogger) Message(messageType, message string, fields ...any) string {
var fieldsToString string
caser := cases.Title(language.English)
messageType = caser.String(messageType)
if len(fields)%2 != 0 {
return `//FIXME TEMPLATE ERROR: logger.Message need even number of fields. Example: logger.Message <messageType> <some msg> <fieldName> <fieldVal>...`
}
for i := 0; i < len(fields); i += 2 {
fieldsToString += fmt.Sprintf(`zap.Any("%v", %v)`, fields[i], fields[i+1])
if i+1 < len(fields)-1 {
fieldsToString += ", "
}
}
return fmt.Sprintf(`logger.%v("%v", %v)`, messageType, message, fieldsToString)
}

@ -0,0 +1,45 @@
package logger
import "testing"
func TestZapLogger_InfoMsg(t *testing.T) {
type args struct {
messageType string
message string
fields []any
}
tests := []struct {
name string
r *ZapLogger
args args
want string
}{
{
name: "default",
r: &ZapLogger{},
args: args{
messageType: "info",
message: `some msg`,
fields: []any{`key1`, 1, `key2`, 2},
},
want: `logger.Info("some msg", zap.Any("key1", 1), zap.Any("key2", 2))`,
},
{
name: "error need even number of fields",
r: &ZapLogger{},
args: args{
messageType: "info",
message: `some msg`,
fields: []any{`key1`},
},
want: `//FIXME TEMPLATE ERROR: logger.Message need even number of fields. Example: logger.Message <messageType> <some msg> <fieldName> <fieldVal>...`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.r.Message(tt.args.messageType, tt.args.message, tt.args.fields...); got != tt.want {
t.Errorf("ZapLogger.Message() = %v, want %v", got, tt.want)
}
})
}
}

@ -11,7 +11,6 @@ import (
"text/template"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v3"
)
const (
@ -22,27 +21,24 @@ var (
TemplateFormats = []string{"tmpl", "gotmpl"}
)
type TemplateSettings struct {
ExecPath string `yaml:"-"`
TemplatePath string `yaml:"path"`
Vars map[string]any `yaml:"vars,omitempty"`
Functions map[string]any `yaml:"-"`
type Settings struct {
ExecPath string `yaml:"-"`
TemplatePath string `yaml:"path"`
Vars map[string]any `yaml:"vars,omitempty"`
Modules map[string]BlueprintModule `yaml:"-"`
// TODO: добавить модули сюда и попробовать вызвать функцию
}
type Template struct {
settings *TemplateSettings
settings *Settings
queue []string
}
func NewTemplate(settings TemplateSettings) *Template {
func NewTemplate(settings Settings) *Template {
if settings.Vars == nil {
settings.Vars = make(map[string]any)
}
if settings.Functions == nil {
settings.Functions = make(map[string]any)
}
return &Template{
settings: &settings,
queue: make([]string, 0),
@ -59,23 +55,6 @@ func (r *Template) SetVar(name string, value any) bool {
return true
}
func (r *Template) SetSettings(settings any) error {
out, err := yaml.Marshal(settings)
if err != nil {
return err
}
if err = yaml.Unmarshal(out, &r.settings); err != nil {
return err
}
if r.settings.TemplatePath == "" {
r.settings.TemplatePath = DefaultPath
}
return nil
}
func (r *Template) Execute() error {
log.Info().Msg("template.Execute: started")
err := filepath.WalkDir(r.settings.TemplatePath, r.walkFunc)
@ -134,7 +113,7 @@ func (r *Template) templateFile(templatePath string) error {
return fmt.Errorf("templateFile: parse template (%v). %v", templatePath, err)
}
if err = t.Execute(file, r.settings.Vars); err != nil {
if err = t.Execute(file, r.settings); err != nil {
return fmt.Errorf("templateFile: execute template (%v). %v", templatePath, err)
}
@ -149,7 +128,7 @@ func (r *Template) execCommands() error {
log.Info().Msg("execCommands: go mod init")
if err := cmd.Run(); err != nil {
return err
return fmt.Errorf("execCommands: go mod init. %v", err)
}
cmd = exec.Command("go", "mod", "tidy")

@ -3,3 +3,12 @@ Description: Some descr
Template:
path: "./"
Modules:
logger:
name: zap
env:
vars:
- name: APP_NAME
type: string
default: "{{.ProjectName}}"

@ -1,11 +1,30 @@
package main
import (
"{{.ProjectName}}/internal/app"
"context"
"os/signal"
"syscall"
"{{.Vars.ProjectName}}/internal/app"
{{.Modules.logger.Import}}
{{.Modules.logger.ImportCore}}
{{.Modules.env.Import}}
)
func main() {
app := app.New()
app.Run()
{{.Modules.logger.Init}}
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
var config app.Config
{{.Modules.env.Declaration "config"}}
if err := app.Run(ctx, config, logger); err != nil {
{{.Modules.logger.Message "Fatal" "Failed to run app" "Error" "err"}}
}
}

@ -1,17 +1,19 @@
package app
type App struct {
config *Config
}
import (
"context"
type Config struct {
Name string `env:"NAME"`
}
{{.Modules.logger.Import}}
)
func New() *App {
return &App{}
}
{{.Modules.env.Struct}}
func (r *App) Run() {
func Run(ctx context.Context, config Config, logger {{.Modules.logger.Type}}) error {
{{.Modules.logger.Message "info" "App started" "config" "config"}}
<-ctx.Done()
logger.Info("App shutting down gracefully")
return nil
}

1
go.mod

@ -12,4 +12,5 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
)

2
go.sum

@ -15,6 +15,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=