Skip to content

Commit

Permalink
Multiple transclusions (#14)
Browse files Browse the repository at this point in the history
* feat: mulitple transclusions

Closes #4

* Update docs
  • Loading branch information
gamebox authored Jan 7, 2024
1 parent b065ac2 commit 0b6a865
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 58 deletions.
51 changes: 45 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,50 @@ be rendered as a string directly into the content using the `Stringer`
interface. Use the `@!` syntax to render content with escaping that is
appropriate to the content's filetype.

#### Transclusions

There are cases of using another template where you want to pass actual template
content to the other template, like a base or layout template. You can do that
in Gwirl using _transclusions_:

```html
@()

@Layout() {
<div>
@* The rest of your page *@
</div>
}
```

And sometimes a template will have multiple slots for content, and Gwirl can
handle that with multiple transclusions.

Given a `card.html.gwirl`:

```html
@(title string, bodyContent string, headerContent string)

@* ... *@
```

You can have another template call it like below:

```html
@()

@* ... *@

@Card("title") { @* slot: body *@
<p>This is content in the card</p>
} { @* slot: footer *@
<button>Card action</button>
}
```

> [!NOTE]
> Each transclusion block can be separated by any number of spaces, but no newlines.
### Imports

```gwirl
Expand All @@ -229,13 +273,8 @@ and other functionality.

### If/Else if/Else statements

> [!NOTE]
> Currently only `if` is supported, `if else` and `else` will be supported as part of
[#11](https://github.com/gamebox/gwirl/issues/11).


```html
<section @if showBorders {class="b-w-sm b-"}>
<section @if showBorders {class="b-w-sm b-"} @else {class=""}>
```

Conditionally render content using `@if` the same way you would in Go, anything
Expand Down
9 changes: 9 additions & 0 deletions gwirl-example/templates/card.html.gwirl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@(title string, bodyContent string, footerContent string)

<div class="card">
@if title == "" {<div class="header">
<h2>@title</h2>
</div>}
<div class="body">@bodyContent</div>
<div class="footer">@footerContent</div>
</div>
6 changes: 6 additions & 0 deletions gwirl-example/templates/testAll.html.gwirl
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,10 @@
} @else {
<h2>@name</h2>
}

@Card("title") {
<p>This is content in the card</p>
} {
<button>Card action</button>
}
</div>
23 changes: 11 additions & 12 deletions gwirl-lsp/semantic_tokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ func TestCreateSemanticTokensForTemplate(t *testing.T) {
}
checkTokens(tokens, expected, t)
tokens = absTokensForContent(res.Template.Content)
t.Logf("tokens: %v", tokens)
}

func checkTokens(received []absToken, expected []absToken, t *testing.T) {
Expand All @@ -88,15 +87,15 @@ func checkTokens(received []absToken, expected []absToken, t *testing.T) {
}

func TestSemanticTokensFromAbsTokens(t *testing.T) {
tokens := []absToken{
{0, 0, 36, protocol.SemanticTokenComment},
{1, 0, 35, protocol.SemanticTokenComment},
{2, 0, 36, protocol.SemanticTokenComment},
{3, 2, 12, protocol.SemanticTokenParameter},
}
res := semanticTokensDataFromAbsTokens(tokens)
for i := 0; i < (len(res) / 5); i += 1 {
t.Logf("Token %d: %v", i, res[5*i:5*i+4])
}
t.Logf("Result: %v", res)
// tokens := []absToken{
// {0, 0, 36, protocol.SemanticTokenComment},
// {1, 0, 35, protocol.SemanticTokenComment},
// {2, 0, 36, protocol.SemanticTokenComment},
// {3, 2, 12, protocol.SemanticTokenParameter},
// }
// res := semanticTokensDataFromAbsTokens(tokens)
// for i := 0; i < (len(res) / 5); i += 1 {
// t.Logf("Token %d: %v", i, res[5*i:5*i+4])
// }
// t.Logf("Result: %v", res)
}
10 changes: 4 additions & 6 deletions gwirl-lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,12 +531,10 @@ func (s *GwirlLspServer) DidChange(ctx context.Context, params *lsp.DidChangeTex
diagnostics[i] = d
}

if len(diagnostics) > 0 {
s.sendNotification("textDocument/publishDiagnostics", lsp.PublishDiagnosticsParams{
Diagnostics: diagnostics,
URI: params.TextDocument.URI,
})
}
s.sendNotification("textDocument/publishDiagnostics", lsp.PublishDiagnosticsParams{
Diagnostics: diagnostics,
URI: params.TextDocument.URI,
})

return nil
}
Expand Down
40 changes: 24 additions & 16 deletions internal/gen/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,33 +127,41 @@ func (G *Generator) GenTemplateTree(tree parser.TemplateTree2) error {
G.newlines()
case parser.TT2GoExp:
if len(tree.Children) > 0 {
varName := fmt.Sprintf("transclusion__%d__%d", tree.Line(), tree.Column())
G.write("var ")
G.writeNoIndent(varName)
G.writeNoIndent(" string\n")
G.write("{\n")
G.indent()
G.write("sb_ := gwirl.TemplateBuilder{}\n")
for _, child := range tree.Children[0] {
G.GenTemplateTree(child)
}
G.write(varName)
G.writeNoIndent(" = sb_.String()\n")
G.dedent()
G.write("}\n")
transclusionParams := strings.Builder{}
for i, transclusion := range tree.Children {
varName := fmt.Sprintf("transclusion__%d__%d__%d", tree.Line(), tree.Column(), i)
if i > 0 {
transclusionParams.WriteString(", ")
}
transclusionParams.WriteString(varName)
G.write("var ")
G.writeNoIndent(varName)
G.writeNoIndent(" string\n")
G.write("{\n")
G.indent()
G.write("sb_ := gwirl.TemplateBuilder{}\n")
for _, child := range transclusion {
G.GenTemplateTree(child)
}
G.write(varName)
G.writeNoIndent(" = sb_.String()\n")
G.dedent()
G.write("}\n")
}
if tree.Metadata.Has(parser.TTMDEscape) {
fmt.Printf("GoExp %v\n", tree)
G.write("gwirl.WriteEscapedHTML(&sb_, ")
} else {
G.write("sb_.WriteString(")
}
transclusionParamsStr := transclusionParams.String()
if strings.HasSuffix(tree.Text, "()") {
text, _ := strings.CutSuffix(tree.Text, ")")
text = text + varName + ")"
text = text + transclusionParamsStr + ")"
G.writeNoIndent(text)
} else if strings.HasSuffix(tree.Text, ")") {
text, _ := strings.CutSuffix(tree.Text, ")")
text = text + ", " + varName + ")"
text = text + ", " + transclusionParamsStr + ")"
G.writeNoIndent(text)
} else {
return errors.New("Transclusion can only occur with a method call")
Expand Down
29 changes: 27 additions & 2 deletions internal/gen/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,26 @@ var simple string
//go:embed testdata/testAll_gwirl.go
var testAll string

type SimplePosition struct {
line int
column int
}
func (p SimplePosition) Line() int {
return p.line
}
func (p SimplePosition) Column() int {
return p.column
}

func ptr(t parser.TemplateTree2) *parser.TemplateTree2 {
return &t
}

func withPos(t *parser.TemplateTree2, pos SimplePosition) parser.TemplateTree2 {
t.SetPos(pos)
return *t
}

var tests = []struct {
filename string
template parser.Template2
Expand Down Expand Up @@ -73,9 +89,18 @@ var tests = []struct {
}),
}, ptr(parser.NewTT2Else([]parser.TemplateTree2{
parser.NewTT2Plain("\n <h2>"),
parser.NewTT2GoExp("name", false, []parser.TemplateTree2{}),
parser.NewTT2GoExp("name", false, [][]parser.TemplateTree2{}),
parser.NewTT2Plain("</h2>\n "),
}))),
parser.NewTT2Plain("\n\n "),
withPos(ptr(parser.NewTT2GoExp(`Card("title")`, false, [][]parser.TemplateTree2{
{
parser.NewTT2Plain("\n <p>This is content in the card</p>\n "),
},
{
parser.NewTT2Plain("\n <button>Card action</button>\n "),
},
})), SimplePosition{20, 5}),
parser.NewTT2Plain("\n</div>\n"),
},
),
Expand All @@ -90,7 +115,7 @@ func TestGenerator(t *testing.T) {
writer := strings.Builder{}
gen.Generate(test.template, "views", &writer)
if writer.String() != test.expected {
edits := myers.ComputeEdits(span.URI("testdata/simple_gwirl.go"), test.expected, writer.String())
edits := myers.ComputeEdits(span.URI(test.filename), test.expected, writer.String())
diff := gotextdiff.ToUnified("expected", "received", test.expected, edits)
t.Fatalf("Generated template did not match golden:\n%s", diff)
}
Expand Down
25 changes: 25 additions & 0 deletions internal/gen/testdata/testAll_gwirl.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,31 @@ func TestAll(name string, index int) string {

}

sb_.WriteString(`
`)

var transclusion__20__5__0 string
{
sb_ := gwirl.TemplateBuilder{}
sb_.WriteString(`
<p>This is content in the card</p>
`)

transclusion__20__5__0 = sb_.String()
}
var transclusion__20__5__1 string
{
sb_ := gwirl.TemplateBuilder{}
sb_.WriteString(`
<button>Card action</button>
`)

transclusion__20__5__1 = sb_.String()
}
sb_.WriteString(Card("title", transclusion__20__5__0, transclusion__20__5__1))


sb_.WriteString(`
</div>
`)
Expand Down
8 changes: 2 additions & 6 deletions internal/parser/nodesv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,7 @@ func NewTT2For(initialization string, blk []TemplateTree2) TemplateTree2 {
}
}

func NewTT2GoExp(content string, escape bool, transclusions []TemplateTree2) TemplateTree2 {
children := [][]TemplateTree2{}
if len(transclusions) > 0 {
children = append(children, transclusions)
}
func NewTT2GoExp(content string, escape bool, transclusions [][]TemplateTree2) TemplateTree2 {
var metadata MetadataFlag
if escape {
metadata.Set(TTMDEscape)
Expand All @@ -188,7 +184,7 @@ func NewTT2GoExp(content string, escape bool, transclusions []TemplateTree2) Tem
Type: TT2GoExp,
Text: content,
Metadata: metadata,
Children: children,
Children: transclusions,
}
}

Expand Down
1 change: 0 additions & 1 deletion internal/parser/parser2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,4 @@ func TestParseTestAll2(t *testing.T) {
t.Errorf("Expected no errors, found %d errors", len(result.Errors))
}
// TODO: Test content parsing better - maybe through parser/pretty print roundtrip?
t.Logf("%v", result.Template.Content)
}
25 changes: 16 additions & 9 deletions internal/parser/parserv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,21 +369,15 @@ func (p *Parser2) expression() *TemplateTree2 {
}

if !strings.HasSuffix(combinedExpression, ")") {
t := NewTT2GoExp(combinedExpression, escape, []TemplateTree2{})
t := NewTT2GoExp(combinedExpression, escape, [][]TemplateTree2{})
p.position(&t, pos)
return &t
}

blk := p.block()
if blk != nil {
t := NewTT2GoExp(combinedExpression, escape, *blk)
transclusions := p.multipleBlocks()
t := NewTT2GoExp(combinedExpression, escape, transclusions)
p.position(&t, pos)
return &t
} else {
t := NewTT2GoExp(combinedExpression, escape, []TemplateTree2{})
p.position(&t, pos)
return &t
}
}

func (p *Parser2) block() *[]TemplateTree2 {
Expand Down Expand Up @@ -417,6 +411,19 @@ func (p *Parser2) block() *[]TemplateTree2 {
return result
}

func (p *Parser2) multipleBlocks() [][]TemplateTree2 {
blocks := [][]TemplateTree2{}
for {
blk := p.block()
if blk != nil {
blocks = append(blocks, *blk)
} else {
break
}
}
return blocks
}

func (p *Parser2) expressionPart(blockArgsAllowed bool) *[]TemplateTree2 {
return p.block()
}
Expand Down

0 comments on commit 0b6a865

Please sign in to comment.