Skip to content
This repository has been archived by the owner on Jul 11, 2023. It is now read-only.

Commit

Permalink
feat: multipart code (:warning: POSSIBLE BREAKING) (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
Eun authored Aug 5, 2021
1 parent 78a3fa4 commit 899d75d
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 32 deletions.
46 changes: 32 additions & 14 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@
package yaegi_template

import (
"bufio"
"io"
"os"
"strconv"
"strings"

"github.com/pkg/errors"
Expand Down Expand Up @@ -233,34 +235,50 @@ func (t *Template) Exec(writer io.Writer, context interface{}) (int, error) {
return 0, err
}

total := 0
var buf bytes.Buffer

for it.Next() {
part := it.Value()
switch part.Type {
case codebuffer.CodePartType:
n, err := t.execCode(string(part.Content), writer, context)
if err != nil {
return total, err
if _, err := buf.Write(part.Content); err != nil {
return 0, errors.Wrap(err, "unable to write code part")
}
if n > 0 {
total += n
if _, err := buf.WriteRune('\n'); err != nil {
return 0, errors.Wrap(err, "unable to write code part")
}
case codebuffer.TextPartType:
var n int
if writer != nil {
n, err = writer.Write(part.Content)
if _, err := buf.WriteString("print("); err != nil {
return 0, errors.Wrap(err, "unable to write text part")
}
if err != nil {
return total, err
if _, err := buf.WriteString(strconv.Quote(string(part.Content))); err != nil {
return 0, errors.Wrap(err, "unable to write text part")
}
if n > 0 {
total += n
if _, err := buf.WriteString(")\n"); err != nil {
return 0, errors.Wrap(err, "unable to write text part")
}
}
}
if err := it.Error(); err != nil {
return 0, err
}

return total, it.Error()
n, err := t.execCode(buf.String(), writer, context)
if err != nil {
var errWriter strings.Builder
scnr := bufio.NewScanner(&buf)
i := 1
for scnr.Scan() {
fmt.Fprintf(&errWriter, "%d\t%s\n", i, scnr.Text())
i++
}
if err := scnr.Err(); err != nil {
return 0, errors.Wrap(err, "unable to scan source")
}

return 0, errors.Wrapf(err, "error during execution of\n%s", errWriter.String())
}
return n, nil
}

// MustExec is like Exec, except it panics on failure.
Expand Down
80 changes: 62 additions & 18 deletions template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,30 +39,14 @@ func TestExec(t *testing.T) {
`<html>Hello Yaegi</html>`,
"",
},
{
"Func",
interp.Options{},
[]interp.Exports{stdlib.Symbols},
[]Import{{Name: "", Path: "fmt"}},
`<html><$func Foo(text string) {
fmt.Printf("Hello %s", text)
}$>
<p><$Foo("Yaegi")$></p>
</html>`,
`<html>
<p>Hello Yaegi</p>
</html>`,
"",
},

{
"Error",
interp.Options{},
[]interp.Exports{stdlib.Symbols},
nil,
`<$ Hello $>`,
"",
`1:29: undefined: Hello`,
"error during execution of\n1\t Hello \n: 1:29: undefined: Hello",
},
{
"Import",
Expand Down Expand Up @@ -282,7 +266,7 @@ func TestPanic(t *testing.T) {
MustParseString(`<$panic("Oh no")$>`)
var buf bytes.Buffer
_, err := template.Exec(&buf, nil)
require.EqualError(t, err, "Oh no")
require.EqualError(t, err, "error during execution of\n1\tpanic(\"Oh no\")\n: Oh no")
}

func TestNoStartOrEnd(t *testing.T) {
Expand Down Expand Up @@ -687,3 +671,63 @@ func TestTemplateWithAdditionalSymbols(t *testing.T) {
})
})
}

func TestMultiParts(t *testing.T) {
t.Run("single line", func(t *testing.T) {
template := MustNew(interp.Options{}, stdlib.Symbols).
MustParseString(`Hello <$ if context.Name == "" { $>Unknown<$ } else { print(context.Name) } $>`)

type Context struct {
Name string
}

var buf bytes.Buffer
template.MustExec(&buf, Context{Name: "Joe"})
require.Equal(t, "Hello Joe", buf.String())
buf.Reset()
template.MustExec(&buf, Context{Name: ""})
require.Equal(t, "Hello Unknown", buf.String())
})

t.Run("multi line", func(t *testing.T) {
template := MustNew(interp.Options{}, stdlib.Symbols).
MustParseString(`Hello
<$-
print(" ")
if context.Name == "" { -$>
Unknown
<$- } else {
print(context.Name)
} -$>`)

type Context struct {
Name string
}

var buf bytes.Buffer
template.MustExec(&buf, Context{Name: "Joe"})
require.Equal(t, "Hello Joe", buf.String())
buf.Reset()
template.MustExec(&buf, Context{Name: ""})
require.Equal(t, "Hello Unknown", buf.String())
})

t.Run("multi line - html", func(t *testing.T) {
template := MustNew(interp.Options{}, stdlib.Symbols).
MustParseString(`<ul>
<$-
for _, name := range context.Names {
$><li><$ print(name) $></li><$
}
-$>
</ul>`)

type Context struct {
Names []string
}

var buf bytes.Buffer
template.MustExec(&buf, Context{Names: []string{"Alice", "Joe"}})
require.Equal(t, "<ul><li>Alice</li><li>Joe</li></ul>", buf.String())
})
}

0 comments on commit 899d75d

Please sign in to comment.