diff --git a/.drone.yml b/.drone.yml index 103e426..a8fb756 100644 --- a/.drone.yml +++ b/.drone.yml @@ -145,3 +145,21 @@ steps: - cd provider/vault - golangci-lint run +--- +kind: pipeline +type: docker +name: definition + +steps: +- name: test + image: golang + commands: + - cd provider/definition + - go test ./... + +- name: golangci-lint + image: golangci/golangci-lint:v1.55 + commands: + - cd provider/definition + - golangci-lint run + diff --git a/.golangci.yml b/.golangci.yml index 6c7c222..391e0d8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -24,6 +24,13 @@ linters-settings: locale: US varnamelen: min-name-length: 2 + ignore-decls: + - w io.Writer + - t testing.T + - e error + - i int + - b bytes.Buffer + - h Handle linters: enable-all: true diff --git a/definition/defenition.go b/definition/defenition.go new file mode 100755 index 0000000..64385e1 --- /dev/null +++ b/definition/defenition.go @@ -0,0 +1,29 @@ +package definition + +import ( + "fmt" +) + +func New() Definition { + return Definition{} +} + +type Definition struct { + options Options +} + +func (d *Definition) Add(opts ...Option) *Definition { + d.options = append(d.options, opts...) + + return d +} + +func (d *Definition) View(handle func(Option) error) error { + for idx, opt := range d.options { + if err := handle(opt); err != nil { + return fmt.Errorf("%s[%d]:%w", opt.Kind(), idx, err) + } + } + + return nil +} diff --git a/definition/generate/generator.go b/definition/generate/generator.go new file mode 100644 index 0000000..fdb9e93 --- /dev/null +++ b/definition/generate/generator.go @@ -0,0 +1,70 @@ +package generate + +import ( + "fmt" + "io" + + "gitoa.ru/go-4devs/config/definition" +) + +type Generator struct { + pkg string + ViewOption + Imp Imports + errs []error + defaultErrors []string +} + +func (g Generator) Pkg() string { + return g.pkg +} + +func (g Generator) Imports() []Import { + return g.Imp.Imports() +} + +func (g Generator) Handle(w io.Writer, data Handler, opt definition.Option) error { + handle := get(opt.Kind()) + + return handle(w, data, opt) +} + +func (g Generator) StructName() string { + return FuncName(g.Prefix + "_" + g.Struct + "_" + g.Suffix) +} + +func (g Generator) Options() ViewOption { + return g.ViewOption +} + +func (g Generator) Keys() []string { + return nil +} + +func (g Generator) DefaultErrors() []string { + if len(g.defaultErrors) > 0 { + return g.defaultErrors + } + + if len(g.ViewOption.Errors.Default) > 0 { + g.Imp.Adds("errors") + } + + g.defaultErrors = make([]string, len(g.ViewOption.Errors.Default)) + for idx, name := range g.ViewOption.Errors.Default { + short, err := g.AddType(name) + if err != nil { + g.errs = append(g.errs, fmt.Errorf("add default error[%d]:%w", idx, err)) + + return nil + } + + g.defaultErrors[idx] = short + } + + return g.defaultErrors +} + +func (g *Generator) AddType(pkg string) (string, error) { + return g.Imp.AddType(pkg) +} diff --git a/definition/generate/helpers.go b/definition/generate/helpers.go new file mode 100644 index 0000000..7a9eb82 --- /dev/null +++ b/definition/generate/helpers.go @@ -0,0 +1,18 @@ +package generate + +import ( + "errors" + + "github.com/iancoleman/strcase" +) + +var ( + ErrNotFound = errors.New("not found") + ErrAlreadyExist = errors.New("already exist") + ErrWrongType = errors.New("wrong type") + ErrWrongFormat = errors.New("wrong format") +) + +func FuncName(in string) string { + return strcase.ToCamel(in) +} diff --git a/definition/generate/imports.go b/definition/generate/imports.go new file mode 100644 index 0000000..5a98e5e --- /dev/null +++ b/definition/generate/imports.go @@ -0,0 +1,89 @@ +package generate + +import ( + "fmt" + "strconv" + "strings" +) + +func NewImports() Imports { + return Imports{ + data: make(map[string]string), + } +} + +type Imports struct { + data map[string]string +} + +func (i Imports) Imports() []Import { + imports := make([]Import, 0, len(i.data)) + for name, alias := range i.data { + imports = append(imports, Import{ + Package: name, + Alias: alias, + }) + } + + return imports +} + +func (i *Imports) Short(fullType string) (string, error) { + idx := strings.LastIndexByte(fullType, '.') + if idx == -1 { + return "", fmt.Errorf("%w: expect package.Type", ErrWrongFormat) + } + + if alias, ok := i.data[fullType[:idx]]; ok { + return alias + fullType[idx:], nil + } + + return "", fmt.Errorf("%w alias for pkg %v", ErrNotFound, fullType[:idx]) +} + +func (i *Imports) AddType(fullType string) (string, error) { + idx := strings.LastIndexByte(fullType, '.') + if idx == -1 { + return "", fmt.Errorf("%w: expect pckage.Type", ErrWrongFormat) + } + + imp := i.Add(fullType[:idx]) + + return imp.Alias + fullType[idx:], nil +} + +func (i *Imports) Adds(pkgs ...string) { + for _, pkg := range pkgs { + i.Add(pkg) + } +} + +func (i *Imports) Add(pkg string) Import { + alias := pkg + + if idx := strings.LastIndexByte(pkg, '/'); idx != -1 { + alias = pkg[idx+1:] + } + + if al, ok := i.data[pkg]; ok { + return Import{Package: pkg, Alias: al} + } + + for _, al := range i.data { + if al == alias { + alias += strconv.Itoa(len(i.data)) + } + } + + i.data[pkg] = alias + + return Import{ + Alias: alias, + Package: pkg, + } +} + +type Import struct { + Alias string + Package string +} diff --git a/definition/generate/run.go b/definition/generate/run.go new file mode 100644 index 0000000..731d361 --- /dev/null +++ b/definition/generate/run.go @@ -0,0 +1,39 @@ +package generate + +import ( + "bytes" + "fmt" + "io" + + "gitoa.ru/go-4devs/config/definition" +) + +func Run(w io.Writer, pkgName string, defs definition.Definition, viewOpt ViewOption) error { + gen := Generator{ + pkg: pkgName, + ViewOption: viewOpt, + Imp: NewImports(), + } + + gen.Imp.Adds("gitoa.ru/go-4devs/config", "fmt", "context") + + var view bytes.Buffer + + err := defs.View(func(o definition.Option) error { + return gen.Handle(&view, &gen, o) + }) + if err != nil { + return fmt.Errorf("render options:%w", err) + } + + if err := tpl.Execute(w, gen); err != nil { + return fmt.Errorf("render base:%w", err) + } + + _, cerr := io.Copy(w, &view) + if cerr != nil { + return fmt.Errorf("copy error:%w", cerr) + } + + return nil +} diff --git a/definition/generate/template.go b/definition/generate/template.go new file mode 100644 index 0000000..99ab6b1 --- /dev/null +++ b/definition/generate/template.go @@ -0,0 +1,43 @@ +package generate + +import "text/template" + +//nolint:gochecknoglobals +var ( + tpl = template.Must(template.New("tpls").Parse(baseTemplate)) + baseTemplate = `// Code generated gitoa.ru/go-4devs/config DO NOT EDIT. +package {{.Pkg}} + +import ( + {{range .Imports}} + {{- .Alias }}"{{ .Package }}" + {{end}} +) + +func With{{.StructName}}Log(log func(context.Context, string, ...any)) func(*{{.StructName}}) { + return func(ci *{{.StructName}}) { + ci.log = log + } +} + +func New{{.StructName}}(prov config.Provider, opts ...func(*{{.StructName}})) {{.StructName}} { + i := {{.StructName}}{ + Provider: prov, + log: func(_ context.Context, format string, args ...any) { + fmt.Printf(format, args...) + }, + } + + for _, opt := range opts { + opt(&i) + } + + return i +} + +type {{.StructName}} struct { + config.Provider + log func(context.Context, string, ...any) +} +` +) diff --git a/definition/generate/view.go b/definition/generate/view.go new file mode 100644 index 0000000..9cef1fb --- /dev/null +++ b/definition/generate/view.go @@ -0,0 +1,63 @@ +package generate + +import ( + "fmt" + "io" + "sync" + + "gitoa.ru/go-4devs/config/definition" +) + +//nolint:gochecknoglobals +var handlers = sync.Map{} + +func Add(kind string, h Handle) error { + _, ok := handlers.Load(kind) + if ok { + return fmt.Errorf("kind %v: %w", kind, ErrAlreadyExist) + } + + handlers.Store(kind, h) + + return nil +} + +//nolint:forcetypeassert +func get(kind string) Handle { + handler, ok := handlers.Load(kind) + if !ok { + return func(w io.Writer, h Handler, o definition.Option) error { + return fmt.Errorf("handler by %v:%w", kind, ErrNotFound) + } + } + + return handler.(Handle) +} + +func MustAdd(kind string, h Handle) { + if err := Add(kind, h); err != nil { + panic(err) + } +} + +type Handle func(io.Writer, Handler, definition.Option) error + +type Handler interface { + StructName() string + Handle(w io.Writer, handler Handler, opt definition.Option) error + Options() ViewOption + Keys() []string + AddType(fullName string) (string, error) + DefaultErrors() []string +} + +type ViewOption struct { + Prefix, Suffix string + Context bool + Struct string + Errors ViewErrors +} + +type ViewErrors struct { + Default []string +} diff --git a/definition/go.mod b/definition/go.mod new file mode 100644 index 0000000..15483b3 --- /dev/null +++ b/definition/go.mod @@ -0,0 +1,5 @@ +module gitoa.ru/go-4devs/config/definition + +go 1.21.5 + +require github.com/iancoleman/strcase v0.3.0 diff --git a/definition/go.sum b/definition/go.sum new file mode 100644 index 0000000..6261b6a --- /dev/null +++ b/definition/go.sum @@ -0,0 +1,2 @@ +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= diff --git a/definition/group/group.go b/definition/group/group.go new file mode 100755 index 0000000..32328c3 --- /dev/null +++ b/definition/group/group.go @@ -0,0 +1,27 @@ +package group + +import ( + "gitoa.ru/go-4devs/config/definition" +) + +const Kind = "group" + +var _ definition.Option = Group{} + +func New(name, desc string, opts ...definition.Option) Group { + return Group{ + Name: name, + Description: desc, + Options: opts, + } +} + +type Group struct { + Options definition.Options + Name string + Description string +} + +func (o Group) Kind() string { + return Kind +} diff --git a/definition/group/view.go b/definition/group/view.go new file mode 100644 index 0000000..8e52c11 --- /dev/null +++ b/definition/group/view.go @@ -0,0 +1,88 @@ +package group + +import ( + "fmt" + "io" + "text/template" + + "gitoa.ru/go-4devs/config/definition" + "gitoa.ru/go-4devs/config/definition/generate" +) + +//nolint:gochecknoinits +func init() { + generate.MustAdd(Kind, handle) +} + +func handle(w io.Writer, data generate.Handler, option definition.Option) error { + group, ok := option.(Group) + if !ok { + return fmt.Errorf("%w:%T", generate.ErrWrongType, option) + } + + viewData := View{ + Group: group, + ParentName: data.StructName(), + ViewOption: data.Options(), + } + + err := tpl.Execute(w, viewData) + if err != nil { + return fmt.Errorf("render group:%w", err) + } + + childData := ChildData{ + Handler: data, + structName: viewData.StructName(), + keys: append(data.Keys(), group.Name), + } + for idx, child := range group.Options { + if cerr := data.Handle(w, childData, child); cerr != nil { + return fmt.Errorf("render group child[%d]:%w", idx, cerr) + } + } + + return nil +} + +type ChildData struct { + generate.Handler + structName string + keys []string +} + +func (c ChildData) StructName() string { + return c.structName +} + +func (c ChildData) Keys() []string { + return c.keys +} + +type View struct { + Group + ParentName string + generate.ViewOption +} + +func (v View) FuncName() string { + return generate.FuncName(v.Name) +} + +func (v View) StructName() string { + return generate.FuncName(v.Prefix + v.Name + v.Suffix) +} + +//nolint:gochecknoglobals +var ( + tpl = template.Must(template.New("tpls").Parse(gpoupTemplate)) + gpoupTemplate = `type {{.StructName}} struct { + {{.ParentName}} +} + +// {{.FuncName}} {{.Description}}. +func (i {{.ParentName}}) {{.FuncName}}() {{.StructName}} { + return {{.StructName}}{i} +} +` +) diff --git a/definition/option.go b/definition/option.go new file mode 100755 index 0000000..9a3bd57 --- /dev/null +++ b/definition/option.go @@ -0,0 +1,27 @@ +package definition + +type Option interface { + Kind() string +} + +type Options []Option + +func (s Options) Len() int { return len(s) } +func (s Options) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +type Params []Param + +func (p Params) Get(name string) (any, bool) { + for _, param := range p { + if param.Name == name { + return param.Value, true + } + } + + return nil, false +} + +type Param struct { + Name string + Value any +} diff --git a/definition/option/option.go b/definition/option/option.go new file mode 100755 index 0000000..1936f2f --- /dev/null +++ b/definition/option/option.go @@ -0,0 +1,100 @@ +package option + +import ( + "gitoa.ru/go-4devs/config/definition" +) + +var _ definition.Option = Option{} + +const ( + Kind = "option" +) + +const ( + TypeString = "string" + TypeInt = "int" + TypeInt64 = "int64" + TypeUint = "uint" + TypeUint64 = "uint64" + TypeFloat64 = "float64" + TypeBool = "bool" + TypeTime = "time.Time" + TypeDuration = "time.Duration" +) + +func Default(v any) func(*Option) { + return func(o *Option) { + o.Default = v + } +} + +func New(name, desc string, vtype any, opts ...func(*Option)) Option { + option := Option{ + Name: name, + Description: desc, + Type: vtype, + } + + for _, opt := range opts { + opt(&option) + } + + return option +} + +type Option struct { + Name string + Description string + Type any + Default any + Params definition.Params +} + +func (o Option) WithParams(params ...definition.Param) Option { + return Option{ + Name: o.Name, + Description: o.Description, + Type: o.Type, + Params: append(params, o.Params...), + } +} + +func (o Option) Kind() string { + return Kind +} + +func Time(name, desc string, opts ...func(*Option)) Option { + return New(name, desc, TypeTime, opts...) +} + +func Duration(name, desc string, opts ...func(*Option)) Option { + return New(name, desc, TypeDuration, opts...) +} + +func String(name, desc string, opts ...func(*Option)) Option { + return New(name, desc, TypeString, opts...) +} + +func Int(name, desc string, opts ...func(*Option)) Option { + return New(name, desc, TypeInt, opts...) +} + +func Int64(name, desc string, opts ...func(*Option)) Option { + return New(name, desc, TypeInt64, opts...) +} + +func Uint(name, desc string, opts ...func(*Option)) Option { + return New(name, desc, TypeUint, opts...) +} + +func Uint64(name, desc string, opts ...func(*Option)) Option { + return New(name, desc, TypeUint64, opts...) +} + +func Float64(name, desc string, opts ...func(*Option)) Option { + return New(name, desc, TypeFloat64, opts...) +} + +func Bool(name, desc string, opts ...func(*Option)) Option { + return New(name, desc, TypeBool, opts...) +} diff --git a/definition/option/tpl/option.tmpl b/definition/option/tpl/option.tmpl new file mode 100644 index 0000000..73f5532 --- /dev/null +++ b/definition/option/tpl/option.tmpl @@ -0,0 +1,33 @@ +// read{{.FuncName}} {{.Description}}. +func (i {{.StructName}}) read{{.FuncName}}(ctx context.Context) (v {{.Type}},e error) { + val, err := i.Value(ctx, {{ .ParentKeys }}"{{ .Name }}") + if err != nil { + {{if .HasDefault}} + {{$default := .Default}} + {{range .DefaultErrors}} + if errors.Is(err,{{.}}){ + return {{$default}} + } + {{end}} + {{end}} + return v, fmt.Errorf("read {{.Keys}}:%w",err) + } + + {{.Parse "val" "v" .Keys }} +} + +// Read{{.FuncName}} {{.Description}}. +func (i {{.StructName}}) Read{{.FuncName}}(ctx context.Context) ({{.Type}}, error) { + return i.read{{.FuncName}}(ctx) +} + +// {{.FuncName}} {{.Description}}. +func (i {{.StructName}}) {{.FuncName}}({{if .Context}} ctx context.Context {{end}}) {{.Type}} { + {{if not .Context}} ctx := context.Background() {{end}} + val, err := i.read{{.FuncName}}(ctx) + if err != nil { + i.log(ctx, "get {{.Keys}}: %v", err) + } + + return val +} diff --git a/definition/option/tpl/parse.tmpl b/definition/option/tpl/parse.tmpl new file mode 100644 index 0000000..0cbc58b --- /dev/null +++ b/definition/option/tpl/parse.tmpl @@ -0,0 +1,3 @@ +{{block "Parse" .}} +return {{.ValName}}.Parse{{ .FuncType}}() +{{end}} \ No newline at end of file diff --git a/definition/option/tpl/unmarshal_json.tmpl b/definition/option/tpl/unmarshal_json.tmpl new file mode 100644 index 0000000..b54d7fb --- /dev/null +++ b/definition/option/tpl/unmarshal_json.tmpl @@ -0,0 +1,8 @@ +{{block "UnmarshalJSON" . }} + pval, perr := {{.ValName}}.ParseString() + if perr != nil { + return {{.Value}}, fmt.Errorf("read {{.Keys}}:%w", perr) + } + + return {{.Value}}, {{.Value}}.UnmarshalJSON([]byte(pval)) +{{end}} \ No newline at end of file diff --git a/definition/option/tpl/unmarshal_text.tmpl b/definition/option/tpl/unmarshal_text.tmpl new file mode 100644 index 0000000..fbb8105 --- /dev/null +++ b/definition/option/tpl/unmarshal_text.tmpl @@ -0,0 +1,8 @@ +{{block "UnmarshalText" . }} + pval, perr := {{.ValName}}.ParseString() + if perr != nil { + return {{.Value}}, fmt.Errorf("read {{.Keys}}:%w", perr) + } + + return {{.Value}}, {{.Value}}.UnmarshalText([]byte(pval)) +{{end}} \ No newline at end of file diff --git a/definition/option/view.go b/definition/option/view.go new file mode 100644 index 0000000..46e8135 --- /dev/null +++ b/definition/option/view.go @@ -0,0 +1,235 @@ +package option + +import ( + "bytes" + "embed" + "encoding" + "encoding/json" + "fmt" + "io" + "reflect" + "strings" + "text/template" + "time" + + "gitoa.ru/go-4devs/config/definition" + "gitoa.ru/go-4devs/config/definition/generate" +) + +//go:embed tpl/* +var tpls embed.FS + +//nolint:gochecknoglobals +var tpl = template.Must(template.New("tpls").ParseFS(tpls, "tpl/*.tmpl")) + +//nolint:gochecknoinits +func init() { + generate.MustAdd(Kind, Handle(tpl.Lookup("option.tmpl"))) +} + +func Handle(tpl *template.Template) generate.Handle { + return func(w io.Writer, h generate.Handler, o definition.Option) error { + opt, _ := o.(Option) + if err := tpl.Execute(w, View{Option: opt, Handler: h}); err != nil { + return fmt.Errorf("option tpl:%w", err) + } + + return nil + } +} + +type View struct { + Option + generate.Handler +} + +func (v View) Context() bool { + return v.Options().Context +} + +func (v View) FuncName() string { + if funcName, ok := v.Option.Params.Get(ViewParamFunctName); ok { + name, _ := funcName.(string) + + return name + } + + return generate.FuncName(v.Name) +} + +func (v View) Description() string { + if desc, ok := v.Option.Params.Get(ViewParamDescription); ok { + description, _ := desc.(string) + + return description + } + + return v.Option.Description +} + +func (v View) Default() string { + switch data := v.Option.Default.(type) { + case time.Time: + return fmt.Sprintf("time.Parse(%q,time.RFC3339Nano)", data.Format(time.RFC3339Nano)) + case time.Duration: + return fmt.Sprintf("time.ParseDuration(%q)", data) + default: + return fmt.Sprintf("%#v, nil", data) + } +} + +func (v View) HasDefault() bool { + return v.Option.Default != nil +} + +func (v View) ParentKeys() string { + if len(v.Handler.Keys()) > 0 { + return `"` + strings.Join(v.Handler.Keys(), `","`) + `",` + } + + return "" +} + +func (v View) Type() string { + slice := "" + + if vtype, ok := v.Option.Type.(string); ok { + if strings.Contains(vtype, ".") { + if name, err := v.AddType(vtype); err == nil { + return slice + name + } + } + + return vtype + } + + rtype := reflect.TypeOf(v.Option.Type) + + if rtype.PkgPath() == "" { + return rtype.String() + } + + if rtype.Kind() == reflect.Slice { + slice = "[]" + } + + short, err := v.AddType(rtype.PkgPath() + "." + rtype.Name()) + if err != nil { + return err.Error() + } + + return slice + short +} + +func (v View) FuncType() string { + return generate.FuncName(v.Type()) +} + +func (v View) Parse(valName string, value string, keys []string) string { + h := parser(v.Option.Type) + + data, err := h(ParseData{ + Value: value, + ValName: valName, + Keys: keys, + View: v, + }) + if err != nil { + return err.Error() + } + + return data +} + +//nolint:gochecknoglobals,unparam +var parses = map[string]func(data ParseData) (string, error){ + typesIntreface[0].Name(): func(data ParseData) (string, error) { + var b bytes.Buffer + + err := tpl.ExecuteTemplate(&b, "unmarshal_text.tmpl", data) + if err != nil { + return "", fmt.Errorf("execute unmarshal text:%w", err) + } + + return b.String(), nil + }, + typesIntreface[1].Name(): func(data ParseData) (string, error) { + var b bytes.Buffer + + err := tpl.ExecuteTemplate(&b, "unmarshal_json.tmpl", data) + if err != nil { + return "", fmt.Errorf("execute unmarshal json:%w", err) + } + + return b.String(), nil + }, + TypeInt: internal, + TypeInt64: internal, + TypeBool: internal, + TypeString: internal, + TypeFloat64: internal, + TypeUint: internal, + TypeUint64: internal, + "time.Duration": func(data ParseData) (string, error) { + return fmt.Sprintf("return %s.ParseDuration()", data.ValName), nil + }, + "time.Time": func(data ParseData) (string, error) { + return fmt.Sprintf("return %s.ParseTime()", data.ValName), nil + }, + "any": func(data ParseData) (string, error) { + return fmt.Sprintf("return %[2]s, %[1]s.Unmarshal(&%[2]s)", data.ValName, data.Value), nil + }, +} + +func internal(data ParseData) (string, error) { + var b bytes.Buffer + + err := tpl.ExecuteTemplate(&b, "parse.tmpl", data) + if err != nil { + return "", fmt.Errorf("execute parse.tmpl:%w", err) + } + + return b.String(), nil +} + +//nolint:gochecknoglobals +var typesIntreface = [...]reflect.Type{ + reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem(), + reflect.TypeOf((*json.Unmarshaler)(nil)).Elem(), +} + +func parser(data any) func(ParseData) (string, error) { + vtype := reflect.TypeOf(data) + name := vtype.Name() + + if v, ok := data.(string); ok { + name = v + } + + if vtype.Kind() == reflect.Slice { + return parses["any"] + } + + if h, ok := parses[name]; ok { + return h + } + + for _, extypes := range typesIntreface { + if vtype.Implements(extypes) { + return parses[extypes.Name()] + } + + if vtype.Kind() != reflect.Ptr && reflect.PointerTo(vtype).Implements(extypes) { + return parses[extypes.Name()] + } + } + + return parses["any"] +} + +type ParseData struct { + Value string + ValName string + Keys []string + View +} diff --git a/definition/option/view_params.go b/definition/option/view_params.go new file mode 100644 index 0000000..36c4ee1 --- /dev/null +++ b/definition/option/view_params.go @@ -0,0 +1,6 @@ +package option + +const ( + ViewParamFunctName = "view.funcName" + ViewParamDescription = "view.description" +) diff --git a/definition/proto/proto.go b/definition/proto/proto.go new file mode 100644 index 0000000..37ea083 --- /dev/null +++ b/definition/proto/proto.go @@ -0,0 +1,27 @@ +package proto + +import ( + "gitoa.ru/go-4devs/config/definition" +) + +const Kind = "proto" + +func New(name, desc string, opt definition.Option) Proto { + pr := Proto{ + Name: name, + Description: desc, + Option: opt, + } + + return pr +} + +type Proto struct { + Name string + Description string + Option definition.Option +} + +func (p Proto) Kind() string { + return Kind +} diff --git a/definition/proto/view.go b/definition/proto/view.go new file mode 100644 index 0000000..19fde6b --- /dev/null +++ b/definition/proto/view.go @@ -0,0 +1,80 @@ +package proto + +import ( + "fmt" + "io" + "strings" + "text/template" + + "gitoa.ru/go-4devs/config/definition" + "gitoa.ru/go-4devs/config/definition/generate" + "gitoa.ru/go-4devs/config/definition/option" +) + +//nolint:gochecknoinits +func init() { + generate.MustAdd(Kind, handle) +} + +func handle(w io.Writer, data generate.Handler, opt definition.Option) error { + proto, ok := opt.(Proto) + if !ok { + return fmt.Errorf("%w:%T", generate.ErrWrongType, opt) + } + + if viewOpt, ok := proto.Option.(option.Option); ok { + viewOpt = viewOpt.WithParams( + definition.Param{ + Name: option.ViewParamFunctName, + Value: generate.FuncName(proto.Name) + generate.FuncName(viewOpt.Name), + }, + definition.Param{ + Name: option.ViewParamDescription, + Value: proto.Description + " " + viewOpt.Description, + }, + ) + + return option.Handle(tpl)(w, data, viewOpt) + } + + return fmt.Errorf("%w:%T", generate.ErrWrongType, opt) +} + +//nolint:gochecknoglobals +var ( + tpl = template.Must(template.New("tpls").Funcs(template.FuncMap{"join": strings.Join}).Parse(templateOption)) + templateOption = `// read{{.FuncName}} {{.Description}}. +func (i {{.StructName}}) read{{.FuncName}}(ctx context.Context, key string) (v {{.Type}},e error) { + val, err := i.Value(ctx, {{ .ParentKeys }} key, "{{.Name}}") + if err != nil { + {{if .HasDefault}} + {{$default := .Default}} + {{range .DefaultErrors}} + if errors.Is(err,{{.}}){ + return {{$default}} + } + {{end}} + {{end}} + return v, fmt.Errorf("read {{.Keys}}:%w",err) + } + + {{.Parse "val" "v" .Keys }} +} + +// Read{{.FuncName}} {{.Description}}. +func (i {{.StructName}}) Read{{.FuncName}}(ctx context.Context, key string) ({{.Type}}, error) { + return i.read{{.FuncName}}(ctx, key) +} + +// {{.FuncName}} {{.Description}}. +func (i {{.StructName}}) {{.FuncName}}({{if .Context}} ctx context.Context, {{end}} key string) {{.Type}} { + {{if not .Context}} ctx := context.Background() {{end}} + val, err := i.read{{.FuncName}}(ctx, key) + if err != nil { + i.log(ctx, "get {{.Keys}}: %v", err) + } + + return val +} +` +)