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

Async data & content evaluation #273

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
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
42 changes: 42 additions & 0 deletions docs/plugins/builtin/content-providers/sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: "`sleep` content provider"
plugin:
name: blackstork/builtin
description: "Sleeps for the specified duration. Useful for testing and debugging"
tags: ["debug"]
version: "v0.4.2"
source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
resource:
type: content-provider
type: docs
---

{{< breadcrumbs 2 >}}

{{< plugin-resource-header "blackstork/builtin" "builtin" "v0.4.2" "sleep" "content provider" >}}

## Description
Sleeps for the specified duration. Useful for testing and debugging.

The content provider is built-in, which means it's a part of `fabric` binary. It's available out-of-the-box, no installation required.


#### Configuration

The content provider doesn't support any configuration arguments.

#### Usage

The content provider supports the following execution arguments:

```hcl
content sleep {
# Duration to sleep
#
# Optional string.
# Must be non-empty
# Default value:
duration = "1s"
}
```

40 changes: 40 additions & 0 deletions docs/plugins/builtin/data-sources/sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
title: "`sleep` data source"
plugin:
name: blackstork/builtin
description: "Sleeps for the specified duration. Useful for testing and debugging"
tags: ["debug"]
version: "v0.4.2"
source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
resource:
type: data-source
type: docs
---

{{< breadcrumbs 2 >}}

{{< plugin-resource-header "blackstork/builtin" "builtin" "v0.4.2" "sleep" "data source" >}}

## Description
Sleeps for the specified duration. Useful for testing and debugging.

The data source is built-in, which means it's a part of `fabric` binary. It's available out-of-the-box, no installation required.

## Configuration

The data source doesn't support any configuration arguments.

## Usage

The data source supports the following execution arguments:

```hcl
data sleep {
# Duration to sleep
#
# Optional string.
# Must be non-empty
# Default value:
duration = "1s"
}
```
14 changes: 14 additions & 0 deletions docs/plugins/plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,20 @@
"url"
]
},
{
"name": "sleep",
"type": "data-source",
"arguments": [
"duration"
]
},
{
"name": "sleep",
"type": "content-provider",
"arguments": [
"duration"
]
},
{
"name": "table",
"type": "content-provider",
Expand Down
12 changes: 7 additions & 5 deletions engine/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ type Options struct {
tracer trace.Tracer
}

var defaultLogger = slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{
Level: slog.LevelError,
}))

var defaultOptions = Options{
registryBaseURL: defaultRegistryBaseURL,
cacheDir: defaultCacheDir,
logger: slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{
Level: slog.LevelError,
})),
tracer: nooptrace.Tracer{},
builtin: builtin.Plugin("v0.0.0", nil, nil),
logger: defaultLogger,
tracer: nooptrace.Tracer{},
builtin: builtin.Plugin("v0.0.0", defaultLogger, nil),
}

type Option func(*Options)
Expand Down
74 changes: 12 additions & 62 deletions eval/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package eval
import (
"context"
"log/slog"
"maps"
"slices"

"github.com/hashicorp/hcl/v2"
Expand All @@ -25,33 +24,8 @@ type Document struct {
}

func (doc *Document) FetchData(ctx context.Context) (plugindata.Data, diagnostics.Diag) {
logger := *slog.Default()
logger.DebugContext(ctx, "Fetching data for the document template")
result := make(plugindata.Map)
diags := diagnostics.Diag{}
for _, block := range doc.DataBlocks {
var dsMap plugindata.Map
found, ok := result[block.PluginName]
if ok {
dsMap = found.(plugindata.Map)
} else {
dsMap = make(plugindata.Map)
result[block.PluginName] = dsMap
}
if _, found := dsMap[block.BlockName]; found {
diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Data conflict",
Detail: "Result of this block overwrites results from the previous invocation.",
Subject: &block.SrcRange,
})
}
dsMap[block.BlockName], diags = block.FetchData(ctx)
if diags.HasErrors() {
return nil, diags
}
}
return result, diags
evaluator := makeAsyncDataEvaluator(ctx, doc, slog.Default())
return evaluator.Execute()
}

func filterChildrenByTags(children []*Content, requiredTags []string) []*Content {
Expand All @@ -71,8 +45,8 @@ func filterChildrenByTags(children []*Content, requiredTags []string) []*Content
}

func (doc *Document) RenderContent(ctx context.Context, docDataCtx plugindata.Map, requiredTags []string) (*plugin.ContentSection, plugindata.Data, diagnostics.Diag) {
logger := *slog.Default()
logger.DebugContext(ctx, "Fetching data for the document template")
logger := slog.Default()
logger.WarnContext(ctx, "Render content for the document template", "document", doc.Source.Name)
data, diags := doc.FetchData(ctx)
if diags.HasErrors() {
return nil, nil, diags
Expand Down Expand Up @@ -110,40 +84,16 @@ func (doc *Document) RenderContent(ctx context.Context, docDataCtx plugindata.Ma
children = filterChildrenByTags(children, requiredTags)
}

result := plugin.NewSection(0)
// create a position map for content blocks
posMap := make(map[int]uint32, len(children))
for i := range children {
empty := new(plugin.ContentEmpty)
result.Add(empty, nil)
posMap[i] = empty.ID()
}
// sort content blocks by invocation order
invokeList := make([]int, 0, len(children))
for i := range children {
invokeList = append(invokeList, i)
evaluator, diag := makeAsyncContentEvaluator(ctx, children)
if diags.Extend(diag) {
return nil, nil, diags
}
slices.SortStableFunc(invokeList, func(a, b int) int {
ao := children[a].InvocationOrder()
bo := children[b].InvocationOrder()
return ao.Weight() - bo.Weight()
})
// execute content blocks based on the invocation order
for _, idx := range invokeList {
// clone the data context for each content block
dataCtx := maps.Clone(docDataCtx)
// set the current content to the data context
dataCtx[definitions.BlockKindDocument].(plugindata.Map)[definitions.BlockKindContent] = result.AsData()
// TODO: if section, set section

// execute the content block
diag := children[idx].RenderContent(ctx, dataCtx, result, result, posMap[idx])
if diags.Extend(diag) {
return nil, nil, diags
}

result, diag := evaluator.Execute(docDataCtx)
if diags.Extend(diag) {
return nil, nil, diags
}
// compact the content tree to remove empty content nodes
result.Compact()

return result, docDataCtx, diags
}

Expand Down
Loading
Loading