From a3113a6c864d8c65b84c26de5b15f56569ddf0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Ryannel?= Date: Fri, 3 Feb 2023 08:44:46 +0100 Subject: [PATCH 1/2] support for system.meta in solution/layer doc We want to push meta information from a solution doc into the template. This will be available on the system (`$.System.Meta`). The meta information on the doc layer will be merged with the meta from the layer element. Additional the cli shall later be able to add meta on the cmd line. (`--meta`) --- pkg/helper/map.go | 11 +++ pkg/sol/helper.go | 2 +- pkg/sol/task.go | 77 ++++++++++++-------- pkg/spec/schema/apigear.solution.schema.yaml | 4 +- pkg/spec/sol.go | 28 +++---- 5 files changed, 77 insertions(+), 45 deletions(-) create mode 100644 pkg/helper/map.go diff --git a/pkg/helper/map.go b/pkg/helper/map.go new file mode 100644 index 00000000..7268156b --- /dev/null +++ b/pkg/helper/map.go @@ -0,0 +1,11 @@ +package helper + +func MergeMaps(maps ...map[string]interface{}) map[string]interface{} { + result := make(map[string]interface{}) + for _, m := range maps { + for k, v := range m { + result[k] = v + } + } + return result +} diff --git a/pkg/sol/helper.go b/pkg/sol/helper.go index 5a14f112..9780fbe7 100644 --- a/pkg/sol/helper.go +++ b/pkg/sol/helper.go @@ -8,7 +8,7 @@ import ( "github.com/apigear-io/cli/pkg/helper" ) -func GetTemplateDir(rootDir string, template string) (string, error) { +func resolveTemplateDir(rootDir string, template string) (string, error) { var templateDir string if helper.IsDir(helper.Join(rootDir, template)) { templateDir = helper.Join(rootDir, template) diff --git a/pkg/sol/task.go b/pkg/sol/task.go index 772cd892..f367aa7c 100644 --- a/pkg/sol/task.go +++ b/pkg/sol/task.go @@ -21,6 +21,17 @@ type task struct { sync.Mutex } +type genConfig struct { + name string + templatesDir string + rulesFile string + outputDir string + inputs []string + features []string + force bool + meta map[string]interface{} +} + func newTask(file string, doc *spec.SolutionDoc) (*task, error) { t := &task{ file: file, @@ -60,42 +71,47 @@ func (t *task) run() error { // processLayer processes a layer from the solution. // A layer contains information about the inputs, used template and output. func (t *task) processLayer(layer *spec.SolutionLayer) error { + var cfg genConfig log.Debug().Msgf("process layer %s", layer.Name) rootDir := t.doc.RootDir - // TODO: template can be a dir or a name of a template - var templateDir string - td, err := GetTemplateDir(rootDir, layer.Template) + + td, err := resolveTemplateDir(rootDir, layer.Template) if err != nil { return err } - templateDir = td - var templatesDir = helper.Join(templateDir, "templates") - var rulesFile = helper.Join(templateDir, "rules.yaml") - var outputDir = helper.Join(rootDir, layer.Output) - // add templates dir and rules file as dependency - t.deps = append(t.deps, templatesDir, rulesFile) - var force = layer.Force - name := layer.Name - if name == "" { + + cfg.templatesDir = helper.Join(td, "templates") + cfg.rulesFile = helper.Join(td, "rules.yaml") + // add templates dir and rules file as watcher dependency + t.deps = append(t.deps, cfg.templatesDir, cfg.rulesFile) + + cfg.outputDir = helper.Join(rootDir, layer.Output) + + cfg.name = layer.Name + if layer.Name == "" { // if no layer name, name is the last part of the output directory - name = filepath.Base(outputDir) + cfg.name = filepath.Base(cfg.outputDir) } + if layer.Inputs == nil { return fmt.Errorf("inputs are empty") } - features := layer.Features - if features == nil { - features = []string{"all"} + cfg.inputs = layer.Inputs + + cfg.features = layer.Features + if layer.Features == nil { + cfg.features = []string{"all"} } + cfg.force = layer.Force + + cfg.meta = helper.MergeMaps(t.doc.Meta, layer.Meta) - return t.runGenerator(name, layer.Inputs, outputDir, templateDir, features, force) + return t.runGenerator(cfg) } -func (t *task) runGenerator(name string, inputs []string, outputDir string, templateDir string, features []string, force bool) error { - log.Debug().Msgf("run generator %s %v", name, inputs) - var templatesDir = helper.Join(templateDir, "templates") - var rulesFile = helper.Join(templateDir, "rules.yaml") - expanded, err := expandInputs(t.doc.RootDir, inputs) +func (t *task) runGenerator(cfg genConfig) error { + log.Debug().Msgf("run generator %s %v", cfg.name, cfg.inputs) + expanded, err := expandInputs(t.doc.RootDir, cfg.inputs) if err != nil { return err } @@ -106,29 +122,32 @@ func (t *task) runGenerator(name string, inputs []string, outputDir string, temp } t.deps = append(t.deps, expanded...) - system := model.NewSystem(name) + system := model.NewSystem(cfg.name) + + system.Meta = cfg.meta + err = parseInputs(system, expanded) if err != nil { return err } - err = helper.MakeDir(outputDir) + err = helper.MakeDir(cfg.outputDir) if err != nil { return fmt.Errorf("error creating output directory: %w", err) } opts := gen.GeneratorOptions{ - OutputDir: outputDir, - TemplatesDir: templatesDir, + OutputDir: cfg.outputDir, + TemplatesDir: cfg.templatesDir, System: system, - UserFeatures: features, - UserForce: force, + UserFeatures: cfg.features, + UserForce: cfg.force, } generator, err := gen.New(opts) if err != nil { return err } - doc, err := gen.ReadRulesDoc(rulesFile) + doc, err := gen.ReadRulesDoc(cfg.rulesFile) if err != nil { return err } diff --git a/pkg/spec/schema/apigear.solution.schema.yaml b/pkg/spec/schema/apigear.solution.schema.yaml index ab6779c9..f701ee8b 100644 --- a/pkg/spec/schema/apigear.solution.schema.yaml +++ b/pkg/spec/schema/apigear.solution.schema.yaml @@ -24,7 +24,7 @@ properties: description: "The root directory of the solution to map all other paths to." meta: type: object - description: "The meta section contains meta data about the solution." + description: "The meta added to the system in the template." layers: type: array items: @@ -57,7 +57,7 @@ definitions: description: "the output directory of the layer." meta: type: object - description: "meta data about the layer which will be passed on to the template." + description: "meta data about the layer which will be passed on to the system in the template" template: type: string description: "path to the template which can be either template package name or a template folder with a rules document." diff --git a/pkg/spec/sol.go b/pkg/spec/sol.go index 7a10e89b..32c29c97 100644 --- a/pkg/spec/sol.go +++ b/pkg/spec/sol.go @@ -5,13 +5,14 @@ import ( ) type SolutionLayer struct { - Name string `json:"name" yaml:"name"` - Description string `json:"description" yaml:"description"` - Inputs []string `json:"inputs" yaml:"inputs"` - Output string `json:"output" yaml:"output"` - Template string `json:"template" yaml:"template"` - Features []string `json:"features" yaml:"features"` - Force bool `json:"force" yaml:"force"` + Name string `json:"name" yaml:"name"` + Description string `json:"description" yaml:"description"` + Inputs []string `json:"inputs" yaml:"inputs"` + Output string `json:"output" yaml:"output"` + Template string `json:"template" yaml:"template"` + Features []string `json:"features" yaml:"features"` + Force bool `json:"force" yaml:"force"` + Meta map[string]interface{} `json:"meta" yaml:"meta"` } func (l *SolutionLayer) Validate() error { @@ -32,12 +33,13 @@ func (l *SolutionLayer) Validate() error { } type SolutionDoc struct { - Schema string `json:"schema" yaml:"schema"` - Version string `json:"version" yaml:"version"` - Name string `json:"name" yaml:"name"` - Description string `json:"description" yaml:"description"` - RootDir string `json:"rootDir" yaml:"rootDir"` - Layers []*SolutionLayer `json:"layers" yaml:"layers"` + Schema string `json:"schema" yaml:"schema"` + Version string `json:"version" yaml:"version"` + Name string `json:"name" yaml:"name"` + Description string `json:"description" yaml:"description"` + RootDir string `json:"rootDir" yaml:"rootDir"` + Layers []*SolutionLayer `json:"layers" yaml:"layers"` + Meta map[string]interface{} `json:"meta" yaml:"meta"` } func (s *SolutionDoc) Validate() error { From 5df586fb84de68df41a3c16e9faec276d6621115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Ryannel?= Date: Mon, 6 Feb 2023 13:36:50 +0100 Subject: [PATCH 2/2] wip: metafile support --- Makefile | 8 +++++++- pkg/cmd/gen/expert.go | 12 ++++++++++++ pkg/model/system.go | 33 +++++++++++++++++++++++++++++++++ pkg/sol/task.go | 23 ++++++++++++++++++----- pkg/spec/sol.go | 1 + 5 files changed, 71 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 446e5195..953f761d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,7 @@ -.PHONY: antlr deb test build check cover +.PHONY: antlr deb test build check cover install + + +export CGO_ENABLED=0 test: gotestsum @@ -35,3 +38,6 @@ check: cover: go test -covermode=count -coverprofile=coverage.out -coverpkg=apigear/... ./... go tool cover -func coverage.out + +install: + go install github.com/apigear-io/cli/cmd/apigear diff --git a/pkg/cmd/gen/expert.go b/pkg/cmd/gen/expert.go index b5477c1d..8306fa23 100644 --- a/pkg/cmd/gen/expert.go +++ b/pkg/cmd/gen/expert.go @@ -8,6 +8,7 @@ import ( "github.com/apigear-io/cli/pkg/log" "github.com/apigear-io/cli/pkg/sol" "github.com/apigear-io/cli/pkg/spec" + "gopkg.in/yaml.v3" "github.com/spf13/cobra" ) @@ -25,6 +26,7 @@ type ExpertOptions struct { force bool watch bool templateDir string + meta string } func NewExpertCommand() *cobra.Command { @@ -68,6 +70,7 @@ func NewExpertCommand() *cobra.Command { cmd.Flags().StringSliceVarP(&options.features, "features", "f", []string{"all"}, "features to enable") cmd.Flags().BoolVarP(&options.force, "force", "", false, "force overwrite") cmd.Flags().BoolVarP(&options.watch, "watch", "", false, "watch for changes") + cmd.Flags().StringVarP(&options.meta, "meta", "m", "", "system meta data") Must(cmd.MarkFlagRequired("input")) Must(cmd.MarkFlagRequired("output")) Must(cmd.MarkFlagRequired("template")) @@ -79,9 +82,18 @@ func makeSolution(options *ExpertOptions) *spec.SolutionDoc { if err != nil { log.Fatal().Err(err).Msg("get current working directory") } + var meta map[string]interface{} + if options.meta != "" { + // convert from yaml to map + err := yaml.Unmarshal([]byte(options.meta), &meta) + if err != nil { + log.Fatal().Err(err).Msg("parse meta data") + } + } return &spec.SolutionDoc{ Schema: "apigear.solution/1.0", RootDir: rootDir, + Meta: meta, Layers: []*spec.SolutionLayer{ { Inputs: options.inputs, diff --git a/pkg/model/system.go b/pkg/model/system.go index 0dc12468..2fc6454e 100644 --- a/pkg/model/system.go +++ b/pkg/model/system.go @@ -1,5 +1,11 @@ package model +import ( + "strings" + + "github.com/apigear-io/cli/pkg/helper" +) + type System struct { NamedNode `json:",inline" yaml:",inline"` Modules []*Module `json:"modules" yaml:"modules"` @@ -82,3 +88,30 @@ func (s *System) ResolveAll() error { } return nil } + +// ApplyMeta applies the meta data to the system +// It looks for a symbols section in the meta data +// and applies the meta data to the corresponding +// module or interface. +func (s *System) ApplyMeta(meta map[string]interface{}) { + if meta != nil { + helper.MergeMaps(s.Meta, meta) + } + data, ok := s.Meta["symbols"].(map[string]interface{}) + if !ok { + return + } + for k, v := range data { + m := s.LookupModule(k) + if m != nil { + m.Meta = v.(map[string]interface{}) + } + kk := strings.Split(k, ".") + mName := strings.Join(kk[:len(kk)-1], ".") + iName := kk[len(kk)-1] + iface := s.LookupInterface(mName, iName) + if iface != nil { + iface.Meta = v.(map[string]interface{}) + } + } +} diff --git a/pkg/sol/task.go b/pkg/sol/task.go index f367aa7c..86ef007d 100644 --- a/pkg/sol/task.go +++ b/pkg/sol/task.go @@ -59,8 +59,21 @@ func (t *task) run() error { log.Debug().Msgf("run task %s", t.file) // reset deps t.deps = make([]string, 0) + meta := make(map[string]interface{}) + + // read meta file if exists + if t.doc.MetaFile != "" { + metaFile := helper.Join(t.doc.RootDir, t.doc.MetaFile) + err := helper.ReadDocument(metaFile, &meta) + if err != nil { + return err + } + } + cfg := genConfig{} + cfg.meta = meta + for _, layer := range t.doc.Layers { - err := t.processLayer(layer) + err := t.processLayer(layer, cfg) if err != nil { return err } @@ -70,8 +83,7 @@ func (t *task) run() error { // processLayer processes a layer from the solution. // A layer contains information about the inputs, used template and output. -func (t *task) processLayer(layer *spec.SolutionLayer) error { - var cfg genConfig +func (t *task) processLayer(layer *spec.SolutionLayer, cfg genConfig) error { log.Debug().Msgf("process layer %s", layer.Name) rootDir := t.doc.RootDir @@ -104,7 +116,8 @@ func (t *task) processLayer(layer *spec.SolutionLayer) error { } cfg.force = layer.Force - cfg.meta = helper.MergeMaps(t.doc.Meta, layer.Meta) + // merge all meta data sources + cfg.meta = helper.MergeMaps(cfg.meta, t.doc.Meta, layer.Meta) return t.runGenerator(cfg) } @@ -124,7 +137,7 @@ func (t *task) runGenerator(cfg genConfig) error { system := model.NewSystem(cfg.name) - system.Meta = cfg.meta + system.ApplyMeta(cfg.meta) err = parseInputs(system, expanded) if err != nil { diff --git a/pkg/spec/sol.go b/pkg/spec/sol.go index 32c29c97..8b3974d8 100644 --- a/pkg/spec/sol.go +++ b/pkg/spec/sol.go @@ -40,6 +40,7 @@ type SolutionDoc struct { RootDir string `json:"rootDir" yaml:"rootDir"` Layers []*SolutionLayer `json:"layers" yaml:"layers"` Meta map[string]interface{} `json:"meta" yaml:"meta"` + MetaFile string `json:"metaFile" yaml:"metaFile"` } func (s *SolutionDoc) Validate() error {