From 899d75d994fb7a0d234d40161be738f89b156030 Mon Sep 17 00:00:00 2001 From: Tobias Salzmann <796084+Eun@users.noreply.github.com> Date: Thu, 5 Aug 2021 16:35:31 +0200 Subject: [PATCH] feat: multipart code (:warning: POSSIBLE BREAKING) (#40) --- template.go | 46 +++++++++++++++++++--------- template_test.go | 80 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 94 insertions(+), 32 deletions(-) diff --git a/template.go b/template.go index 50c96da..b89f400 100644 --- a/template.go +++ b/template.go @@ -44,8 +44,10 @@ package yaegi_template import ( + "bufio" "io" "os" + "strconv" "strings" "github.com/pkg/errors" @@ -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. diff --git a/template_test.go b/template_test.go index b29043f..057abe3 100644 --- a/template_test.go +++ b/template_test.go @@ -39,22 +39,6 @@ func TestExec(t *testing.T) { `Hello Yaegi`, "", }, - { - "Func", - interp.Options{}, - []interp.Exports{stdlib.Symbols}, - []Import{{Name: "", Path: "fmt"}}, - `<$func Foo(text string) { - fmt.Printf("Hello %s", text) -}$> -
<$Foo("Yaegi")$>
-`, - ` -Hello Yaegi
-`, - "", - }, - { "Error", interp.Options{}, @@ -62,7 +46,7 @@ func TestExec(t *testing.T) { nil, `<$ Hello $>`, "", - `1:29: undefined: Hello`, + "error during execution of\n1\t Hello \n: 1:29: undefined: Hello", }, { "Import", @@ -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) { @@ -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(`