Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
12 changes: 12 additions & 0 deletions pkg/cmd/gen/expert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -25,6 +26,7 @@ type ExpertOptions struct {
force bool
watch bool
templateDir string
meta string
}

func NewExpertCommand() *cobra.Command {
Expand Down Expand Up @@ -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"))
Expand All @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions pkg/helper/map.go
Original file line number Diff line number Diff line change
@@ -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
}
33 changes: 33 additions & 0 deletions pkg/model/system.go
Original file line number Diff line number Diff line change
@@ -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"`
Expand Down Expand Up @@ -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{})
}
}
}
2 changes: 1 addition & 1 deletion pkg/sol/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
94 changes: 63 additions & 31 deletions pkg/sol/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -48,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
}
Expand All @@ -59,43 +83,48 @@ 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 {
func (t *task) processLayer(layer *spec.SolutionLayer, cfg genConfig) error {
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

// merge all meta data sources
cfg.meta = helper.MergeMaps(cfg.meta, 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
}
Expand All @@ -106,29 +135,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.ApplyMeta(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
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/spec/schema/apigear.solution.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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."
Expand Down
29 changes: 16 additions & 13 deletions pkg/spec/sol.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -32,12 +33,14 @@ 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"`
MetaFile string `json:"metaFile" yaml:"metaFile"`
}

func (s *SolutionDoc) Validate() error {
Expand Down