Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New parse tree #96

Merged
merged 6 commits into from
Feb 19, 2024
Merged
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
121 changes: 117 additions & 4 deletions cmd/data.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,135 @@
package cmd

import (
"context"
"encoding/json"
"fmt"
"os"
"regexp"

"github.com/TylerBrock/colorjson"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"

"github.com/blackstork-io/fabric/parser"
"github.com/blackstork-io/fabric/parser/definitions"
"github.com/blackstork-io/fabric/pkg/diagnostics"
"github.com/blackstork-io/fabric/plugin"
)

func data(target string) {
fmt.Println("data called on", target)
var dataTgtRe = regexp.MustCompile(`(?:document\.([^.]+)\.data\.([^.]+)\.([^.\n]+))|(?:data\.([^.]+)\.([^.]+))`)

func Data(ctx context.Context, blocks *parser.DefinedBlocks, caller *parser.Caller, target string) (result plugin.MapData, diags diagnostics.Diag) {
// docName, pluginName, blockName
// target: document.<doc-name>.data.<plugin-name>.<data-name>
tgt := dataTgtRe.FindStringSubmatch(target)
if tgt == nil {
diags.Add(
"Incorrect target",
"Target should have the format 'document.<doc-name>.data.<plugin-name>.<block-name>' or 'data.<plugin-name>.<block-name>'",
)
return
}

var data *definitions.ParsedData

if tgt[1] != "" {
// document.<doc-name>.data.<plugin-name>.<block-name>
doc, found := blocks.Documents[tgt[1]]
if !found {
diags.Add(
"Document not found",
fmt.Sprintf(
"Definition for document named '%s' not found",
tgt[1],
),
)
return
}

pd, diag := blocks.ParseDocument(doc)
if diags.Extend(diag) {
return
}

idx := slices.IndexFunc(pd.Data, func(data *definitions.ParsedData) bool {
return data.PluginName == tgt[2] && data.BlockName == tgt[3]
})
if idx == -1 {
diags.Add(
"Data block not found",
fmt.Sprintf("Data block '%s.%s' not found in document '%s'", tgt[2], tgt[3], tgt[1]),
)
return
}
data = pd.Data[idx]
} else {
// data.<plugin-name>.<block-name>
defPlugin, found := blocks.Plugins[definitions.Key{
PluginKind: definitions.BlockKindData,
PluginName: tgt[4],
BlockName: tgt[5],
}]
if !found {
diags.Add(
"Data block not found",
fmt.Sprintf("Data block '%s.%s' not found in global scope", tgt[4], tgt[5]),
)
return
}
res, diag := blocks.ParsePlugin(defPlugin)
if diags.Extend(diag) {
return
}
data = (*definitions.ParsedData)(res)
}
res, diag := caller.CallData(ctx, data.PluginName, data.Config, data.Invocation)
if diags.Extend(diag) {
return
}
return res, diags
}

// dataCmd represents the data command
var dataCmd = &cobra.Command{
Use: "data TARGET",
Short: "Execute a single data block",
Long: `Execute the data block and print out prettified JSON to stdout`,
Run: func(_ *cobra.Command, args []string) {
data(args[0])
RunE: func(cmd *cobra.Command, args []string) (err error) {
var diags diagnostics.Diag
eval := NewEvaluator(cliArgs.pluginsDir)
defer func() {
err = eval.Cleanup(diags)
}()
diags = eval.ParseFabricFiles(os.DirFS(cliArgs.sourceDir))
if diags.HasErrors() {
return
}
if diags.Extend(eval.LoadRunner()) {
return
}

res, diag := Data(cmd.Context(), eval.Blocks, eval.PluginCaller(), args[0])
if diags.Extend(diag) {
return
}

val := res.Any()
var ser []byte
if cliArgs.colorize {
fmt := colorjson.NewFormatter()
fmt.Indent = 4
ser, err = fmt.Marshal(val)
} else {
ser, err = json.MarshalIndent(val, "", " ")
}
if diags.AppendErr(err, "Failed to serialize data output to json") {
return
}
_, err = os.Stdout.Write(ser)

diags.AppendErr(err, "Failed to output json data")
return
},
Args: cobra.ExactArgs(1),
}
Expand Down
73 changes: 73 additions & 0 deletions cmd/evaluator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package cmd

import (
"io/fs"
"os"

"github.com/hashicorp/hcl/v2"

"github.com/blackstork-io/fabric/internal/builtin"
"github.com/blackstork-io/fabric/parser"
"github.com/blackstork-io/fabric/pkg/diagnostics"
"github.com/blackstork-io/fabric/plugin/runner"
)

type Evaluator struct {
PluginsDir string
Blocks *parser.DefinedBlocks
Runner *runner.Runner
FileMap map[string]*hcl.File
}

func NewEvaluator(pluginsDir string) *Evaluator {
return &Evaluator{
PluginsDir: pluginsDir,
}
}

func (e *Evaluator) Cleanup(diags diagnostics.Diag) error {
if e.Runner != nil {
diags.ExtendHcl(e.Runner.Close())
}
diagnostics.PrintDiags(os.Stderr, diags, e.FileMap, cliArgs.colorize)
// Errors have been already displayed
if diags.HasErrors() {
rootCmd.SilenceErrors = true
rootCmd.SilenceUsage = true
return diags
}
return nil
}

func (e *Evaluator) ParseFabricFiles(sourceDir fs.FS) (diags diagnostics.Diag) {
e.Blocks, e.FileMap, diags = parser.ParseDir(sourceDir)
if diags.HasErrors() {
return
}
if e.PluginsDir == "" && e.Blocks.GlobalConfig != nil && e.Blocks.GlobalConfig.PluginRegistry != nil {
// use pluginsDir from config, unless overridden by cli arg
e.PluginsDir = e.Blocks.GlobalConfig.PluginRegistry.MirrorDir
}
return
}

func (e *Evaluator) LoadRunner() diagnostics.Diag {
var pluginVersions runner.VersionMap
if e.Blocks.GlobalConfig != nil {
pluginVersions = e.Blocks.GlobalConfig.PluginVersions
}
var stdDiag hcl.Diagnostics

e.Runner, stdDiag = runner.Load(
runner.WithBuiltIn(
builtin.Plugin(version),
),
runner.WithPluginDir(e.PluginsDir),
runner.WithPluginVersions(pluginVersions),
)
return diagnostics.Diag(stdDiag)
}

func (e *Evaluator) PluginCaller() *parser.Caller {
return parser.NewPluginCaller(e.Runner)
}
83 changes: 28 additions & 55 deletions cmd/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,20 @@ package cmd

import (
"bufio"
"context"
"fmt"
"io"
"io/fs"
"os"
"strings"

"github.com/hashicorp/hcl/v2"
"github.com/spf13/cobra"

"github.com/blackstork-io/fabric/internal/builtin"
"github.com/blackstork-io/fabric/parser"
"github.com/blackstork-io/fabric/parser/definitions"
"github.com/blackstork-io/fabric/pkg/diagnostics"
"github.com/blackstork-io/fabric/plugin/runner"
)

func Render(pluginsDir string, sourceDir fs.FS, docName string) (results []string, fileMap map[string]*hcl.File, diags diagnostics.Diag) {
blocks, fileMap, diags := parser.ParseDir(sourceDir)
if diags.HasErrors() {
return
}
if len(fileMap) == 0 {
diags.Add(
"No correct fabric files found",
fmt.Sprintf("There are no *.fabric files at '%s' or all of them have failed to parse", cliArgs.sourceDir),
)
return
}
func Render(ctx context.Context, blocks *parser.DefinedBlocks, pluginCaller *parser.Caller, docName string) (results []string, diags diagnostics.Diag) {
doc, found := blocks.Documents[docName]
if !found {
diags.Add(
Expand All @@ -42,33 +28,12 @@ func Render(pluginsDir string, sourceDir fs.FS, docName string) (results []strin
return
}

if pluginsDir == "" && blocks.GlobalConfig != nil && blocks.GlobalConfig.PluginRegistry != nil {
// use pluginsDir from config, unless overridden by cli arg
pluginsDir = blocks.GlobalConfig.PluginRegistry.MirrorDir
}

var pluginVersions runner.VersionMap
if blocks.GlobalConfig != nil {
pluginVersions = blocks.GlobalConfig.PluginVersions
}

runner, stdDiag := runner.Load(
runner.WithBuiltIn(
builtin.Plugin(version),
),
runner.WithPluginDir(pluginsDir),
runner.WithPluginVersions(pluginVersions),
)
if diags.ExtendHcl(stdDiag) {
pd, diag := blocks.ParseDocument(doc)
if diags.Extend(diag) {
return
}
defer func() { diags.ExtendHcl(runner.Close()) }()

eval := parser.NewEvaluator(
parser.NewPluginCaller(runner),
blocks,
)
results, diag := eval.EvaluateDocument(doc)
results, diag = pd.Render(ctx, pluginCaller)
if diags.Extend(diag) {
return
}
Expand Down Expand Up @@ -101,7 +66,7 @@ var renderCmd = &cobra.Command{
Short: "Render the document",
Long: `Render the specified document into Markdown and output it either to stdout or to a file`,
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) (err error) {
RunE: func(cmd *cobra.Command, args []string) (err error) {
target := strings.TrimSpace(args[0])
const docPrefix = definitions.BlockKindDocument + "."
switch {
Expand All @@ -111,28 +76,36 @@ var renderCmd = &cobra.Command{
return fmt.Errorf("target should have the format '%s<name_of_the_document>'", docPrefix)
}

var dest *os.File
if outFile == "" {
dest = os.Stdout
} else {
dest := os.Stdout
if outFile != "" {
dest, err = os.Create(outFile)
if err != nil {
return fmt.Errorf("can't create the out-file: %w", err)
}
defer dest.Close()
}
res, fileMap, diags := Render(cliArgs.pluginsDir, os.DirFS(cliArgs.sourceDir), target)
if !diags.HasErrors() {
diags.Extend(writeResults(dest, res))
}
diagnostics.PrintDiags(os.Stderr, diags, fileMap, cliArgs.colorize)

var diags diagnostics.Diag
eval := NewEvaluator(cliArgs.pluginsDir)
defer func() {
err = eval.Cleanup(diags)
}()
diags = eval.ParseFabricFiles(os.DirFS(cliArgs.sourceDir))
if diags.HasErrors() {
// Errors have been already displayed
rootCmd.SilenceErrors = true
rootCmd.SilenceUsage = true
return diags
return
}
return nil
diag := eval.LoadRunner()
if diags.Extend(diag) {
return
}
res, diag := Render(cmd.Context(), eval.Blocks, eval.PluginCaller(), target)
if diags.Extend(diag) {
return
}
diags.Extend(
writeResults(dest, res),
)
return
},
}

Expand Down
3 changes: 2 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/spf13/cobra"
"golang.org/x/term"

"github.com/blackstork-io/fabric/pkg/clicontext"
"github.com/blackstork-io/fabric/pkg/utils"
)

Expand Down Expand Up @@ -149,7 +150,7 @@ var rootCmd = &cobra.Command{
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
err := rootCmd.ExecuteContext(clicontext.New())
if err != nil {
os.Exit(1)
}
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ toolchain go1.21.1

require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2
github.com/elastic/go-elasticsearch/v8 v8.11.1
github.com/golang-cz/devslog v0.0.8
github.com/google/go-github/v58 v58.0.0
Expand Down Expand Up @@ -54,14 +55,15 @@ require (
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/elastic/elastic-transport-go/v8 v8.3.0 // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/timefmt-go v0.1.5 // indirect
github.com/klauspost/compress v1.17.0 // indirect
Expand Down
Loading
Loading