diff --git a/.gitignore b/.gitignore index 71ee55e..2cfc73f 100644 --- a/.gitignore +++ b/.gitignore @@ -149,3 +149,4 @@ $RECYCLE.BIN/ *.lnk # End of https://www.toptal.com/developers/gitignore/api/goland+all,windows,macos +/scratch/ diff --git a/.version b/.version index 5d7996f..3442262 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -VERSION=v0.5.0 +VERSION=v0.6.0 diff --git a/README.md b/README.md index 2db423f..d549c93 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# GoHT (Go Haml Templates) -A [Haml](http://haml.info/) template engine and file generation tool for Go. +# GoHT (Go HTML Templates) +A [Haml](http://haml.info/) and [Slim](https://slim-template.github.io/) template engine and file generation tool for Go. -![GoHT](docs/goht_header.png) +![GoHT](docs/goht_header_html.png) [![Go Report Card](https://goreportcard.com/badge/github.com/stackus/goht)](https://goreportcard.com/report/github.com/stackus/goht) [![](https://godoc.org/github.com/stackus/goht?status.svg)](https://pkg.go.dev/github.com/stackus/goht) @@ -11,6 +11,8 @@ A [Haml](http://haml.info/) template engine and file generation tool for Go. - [Quick Start](#quick-start) - [Supported Haml Syntax & Features](#supported-haml-syntax--features) - [Unsupported Haml Features](#unsupported-haml-features) +- [Supported Slim Syntax & Features](#supported-slim-syntax--features) + - [Unsupported Slim Features](#unsupported-slim-features) - [GoHT CLI](#goht-cli) - [IDE Support](#ide-support) - [LSP](#lsp) @@ -19,8 +21,8 @@ A [Haml](http://haml.info/) template engine and file generation tool for Go. - [Using GoHT with HTTP handlers](#using-goht-with-http-handlers) - [A big nod to Templ](#a-big-nod-to-templ) - [The GoHT template](#the-goht-template) -- [Haml Syntax](#haml-syntax) - - [GoHT and Haml differences](#goht-and-haml-differences) +- [GoHT Syntax](#goht-syntax) + - [GoHT template differences](#goht-template-differences) - [Go package and imports](#go-package-and-imports) - [Multiple templates per file](#multiple-templates-per-file) - [Doctypes](#doctypes) @@ -29,6 +31,7 @@ A [Haml](http://haml.info/) template engine and file generation tool for Go. - [Attributes](#attributes) - [Classes](#classes) - [Object References](#object-references) + - [Inlined Tags](#inlined-tags) - [Filters](#filters) - [Template nesting](#template-nesting) - [Contributing](#contributing) @@ -42,13 +45,13 @@ A [Haml](http://haml.info/) template engine and file generation tool for Go. - Easy nesting of templates ## Quick Start -First create a GoHT file, a file which mixes Go and Haml with a `.goht` extension: +First create a GoHT file, a file which mixes Go and Haml (and Slim!) with a `.goht` extension: ```haml package main var siteTitle = "GoHT" -@goht SiteLayout(pageTitle string) { +@haml SiteLayout(pageTitle string) { !!! %html{lang:"en"} %head @@ -59,9 +62,9 @@ var siteTitle = "GoHT" = @children } -@goht HomePage() { +@slim HomePage() { = @render SiteLayout("Home Page") - %p This is the home page for GoHT. + p This is the home page for GoHT. } ``` @@ -110,19 +113,39 @@ Which would serve the following HTML: - [x] Doctypes (`!!!`) - [x] Tags (`%tag`) - [x] Attributes (`{name: value}`) [(more info)](#attributes) -- [x] Classes and IDs (`.class` `#id`) [(more info)](#classes) +- [x] Classes and IDs (`.class`, `#id`) [(more info)](#classes) - [x] Object References (`[obj]`) [(more info)](#object-references) - [x] Unescaped Text (`!` `!=`) - [x] Comments (`/` `-#`) +- [x] Self-closing Tags (`%tag/`)] - [x] Inline Interpolation (`#{value}`) - [x] Inlining Code (`- code`) - [x] Rendering Code (`= code`) - [x] Filters (`:plain`, ...) [(more info)](#filters) -- [x] Whitespace Removal (`%tag>` `%tag<`) [(more info)](#whitespace-removal) +- [x] Whitespace Removal (`%tag>`, `%tag<`) [(more info)](#whitespace-removal) ### Unsupported Haml Features - [ ] Probably something I've missed, please raise an issue if you find something missing. +## Supported Slim Syntax & Features +- [x] Doctypes (`doctype`) +- [x] Tags (`tag`) +- [x] Attributes (`{name: value}`) [(more info)](#attributes) +- [x] Classes and IDs (`.class`, `#id`) [(more info)](#classes) +- [x] Inline Tags (`tag: othertag`) +- [x] Unescaped Text (`|`) +- [x] Comments (`/`, `/!`) +- [x] Self-closing Tags (`tag/`) +- [x] Inline Interpolation (`#{value}`) +- [x] Inlining Code (`- code`) +- [x] Rendering Code (`= code`, `== code`) +- [x] Filters (`:plain`, ...) [(more info)](#filters) +- [x] Long Statement wrapping (`\`), (`,`)] + +### Unsupported Slim Features + +- [ ] Whitespace Addition (`tag<` `tag>`) + ## GoHT CLI ### Installation @@ -149,6 +172,9 @@ goht generate --force See more options with `goht help generate` or `goht generate -h`. ## IDE Support + +> Note: The IDE extensions are being worked on to add the new Slim syntax highlighting. + ![vscode_ide_example.png](docs/vscode_ide_example.png) - VSCode [Extension](https://marketplace.visualstudio.com/items?itemName=stackus.goht-vscode) and code [repository](https://github.com/stackus/goht-vscode) - JetBrains (GoLand and others) [Plugin](https://plugins.jetbrains.com/plugin/23783-goht) and code [repository](https://github.com/stackus/goht-jetbrains) @@ -217,7 +243,7 @@ The second parameter passed into the `Render` method can be anything that implem such as a file or a buffer, or the `http.ResponseWriter` that you get from an HTTP handler. ### Using GoHT with HTTP handlers -Using the GoHT templates is made very easy. +Using the GoHT templates is made straightforward. ```go package main @@ -245,36 +271,48 @@ There are a number of examples showing various Haml and GoHT features in the [ex ### A big nod to Templ The way that you use GoHT is very similar to how you would use [Templ](https://templ.guide). This is no accident as I am a big fan of the work being done with that engine. -After getting the Haml properly lexed, and parsed, I did not want to reinvent the wheel and come up with a whole new rendering API. +After getting the Haml properly lexed and parsed, I did not want to reinvent the wheel and come up with a whole new rendering API. The API that Templ presents is nice and easy to use, so I decided to replicate it in GoHT. ## The GoHT template GoHT templates are files with the extension `.goht` that when processed will produce a matching Go file with the extension `.goht.go`. -In these files you are free to write any Go code that you wish, and then drop into Haml mode using the `@goht` directive. +In these files you are free to write any Go code that you wish, and then drop into Haml mode using the `@haml` directive. + +> Note: The original `@goht` directive is still supported for HAML templating, but it is deprecated and will be removed in a future version. The following starts the creation of a SiteLayout template: ```haml -@goht SiteLayout() { +@haml SiteLayout() { + +or + +@slim SiteLayout() { ``` GoHT templates are closed like Go functions, with a closing brace `}`. So a complete but empty example is this: ```haml -@goht SiteLayout() { +@haml SiteLayout() { +} + +or + +@slim SiteLayout() { } ``` -Inside the template you can use any Haml features, such as tags, attributes, classes, +Inside the templates you can use any Haml or Slim features, such as tags, attributes, classes, IDs, text, comments, interpolation, code inlining, code rendering, and filters. -## Haml Syntax +## GoHT Syntax The Haml syntax is documented at the [Haml](http://haml.info/) website. Please see that site or the [Haml Reference](https://haml.info/docs/yardoc/file.REFERENCE.html) for more information. +The Slim syntax is documented at the [Slim](https://slim-lang.com/) website. -GoHT has implemented the Haml syntax very closely. -So, if you are already familiar with Haml then you should be able to jump right in. +GoHT has implemented nearly all Haml and Slim syntax. +So, if you are already familiar with Haml or Slim then you should be able to jump right in. There are some minor differences that I will document in the next section. -### GoHT and Haml differences +### GoHT template differences Important differences are: - [Go package and imports](#go-package-and-imports): You can declare a package and imports for your templates. @@ -283,9 +321,9 @@ Important differences are: - [Indents](#indents): GoHT follows the rules of GoFMT for indents. - [Inlined code](#inlined-code): You won't be using Ruby here, you'll be using Go. - [Rendering code](#rendering-code): The catch is what is being outputted will need to be a string in all cases. -- [Attributes](#attributes): Only the Ruby 1.9 style of attributes is supported. +- [Attributes](#attributes): Only the Ruby 1.9 (`{...}`) style of attributes is supported. - [Classes](#classes): Multiple sources of classes are supported. -- [Object References](#object-references): Limited support for object references. +- [Object References](#object-references): Haml Only: Limited support for object references. - [Filters](#filters): Partial list of supported filters. - [Template nesting](#template-nesting): Templates can be nested, and content can be passed into them. @@ -295,19 +333,21 @@ You can provide a package name at the top of your GoHT template file. If you do You may also import any packages that you need to use in your template. The imports you use and the ones brought in by GoHT will be combined and deduplicated. ### Multiple templates per file -You can declare as many templates in a file as you wish. Each template must have a unique name in the module they will be output into. +You can declare as many templates in a file as you wish. +Each template must have a unique name in the module they will be output into. +You may also mix Haml and Slim templates in the same file. ```haml -@goht SiteLayout() { +@slim SiteLayout() { } -@goht HomePage() { +@haml HomePage() { } ``` The templates are converted into Go functions, so they must be valid Go function names. This also means that you can declare them with parameters and can use those parameters in the template. ```haml -@goht SiteLayout(title string) { +@haml SiteLayout(title string) { !!! %html{lang:"en"} %head @@ -316,23 +356,38 @@ This also means that you can declare them with parameters and can use those para -# ... the rest of the template } ``` +The same applies to Slim templates: +```slim +@slim SiteLayout(title string) { + doctype html + html(lang="en") + head + title= title + body + / ... the rest of the template +} +``` ### Doctypes Only the HTML 5 doctype is supported, and is written using `!!!`. ```haml -@goht SiteLayout() { +@haml SiteLayout() { !!! } + +@slim SiteLayout() { + doctype +} ``` -> Note about indenting. GoHT follows the similar rules as Haml for indenting. The first line of the template must be at the same level as the `@goht` directive. After that, you MUST use tabs to indent the content of the template. +> Note about indenting. GoHT follows the same rules as Haml for indenting. The first line of the template must be at the same level as the `@haml` or `@slim` directive. After that, you MUST use tabs to indent the content of the template. ### Indents GoHT follows the rules of GoFMT for indents, meaning that you should use tabs for indentation. -You must also indent the content of the template, and the closing brace should be at the same level as the `@goht` directive. +You must also indent the content of the template, and the closing brace should be at the same level as the `@haml` directive. ```haml -@goht SiteLayout() { +@haml SiteLayout() { %html %head %title GoHT @@ -341,6 +396,18 @@ You must also indent the content of the template, and the closing brace should b } ``` +Slim: +```slim +@slim SiteLayout() { + doctype + html + head + title GoHT + body + h1 GoHT +} +``` + > Note: Two spaces are being used in this README for display only. Keep that in mind if you copy and paste the examples from this document. ### Inlined code @@ -353,20 +420,26 @@ So instead of this with Ruby: - if user %strong The user exists ``` -You would write this with Go: +You would write this with Go (Go needs the `!= nil` check): ```haml - if user != nil %strong The user exists ``` There is minimal processing performed on the Go code you put into the templates, so it needs to be valid Go code sans braces. + > You may continue to use the braces if you wish. Existing code with braces will continue to work without modifications. ### Rendering code -Like in Haml, you can output variables and the results of expressions. The `=` script syntax and text interpolation `#{}` are supported. +Like in Haml, you can output variables and the results of expressions. The `=` script syntax and text interpolation `#{}` are supported for both template languages. ```haml %strong= user.Name %strong The user's name is #{user.Name} ``` +Slim: +```slim + strong= user.Name + strong The user's name is #{user.Name} +``` The catch is what is being outputted will need to be a string in all cases. So instead of writing this to output an integer value: @@ -388,7 +461,8 @@ The interpolation also supports the shortcut: When formatting a value into a string `fmt.Sprintf` is used under the hood, so you can use any of the formatting options that it supports. ### Attributes -Only the Ruby 1.9 style of attributes is supported. +**Only the Ruby 1.9 style of attributes is supported.** + This syntax is closest to the Go syntax, and is the most readable. Between the attribute name, operator, and value you can include or leave out as much whitespace as you like. ```haml @@ -472,15 +546,29 @@ type ObjectClasser interface { The result of these methods will be used to populate the id and class attributes in a similar way to how Haml would apply the Ruby object references. +### Inlined Tags +**Slim Only** + +GoHT supports inlining tags to keep templates as compact as possible. + +```slim + ul + li: a.First Fist Item + li: a.Second Second Item + li: a.Third Third Item +``` + ### Filters Only the following Haml filters are supported: -- `:plain` -- `:escaped` -- `:preserve` +- `:plain` (Haml Only) +- `:escaped` (Haml Only) +- `:preserve` (Haml Only) - `:javascript` - `:css` ### Whitespace Removal +**Haml Only** + GoHT supports the removal of whitespace between tags. This is done by adding a `>` or `<` to the end of the tag. - `>` will remove all whitespace between the tag and its parent or siblings. @@ -491,20 +579,20 @@ Both can be used together to remove whitespace both inside and outside a tag; th ### Template nesting The biggest departure from Haml is how templates can be combined. When working Haml you could use `= render :partial_name` or `= haml :partial_name` to render a partial. The `render` and `haml` functions are not available in GoHT, instead you can use the `@render` directive. ```haml -@goht HomePage() { +@haml HomePage() { = @render SiteLayout() } ``` The above would render the `SiteLayout` template, and you would call it with any parameters that it needs. You can also call it and provide it with a block of content to render where it chooses. ```haml -@goht HomePage() { +@haml HomePage() { = @render SiteLayout() %p This is the home page for GoHT. } ``` Any content nested under the `@render` directive will be passed into the template that it can render where it wants using the `@children` directive. ```haml -@goht SiteLayout() { +@haml SiteLayout() { !!! %html{lang:"en"} %head diff --git a/compiler/lexer.go b/compiler/lexer.go index 96d2162..b4c70cf 100644 --- a/compiler/lexer.go +++ b/compiler/lexer.go @@ -43,6 +43,7 @@ func (l *lexer) nextToken() token { } } +// next consumes the next rune from the input. func (l *lexer) next() rune { ch, size, err := l.reader.ReadRune() if err != nil { @@ -59,6 +60,7 @@ func (l *lexer) next() rune { return ch } +// backup steps back one rune. func (l *lexer) backup() { if l.width == 0 { return @@ -72,13 +74,15 @@ func (l *lexer) backup() { l.s = l.s[:len(l.s)-l.width] } +// peek returns the next rune without consuming it. func (l *lexer) peek() rune { ch := l.next() l.backup() return ch } -func (l *lexer) peekAhead(length int) (string, error) { +// peekAhead returns the next length runes without consuming them. +func (l *lexer) peekAhead(length int) string { width := 0 s := "" var err error @@ -92,39 +96,34 @@ func (l *lexer) peekAhead(length int) (string, error) { width += size s += string(ch) } - if err != nil && err != io.EOF { - return "", fmt.Errorf("failed to peek ahead: %v", err) - } - _, err = l.reader.Seek(int64(-width), io.SeekCurrent) - if err != nil { - return "", fmt.Errorf("failed to seek back: %v", err) - } - return s, nil + _, _ = l.reader.Seek(int64(-width), io.SeekCurrent) + return s } +// ignore discards the current captured string. func (l *lexer) ignore() { l.s = "" } -// accept consumes the next rune if it's contained in the valid string. -func (l *lexer) accept(valid string) bool { - if strings.ContainsRune(valid, l.next()) { +// accept consumes the next rune if it's contained in the acceptRunes list. +func (l *lexer) accept(acceptRunes string) bool { + if strings.ContainsRune(acceptRunes, l.next()) { return true } l.backup() return false } -// acceptRun consumes a run of runes from the valid string. -func (l *lexer) acceptRun(valid string) { - for strings.ContainsRune(valid, l.next()) { +// acceptRun consumes a run of runes from the acceptRunes list. +func (l *lexer) acceptRun(acceptRunes string) { + for strings.ContainsRune(acceptRunes, l.next()) { } l.backup() } -// acceptUntil consumes runes until it finds a rune in the invalid string. -func (l *lexer) acceptUntil(invalid string) { - for r := l.next(); !strings.ContainsRune(invalid, r) && r != scanner.EOF; r = l.next() { +// acceptUntil consumes runes until it encounters a rune in the stopRunes list. +func (l *lexer) acceptUntil(stopRunes string) { + for r := l.next(); !strings.ContainsRune(stopRunes, r) && r != scanner.EOF; r = l.next() { } l.backup() } @@ -136,24 +135,24 @@ func (l *lexer) acceptAhead(length int) { } } -// skip consumes the next rune and then discards it. +// skip discards the next rune. func (l *lexer) skip() rune { r := l.next() l.s = l.s[:len(l.s)-1] return r } -// skipRun consumes a run of runes from the skipList and discards them. -func (l *lexer) skipRun(skipList string) { - for strings.ContainsRune(skipList, l.next()) { +// skipRun discards a contiguous run of runes from the skipRunes list. +func (l *lexer) skipRun(skipRunes string) { + for strings.ContainsRune(skipRunes, l.next()) { l.s = l.s[:len(l.s)-1] } l.backup() } -// skipUntil consumes and discards runes until it finds a rune in the stopList. -func (l *lexer) skipUntil(stopList string) { - for r := l.next(); !strings.ContainsRune(stopList, r) && r != scanner.EOF; r = l.next() { +// skipUntil discards runes until it encounters a rune in the stopRunes list. +func (l *lexer) skipUntil(stopRunes string) { + for r := l.next(); !strings.ContainsRune(stopRunes, r) && r != scanner.EOF; r = l.next() { l.s = l.s[:len(l.s)-1] } l.backup() @@ -164,10 +163,11 @@ func (l *lexer) skipAhead(length int) { for i := 0; i < length; i++ { l.next() } - l.ignore() + l.s = l.s[:len(l.s)-length] + // l.ignore() } -// current returns the current string being built by the lexer. +// current returns the current captured string being built by the lexer. func (l *lexer) current() string { return l.s } @@ -186,7 +186,7 @@ func (l *lexer) errorf(format string, args ...any) lexFn { return func(l *lexer) lexFn { return nil } } -// position returns the current line and column of the file being lexed. +// position returns the current line and column of the content being lexed. func (l *lexer) position() (int, int) { newLinesInString := strings.Count(l.s, "\n") line := len(l.pos) - newLinesInString diff --git a/compiler/lexer_iface.go b/compiler/lexer_iface.go new file mode 100644 index 0000000..34b4413 --- /dev/null +++ b/compiler/lexer_iface.go @@ -0,0 +1,32 @@ +package compiler + +type ilexer interface { + // next consumes the next rune from the input. + next() rune + // backup steps back one rune. + backup() + // peek returns the next rune without consuming it. + peek() rune + // peekAhead returns the next length runes without consuming them. + peekAhead(length int) (string, error) + // ignore discards the current captured string. + ignore() + // accept consumes the next rune if it's contained in the acceptRunes list. + accept(acceptRunes string) bool + // acceptRun consumes a run of runes from the acceptRunes list. + acceptRun(acceptRunes string) + // acceptUntil consumes runes until it encounters a rune in the stopRunes list. + acceptUntil(stopRunes string) + // acceptAhead consumes the next length runes. + acceptAhead(length int) + // skip discards the next rune. + skip() rune + // skipRun discards a contiguous run of runes from the skipRunes list. + skipRun(skipRunes string) + // skipUntil discards runes until it encounters a rune in the stopRunes list. + skipUntil(stopRunes string) + // skipAhead consumes the next length runes and discards them. + skipAhead(length int) + // current returns the current captured string being built by the lexer. + current() string +} diff --git a/compiler/lexer_test.go b/compiler/lexer_test.go index 809885b..0518d2d 100644 --- a/compiler/lexer_test.go +++ b/compiler/lexer_test.go @@ -193,11 +193,7 @@ func Test_LexerPeekAhead(t *testing.T) { for i := 0; i < tt.next; i++ { l.next() } - got, err := l.peekAhead(tt.length) - if err != nil { - t.Errorf("peekAhead: %v", err) - return - } + got := l.peekAhead(tt.length) if got != tt.want { t.Errorf("peekAhead: want %v, got %v", tt.want, got) return @@ -411,8 +407,32 @@ func Test_GoTransition(t *testing.T) { {typ: tPackage, lit: "foo"}, {typ: tNewLine, lit: "\n"}, {typ: tNewLine, lit: "\n"}, - {typ: tGohtStart, lit: "test()"}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateStart, lit: "test()"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tNewLine, lit: "\n"}, + {typ: tEOF, lit: ""}, + }, + }, + "into haml": { + input: "package foo\n\n@haml test() {\n}\n", + want: []token{ + {typ: tPackage, lit: "foo"}, + {typ: tNewLine, lit: "\n"}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateStart, lit: "test()"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tNewLine, lit: "\n"}, + {typ: tEOF, lit: ""}, + }, + }, + "into slim": { + input: "package foo\n\n@slim test() {\n}\n", + want: []token{ + {typ: tPackage, lit: "foo"}, + {typ: tNewLine, lit: "\n"}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateStart, lit: "test()"}, + {typ: tTemplateEnd, lit: ""}, {typ: tNewLine, lit: "\n"}, {typ: tEOF, lit: ""}, }, @@ -436,11 +456,41 @@ func Test_GohtTransition(t *testing.T) { input string want []token }{ - "into go": { + "into go from goht": { input: "@goht test() {\n}\n\nfunc foo() {\n\tprintln(`bar`)\n}", want: []token{ - {typ: tGohtStart, lit: "test()"}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateStart, lit: "test()"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tNewLine, lit: "\n"}, + {typ: tNewLine, lit: "\n"}, + {typ: tGoCode, lit: "func foo() {"}, + {typ: tNewLine, lit: "\n"}, + {typ: tGoCode, lit: "\tprintln(`bar`)"}, + {typ: tNewLine, lit: "\n"}, + {typ: tGoCode, lit: "}"}, + {typ: tEOF, lit: ""}, + }, + }, + "into go from haml": { + input: "@haml test() {\n}\n\nfunc foo() {\n\tprintln(`bar`)\n}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tNewLine, lit: "\n"}, + {typ: tNewLine, lit: "\n"}, + {typ: tGoCode, lit: "func foo() {"}, + {typ: tNewLine, lit: "\n"}, + {typ: tGoCode, lit: "\tprintln(`bar`)"}, + {typ: tNewLine, lit: "\n"}, + {typ: tGoCode, lit: "}"}, + {typ: tEOF, lit: ""}, + }, + }, + "into go from slim": { + input: "@slim test() {\n}\n\nfunc foo() {\n\tprintln(`bar`)\n}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tTemplateEnd, lit: ""}, {typ: tNewLine, lit: "\n"}, {typ: tNewLine, lit: "\n"}, {typ: tGoCode, lit: "func foo() {"}, @@ -454,7 +504,7 @@ func Test_GohtTransition(t *testing.T) { "incomplete goht": { input: "@goht test() {\n", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tEOF, lit: ""}, }, }, @@ -480,7 +530,7 @@ func Test_HamlText(t *testing.T) { "simple": { input: "@goht test() {\n\tfoobar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tPlainText, lit: "foobar"}, {typ: tEOF, lit: ""}, @@ -489,7 +539,7 @@ func Test_HamlText(t *testing.T) { "multiple lines": { input: "@goht test() {\n\tfoobar\n\tbaz", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tPlainText, lit: "foobar"}, {typ: tNewLine, lit: "\n"}, @@ -501,7 +551,7 @@ func Test_HamlText(t *testing.T) { "with escaped quotes": { input: "@goht test() {\n\t\"foo\\\"bar\"", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tPlainText, lit: "\"foo\\\"bar\""}, {typ: tEOF, lit: ""}, @@ -510,7 +560,7 @@ func Test_HamlText(t *testing.T) { "escape control characters": { input: "@goht test() {\n\t\\#foo\n\t\\%bar\n\t\\.baz", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tPlainText, lit: "#foo"}, {typ: tNewLine, lit: "\n"}, @@ -524,7 +574,7 @@ func Test_HamlText(t *testing.T) { "text with dynamic value": { input: "@goht test() {\n\tfoo #{bar} baz", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tPlainText, lit: "foo "}, {typ: tDynamicText, lit: "bar"}, @@ -535,7 +585,7 @@ func Test_HamlText(t *testing.T) { "escape dynamic text at start of line": { input: "@goht test() {\n\t\\#{foo}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tDynamicText, lit: "foo"}, {typ: tEOF, lit: ""}, @@ -544,7 +594,7 @@ func Test_HamlText(t *testing.T) { "ignore dynamic text in line": { input: "@goht test() {\n\tfoo \\#{bar} \\{f} baz", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tPlainText, lit: "foo #{bar} \\{f} baz"}, {typ: tEOF, lit: ""}, @@ -553,7 +603,7 @@ func Test_HamlText(t *testing.T) { "error in dynamic syntax": { input: "@goht test() {\n\tfoo #{bar baz", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tPlainText, lit: "foo "}, {typ: tError, lit: "dynamic text value was not closed: eof"}, @@ -574,6 +624,159 @@ func Test_HamlText(t *testing.T) { } } +func Test_SlimText(t *testing.T) { + tests := map[string]struct { + input string + want []token + }{ + "simple": { + input: "@slim test() {\n\t|foobar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: "foobar"}, + {typ: tEOF, lit: ""}, + }, + }, + "simple with space": { + input: "@slim test() {\n\t| foobar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: "foobar"}, + {typ: tEOF, lit: ""}, + }, + }, + "simple with spaces": { + input: "@slim test() {\n\t| foobar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: " foobar"}, + {typ: tEOF, lit: ""}, + }, + }, + "simple with tab": { + input: "@slim test() {\n\t|\tfoobar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: "foobar"}, + {typ: tEOF, lit: ""}, + }, + }, + "simple with tabs": { + input: "@slim test() {\n\t|\t\tfoobar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: "\tfoobar"}, + {typ: tEOF, lit: ""}, + }, + }, + "multiple lines": { + input: "@slim test() {\n\t|foobar\n\t|baz", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: "foobar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: "baz"}, + {typ: tEOF, lit: ""}, + }, + }, + "multiple indented lines": { + input: "@slim test() {\n\t|foobar\n\t\tbaz", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: "foobar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tPlainText, lit: "baz"}, + {typ: tEOF, lit: ""}, + }, + }, + "multiple indented lines with space": { + input: "@slim test() {\n\t| foobar\n\t\t baz", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: "foobar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tPlainText, lit: "baz"}, + {typ: tEOF, lit: ""}, + }, + }, + "multiple indented lines with tab": { + input: "@slim test() {\n\t| foobar\n\t\t\tbaz", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: "foobar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tPlainText, lit: "baz"}, + {typ: tEOF, lit: ""}, + }, + }, + "multiple indented lines with spaces": { + input: "@slim test() {\n\t| foobar\n\t\t baz", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: "foobar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tPlainText, lit: " baz"}, + {typ: tEOF, lit: ""}, + }, + }, + "multiple indented lines with tabs and spaces": { + input: "@slim test() {\n\t| foobar\n\t\t\t baz", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: "foobar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tPlainText, lit: " baz"}, + {typ: tEOF, lit: ""}, + }, + }, + "text with dynamic value": { + input: "@slim test() {\n\t|foo #{bar} baz", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: "foo "}, + {typ: tDynamicText, lit: "bar"}, + {typ: tPlainText, lit: " baz"}, + {typ: tEOF, lit: ""}, + }, + }, + "text and close": { + input: "@slim test() {\n\t|foo bar\n}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tPlainText, lit: "foo bar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tEOF, lit: ""}, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + l := newLexer([]byte(tt.input)) + for _, want := range tt.want { + got := l.nextToken() + if got.typ != want.typ || got.lit != want.lit { + t.Errorf("want %v, got %v", want, got) + } + } + }) + } +} + func Test_HamlTag(t *testing.T) { tests := map[string]struct { input string @@ -582,7 +785,7 @@ func Test_HamlTag(t *testing.T) { "simple": { input: "@goht test() {\n\t%foo\n", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tNewLine, lit: "\n"}, @@ -592,7 +795,7 @@ func Test_HamlTag(t *testing.T) { "multiple tags": { input: "@goht test() {\n\t%foo\n\t%bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tNewLine, lit: "\n"}, @@ -604,7 +807,7 @@ func Test_HamlTag(t *testing.T) { "tag and id": { input: "@goht test() {\n\t%foo#bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tId, lit: "bar"}, @@ -614,7 +817,7 @@ func Test_HamlTag(t *testing.T) { "tag and class": { input: "@goht test() {\n\t%foo.bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tClass, lit: "bar"}, @@ -624,7 +827,7 @@ func Test_HamlTag(t *testing.T) { "tag and attribute": { input: "@goht test() {\n\t%foo{id:\"bar\"}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "id"}, @@ -636,7 +839,7 @@ func Test_HamlTag(t *testing.T) { "tag and text": { input: "@goht test() {\n\t%foo bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tPlainText, lit: "bar"}, @@ -646,18 +849,18 @@ func Test_HamlTag(t *testing.T) { "tag and text and close": { input: "@goht test() {\n\t%foo bar\n}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tPlainText, lit: "bar"}, {typ: tNewLine, lit: "\n"}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, "tag and unescaped text": { input: "@goht test() {\n\t%foo! bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tUnescaped, lit: ""}, @@ -668,7 +871,7 @@ func Test_HamlTag(t *testing.T) { "tag and output code": { input: "@goht test() {\n\t%foo= bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tScript, lit: "bar"}, @@ -678,7 +881,7 @@ func Test_HamlTag(t *testing.T) { "tag and tag again": { input: "@goht test() {\n\t%foo%bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tPlainText, lit: "%bar"}, @@ -688,7 +891,7 @@ func Test_HamlTag(t *testing.T) { "space before tag identifier": { input: "@goht test() {\n\t% foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tError, lit: "Tag identifier expected"}, {typ: tEOF, lit: ""}, @@ -708,133 +911,154 @@ func Test_HamlTag(t *testing.T) { } } -func Test_HamlId(t *testing.T) { +func Test_SlimTag(t *testing.T) { tests := map[string]struct { input string want []token }{ "simple": { - input: "@goht test() {\n\t#foo", + input: "@slim test() {\n\tfoo\n", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tId, lit: "foo"}, + {typ: tTag, lit: "foo"}, + {typ: tNewLine, lit: "\n"}, {typ: tEOF, lit: ""}, }, }, - "multiple ids": { - input: "@goht test() {\n\t#foo\n\t#bar", + "multiple tags": { + input: "@slim test() {\n\tfoo\n\tbar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tId, lit: "foo"}, + {typ: tTag, lit: "foo"}, {typ: tNewLine, lit: "\n"}, {typ: tIndent, lit: "\t"}, - {typ: tId, lit: "bar"}, + {typ: tTag, lit: "bar"}, {typ: tEOF, lit: ""}, }, }, - "with underscore": { - input: "@goht test() {\n\t#foo_bar\n}", + "tag and id": { + input: "@slim test() {\n\tfoo#bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tId, lit: "foo_bar"}, - {typ: tNewLine, lit: "\n"}, - {typ: tGohtEnd, lit: ""}, + {typ: tTag, lit: "foo"}, + {typ: tId, lit: "bar"}, {typ: tEOF, lit: ""}, }, }, - "with hyphen": { - input: "@goht test() {\n\t#foo-bar\n}", + "tag and class": { + input: "@slim test() {\n\tfoo.bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tId, lit: "foo-bar"}, - {typ: tNewLine, lit: "\n"}, - {typ: tGohtEnd, lit: ""}, + {typ: tTag, lit: "foo"}, + {typ: tClass, lit: "bar"}, {typ: tEOF, lit: ""}, }, }, - "id and class": { - input: "@goht test() {\n\t#foo.bar", + "tag and classes": { + input: "@slim test() {\n\tfoo.bar.baz", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tId, lit: "foo"}, + {typ: tTag, lit: "foo"}, {typ: tClass, lit: "bar"}, + {typ: tClass, lit: "baz"}, {typ: tEOF, lit: ""}, }, }, - "id and tag": { - input: "@goht test() {\n\t#foo%bar", + "tag and attribute": { + input: "@slim test() {\n\tfoo{id:\"bar\"}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tId, lit: "foo"}, - {typ: tPlainText, lit: "%bar"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, {typ: tEOF, lit: ""}, }, }, - "id and attribute": { - input: "@goht test() {\n\t#foo{id:\"bar\"}", + "tag and text": { + input: "@slim test() {\n\tfoo bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tId, lit: "foo"}, - {typ: tAttrName, lit: "id"}, - {typ: tAttrOperator, lit: ":"}, - {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tTag, lit: "foo"}, + {typ: tPlainText, lit: "bar"}, {typ: tEOF, lit: ""}, }, }, - "id and text": { - input: "@goht test() {\n\t#foo bar", + "tag and text and close": { + input: "@slim test() {\n\tfoo bar\n}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tId, lit: "foo"}, + {typ: tTag, lit: "foo"}, {typ: tPlainText, lit: "bar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, - "id and unescaped text": { - input: "@goht test() {\n\t#foo! bar", + "tag and multiple text lines": { + input: "@slim test() {\n\tfoo bar\n\t\tbaz", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tId, lit: "foo"}, - {typ: tUnescaped, lit: ""}, + {typ: tTag, lit: "foo"}, {typ: tPlainText, lit: "bar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tPlainText, lit: "baz"}, {typ: tEOF, lit: ""}, }, }, - "id and output code": { - input: "@goht test() {\n\t#foo= bar", + "tag and interpolation": { + input: "@slim test() {\n\tfoo #{bar} baz", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tId, lit: "foo"}, - {typ: tScript, lit: "bar"}, + {typ: tTag, lit: "foo"}, + {typ: tDynamicText, lit: "bar"}, + {typ: tPlainText, lit: " baz"}, {typ: tEOF, lit: ""}, }, }, - "id and id again": { - input: "@goht test() {\n\t#foo#bar", + "tag and multiline interpolation": { + input: "@slim test() {\n\tfoo bar\n\t\t#{baz} qux", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tId, lit: "foo"}, - {typ: tId, lit: "bar"}, + {typ: tTag, lit: "foo"}, + {typ: tPlainText, lit: "bar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tDynamicText, lit: "baz"}, + {typ: tPlainText, lit: " qux"}, {typ: tEOF, lit: ""}, }, }, - "space before id identifier": { - input: "@goht test() {\n\t# foo", + "tag and output code": { + input: "@slim test() {\n\tfoo= bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tError, lit: "Id identifier expected"}, + {typ: tTag, lit: "foo"}, + {typ: tScript, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "inlined tag": { + input: "@slim test() {\n\tfoo: a.bar\n}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tTag, lit: "a"}, + {typ: tClass, lit: "bar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, @@ -852,36 +1076,304 @@ func Test_HamlId(t *testing.T) { } } -func Test_HamlClass(t *testing.T) { +func Test_HamlId(t *testing.T) { tests := map[string]struct { input string want []token }{ "simple": { - input: "@goht test() {\n\t.foo", + input: "@goht test() {\n\t#foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tClass, lit: "foo"}, + {typ: tId, lit: "foo"}, {typ: tEOF, lit: ""}, }, }, "multiple ids": { - input: "@goht test() {\n\t.foo\n\t.bar", + input: "@goht test() {\n\t#foo\n\t#bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tClass, lit: "foo"}, + {typ: tId, lit: "foo"}, {typ: tNewLine, lit: "\n"}, {typ: tIndent, lit: "\t"}, - {typ: tClass, lit: "bar"}, + {typ: tId, lit: "bar"}, {typ: tEOF, lit: ""}, }, }, - "class and id": { - input: "@goht test() {\n\t.foo#bar", + "with underscore": { + input: "@goht test() {\n\t#foo_bar\n}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo_bar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tEOF, lit: ""}, + }, + }, + "with hyphen": { + input: "@goht test() {\n\t#foo-bar\n}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo-bar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tEOF, lit: ""}, + }, + }, + "id and class": { + input: "@goht test() {\n\t#foo.bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tClass, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "id and tag": { + input: "@goht test() {\n\t#foo%bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tPlainText, lit: "%bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "id and attribute": { + input: "@goht test() {\n\t#foo{id:\"bar\"}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tEOF, lit: ""}, + }, + }, + "id and text": { + input: "@goht test() {\n\t#foo bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tPlainText, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "id and unescaped text": { + input: "@goht test() {\n\t#foo! bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tUnescaped, lit: ""}, + {typ: tPlainText, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "id and output code": { + input: "@goht test() {\n\t#foo= bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tScript, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "id and id again": { + input: "@goht test() {\n\t#foo#bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tId, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "space before id identifier": { + input: "@goht test() {\n\t# foo", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tError, lit: "Id identifier expected"}, + {typ: tEOF, lit: ""}, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + l := newLexer([]byte(tt.input)) + for _, want := range tt.want { + got := l.nextToken() + if got.typ != want.typ || got.lit != want.lit { + t.Errorf("want %v, got %v", want, got) + } + } + }) + } +} + +func Test_SlimId(t *testing.T) { + tests := map[string]struct { + input string + want []token + }{ + "simple": { + input: "@slim test() {\n\t#bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "multiple ids": { + input: "@slim test() {\n\t#foo\n\t#bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tNewLine, lit: "\n"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "with underscore": { + input: "@slim test() {\n\t#foo_bar\n}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo_bar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tEOF, lit: ""}, + }, + }, + "with hyphen": { + input: "@slim test() {\n\t#foo-bar\n}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo-bar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tEOF, lit: ""}, + }, + }, + "id and class": { + input: "@slim test() {\n\t#foo.bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tClass, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "id and attribute": { + input: "@slim test() {\n\t#foo{id:\"bar\"}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tEOF, lit: ""}, + }, + }, + "id and text": { + input: "@slim test() {\n\t#foo bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tPlainText, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "id and interpolation": { + input: "@slim test() {\n\t#foo #{bar} baz", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tDynamicText, lit: "bar"}, + {typ: tPlainText, lit: " baz"}, + {typ: tEOF, lit: ""}, + }, + }, + "id and output code": { + input: "@slim test() {\n\t#foo= bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tScript, lit: "bar"}, + }, + }, + "id and id again": { + input: "@slim test() {\n\t#foo#bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tId, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + l := newLexer([]byte(tt.input)) + for _, want := range tt.want { + got := l.nextToken() + if got.typ != want.typ || got.lit != want.lit { + t.Errorf("want %v, got %v", want, got) + } + } + }) + } +} + +func Test_HamlClass(t *testing.T) { + tests := map[string]struct { + input string + want []token + }{ + "simple": { + input: "@goht test() {\n\t.foo", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tClass, lit: "foo"}, + {typ: tEOF, lit: ""}, + }, + }, + "multiple classes": { + input: "@goht test() {\n\t.foo\n\t.bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tClass, lit: "foo"}, + {typ: tNewLine, lit: "\n"}, + {typ: tIndent, lit: "\t"}, + {typ: tClass, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "class and id": { + input: "@goht test() {\n\t.foo#bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tClass, lit: "foo"}, {typ: tId, lit: "bar"}, @@ -891,7 +1383,7 @@ func Test_HamlClass(t *testing.T) { "class and tag": { input: "@goht test() {\n\t.foo%bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tClass, lit: "foo"}, {typ: tPlainText, lit: "%bar"}, @@ -901,7 +1393,7 @@ func Test_HamlClass(t *testing.T) { "class and attribute": { input: "@goht test() {\n\t.foo{id:\"bar\"}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tClass, lit: "foo"}, {typ: tAttrName, lit: "id"}, @@ -913,7 +1405,7 @@ func Test_HamlClass(t *testing.T) { "class and text": { input: "@goht test() {\n\t.foo bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tClass, lit: "foo"}, {typ: tPlainText, lit: "bar"}, @@ -923,7 +1415,7 @@ func Test_HamlClass(t *testing.T) { "class and unescaped text": { input: "@goht test() {\n\t.foo! bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tClass, lit: "foo"}, {typ: tUnescaped, lit: ""}, @@ -934,7 +1426,7 @@ func Test_HamlClass(t *testing.T) { "class and output code": { input: "@goht test() {\n\t.foo= bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tClass, lit: "foo"}, {typ: tScript, lit: "bar"}, @@ -944,7 +1436,7 @@ func Test_HamlClass(t *testing.T) { "class and class again": { input: "@goht test() {\n\t.foo.bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tClass, lit: "foo"}, {typ: tClass, lit: "bar"}, @@ -954,7 +1446,7 @@ func Test_HamlClass(t *testing.T) { "space before class identifier": { input: "@goht test() {\n\t. foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tError, lit: "Class identifier expected"}, {typ: tEOF, lit: ""}, @@ -974,104 +1466,468 @@ func Test_HamlClass(t *testing.T) { } } -func Test_WhitespaceRemoval(t *testing.T) { +func Test_SlimClass(t *testing.T) { tests := map[string]struct { input string want []token }{ - "remove outer": { - input: "@goht test() {\n\t%p>\n}", + "simple": { + input: "@slim test() {\n\t.bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tTag, lit: "p"}, - {typ: tNukeOuterWhitespace, lit: ""}, - {typ: tNewLine, lit: "\n"}, - {typ: tGohtEnd, lit: ""}, + {typ: tClass, lit: "bar"}, {typ: tEOF, lit: ""}, }, }, - "remove inner": { - input: "@goht test() {\n\t%p<\n}", + "multiple classes": { + input: "@slim test() {\n\t.foo\n\t.bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tTag, lit: "p"}, - {typ: tNukeInnerWhitespace, lit: ""}, + {typ: tClass, lit: "foo"}, {typ: tNewLine, lit: "\n"}, - {typ: tGohtEnd, lit: ""}, + {typ: tIndent, lit: "\t"}, + {typ: tClass, lit: "bar"}, {typ: tEOF, lit: ""}, }, }, - "remove both": { - input: "@goht test() {\n\t%p<>\n}", + "class and id": { + input: "@slim test() {\n\t.foo#bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tTag, lit: "p"}, - {typ: tNukeInnerWhitespace, lit: ""}, - {typ: tNukeOuterWhitespace, lit: ""}, - {typ: tNewLine, lit: "\n"}, - {typ: tGohtEnd, lit: ""}, + {typ: tClass, lit: "foo"}, + {typ: tId, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "class and attribute": { + input: "@slim test() {\n\t.foo{id:\"bar\"}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tClass, lit: "foo"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tEOF, lit: ""}, + }, + }, + "class and text": { + input: "@slim test() {\n\t.foo bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tClass, lit: "foo"}, + {typ: tPlainText, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "class and interpolation": { + input: "@slim test() {\n\t.foo #{bar} baz", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tClass, lit: "foo"}, + {typ: tDynamicText, lit: "bar"}, + {typ: tPlainText, lit: " baz"}, + {typ: tEOF, lit: ""}, + }, + }, + "class and output code": { + input: "@slim test() {\n\t.foo= bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tClass, lit: "foo"}, + {typ: tScript, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + l := newLexer([]byte(tt.input)) + for _, want := range tt.want { + got := l.nextToken() + if got.typ != want.typ || got.lit != want.lit { + t.Errorf("want %v, got %v", want, got) + } + } + }) + } +} + +func Test_WhitespaceRemoval(t *testing.T) { + tests := map[string]struct { + input string + want []token + }{ + "remove outer": { + input: "@goht test() {\n\t%p>\n}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "p"}, + {typ: tNukeOuterWhitespace, lit: ""}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tEOF, lit: ""}, + }, + }, + "remove inner": { + input: "@goht test() {\n\t%p<\n}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "p"}, + {typ: tNukeInnerWhitespace, lit: ""}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tEOF, lit: ""}, + }, + }, + "remove both": { + input: "@goht test() {\n\t%p<>\n}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "p"}, + {typ: tNukeInnerWhitespace, lit: ""}, + {typ: tNukeOuterWhitespace, lit: ""}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tEOF, lit: ""}, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + l := newLexer([]byte(tt.input)) + for _, want := range tt.want { + got := l.nextToken() + if got.typ != want.typ || got.lit != want.lit { + t.Errorf("want %v, got %v", want, got) + } + } + }) + } +} + +func Test_HamlObjectRef(t *testing.T) { + tests := map[string]struct { + input string + want []token + }{ + "simple": { + input: "@goht test() {\n\t%p[foo]", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "p"}, + {typ: tObjectRef, lit: "foo"}, + {typ: tEOF, lit: ""}, + }, + }, + "with prefix": { + input: "@goht test() {\n\t%p[foo, \"bar\"]", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "p"}, + {typ: tObjectRef, lit: "foo, \"bar\""}, + {typ: tEOF, lit: ""}, + }, + }, + "on tag from class": { + input: "@goht test() {\n\t.foo[foo, \"bar\"]", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tClass, lit: "foo"}, + {typ: tObjectRef, lit: "foo, \"bar\""}, + {typ: tEOF, lit: ""}, + }, + }, + "on tag from id": { + input: "@goht test() {\n\t#foo[foo, \"bar\"]", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tId, lit: "foo"}, + {typ: tObjectRef, lit: "foo, \"bar\""}, + {typ: tEOF, lit: ""}, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + l := newLexer([]byte(tt.input)) + for _, want := range tt.want { + got := l.nextToken() + if got.typ != want.typ || got.lit != want.lit { + t.Errorf("want %v, got %v", want, got) + } + } + }) + } +} + +func Test_HamlAttributes(t *testing.T) { + tests := map[string]struct { + input string + want []token + }{ + "simple": { + input: "@goht test() {\n\t%foo{id:\"bar\"}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tEOF, lit: ""}, + }, + }, + "no tag": { + input: "@goht test() {\n\t{id:\"bar\"}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tEOF, lit: ""}, + }, + }, + "names with dashes": { + input: "@goht test() {\n\t%foo{data-foo:\"bar\"}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "data-foo"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tEOF, lit: ""}, + }, + }, + "names with underscores": { + input: "@goht test() {\n\t%foo{data_foo:\"bar\"}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "data_foo"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tEOF, lit: ""}, + }, + }, + "names with numbers": { + input: "@goht test() {\n\t%foo{data1:\"bar\"}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "data1"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tEOF, lit: ""}, + }, + }, + "names with colons": { + input: "@goht test() {\n\t%foo{\":x-data\":\"bar\",`x-on:click`:#{onClick}}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: ":x-data"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tAttrName, lit: "x-on:click"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrDynamicValue, lit: "onClick"}, + {typ: tEOF, lit: ""}, + }, + }, + "names with dots": { + input: "@goht test() {\n\t%foo{data.foo:\"bar\",x.on.click:#{onClick}}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "data.foo"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tAttrName, lit: "x.on.click"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrDynamicValue, lit: "onClick"}, + {typ: tEOF, lit: ""}, + }, + }, + "names with at signs": { + input: "@goht test() {\n\t%foo{\"@data\":\"bar\",`x@on@click`:#{onClick}}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "@data"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tAttrName, lit: "x@on@click"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrDynamicValue, lit: "onClick"}, + }, + }, + "several": { + input: "@goht test() {\n\t%foo{id:\"bar\", class: `baz` , title : \"qux\"}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tAttrName, lit: "class"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "`baz`"}, + {typ: tAttrName, lit: "title"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"qux\""}, + {typ: tEOF, lit: ""}, + }, + }, + "several on multiple lines": { + input: "@goht test() {\n\t%foo{\n\tid:\"bar\",\n\tclass: `baz` ,\n\ttitle : \"qux\"\n}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tAttrName, lit: "class"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "`baz`"}, + {typ: tAttrName, lit: "title"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"qux\""}, + {typ: tEOF, lit: ""}, + }, + }, + "static value with escaped quotes": { + input: "@goht test() {\n\t%foo{id:\"bar\\\"baz\"}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\\\"baz\""}, + {typ: tEOF, lit: ""}, + }, + }, + "dynamic value": { + input: "@goht test() {\n\t%foo{id:#{bar}}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrDynamicValue, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "dynamic value with escaped curly": { + input: "@goht test() {\n\t%foo{id:#{\"big}\"}, class: #{\"ba\"+'}'}}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrDynamicValue, lit: "\"big}\""}, + {typ: tAttrName, lit: "class"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrDynamicValue, lit: "\"ba\"+'}'"}, + {typ: tEOF, lit: ""}, + }, + }, + "dynamic values": { + input: "@goht test() {\n\t%foo{id:#{bar}, class: #{baz}}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrDynamicValue, lit: "bar"}, + {typ: tAttrName, lit: "class"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrDynamicValue, lit: "baz"}, + {typ: tEOF, lit: ""}, + }, + }, + "boolean attribute": { + input: "@goht test() {\n\t%foo{bar}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "bar"}, {typ: tEOF, lit: ""}, }, }, - } - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - l := newLexer([]byte(tt.input)) - for _, want := range tt.want { - got := l.nextToken() - if got.typ != want.typ || got.lit != want.lit { - t.Errorf("want %v, got %v", want, got) - } - } - }) - } -} - -func Test_HamlObjectRef(t *testing.T) { - tests := map[string]struct { - input string - want []token - }{ - "simple": { - input: "@goht test() {\n\t%p[foo]", + "boolean operator": { + input: "@goht test() {\n\t%foo{bar?#{isBar}, baz ? #{isBaz}}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tTag, lit: "p"}, - {typ: tObjectRef, lit: "foo"}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "bar"}, + {typ: tAttrOperator, lit: "?"}, + {typ: tAttrDynamicValue, lit: "isBar"}, + {typ: tAttrName, lit: "baz"}, + {typ: tAttrOperator, lit: "?"}, + {typ: tAttrDynamicValue, lit: "isBaz"}, {typ: tEOF, lit: ""}, }, }, - "with prefix": { - input: "@goht test() {\n\t%p[foo, \"bar\"]", + "attributes command": { + input: "@goht test() {\n\t%foo{@attributes:#{listA, \"}}\", listB}}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tTag, lit: "p"}, - {typ: tObjectRef, lit: "foo, \"bar\""}, + {typ: tTag, lit: "foo"}, + {typ: tAttributesCommand, lit: "listA, \"}}\", listB"}, {typ: tEOF, lit: ""}, }, }, - "on tag from class": { - input: "@goht test() {\n\t.foo[foo, \"bar\"]", + "missing delimiter": { + input: "@goht test() {\n\t%foo{id\"bar\", class: \"baz\"}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tClass, lit: "foo"}, - {typ: tObjectRef, lit: "foo, \"bar\""}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "id"}, + {typ: tError, lit: "unexpected character: '\"'"}, {typ: tEOF, lit: ""}, }, }, - "on tag from id": { - input: "@goht test() {\n\t#foo[foo, \"bar\"]", + "missing separator": { + input: "@goht test() {\n\t%foo{id:\"bar\" class: \"baz\"}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, - {typ: tId, lit: "foo"}, - {typ: tObjectRef, lit: "foo, \"bar\""}, + {typ: tTag, lit: "foo"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tError, lit: "unexpected character: c"}, {typ: tEOF, lit: ""}, }, }, @@ -1089,15 +1945,15 @@ func Test_HamlObjectRef(t *testing.T) { } } -func Test_HamlAttributes(t *testing.T) { +func Test_SlimAttributes(t *testing.T) { tests := map[string]struct { input string want []token }{ "simple": { - input: "@goht test() {\n\t%foo{id:\"bar\"}", + input: "@slim test() {\n\tfoo{id:\"bar\"}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "id"}, @@ -1106,10 +1962,21 @@ func Test_HamlAttributes(t *testing.T) { {typ: tEOF, lit: ""}, }, }, + "no tag": { + input: "@slim test() {\n\t{id:\"bar\"}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tAttrName, lit: "id"}, + {typ: tAttrOperator, lit: ":"}, + {typ: tAttrEscapedValue, lit: "\"bar\""}, + {typ: tEOF, lit: ""}, + }, + }, "names with dashes": { - input: "@goht test() {\n\t%foo{data-foo:\"bar\"}", + input: "@slim test() {\n\tfoo{data-foo:\"bar\"}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "data-foo"}, @@ -1119,9 +1986,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "names with underscores": { - input: "@goht test() {\n\t%foo{data_foo:\"bar\"}", + input: "@slim test() {\n\tfoo{data_foo:\"bar\"}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "data_foo"}, @@ -1131,9 +1998,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "names with numbers": { - input: "@goht test() {\n\t%foo{data1:\"bar\"}", + input: "@slim test() {\n\tfoo{data1:\"bar\"}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "data1"}, @@ -1143,9 +2010,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "names with colons": { - input: "@goht test() {\n\t%foo{\":x-data\":\"bar\",`x-on:click`:#{onClick}}", + input: "@slim test() {\n\tfoo{\":x-data\":\"bar\",`x-on:click`:#{onClick}}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: ":x-data"}, @@ -1158,9 +2025,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "names with dots": { - input: "@goht test() {\n\t%foo{data.foo:\"bar\",x.on.click:#{onClick}}", + input: "@slim test() {\n\tfoo{data.foo:\"bar\",x.on.click:#{onClick}}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "data.foo"}, @@ -1173,9 +2040,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "names with at signs": { - input: "@goht test() {\n\t%foo{\"@data\":\"bar\",`x@on@click`:#{onClick}}", + input: "@slim test() {\n\tfoo{\"@data\":\"bar\",`x@on@click`:#{onClick}}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "@data"}, @@ -1187,9 +2054,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "several": { - input: "@goht test() {\n\t%foo{id:\"bar\", class: `baz` , title : \"qux\"}", + input: "@slim test() {\n\tfoo{id:\"bar\", class: `baz` , title : \"qux\"}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "id"}, @@ -1205,9 +2072,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "several on multiple lines": { - input: "@goht test() {\n\t%foo{\n\tid:\"bar\",\n\tclass: `baz` ,\n\ttitle : \"qux\"\n}", + input: "@slim test() {\n\tfoo{\n\tid:\"bar\",\n\tclass: `baz` ,\n\ttitle : \"qux\"\n}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "id"}, @@ -1223,9 +2090,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "static value with escaped quotes": { - input: "@goht test() {\n\t%foo{id:\"bar\\\"baz\"}", + input: "@slim test() {\n\tfoo{id:\"bar\\\"baz\"}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "id"}, @@ -1235,9 +2102,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "dynamic value": { - input: "@goht test() {\n\t%foo{id:#{bar}}", + input: "@slim test() {\n\tfoo{id:#{bar}}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "id"}, @@ -1247,9 +2114,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "dynamic value with escaped curly": { - input: "@goht test() {\n\t%foo{id:#{\"big}\"}, class: #{\"ba\"+'}'}}", + input: "@slim test() {\n\tfoo{id:#{\"big}\"}, class: #{\"ba\"+'}'}}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "id"}, @@ -1262,9 +2129,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "dynamic values": { - input: "@goht test() {\n\t%foo{id:#{bar}, class: #{baz}}", + input: "@slim test() {\n\tfoo{id:#{bar}, class: #{baz}}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "id"}, @@ -1277,9 +2144,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "boolean attribute": { - input: "@goht test() {\n\t%foo{bar}", + input: "@slim test() {\n\tfoo{bar}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "bar"}, @@ -1287,9 +2154,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "boolean operator": { - input: "@goht test() {\n\t%foo{bar?#{isBar}, baz ? #{isBaz}}", + input: "@slim test() {\n\tfoo{bar?#{isBar}, baz ? #{isBaz}}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "bar"}, @@ -1302,9 +2169,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "attributes command": { - input: "@goht test() {\n\t%foo{@attributes:#{listA, \"}}\", listB}}", + input: "@slim test() {\n\tfoo{@attributes:#{listA, \"}}\", listB}}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttributesCommand, lit: "listA, \"}}\", listB"}, @@ -1312,9 +2179,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "missing delimiter": { - input: "@goht test() {\n\t%foo{id\"bar\", class: \"baz\"}", + input: "@slim test() {\n\tfoo{id\"bar\", class: \"baz\"}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "id"}, @@ -1323,9 +2190,9 @@ func Test_HamlAttributes(t *testing.T) { }, }, "missing separator": { - input: "@goht test() {\n\t%foo{id:\"bar\" class: \"baz\"}", + input: "@slim test() {\n\tfoo{id:\"bar\" class: \"baz\"}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tAttrName, lit: "id"}, @@ -1357,7 +2224,7 @@ func Test_HamlDoctype(t *testing.T) { "simple": { input: "@goht test() {\n\t!!!", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tDoctype, lit: ""}, {typ: tEOF, lit: ""}, @@ -1366,7 +2233,7 @@ func Test_HamlDoctype(t *testing.T) { "with type": { input: "@goht test() {\n\t!!! Strict", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tDoctype, lit: "Strict"}, {typ: tEOF, lit: ""}, @@ -1375,7 +2242,7 @@ func Test_HamlDoctype(t *testing.T) { "not a doctype": { input: "@goht test() {\n\t!foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tUnescaped, lit: ""}, {typ: tPlainText, lit: "foo"}, @@ -1385,14 +2252,69 @@ func Test_HamlDoctype(t *testing.T) { "doctype with content": { input: "@goht test() {\n\t!!! 5\n\t%html\n}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tDoctype, lit: "5"}, + {typ: tNewLine, lit: "\n"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "html"}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tEOF, lit: ""}, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + l := newLexer([]byte(tt.input)) + for _, want := range tt.want { + got := l.nextToken() + if got.typ != want.typ || got.lit != want.lit { + t.Errorf("want %v, got %v", want, got) + } + } + }) + } +} + +func TestSlimDoctype(t *testing.T) { + tests := map[string]struct { + input string + want []token + }{ + "simple": { + input: "@slim test() {\n\tdoctype", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tDoctype, lit: ""}, + {typ: tEOF, lit: ""}, + }, + }, + "with type": { + input: "@slim test() {\n\tdoctype Strict", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tDoctype, lit: "Strict"}, + {typ: tEOF, lit: ""}, + }, + }, + "with content": { + input: "@slim test() {\n\tdoctype 5\n\thtml\n\t\ttitle foo\n}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tDoctype, lit: "5"}, {typ: tNewLine, lit: "\n"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "html"}, {typ: tNewLine, lit: "\n"}, - {typ: tGohtEnd, lit: ""}, + {typ: tIndent, lit: "\t\t"}, + {typ: tTag, lit: "title"}, + {typ: tPlainText, lit: "foo"}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, @@ -1418,7 +2340,7 @@ func Test_HamlUnescaped(t *testing.T) { "simple": { input: "@goht test() {\n\t!foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tUnescaped, lit: ""}, {typ: tPlainText, lit: "foo"}, @@ -1428,7 +2350,7 @@ func Test_HamlUnescaped(t *testing.T) { "with space": { input: "@goht test() {\n\t! foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tUnescaped, lit: ""}, {typ: tPlainText, lit: "foo"}, @@ -1438,7 +2360,7 @@ func Test_HamlUnescaped(t *testing.T) { "dynamic text": { input: "@goht test() {\n\t! #{foo}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tUnescaped, lit: ""}, {typ: tDynamicText, lit: "foo"}, @@ -1448,7 +2370,7 @@ func Test_HamlUnescaped(t *testing.T) { "unescaped code": { input: "@goht test() {\n\t!= foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tUnescaped, lit: ""}, {typ: tScript, lit: "foo"}, @@ -1457,7 +2379,7 @@ func Test_HamlUnescaped(t *testing.T) { "not unescaped": { input: "@goht test() {\n\t%p ! foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "p"}, {typ: tPlainText, lit: "! foo"}, @@ -1467,7 +2389,7 @@ func Test_HamlUnescaped(t *testing.T) { "tag with unescaped text": { input: "@goht test() {\n\t%foo! bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tUnescaped, lit: ""}, @@ -1477,7 +2399,7 @@ func Test_HamlUnescaped(t *testing.T) { "tag with unescaped code": { input: "@goht test() {\n\t%foo!= bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tUnescaped, lit: ""}, @@ -1491,7 +2413,7 @@ func Test_HamlUnescaped(t *testing.T) { %p! This #{html} HTML. }`, want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tSilentScript, lit: "var html = \"is\""}, {typ: tNewLine, lit: "\n"}, @@ -1508,7 +2430,7 @@ func Test_HamlUnescaped(t *testing.T) { {typ: tDynamicText, lit: "html"}, {typ: tPlainText, lit: " HTML."}, {typ: tNewLine, lit: "\n"}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, @@ -1534,16 +2456,16 @@ func Test_HamlComment(t *testing.T) { "simple": { input: "@goht test() {\n\t/ foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tComment, lit: "foo"}, {typ: tEOF, lit: ""}, }, }, - "nested content": { + "with content": { input: "@goht test() {\n\t/\n\t%p bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tComment, lit: ""}, {typ: tNewLine, lit: "\n"}, @@ -1556,7 +2478,7 @@ func Test_HamlComment(t *testing.T) { "haml comment": { input: "@goht test() {\n\t-# foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tRubyComment, lit: ""}, {typ: tEOF, lit: ""}, @@ -1569,21 +2491,21 @@ func Test_HamlComment(t *testing.T) { %p bar }`, want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "p"}, {typ: tPlainText, lit: "foo"}, {typ: tNewLine, lit: "\n"}, {typ: tIndent, lit: "\t"}, {typ: tRubyComment, lit: ""}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, "both comments": { input: "@goht test() {\n\t/ foo\n\t-# bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tComment, lit: "foo"}, {typ: tNewLine, lit: "\n"}, @@ -1600,7 +2522,7 @@ func Test_HamlComment(t *testing.T) { %p bar }`, want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tComment, lit: ""}, {typ: tNewLine, lit: "\n"}, @@ -1609,7 +2531,96 @@ func Test_HamlComment(t *testing.T) { {typ: tNewLine, lit: "\n"}, {typ: tIndent, lit: "\t"}, {typ: tRubyComment, lit: ""}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateEnd, lit: ""}, + {typ: tEOF, lit: ""}, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + l := newLexer([]byte(tt.input)) + for _, want := range tt.want { + got := l.nextToken() + if got.typ != want.typ || got.lit != want.lit { + t.Errorf("want %v, got %v", want, got) + } + } + }) + } +} + +func Test_SlimComment(t *testing.T) { + tests := map[string]struct { + input string + want []token + }{ + "simple": { + input: "@slim test() {\n\t/ foo", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tRubyComment, lit: ""}, + {typ: tEOF, lit: ""}, + }, + }, + "html comment": { + input: "@slim test() {\n\t/! foo", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tComment, lit: "foo"}, + {typ: tEOF, lit: ""}, + }, + }, + "simple with content": { + input: "@slim test() {\n\t/\n\tp bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tRubyComment, lit: ""}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "p"}, + {typ: tPlainText, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "html comment with content": { + input: "@slim test() {\n\t/! foo\n\tp bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tComment, lit: "foo"}, + {typ: tNewLine, lit: "\n"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "p"}, + {typ: tPlainText, lit: "bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "multiline comment": { + input: "@slim test() {\n\t/foo\n\t\tbar\n\tp baz", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tRubyComment, lit: ""}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "p"}, + {typ: tPlainText, lit: "baz"}, + {typ: tEOF, lit: ""}, + }, + }, + "multiline html comment": { + input: "@slim test() {\n\t/! foo\n\t\tbar\n\tp baz", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tComment, lit: "foo"}, + {typ: tNewLine, lit: "\n"}, + {typ: tComment, lit: "bar"}, + {typ: tNewLine, lit: "\n"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "p"}, + {typ: tPlainText, lit: "baz"}, {typ: tEOF, lit: ""}, }, }, @@ -1635,7 +2646,7 @@ func Test_HamlVoidTags(t *testing.T) { "simple": { input: "@goht test() {\n\t%foo/", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tVoidTag, lit: ""}, @@ -1645,7 +2656,7 @@ func Test_HamlVoidTags(t *testing.T) { "line is ignored": { input: "@goht test() {\n\t%foo/ bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tError, lit: "self-closing tags can't have content"}, @@ -1673,7 +2684,7 @@ func Test_HamlOutputCode(t *testing.T) { "simple": { input: "@goht test() {\n\t=foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tScript, lit: "foo"}, {typ: tEOF, lit: ""}, @@ -1682,7 +2693,7 @@ func Test_HamlOutputCode(t *testing.T) { "with space": { input: "@goht test() {\n\t= foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tScript, lit: "foo"}, {typ: tEOF, lit: ""}, @@ -1691,7 +2702,7 @@ func Test_HamlOutputCode(t *testing.T) { "after tag": { input: "@goht test() {\n\t%foo= bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tScript, lit: "bar"}, @@ -1701,7 +2712,7 @@ func Test_HamlOutputCode(t *testing.T) { "without space": { input: "@goht test() {\n\t=foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tScript, lit: "foo"}, {typ: tEOF, lit: ""}, @@ -1710,7 +2721,7 @@ func Test_HamlOutputCode(t *testing.T) { "with parens": { input: "@goht test() {\n\t= foo()", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tScript, lit: "foo()"}, {typ: tEOF, lit: ""}, @@ -1719,7 +2730,7 @@ func Test_HamlOutputCode(t *testing.T) { "with render command": { input: "@goht test() {\n\t= @render foo(\"bar\")", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tRenderCommand, lit: "foo(\"bar\")"}, {typ: tEOF, lit: ""}, @@ -1728,7 +2739,7 @@ func Test_HamlOutputCode(t *testing.T) { "with render command and parens": { input: "@goht test() {\n\t= @render() foo(\"bar\")", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tRenderCommand, lit: "foo(\"bar\")"}, {typ: tEOF, lit: ""}, @@ -1737,7 +2748,7 @@ func Test_HamlOutputCode(t *testing.T) { "with missing render argument": { input: "@goht test() {\n\t= @render", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tError, lit: "render argument expected"}, {typ: tEOF, lit: ""}, @@ -1746,7 +2757,7 @@ func Test_HamlOutputCode(t *testing.T) { "with children command": { input: "@goht test() {\n\t= @children", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tChildrenCommand, lit: ""}, {typ: tEOF, lit: ""}, @@ -1755,7 +2766,7 @@ func Test_HamlOutputCode(t *testing.T) { "with children command and parens": { input: "@goht test() {\n\t= @children()", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tChildrenCommand, lit: ""}, {typ: tEOF, lit: ""}, @@ -1764,7 +2775,7 @@ func Test_HamlOutputCode(t *testing.T) { "without any children arguments": { input: "@goht test() {\n\t= @children() asdfasdf", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tError, lit: "children command does not accept arguments"}, {typ: tEOF, lit: ""}, @@ -1792,7 +2803,7 @@ func Test_HamlExecuteCode(t *testing.T) { "simple": { input: "@goht test() {\n\t-foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tSilentScript, lit: "foo"}, {typ: tEOF, lit: ""}, @@ -1801,7 +2812,7 @@ func Test_HamlExecuteCode(t *testing.T) { "with space": { input: "@goht test() {\n\t- foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tSilentScript, lit: "foo"}, {typ: tEOF, lit: ""}, @@ -1810,7 +2821,7 @@ func Test_HamlExecuteCode(t *testing.T) { "not code": { input: "@goht test() {\n\t%foo- bar\n\t#foo-bar bar\n\t%p - bar", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo-"}, {typ: tPlainText, lit: "bar"}, @@ -1828,7 +2839,7 @@ func Test_HamlExecuteCode(t *testing.T) { "without space": { input: "@goht test() {\n\t-foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tSilentScript, lit: "foo"}, {typ: tEOF, lit: ""}, @@ -1841,7 +2852,7 @@ func Test_HamlExecuteCode(t *testing.T) { - } `, want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tSilentScript, lit: "if foo != \"\" {"}, {typ: tNewLine, lit: "\n"}, @@ -1860,7 +2871,7 @@ func Test_HamlExecuteCode(t *testing.T) { "ruby style comment": { input: "@goht test() {\n\t-# comment\n\t- foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tRubyComment, lit: ""}, {typ: tIndent, lit: "\t"}, @@ -1871,7 +2882,7 @@ func Test_HamlExecuteCode(t *testing.T) { "nested ruby style comment": { input: "@goht test() {\n\t-#\n\t\tcomment\n\t- foo", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tRubyComment, lit: ""}, {typ: tIndent, lit: "\t"}, @@ -1882,7 +2893,7 @@ func Test_HamlExecuteCode(t *testing.T) { "with receiver": { input: "@goht (t Tester) test() {\n\t- t.bar", want: []token{ - {typ: tGohtStart, lit: "(t Tester) test()"}, + {typ: tTemplateStart, lit: "(t Tester) test()"}, {typ: tIndent, lit: "\t"}, {typ: tSilentScript, lit: "t.bar"}, {typ: tEOF, lit: ""}, @@ -1891,7 +2902,7 @@ func Test_HamlExecuteCode(t *testing.T) { "with receiver and interface": { input: "@goht (t Tester) test(v interface{}) {\n\t- t.bar", want: []token{ - {typ: tGohtStart, lit: "(t Tester) test(v interface{})"}, + {typ: tTemplateStart, lit: "(t Tester) test(v interface{})"}, {typ: tIndent, lit: "\t"}, {typ: tSilentScript, lit: "t.bar"}, {typ: tEOF, lit: ""}, @@ -1900,7 +2911,7 @@ func Test_HamlExecuteCode(t *testing.T) { "with receiver and interface with methods": { input: "@goht (t Tester) test(v interface{ Foo() string }) {\n\t- t.bar", want: []token{ - {typ: tGohtStart, lit: "(t Tester) test(v interface{ Foo() string })"}, + {typ: tTemplateStart, lit: "(t Tester) test(v interface{ Foo() string })"}, {typ: tIndent, lit: "\t"}, {typ: tSilentScript, lit: "t.bar"}, {typ: tEOF, lit: ""}, @@ -1920,6 +2931,67 @@ func Test_HamlExecuteCode(t *testing.T) { } } +func Test_SlimSilentCode(t *testing.T) { + tests := map[string]struct { + input string + want []token + }{ + "simple": { + input: "@slim test() {\n\t-foo", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tSilentScript, lit: "foo"}, + {typ: tEOF, lit: ""}, + }, + }, + "with space": { + input: "@slim test() {\n\t- foo", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tSilentScript, lit: "foo"}, + {typ: tEOF, lit: ""}, + }, + }, + "not code": { + input: "@slim test() {\n\tp - bar", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "p"}, + {typ: tPlainText, lit: "- bar"}, + {typ: tEOF, lit: ""}, + }, + }, + "multiline code": { + input: "@slim test() {\n\t- foo(\\\n\t\tbar,\n\t\tbaz,\n\t\t)\n\tp foo\n}", + want: []token{ + {typ: tTemplateStart, lit: "test()"}, + {typ: tIndent, lit: "\t"}, + {typ: tSilentScript, lit: "foo(bar,baz,)"}, + {typ: tIndent, lit: "\t"}, + {typ: tTag, lit: "p"}, + {typ: tPlainText, lit: "foo"}, + {typ: tNewLine, lit: "\n"}, + {typ: tTemplateEnd, lit: ""}, + {typ: tEOF, lit: ""}, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + l := newLexer([]byte(tt.input)) + for _, want := range tt.want { + got := l.nextToken() + if got.typ != want.typ || got.lit != want.lit { + t.Errorf("want %v, got %v", want, got) + } + } + }) + } +} + func Test_HamlIndent(t *testing.T) { tests := map[string]struct { input string @@ -1928,21 +3000,21 @@ func Test_HamlIndent(t *testing.T) { "simple": { input: "@goht test() {\n\t%foo\n\t\tbar\n}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tNewLine, lit: "\n"}, {typ: tIndent, lit: "\t\t"}, {typ: tPlainText, lit: "bar"}, {typ: tNewLine, lit: "\n"}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, "indented too deep": { input: "@goht test() {\n\t%foo\n\t\t\tbar\n}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "foo"}, {typ: tNewLine, lit: "\n"}, @@ -1957,7 +3029,7 @@ func Test_HamlIndent(t *testing.T) { %p3 three }`, want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "p1"}, {typ: tNewLine, lit: "\n"}, @@ -1972,7 +3044,7 @@ func Test_HamlIndent(t *testing.T) { {typ: tTag, lit: "p3"}, {typ: tPlainText, lit: "three"}, {typ: tNewLine, lit: "\n"}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, @@ -1982,7 +3054,7 @@ func Test_HamlIndent(t *testing.T) { %p2 }`, want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "p1"}, {typ: tNewLine, lit: "\n"}, @@ -1997,7 +3069,7 @@ func Test_HamlIndent(t *testing.T) { %p3 }`, want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "p1"}, {typ: tNewLine, lit: "\n"}, @@ -2030,50 +3102,50 @@ func Test_HamlFilters(t *testing.T) { "simple javascript": { input: "@goht test() {\n\t:javascript\n\t\tfoo\n}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tFilterStart, lit: "javascript"}, {typ: tPlainText, lit: "foo\n"}, {typ: tFilterEnd, lit: ""}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, "simple css": { input: "@goht test() {\n\t:css\n\t\tfoo\n}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tFilterStart, lit: "css"}, {typ: tPlainText, lit: "foo\n"}, {typ: tFilterEnd, lit: ""}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, "indented": { input: "@goht test() {\n\t:javascript\n\t\tfoo\n}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tFilterStart, lit: "javascript"}, {typ: tPlainText, lit: "foo\n"}, {typ: tFilterEnd, lit: ""}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, "interpolation": { input: "@goht test() {\n\t:javascript\n\t\tfoo #{bar}\n}", want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tFilterStart, lit: "javascript"}, {typ: tPlainText, lit: "foo "}, {typ: tDynamicText, lit: "bar"}, {typ: tPlainText, lit: "\n"}, {typ: tFilterEnd, lit: ""}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, @@ -2084,7 +3156,7 @@ func Test_HamlFilters(t *testing.T) { console.log("hello world") }`, want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "p"}, {typ: tNewLine, lit: "\n"}, @@ -2092,7 +3164,7 @@ func Test_HamlFilters(t *testing.T) { {typ: tFilterStart, lit: "javascript"}, {typ: tPlainText, lit: "console.log(\"hello world\")\n"}, {typ: tFilterEnd, lit: ""}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, @@ -2104,7 +3176,7 @@ func Test_HamlFilters(t *testing.T) { %p foo }`, want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tTag, lit: "p"}, {typ: tNewLine, lit: "\n"}, @@ -2116,7 +3188,7 @@ func Test_HamlFilters(t *testing.T) { {typ: tTag, lit: "p"}, {typ: tPlainText, lit: "foo"}, {typ: tNewLine, lit: "\n"}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, @@ -2128,7 +3200,7 @@ func Test_HamlFilters(t *testing.T) { .color { color: red; } }`, want: []token{ - {typ: tGohtStart, lit: "test()"}, + {typ: tTemplateStart, lit: "test()"}, {typ: tIndent, lit: "\t"}, {typ: tFilterStart, lit: "javascript"}, {typ: tPlainText, lit: "console.log(\"Hello\");\n"}, @@ -2137,7 +3209,7 @@ func Test_HamlFilters(t *testing.T) { {typ: tFilterStart, lit: "css"}, {typ: tPlainText, lit: ".color { color: red; }\n"}, {typ: tFilterEnd, lit: ""}, - {typ: tGohtEnd, lit: ""}, + {typ: tTemplateEnd, lit: ""}, {typ: tEOF, lit: ""}, }, }, diff --git a/compiler/lexers.go b/compiler/lexers.go index 3715ca7..37286d8 100644 --- a/compiler/lexers.go +++ b/compiler/lexers.go @@ -1,7 +1,6 @@ package compiler import ( - "slices" "strings" "text/scanner" ) @@ -111,13 +110,15 @@ func lexGoCode(l *lexer) lexFn { func lexTemplate(l *lexer) lexFn { l.acceptUntil(" ") switch l.current() { - case "@goht": - return lexGohtStart + case "@goht", "@haml": + return lexTemplateStart(l, lexHamlLineStart) + case "@slim": + return lexTemplateStart(l, lexSlimLineStart) } return nil } -func lexGohtStart(l *lexer) lexFn { +func lexTemplateStart(l *lexer, next lexFn) lexFn { l.ignore() l.skipRun(" ") l.acceptUntil(")") @@ -136,632 +137,11 @@ func lexGohtStart(l *lexer) lexFn { } } l.next() - l.emit(tGohtStart) + l.emit(tTemplateStart) l.skipRun(" {") l.skipRun("\n\r") - return lexGohtLineStart -} - -func lexGohtLineStart(l *lexer) lexFn { - switch l.peek() { - case '}': - l.emit(tGohtEnd) - l.skip() - return lexGoLineStart - case scanner.EOF: - l.emit(tEOF) - return nil - case '\n', '\r': - return lexGohtLineEnd - default: - return lexGohtIndent - } -} - -func lexGohtIndent(l *lexer) lexFn { - // accept spaces and tabs so that we can report about improper indentation - l.acceptRun(" \t") - indent := l.current() - - // there has not been any indentation yet - if l.indent == 0 && len(indent) == 0 { - // return an error that indents are required - return l.errorf("templates must be indented") - } - - if len(indent) == 0 { - l.indent = 0 - l.emit(tIndent) - return lexGohtContentStart - } - - // validate the indent against the sequence and char - if lexErr := l.validateIndent(indent); lexErr != nil { - return lexErr - } - - l.indent = len(l.current()) // useful for parsing filters - l.emit(tIndent) - return lexGohtContentStart -} - -func lexGohtContentStart(l *lexer) lexFn { - switch l.peek() { - case '%': - return lexGohtTag - case '#': - return lexGohtId - case '.': - return lexGohtClass - case '\\': - l.skip() - return lexGohtTextStart - case '!': - if s, err := l.peekAhead(3); err != nil { - return l.errorf("unexpected error: %s", err) - } else if s == "!!!" { - // TODO return an error if we're nesting doctypes - return lexGohtDoctype - } - return lexGohtUnescaped - case '-': - return lexGohtSilentScript - case '=': - return lexGohtOutputCode - case '/': - return lexComment - case ':': - return lexFilterStart - case scanner.EOF, '\n', '\r': - return lexGohtLineEnd - default: - return lexGohtTextStart - } -} - -func lexGohtContent(l *lexer) lexFn { - switch l.peek() { - case '#': - return lexGohtId - case '.': - return lexGohtClass - case '[': - return lexObjectReference - case '{': - return lexGohtAttributesStart - case '!': - return lexGohtUnescaped - case '-': - return lexGohtSilentScript - case '=': - return lexGohtOutputCode - case '/': - return lexVoidTag - case '>', '<': - return lexWhitespaceRemoval - case scanner.EOF, '\n', '\r': - return lexGohtLineEnd - default: - return lexGohtTextStart - } -} - -func lexGohtContentEnd(l *lexer) lexFn { - switch l.peek() { - case '=': - return lexGohtOutputCode - case '/': - return lexVoidTag - case '>', '<': - return lexWhitespaceRemoval - case scanner.EOF, '\n', '\r': - return lexGohtLineEnd - default: - return lexGohtTextStart - } -} - -func lexGohtLineEnd(l *lexer) lexFn { - l.skipRun(" \t") - - switch l.peek() { - case '\n', '\r': - return lexGohtNewLine - case scanner.EOF: - l.emit(tEOF) - return nil - default: - return l.errorf("unexpected character: %q", l.peek()) - } -} - -func lexGohtNewLine(l *lexer) lexFn { - l.acceptRun("\n\r") - l.emit(tNewLine) - return lexGohtLineStart -} - -func hamlIdentifier(typ tokenType, l *lexer) lexFn { - l.skip() // eat symbol - - // these characters may follow an identifier - const mayFollowIdentifier = "%#.[{=!/<> \t\n\r" - - l.acceptUntil(mayFollowIdentifier) - if l.current() == "" { - return l.errorf("%s identifier expected", typ) - } - l.emit(typ) - return lexGohtContent -} - -func lexGohtTag(l *lexer) lexFn { - return hamlIdentifier(tTag, l) -} - -func lexGohtId(l *lexer) lexFn { - return hamlIdentifier(tId, l) -} - -func lexGohtClass(l *lexer) lexFn { - return hamlIdentifier(tClass, l) -} - -func lexObjectReference(l *lexer) lexFn { - l.skip() // eat opening bracket - r := continueToMatchingBrace(l, ']') - if r == scanner.EOF { - return l.errorf("object reference not closed: eof") - } - l.backup() - l.emit(tObjectRef) - l.skip() // skip closing bracket - return lexGohtContent -} - -func lexGohtAttributesStart(l *lexer) lexFn { - l.skip() - return lexGohtAttribute -} - -func lexGohtAttributesEnd(l *lexer) lexFn { - l.skip() - return lexGohtContent -} - -func lexGohtAttribute(l *lexer) lexFn { - // supported attributes - // key - // key:value - // key?value - // @attributes: []any (string, map[string]string, map[string]bool) - - l.skipRun(", \t\n\r") - - switch l.peek() { - case '}': - return lexGohtAttributesEnd - case '@': - return lexAttributeCommandStart - default: - return lexGohtAttributeName - } -} - -func lexGohtAttributeName(l *lexer) lexFn { - if l.peek() == '"' || l.peek() == '`' { - r := continueToMatchingQuote(l, tAttrName, false) - if r == scanner.EOF { - return l.errorf("attribute name not closed: eof") - } else if r != '"' && r != '`' { - return l.errorf("unexpected character: %q", r) - } - } else { - l.acceptUntil("?:,}{\" \t\n\r") - if l.current() == "" { - return l.errorf("attribute name expected") - } - l.emit(tAttrName) - } - - l.skipRun(" \t\n\r") - switch l.peek() { - case '?', ':': - return lexGohtAttributeOperator - case ',', '}': - return lexGohtAttributeEnd - default: - return l.errorf("unexpected character: %q", l.peek()) - } -} - -func lexGohtAttributeOperator(l *lexer) lexFn { - l.skipRun(" \t\n\r") - switch l.peek() { - case '?', ':': - l.next() - l.emit(tAttrOperator) - return lexGohtAttributeValue - } - return l.errorf("unexpected character: %q", l.peek()) -} - -func lexGohtAttributeValue(l *lexer) lexFn { - l.skipRun(" \t\n\r") - - switch l.peek() { - case '"', '`': - return lexGohtAttributeStaticValue - case '#': - return lexGohtAttributeDynamicValue - } - return l.errorf("unexpected character: %q", l.peek()) -} - -func lexGohtAttributeStaticValue(l *lexer) lexFn { - r := continueToMatchingQuote(l, tAttrEscapedValue, true) - if r == scanner.EOF { - return l.errorf("attribute value not closed: eof") - } else if r != '"' && r != '`' { - return l.errorf("unexpected character: %q", r) - } - return lexGohtAttributeEnd -} - -func lexGohtAttributeDynamicValue(l *lexer) lexFn { - l.skip() // skip hash - if l.peek() != '{' { - return l.errorf("unexpected character: %q", l.peek()) - } - l.skip() // skip opening brace - r := continueToMatchingBrace(l, '}') - if r == scanner.EOF { - return l.errorf("attribute value not closed: eof") - } - l.backup() - l.emit(tAttrDynamicValue) - l.skip() // skip closing brace - return lexGohtAttributeEnd -} - -func lexAttributeCommandStart(l *lexer) lexFn { - l.skipRun("@") - l.acceptUntil(": \t\n\r") - if l.current() == "" { - return l.errorf("command code expected") - } - switch l.current() { - case "attributes": - return lexGohtAttributeCommand(tAttributesCommand) - default: - return l.errorf("unknown attribute command: %s", l.current()) - } -} - -func lexGohtAttributeCommand(command tokenType) lexFn { - return func(l *lexer) lexFn { - l.ignore() - l.skipUntil(":") - l.skipUntil("{") - l.skip() // skip opening brace - r := continueToMatchingBrace(l, '}') - if r == scanner.EOF { - return l.errorf("attribute value not closed: eof") - } - l.backup() - l.emit(command) - l.skip() // skip closing brace - - return lexGohtAttributeEnd - } -} - -func lexGohtAttributeEnd(l *lexer) lexFn { - l.skipRun(" \t\n\r") - switch l.peek() { - case ',': - l.skip() - return lexGohtAttribute - case '}': - return lexGohtAttributesEnd - default: - return l.errorf("unexpected character: %c", l.peek()) - } -} - -func lexWhitespaceRemoval(l *lexer) lexFn { - direction := l.skip() - switch direction { - case '>': - l.emit(tNukeOuterWhitespace) - case '<': - l.emit(tNukeInnerWhitespace) - default: - return l.errorf("unexpected character: %q", direction) - } - return lexGohtContentEnd -} - -func lexGohtTextStart(l *lexer) lexFn { - l.skipRun(" \t") - return lexGohtTextContent -} - -func lexGohtTextContent(l *lexer) lexFn { - l.acceptUntil("\\#\n\r") - switch l.peek() { - case '\\': - isHashComing, err := l.peekAhead(2) - if err != nil { - return l.errorf("unexpected error: %s", err) - } - if isHashComing == "\\#" { - l.skip() - // was the backslash being escaped? - if !strings.HasSuffix(l.current(), "\\") { - l.next() - } - } else { - l.next() - } - return lexGohtTextContent - case '#': - return lexGohtDynamicText - default: - if l.current() != "" { - l.emit(tPlainText) - } - return lexGohtLineEnd - } -} - -func lexGohtDynamicText(l *lexer) lexFn { - if s, err := l.peekAhead(2); err != nil { - return l.errorf("unexpected error: %s", err) - } else if s != "#{" { - l.next() - return lexGohtTextContent - } - if l.current() != "" { - l.emit(tPlainText) - } - l.skipRun("#{") - r := continueToMatchingBrace(l, '}') - if r == scanner.EOF { - return l.errorf("dynamic text value was not closed: eof") - } - l.backup() - l.emit(tDynamicText) - l.skip() // skip closing brace - return lexGohtTextContent -} - -func lexGohtDoctype(l *lexer) lexFn { - l.skipRun("! ") - l.acceptUntil("\n\r") - l.emit(tDoctype) - return lexGohtLineEnd -} - -func lexGohtUnescaped(l *lexer) lexFn { - l.skip() - l.ignore() - l.emit(tUnescaped) - switch l.peek() { - case '=': - return lexGohtOutputCode - default: - return lexGohtTextStart - } -} - -func lexGohtSilentScript(l *lexer) lexFn { - l.skip() // eat dash - - // ruby style comment - if l.peek() == '#' { - // ignore the rest of the line - l.skipUntil("\n\r") - l.emit(tRubyComment) - return ignoreIndentedLines(l.indent + 1) - } - - l.skipRun(" \t") - l.acceptUntil("\n\r") - l.emit(tSilentScript) - return lexGohtLineEnd -} - -func ignoreIndentedLines(indent int) lexFn { - return func(l *lexer) lexFn { - switch l.peek() { - case '\n', '\r': - l.skip() - return ignoreIndentedLines(indent) - case ' ', '\t': - priorIndents, err := l.peekAhead(indent) - if err != nil { - return l.errorf("unexpected error while evaluating indents: %s", err) - } - if len(strings.TrimSpace(priorIndents)) != 0 { - return lexGohtLineStart - } - // validate we have the correct indents - if lexErr := l.validateIndent(priorIndents); lexErr != nil { - return lexErr - } - l.skipUntil("\n\r") - return ignoreIndentedLines(indent) - case scanner.EOF: - l.emit(tEOF) - return nil - default: - return lexGohtLineStart - } - } -} - -func lexGohtOutputCode(l *lexer) lexFn { - l.skipRun("= \t") - switch l.peek() { - case '@': - return lexGohtCommandCode - default: - l.acceptUntil("\n\r") - l.emit(tScript) - return lexGohtLineEnd - } -} - -func lexComment(l *lexer) lexFn { - l.skipRun("/ \t") - l.acceptUntil("\n\r") - l.emit(tComment) - return lexGohtLineEnd -} - -func lexVoidTag(l *lexer) lexFn { - l.skipRun("/ \t") - l.acceptUntil("\n\r") - if l.current() != "" { - l.ignore() - return l.errorf("self-closing tags can't have content") - } - l.emit(tVoidTag) - return lexGohtLineEnd -} - -func lexGohtCommandCode(l *lexer) lexFn { - l.skipRun("@") - l.acceptUntil("() \t\n\r") - if l.current() == "" { - return l.errorf("command code expected") - } - switch l.current() { - case "render": - l.acceptRun("() \t") - l.ignore() - l.acceptUntil("\n\r") - if l.current() == "" { - return l.errorf("render argument expected") - } - l.emit(tRenderCommand) - case "children": - l.acceptRun("() \t") - l.ignore() - l.acceptUntil("\n\r") - if l.current() != "" { - return l.errorf("children command does not accept arguments") - } - l.emit(tChildrenCommand) - } - l.skipRun("\n\r") - return lexGohtLineStart -} - -var filters = []string{"javascript", "css", "plain", "escaped", "preserve"} - -func lexFilterStart(l *lexer) lexFn { - l.skipRun(": \t") - l.acceptUntil(" \t\n\r") - if l.current() == "" { - return l.errorf("filter name expected") - } - if !slices.Contains(filters, l.current()) { - return l.errorf("unknown filter: %s", l.current()) - } - filter := l.current() - l.emit(tFilterStart) - l.skipUntil("\n\r") // ignore the rest of the current line - l.skipRun("\n\r") // split so we don't consume the indent on the next line - - switch filter { - case "javascript", "css", "plain": - return lexFilterLineStart(l.indent+1, tPlainText) - case "escaped": - return lexFilterLineStart(l.indent+1, tEscapedText) - case "preserve": - return lexFilterLineStart(l.indent+1, tPreserveText) - } - return lexGohtLineEnd -} - -func lexFilterLineStart(indent int, textType tokenType) lexFn { - return func(l *lexer) lexFn { - switch l.peek() { - case ' ', '\t': - return lexFilterIndent(indent, textType) - case scanner.EOF: - l.emit(tEOF) - return nil - default: - l.emit(tFilterEnd) - return lexGohtLineStart - } - } -} - -func lexFilterIndent(indent int, textType tokenType) lexFn { - return func(l *lexer) lexFn { - var indents string - - // only accept the whitespace that belongs to the indent - var err error - - // peeking first, in case we've reached the end of the filter - indents, err = l.peekAhead(indent) - if err != nil { - return l.errorf("unexpected error while evaluating filter indents: %s", err) - } - - // trim the tabs from what we've peeked into; no longer using TrimSpace as that would trim spaces and newlines - if len(strings.Trim(indents, "\t")) != 0 { - l.emit(tFilterEnd) - return lexGohtLineStart - } - - l.skipAhead(indent) - - return lexFilterContent(indent, textType) - } -} - -func lexFilterContent(indent int, textType tokenType) lexFn { - return func(l *lexer) lexFn { - l.acceptUntil("#\n\r") - if l.peek() == '#' { - return lexFilterDynamicText(textType, lexFilterContent(indent, textType)) - } - l.acceptRun("\n\r") - if l.current() != "" { - l.emit(textType) - } - return lexFilterLineStart(indent, textType) - } -} - -func lexFilterDynamicText(textType tokenType, next lexFn) lexFn { - return func(l *lexer) lexFn { - if s, err := l.peekAhead(2); err != nil { - return l.errorf("unexpected error: %s", err) - } else if s != "#{" { - l.next() - return next - } - if l.current() != "" { - l.emit(textType) - } - l.skipRun("#{") - r := continueToMatchingBrace(l, '}') - if r == scanner.EOF { - return l.errorf("dynamic text value was not closed: eof") - } - l.backup() - l.emit(tDynamicText) - l.skip() // skip closing brace - return next - } + return next } func continueToMatchingQuote(l *lexer, typ tokenType, captureQuotes bool) rune { @@ -830,3 +210,61 @@ func continueToMatchingBrace(l *lexer, endBrace rune) rune { } } } + +func ignoreIndentedLines(indent int, next lexFn) lexFn { + return func(l *lexer) lexFn { + switch l.peek() { + case '\n', '\r': + l.skip() + return ignoreIndentedLines(indent, next) + case ' ', '\t': + priorIndents := l.peekAhead(indent) + // if err != nil { + // return l.errorf("unexpected error while evaluating indents: %s", err) + // } + if len(strings.TrimSpace(priorIndents)) != 0 { + return next + } + // validate we have the correct indents + if lexErr := l.validateIndent(priorIndents); lexErr != nil { + return lexErr + } + l.skipUntil("\n\r") + return ignoreIndentedLines(indent, next) + case scanner.EOF: + l.emit(tEOF) + return nil + default: + return next + } + } +} + +func acceptIndentedLines(indent int, next lexFn) lexFn { + return func(l *lexer) lexFn { + switch l.peek() { + case '\n', '\r': + l.next() + return ignoreIndentedLines(indent, next) + case ' ', '\t': + priorIndents := l.peekAhead(indent) + // if err != nil { + // return l.errorf("unexpected error while evaluating indents: %s", err) + // } + if len(strings.TrimSpace(priorIndents)) != 0 { + return next + } + // validate we have the correct indents + if lexErr := l.validateIndent(priorIndents); lexErr != nil { + return lexErr + } + l.acceptUntil("\n\r") + return ignoreIndentedLines(indent, next) + case scanner.EOF: + l.emit(tEOF) + return nil + default: + return next + } + } +} diff --git a/compiler/lexers_haml.go b/compiler/lexers_haml.go new file mode 100644 index 0000000..6ceb738 --- /dev/null +++ b/compiler/lexers_haml.go @@ -0,0 +1,592 @@ +package compiler + +import ( + "slices" + "strings" + "text/scanner" +) + +func lexHamlLineStart(l *lexer) lexFn { + switch l.peek() { + case '}': + l.emit(tTemplateEnd) + l.skip() + return lexGoLineStart + case scanner.EOF: + l.emit(tEOF) + return nil + case '\n', '\r': + return lexHamlLineEnd + default: + return lexHamlIndent + } +} + +func lexHamlIndent(l *lexer) lexFn { + // accept spaces and tabs so that we can report about improper indentation + l.acceptRun(" \t") + indent := l.current() + + // there has not been any indentation yet + if l.indent == 0 && len(indent) == 0 { + // return an error that indents are required + return l.errorf("haml templates must be indented") + } + + // validate the indent against the sequence and char + if lexHamlErr := l.validateIndent(indent); lexHamlErr != nil { + return lexHamlErr + } + + l.indent = len(l.current()) // useful for parsing filters + l.emit(tIndent) + return lexHamlContentStart +} + +func lexHamlContentStart(l *lexer) lexFn { + switch l.peek() { + case '%': + return lexHamlTag + case '#': + return lexHamlId + case '.': + return lexHamlClass + case '\\': + l.skip() + return lexHamlTextStart + case '!': + if s := l.peekAhead(3); s == "!!!" { + // TODO return an error if we're nesting doctypes + return lexHamlDoctype + } + return lexHamlUnescaped + case '-': + return lexHamlSilentScript + case '=': + return lexHamlOutputCode + case '/': + return lexHamlComment + case ':': + return lexHamlFilterStart + case '{': + return lexHamlAttributesStart + case scanner.EOF, '\n', '\r': + return lexHamlLineEnd + default: + return lexHamlTextStart + } +} + +func lexHamlContent(l *lexer) lexFn { + switch l.peek() { + case '#': + return lexHamlId + case '.': + return lexHamlClass + case '[': + return lexHamlObjectReference + case '{': + return lexHamlAttributesStart + case '!': + return lexHamlUnescaped + case '-': + return lexHamlSilentScript + case '=': + return lexHamlOutputCode + case '/': + return lexHamlVoidTag + case '>', '<': + return lexHamlWhitespaceRemoval + case scanner.EOF, '\n', '\r': + return lexHamlLineEnd + default: + return lexHamlTextStart + } +} + +func lexHamlContentEnd(l *lexer) lexFn { + switch l.peek() { + case '=': + return lexHamlOutputCode + case '/': + return lexHamlVoidTag + case '>', '<': + return lexHamlWhitespaceRemoval + case scanner.EOF, '\n', '\r': + return lexHamlLineEnd + default: + return lexHamlTextStart + } +} + +func lexHamlLineEnd(l *lexer) lexFn { + l.skipRun(" \t") + + switch l.peek() { + case '\n', '\r': + return lexHamlNewLine + case scanner.EOF: + l.emit(tEOF) + return nil + default: + return l.errorf("unexpected character: %q", l.peek()) + } +} + +func lexHamlNewLine(l *lexer) lexFn { + l.acceptRun("\n\r") + l.emit(tNewLine) + return lexHamlLineStart +} + +func hamlIdentifier(typ tokenType, l *lexer) lexFn { + l.skip() // eat symbol + + // these characters may follow an identifier + const mayFollowIdentifier = "%#.[{=!/<> \t\n\r" + + l.acceptUntil(mayFollowIdentifier) + if l.current() == "" { + return l.errorf("%s identifier expected", typ) + } + l.emit(typ) + return lexHamlContent +} + +func lexHamlTag(l *lexer) lexFn { + return hamlIdentifier(tTag, l) +} + +func lexHamlId(l *lexer) lexFn { + return hamlIdentifier(tId, l) +} + +func lexHamlClass(l *lexer) lexFn { + return hamlIdentifier(tClass, l) +} + +func lexHamlObjectReference(l *lexer) lexFn { + l.skip() // eat opening bracket + r := continueToMatchingBrace(l, ']') + if r == scanner.EOF { + return l.errorf("object reference not closed: eof") + } + l.backup() + l.emit(tObjectRef) + l.skip() // skip closing bracket + return lexHamlContent +} + +func lexHamlAttributesStart(l *lexer) lexFn { + l.skip() + return lexHamlAttribute +} + +func lexHamlAttributesEnd(l *lexer) lexFn { + l.skip() + return lexHamlContent +} + +func lexHamlAttribute(l *lexer) lexFn { + // supported attributes + // key + // key:value + // key?value + // @attributes: []any (string, map[string]string, map[string]bool) + + l.skipRun(", \t\n\r") + + switch l.peek() { + case '}': + return lexHamlAttributesEnd + case '@': + return lexHamlAttributeCommandStart + default: + return lexHamlAttributeName + } +} + +func lexHamlAttributeName(l *lexer) lexFn { + if l.peek() == '"' || l.peek() == '`' { + r := continueToMatchingQuote(l, tAttrName, false) + if r == scanner.EOF { + return l.errorf("attribute name not closed: eof") + } else if r != '"' && r != '`' { + return l.errorf("unexpected character: %q", r) + } + } else { + l.acceptUntil("?:,}{\" \t\n\r") + if l.current() == "" { + return l.errorf("attribute name expected") + } + l.emit(tAttrName) + } + + l.skipRun(" \t\n\r") + switch l.peek() { + case '?', ':': + return lexHamlAttributeOperator + case ',', '}': + return lexHamlAttributeEnd + default: + return l.errorf("unexpected character: %q", l.peek()) + } +} + +func lexHamlAttributeOperator(l *lexer) lexFn { + l.skipRun(" \t\n\r") + switch l.peek() { + case '?', ':': + l.next() + l.emit(tAttrOperator) + return lexHamlAttributeValue + } + return l.errorf("unexpected character: %q", l.peek()) +} + +func lexHamlAttributeValue(l *lexer) lexFn { + l.skipRun(" \t\n\r") + + switch l.peek() { + case '"', '`': + return lexHamlAttributeStaticValue + case '#': + return lexHamlAttributeDynamicValue + } + return l.errorf("unexpected character: %q", l.peek()) +} + +func lexHamlAttributeStaticValue(l *lexer) lexFn { + r := continueToMatchingQuote(l, tAttrEscapedValue, true) + if r == scanner.EOF { + return l.errorf("attribute value not closed: eof") + } else if r != '"' && r != '`' { + return l.errorf("unexpected character: %q", r) + } + return lexHamlAttributeEnd +} + +func lexHamlAttributeDynamicValue(l *lexer) lexFn { + l.skip() // skip hash + if l.peek() != '{' { + return l.errorf("unexpected character: %q", l.peek()) + } + l.skip() // skip opening brace + r := continueToMatchingBrace(l, '}') + if r == scanner.EOF { + return l.errorf("attribute value not closed: eof") + } + l.backup() + l.emit(tAttrDynamicValue) + l.skip() // skip closing brace + return lexHamlAttributeEnd +} + +func lexHamlAttributeCommandStart(l *lexer) lexFn { + l.skipRun("@") + l.acceptUntil(": \t\n\r") + if l.current() == "" { + return l.errorf("command code expected") + } + switch l.current() { + case "attributes": + return lexHamlAttributeCommand(tAttributesCommand) + default: + return l.errorf("unknown attribute command: %s", l.current()) + } +} + +func lexHamlAttributeCommand(command tokenType) lexFn { + return func(l *lexer) lexFn { + l.ignore() + l.skipUntil(":") + l.skipUntil("{") + l.skip() // skip opening brace + r := continueToMatchingBrace(l, '}') + if r == scanner.EOF { + return l.errorf("attribute value not closed: eof") + } + l.backup() + l.emit(command) + l.skip() // skip closing brace + + return lexHamlAttributeEnd + } +} + +func lexHamlAttributeEnd(l *lexer) lexFn { + l.skipRun(" \t\n\r") + switch l.peek() { + case ',': + l.skip() + return lexHamlAttribute + case '}': + return lexHamlAttributesEnd + default: + return l.errorf("unexpected character: %c", l.peek()) + } +} + +func lexHamlWhitespaceRemoval(l *lexer) lexFn { + direction := l.skip() + switch direction { + case '>': + l.emit(tNukeOuterWhitespace) + case '<': + l.emit(tNukeInnerWhitespace) + default: + return l.errorf("unexpected character: %q", direction) + } + return lexHamlContentEnd +} + +func lexHamlTextStart(l *lexer) lexFn { + l.skipRun(" \t") + return lexHamlTextContent +} + +func lexHamlTextContent(l *lexer) lexFn { + l.acceptUntil("\\#\n\r") + switch l.peek() { + case '\\': + isHashComing := l.peekAhead(2) + if isHashComing == "\\#" { + l.skip() + // was the backslash being escaped? + if !strings.HasSuffix(l.current(), "\\") { + l.next() + } + } else { + l.next() + } + return lexHamlTextContent + case '#': + return lexHamlDynamicText + default: + if l.current() != "" { + l.emit(tPlainText) + } + return lexHamlLineEnd + } +} + +func lexHamlDynamicText(l *lexer) lexFn { + if s := l.peekAhead(2); s != "#{" { + l.next() + return lexHamlTextContent + } + if l.current() != "" { + l.emit(tPlainText) + } + l.skipRun("#{") + r := continueToMatchingBrace(l, '}') + if r == scanner.EOF { + return l.errorf("dynamic text value was not closed: eof") + } + l.backup() + l.emit(tDynamicText) + l.skip() // skip closing brace + return lexHamlTextContent +} + +func lexHamlDoctype(l *lexer) lexFn { + l.skipRun("! ") + l.acceptUntil("\n\r") + l.emit(tDoctype) + return lexHamlLineEnd +} + +func lexHamlUnescaped(l *lexer) lexFn { + l.skip() + l.ignore() + l.emit(tUnescaped) + switch l.peek() { + case '=': + return lexHamlOutputCode + default: + return lexHamlTextStart + } +} + +func lexHamlSilentScript(l *lexer) lexFn { + l.skip() // eat dash + + // ruby style comment + if l.peek() == '#' { + // ignore the rest of the line + l.skipUntil("\n\r") + l.emit(tRubyComment) + return ignoreIndentedLines(l.indent+1, lexHamlLineStart) + } + + l.skipRun(" \t") + l.acceptUntil("\n\r") + // TODO: Support multiline silent scripts when they end with a backslash or comma + // example: + // - foo = bar \ + // + baz + // - foo = bigCall( \ + // bar, + // baz, + // ) + // Extended lines must be indented once. + // Additional indentation is captured and emitted with the script + l.emit(tSilentScript) + return lexHamlLineEnd +} + +func lexHamlOutputCode(l *lexer) lexFn { + l.skipRun("= \t") + switch l.peek() { + case '@': + return lexHamlCommandCode + default: + l.acceptUntil("\n\r") + // TODO: Support multiline output code when they end with a backslash or comma + // see the comments in lexHamlSilentScript + l.emit(tScript) + return lexHamlLineEnd + } +} + +func lexHamlComment(l *lexer) lexFn { + l.skipRun("/ \t") + l.acceptUntil("\n\r") + l.emit(tComment) + return lexHamlLineEnd +} + +func lexHamlVoidTag(l *lexer) lexFn { + l.skipRun("/ \t") + l.acceptUntil("\n\r") + if l.current() != "" { + l.ignore() + return l.errorf("self-closing tags can't have content") + } + l.emit(tVoidTag) + return lexHamlLineEnd +} + +func lexHamlCommandCode(l *lexer) lexFn { + l.skipRun("@") + l.acceptUntil("() \t\n\r") + if l.current() == "" { + return l.errorf("command code expected") + } + switch l.current() { + case "render": + l.acceptRun("() \t") + l.ignore() + l.acceptUntil("\n\r") + if l.current() == "" { + return l.errorf("render argument expected") + } + l.emit(tRenderCommand) + case "children": + l.acceptRun("() \t") + l.ignore() + l.acceptUntil("\n\r") + if l.current() != "" { + return l.errorf("children command does not accept arguments") + } + l.emit(tChildrenCommand) + } + l.skipRun("\n\r") + return lexHamlLineStart +} + +var hamlFilters = []string{"javascript", "css", "plain", "escaped", "preserve"} + +func lexHamlFilterStart(l *lexer) lexFn { + l.skipRun(": \t") + l.acceptUntil(" \t\n\r") + if l.current() == "" { + return l.errorf("filter name expected") + } + if !slices.Contains(hamlFilters, l.current()) { + return l.errorf("unknown filter: %s", l.current()) + } + filter := l.current() + l.emit(tFilterStart) + l.skipUntil("\n\r") // ignore the rest of the current line + l.skipRun("\n\r") // split so we don't consume the indent on the next line + + switch filter { + case "javascript", "css", "plain": + return lexHamlFilterLineStart(l.indent+1, tPlainText) + case "escaped": + return lexHamlFilterLineStart(l.indent+1, tEscapedText) + case "preserve": + return lexHamlFilterLineStart(l.indent+1, tPreserveText) + } + return lexHamlLineEnd +} + +func lexHamlFilterLineStart(indent int, textType tokenType) lexFn { + return func(l *lexer) lexFn { + switch l.peek() { + case ' ', '\t': + return lexHamlFilterIndent(indent, textType) + case scanner.EOF: + l.emit(tEOF) + return nil + default: + l.emit(tFilterEnd) + return lexHamlLineStart + } + } +} + +func lexHamlFilterIndent(indent int, textType tokenType) lexFn { + return func(l *lexer) lexFn { + var indents string + + // peeking first, in case we've reached the end of the filter + indents = l.peekAhead(indent) + + // trim the tabs from what we've peeked into; no longer using TrimSpace as that would trim spaces and newlines + if len(strings.Trim(indents, "\t")) != 0 { + l.emit(tFilterEnd) + return lexHamlLineStart + } + + l.skipAhead(indent) + + return lexHamlFilterContent(indent, textType) + } +} + +func lexHamlFilterContent(indent int, textType tokenType) lexFn { + return func(l *lexer) lexFn { + l.acceptUntil("#\n\r") + if l.peek() == '#' { + return lexHamlFilterDynamicText(textType, lexHamlFilterContent(indent, textType)) + } + l.acceptRun("\n\r") + if l.current() != "" { + l.emit(textType) + } + return lexHamlFilterLineStart(indent, textType) + } +} + +func lexHamlFilterDynamicText(textType tokenType, next lexFn) lexFn { + return func(l *lexer) lexFn { + if s := l.peekAhead(2); s != "#{" { + l.next() + return next + } + if l.current() != "" { + l.emit(textType) + } + l.skipRun("#{") + r := continueToMatchingBrace(l, '}') + if r == scanner.EOF { + return l.errorf("dynamic text value was not closed: eof") + } + l.backup() + l.emit(tDynamicText) + l.skip() // skip closing brace + return next + } +} diff --git a/compiler/lexers_slim.go b/compiler/lexers_slim.go new file mode 100644 index 0000000..fa0009f --- /dev/null +++ b/compiler/lexers_slim.go @@ -0,0 +1,647 @@ +package compiler + +import ( + "slices" + "strings" + "text/scanner" +) + +func lexSlimLineStart(l *lexer) lexFn { + switch l.peek() { + case '}': + l.emit(tTemplateEnd) + l.skip() + return lexGoLineStart + case scanner.EOF: + l.emit(tEOF) + return nil + case '\n', '\r': + return lexSlimLineEnd + default: + return lexSlimIndent + } +} + +func lexSlimIndent(l *lexer) lexFn { + // accept spaces and tabs so that we can report about improper indentation + l.acceptRun(" \t") + indent := l.current() + + // there has not been any indentation yet + if l.indent == 0 && len(indent) == 0 { + // return an error that indents are required + return l.errorf("slim templates must be indented") + } + + // validate the indent against the sequence and char + if lexSlimErr := l.validateIndent(indent); lexSlimErr != nil { + return lexSlimErr + } + + l.indent = len(l.current()) // useful for parsing filters + l.emit(tIndent) + return lexSlimContentStart +} + +func lexSlimContentStart(l *lexer) lexFn { + switch p := l.peek(); p { + case '#': + return lexSlimId + case '.': + return lexSlimClass + case '-': + return lexSlimControlCode + case '=': + return lexSlimOutputCode + case '/': + return lexSlimComment + case ':': + return lexSlimFilterStart + case '|': + return lexSlimTextBlock + case '{': + return lexSlimAttributesStart + case scanner.EOF, '\n', '\r': + return lexSlimLineEnd + default: + // if the next character is a letter, we're starting a tag + if isLetter(p) { + return lexSlimTag + } + return l.errorf("unexpected character: %q", p) + } +} + +func isLetter(r rune) bool { + return r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' +} + +func lexSlimContent(l *lexer) lexFn { + switch l.peek() { + case '#': + return lexSlimId + case '.': + return lexSlimClass + case '{': + return lexSlimAttributesStart + case '-': + return lexSlimControlCode + case '=': + return lexSlimOutputCode + case '/': + return lexSlimCloseTag + case '>', '<': + return lexSlimWhitespaceAddition + case ':': + return lexSlimInlineTag + case ' ', '\t': + l.skip() + return lexSlimTextBlockContent(l.indent+1, 0, tPlainText) + case scanner.EOF, '\n', '\r': + return lexSlimLineEnd + default: + return lexSlimTextBlockContent(l.indent+1, 0, tPlainText) + } +} + +func lexSlimLineEnd(l *lexer) lexFn { + l.skipRun(" \t") + + switch l.peek() { + case '\n', '\r': + return lexSlimNewLine + case scanner.EOF: + l.emit(tEOF) + return nil + default: + return l.errorf("unexpected character: %q", l.peek()) + } +} + +func lexSlimNewLine(l *lexer) lexFn { + l.acceptRun("\n\r") + l.emit(tNewLine) + return lexSlimLineStart +} + +func slimIdentifier(typ tokenType, l *lexer) lexFn { + if typ != tTag { + l.skip() // eat symbol + } + + // these characters may follow an identifier + const mayFollowIdentifier = "#.{=!/<>: \t\n\r" + + l.acceptUntil(mayFollowIdentifier) + if l.current() == "" { + return l.errorf("%s identifier expected", typ) + } + if l.current() == "doctype" { + return lexSlimDoctype + } + l.emit(typ) + return lexSlimContent +} + +func lexSlimDoctype(l *lexer) lexFn { + l.ignore() + l.skipRun(" \t") + l.acceptUntil("\n\r") + l.emit(tDoctype) + return lexSlimLineEnd +} + +func lexSlimTag(l *lexer) lexFn { + return slimIdentifier(tTag, l) +} + +func lexSlimId(l *lexer) lexFn { + return slimIdentifier(tId, l) +} + +func lexSlimClass(l *lexer) lexFn { + return slimIdentifier(tClass, l) +} + +func lexSlimInlineTag(l *lexer) lexFn { + l.skip() // eat colon + l.skipRun(" \t") + return lexSlimContentStart +} + +func lexSlimControlCode(l *lexer) lexFn { + l.skip() // eat dash + + l.skipRun(" \t") + l.acceptUntil("\\\n\r") + // support long code lines split across multiple lines + if n := l.peek(); n == '\\' || strings.HasSuffix(l.current(), ",") { + if n == '\\' { + l.skip() + } + return lexSlimCodeBlockContent(l.indent+1, tSilentScript) + } + l.emit(tSilentScript) + return lexSlimLineEnd +} + +func lexSlimOutputCode(l *lexer) lexFn { + l.skip() // eat equals + // if next character is an equals sign, then this content is not escaped + if l.peek() == '=' { + l.skip() + l.emit(tUnescaped) + } + l.skipRun(" \t") + switch l.peek() { + case '@': + return lexSlimCommandCode + default: + l.acceptUntil("\\\n\r") + if n := l.peek(); n == '\\' || strings.HasSuffix(l.current(), ",") { + if n == '\\' { + l.skip() + } + return lexSlimCodeBlockContent(l.indent+1, tScript) + } + l.emit(tScript) + return lexSlimLineEnd + } +} + +func lexSlimComment(l *lexer) lexFn { + l.skip() // eat slash + if l.peek() != '!' { + // ignore the rest of the line + l.skipUntil("\n\r") + l.emit(tRubyComment) + return ignoreIndentedLines(l.indent+1, lexSlimLineStart) + } + + l.skip() // eat bang + l.skipRun(" \t") + return lexSlimTextBlockContent(l.indent+1, 0, tComment) +} + +func lexSlimTextBlock(l *lexer) lexFn { + l.skip() // eat pipe + // test for a space after the pipe + if n := l.peek(); n == ' ' || n == '\t' { + return lexSlimTextBlockContent(l.indent+1, 1, tPlainText) + } + return lexSlimTextBlockContent(l.indent+1, 0, tPlainText) +} + +func lexSlimTextBlockLineStart(indent int, spaces int, textType tokenType) lexFn { + return func(l *lexer) lexFn { + switch l.peek() { + case ' ', '\t': + return lexSlimTextBlockIndent(indent, spaces, textType) + case scanner.EOF: + l.emit(tEOF) + return nil + default: + if l.current() != "" { + l.emit(textType) + } + return lexSlimLineStart + } + } +} + +func lexSlimTextBlockIndent(indent int, spaces int, textType tokenType) lexFn { + return func(l *lexer) lexFn { + // only accept the whitespace that belongs to the indent + + // peeking first, in case we've reached the end of the filter + indents := l.peekAhead(indent) + + if len(strings.Trim(indents, "\t")) != 0 { + if l.current() != "" { + l.emit(textType) + } + return lexSlimLineStart + } + + l.skipAhead(indent) + + return lexSlimTextBlockContent(indent, spaces, textType) + } +} + +func lexSlimTextBlockContent(indent int, spaces int, textType tokenType) lexFn { + return func(l *lexer) lexFn { + if spaces != 0 { + if n := l.peek(); n == ' ' || n == '\t' { + l.skip() // eat space + } + } + l.acceptUntil("#\n\r") + if len(l.current()) > 0 { + l.emit(textType) + } + + if l.peek() == '#' && !strings.HasSuffix(l.current(), "\\") { + // l.emit(textType) + return lexSlimFilterDynamicText(textType, lexSlimTextBlockContent(indent, spaces, textType)) + } + l.acceptRun("\n\r") + if l.current() != "" { + l.emit(tNewLine) + } + return lexSlimTextBlockLineStart(indent, spaces, textType) + } +} + +func lexSlimCodeBlockLineStart(indent int, textType tokenType) lexFn { + return func(l *lexer) lexFn { + switch l.peek() { + case ' ', '\t': + return lexSlimCodeBlockIndent(indent, textType) + case scanner.EOF: + l.emit(tEOF) + return nil + default: + if l.current() != "" { + l.emit(textType) + } + return lexSlimLineStart + } + } +} + +func lexSlimCodeBlockIndent(indent int, textType tokenType) lexFn { + return func(l *lexer) lexFn { + // only accept the whitespace that belongs to the indent + + // peeking first, in case we've reached the end of the block + indents := l.peekAhead(indent) + + if len(strings.Trim(indents, "\t")) != 0 { + if l.current() != "" { + l.emit(textType) + } + return lexSlimLineStart + } + + l.skipAhead(indent) + + return lexSlimCodeBlockContent(indent, textType) + } +} + +func lexSlimCodeBlockContent(indent int, textType tokenType) lexFn { + return func(l *lexer) lexFn { + l.acceptUntil("\n\r") + l.skipRun("\n\r") + return lexSlimCodeBlockLineStart(indent, textType) + } +} + +func lexSlimCloseTag(l *lexer) lexFn { + l.skip() // eat slash + l.skipRun(" \t") + l.acceptUntil("\n\r") + if l.current() != "" { + return l.errorf("no content expected") + } + l.emit(tVoidTag) + return lexSlimLineEnd +} + +func lexSlimCommandCode(l *lexer) lexFn { + l.skipRun("@") + l.acceptUntil("() \t\n\r") + if l.current() == "" { + return l.errorf("command code expected") + } + switch l.current() { + case "render": + l.acceptRun("() \t") + l.ignore() + l.acceptUntil("\\\n\r") + if l.current() == "" { + return l.errorf("render argument expected") + } + if n := l.peek(); n == '\\' || strings.HasSuffix(l.current(), ",") { + if n == '\\' { + l.skip() + } + return lexSlimCodeBlockContent(l.indent+1, tRenderCommand) + } + l.emit(tRenderCommand) + case "children": + l.acceptRun("() \t") + l.ignore() + l.acceptUntil("\n\r") + if l.current() != "" { + return l.errorf("children command does not accept arguments") + } + l.emit(tChildrenCommand) + } + l.skipRun("\n\r") + return lexSlimLineStart +} + +var slimFilters = []string{"javascript", "css"} + +func lexSlimFilterStart(l *lexer) lexFn { + l.skipRun(": \t") + l.acceptUntil(" \t\n\r") + if l.current() == "" { + return l.errorf("filter name expected") + } + if !slices.Contains(slimFilters, l.current()) { + return l.errorf("unknown filter: %s", l.current()) + } + filter := l.current() + l.emit(tFilterStart) + l.skipUntil("\n\r") // ignore the rest of the current line + l.skipRun("\n\r") // split so we don't consume the indent on the next line + + switch filter { + case "javascript", "css": + return lexSlimFilterLineStart(l.indent+1, tPlainText) + } + return lexSlimLineEnd +} + +func lexSlimFilterLineStart(indent int, textType tokenType) lexFn { + return func(l *lexer) lexFn { + switch l.peek() { + case ' ', '\t': + return lexSlimFilterIndent(indent, textType) + case scanner.EOF: + l.emit(tEOF) + return nil + default: + l.emit(tFilterEnd) + return lexSlimLineStart + } + } +} + +func lexSlimFilterIndent(indent int, textType tokenType) lexFn { + return func(l *lexer) lexFn { + var indents string + + // peeking first, in case we've reached the end of the filter + indents = l.peekAhead(indent) + + // trim the tabs from what we've peeked into; no longer using TrimSpace as that would trim spaces and newlines + if len(strings.Trim(indents, "\t")) != 0 { + l.emit(tFilterEnd) + return lexSlimLineStart + } + + l.skipAhead(indent) + + return lexSlimFilterContent(indent, textType) + } +} + +func lexSlimFilterContent(indent int, textType tokenType) lexFn { + return func(l *lexer) lexFn { + l.acceptUntil("#\n\r") + // we have reached some interpolation as long as it wasn't escaped + if l.peek() == '#' && !strings.HasSuffix(l.current(), "\\") { + return lexSlimFilterDynamicText(textType, lexSlimFilterContent(indent, textType)) + } + l.acceptRun("\n\r") + if l.current() != "" { + l.emit(textType) + } + return lexSlimFilterLineStart(indent, textType) + } +} + +// lexSlimFilterDynamicText parses out dynamic text values within a filter block. +func lexSlimFilterDynamicText(textType tokenType, next lexFn) lexFn { + return func(l *lexer) lexFn { + if s := l.peekAhead(2); s != "#{" { + l.next() + return next + } + if l.current() != "" { + l.emit(textType) + } + l.skipRun("#{") + r := continueToMatchingBrace(l, '}') + if r == scanner.EOF { + return l.errorf("dynamic text value was not closed: eof") + } + l.backup() + l.emit(tDynamicText) + l.skip() // skip closing brace + return next + } +} + +// Parsing the Slim attributes the same as the Haml attributes + +func lexSlimAttributesStart(l *lexer) lexFn { + l.skip() + return lexSlimAttribute +} + +func lexSlimAttributesEnd(l *lexer) lexFn { + l.skip() + return lexSlimContent +} + +func lexSlimAttribute(l *lexer) lexFn { + // supported attributes + // key + // key:value + // key?value + // @attributes: []any (string, map[string]string, map[string]bool) + + l.skipRun(", \t\n\r") + + switch l.peek() { + case '}': + return lexSlimAttributesEnd + case '@': + return lexSlimAttributeCommandStart + default: + return lexSlimAttributeName + } +} + +func lexSlimAttributeName(l *lexer) lexFn { + if l.peek() == '"' || l.peek() == '`' { + r := continueToMatchingQuote(l, tAttrName, false) + if r == scanner.EOF { + return l.errorf("attribute name not closed: eof") + } else if r != '"' && r != '`' { + return l.errorf("unexpected character: %q", r) + } + } else { + l.acceptUntil("?:,}{\" \t\n\r") + if l.current() == "" { + return l.errorf("attribute name expected") + } + l.emit(tAttrName) + } + + l.skipRun(" \t\n\r") + switch l.peek() { + case '?', ':': + return lexSlimAttributeOperator + case ',', '}': + return lexSlimAttributeEnd + default: + return l.errorf("unexpected character: %q", l.peek()) + } +} + +func lexSlimAttributeOperator(l *lexer) lexFn { + l.skipRun(" \t\n\r") + switch l.peek() { + case '?', ':': + l.next() + l.emit(tAttrOperator) + return lexSlimAttributeValue + } + return l.errorf("unexpected character: %q", l.peek()) +} + +func lexSlimAttributeValue(l *lexer) lexFn { + l.skipRun(" \t\n\r") + + switch l.peek() { + case '"', '`': + return lexSlimAttributeStaticValue + case '#': + return lexSlimAttributeDynamicValue + } + return l.errorf("unexpected character: %q", l.peek()) +} + +func lexSlimAttributeStaticValue(l *lexer) lexFn { + r := continueToMatchingQuote(l, tAttrEscapedValue, true) + if r == scanner.EOF { + return l.errorf("attribute value not closed: eof") + } else if r != '"' && r != '`' { + return l.errorf("unexpected character: %q", r) + } + return lexSlimAttributeEnd +} + +func lexSlimAttributeDynamicValue(l *lexer) lexFn { + l.skip() // skip hash + if l.peek() != '{' { + return l.errorf("unexpected character: %q", l.peek()) + } + l.skip() // skip opening brace + r := continueToMatchingBrace(l, '}') + if r == scanner.EOF { + return l.errorf("attribute value not closed: eof") + } + l.backup() + l.emit(tAttrDynamicValue) + l.skip() // skip closing brace + return lexSlimAttributeEnd +} + +func lexSlimAttributeCommandStart(l *lexer) lexFn { + l.skipRun("@") + l.acceptUntil(": \t\n\r") + if l.current() == "" { + return l.errorf("command code expected") + } + switch l.current() { + case "attributes": + return lexSlimAttributeCommand(tAttributesCommand) + default: + return l.errorf("unknown attribute command: %s", l.current()) + } +} + +func lexSlimAttributeCommand(command tokenType) lexFn { + return func(l *lexer) lexFn { + l.ignore() + l.skipUntil(":") + l.skipUntil("{") + l.skip() // skip opening brace + r := continueToMatchingBrace(l, '}') + if r == scanner.EOF { + return l.errorf("attribute value not closed: eof") + } + l.backup() + l.emit(command) + l.skip() // skip closing brace + + return lexSlimAttributeEnd + } +} + +func lexSlimAttributeEnd(l *lexer) lexFn { + l.skipRun(" \t\n\r") + switch l.peek() { + case ',': + l.skip() + return lexSlimAttribute + case '}': + return lexSlimAttributesEnd + default: + return l.errorf("unexpected character: %c", l.peek()) + } +} + +// TODO: Drop support for keeping whitespace; Also update Haml parsing to eat whitespace. +func lexSlimWhitespaceAddition(l *lexer) lexFn { + l.skip() + l.skipRun(" \t") + switch l.peek() { + case '>': + l.skip() + l.emit(tNukeOuterWhitespace) + case '<': + l.skip() + l.emit(tNukeInnerWhitespace) + default: + return l.errorf("unexpected character: %q", l.peek()) + } + return lexSlimContent +} diff --git a/compiler/nodes.go b/compiler/nodes.go index 81ca278..f8894e0 100644 --- a/compiler/nodes.go +++ b/compiler/nodes.go @@ -18,7 +18,7 @@ const ( nError nodeType = iota nRoot nGoCode - nGoht + nTemplate nIndent nDoctype nElement @@ -41,8 +41,8 @@ func (n nodeType) String() string { return "Root" case nGoCode: return "GoCode" - case nGoht: - return "Goht" + case nTemplate: + return "Template" case nIndent: return "Indent" case nDoctype: @@ -175,6 +175,8 @@ func (n *node) handleNode(p *parser, indent int) error { p.addChild(NewDoctypeNode(p.next())) case tTag, tId, tClass: p.addNode(NewElementNode(p.next(), indent)) + case tAttrName: + p.addNode(NewElementNode(p.peek(), indent)) case tComment: p.addNode(NewCommentNode(p.next(), indent)) case tUnescaped: @@ -201,8 +203,10 @@ func (n *node) handleNode(p *parser, indent int) error { default: return n.errorf("unknown filter: %s", t) } - case tGohtEnd: - return p.backToType(nGoht) + // case tGohtEnd: + // return p.backToType(nGoht) + case tTemplateEnd: + return p.backToType(nTemplate) case tEOF: return n.errorf("template is incomplete: %s", p.peek()) case tError: @@ -237,7 +241,10 @@ func NewRootNode() *RootNode { } func (n *RootNode) Source(tw *templateWriter) error { - if _, err := tw.Write("// Code generated by GoHT - DO NOT EDIT.\n// https://github.com/stackus/goht\n\n"); err != nil { + if _, err := tw.Write( + fmt.Sprintf("// Code generated by GoHT %s - DO NOT EDIT.\n// https://github.com/stackus/goht\n\n", + goht.Version(), + )); err != nil { return err } @@ -308,8 +315,8 @@ func (n *RootNode) parse(p *parser) error { n.addImport(t) case tGoCode, tNewLine: p.addNode(NewCodeNode(p.next())) - case tGohtStart: - p.addNode(NewGohtNode(p.next())) + case tTemplateStart: + p.addNode(NewTemplateNode(p.next())) case tEOF: p.next() return nil @@ -357,26 +364,26 @@ func (n *CodeNode) parse(p *parser) error { } n.tokens = append(n.tokens, t) return nil - case tPackage, tImport, tGohtStart, tEOF: + case tPackage, tImport, tTemplateStart, tEOF: return p.backToType(nRoot) default: return n.errorf("unexpected: %s", p.peek()) } } -type GohtNode struct { +type TemplateNode struct { node decl string } -func NewGohtNode(t token) *GohtNode { - return &GohtNode{ - node: newNode(nGoht, -1, t), +func NewTemplateNode(t token) *TemplateNode { + return &TemplateNode{ + node: newNode(nTemplate, -1, t), decl: t.lit, } } -func (n *GohtNode) Source(tw *templateWriter) error { +func (n *TemplateNode) Source(tw *templateWriter) error { entry := ` goht.Template { return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { __buf, __isBuf := __w.(goht.Buffer) @@ -421,9 +428,9 @@ func (n *GohtNode) Source(tw *templateWriter) error { return err } -func (n *GohtNode) parse(p *parser) error { +func (n *TemplateNode) parse(p *parser) error { switch p.peek().Type() { - case tGohtEnd: + case tTemplateEnd: p.next() return p.backToType(nRoot) default: @@ -641,7 +648,7 @@ func (n *ElementNode) renderAttributes(tw *templateWriter) error { if _, err := tw.WriteErrorHandler(); err != nil { return err } - if _, err := tw.WriteStringIndent(vName); err != nil { + if _, err := tw.WriteStringIndent(`" "+` + vName); err != nil { return err } } diff --git a/compiler/parser_test.go b/compiler/nodes_test.go similarity index 87% rename from compiler/parser_test.go rename to compiler/nodes_test.go index fd33035..871418d 100644 --- a/compiler/parser_test.go +++ b/compiler/nodes_test.go @@ -5,18 +5,18 @@ import ( "testing" ) -func Test_GohtParsing(t *testing.T) { +func Test_Parsing(t *testing.T) { tests := map[string]struct { input string want string wantErr bool }{ - "full document": { + "full haml document": { input: `package testing var foo = "bar" -@goht test(title string, err error) { +@haml test(title string, err error) { !!! 5 %html %head @@ -32,7 +32,54 @@ var foo = "bar" `, want: `Root GoCode - Goht + Template + Doctype + NewLine + Element html() + NewLine + Element head() + NewLine + Element title() + Script + Element body() + NewLine + Element p() + Text(S) + Text(D) + Element div() + NewLine + Element p() + Script + SilentScript + Element div() + NewLine + Element p() + Script + GoCode +`, + }, + "full slim document": { + input: `package testing + +var foo = "bar" + +@slim test(title string, err error) { + doctype + html + head + title= title + body + p some text #{foo} + #main-content + p= "Hello World" + - if err != nil + .error + p= "Something went wrong" +} +`, + want: `Root + GoCode + Template Doctype NewLine Element html() @@ -92,7 +139,7 @@ func Test_RootNode(t *testing.T) { }, "simple goht": { input: "package main\n@goht test() {\n}", - want: "Root\n\tGoCode\n\tGoht\n", + want: "Root\n\tGoCode\n\tTemplate\n", }, } @@ -113,7 +160,7 @@ func Test_RootNode(t *testing.T) { } } -func Test_GohtNode(t *testing.T) { +func Test_TemplateNode(t *testing.T) { tests := map[string]struct { input string want string @@ -121,12 +168,12 @@ func Test_GohtNode(t *testing.T) { }{ "empty": { input: "@goht empty() {\n", - want: "Root\n\tGoht\n", + want: "Root\n\tTemplate\n", wantErr: true, }, "simple": { input: "@goht test() {\n\tFoo\n}", - want: "Root\n\tGoht\n\t\tText(S)\n\t\tNewLine\n", + want: "Root\n\tTemplate\n\t\tText(S)\n\t\tNewLine\n", }, } for name, test := range tests { @@ -154,11 +201,11 @@ func Test_TextNode(t *testing.T) { }{ "simple": { input: "@goht test() {\n\tFoo\n}", - want: "Root\n\tGoht\n\t\tText(S)\n\t\tNewLine\n", + want: "Root\n\tTemplate\n\t\tText(S)\n\t\tNewLine\n", }, "with dynamic text": { input: "@goht test() {\n\tFoo #{foo}\n}", - want: "Root\n\tGoht\n\t\tText(S)\n\t\tText(D)\n\t\tNewLine\n", + want: "Root\n\tTemplate\n\t\tText(S)\n\t\tText(D)\n\t\tNewLine\n", }, } for name, test := range tests { @@ -187,7 +234,7 @@ func Test_UnescapeNode(t *testing.T) { "empty": { input: "@goht test() {\n\t!\n}", want: `Root - Goht + Template Unescape NewLine `, @@ -197,7 +244,7 @@ func Test_UnescapeNode(t *testing.T) { ! Foo }`, want: `Root - Goht + Template Unescape Text(S) NewLine @@ -205,11 +252,11 @@ func Test_UnescapeNode(t *testing.T) { }, "dynamic text": { input: "@goht test() {\n\t! #{foo}\n}", - want: "Root\n\tGoht\n\t\tUnescape\n\t\t\tText(D)\n\t\tNewLine\n", + want: "Root\n\tTemplate\n\t\tUnescape\n\t\t\tText(D)\n\t\tNewLine\n", }, "static and dynamic text": { input: "@goht test() {\n\t! Foo #{foo}\n}", - want: "Root\n\tGoht\n\t\tUnescape\n\t\t\tText(S)\n\t\t\tText(D)\n\t\tNewLine\n", + want: "Root\n\tTemplate\n\t\tUnescape\n\t\t\tText(S)\n\t\t\tText(D)\n\t\tNewLine\n", }, "illegal nesting": { input: `@goht test() { @@ -217,7 +264,7 @@ func Test_UnescapeNode(t *testing.T) { bar }`, want: `Root - Goht + Template Element p() Unescape Text(S) @@ -231,7 +278,7 @@ func Test_UnescapeNode(t *testing.T) { %p! This #{html} HTML. }`, want: `Root - Goht + Template SilentScript Element p() Text(S) @@ -270,12 +317,12 @@ func Test_ElementNode(t *testing.T) { }{ "simple": { input: "@goht test() {\n\t%p\n}", - want: "Root\n\tGoht\n\t\tElement p()\n\t\t\tNewLine\n", + want: "Root\n\tTemplate\n\t\tElement p()\n\t\t\tNewLine\n", }, "illegal nesting": { input: "@goht test() {\n\t%p foo\n\t\t%p bar\n}", want: `Root - Goht + Template Element p() Text(S) `, @@ -284,7 +331,7 @@ func Test_ElementNode(t *testing.T) { "unescaped text": { input: "@goht test() {\n\t%p! foo\n}", want: `Root - Goht + Template Element p() Unescape Text(S) @@ -296,7 +343,7 @@ func Test_ElementNode(t *testing.T) { %p bar }`, want: `Root - Goht + Template Element p() Unescape Text(S) @@ -312,7 +359,7 @@ func Test_ElementNode(t *testing.T) { %img{src: "foo.png"} }`, want: `Root - Goht + Template Element p() Text(S) Element img(src="foo.png") @@ -331,7 +378,7 @@ func Test_ElementNode(t *testing.T) { %closed/ }`, want: `Root - Goht + Template Element p() Text(S) Element closed() @@ -344,7 +391,7 @@ func Test_ElementNode(t *testing.T) { }, "with object ref": { input: "@goht test() {\n\t%p[foo]\n}", - want: "Root\n\tGoht\n\t\tElement p()\n\t\t\tNewLine\n", + want: "Root\n\tTemplate\n\t\tElement p()\n\t\t\tNewLine\n", }, } for name, test := range tests { @@ -373,15 +420,23 @@ func Test_ElementAttributes(t *testing.T) { "simple": { input: "@goht test() {\n\t%p{foo:\"bar\"}\n}", want: `Root - Goht + Template Element p(foo="bar") NewLine +`, + }, + "no tag": { + input: "@goht test() {\n\t{foo:\"bar\"}\n}", + want: `Root + Template + Element div(foo="bar") + NewLine `, }, "dynamic attribute": { input: "@goht test() {\n\t%p{foo:#{bar}}\n}", want: `Root - Goht + Template Element p(foo={bar}) NewLine `, @@ -389,7 +444,7 @@ func Test_ElementAttributes(t *testing.T) { "quoted attribute names": { input: "@goht test() {\n\t%p{\"x:foo\":#{bar}, `@fizz`:`b\"uzz`}\n}", want: `Root - Goht + Template Element p(x:foo={bar},@fizz="b\"uzz") NewLine `, @@ -397,7 +452,7 @@ func Test_ElementAttributes(t *testing.T) { "attributes command": { input: "@goht test() {\n\t%p{foo:#{bar}, @attributes:#{list}}\n}", want: `Root - Goht + Template Element p(foo={bar},@attrs={list...}) NewLine `, @@ -410,7 +465,7 @@ func Test_ElementAttributes(t *testing.T) { } }`, want: `Root - Goht + Template Element p(foo={bar},@attrs={list...}) NewLine `, @@ -421,7 +476,7 @@ func Test_ElementAttributes(t *testing.T) { %p bar }`, want: `Root - Goht + Template Element p() Text(S) Element p() @@ -433,7 +488,7 @@ func Test_ElementAttributes(t *testing.T) { .foo{bar} fizz }`, want: `Root - Goht + Template Element div(bar) Text(S) `, @@ -465,14 +520,14 @@ func Test_SilentScriptNode(t *testing.T) { "simple": { input: "@goht test() {\n\t- var foo = \"bar\"\n}", want: `Root - Goht + Template SilentScript `, }, "nested content": { input: "@goht test() {\n\t- var foo = \"bar\"\n\t\t%p= foo\n}", want: `Root - Goht + Template SilentScript Element p() Script @@ -481,7 +536,7 @@ func Test_SilentScriptNode(t *testing.T) { "mixed indents": { input: "@goht test() {\n\t%p1 one\n\t- if foo {\n\t\t%p bar\n\t- }\n\t%p2 two\n}", want: `Root - Goht + Template Element p1() Text(S) SilentScript @@ -495,7 +550,7 @@ func Test_SilentScriptNode(t *testing.T) { "shorter indent": { input: "@goht test() {\n\t%p1\n\t\t- if foo\n\t%p2 two\n}", want: `Root - Goht + Template Element p1() NewLine SilentScript @@ -506,7 +561,7 @@ func Test_SilentScriptNode(t *testing.T) { "ruby style comment": { input: "@goht test() {\n\t-# foo\n\t%p bar\n}", want: `Root - Goht + Template Element p() Text(S) `, @@ -538,7 +593,7 @@ func Test_CommentNode(t *testing.T) { "same line": { input: "@goht test() {\n\t/ foo\n}", want: `Root - Goht + Template Comment NewLine `, @@ -546,7 +601,7 @@ func Test_CommentNode(t *testing.T) { "nested content": { input: "@goht test() {\n\t/\n\t\t%p bar\n}", want: `Root - Goht + Template Comment NewLine Element p() @@ -580,7 +635,7 @@ func Test_ScriptNode(t *testing.T) { "simple": { input: "@goht test() {\n\t= foo\n}", want: `Root - Goht + Template Script NewLine `, @@ -588,7 +643,7 @@ func Test_ScriptNode(t *testing.T) { "after element": { input: "@goht test() {\n\t%p= foo\n}", want: `Root - Goht + Template Element p() Script `, @@ -596,7 +651,7 @@ func Test_ScriptNode(t *testing.T) { "before content": { input: "@goht test() {\n\t= foo\n\t%p bar\n}", want: `Root - Goht + Template Script NewLine Element p() @@ -606,7 +661,7 @@ func Test_ScriptNode(t *testing.T) { "mixed indents": { input: "@goht test() {\n\t%p1\n\t\t%p2= foo\n\t%p3 bar\n}", want: `Root - Goht + Template Element p1() NewLine Element p2() @@ -642,14 +697,14 @@ func Test_RenderNode(t *testing.T) { "simple": { input: "@goht test() {\n\t= @render foo()\n}", want: `Root - Goht + Template RenderCommand `, }, "with children": { input: "@goht test() {\n\t= @render foo()\n\t\t%p bar\n}", want: `Root - Goht + Template RenderCommand Element p() Text(S) @@ -658,7 +713,7 @@ func Test_RenderNode(t *testing.T) { "mixed indents": { input: "@goht test() {\n\t%p1 one\n\t= @render foo()\n\t\t%p bar\n\t%p2 two\n}", want: `Root - Goht + Template Element p1() Text(S) RenderCommand @@ -695,14 +750,14 @@ func Test_ChildrenCommand(t *testing.T) { "simple": { input: "@goht test() {\n\t= @children\n}", want: `Root - Goht + Template ChildrenCommand `, }, "mixed indents": { input: "@goht test() {\n\t%p1 one\n\t%parent\n\t\t= @children\n\t%p2 two\n}", want: `Root - Goht + Template Element p1() Text(S) Element parent() diff --git a/compiler/testdata/attributes.goht.go b/compiler/testdata/attributes.goht.go index 72b076c..b64021d 100644 --- a/compiler/testdata/attributes.goht.go +++ b/compiler/testdata/attributes.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package testdata @@ -41,7 +41,7 @@ func AttributesTest() goht.Template { if __err != nil { return } - if _, __err = __buf.WriteString(__var1); __err != nil { + if _, __err = __buf.WriteString(" " + __var1); __err != nil { return } if _, __err = __buf.WriteString(">\n"); __err != nil { diff --git a/compiler/testdata/comments.goht.go b/compiler/testdata/comments.goht.go index 6b05793..c4e2a3d 100644 --- a/compiler/testdata/comments.goht.go +++ b/compiler/testdata/comments.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package testdata diff --git a/compiler/testdata/conditionals.goht.go b/compiler/testdata/conditionals.goht.go index 11949c7..bf072ca 100644 --- a/compiler/testdata/conditionals.goht.go +++ b/compiler/testdata/conditionals.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package testdata diff --git a/compiler/testdata/elements.goht.go b/compiler/testdata/elements.goht.go index 298e1f9..544af02 100644 --- a/compiler/testdata/elements.goht.go +++ b/compiler/testdata/elements.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package testdata diff --git a/compiler/testdata/filters.goht.go b/compiler/testdata/filters.goht.go index dabf043..8e551e2 100644 --- a/compiler/testdata/filters.goht.go +++ b/compiler/testdata/filters.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package testdata diff --git a/compiler/testdata/imports.goht.go b/compiler/testdata/imports.goht.go index 6ca7668..27237fb 100644 --- a/compiler/testdata/imports.goht.go +++ b/compiler/testdata/imports.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package testdata diff --git a/compiler/testdata/interpolation.goht.go b/compiler/testdata/interpolation.goht.go index 1106383..e5d75d4 100644 --- a/compiler/testdata/interpolation.goht.go +++ b/compiler/testdata/interpolation.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package testdata diff --git a/compiler/testdata/newlines.goht.go b/compiler/testdata/newlines.goht.go index af50403..2965a99 100644 --- a/compiler/testdata/newlines.goht.go +++ b/compiler/testdata/newlines.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package testdata diff --git a/compiler/testdata/obj_references.goht.go b/compiler/testdata/obj_references.goht.go index fa89f7b..42dc461 100644 --- a/compiler/testdata/obj_references.goht.go +++ b/compiler/testdata/obj_references.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package testdata diff --git a/compiler/testdata/package.goht.go b/compiler/testdata/package.goht.go index 443b907..58e386a 100644 --- a/compiler/testdata/package.goht.go +++ b/compiler/testdata/package.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package testdata diff --git a/compiler/testdata/rendering.goht.go b/compiler/testdata/rendering.goht.go index 0be9337..bad7346 100644 --- a/compiler/testdata/rendering.goht.go +++ b/compiler/testdata/rendering.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package testdata diff --git a/compiler/testdata/whitespace.goht.go b/compiler/testdata/whitespace.goht.go index 70666b3..d1a1fb9 100644 --- a/compiler/testdata/whitespace.goht.go +++ b/compiler/testdata/whitespace.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package testdata diff --git a/compiler/tokens.go b/compiler/tokens.go index 0671e5b..155ee10 100644 --- a/compiler/tokens.go +++ b/compiler/tokens.go @@ -22,8 +22,10 @@ const ( tPackage tImport tGoCode - tGohtStart - tGohtEnd + + // Template tokens + tTemplateStart + tTemplateEnd // Goht/Haml tokens tDoctype @@ -71,10 +73,14 @@ func (t tokenType) String() string { return "Import" case tGoCode: return "GoCode" - case tGohtStart: - return "GohtStart" - case tGohtEnd: - return "GohtEnd" + // case tGohtStart: + // return "GohtStart" + // case tGohtEnd: + // return "GohtEnd" + case tTemplateStart: + return "TemplateStart" + case tTemplateEnd: + return "TemplateEnd" case tDoctype: return "Doctype" case tTag: diff --git a/compiler/tokens_test.go b/compiler/tokens_test.go index 8198401..69e0a35 100644 --- a/compiler/tokens_test.go +++ b/compiler/tokens_test.go @@ -37,14 +37,6 @@ func Test_TokenStringer(t *testing.T) { typ: tGoCode, want: "GoCode", }, - "tGohtStart": { - typ: tGohtStart, - want: "GohtStart", - }, - "tGohtEnd": { - typ: tGohtEnd, - want: "GohtEnd", - }, "tDoctype": { typ: tDoctype, want: "Doctype", diff --git a/docs/goht_header_html.png b/docs/goht_header_html.png new file mode 100644 index 0000000..9d205b2 Binary files /dev/null and b/docs/goht_header_html.png differ diff --git a/examples/attributes/additional.goht b/examples/attributes/additional.goht index 0e074a2..d75b99e 100644 --- a/examples/attributes/additional.goht +++ b/examples/attributes/additional.goht @@ -17,7 +17,19 @@ var strAttrs = map[string]string{ } @goht AttributesCmd() { -%input{ - @attributes: #{boolAttrs, strAttrs}, + %input{ + @attributes: #{boolAttrs, strAttrs}, + } } + +@haml HamlAttributesCmd() { + %input{ + @attributes: #{boolAttrs, strAttrs}, + } +} + +@slim SlimAttributesCmd() { + input{ + @attributes: #{boolAttrs, strAttrs}, + } } diff --git a/examples/attributes/additional.goht.go b/examples/attributes/additional.goht.go index 7a003f4..7b06d3d 100644 --- a/examples/attributes/additional.goht.go +++ b/examples/attributes/additional.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package attributes @@ -41,7 +41,69 @@ func AttributesCmd() goht.Template { if __err != nil { return } - if _, __err = __buf.WriteString(__var1); __err != nil { + if _, __err = __buf.WriteString(" " + __var1); __err != nil { + return + } + if _, __err = __buf.WriteString(">"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func HamlAttributesCmd() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString(""); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimAttributesCmd() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString(""); __err != nil { diff --git a/examples/attributes/classes.goht b/examples/attributes/classes.goht index b8268c5..1cc3a20 100644 --- a/examples/attributes/classes.goht +++ b/examples/attributes/classes.goht @@ -24,5 +24,13 @@ var myOptionalClasses = map[string]bool{ } @goht Classes() { -%p.fizz.buzz{class: #{myClassList, myOptionalClasses}} + %p.fizz.buzz{class: #{myClassList, myOptionalClasses}} +} + +@haml HamlClasses() { + %p.fizz.buzz{class: #{myClassList, myOptionalClasses}} +} + +@slim SlimClasses() { + p.fizz.buzz{class: #{myClassList, myOptionalClasses}} } diff --git a/examples/attributes/classes.goht.go b/examples/attributes/classes.goht.go index 37c82ae..b7b086a 100644 --- a/examples/attributes/classes.goht.go +++ b/examples/attributes/classes.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package attributes @@ -60,3 +60,65 @@ func Classes() goht.Template { return }) } + +func HamlClasses() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimClasses() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/attributes/general.goht b/examples/attributes/general.goht index 2fa1013..dbd426e 100644 --- a/examples/attributes/general.goht +++ b/examples/attributes/general.goht @@ -6,7 +6,15 @@ package attributes // by Go for maps. @goht StaticAttrs() { -%p{class: "foo", id: "bar"} This is a paragraph. + %p{class: "foo", id: "bar"} This is a paragraph. +} + +@haml HamlStaticAttrs() { + %p{class: "foo", id: "bar"} This is a paragraph. +} + +@slim SlimStaticAttrs() { + p{class: "foo", id: "bar"} This is a paragraph. } // You can also use dynamic values for your attributes. Dynamic attribute @@ -16,7 +24,15 @@ package attributes var myDynamicValue = "foo" @goht DynamicAttrs() { -%p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. + %p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. +} + +@haml HamlDynamicAttrs() { + %p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. +} + +@slim SlimDynamicAttrs() { + p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. } // There are times when you have a lot of attributes and you want to keep @@ -26,20 +42,48 @@ var myDynamicValue = "foo" // not required. @goht MultilineAttrs() { -%p{ - class: #{myDynamicValue}, - id: "bar", -} This is a paragraph. + %p{ + class: #{myDynamicValue}, + id: "bar", + } This is a paragraph. +} + +@haml HamlMultilineAttrs() { + %p{ + class: #{myDynamicValue}, + id: "bar", + } This is a paragraph. +} + +@slim SlimMultilineAttrs() { + p{ + class: #{myDynamicValue}, + id: "bar", + } This is a paragraph. } // You may include as much whitespace as you wish between the attribute, // operator, value, and attribute separator. The following are all valid. @goht WhitespaceAttrs() { -%p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. -%p{class:#{myDynamicValue},id:"bar"} This is a paragraph. -%p{class: #{myDynamicValue},id: "bar"} This is a paragraph. -%p{class :#{myDynamicValue}, id : "bar"} This is a paragraph. + %p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. + %p{class:#{myDynamicValue},id:"bar"} This is a paragraph. + %p{class: #{myDynamicValue},id: "bar"} This is a paragraph. + %p{class :#{myDynamicValue}, id : "bar"} This is a paragraph. +} + +@haml HamlWhitespaceAttrs() { + %p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. + %p{class:#{myDynamicValue},id:"bar"} This is a paragraph. + %p{class: #{myDynamicValue},id: "bar"} This is a paragraph. + %p{class :#{myDynamicValue}, id : "bar"} This is a paragraph. +} + +@slim SlimWhitespaceAttrs() { + p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. + p{class:#{myDynamicValue},id:"bar"} This is a paragraph. + p{class: #{myDynamicValue},id: "bar"} This is a paragraph. + p{class :#{myDynamicValue}, id : "bar"} This is a paragraph. } // The dynamic attribute values may also include formatting rules just like @@ -49,5 +93,13 @@ var myDynamicValue = "foo" var intVar = 10 @goht FormattedValue() { -%textarea{rows: #{%d intVar}, cols: "80"} + %textarea{rows: #{%d intVar}, cols: "80"} +} + +@haml HamlFormattedValue() { + %textarea{rows: #{%d intVar}, cols: "80"} +} + +@slim SlimFormattedValue() { + textarea{rows: #{%d intVar}, cols: "80"} } diff --git a/examples/attributes/general.goht.go b/examples/attributes/general.goht.go index 65a832a..79f3d73 100644 --- a/examples/attributes/general.goht.go +++ b/examples/attributes/general.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package attributes @@ -32,6 +32,46 @@ func StaticAttrs() goht.Template { }) } +func HamlStaticAttrs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is a paragraph.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimStaticAttrs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is a paragraph.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // You can also use dynamic values for your attributes. Dynamic attribute // values share the same syntax as the interpolated values. A hash and a // pair of curly braces. @@ -69,6 +109,68 @@ func DynamicAttrs() goht.Template { }) } +func HamlDynamicAttrs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("This is a paragraph.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimDynamicAttrs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("This is a paragraph.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // There are times when you have a lot of attributes and you want to keep // your lines short. You can break up your attributes into multiple lines // without any additional syntax. @@ -106,6 +208,68 @@ func MultilineAttrs() goht.Template { }) } +func HamlMultilineAttrs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("This is a paragraph.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimMultilineAttrs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("This is a paragraph.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // You may include as much whitespace as you wish between the attribute, // operator, value, and attribute separator. The following are all valid. @@ -173,6 +337,134 @@ func WhitespaceAttrs() goht.Template { }) } +func HamlWhitespaceAttrs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("This is a paragraph.

\nThis is a paragraph.

\nThis is a paragraph.

\nThis is a paragraph.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimWhitespaceAttrs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("This is a paragraph.

\nThis is a paragraph.

\nThis is a paragraph.

\nThis is a paragraph.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // The dynamic attribute values may also include formatting rules just like // the interpolated values. The attribute values are always evaluated as // strings and are always rendered inside double quotes in the final HTML. @@ -204,3 +496,55 @@ func FormattedValue() goht.Template { return }) } + +func HamlFormattedValue() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimFormattedValue() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/attributes/names.goht b/examples/attributes/names.goht index 119d020..e2a80c2 100644 --- a/examples/attributes/names.goht +++ b/examples/attributes/names.goht @@ -6,12 +6,30 @@ package attributes // underscores (_) are all acceptable as-is. @goht SimpleNames() { -%a{ - href: "https://github.com/stackus/goht", - data-foo: "bar", - odd_name: "baz", - _: "I'm a _hyperscript attribute!" -} Goht + %a{ + href: "https://github.com/stackus/goht", + data-foo: "bar", + odd_name: "baz", + _: "I'm a _hyperscript attribute!" + } Goht +} + +@haml HamlSimpleNames() { + %a{ + href: "https://github.com/stackus/goht", + data-foo: "bar", + odd_name: "baz", + _: "I'm a _hyperscript attribute!" + } Goht +} + +@slim SlimSimpleNames() { + a{ + href: "https://github.com/stackus/goht", + data-foo: "bar", + odd_name: "baz", + _: "I'm a _hyperscript attribute!" + } Goht } // For more complex names, such as data attributes, you can use @@ -22,9 +40,25 @@ package attributes // The names will be rendered into the HTML without the quotes. @goht ComplexNames() { -%a{ - href: "https://github.com/stackus/goht", - `:class`: "show ? '' : 'hidden'", - `@click`: "show = !show", -} Goht + %a{ + href: "https://github.com/stackus/goht", + `:class`: "show ? '' : 'hidden'", + `@click`: "show = !show", + } Goht +} + +@haml HamlComplexNames() { + %a{ + href: "https://github.com/stackus/goht", + `:class`: "show ? '' : 'hidden'", + `@click`: "show = !show", + } Goht +} + +@slim SlimComplexNames() { + a{ + href: "https://github.com/stackus/goht", + `:class`: "show ? '' : 'hidden'", + `@click`: "show = !show", + } Goht } diff --git a/examples/attributes/names.goht.go b/examples/attributes/names.goht.go index bbb20eb..cb52441 100644 --- a/examples/attributes/names.goht.go +++ b/examples/attributes/names.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package attributes @@ -32,6 +32,46 @@ func SimpleNames() goht.Template { }) } +func HamlSimpleNames() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("Goht\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimSimpleNames() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("Goht\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // For more complex names, such as data attributes, you can use // enclose the name in in double quotes or backticks. // - Names that start with an at sign (@). @@ -58,3 +98,43 @@ func ComplexNames() goht.Template { return }) } + +func HamlComplexNames() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("Goht\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimComplexNames() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("Goht\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/attributes/optional.goht b/examples/attributes/optional.goht index d31ec0d..f37cc76 100644 --- a/examples/attributes/optional.goht +++ b/examples/attributes/optional.goht @@ -15,6 +15,16 @@ var disabled = true var foo = "bar" @goht ConditionalAttrs() { -%button{disabled? #{disabled}} Click me! -%button{disabled? #{foo == "bar"}} Click me! + %button{disabled? #{disabled}} Click me! + %button{disabled? #{foo == "bar"}} Click me! +} + +@haml HamlConditionalAttrs() { + %button{disabled? #{disabled}} Click me! + %button{disabled? #{foo == "bar"}} Click me! +} + +@slim SlimConditionalAttrs() { + button{disabled? #{disabled}} Click me! + button{disabled? #{foo == "bar"}} Click me! } diff --git a/examples/attributes/optional.goht.go b/examples/attributes/optional.goht.go index 53bf5e4..1fd49f1 100644 --- a/examples/attributes/optional.goht.go +++ b/examples/attributes/optional.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package attributes @@ -56,3 +56,75 @@ func ConditionalAttrs() goht.Template { return }) } + +func HamlConditionalAttrs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("Click me!\nClick me!\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimConditionalAttrs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("Click me!\nClick me!\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/commands/children.goht b/examples/commands/children.goht index a3a24d4..eec83ca 100644 --- a/examples/commands/children.goht +++ b/examples/commands/children.goht @@ -11,7 +11,19 @@ package commands // code syntax `=`. @goht ChildrenExample() { -%p - The following was passed in from the calling template: - = @children + %p + The following was passed in from the calling template: + = @children +} + +@haml HamlChildrenExample() { + %p + The following was passed in from the calling template: + = @children +} + +@slim SlimChildrenExample() { + p + The following was passed in from the calling template: + = @children } diff --git a/examples/commands/children.goht.go b/examples/commands/children.goht.go index 0ced31e..d82bd25 100644 --- a/examples/commands/children.goht.go +++ b/examples/commands/children.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package commands @@ -42,3 +42,55 @@ func ChildrenExample() goht.Template { return }) } + +func HamlChildrenExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

\nThe following was passed in from the calling template:\n"); __err != nil { + return + } + if __err = __children.Render(ctx, __buf); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimChildrenExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

\nfollowing was passed in from the calling template:\n"); __err != nil { + return + } + if __err = __children.Render(ctx, __buf); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/commands/render.goht b/examples/commands/render.goht index 4c09a73..11472b7 100644 --- a/examples/commands/render.goht +++ b/examples/commands/render.goht @@ -6,8 +6,18 @@ package commands // code syntax `=`. @goht RenderExample() { -%p= @render ChildrenExample() -%p the other template was rendered above. + %p= @render ChildrenExample() + %p the other template was rendered above. +} + +@haml HamlRenderExample() { + %p= @render ChildrenExample() + %p the other template was rendered above. +} + +@slim SlimRenderExample() { + p= @render ChildrenExample() + p the other template was rendered above. } // You may also include nested content to be rendered by the template. @@ -15,7 +25,19 @@ package commands // are passing content on to be rendered by another template. @goht RenderWithChildrenExample() { -%p The other template will be rendered below. -= @render ChildrenExample() - %span this content will be rendered by the other template. + %p The other template will be rendered below. + = @render ChildrenExample() + %span this content will be rendered by the other template. +} + +@haml HamlRenderWithChildrenExample() { + %p The other template will be rendered below. + = @render ChildrenExample() + %span this content will be rendered by the other template. +} + +@slim SlimRenderWithChildrenExample() { + p The other template will be rendered below. + = @render ChildrenExample() + span this content will be rendered by the other template. } diff --git a/examples/commands/render.goht.go b/examples/commands/render.goht.go index a517215..33f0a4e 100644 --- a/examples/commands/render.goht.go +++ b/examples/commands/render.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package commands @@ -38,6 +38,58 @@ func RenderExample() goht.Template { }) } +func HamlRenderExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + if __err = ChildrenExample().Render(ctx, __buf); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n

the other template was rendered above.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimRenderExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + if __err = ChildrenExample().Render(ctx, __buf); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n

the other template was rendered above.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // You may also include nested content to be rendered by the template. // You do not need to include any opening or closing braces when you // are passing content on to be rendered by another template. @@ -78,3 +130,77 @@ func RenderWithChildrenExample() goht.Template { return }) } + +func HamlRenderWithChildrenExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

The other template will be rendered below.

\n"); __err != nil { + return + } + __var1 := goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + if _, __err = __buf.WriteString("this content will be rendered by the other template.\n"); __err != nil { + return + } + if !__isBuf { + _, __err = io.Copy(__w, __buf) + } + return + }) + if __err = ChildrenExample().Render(goht.PushChildren(ctx, __var1), __buf); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimRenderWithChildrenExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

The other template will be rendered below.

\n"); __err != nil { + return + } + __var1 := goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + if _, __err = __buf.WriteString("this content will be rendered by the other template.\n"); __err != nil { + return + } + if !__isBuf { + _, __err = io.Copy(__w, __buf) + } + return + }) + if __err = ChildrenExample().Render(goht.PushChildren(ctx, __var1), __buf); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/comments/html.goht b/examples/comments/html.goht index 9d73e07..9aad421 100644 --- a/examples/comments/html.goht +++ b/examples/comments/html.goht @@ -6,8 +6,21 @@ package comments // of the line @goht HtmlComments() { -%p This is a paragraph -/ This is a HTML comment + %p This is a paragraph + / This is a HTML comment +} + +@haml HamlHtmlComments() { + %p This is a paragraph + / This is a HTML comment +} + +// HTML comments in the Slim syntax use "/!" to indicate the start of +// the comment. + +@slim SlimHtmlComments() { + p This is a paragraph + /! This is a HTML comment } // You may also use them to comment out nested elements. This does @@ -15,7 +28,19 @@ package comments // displayed. @goht HtmlCommentsNested() { -%p This is a paragraph -/ - %p This is a paragraph that is commented out + %p This is a paragraph + / + %p This is a paragraph that is commented out +} + +@haml HamlHtmlCommentsNested() { + %p This is a paragraph + / + %p This is a paragraph that is commented out +} + +@slim SlimHtmlCommentsNested() { + p This is a paragraph + /! + p This is a paragraph that is commented out } diff --git a/examples/comments/html.goht.go b/examples/comments/html.goht.go index 9c65a6a..f9ba9e8 100644 --- a/examples/comments/html.goht.go +++ b/examples/comments/html.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package comments @@ -32,6 +32,49 @@ func HtmlComments() goht.Template { }) } +func HamlHtmlComments() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is a paragraph

\n\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +// HTML comments in the Slim syntax use "/!" to indicate the start of +// the comment. + +func SlimHtmlComments() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is a paragraph

\n\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // You may also use them to comment out nested elements. This does // not stop the nested elements from being parsed, just from being // displayed. @@ -55,3 +98,43 @@ func HtmlCommentsNested() goht.Template { return }) } + +func HamlHtmlCommentsNested() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is a paragraph

\n\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimHtmlCommentsNested() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is a paragraph

\n\n\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/comments/rubystyle.goht b/examples/comments/rubystyle.goht index 96c5928..3e426bd 100644 --- a/examples/comments/rubystyle.goht +++ b/examples/comments/rubystyle.goht @@ -10,16 +10,42 @@ package comments // output. @goht RubyStyle() { -%p This is the only paragraph in the output. --# %p This comment is removed from the output. + %p This is the only paragraph in the output. + -# %p This comment is removed from the output. +} + +@haml HamlRubyStyle() { + %p This is the only paragraph in the output. + -# %p This comment is removed from the output. +} + +// In the Slim syntax "RubyStyle" comments use a "/" to indicate the +// start of the comment +@slim SlimRubyStyle() { + p This is the only paragraph in the output. + / p This comment is removed from the output. } -// Ruby style comments can also be nested. +// Ruby style comments can comment nested content. @goht RubyStyleNested() { -%p This is the only paragraph in the output. --# - %p This paragraph is removed. - %%% broken syntax is no problem. + %p This is the only paragraph in the output. + -# + %p This paragraph is removed. + %%% broken syntax is no problem. +} + +@haml HamlRubyStyleNested() { + %p This is the only paragraph in the output. + -# + %p This paragraph is removed. + %%% broken syntax is no problem. +} + +@slim SlimRubyStyleNested() { + p This is the only paragraph in the output. + / + p This paragraph is removed. + %%% broken syntax is no problem. } diff --git a/examples/comments/rubystyle.goht.go b/examples/comments/rubystyle.goht.go index 8cc5f7d..048b9f7 100644 --- a/examples/comments/rubystyle.goht.go +++ b/examples/comments/rubystyle.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package comments @@ -36,7 +36,50 @@ func RubyStyle() goht.Template { }) } -// Ruby style comments can also be nested. +func HamlRubyStyle() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is the only paragraph in the output.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +// In the Slim syntax "RubyStyle" comments use a "/" to indicate the +// start of the comment + +func SlimRubyStyle() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is the only paragraph in the output.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +// Ruby style comments can comment nested content. func RubyStyleNested() goht.Template { return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { @@ -57,3 +100,43 @@ func RubyStyleNested() goht.Template { return }) } + +func HamlRubyStyleNested() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is the only paragraph in the output.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimRubyStyleNested() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is the only paragraph in the output.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/doctype/doctype.goht b/examples/doctype/doctype.goht index a40b432..7d71f83 100644 --- a/examples/doctype/doctype.goht +++ b/examples/doctype/doctype.goht @@ -3,5 +3,13 @@ package doctype // Adds a doctype to the top of the page // HTML5 doctype is the default @goht Doctype() { -!!! + !!! +} + +@haml HamlDoctype() { + !!! +} + +@slim SlimDoctype() { + doctype } diff --git a/examples/doctype/doctype.goht.go b/examples/doctype/doctype.goht.go index 1f7f85b..3420609 100644 --- a/examples/doctype/doctype.goht.go +++ b/examples/doctype/doctype.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package doctype @@ -28,3 +28,43 @@ func Doctype() goht.Template { return }) } + +func HamlDoctype() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimDoctype() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/examples_test.go b/examples/examples_test.go index 1ba05a7..7f7a448 100644 --- a/examples/examples_test.go +++ b/examples/examples_test.go @@ -41,7 +41,13 @@ func goldenFile(t *testing.T, fileName string, got []byte, update bool) ([]byte, // If the update flag is set, write the golden file when either the file does not exist or the contents do not match. if update && (!bytes.Equal(want, got) || err != nil) { - err := os.WriteFile(fileName, got, 0644) + // Create the directory if it doesn't exist + dir := filepath.Dir(fileName) + err := os.MkdirAll(dir, 0755) + if err != nil { + return nil, err + } + err = os.WriteFile(fileName, got, 0644) if err != nil { return nil, err } @@ -52,21 +58,21 @@ func goldenFile(t *testing.T, fileName string, got []byte, update bool) ([]byte, return want, nil } -func TestExamples(t *testing.T) { +func TestHamlExamples(t *testing.T) { tests := map[string]struct { template goht.Template htmlFile string }{ "attributes_attributesCmd": { - template: attributes.AttributesCmd(), + template: attributes.HamlAttributesCmd(), htmlFile: "attributes_attributesCmd", }, "attributes_classes": { - template: attributes.Classes(), + template: attributes.HamlClasses(), htmlFile: "attributes_classes", }, "attributes_staticAttrs": { - template: attributes.StaticAttrs(), + template: attributes.HamlStaticAttrs(), htmlFile: "attributes_staticAttrs", }, "attributes_dynamicAttrs": { @@ -98,149 +104,145 @@ func TestExamples(t *testing.T) { htmlFile: "attributes_conditionalAttrs", }, "commands_childrenExample": { - template: commands.ChildrenExample(), + template: commands.HamlChildrenExample(), htmlFile: "commands_childrenExample", }, "commands_renderExample": { - template: commands.RenderExample(), + template: commands.HamlRenderExample(), htmlFile: "commands_renderExample", }, "commands_renderWithChildrenExample": { - template: commands.RenderWithChildrenExample(), + template: commands.HamlRenderWithChildrenExample(), htmlFile: "commands_renderWithChildrenExample", }, "comments_htmlComments": { - template: comments.HtmlComments(), + template: comments.HamlHtmlComments(), htmlFile: "comments_htmlComments", }, "comments_htmlCommentsNested": { - template: comments.HtmlCommentsNested(), + template: comments.HamlHtmlCommentsNested(), htmlFile: "comments_htmlCommentsNested", }, "comments_rubyStyle": { - template: comments.RubyStyle(), + template: comments.HamlRubyStyle(), htmlFile: "comments_rubyStyle", }, "comments_rubyStyleNested": { - template: comments.RubyStyleNested(), + template: comments.HamlRubyStyleNested(), htmlFile: "comments_rubyStyleNested", }, "doctype_doctype": { - template: doctype.Doctype(), + template: doctype.HamlDoctype(), htmlFile: "doctype_doctype", }, "filters_css": { - template: filters.Css(), + template: filters.HamlCss(), htmlFile: "filters_css", }, "filters_javascript": { - template: filters.JavaScript(), + template: filters.HamlJavaScript(), htmlFile: "filters_javascript", }, "filters_plain": { - template: filters.Plain(), + template: filters.HamlPlain(), htmlFile: "filters_plain", }, "filters_escaped": { - template: filters.Escaped(), + template: filters.HamlEscaped(), htmlFile: "filters_escaped", }, "filters_preserve": { - template: filters.Preserve(), + template: filters.HamlPreserve(), htmlFile: "filters_preserve", }, "formatting_intExample": { - template: formatting.IntExample(), + template: formatting.HamlIntExample(), htmlFile: "formatting_intExample", }, "formatting_floatExample": { - template: formatting.FloatExample(), + template: formatting.HamlFloatExample(), htmlFile: "formatting_floatExample", }, "formatting_boolExample": { - template: formatting.BoolExample(), + template: formatting.HamlBoolExample(), htmlFile: "formatting_boolExample", }, "formatting_stringExample": { - template: formatting.StringExample(), + template: formatting.HamlStringExample(), htmlFile: "formatting_stringExample", }, "example_executeCode": { - template: example.ExecuteCode(), + template: example.HamlExecuteCode(), htmlFile: "example_executeCode", }, "example_renderCode": { - template: example.RenderCode(), + template: example.HamlRenderCode(), htmlFile: "example_renderCode", }, "example_doc": { - template: example.Doc(), + template: example.HamlDoc(), htmlFile: "example_doc", }, "example_importExample": { - template: example.ImportExample(), + template: example.HamlImportExample(), htmlFile: "example_importExample", }, "example_conditional": { - template: example.Conditional(), + template: example.HamlConditional(), htmlFile: "example_conditional", }, "example_shorthandConditional": { - template: example.ShorthandConditional(), + template: example.HamlShorthandConditional(), htmlFile: "example_shorthandConditional", }, "example_shorthandSwitch": { - template: example.ShorthandSwitch(), + template: example.HamlShorthandSwitch(), htmlFile: "example_shorthandSwitch", }, "example_interpolateCode": { - template: example.InterpolateCode(), + template: example.HamlInterpolateCode(), htmlFile: "example_interpolateCode", }, "example_noInterpolation": { - template: example.NoInterpolation(), + template: example.HamlNoInterpolation(), htmlFile: "example_noInterpolation", }, "example_escapeInterpolation": { - template: example.EscapeInterpolation(), + template: example.HamlEscapeInterpolation(), htmlFile: "example_escapeInterpolation", }, "example_ignoreInterpolation": { - template: example.IgnoreInterpolation(), + template: example.HamlIgnoreInterpolation(), htmlFile: "example_ignoreInterpolation", }, - "example_packageExample": { - template: example.PackageExample(), - htmlFile: "example_packageExample", - }, "example_userDetails": { template: func() goht.Template { user := example.User{ Name: "John", Age: 30, } - return user.Details() + return user.HamlDetails() }(), htmlFile: "example_userDetails", }, "indents_usingTabs": { - template: indents.UsingTabs(), + template: indents.HamlUsingTabs(), htmlFile: "indents_usingTabs", }, "tags_specifyTag": { - template: tags.SpecifyTag(), + template: tags.HamlSpecifyTag(), htmlFile: "tags_specifyTag", }, "tags_defaultToDivs": { - template: tags.DefaultToDivs(), + template: tags.HamlDefaultToDivs(), htmlFile: "tags_defaultToDivs", }, "tabs_combined": { - template: tags.Combined(), + template: tags.HamlCombined(), htmlFile: "tags_combined", }, "tags_multipleClasses": { - template: tags.MultipleClasses(), + template: tags.HamlMultipleClasses(), htmlFile: "tags_multipleClasses", }, "tags_objectRefs": { @@ -248,7 +250,7 @@ func TestExamples(t *testing.T) { foo := tags.Foo{ ID: "foo", } - return tags.ObjectRefs(foo) + return tags.HamlObjectRefs(foo) }(), htmlFile: "tags_objectRefs", }, @@ -257,24 +259,24 @@ func TestExamples(t *testing.T) { foo := tags.Foo{ ID: "foo", } - return tags.PrefixedObjectRefs(foo) + return tags.HamlPrefixedObjectRefs(foo) }(), htmlFile: "tags_prefixedObjectRefs", }, "tags_selfClosing": { - template: tags.SelfClosing(), + template: tags.HamlSelfClosing(), htmlFile: "tags_selfClosing", }, "tags_alsoSelfClosing": { - template: tags.AlsoSelfClosing(), + template: tags.HamlAlsoSelfClosing(), htmlFile: "tags_alsoSelfClosing", }, "tags_whitespace": { - template: tags.Whitespace(), + template: tags.HamlWhitespace(), htmlFile: "tags_whitespace", }, "tags_removeWhitespace": { - template: tags.RemoveWhitespace(), + template: tags.HamlRemoveWhitespace(), htmlFile: "tags_removeWhitespace", }, "hello_world": { @@ -282,15 +284,15 @@ func TestExamples(t *testing.T) { htmlFile: "hello_world", }, "unescape_unescapeCode": { - template: unescape.UnescapeCode(), + template: unescape.HamlUnescapeCode(), htmlFile: "unescape_unescapeCode", }, "unescape_unescapeInterpolation": { - template: unescape.UnescapeInterpolation(), + template: unescape.HamlUnescapeInterpolation(), htmlFile: "unescape_unescapeInterpolation", }, "unescape_unescapeText": { - template: unescape.UnescapeText(), + template: unescape.HamlUnescapeText(), htmlFile: "unescape_unescapeText", }, } @@ -304,7 +306,245 @@ func TestExamples(t *testing.T) { } got := gotW.Bytes() - goldenFileName := filepath.Join("testdata", tt.htmlFile+".html") + goldenFileName := filepath.Join("testdata", "haml", tt.htmlFile+".html") + want, err := goldenFile(t, goldenFileName, got, *update) + if err != nil { + t.Errorf("error reading golden file: %v", err) + return + } + + if bytes.Equal(want, got) { + return + } + + dmp := diffmatchpatch.New() + diffs := dmp.DiffMain(string(want), string(got), true) + if len(diffs) > 1 { + t.Errorf("diff:\n%s", dmp.DiffPrettyText(diffs)) + } + }) + } +} + +func TestSlimExamples(t *testing.T) { + tests := map[string]struct { + template goht.Template + htmlFile string + }{ + "attributes_attributesCmd": { + template: attributes.SlimAttributesCmd(), + htmlFile: "attributes_attributesCmd", + }, + "attributes_classes": { + template: attributes.SlimClasses(), + htmlFile: "attributes_classes", + }, + "attributes_staticAttrs": { + template: attributes.SlimStaticAttrs(), + htmlFile: "attributes_staticAttrs", + }, + "attributes_dynamicAttrs": { + template: attributes.SlimDynamicAttrs(), + htmlFile: "attributes_dynamicAttrs", + }, + "attributes_multilineAttrs": { + template: attributes.SlimMultilineAttrs(), + htmlFile: "attributes_multilineAttrs", + }, + "attributes_whitespaceAttrs": { + template: attributes.SlimWhitespaceAttrs(), + htmlFile: "attributes_whitespaceAttrs", + }, + "attributes_formattedValue": { + template: attributes.SlimFormattedValue(), + htmlFile: "attributes_formattedValue", + }, + "attributes_simpleNames": { + template: attributes.SlimSimpleNames(), + htmlFile: "attributes_simpleNames", + }, + "attributes_complexNames": { + template: attributes.SlimComplexNames(), + htmlFile: "attributes_complexNames", + }, + "attributes_conditionalAttrs": { + template: attributes.SlimConditionalAttrs(), + htmlFile: "attributes_conditionalAttrs", + }, + "commands_childrenExample": { + template: commands.SlimChildrenExample(), + htmlFile: "commands_childrenExample", + }, + "commands_renderExample": { + template: commands.SlimRenderExample(), + htmlFile: "commands_renderExample", + }, + "commands_renderWithChildrenExample": { + template: commands.SlimRenderWithChildrenExample(), + htmlFile: "commands_renderWithChildrenExample", + }, + "comments_htmlComments": { + template: comments.SlimHtmlComments(), + htmlFile: "comments_htmlComments", + }, + "comments_htmlCommentsNested": { + template: comments.SlimHtmlCommentsNested(), + htmlFile: "comments_htmlCommentsNested", + }, + "comments_rubyStyle": { + template: comments.SlimRubyStyle(), + htmlFile: "comments_rubyStyle", + }, + "comments_rubyStyleNested": { + template: comments.SlimRubyStyleNested(), + htmlFile: "comments_rubyStyleNested", + }, + "doctype_doctype": { + template: doctype.SlimDoctype(), + htmlFile: "doctype_doctype", + }, + "filters_css": { + template: filters.SlimCss(), + htmlFile: "filters_css", + }, + "filters_javascript": { + template: filters.SlimJavaScript(), + htmlFile: "filters_javascript", + }, + // "filters_plain": { + // template: filters.SlimPlain(), + // htmlFile: "filters_plain", + // }, + // "filters_escaped": { + // template: filters.SlimEscaped(), + // htmlFile: "filters_escaped", + // }, + // "filters_preserve": { + // template: filters.SlimPreserve(), + // htmlFile: "filters_preserve", + // }, + "formatting_intExample": { + template: formatting.SlimIntExample(), + htmlFile: "formatting_intExample", + }, + "formatting_floatExample": { + template: formatting.SlimFloatExample(), + htmlFile: "formatting_floatExample", + }, + "formatting_boolExample": { + template: formatting.SlimBoolExample(), + htmlFile: "formatting_boolExample", + }, + "formatting_stringExample": { + template: formatting.SlimStringExample(), + htmlFile: "formatting_stringExample", + }, + "example_executeCode": { + template: example.SlimExecuteCode(), + htmlFile: "example_executeCode", + }, + "example_renderCode": { + template: example.SlimRenderCode(), + htmlFile: "example_renderCode", + }, + "example_doc": { + template: example.SlimDoc(), + htmlFile: "example_doc", + }, + "example_importExample": { + template: example.SlimImportExample(), + htmlFile: "example_importExample", + }, + "example_conditional": { + template: example.SlimConditional(), + htmlFile: "example_conditional", + }, + "example_shorthandConditional": { + template: example.SlimShorthandConditional(), + htmlFile: "example_shorthandConditional", + }, + "example_shorthandSwitch": { + template: example.SlimShorthandSwitch(), + htmlFile: "example_shorthandSwitch", + }, + "example_interpolateCode": { + template: example.SlimInterpolateCode(), + htmlFile: "example_interpolateCode", + }, + "example_noInterpolation": { + template: example.SlimNoInterpolation(), + htmlFile: "example_noInterpolation", + }, + "example_userDetails": { + template: func() goht.Template { + user := example.User{ + Name: "John", + Age: 30, + } + return user.SlimDetails() + }(), + htmlFile: "example_userDetails", + }, + "indents_usingTabs": { + template: indents.SlimUsingTabs(), + htmlFile: "indents_usingTabs", + }, + "tags_inlineTags": { + template: tags.SlimInlineTags(), + htmlFile: "tags_inlineTags", + }, + "tags_specifyTag": { + template: tags.SlimSpecifyTag(), + htmlFile: "tags_specifyTag", + }, + "tags_defaultToDivs": { + template: tags.SlimDefaultToDivs(), + htmlFile: "tags_defaultToDivs", + }, + "tabs_combined": { + template: tags.SlimCombined(), + htmlFile: "tags_combined", + }, + "tags_multipleClasses": { + template: tags.SlimMultipleClasses(), + htmlFile: "tags_multipleClasses", + }, + "tags_selfClosing": { + template: tags.SlimSelfClosing(), + htmlFile: "tags_selfClosing", + }, + "tags_alsoSelfClosing": { + template: tags.SlimAlsoSelfClosing(), + htmlFile: "tags_alsoSelfClosing", + }, + "tags_whitespace": { + template: tags.SlimWhitespace(), + htmlFile: "tags_whitespace", + }, + "tags_removeWhitespace": { + template: tags.SlimRemoveWhitespace(), + htmlFile: "tags_removeWhitespace", + }, + "hello_world": { + template: hello.SlimWorld(), + htmlFile: "hello_world", + }, + "unescape_unescapeCode": { + template: unescape.SlimUnescapeCode(), + htmlFile: "unescape_unescapeCode", + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + var gotW bytes.Buffer + err := tt.template.Render(context.Background(), &gotW) + if err != nil { + t.Errorf("error generating template: %v", err) + return + } + + got := gotW.Bytes() + goldenFileName := filepath.Join("testdata", "slim", tt.htmlFile+".html") want, err := goldenFile(t, goldenFileName, got, *update) if err != nil { t.Errorf("error reading golden file: %v", err) diff --git a/examples/filters/css.goht b/examples/filters/css.goht index 1ac215b..fa54eb2 100644 --- a/examples/filters/css.goht +++ b/examples/filters/css.goht @@ -6,8 +6,22 @@ package filters var color = "red" @goht Css() { -:css - .color { - color: #{color}; - } + :css + .color { + color: #{color}; + } +} + +@haml HamlCss() { + :css + .color { + color: #{color}; + } +} + +@slim SlimCss() { + :css + .color { + color: #{color}; + } } diff --git a/examples/filters/css.goht.go b/examples/filters/css.goht.go index 938b118..eaf805d 100644 --- a/examples/filters/css.goht.go +++ b/examples/filters/css.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package filters @@ -41,3 +41,63 @@ func Css() goht.Template { return }) } + +func HamlCss() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString(""); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimCss() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString(""); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/filters/javascript.goht b/examples/filters/javascript.goht index d0556c5..756d9c1 100644 --- a/examples/filters/javascript.goht +++ b/examples/filters/javascript.goht @@ -7,6 +7,16 @@ package filters var name = "Bob" @goht JavaScript() { -:javascript - console.log("Hello #{name}!"); + :javascript + console.log("Hello #{name}!"); +} + +@haml HamlJavaScript() { + :javascript + console.log("Hello #{name}!"); +} + +@slim SlimJavaScript() { + :javascript + console.log("Hello #{name}!"); } diff --git a/examples/filters/javascript.goht.go b/examples/filters/javascript.goht.go index 3b82465..e07c9f8 100644 --- a/examples/filters/javascript.goht.go +++ b/examples/filters/javascript.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package filters @@ -42,3 +42,63 @@ func JavaScript() goht.Template { return }) } + +func HamlJavaScript() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString(""); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimJavaScript() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString(""); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/filters/text.goht b/examples/filters/text.goht index db8abf0..be9ca4f 100644 --- a/examples/filters/text.goht +++ b/examples/filters/text.goht @@ -6,22 +6,43 @@ package filters // Variable interpolation is still performed. @goht Plain() { -%p - :plain - This is plain text. It
will
be displayed as HTML. - #{"This
\"will\"
be interpolated with HTML intact."} + %p + :plain + This is plain text. It
will
be displayed as HTML. + #{"This
\"will\"
be interpolated with HTML intact."} +} + +@haml HamlPlain() { + %p + :plain + This is plain text. It
will
be displayed as HTML. + #{"This
\"will\"
be interpolated with HTML intact."} } @goht Escaped() { -%p - :escaped - This is escaped text. It
will not
be displayed as HTML. - #{"This
\"will not\"
be interpolated with HTML intact."} + %p + :escaped + This is escaped text. It
will not
be displayed as HTML. + #{"This
\"will not\"
be interpolated with HTML intact."} +} + +@haml HamlEscaped() { + %p + :escaped + This is escaped text. It
will not
be displayed as HTML. + #{"This
\"will not\"
be interpolated with HTML intact."} } @goht Preserve() { -%p - :preserve - This is preserved text. It
will
be displayed as HTML. - #{"This
\"will\"
be interpolated with HTML intact."} + %p + :preserve + This is preserved text. It
will
be displayed as HTML. + #{"This
\"will\"
be interpolated with HTML intact."} +} + +@haml HamlPreserve() { + %p + :preserve + This is preserved text. It
will
be displayed as HTML. + #{"This
\"will\"
be interpolated with HTML intact."} } diff --git a/examples/filters/text.goht.go b/examples/filters/text.goht.go index f61bee4..f70be24 100644 --- a/examples/filters/text.goht.go +++ b/examples/filters/text.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package filters @@ -42,6 +42,36 @@ func Plain() goht.Template { }) } +func HamlPlain() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

\nThis is plain text. It

will
be displayed as HTML.\n"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors("This
\"will\"
be interpolated with HTML intact."); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("\n

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + func Escaped() goht.Template { return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { __buf, __isBuf := __w.(goht.Buffer) @@ -72,6 +102,36 @@ func Escaped() goht.Template { }) } +func HamlEscaped() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

\nThis is escaped text. It <pre>will not</pre> be displayed as HTML.\n"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString("This

\"will not\"
be interpolated with HTML intact.")); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("\n

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + func Preserve() goht.Template { return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { __buf, __isBuf := __w.(goht.Buffer) @@ -101,3 +161,33 @@ func Preserve() goht.Template { return }) } + +func HamlPreserve() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

\nThis is preserved text. It

will
be displayed as HTML. "); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors("This
\"will\"
be interpolated with HTML intact."); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString(" \n

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/formatting/formats.goht b/examples/formatting/formats.goht index 7ab44d1..bd70977 100644 --- a/examples/formatting/formats.goht +++ b/examples/formatting/formats.goht @@ -15,35 +15,103 @@ var boolVar = true var stringVar = "Hello" @goht IntExample() { -%p The integer is (#{%d intVar}). -%p The integer is (#{%b intVar}) in binary. -%p The integer is (#{%o intVar}) in octal. -%p The integer is (#{%x intVar}) in hex. -%p The integer is (#{%X intVar}) in hex with uppercase. -%p The integer is (#{%c intVar}) as a character. + %p The integer is (#{%d intVar}). + %p The integer is (#{%b intVar}) in binary. + %p The integer is (#{%o intVar}) in octal. + %p The integer is (#{%x intVar}) in hex. + %p The integer is (#{%X intVar}) in hex with uppercase. + %p The integer is (#{%c intVar}) as a character. +} + +@haml HamlIntExample() { + %p The integer is (#{%d intVar}). + %p The integer is (#{%b intVar}) in binary. + %p The integer is (#{%o intVar}) in octal. + %p The integer is (#{%x intVar}) in hex. + %p The integer is (#{%X intVar}) in hex with uppercase. + %p The integer is (#{%c intVar}) as a character. +} + +@slim SlimIntExample() { + p The integer is (#{%d intVar}). + p The integer is (#{%b intVar}) in binary. + p The integer is (#{%o intVar}) in octal. + p The integer is (#{%x intVar}) in hex. + p The integer is (#{%X intVar}) in hex with uppercase. + p The integer is (#{%c intVar}) as a character. } @goht FloatExample() { -%p The float is (#{%f floatVar}). -%p The float is (#{%e floatVar}) in scientific notation. -%p The float is (#{%.2f floatVar}) with 2 decimal places. -%p The float is (#{%9.2f floatVar}) with 2 decimal places and padded to 9 characters. -%p The float is (#{%-9.2f floatVar}) with 2 decimal places and padded to 9 characters and left aligned. -%p The float is (#{%09.2f floatVar}) with 2 decimal places and padded to 9 characters with 0s. + %p The float is (#{%f floatVar}). + %p The float is (#{%e floatVar}) in scientific notation. + %p The float is (#{%.2f floatVar}) with 2 decimal places. + %p The float is (#{%9.2f floatVar}) with 2 decimal places and padded to 9 characters. + %p The float is (#{%-9.2f floatVar}) with 2 decimal places and padded to 9 characters and left aligned. + %p The float is (#{%09.2f floatVar}) with 2 decimal places and padded to 9 characters with 0s. +} + +@haml HamlFloatExample() { + %p The float is (#{%f floatVar}). + %p The float is (#{%e floatVar}) in scientific notation. + %p The float is (#{%.2f floatVar}) with 2 decimal places. + %p The float is (#{%9.2f floatVar}) with 2 decimal places and padded to 9 characters. + %p The float is (#{%-9.2f floatVar}) with 2 decimal places and padded to 9 characters and left aligned. + %p The float is (#{%09.2f floatVar}) with 2 decimal places and padded to 9 characters with 0s. +} + +@slim SlimFloatExample() { + p The float is (#{%f floatVar}). + p The float is (#{%e floatVar}) in scientific notation. + p The float is (#{%.2f floatVar}) with 2 decimal places. + p The float is (#{%9.2f floatVar}) with 2 decimal places and padded to 9 characters. + p The float is (#{%-9.2f floatVar}) with 2 decimal places and padded to 9 characters and left aligned. + p The float is (#{%09.2f floatVar}) with 2 decimal places and padded to 9 characters with 0s. } @goht BoolExample() { -%p The bool is (#{%t boolVar}). + %p The bool is (#{%t boolVar}). +} + +@haml HamlBoolExample() { + %p The bool is (#{%t boolVar}). +} + +@slim SlimBoolExample() { + p The bool is (#{%t boolVar}). } @goht StringExample() { -%p The string is (#{stringVar}). Strings do not require any additional formatting. -%p The string is (#{%q stringVar}) quoted. -%p The string is (#{%x stringVar}) as hex. -%p The string is (#{%X stringVar}) as hex with uppercase. -%p The string is (#{%s stringVar}) as is. -%p The string is (#{%.4s stringVar}), truncated to 4 characters. -%p The string is (#{%6s stringVar}), padded to 6 characters. -%p The string is (#{%6.4s stringVar}), truncated to 4 characters and padded to 6 characters. -%p The string is (#{%6.4q stringVar}), truncated to 4 characters and padded to 6 characters and quoted. + %p The string is (#{stringVar}). Strings do not require any additional formatting. + %p The string is (#{%q stringVar}) quoted. + %p The string is (#{%x stringVar}) as hex. + %p The string is (#{%X stringVar}) as hex with uppercase. + %p The string is (#{%s stringVar}) as is. + %p The string is (#{%.4s stringVar}), truncated to 4 characters. + %p The string is (#{%6s stringVar}), padded to 6 characters. + %p The string is (#{%6.4s stringVar}), truncated to 4 characters and padded to 6 characters. + %p The string is (#{%6.4q stringVar}), truncated to 4 characters and padded to 6 characters and quoted. +} + +@haml HamlStringExample() { + %p The string is (#{stringVar}). Strings do not require any additional formatting. + %p The string is (#{%q stringVar}) quoted. + %p The string is (#{%x stringVar}) as hex. + %p The string is (#{%X stringVar}) as hex with uppercase. + %p The string is (#{%s stringVar}) as is. + %p The string is (#{%.4s stringVar}), truncated to 4 characters. + %p The string is (#{%6s stringVar}), padded to 6 characters. + %p The string is (#{%6.4s stringVar}), truncated to 4 characters and padded to 6 characters. + %p The string is (#{%6.4q stringVar}), truncated to 4 characters and padded to 6 characters and quoted. +} + +@slim SlimStringExample() { + p The string is (#{stringVar}). Strings do not require any additional formatting. + p The string is (#{%q stringVar}) quoted. + p The string is (#{%x stringVar}) as hex. + p The string is (#{%X stringVar}) as hex with uppercase. + p The string is (#{%s stringVar}) as is. + p The string is (#{%.4s stringVar}), truncated to 4 characters. + p The string is (#{%6s stringVar}), padded to 6 characters. + p The string is (#{%6.4s stringVar}), truncated to 4 characters and padded to 6 characters. + p The string is (#{%6.4q stringVar}), truncated to 4 characters and padded to 6 characters and quoted. } diff --git a/examples/formatting/formats.goht.go b/examples/formatting/formats.goht.go index 9a07f47..0b7d554 100644 --- a/examples/formatting/formats.goht.go +++ b/examples/formatting/formats.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package formatting @@ -102,6 +102,166 @@ func IntExample() goht.Template { }) } +func HamlIntExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

The integer is ("); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%d", intVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString(").

\n

The integer is ("); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%b", intVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString(") in binary.

\n

The integer is ("); __err != nil { + return + } + var __var3 string + if __var3, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%o", intVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var3); __err != nil { + return + } + if _, __err = __buf.WriteString(") in octal.

\n

The integer is ("); __err != nil { + return + } + var __var4 string + if __var4, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%x", intVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var4); __err != nil { + return + } + if _, __err = __buf.WriteString(") in hex.

\n

The integer is ("); __err != nil { + return + } + var __var5 string + if __var5, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%X", intVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var5); __err != nil { + return + } + if _, __err = __buf.WriteString(") in hex with uppercase.

\n

The integer is ("); __err != nil { + return + } + var __var6 string + if __var6, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%c", intVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var6); __err != nil { + return + } + if _, __err = __buf.WriteString(") as a character.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimIntExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

The integer is ("); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%d", intVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString(").

\n

The integer is ("); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%b", intVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString(") in binary.

\n

The integer is ("); __err != nil { + return + } + var __var3 string + if __var3, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%o", intVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var3); __err != nil { + return + } + if _, __err = __buf.WriteString(") in octal.

\n

The integer is ("); __err != nil { + return + } + var __var4 string + if __var4, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%x", intVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var4); __err != nil { + return + } + if _, __err = __buf.WriteString(") in hex.

\n

The integer is ("); __err != nil { + return + } + var __var5 string + if __var5, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%X", intVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var5); __err != nil { + return + } + if _, __err = __buf.WriteString(") in hex with uppercase.

\n

The integer is ("); __err != nil { + return + } + var __var6 string + if __var6, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%c", intVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var6); __err != nil { + return + } + if _, __err = __buf.WriteString(") as a character.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + func FloatExample() goht.Template { return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { __buf, __isBuf := __w.(goht.Buffer) @@ -182,7 +342,7 @@ func FloatExample() goht.Template { }) } -func BoolExample() goht.Template { +func HamlFloatExample() goht.Template { return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { __buf, __isBuf := __w.(goht.Buffer) if !__isBuf { @@ -192,17 +352,67 @@ func BoolExample() goht.Template { var __children goht.Template ctx, __children = goht.PopChildren(ctx) _ = __children - if _, __err = __buf.WriteString("

The bool is ("); __err != nil { + if _, __err = __buf.WriteString("

The float is ("); __err != nil { return } var __var1 string - if __var1, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%t", boolVar))); __err != nil { + if __var1, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%f", floatVar))); __err != nil { return } if _, __err = __buf.WriteString(__var1); __err != nil { return } - if _, __err = __buf.WriteString(").

\n"); __err != nil { + if _, __err = __buf.WriteString(").

\n

The float is ("); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%e", floatVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString(") in scientific notation.

\n

The float is ("); __err != nil { + return + } + var __var3 string + if __var3, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%.2f", floatVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var3); __err != nil { + return + } + if _, __err = __buf.WriteString(") with 2 decimal places.

\n

The float is ("); __err != nil { + return + } + var __var4 string + if __var4, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%9.2f", floatVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var4); __err != nil { + return + } + if _, __err = __buf.WriteString(") with 2 decimal places and padded to 9 characters.

\n

The float is ("); __err != nil { + return + } + var __var5 string + if __var5, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%-9.2f", floatVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var5); __err != nil { + return + } + if _, __err = __buf.WriteString(") with 2 decimal places and padded to 9 characters and left aligned.

\n

The float is ("); __err != nil { + return + } + var __var6 string + if __var6, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%09.2f", floatVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var6); __err != nil { + return + } + if _, __err = __buf.WriteString(") with 2 decimal places and padded to 9 characters with 0s.

\n"); __err != nil { return } if !__isBuf { @@ -212,7 +422,397 @@ func BoolExample() goht.Template { }) } -func StringExample() goht.Template { +func SlimFloatExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

The float is ("); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%f", floatVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString(").

\n

The float is ("); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%e", floatVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString(") in scientific notation.

\n

The float is ("); __err != nil { + return + } + var __var3 string + if __var3, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%.2f", floatVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var3); __err != nil { + return + } + if _, __err = __buf.WriteString(") with 2 decimal places.

\n

The float is ("); __err != nil { + return + } + var __var4 string + if __var4, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%9.2f", floatVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var4); __err != nil { + return + } + if _, __err = __buf.WriteString(") with 2 decimal places and padded to 9 characters.

\n

The float is ("); __err != nil { + return + } + var __var5 string + if __var5, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%-9.2f", floatVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var5); __err != nil { + return + } + if _, __err = __buf.WriteString(") with 2 decimal places and padded to 9 characters and left aligned.

\n

The float is ("); __err != nil { + return + } + var __var6 string + if __var6, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%09.2f", floatVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var6); __err != nil { + return + } + if _, __err = __buf.WriteString(") with 2 decimal places and padded to 9 characters with 0s.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func BoolExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

The bool is ("); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%t", boolVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString(").

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func HamlBoolExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

The bool is ("); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%t", boolVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString(").

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimBoolExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

The bool is ("); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%t", boolVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString(").

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func StringExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

The string is ("); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(stringVar)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("). Strings do not require any additional formatting.

\n

The string is ("); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%q", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString(") quoted.

\n

The string is ("); __err != nil { + return + } + var __var3 string + if __var3, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%x", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var3); __err != nil { + return + } + if _, __err = __buf.WriteString(") as hex.

\n

The string is ("); __err != nil { + return + } + var __var4 string + if __var4, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%X", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var4); __err != nil { + return + } + if _, __err = __buf.WriteString(") as hex with uppercase.

\n

The string is ("); __err != nil { + return + } + var __var5 string + if __var5, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%s", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var5); __err != nil { + return + } + if _, __err = __buf.WriteString(") as is.

\n

The string is ("); __err != nil { + return + } + var __var6 string + if __var6, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%.4s", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var6); __err != nil { + return + } + if _, __err = __buf.WriteString("), truncated to 4 characters.

\n

The string is ("); __err != nil { + return + } + var __var7 string + if __var7, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%6s", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var7); __err != nil { + return + } + if _, __err = __buf.WriteString("), padded to 6 characters.

\n

The string is ("); __err != nil { + return + } + var __var8 string + if __var8, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%6.4s", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var8); __err != nil { + return + } + if _, __err = __buf.WriteString("), truncated to 4 characters and padded to 6 characters.

\n

The string is ("); __err != nil { + return + } + var __var9 string + if __var9, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%6.4q", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var9); __err != nil { + return + } + if _, __err = __buf.WriteString("), truncated to 4 characters and padded to 6 characters and quoted.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func HamlStringExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

The string is ("); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(stringVar)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("). Strings do not require any additional formatting.

\n

The string is ("); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%q", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString(") quoted.

\n

The string is ("); __err != nil { + return + } + var __var3 string + if __var3, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%x", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var3); __err != nil { + return + } + if _, __err = __buf.WriteString(") as hex.

\n

The string is ("); __err != nil { + return + } + var __var4 string + if __var4, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%X", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var4); __err != nil { + return + } + if _, __err = __buf.WriteString(") as hex with uppercase.

\n

The string is ("); __err != nil { + return + } + var __var5 string + if __var5, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%s", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var5); __err != nil { + return + } + if _, __err = __buf.WriteString(") as is.

\n

The string is ("); __err != nil { + return + } + var __var6 string + if __var6, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%.4s", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var6); __err != nil { + return + } + if _, __err = __buf.WriteString("), truncated to 4 characters.

\n

The string is ("); __err != nil { + return + } + var __var7 string + if __var7, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%6s", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var7); __err != nil { + return + } + if _, __err = __buf.WriteString("), padded to 6 characters.

\n

The string is ("); __err != nil { + return + } + var __var8 string + if __var8, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%6.4s", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var8); __err != nil { + return + } + if _, __err = __buf.WriteString("), truncated to 4 characters and padded to 6 characters.

\n

The string is ("); __err != nil { + return + } + var __var9 string + if __var9, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%6.4q", stringVar))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var9); __err != nil { + return + } + if _, __err = __buf.WriteString("), truncated to 4 characters and padded to 6 characters and quoted.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimStringExample() goht.Template { return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { __buf, __isBuf := __w.(goht.Buffer) if !__isBuf { diff --git a/examples/go/code.goht b/examples/go/code.goht index 4da42bd..6b140c8 100644 --- a/examples/go/code.goht +++ b/examples/go/code.goht @@ -10,10 +10,28 @@ func sayHello() string { } @goht ExecuteCode() { -- foo := sayHello() -%p= foo + - foo := sayHello() + %p= foo +} + +@haml HamlExecuteCode() { + - foo := sayHello() + %p= foo +} + +@slim SlimExecuteCode() { + - foo := sayHello() + p= foo } @goht RenderCode() { -%p= sayHello() + %p= sayHello() +} + +@haml HamlRenderCode() { + %p= sayHello() +} + +@slim SlimRenderCode() { + p= sayHello() } diff --git a/examples/go/code.goht.go b/examples/go/code.goht.go index 82eb023..c975351 100644 --- a/examples/go/code.goht.go +++ b/examples/go/code.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package example @@ -47,6 +47,68 @@ func ExecuteCode() goht.Template { }) } +func HamlExecuteCode() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + foo := sayHello() + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(foo)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimExecuteCode() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + foo := sayHello() + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(foo)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + func RenderCode() goht.Template { return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { __buf, __isBuf := __w.(goht.Buffer) @@ -76,3 +138,63 @@ func RenderCode() goht.Template { return }) } + +func HamlRenderCode() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(sayHello())); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimRenderCode() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(sayHello())); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/go/doc.goht b/examples/go/doc.goht index e972a03..37b5e99 100644 --- a/examples/go/doc.goht +++ b/examples/go/doc.goht @@ -2,5 +2,13 @@ package example @goht Doc() { -.doc An example of package documentation. + .doc An example of package documentation. +} + +@haml HamlDoc() { + .doc An example of package documentation. +} + +@slim SlimDoc() { + .doc An example of package documentation. } diff --git a/examples/go/doc.goht.go b/examples/go/doc.goht.go index a2c8262..d4ce08e 100644 --- a/examples/go/doc.goht.go +++ b/examples/go/doc.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package example @@ -28,3 +28,43 @@ func Doc() goht.Template { return }) } + +func HamlDoc() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
An example of package documentation.
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimDoc() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
An example of package documentation.
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/go/imports.goht b/examples/go/imports.goht index 4b5493f..46c5212 100644 --- a/examples/go/imports.goht +++ b/examples/go/imports.goht @@ -13,5 +13,13 @@ import ( // will be removed. @goht ImportExample() { -%p= fmt.Sprintf("Hello, %s!", strings.TrimSuffix("World!", "!")) + %p= fmt.Sprintf("Hello, %s!", strings.TrimSuffix("World!", "!")) +} + +@haml HamlImportExample() { + %p= fmt.Sprintf("Hello, %s!", strings.TrimSuffix("World!", "!")) +} + +@slim SlimImportExample() { + p= fmt.Sprintf("Hello, %s!", strings.TrimSuffix("World!", "!")) } diff --git a/examples/go/imports.goht.go b/examples/go/imports.goht.go index 8a856fe..8195607 100644 --- a/examples/go/imports.goht.go +++ b/examples/go/imports.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package example @@ -46,3 +46,63 @@ func ImportExample() goht.Template { return }) } + +func HamlImportExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(fmt.Sprintf("Hello, %s!", strings.TrimSuffix("World!", "!")))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimImportExample() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(fmt.Sprintf("Hello, %s!", strings.TrimSuffix("World!", "!")))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/go/inlining.goht b/examples/go/inlining.goht index a4d154f..9bc0b43 100644 --- a/examples/go/inlining.goht +++ b/examples/go/inlining.goht @@ -1,19 +1,45 @@ package example +import ( + "fmt" +) + // You can include Go code to handle conditional and loop statements // in your Goht templates. var isAdmin = true @goht Conditional() { -.actions - - if isAdmin { - %button< - Edit content - - } else { - %button Login - - } - %button View content + .actions + - if isAdmin { + %button< + Edit content + - } else { + %button Login + - } + %button View content +} + +@haml HamlConditional() { + .actions + - if isAdmin { + %button< + Edit content + - } else { + %button Login + - } + %button View content +} + +@slim SlimConditional() { + .actions + - if isAdmin { + button + Edit content + - } else { + button Login + - } + button View content } // However, we are using Haml and so we're into shortcuts. We can @@ -25,13 +51,33 @@ var isAdmin = true // for, if, else, else if, switch @goht ShorthandConditional() { -.actions - - if isAdmin - %button< - Edit content - - else - %button Login - %button View content + .actions + - if isAdmin + %button< + Edit content + - else + %button Login + %button View content +} + +@haml HamlShorthandConditional() { + .actions + - if isAdmin + %button< + Edit content + - else + %button Login + %button View content +} + +@slim SlimShorthandConditional() { + .actions + - if isAdmin + button + |Edit content + - else + button Login + button View content } // With a switch statement, we can use the case and default keywords @@ -39,12 +85,58 @@ var isAdmin = true // shorthand syntax. (win some, lose some) @goht ShorthandSwitch() { -.actions - - switch isAdmin - - case true: - %button< - Edit content - - case false: - %button Login - %button View content + .actions + - switch isAdmin + - case true: + %button< + Edit content + - case false: + %button Login + %button View content +} + +@haml HamlShorthandSwitch() { + .actions + - switch isAdmin + - case true: + %button< + Edit content + - case false: + %button Login + %button View content +} + +@slim SlimShorthandSwitch() { + .actions + - switch isAdmin + - case true: + button + |Edit content + - case false: + button Login + button View content +} + +// Slim supports splitting the control code across multiple lines which +// is useful for long statements. The additional lines must be indented +// one additional level. +// +// Ending a statement with a backslash or a comma will let you break +// long statements across multiple lines. + +type longType struct { + title string + actions string +} + +@slim SlimLongStatement() { + .actions + - action := longType{\ + title: "Edit content", + actions: "Edit content", + } + p=action.actions + = fmt.Sprintf("Title: %s", + action.title, + ) } diff --git a/examples/go/inlining.goht.go b/examples/go/inlining.goht.go index 24be191..ed69e14 100644 --- a/examples/go/inlining.goht.go +++ b/examples/go/inlining.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package example @@ -6,6 +6,9 @@ package example import "context" import "io" import "github.com/stackus/goht" +import ( + "fmt" +) // You can include Go code to handle conditional and loop statements // in your Goht templates. @@ -44,6 +47,70 @@ func Conditional() goht.Template { }) } +func HamlConditional() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
\n"); __err != nil { + return + } + if isAdmin { + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + } else { + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + } + if _, __err = __buf.WriteString("\n
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimConditional() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
\n"); __err != nil { + return + } + if isAdmin { + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + } else { + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + } + if _, __err = __buf.WriteString("\n
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // However, we are using Haml and so we're into shortcuts. We can // continue to write out the brackets or we can use the shorthand // syntax. @@ -82,6 +149,66 @@ func ShorthandConditional() goht.Template { }) } +func HamlShorthandConditional() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
\n"); __err != nil { + return + } + if isAdmin { + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + } else if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + if _, __err = __buf.WriteString("\n
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimShorthandConditional() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
\n"); __err != nil { + return + } + if isAdmin { + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + } else if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + if _, __err = __buf.WriteString("\n
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // With a switch statement, we can use the case and default keywords // but we will need to nest these statements if we're using the // shorthand syntax. (win some, lose some) @@ -118,3 +245,122 @@ func ShorthandSwitch() goht.Template { return }) } + +func HamlShorthandSwitch() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
\n"); __err != nil { + return + } + switch isAdmin { + case true: + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + case false: + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + } + if _, __err = __buf.WriteString("\n
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimShorthandSwitch() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
\n"); __err != nil { + return + } + switch isAdmin { + case true: + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + case false: + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + } + if _, __err = __buf.WriteString("\n
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +// Slim supports splitting the control code across multiple lines which +// is useful for long statements. The additional lines must be indented +// one additional level. +// +// Ending a statement with a backslash or a comma will let you break +// long statements across multiple lines. + +type longType struct { + title string + actions string +} + +func SlimLongStatement() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
\n"); __err != nil { + return + } + action := longType{title: "Edit content", actions: "Edit content"} + if _, __err = __buf.WriteString("
\n

"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(action.actions)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.EscapeString(fmt.Sprintf("Title: %s", action.title))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/go/interpolation.goht b/examples/go/interpolation.goht index 7c86477..3f89fd9 100644 --- a/examples/go/interpolation.goht +++ b/examples/go/interpolation.goht @@ -6,14 +6,34 @@ package example var someVar = "Hello" @goht InterpolateCode() { -%p #{someVar}, World! + %p #{someVar}, World! +} + +@haml HamlInterpolateCode() { + %p #{someVar}, World! +} + +@slim SlimInterpolateCode() { + p #{someVar}, World! } // Interpolation is not done within Go code or within a string literal. @goht NoInterpolation() { -%p Do the following; No interpolation is necessary. -%p= someVar + ", World!" -%p= "No interpolation is #{performed} here." + %p Do the following; No interpolation is necessary. + %p= someVar + ", World!" + %p= "No interpolation is #{performed} here." +} + +@haml HamlNoInterpolation() { + %p Do the following; No interpolation is necessary. + %p= someVar + ", World!" + %p= "No interpolation is #{performed} here." +} + +@slim SlimNoInterpolation() { + p Do the following; No interpolation is necessary. + p= someVar + ", World!" + p= "No interpolation is #{performed} here." } // Because the interpolation and tag id share the same starting character, @@ -23,8 +43,13 @@ var someVar = "Hello" // when it is the first character of text following a tag. @goht EscapeInterpolation() { -\#{someVar}, World! -%p #{someVar}, World! + \#{someVar}, World! + %p #{someVar}, World! +} + +@haml HamlEscapeInterpolation() { + \#{someVar}, World! + %p #{someVar}, World! } // There are also times when you want to ignore the interpolation and just @@ -42,11 +67,21 @@ var someVar = "Hello" // will render as "#{someVar}" in the final HTML. @goht IgnoreInterpolation() { -\\#{someVar}, World! -%p - \\#{someVar}, World! -A greeting: \#{someVar}, World! -\. this line begins with a period -\# this line begins with a hash -\% this line begins with a percent + \\#{someVar}, World! + %p + \\#{someVar}, World! + A greeting: \#{someVar}, World! + \. this line begins with a period + \# this line begins with a hash + \% this line begins with a percent +} + +@haml HamlIgnoreInterpolation() { + \\#{someVar}, World! + %p + \\#{someVar}, World! + A greeting: \#{someVar}, World! + \. this line begins with a period + \# this line begins with a hash + \% this line begins with a percent } diff --git a/examples/go/interpolation.goht.go b/examples/go/interpolation.goht.go index a1dcba9..d7877b6 100644 --- a/examples/go/interpolation.goht.go +++ b/examples/go/interpolation.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package example @@ -42,6 +42,66 @@ func InterpolateCode() goht.Template { }) } +func HamlInterpolateCode() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(someVar)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString(", World!

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimInterpolateCode() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(someVar)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString(", World!

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // Interpolation is not done within Go code or within a string literal. func NoInterpolation() goht.Template { return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { @@ -83,6 +143,86 @@ func NoInterpolation() goht.Template { }) } +func HamlNoInterpolation() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

Do the following; No interpolation is necessary.

\n

"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(someVar + ", World!")); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n

"); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.EscapeString("No interpolation is #{performed} here.")); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimNoInterpolation() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

Do the following; No interpolation is necessary.

\n

"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(someVar + ", World!")); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n

"); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.EscapeString("No interpolation is #{performed} here.")); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // Because the interpolation and tag id share the same starting character, // a `#` you will need to escape the interpolation with a backslash when it // is the first character of a line. @@ -126,6 +266,43 @@ func EscapeInterpolation() goht.Template { }) } +func HamlEscapeInterpolation() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(someVar)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString(", World!\n

"); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.EscapeString(someVar)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString(", World!

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // There are also times when you want to ignore the interpolation and just // print the text. This is also handled with the backslash. // This can be done at the start of a line, after a tag or even mid-text. @@ -159,3 +336,23 @@ func IgnoreInterpolation() goht.Template { return }) } + +func HamlIgnoreInterpolation() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("#{someVar}, World!\n

\n#{someVar}, World!\n

\nA greeting: #{someVar}, World!\n. this line begins with a period\n# this line begins with a hash\n% this line begins with a percent\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/go/package.goht b/examples/go/package.goht index 692e8a3..fbcea6f 100644 --- a/examples/go/package.goht +++ b/examples/go/package.goht @@ -5,6 +5,5 @@ package example // must be the first line of the file. If you do not specify a package, // the generated code will use "main" as the package name. - @goht PackageExample() { } diff --git a/examples/go/package.goht.go b/examples/go/package.goht.go index aa0d587..9658caf 100644 --- a/examples/go/package.goht.go +++ b/examples/go/package.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package example diff --git a/examples/go/receivers.goht b/examples/go/receivers.goht index dcf8547..19df318 100644 --- a/examples/go/receivers.goht +++ b/examples/go/receivers.goht @@ -17,8 +17,22 @@ type User struct { // @render u.Details() @goht (u User) Details() { -.name User name: #{u.Name} -.age - User Age: - !=%d u.Age + .name User name: #{u.Name} + .age + User Age: + !=%d u.Age +} + +@haml (u User) HamlDetails() { + .name User name: #{u.Name} + .age + User Age: + !=%d u.Age +} + +@slim (u User) SlimDetails() { + .name User name: #{u.Name} + .age + User Age: + =%d u.Age } diff --git a/examples/go/receivers.goht.go b/examples/go/receivers.goht.go index ef30220..2d24a34 100644 --- a/examples/go/receivers.goht.go +++ b/examples/go/receivers.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package example @@ -62,3 +62,83 @@ func (u User) Details() goht.Template { return }) } + +func (u User) HamlDetails() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
User name: "); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(u.Name)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("
\n
\nUser Age:\n"); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.FormatString("%d", u.Age)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString("\n
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func (u User) SlimDetails() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
User name: "); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(u.Name)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("
\n
\nAge:\n"); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%d", u.Age))); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString("\n
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/hello/world.goht b/examples/hello/world.goht index 49b22ec..7efd95f 100644 --- a/examples/hello/world.goht +++ b/examples/hello/world.goht @@ -4,30 +4,88 @@ package hello var terms = []string{"foo", "bar", "baz", "fizz", "buzz", "quux"} @goht termsWrapper(term string) { -%p=@children -%p And it was passed in as well #{term} + %p=@children + %p And it was passed in as well #{term} +} + +@haml hamlTermsWrapper(term string) { + %p=@children + %p And it was passed in as well #{term} +} + +@slim slimTermsWrapper(term string) { + p=@children + p And it was passed in as well #{term} } @goht World() { -!!! -%html{lang: "en"} - %head - %meta{charset: "utf-8"} - %title Hello World - :css - body { - color: white; - font-family: sans-serif; - background-color: #333; - } - .term { - font-weight: bold; - color: #99f; - } - %body - %h1 Hello World - %p the following will loop a slice of strings and will pass each string into a child template - - for _, term := range terms - =@render termsWrapper(term) - %p.term= term + !!! + %html{lang: "en"} + %head + %meta{charset: "utf-8"} + %title Hello World + :css + body { + color: white; + font-family: sans-serif; + background-color: #333; + } + .term { + font-weight: bold; + color: #99f; + } + %body + %h1 Hello World + %p the following will loop a slice of strings and will pass each string into a child template + - for _, term := range terms + =@render termsWrapper(term) + %p.term= term +} + +@haml HamlWorld() { + !!! + %html{lang: "en"} + %head + %meta{charset: "utf-8"} + %title Hello World + :css + body { + color: white; + font-family: sans-serif; + background-color: #333; + } + .term { + font-weight: bold; + color: #99f; + } + %body + %h1 Hello World + %p the following will loop a slice of strings and will pass each string into a child template + - for _, term := range terms + =@render hamlTermsWrapper(term) + %p.term= term +} + +@slim SlimWorld() { + doctype + html{lang: "en"} + head + meta{charset: "utf-8"} + title Hello World + :css + body { + color: white; + font-family: sans-serif; + background-color: #333; + } + .term { + font-weight: bold; + color: #99f; + } + body + h1 Hello World + p the following will loop a slice of strings and will pass each string into a child template + - for _, term := range terms + =@render slimTermsWrapper(term) + p.term= term } diff --git a/examples/hello/world.goht.go b/examples/hello/world.goht.go index 64e96b6..64f1cd6 100644 --- a/examples/hello/world.goht.go +++ b/examples/hello/world.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package hello @@ -47,6 +47,78 @@ func termsWrapper(term string) goht.Template { }) } +func hamlTermsWrapper(term string) goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + if __err = __children.Render(ctx, __buf); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n

And it was passed in as well "); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(term)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func slimTermsWrapper(term string) goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + if __err = __children.Render(ctx, __buf); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n

And it was passed in as well "); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(term)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + func World() goht.Template { return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { __buf, __isBuf := __w.(goht.Buffer) @@ -98,3 +170,107 @@ func World() goht.Template { return }) } + +func HamlWorld() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("\n\n\nHello World\n\n\n

Hello World

\n

the following will loop a slice of strings and will pass each string into a child template

\n"); __err != nil { + return + } + for _, term := range terms { + __var1 := goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.EscapeString(term)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = io.Copy(__w, __buf) + } + return + }) + if __err = hamlTermsWrapper(term).Render(goht.PushChildren(ctx, __var1), __buf); __err != nil { + return + } + } + if _, __err = __buf.WriteString("\n\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimWorld() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("\n\n\nHello World\n\n\n

Hello World

\n

the following will loop a slice of strings and will pass each string into a child template

\n"); __err != nil { + return + } + for _, term := range terms { + __var1 := goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(goht.EscapeString(term)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = io.Copy(__w, __buf) + } + return + }) + if __err = slimTermsWrapper(term).Render(goht.PushChildren(ctx, __var1), __buf); __err != nil { + return + } + } + if _, __err = __buf.WriteString("\n\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/indents/tabs.goht b/examples/indents/tabs.goht index b5a9606..4818515 100644 --- a/examples/indents/tabs.goht +++ b/examples/indents/tabs.goht @@ -8,15 +8,43 @@ package indents // spaces in another. @goht UsingTabs() { -!!! -%html{lang: "en"} - %head - %meta{charset: "utf-8"} - %title - Hello World - %body - %h1 - Hello World - %p - Hello World + !!! + %html{lang: "en"} + %head + %meta{charset: "utf-8"} + %title + Hello World + %body + %h1 + Hello World + %p + Hello World +} + +@haml HamlUsingTabs() { + !!! + %html{lang: "en"} + %head + %meta{charset: "utf-8"} + %title + Hello World + %body + %h1 + Hello World + %p + Hello World +} + +@slim SlimUsingTabs() { + doctype + html{lang: "en"} + head + meta{charset: "utf-8"} + title + |Hello World + body + h1 + |Hello World + p + |Hello World } diff --git a/examples/indents/tabs.goht.go b/examples/indents/tabs.goht.go index 5709d2c..8514271 100644 --- a/examples/indents/tabs.goht.go +++ b/examples/indents/tabs.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package indents @@ -33,3 +33,43 @@ func UsingTabs() goht.Template { return }) } + +func HamlUsingTabs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("\n\n\n\nHello World\n\n\n\n

\nHello World\n

\n

\nHello World\n

\n\n\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimUsingTabs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("\n\n\n\nHello World\n\n\n\n

\nHello World\n

\n

\nHello World\n

\n\n\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/tags/general.goht b/examples/tags/general.goht index facdc6d..68e4ae7 100644 --- a/examples/tags/general.goht +++ b/examples/tags/general.goht @@ -2,26 +2,60 @@ package tags // You may specify the type of tag that you want created with `%`. @goht SpecifyTag() { -%p This is a paragraph tag. -%main This is a main tag. + %p This is a paragraph tag. + %main This is a main tag. +} + +@haml HamlSpecifyTag() { + %p This is a paragraph tag. + %main This is a main tag. +} + +@slim SlimSpecifyTag() { + p This is a paragraph tag. + main This is a main tag. } // You may also let the tag default to a `div` when using the id or // class syntax's, `#` and `.` respectively. @goht DefaultToDivs() { -#main This is a div tag with an id of `main`. -.main This is a div tag with a class of `main`. + #main This is a div tag with an id of `main`. + .main This is a div tag with a class of `main`. +} + +@haml HamlDefaultToDivs() { + #main This is a div tag with an id of `main`. + .main This is a div tag with a class of `main`. +} + +@slim SlimDefaultToDivs() { + #main This is a div tag with an id of `main`. + .main This is a div tag with a class of `main`. } // The three may also be combined. The `%` must come first, followed // by either the `#` or `.`. The `#` and `.` may be in any order. @goht Combined() { -%p#main This is a paragraph tag with an id of `main`. -%main.main This is a main tag with a class of `main`. -.main#main This is a div tag with an id and class of `main`. -%p.main#main This is a paragraph tag with an id and class of `main`. + %p#main This is a paragraph tag with an id of `main`. + %main.main This is a main tag with a class of `main`. + .main#main This is a div tag with an id and class of `main`. + %p.main#main This is a paragraph tag with an id and class of `main`. +} + +@haml HamlCombined() { + %p#main This is a paragraph tag with an id of `main`. + %main.main This is a main tag with a class of `main`. + .main#main This is a div tag with an id and class of `main`. + %p.main#main This is a paragraph tag with an id and class of `main`. +} + +@slim SlimCombined() { + p#main This is a paragraph tag with an id of `main`. + main.main This is a main tag with a class of `main`. + .main#main This is a div tag with an id and class of `main`. + p.main#main This is a paragraph tag with an id and class of `main`. } // The class operator may be repeated to add multiple classes. @@ -29,6 +63,16 @@ package tags // but will not throw an error. @goht MultipleClasses() { -.main.main2 This is a div tag with two classes, `main` and `main2`. -#main#main2 This is a div tag with an id of `main2`. + .main.main2 This is a div tag with two classes, `main` and `main2`. + #main#main2 This is a div tag with an id of `main2`. +} + +@haml HamlMultipleClasses() { + .main.main2 This is a div tag with two classes, `main` and `main2`. + #main#main2 This is a div tag with an id of `main2`. +} + +@slim SlimMultipleClasses() { + .main.main2 This is a div tag with two classes, `main` and `main2`. + #main#main2 This is a div tag with an id of `main2`. } diff --git a/examples/tags/general.goht.go b/examples/tags/general.goht.go index dd825fa..98126a0 100644 --- a/examples/tags/general.goht.go +++ b/examples/tags/general.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package tags @@ -29,6 +29,46 @@ func SpecifyTag() goht.Template { }) } +func HamlSpecifyTag() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is a paragraph tag.

\n
This is a main tag.
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimSpecifyTag() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is a paragraph tag.

\n
This is a main tag.
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // You may also let the tag default to a `div` when using the id or // class syntax's, `#` and `.` respectively. @@ -52,6 +92,46 @@ func DefaultToDivs() goht.Template { }) } +func HamlDefaultToDivs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
This is a div tag with an id of `main`.
\n
This is a div tag with a class of `main`.
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimDefaultToDivs() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
This is a div tag with an id of `main`.
\n
This is a div tag with a class of `main`.
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // The three may also be combined. The `%` must come first, followed // by either the `#` or `.`. The `#` and `.` may be in any order. @@ -75,6 +155,46 @@ func Combined() goht.Template { }) } +func HamlCombined() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is a paragraph tag with an id of `main`.

\n
This is a main tag with a class of `main`.
\n
This is a div tag with an id and class of `main`.
\n

This is a paragraph tag with an id and class of `main`.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimCombined() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is a paragraph tag with an id of `main`.

\n
This is a main tag with a class of `main`.
\n
This is a div tag with an id and class of `main`.
\n

This is a paragraph tag with an id and class of `main`.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // The class operator may be repeated to add multiple classes. // Repeating the id operator will result in the id being overwritten // but will not throw an error. @@ -98,3 +218,43 @@ func MultipleClasses() goht.Template { return }) } + +func HamlMultipleClasses() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
This is a div tag with two classes, `main` and `main2`.
\n
This is a div tag with an id of `main2`.
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimMultipleClasses() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("
This is a div tag with two classes, `main` and `main2`.
\n
This is a div tag with an id of `main2`.
\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/tags/inline.goht b/examples/tags/inline.goht new file mode 100644 index 0000000..c2296af --- /dev/null +++ b/examples/tags/inline.goht @@ -0,0 +1,11 @@ +package tags + +// Slim allows you to inline tags for those times when you really +// want to keep things concise. + +@slim SlimInlineTags() { + ul + li: a.first First Item + li: a.second First Item + li: a.third First Item +} diff --git a/examples/tags/inline.goht.go b/examples/tags/inline.goht.go new file mode 100644 index 0000000..dce5fdb --- /dev/null +++ b/examples/tags/inline.goht.go @@ -0,0 +1,31 @@ +// Code generated by GoHT v0.6.0 - DO NOT EDIT. +// https://github.com/stackus/goht + +package tags + +import "context" +import "io" +import "github.com/stackus/goht" + +// Slim allows you to inline tags for those times when you really +// want to keep things concise. + +func SlimInlineTags() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/tags/objectreference.goht b/examples/tags/objectreference.goht index 29362cb..04a444b 100644 --- a/examples/tags/objectreference.goht +++ b/examples/tags/objectreference.goht @@ -25,13 +25,22 @@ func (f *Foo) ObjectClass() string { //
Foo article
@goht ObjectRefs(obj Foo) { -%article[obj] Foo article + %article[obj] Foo article +} + +@haml HamlObjectRefs(obj Foo) { + %article[obj] Foo article } // You may include a prefix to be used with the id and class. var prefixVar = "article" @goht PrefixedObjectRefs(obj Foo) { -%article[obj, "prefix"] Foo article with id "prefix_foo_bar" and class "prefix_foo" -%article[obj, prefixVar] Foo article with id "article_foo_bar" and class "article_foo" + %article[obj, "prefix"] Foo article with id "prefix_foo_bar" and class "prefix_foo" + %article[obj, prefixVar] Foo article with id "article_foo_bar" and class "article_foo" +} + +@haml HamlPrefixedObjectRefs(obj Foo) { + %article[obj, "prefix"] Foo article with id "prefix_foo_bar" and class "prefix_foo" + %article[obj, prefixVar] Foo article with id "article_foo_bar" and class "article_foo" } diff --git a/examples/tags/objectreference.goht.go b/examples/tags/objectreference.goht.go index 5571857..2f2253b 100644 --- a/examples/tags/objectreference.goht.go +++ b/examples/tags/objectreference.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package tags @@ -68,6 +68,42 @@ func ObjectRefs(obj Foo) goht.Template { }) } +func HamlObjectRefs(obj Foo) goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("Foo article\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // You may include a prefix to be used with the id and class. var prefixVar = "article" @@ -122,3 +158,55 @@ func PrefixedObjectRefs(obj Foo) goht.Template { return }) } + +func HamlPrefixedObjectRefs(obj Foo) goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("Foo article with id \"prefix_foo_bar\" and class \"prefix_foo\"\nFoo article with id \"article_foo_bar\" and class \"article_foo\"\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/tags/selfclosing.goht b/examples/tags/selfclosing.goht index e6f8928..2aa0f4b 100644 --- a/examples/tags/selfclosing.goht +++ b/examples/tags/selfclosing.goht @@ -8,10 +8,24 @@ package tags // to display an error. @goht SelfClosing() { -%img{src: "logo.png", alt: "logo"} -%p - A paragraph is not self closing. - %img{src: "logo.png", alt: "logo"} + %img{src: "logo.png", alt: "logo"} + %p + A paragraph is not self closing. + %img{src: "logo.png", alt: "logo"} +} + +@haml HamlSelfClosing() { + %img{src: "logo.png", alt: "logo"} + %p + A paragraph is not self closing. + %img{src: "logo.png", alt: "logo"} +} + +@slim SlimSelfClosing() { + img{src: "logo.png", alt: "logo"} + p + A paragraph is not self closing. + img{src: "logo.png", alt: "logo"} } // You may also use the self closing tag syntax to create a tag @@ -25,5 +39,13 @@ package tags // "meta", "param", "source", "track", "wbr", @goht AlsoSelfClosing() { -%isNowSelfClosing/ + %isNowSelfClosing/ +} + +@haml HamlAlsoSelfClosing() { + %isNowSelfClosing/ +} + +@slim SlimAlsoSelfClosing() { + isNowSelfClosing/ } diff --git a/examples/tags/selfclosing.goht.go b/examples/tags/selfclosing.goht.go index e9684d3..4b3ec96 100644 --- a/examples/tags/selfclosing.goht.go +++ b/examples/tags/selfclosing.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package tags @@ -35,6 +35,46 @@ func SelfClosing() goht.Template { }) } +func HamlSelfClosing() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("\"logo\"

\nA paragraph is not self closing.\n\"logo\"

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimSelfClosing() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("\"logo\"

\nparagraph is not self closing.\n\"logo\"

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // You may also use the self closing tag syntax to create a tag // that is not self closing. This is useful for creating tags // that are not known by the parser. @@ -64,3 +104,43 @@ func AlsoSelfClosing() goht.Template { return }) } + +func HamlAlsoSelfClosing() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString(""); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimAlsoSelfClosing() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString(""); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/tags/whitespace.goht b/examples/tags/whitespace.goht index a01ec4c..4c484db 100644 --- a/examples/tags/whitespace.goht +++ b/examples/tags/whitespace.goht @@ -17,10 +17,24 @@ package tags // rendered HTML, so it is important to understand how it works. @goht Whitespace() { -%p This text has no whitespace between it and the tag. -%p - This text has whitespace between it and the tag. - %p This tag has whitespace between it and the tag above. + %p This text has no whitespace between it and the tag. + %p + This text has whitespace between it and the tag. + %p This tag has whitespace between it and the tag above. +} + +@haml HamlWhitespace() { + %p This text has no whitespace between it and the tag. + %p + This text has whitespace between it and the tag. + %p This tag has whitespace between it and the tag above. +} + +@slim SlimWhitespace() { + p This text has no whitespace between it and the tag. + p + This text has NO whitespace between it and the tag. + p This tag has NO whitespace between it and the tag above. } // You can control the whitespace that will be rendered between tags @@ -33,12 +47,36 @@ package tags // both, the order does not matter. @goht RemoveWhitespace() { -%p< - This text has no whitespace between it and the parent tag. -%p - There is whitespace between this text and the parent tag. - %p>< + %p< This text has no whitespace between it and the parent tag. - There is also no whitespace between this tag and the sibling text above it. - Finally, the tag has no whitespace between it and the outer tag. + %p + There is whitespace between this text and the parent tag. + %p>< + This text has no whitespace between it and the parent tag. + There is also no whitespace between this tag and the sibling text above it. + Finally, the tag has no whitespace between it and the outer tag. +} + +@haml HamlRemoveWhitespace() { + %p< + This text has no whitespace between it and the parent tag. + %p + There is whitespace between this text and the parent tag. + %p>< + This text has no whitespace between it and the parent tag. + There is also no whitespace between this tag and the sibling text above it. + Finally, the tag has no whitespace between it and the outer tag. +} + +@slim SlimRemoveWhitespace() { + p + |< + |This text has no whitespace between it and the parent tag. + p + |There is whitespace between this text and the parent tag. + p + |<> + |This text has no whitespace between it and the parent tag. + |There is also no whitespace between this tag and the sibling text above it. + |Finally, the tag has no whitespace between it and the outer tag. } diff --git a/examples/tags/whitespace.goht.go b/examples/tags/whitespace.goht.go index 91284df..86b784a 100644 --- a/examples/tags/whitespace.goht.go +++ b/examples/tags/whitespace.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package tags @@ -44,6 +44,46 @@ func Whitespace() goht.Template { }) } +func HamlWhitespace() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This text has no whitespace between it and the tag.

\n

\nThis text has whitespace between it and the tag.\n

This tag has whitespace between it and the tag above.

\n

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimWhitespace() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This text has no whitespace between it and the tag.

\n

\ntext has NO whitespace between it and the tag.\n

This tag has NO whitespace between it and the tag above.

\n

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // You can control the whitespace that will be rendered between tags // in the final output by using the `>` and <` operators. // The `>` operator will remove all whitespace outside the tag, and @@ -72,3 +112,43 @@ func RemoveWhitespace() goht.Template { return }) } + +func HamlRemoveWhitespace() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

~☢<\nThis text has no whitespace between it and the parent tag.\n>☢~

\n

\nThere is whitespace between this text and the parent tag.\n>☢~

~☢<\nThis text has no whitespace between it and the parent tag.\nThere is also no whitespace between this tag and the sibling text above it.\nFinally, the tag has no whitespace between it and the outer tag.\n>☢~

~☢<

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimRemoveWhitespace() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

\n<\nThis text has no whitespace between it and the parent tag.\n

\n

\nThere is whitespace between this text and the parent tag.\n

\n<>\nThis text has no whitespace between it and the parent tag.\nThere is also no whitespace between this tag and the sibling text above it.\nFinally, the tag has no whitespace between it and the outer tag.\n

\n

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/examples/testdata/attributes_attributesCmd.html b/examples/testdata/attributes_attributesCmd.html deleted file mode 100644 index 545b161..0000000 --- a/examples/testdata/attributes_attributesCmd.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/testdata/haml/attributes_attributesCmd.html b/examples/testdata/haml/attributes_attributesCmd.html new file mode 100644 index 0000000..72cb197 --- /dev/null +++ b/examples/testdata/haml/attributes_attributesCmd.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/testdata/attributes_classes.html b/examples/testdata/haml/attributes_classes.html similarity index 100% rename from examples/testdata/attributes_classes.html rename to examples/testdata/haml/attributes_classes.html diff --git a/examples/testdata/attributes_complexNames.html b/examples/testdata/haml/attributes_complexNames.html similarity index 100% rename from examples/testdata/attributes_complexNames.html rename to examples/testdata/haml/attributes_complexNames.html diff --git a/examples/testdata/attributes_conditionalAttrs.html b/examples/testdata/haml/attributes_conditionalAttrs.html similarity index 100% rename from examples/testdata/attributes_conditionalAttrs.html rename to examples/testdata/haml/attributes_conditionalAttrs.html diff --git a/examples/testdata/attributes_dynamicAttrs.html b/examples/testdata/haml/attributes_dynamicAttrs.html similarity index 100% rename from examples/testdata/attributes_dynamicAttrs.html rename to examples/testdata/haml/attributes_dynamicAttrs.html diff --git a/examples/testdata/attributes_formattedValue.html b/examples/testdata/haml/attributes_formattedValue.html similarity index 100% rename from examples/testdata/attributes_formattedValue.html rename to examples/testdata/haml/attributes_formattedValue.html diff --git a/examples/testdata/attributes_multilineAttrs.html b/examples/testdata/haml/attributes_multilineAttrs.html similarity index 100% rename from examples/testdata/attributes_multilineAttrs.html rename to examples/testdata/haml/attributes_multilineAttrs.html diff --git a/examples/testdata/attributes_simpleNames.html b/examples/testdata/haml/attributes_simpleNames.html similarity index 100% rename from examples/testdata/attributes_simpleNames.html rename to examples/testdata/haml/attributes_simpleNames.html diff --git a/examples/testdata/attributes_staticAttrs.html b/examples/testdata/haml/attributes_staticAttrs.html similarity index 100% rename from examples/testdata/attributes_staticAttrs.html rename to examples/testdata/haml/attributes_staticAttrs.html diff --git a/examples/testdata/attributes_whitespaceAttrs.html b/examples/testdata/haml/attributes_whitespaceAttrs.html similarity index 100% rename from examples/testdata/attributes_whitespaceAttrs.html rename to examples/testdata/haml/attributes_whitespaceAttrs.html diff --git a/examples/testdata/commands_childrenExample.html b/examples/testdata/haml/commands_childrenExample.html similarity index 100% rename from examples/testdata/commands_childrenExample.html rename to examples/testdata/haml/commands_childrenExample.html diff --git a/examples/testdata/commands_renderExample.html b/examples/testdata/haml/commands_renderExample.html similarity index 100% rename from examples/testdata/commands_renderExample.html rename to examples/testdata/haml/commands_renderExample.html diff --git a/examples/testdata/commands_renderWithChildrenExample.html b/examples/testdata/haml/commands_renderWithChildrenExample.html similarity index 100% rename from examples/testdata/commands_renderWithChildrenExample.html rename to examples/testdata/haml/commands_renderWithChildrenExample.html diff --git a/examples/testdata/comments_htmlComments.html b/examples/testdata/haml/comments_htmlComments.html similarity index 100% rename from examples/testdata/comments_htmlComments.html rename to examples/testdata/haml/comments_htmlComments.html diff --git a/examples/testdata/comments_htmlCommentsNested.html b/examples/testdata/haml/comments_htmlCommentsNested.html similarity index 100% rename from examples/testdata/comments_htmlCommentsNested.html rename to examples/testdata/haml/comments_htmlCommentsNested.html diff --git a/examples/testdata/comments_rubyStyle.html b/examples/testdata/haml/comments_rubyStyle.html similarity index 100% rename from examples/testdata/comments_rubyStyle.html rename to examples/testdata/haml/comments_rubyStyle.html diff --git a/examples/testdata/comments_rubyStyleNested.html b/examples/testdata/haml/comments_rubyStyleNested.html similarity index 100% rename from examples/testdata/comments_rubyStyleNested.html rename to examples/testdata/haml/comments_rubyStyleNested.html diff --git a/examples/testdata/doctype_doctype.html b/examples/testdata/haml/doctype_doctype.html similarity index 100% rename from examples/testdata/doctype_doctype.html rename to examples/testdata/haml/doctype_doctype.html diff --git a/examples/testdata/example_conditional.html b/examples/testdata/haml/example_conditional.html similarity index 100% rename from examples/testdata/example_conditional.html rename to examples/testdata/haml/example_conditional.html diff --git a/examples/testdata/example_doc.html b/examples/testdata/haml/example_doc.html similarity index 100% rename from examples/testdata/example_doc.html rename to examples/testdata/haml/example_doc.html diff --git a/examples/testdata/example_escapeInterpolation.html b/examples/testdata/haml/example_escapeInterpolation.html similarity index 100% rename from examples/testdata/example_escapeInterpolation.html rename to examples/testdata/haml/example_escapeInterpolation.html diff --git a/examples/testdata/example_executeCode.html b/examples/testdata/haml/example_executeCode.html similarity index 100% rename from examples/testdata/example_executeCode.html rename to examples/testdata/haml/example_executeCode.html diff --git a/examples/testdata/example_ignoreInterpolation.html b/examples/testdata/haml/example_ignoreInterpolation.html similarity index 100% rename from examples/testdata/example_ignoreInterpolation.html rename to examples/testdata/haml/example_ignoreInterpolation.html diff --git a/examples/testdata/example_importExample.html b/examples/testdata/haml/example_importExample.html similarity index 100% rename from examples/testdata/example_importExample.html rename to examples/testdata/haml/example_importExample.html diff --git a/examples/testdata/example_interpolateCode.html b/examples/testdata/haml/example_interpolateCode.html similarity index 100% rename from examples/testdata/example_interpolateCode.html rename to examples/testdata/haml/example_interpolateCode.html diff --git a/examples/testdata/example_noInterpolation.html b/examples/testdata/haml/example_noInterpolation.html similarity index 100% rename from examples/testdata/example_noInterpolation.html rename to examples/testdata/haml/example_noInterpolation.html diff --git a/examples/testdata/example_packageExample.html b/examples/testdata/haml/example_packageExample.html similarity index 100% rename from examples/testdata/example_packageExample.html rename to examples/testdata/haml/example_packageExample.html diff --git a/examples/testdata/example_renderCode.html b/examples/testdata/haml/example_renderCode.html similarity index 100% rename from examples/testdata/example_renderCode.html rename to examples/testdata/haml/example_renderCode.html diff --git a/examples/testdata/example_shorthandConditional.html b/examples/testdata/haml/example_shorthandConditional.html similarity index 100% rename from examples/testdata/example_shorthandConditional.html rename to examples/testdata/haml/example_shorthandConditional.html diff --git a/examples/testdata/example_shorthandSwitch.html b/examples/testdata/haml/example_shorthandSwitch.html similarity index 100% rename from examples/testdata/example_shorthandSwitch.html rename to examples/testdata/haml/example_shorthandSwitch.html diff --git a/examples/testdata/example_userDetails.html b/examples/testdata/haml/example_userDetails.html similarity index 100% rename from examples/testdata/example_userDetails.html rename to examples/testdata/haml/example_userDetails.html diff --git a/examples/testdata/filters_css.html b/examples/testdata/haml/filters_css.html similarity index 100% rename from examples/testdata/filters_css.html rename to examples/testdata/haml/filters_css.html diff --git a/examples/testdata/filters_escaped.html b/examples/testdata/haml/filters_escaped.html similarity index 100% rename from examples/testdata/filters_escaped.html rename to examples/testdata/haml/filters_escaped.html diff --git a/examples/testdata/filters_javascript.html b/examples/testdata/haml/filters_javascript.html similarity index 100% rename from examples/testdata/filters_javascript.html rename to examples/testdata/haml/filters_javascript.html diff --git a/examples/testdata/filters_plain.html b/examples/testdata/haml/filters_plain.html similarity index 100% rename from examples/testdata/filters_plain.html rename to examples/testdata/haml/filters_plain.html diff --git a/examples/testdata/filters_preserve.html b/examples/testdata/haml/filters_preserve.html similarity index 100% rename from examples/testdata/filters_preserve.html rename to examples/testdata/haml/filters_preserve.html diff --git a/examples/testdata/formatting_boolExample.html b/examples/testdata/haml/formatting_boolExample.html similarity index 100% rename from examples/testdata/formatting_boolExample.html rename to examples/testdata/haml/formatting_boolExample.html diff --git a/examples/testdata/formatting_floatExample.html b/examples/testdata/haml/formatting_floatExample.html similarity index 100% rename from examples/testdata/formatting_floatExample.html rename to examples/testdata/haml/formatting_floatExample.html diff --git a/examples/testdata/formatting_intExample.html b/examples/testdata/haml/formatting_intExample.html similarity index 100% rename from examples/testdata/formatting_intExample.html rename to examples/testdata/haml/formatting_intExample.html diff --git a/examples/testdata/formatting_stringExample.html b/examples/testdata/haml/formatting_stringExample.html similarity index 100% rename from examples/testdata/formatting_stringExample.html rename to examples/testdata/haml/formatting_stringExample.html diff --git a/examples/testdata/hello_world.html b/examples/testdata/haml/hello_world.html similarity index 100% rename from examples/testdata/hello_world.html rename to examples/testdata/haml/hello_world.html diff --git a/examples/testdata/indents_usingTabs.html b/examples/testdata/haml/indents_usingTabs.html similarity index 100% rename from examples/testdata/indents_usingTabs.html rename to examples/testdata/haml/indents_usingTabs.html diff --git a/examples/testdata/tags_alsoSelfClosing.html b/examples/testdata/haml/tags_alsoSelfClosing.html similarity index 100% rename from examples/testdata/tags_alsoSelfClosing.html rename to examples/testdata/haml/tags_alsoSelfClosing.html diff --git a/examples/testdata/tags_combined.html b/examples/testdata/haml/tags_combined.html similarity index 100% rename from examples/testdata/tags_combined.html rename to examples/testdata/haml/tags_combined.html diff --git a/examples/testdata/tags_defaultToDivs.html b/examples/testdata/haml/tags_defaultToDivs.html similarity index 100% rename from examples/testdata/tags_defaultToDivs.html rename to examples/testdata/haml/tags_defaultToDivs.html diff --git a/examples/testdata/tags_multipleClasses.html b/examples/testdata/haml/tags_multipleClasses.html similarity index 100% rename from examples/testdata/tags_multipleClasses.html rename to examples/testdata/haml/tags_multipleClasses.html diff --git a/examples/testdata/tags_objectRefs.html b/examples/testdata/haml/tags_objectRefs.html similarity index 100% rename from examples/testdata/tags_objectRefs.html rename to examples/testdata/haml/tags_objectRefs.html diff --git a/examples/testdata/tags_prefixedObjectRefs.html b/examples/testdata/haml/tags_prefixedObjectRefs.html similarity index 100% rename from examples/testdata/tags_prefixedObjectRefs.html rename to examples/testdata/haml/tags_prefixedObjectRefs.html diff --git a/examples/testdata/tags_removeWhitespace.html b/examples/testdata/haml/tags_removeWhitespace.html similarity index 100% rename from examples/testdata/tags_removeWhitespace.html rename to examples/testdata/haml/tags_removeWhitespace.html diff --git a/examples/testdata/tags_selfClosing.html b/examples/testdata/haml/tags_selfClosing.html similarity index 100% rename from examples/testdata/tags_selfClosing.html rename to examples/testdata/haml/tags_selfClosing.html diff --git a/examples/testdata/tags_specifyTag.html b/examples/testdata/haml/tags_specifyTag.html similarity index 100% rename from examples/testdata/tags_specifyTag.html rename to examples/testdata/haml/tags_specifyTag.html diff --git a/examples/testdata/tags_whitespace.html b/examples/testdata/haml/tags_whitespace.html similarity index 100% rename from examples/testdata/tags_whitespace.html rename to examples/testdata/haml/tags_whitespace.html diff --git a/examples/testdata/haml/unescape_unescapeCode.html b/examples/testdata/haml/unescape_unescapeCode.html new file mode 100644 index 0000000..b0b9ad9 --- /dev/null +++ b/examples/testdata/haml/unescape_unescapeCode.html @@ -0,0 +1,2 @@ +

This is <em>NOT</em> unescaped HTML. (Ampersands everywhere!)

+

This is unescaped HTML.

diff --git a/examples/testdata/haml/unescape_unescapeInterpolation.html b/examples/testdata/haml/unescape_unescapeInterpolation.html new file mode 100644 index 0000000..1e161fc --- /dev/null +++ b/examples/testdata/haml/unescape_unescapeInterpolation.html @@ -0,0 +1,2 @@ +

This <em>is</em> is escaped. (Ampersands everywhere!)

+

This is is NOT escaped.

diff --git a/examples/testdata/unescape_unescapeText.html b/examples/testdata/haml/unescape_unescapeText.html similarity index 100% rename from examples/testdata/unescape_unescapeText.html rename to examples/testdata/haml/unescape_unescapeText.html diff --git a/examples/testdata/slim/attributes_attributesCmd.html b/examples/testdata/slim/attributes_attributesCmd.html new file mode 100644 index 0000000..72cb197 --- /dev/null +++ b/examples/testdata/slim/attributes_attributesCmd.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/testdata/slim/attributes_classes.html b/examples/testdata/slim/attributes_classes.html new file mode 100644 index 0000000..00fa896 --- /dev/null +++ b/examples/testdata/slim/attributes_classes.html @@ -0,0 +1 @@ +

diff --git a/examples/testdata/slim/attributes_complexNames.html b/examples/testdata/slim/attributes_complexNames.html new file mode 100644 index 0000000..803d7b3 --- /dev/null +++ b/examples/testdata/slim/attributes_complexNames.html @@ -0,0 +1 @@ +Goht diff --git a/examples/testdata/slim/attributes_conditionalAttrs.html b/examples/testdata/slim/attributes_conditionalAttrs.html new file mode 100644 index 0000000..3d0aafa --- /dev/null +++ b/examples/testdata/slim/attributes_conditionalAttrs.html @@ -0,0 +1,2 @@ + + diff --git a/examples/testdata/slim/attributes_dynamicAttrs.html b/examples/testdata/slim/attributes_dynamicAttrs.html new file mode 100644 index 0000000..c41afd1 --- /dev/null +++ b/examples/testdata/slim/attributes_dynamicAttrs.html @@ -0,0 +1 @@ +

This is a paragraph.

diff --git a/examples/testdata/slim/attributes_formattedValue.html b/examples/testdata/slim/attributes_formattedValue.html new file mode 100644 index 0000000..b437633 --- /dev/null +++ b/examples/testdata/slim/attributes_formattedValue.html @@ -0,0 +1 @@ + diff --git a/examples/testdata/slim/attributes_multilineAttrs.html b/examples/testdata/slim/attributes_multilineAttrs.html new file mode 100644 index 0000000..c41afd1 --- /dev/null +++ b/examples/testdata/slim/attributes_multilineAttrs.html @@ -0,0 +1 @@ +

This is a paragraph.

diff --git a/examples/testdata/slim/attributes_simpleNames.html b/examples/testdata/slim/attributes_simpleNames.html new file mode 100644 index 0000000..c6c8e36 --- /dev/null +++ b/examples/testdata/slim/attributes_simpleNames.html @@ -0,0 +1 @@ +Goht diff --git a/examples/testdata/slim/attributes_staticAttrs.html b/examples/testdata/slim/attributes_staticAttrs.html new file mode 100644 index 0000000..c41afd1 --- /dev/null +++ b/examples/testdata/slim/attributes_staticAttrs.html @@ -0,0 +1 @@ +

This is a paragraph.

diff --git a/examples/testdata/slim/attributes_whitespaceAttrs.html b/examples/testdata/slim/attributes_whitespaceAttrs.html new file mode 100644 index 0000000..9cddabe --- /dev/null +++ b/examples/testdata/slim/attributes_whitespaceAttrs.html @@ -0,0 +1,4 @@ +

This is a paragraph.

+

This is a paragraph.

+

This is a paragraph.

+

This is a paragraph.

diff --git a/examples/testdata/slim/commands_childrenExample.html b/examples/testdata/slim/commands_childrenExample.html new file mode 100644 index 0000000..1084151 --- /dev/null +++ b/examples/testdata/slim/commands_childrenExample.html @@ -0,0 +1,3 @@ +

+following was passed in from the calling template: +

diff --git a/examples/testdata/slim/commands_renderExample.html b/examples/testdata/slim/commands_renderExample.html new file mode 100644 index 0000000..6963405 --- /dev/null +++ b/examples/testdata/slim/commands_renderExample.html @@ -0,0 +1,5 @@ +

+The following was passed in from the calling template: +

+

+

the other template was rendered above.

diff --git a/examples/testdata/slim/commands_renderWithChildrenExample.html b/examples/testdata/slim/commands_renderWithChildrenExample.html new file mode 100644 index 0000000..c973246 --- /dev/null +++ b/examples/testdata/slim/commands_renderWithChildrenExample.html @@ -0,0 +1,5 @@ +

The other template will be rendered below.

+

+The following was passed in from the calling template: +this content will be rendered by the other template. +

diff --git a/examples/testdata/slim/comments_htmlComments.html b/examples/testdata/slim/comments_htmlComments.html new file mode 100644 index 0000000..582a1a4 --- /dev/null +++ b/examples/testdata/slim/comments_htmlComments.html @@ -0,0 +1,2 @@ +

This is a paragraph

+ diff --git a/examples/testdata/slim/comments_htmlCommentsNested.html b/examples/testdata/slim/comments_htmlCommentsNested.html new file mode 100644 index 0000000..7491971 --- /dev/null +++ b/examples/testdata/slim/comments_htmlCommentsNested.html @@ -0,0 +1,3 @@ +

This is a paragraph

+ + diff --git a/examples/testdata/slim/comments_rubyStyle.html b/examples/testdata/slim/comments_rubyStyle.html new file mode 100644 index 0000000..4b8a0d0 --- /dev/null +++ b/examples/testdata/slim/comments_rubyStyle.html @@ -0,0 +1 @@ +

This is the only paragraph in the output.

diff --git a/examples/testdata/slim/comments_rubyStyleNested.html b/examples/testdata/slim/comments_rubyStyleNested.html new file mode 100644 index 0000000..4b8a0d0 --- /dev/null +++ b/examples/testdata/slim/comments_rubyStyleNested.html @@ -0,0 +1 @@ +

This is the only paragraph in the output.

diff --git a/examples/testdata/slim/doctype_doctype.html b/examples/testdata/slim/doctype_doctype.html new file mode 100644 index 0000000..0e76edd --- /dev/null +++ b/examples/testdata/slim/doctype_doctype.html @@ -0,0 +1 @@ + diff --git a/examples/testdata/slim/example_conditional.html b/examples/testdata/slim/example_conditional.html new file mode 100644 index 0000000..2ce5b24 --- /dev/null +++ b/examples/testdata/slim/example_conditional.html @@ -0,0 +1,6 @@ +
+ + +
diff --git a/examples/testdata/slim/example_doc.html b/examples/testdata/slim/example_doc.html new file mode 100644 index 0000000..42ac9d9 --- /dev/null +++ b/examples/testdata/slim/example_doc.html @@ -0,0 +1 @@ +
An example of package documentation.
diff --git a/examples/testdata/slim/example_escapeInterpolation.html b/examples/testdata/slim/example_escapeInterpolation.html new file mode 100644 index 0000000..b4dfc9c --- /dev/null +++ b/examples/testdata/slim/example_escapeInterpolation.html @@ -0,0 +1,2 @@ +Hello, World! +

Hello, World!

diff --git a/examples/testdata/slim/example_executeCode.html b/examples/testdata/slim/example_executeCode.html new file mode 100644 index 0000000..7ce5354 --- /dev/null +++ b/examples/testdata/slim/example_executeCode.html @@ -0,0 +1 @@ +

Hello, world!

diff --git a/examples/testdata/slim/example_ignoreInterpolation.html b/examples/testdata/slim/example_ignoreInterpolation.html new file mode 100644 index 0000000..fc3a2cb --- /dev/null +++ b/examples/testdata/slim/example_ignoreInterpolation.html @@ -0,0 +1,8 @@ +#{someVar}, World! +

+#{someVar}, World! +

+A greeting: #{someVar}, World! +. this line begins with a period +# this line begins with a hash +% this line begins with a percent diff --git a/examples/testdata/slim/example_importExample.html b/examples/testdata/slim/example_importExample.html new file mode 100644 index 0000000..ff8feb5 --- /dev/null +++ b/examples/testdata/slim/example_importExample.html @@ -0,0 +1 @@ +

Hello, World!

diff --git a/examples/testdata/slim/example_interpolateCode.html b/examples/testdata/slim/example_interpolateCode.html new file mode 100644 index 0000000..ff8feb5 --- /dev/null +++ b/examples/testdata/slim/example_interpolateCode.html @@ -0,0 +1 @@ +

Hello, World!

diff --git a/examples/testdata/slim/example_noInterpolation.html b/examples/testdata/slim/example_noInterpolation.html new file mode 100644 index 0000000..f880be4 --- /dev/null +++ b/examples/testdata/slim/example_noInterpolation.html @@ -0,0 +1,3 @@ +

Do the following; No interpolation is necessary.

+

Hello, World!

+

No interpolation is #{performed} here.

diff --git a/examples/testdata/slim/example_packageExample.html b/examples/testdata/slim/example_packageExample.html new file mode 100644 index 0000000..e69de29 diff --git a/examples/testdata/slim/example_renderCode.html b/examples/testdata/slim/example_renderCode.html new file mode 100644 index 0000000..7ce5354 --- /dev/null +++ b/examples/testdata/slim/example_renderCode.html @@ -0,0 +1 @@ +

Hello, world!

diff --git a/examples/testdata/slim/example_shorthandConditional.html b/examples/testdata/slim/example_shorthandConditional.html new file mode 100644 index 0000000..38154f1 --- /dev/null +++ b/examples/testdata/slim/example_shorthandConditional.html @@ -0,0 +1,6 @@ +
+ + +
diff --git a/examples/testdata/slim/example_shorthandSwitch.html b/examples/testdata/slim/example_shorthandSwitch.html new file mode 100644 index 0000000..38154f1 --- /dev/null +++ b/examples/testdata/slim/example_shorthandSwitch.html @@ -0,0 +1,6 @@ +
+ + +
diff --git a/examples/testdata/slim/example_userDetails.html b/examples/testdata/slim/example_userDetails.html new file mode 100644 index 0000000..d44b736 --- /dev/null +++ b/examples/testdata/slim/example_userDetails.html @@ -0,0 +1,5 @@ +
User name: John
+
+Age: +30 +
diff --git a/examples/testdata/slim/filters_css.html b/examples/testdata/slim/filters_css.html new file mode 100644 index 0000000..f746352 --- /dev/null +++ b/examples/testdata/slim/filters_css.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/examples/testdata/slim/filters_javascript.html b/examples/testdata/slim/filters_javascript.html new file mode 100644 index 0000000..657fe92 --- /dev/null +++ b/examples/testdata/slim/filters_javascript.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/examples/testdata/slim/formatting_boolExample.html b/examples/testdata/slim/formatting_boolExample.html new file mode 100644 index 0000000..a771953 --- /dev/null +++ b/examples/testdata/slim/formatting_boolExample.html @@ -0,0 +1 @@ +

The bool is (true).

diff --git a/examples/testdata/slim/formatting_floatExample.html b/examples/testdata/slim/formatting_floatExample.html new file mode 100644 index 0000000..9f3a2cf --- /dev/null +++ b/examples/testdata/slim/formatting_floatExample.html @@ -0,0 +1,6 @@ +

The float is (123.456000).

+

The float is (1.234560e+02) in scientific notation.

+

The float is (123.46) with 2 decimal places.

+

The float is ( 123.46) with 2 decimal places and padded to 9 characters.

+

The float is (123.46 ) with 2 decimal places and padded to 9 characters and left aligned.

+

The float is (000123.46) with 2 decimal places and padded to 9 characters with 0s.

diff --git a/examples/testdata/slim/formatting_intExample.html b/examples/testdata/slim/formatting_intExample.html new file mode 100644 index 0000000..acc25b1 --- /dev/null +++ b/examples/testdata/slim/formatting_intExample.html @@ -0,0 +1,6 @@ +

The integer is (123).

+

The integer is (1111011) in binary.

+

The integer is (173) in octal.

+

The integer is (7b) in hex.

+

The integer is (7B) in hex with uppercase.

+

The integer is ({) as a character.

diff --git a/examples/testdata/slim/formatting_stringExample.html b/examples/testdata/slim/formatting_stringExample.html new file mode 100644 index 0000000..bd8f69c --- /dev/null +++ b/examples/testdata/slim/formatting_stringExample.html @@ -0,0 +1,9 @@ +

The string is (Hello). Strings do not require any additional formatting.

+

The string is ("Hello") quoted.

+

The string is (48656c6c6f) as hex.

+

The string is (48656C6C6F) as hex with uppercase.

+

The string is (Hello) as is.

+

The string is (Hell), truncated to 4 characters.

+

The string is ( Hello), padded to 6 characters.

+

The string is ( Hell), truncated to 4 characters and padded to 6 characters.

+

The string is ("Hell"), truncated to 4 characters and padded to 6 characters and quoted.

diff --git a/examples/testdata/slim/hello_world.html b/examples/testdata/slim/hello_world.html new file mode 100644 index 0000000..f1b978d --- /dev/null +++ b/examples/testdata/slim/hello_world.html @@ -0,0 +1,38 @@ + + + +Hello World + + +

Hello World

+

the following will loop a slice of strings and will pass each string into a child template

+

foo

+

+

And it was passed in as well foo

+

bar

+

+

And it was passed in as well bar

+

baz

+

+

And it was passed in as well baz

+

fizz

+

+

And it was passed in as well fizz

+

buzz

+

+

And it was passed in as well buzz

+

quux

+

+

And it was passed in as well quux

+ + diff --git a/examples/testdata/indents_usingSpaces.html b/examples/testdata/slim/indents_usingTabs.html similarity index 87% rename from examples/testdata/indents_usingSpaces.html rename to examples/testdata/slim/indents_usingTabs.html index 78ca4a7..30b9dc8 100644 --- a/examples/testdata/indents_usingSpaces.html +++ b/examples/testdata/slim/indents_usingTabs.html @@ -9,5 +9,8 @@

Hello World

+

+Hello World +

diff --git a/examples/testdata/slim/tags_alsoSelfClosing.html b/examples/testdata/slim/tags_alsoSelfClosing.html new file mode 100644 index 0000000..a6c709c --- /dev/null +++ b/examples/testdata/slim/tags_alsoSelfClosing.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/testdata/slim/tags_combined.html b/examples/testdata/slim/tags_combined.html new file mode 100644 index 0000000..f3d397f --- /dev/null +++ b/examples/testdata/slim/tags_combined.html @@ -0,0 +1,4 @@ +

This is a paragraph tag with an id of `main`.

+
This is a main tag with a class of `main`.
+
This is a div tag with an id and class of `main`.
+

This is a paragraph tag with an id and class of `main`.

diff --git a/examples/testdata/slim/tags_defaultToDivs.html b/examples/testdata/slim/tags_defaultToDivs.html new file mode 100644 index 0000000..94d3285 --- /dev/null +++ b/examples/testdata/slim/tags_defaultToDivs.html @@ -0,0 +1,2 @@ +
This is a div tag with an id of `main`.
+
This is a div tag with a class of `main`.
diff --git a/examples/testdata/slim/tags_inlineTags.html b/examples/testdata/slim/tags_inlineTags.html new file mode 100644 index 0000000..fe57a71 --- /dev/null +++ b/examples/testdata/slim/tags_inlineTags.html @@ -0,0 +1,8 @@ + diff --git a/examples/testdata/slim/tags_multipleClasses.html b/examples/testdata/slim/tags_multipleClasses.html new file mode 100644 index 0000000..17864b1 --- /dev/null +++ b/examples/testdata/slim/tags_multipleClasses.html @@ -0,0 +1,2 @@ +
This is a div tag with two classes, `main` and `main2`.
+
This is a div tag with an id of `main2`.
diff --git a/examples/testdata/slim/tags_removeWhitespace.html b/examples/testdata/slim/tags_removeWhitespace.html new file mode 100644 index 0000000..18588c8 --- /dev/null +++ b/examples/testdata/slim/tags_removeWhitespace.html @@ -0,0 +1,13 @@ +

+< +This text has no whitespace between it and the parent tag. +

+

+There is whitespace between this text and the parent tag. +

+<> +This text has no whitespace between it and the parent tag. +There is also no whitespace between this tag and the sibling text above it. +Finally, the tag has no whitespace between it and the outer tag. +

+

diff --git a/examples/testdata/slim/tags_selfClosing.html b/examples/testdata/slim/tags_selfClosing.html new file mode 100644 index 0000000..932f674 --- /dev/null +++ b/examples/testdata/slim/tags_selfClosing.html @@ -0,0 +1,3 @@ +logo

+paragraph is not self closing. +logo

diff --git a/examples/testdata/slim/tags_specifyTag.html b/examples/testdata/slim/tags_specifyTag.html new file mode 100644 index 0000000..a7c6a9f --- /dev/null +++ b/examples/testdata/slim/tags_specifyTag.html @@ -0,0 +1,2 @@ +

This is a paragraph tag.

+
This is a main tag.
diff --git a/examples/testdata/slim/tags_whitespace.html b/examples/testdata/slim/tags_whitespace.html new file mode 100644 index 0000000..c1c5e52 --- /dev/null +++ b/examples/testdata/slim/tags_whitespace.html @@ -0,0 +1,5 @@ +

This text has no whitespace between it and the tag.

+

+text has NO whitespace between it and the tag. +

This tag has NO whitespace between it and the tag above.

+

diff --git a/examples/testdata/slim/unescape_unescapeCode.html b/examples/testdata/slim/unescape_unescapeCode.html new file mode 100644 index 0000000..d192cf8 --- /dev/null +++ b/examples/testdata/slim/unescape_unescapeCode.html @@ -0,0 +1,2 @@ +

This is <em>is</em> escaped HTML. (Ampersands everywhere!)

+

This is NOT escaped HTML.

diff --git a/examples/testdata/unescape_unescapeCode.html b/examples/testdata/unescape_unescapeCode.html deleted file mode 100644 index 251a9a7..0000000 --- a/examples/testdata/unescape_unescapeCode.html +++ /dev/null @@ -1,2 +0,0 @@ -

This is <em>not</em> unescaped HTML.

-

This is unescaped HTML.

diff --git a/examples/testdata/unescape_unescapeInterpolation.html b/examples/testdata/unescape_unescapeInterpolation.html deleted file mode 100644 index 13f2e28..0000000 --- a/examples/testdata/unescape_unescapeInterpolation.html +++ /dev/null @@ -1,2 +0,0 @@ -

This <em>is</em> HTML.

-

This is HTML.

diff --git a/examples/unescaping/unescape.goht b/examples/unescaping/unescape.goht index a2472a0..fc367b3 100644 --- a/examples/unescaping/unescape.goht +++ b/examples/unescaping/unescape.goht @@ -9,16 +9,32 @@ package unescape // you should use the default escaping. @goht UnescapeCode() { -%p= "This is not unescaped HTML." -%p!= "This is unescaped HTML." + %p= "This is NOT unescaped HTML. (Ampersands everywhere!)" + %p!= "This is unescaped HTML." +} + +@haml HamlUnescapeCode() { + %p= "This is NOT unescaped HTML. (Ampersands everywhere!)" + %p!= "This is unescaped HTML." +} + +@slim SlimUnescapeCode() { + p= "This is is escaped HTML. (Ampersands everywhere!)" + p== "This is NOT escaped HTML." } // It can also affect the interpolated values. @goht UnescapeInterpolation() { -- var html = "is" -%p This #{html} HTML. -%p! This #{html} HTML. + - var html = "is" + %p This #{html} is escaped. (Ampersands everywhere!) + %p! This #{html} is NOT escaped. +} + +@haml HamlUnescapeInterpolation() { + - var html = "is" + %p This #{html} is escaped. (Ampersands everywhere!) + %p! This #{html} is NOT escaped. } // The plain text that you write into your Goht templates will not be @@ -26,6 +42,11 @@ package unescape // text has already been HTML escaped properly. @goht UnescapeText() { -%p This is HTML. -%p! This is HTML. + %p This is HTML. + %p! This is HTML. +} + +@haml HamlUnescapeText() { + %p This is HTML. + %p! This is HTML. } diff --git a/examples/unescaping/unescape.goht.go b/examples/unescaping/unescape.goht.go index 7d1640d..97d4d29 100644 --- a/examples/unescaping/unescape.goht.go +++ b/examples/unescaping/unescape.goht.go @@ -1,4 +1,4 @@ -// Code generated by GoHT - DO NOT EDIT. +// Code generated by GoHT v0.6.0 - DO NOT EDIT. // https://github.com/stackus/goht package unescape @@ -29,7 +29,7 @@ func UnescapeCode() goht.Template { return } var __var1 string - if __var1, __err = goht.CaptureErrors(goht.EscapeString("This is not unescaped HTML.")); __err != nil { + if __var1, __err = goht.CaptureErrors(goht.EscapeString("This is NOT unescaped HTML. (Ampersands everywhere!)")); __err != nil { return } if _, __err = __buf.WriteString(__var1); __err != nil { @@ -55,6 +55,86 @@ func UnescapeCode() goht.Template { }) } +func HamlUnescapeCode() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString("This is NOT unescaped HTML. (Ampersands everywhere!)")); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n

"); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors("This is unescaped HTML."); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func SlimUnescapeCode() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

"); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString("This is is escaped HTML. (Ampersands everywhere!)")); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n

"); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors("This is NOT escaped HTML."); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString("

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + // It can also affect the interpolated values. func UnescapeInterpolation() goht.Template { @@ -78,7 +158,48 @@ func UnescapeInterpolation() goht.Template { if _, __err = __buf.WriteString(__var1); __err != nil { return } - if _, __err = __buf.WriteString(" HTML.

\n

This "); __err != nil { + if _, __err = __buf.WriteString(" is escaped. (Ampersands everywhere!)

\n

This "); __err != nil { + return + } + var __var2 string + if __var2, __err = goht.CaptureErrors(html); __err != nil { + return + } + if _, __err = __buf.WriteString(__var2); __err != nil { + return + } + if _, __err = __buf.WriteString(" is NOT escaped.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} + +func HamlUnescapeInterpolation() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + var html = "is" + if _, __err = __buf.WriteString("

This "); __err != nil { + return + } + var __var1 string + if __var1, __err = goht.CaptureErrors(goht.EscapeString(html)); __err != nil { + return + } + if _, __err = __buf.WriteString(__var1); __err != nil { + return + } + if _, __err = __buf.WriteString(" is escaped. (Ampersands everywhere!)

\n

This "); __err != nil { return } var __var2 string @@ -88,7 +209,7 @@ func UnescapeInterpolation() goht.Template { if _, __err = __buf.WriteString(__var2); __err != nil { return } - if _, __err = __buf.WriteString(" HTML.

\n"); __err != nil { + if _, __err = __buf.WriteString(" is NOT escaped.

\n"); __err != nil { return } if !__isBuf { @@ -121,3 +242,23 @@ func UnescapeText() goht.Template { return }) } + +func HamlUnescapeText() goht.Template { + return goht.TemplateFunc(func(ctx context.Context, __w io.Writer) (__err error) { + __buf, __isBuf := __w.(goht.Buffer) + if !__isBuf { + __buf = goht.GetBuffer() + defer goht.ReleaseBuffer(__buf) + } + var __children goht.Template + ctx, __children = goht.PopChildren(ctx) + _ = __children + if _, __err = __buf.WriteString("

This is HTML.

\n

This is HTML.

\n"); __err != nil { + return + } + if !__isBuf { + _, __err = __w.Write(__buf.Bytes()) + } + return + }) +} diff --git a/version.go b/version.go index 48bc014..9a24b82 100644 --- a/version.go +++ b/version.go @@ -9,5 +9,5 @@ import ( var version string func Version() string { - return strings.TrimPrefix(version, "VERSION=") + return strings.TrimSpace(strings.TrimPrefix(version, "VERSION=")) }