├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── test.yaml ├── .gitignore ├── .version ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── Taskfile.yml ├── bundle ├── Syntaxes │ └── goht.tmLanguage.json └── info.plist ├── cmd └── goht │ ├── LICENSE │ ├── cmd │ ├── generate.go │ ├── lsp.go │ └── root.go │ └── main.go ├── compiler ├── errors.go ├── helpers_test.go ├── lexer.go ├── lexer_iface.go ├── lexer_test.go ├── lexers.go ├── lexers_ego.go ├── lexers_ego_test.go ├── lexers_haml.go ├── lexers_haml_test.go ├── lexers_slim.go ├── lexers_slim_test.go ├── nodes.go ├── nodes_test.go ├── parser.go ├── render_test.go ├── source_map.go ├── source_map_test.go ├── template.go ├── template_test.go ├── testdata │ ├── attributes.goht │ ├── attributes.goht.go │ ├── attributes.html │ ├── comments.goht │ ├── comments.goht.go │ ├── comments.html │ ├── conditionals.false.html │ ├── conditionals.goht │ ├── conditionals.goht.go │ ├── conditionals.true.html │ ├── ego_template.goht │ ├── ego_template.goht.go │ ├── ego_template.html │ ├── elements.goht │ ├── elements.goht.go │ ├── elements.html │ ├── filters.goht │ ├── filters.goht.go │ ├── filters.html │ ├── imports.goht │ ├── imports.goht.go │ ├── imports.html │ ├── interpolation.goht │ ├── interpolation.goht.go │ ├── interpolation.html │ ├── nesting.html │ ├── newlines.goht │ ├── newlines.goht.go │ ├── newlines.html │ ├── obj_references.goht │ ├── obj_references.goht.go │ ├── obj_references.html │ ├── package.goht │ ├── package.goht.go │ ├── package.html │ ├── rendering.goht │ ├── rendering.goht.go │ ├── rendering.html │ ├── slim_template.goht │ ├── slim_template.goht.go │ ├── slim_template.html │ ├── slots.html │ ├── slots_with_defaults.html │ ├── whitespace.goht │ ├── whitespace.goht.go │ ├── whitespace.html │ └── without_children.html ├── tokens.go ├── tokens_test.go └── utils.go ├── docs ├── goht_header.png ├── goht_header_html.png └── vscode_ide_example.png ├── examples ├── attributes │ ├── additional.goht │ ├── additional.goht.go │ ├── classes.goht │ ├── classes.goht.go │ ├── general.goht │ ├── general.goht.go │ ├── names.goht │ ├── names.goht.go │ ├── optional.goht │ └── optional.goht.go ├── commands │ ├── children.goht │ ├── children.goht.go │ ├── render.goht │ ├── render.goht.go │ ├── slots.goht │ └── slots.goht.go ├── comments │ ├── html.goht │ ├── html.goht.go │ ├── rubystyle.goht │ └── rubystyle.goht.go ├── doctype │ ├── doctype.goht │ └── doctype.goht.go ├── examples_test.go ├── filters │ ├── css.goht │ ├── css.goht.go │ ├── javascript.goht │ ├── javascript.goht.go │ ├── text.goht │ └── text.goht.go ├── formatting │ ├── formats.goht │ └── formats.goht.go ├── go │ ├── code.goht │ ├── code.goht.go │ ├── doc.goht │ ├── doc.goht.go │ ├── imports.goht │ ├── imports.goht.go │ ├── inlining.goht │ ├── inlining.goht.go │ ├── interpolation.goht │ ├── interpolation.goht.go │ ├── package.goht │ ├── package.goht.go │ ├── receivers.goht │ └── receivers.goht.go ├── hello │ ├── world.goht │ └── world.goht.go ├── indents │ ├── tabs.goht │ └── tabs.goht.go ├── tags │ ├── general.goht │ ├── general.goht.go │ ├── inline.goht │ ├── inline.goht.go │ ├── objectreference.goht │ ├── objectreference.goht.go │ ├── selfclosing.goht │ ├── selfclosing.goht.go │ ├── whitespace.goht │ └── whitespace.goht.go ├── testdata │ ├── ego │ │ └── hello_world.html │ ├── haml │ │ ├── attributes_attributesCmd.html │ │ ├── attributes_classes.html │ │ ├── attributes_complexNames.html │ │ ├── attributes_conditionalAttrs.html │ │ ├── attributes_dynamicAttrs.html │ │ ├── attributes_formattedValue.html │ │ ├── attributes_multilineAttrs.html │ │ ├── attributes_simpleNames.html │ │ ├── attributes_staticAttrs.html │ │ ├── attributes_whitespaceAttrs.html │ │ ├── commands_childrenExample.html │ │ ├── commands_renderExample.html │ │ ├── commands_renderWithChildrenExample.html │ │ ├── comments_htmlComments.html │ │ ├── comments_htmlCommentsNested.html │ │ ├── comments_rubyStyle.html │ │ ├── comments_rubyStyleNested.html │ │ ├── doctype_doctype.html │ │ ├── example_conditional.html │ │ ├── example_doc.html │ │ ├── example_escapeInterpolation.html │ │ ├── example_executeCode.html │ │ ├── example_ignoreInterpolation.html │ │ ├── example_importExample.html │ │ ├── example_interpolateCode.html │ │ ├── example_noInterpolation.html │ │ ├── example_packageExample.html │ │ ├── example_renderCode.html │ │ ├── example_shorthandConditional.html │ │ ├── example_shorthandSwitch.html │ │ ├── example_userDetails.html │ │ ├── filters_css.html │ │ ├── filters_escaped.html │ │ ├── filters_javascript.html │ │ ├── filters_plain.html │ │ ├── filters_preserve.html │ │ ├── formatting_boolExample.html │ │ ├── formatting_floatExample.html │ │ ├── formatting_intExample.html │ │ ├── formatting_stringExample.html │ │ ├── hello_world.html │ │ ├── indents_usingTabs.html │ │ ├── tags_alsoSelfClosing.html │ │ ├── tags_combined.html │ │ ├── tags_defaultToDivs.html │ │ ├── tags_multipleClasses.html │ │ ├── tags_objectRefs.html │ │ ├── tags_prefixedObjectRefs.html │ │ ├── tags_removeWhitespace.html │ │ ├── tags_selfClosing.html │ │ ├── tags_specifyTag.html │ │ ├── tags_whitespace.html │ │ ├── unescape_unescapeCode.html │ │ ├── unescape_unescapeInterpolation.html │ │ └── unescape_unescapeText.html │ └── slim │ │ ├── attributes_attributesCmd.html │ │ ├── attributes_classes.html │ │ ├── attributes_complexNames.html │ │ ├── attributes_conditionalAttrs.html │ │ ├── attributes_dynamicAttrs.html │ │ ├── attributes_formattedValue.html │ │ ├── attributes_multilineAttrs.html │ │ ├── attributes_simpleNames.html │ │ ├── attributes_staticAttrs.html │ │ ├── attributes_whitespaceAttrs.html │ │ ├── commands_childrenExample.html │ │ ├── commands_renderExample.html │ │ ├── commands_renderWithChildrenExample.html │ │ ├── comments_htmlComments.html │ │ ├── comments_htmlCommentsNested.html │ │ ├── comments_rubyStyle.html │ │ ├── comments_rubyStyleNested.html │ │ ├── doctype_doctype.html │ │ ├── example_conditional.html │ │ ├── example_doc.html │ │ ├── example_escapeInterpolation.html │ │ ├── example_executeCode.html │ │ ├── example_ignoreInterpolation.html │ │ ├── example_importExample.html │ │ ├── example_interpolateCode.html │ │ ├── example_noInterpolation.html │ │ ├── example_packageExample.html │ │ ├── example_renderCode.html │ │ ├── example_shorthandConditional.html │ │ ├── example_shorthandSwitch.html │ │ ├── example_userDetails.html │ │ ├── filters_css.html │ │ ├── filters_javascript.html │ │ ├── formatting_boolExample.html │ │ ├── formatting_floatExample.html │ │ ├── formatting_intExample.html │ │ ├── formatting_stringExample.html │ │ ├── hello_world.html │ │ ├── indents_usingTabs.html │ │ ├── tags_addWhitespace.html │ │ ├── tags_alsoSelfClosing.html │ │ ├── tags_combined.html │ │ ├── tags_defaultToDivs.html │ │ ├── tags_inlineTags.html │ │ ├── tags_multipleClasses.html │ │ ├── tags_selfClosing.html │ │ ├── tags_specifyTag.html │ │ ├── tags_whitespace.html │ │ └── unescape_unescapeCode.html └── unescaping │ ├── unescape.goht │ └── unescape.goht.go ├── go.mod ├── go.sum ├── helpers.go ├── internal ├── logging │ ├── logged_stream.go │ └── logger.go ├── protocol │ ├── LICENSE │ ├── protocol.go │ ├── tsclient.go │ ├── tsdocument_changes.go │ ├── tsjson.go │ ├── tsprotocol.go │ ├── tsserver.go │ ├── uri.go │ └── util.go └── proxy │ ├── client.go │ ├── diagnostics_cache.go │ ├── document.go │ ├── document_contents.go │ ├── file_names.go │ ├── server.go │ └── source_map_cache.go ├── runtime.go └── version.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = tab 8 | insert_final_newline = true 9 | max_line_length = 140 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test & Coverage 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | # a single job to run tests and coverage for a Golang project; report coverage to Coveralls 11 | jobs: 12 | test: 13 | name: Test 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | - name: Setup Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version: 1.21 22 | - name: Install dependencies 23 | run: go mod download 24 | - name: Run tests 25 | run: go test -v -vet=all -coverprofile=profile.cov ./... 26 | - name: Install goveralls 27 | run: go install github.com/mattn/goveralls@latest 28 | - name: Report coverage to Coveralls 29 | env: 30 | COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | run: goveralls -coverprofile=profile.cov -service=github 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/goland+all,windows,macos 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=goland+all,windows,macos 3 | 4 | ### GoLand+all ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/**/usage.statistics.xml 12 | .idea/**/dictionaries 13 | .idea/**/shelf 14 | 15 | # AWS User-specific 16 | .idea/**/aws.xml 17 | 18 | # Generated files 19 | .idea/**/contentModel.xml 20 | 21 | # Sensitive or high-churn files 22 | .idea/**/dataSources/ 23 | .idea/**/dataSources.ids 24 | .idea/**/dataSources.local.xml 25 | .idea/**/sqlDataSources.xml 26 | .idea/**/dynamic.xml 27 | .idea/**/uiDesigner.xml 28 | .idea/**/dbnavigator.xml 29 | 30 | # Gradle 31 | .idea/**/gradle.xml 32 | .idea/**/libraries 33 | 34 | # Gradle and Maven with auto-import 35 | # When using Gradle or Maven with auto-import, you should exclude module files, 36 | # since they will be recreated, and may cause churn. Uncomment if using 37 | # auto-import. 38 | # .idea/artifacts 39 | # .idea/compiler.xml 40 | # .idea/jarRepositories.xml 41 | # .idea/modules.xml 42 | # .idea/*.iml 43 | # .idea/modules 44 | # *.iml 45 | # *.ipr 46 | 47 | # CMake 48 | cmake-build-*/ 49 | 50 | # Mongo Explorer plugin 51 | .idea/**/mongoSettings.xml 52 | 53 | # File-based project format 54 | *.iws 55 | 56 | # IntelliJ 57 | out/ 58 | 59 | # mpeltonen/sbt-idea plugin 60 | .idea_modules/ 61 | 62 | # JIRA plugin 63 | atlassian-ide-plugin.xml 64 | 65 | # Cursive Clojure plugin 66 | .idea/replstate.xml 67 | 68 | # SonarLint plugin 69 | .idea/sonarlint/ 70 | 71 | # Crashlytics plugin (for Android Studio and IntelliJ) 72 | com_crashlytics_export_strings.xml 73 | crashlytics.properties 74 | crashlytics-build.properties 75 | fabric.properties 76 | 77 | # Editor-based Rest Client 78 | .idea/httpRequests 79 | 80 | # Android studio 3.1+ serialized cache file 81 | .idea/caches/build_file_checksums.ser 82 | 83 | ### GoLand+all Patch ### 84 | # Ignore everything but code style settings and run configurations 85 | # that are supposed to be shared within teams. 86 | 87 | .idea/* 88 | 89 | !.idea/codeStyles 90 | !.idea/runConfigurations 91 | 92 | ### macOS ### 93 | # General 94 | .DS_Store 95 | .AppleDouble 96 | .LSOverride 97 | 98 | # Icon must end with two \r 99 | Icon 100 | 101 | 102 | # Thumbnails 103 | ._* 104 | 105 | # Files that might appear in the root of a volume 106 | .DocumentRevisions-V100 107 | .fseventsd 108 | .Spotlight-V100 109 | .TemporaryItems 110 | .Trashes 111 | .VolumeIcon.icns 112 | .com.apple.timemachine.donotpresent 113 | 114 | # Directories potentially created on remote AFP share 115 | .AppleDB 116 | .AppleDesktop 117 | Network Trash Folder 118 | Temporary Items 119 | .apdisk 120 | 121 | ### macOS Patch ### 122 | # iCloud generated files 123 | *.icloud 124 | 125 | ### Windows ### 126 | # Windows thumbnail cache files 127 | Thumbs.db 128 | Thumbs.db:encryptable 129 | ehthumbs.db 130 | ehthumbs_vista.db 131 | 132 | # Dump file 133 | *.stackdump 134 | 135 | # Folder config file 136 | [Dd]esktop.ini 137 | 138 | # Recycle Bin used on file shares 139 | $RECYCLE.BIN/ 140 | 141 | # Windows Installer files 142 | *.cab 143 | *.msi 144 | *.msix 145 | *.msm 146 | *.msp 147 | 148 | # Windows shortcuts 149 | *.lnk 150 | 151 | # End of https://www.toptal.com/developers/gitignore/api/goland+all,windows,macos 152 | /scratch/ 153 | -------------------------------------------------------------------------------- /.version: -------------------------------------------------------------------------------- 1 | VERSION=v0.8.1 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to GoHT 2 | 3 | We are thrilled that you are interested in contributing to GoHT! 4 | GoHT is a Haml engine for Go, focused on compiling Haml into type-safe Go code. 5 | This document provides guidelines for contributing to various parts of the project, including the CLI, compiler, and runtime. 6 | 7 | ## Table of Contents 8 | 9 | - [Getting Started](#getting-started) 10 | - [Contributing to Different Sections](#contributing-to-different-sections) 11 | - [CLI](#cli) 12 | - [Compiler](#compiler) 13 | - [Runtime](#runtime) 14 | - [Documentation](#documentation) 15 | - [Bug Submissions](#bug-submissions) 16 | - [Pull Requests](#pull-requests) 17 | - [Adhering to the Haml Spec](#adhering-to-the-haml-spec) 18 | - [Coding Standards](#coding-standards) 19 | 20 | ## Getting Started 21 | 22 | Before you begin, please ensure you have a GitHub account and are familiar with the basics of making a pull request. If you are new to Git or GitHub, we recommend reviewing [GitHub's documentation](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests). 23 | 24 | ## Contributing to Different Sections 25 | 26 | ### CLI 27 | - Share your ideas for improving the CLI experience. 28 | - Contribute to the development or debugging of CLI features. 29 | 30 | ### Compiler 31 | - Help enhance the compiler’s efficiency and reliability. 32 | - Work on feature additions or bug fixes related to the compiler. 33 | - Improve code coverage of the compiler, covering corner and edge cases. 34 | 35 | ### Runtime 36 | - Participate in optimizing runtime performance. 37 | - Contribute to making the runtime more robust and fault-tolerant. 38 | 39 | ### Documentation 40 | - Help write and improve the documentation. 41 | - Contribute to the [examples](examples) directory. 42 | 43 | ## IDE Extensions 44 | - Contribute to the development of extensions and plugins for GoHT in various editors and IDEs that will use the built-in Language Server Protocol (LSP) support. 45 | 46 | ## Bug Submissions 47 | 48 | We welcome bug reports! If you've found a bug in GoHT, please submit it as an issue in our GitHub repository. Include as much detail as possible, such as: 49 | 50 | - A clear and concise description of the bug. 51 | - Steps to reproduce the bug. 52 | - Expected and actual behavior. 53 | - Screenshots or code snippets, if applicable. 54 | 55 | ## Pull Requests 56 | 57 | Contributions to fix bugs or add features are made through pull requests (PRs). Here's how you can submit a PR: 58 | 59 | 1. Fork the repository and create your branch from `master`. 60 | 2. Make your changes, ensuring they adhere to the project's coding standards. 61 | 3. Write tests for your changes and ensure that all tests pass. 62 | 4. Submit a pull request with a clear description of your changes. 63 | 64 | ## Adhering to the Haml Spec 65 | 66 | It is crucial for GoHT to stick as closely as possible to the Haml specification. However, due to differences in syntax and structure between Go and Ruby, some deviations are inevitable. When contributing, consider the following: 67 | 68 | - Strive for consistency with the Haml spec. 69 | - Document any necessary deviations due to language differences. 70 | 71 | ## Coding Standards 72 | 73 | - Write clean, readable, and well-documented code. 74 | - Follow Go's standard coding conventions. 75 | - Include tests for new features or bug fixes. 76 | 77 | Thank you for contributing to GoHT! Your efforts help make this project better for everyone. 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Michael Stack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | # https://taskfile.dev 2 | version: '3' 3 | 4 | silent: true 5 | 6 | dotenv: 7 | - ./.version 8 | 9 | tasks: 10 | version: 11 | desc: Set the version 12 | summary: | 13 | Set the version 14 | cmds: 15 | - git tag -a {{ .VERSION }} -m "Release {{ .VERSION }}" 16 | - git push origin 17 | -------------------------------------------------------------------------------- /bundle/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | contactEmailRot13 6 | zvpunry.fgnpx@tznvy.pbz 7 | contactName 8 | Michael Stack 9 | description 10 | GoHT: HTML UI language in Go and Haml. 11 | name 12 | GoHT 13 | uuid 14 | 6847d312-74ff-49b1-9b53-f1cde15dff8d 15 | 16 | 17 | -------------------------------------------------------------------------------- /cmd/goht/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2023 Michael Stack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /cmd/goht/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/charmbracelet/log" 7 | "github.com/spf13/cobra" 8 | 9 | "github.com/stackus/goht" 10 | ) 11 | 12 | // rootCmd represents the base command when called without any subcommands 13 | var rootCmd = &cobra.Command{ 14 | Use: "goht", 15 | Short: "A templating language for Go", 16 | Long: `Goht is a templating language for Go. It's designed to be simple and easy to use. 17 | It combines Go and Haml to create a powerful templating language that's easy to learn.`, 18 | Version: goht.Version(), 19 | } 20 | 21 | // Execute adds all child commands to the root command and sets flags appropriately. 22 | // This is called by main.main(). It only needs to happen once to the rootCmd. 23 | func Execute() { 24 | err := rootCmd.Execute() 25 | if err != nil { 26 | os.Exit(1) 27 | } 28 | } 29 | 30 | func init() { 31 | log.SetReportTimestamp(false) 32 | // Cobra also supports local flags, which will only run 33 | // when this action is called directly. 34 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 35 | } 36 | -------------------------------------------------------------------------------- /cmd/goht/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/stackus/goht/cmd/goht/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /compiler/errors.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type PositionalError struct { 8 | Line int 9 | Column int 10 | Err error 11 | } 12 | 13 | func (e PositionalError) Error() string { 14 | return fmt.Sprintf("%s: %s", e.Position(), e.Err) 15 | } 16 | 17 | func (e PositionalError) Position() string { 18 | return fmt.Sprintf("[%d:%d]", e.Line, e.Column) 19 | } 20 | 21 | func (e PositionalError) Unwrap() error { 22 | return e.Err 23 | } 24 | -------------------------------------------------------------------------------- /compiler/helpers_test.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "flag" 7 | "os" 8 | "testing" 9 | ) 10 | 11 | var ( 12 | update = flag.Bool("update", false, "update the generated golden files") 13 | ) 14 | 15 | func goldenFile(t *testing.T, fileName string, got []byte, update bool) ([]byte, error) { 16 | t.Helper() 17 | 18 | want, err := os.ReadFile(fileName) 19 | if err != nil { 20 | if !update || !errors.Is(err, os.ErrNotExist) { 21 | return nil, err 22 | } 23 | } 24 | 25 | // If the update flag is set, write the golden file when either the file does not exist or the contents do not match. 26 | if update && (!bytes.Equal(want, got) || err != nil) { 27 | err := os.WriteFile(fileName, got, 0644) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | return got, nil 33 | } 34 | 35 | return want, nil 36 | } 37 | -------------------------------------------------------------------------------- /compiler/lexer_iface.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | type ilexer interface { 4 | // next consumes the next rune from the input. 5 | next() rune 6 | // backup steps back one rune. 7 | backup() 8 | // peek returns the next rune without consuming it. 9 | peek() rune 10 | // peekAhead returns the next length runes without consuming them. 11 | peekAhead(length int) (string, error) 12 | // ignore discards the current captured string. 13 | ignore() 14 | // accept consumes the next rune if it's contained in the acceptRunes list. 15 | accept(acceptRunes string) bool 16 | // acceptRun consumes a run of runes from the acceptRunes list. 17 | acceptRun(acceptRunes string) 18 | // acceptUntil consumes runes until it encounters a rune in the stopRunes list. 19 | acceptUntil(stopRunes string) 20 | // acceptAhead consumes the next length runes. 21 | acceptAhead(length int) 22 | // skip discards the next rune. 23 | skip() rune 24 | // skipRun discards a contiguous run of runes from the skipRunes list. 25 | skipRun(skipRunes string) 26 | // skipUntil discards runes until it encounters a rune in the stopRunes list. 27 | skipUntil(stopRunes string) 28 | // skipAhead consumes the next length runes and discards them. 29 | skipAhead(length int) 30 | // current returns the current captured string being built by the lexer. 31 | current() string 32 | } 33 | -------------------------------------------------------------------------------- /compiler/parser.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | type parser struct { 9 | lexer *lexer 10 | template *Template 11 | n parsingNode 12 | nodes stack[parsingNode] 13 | token token 14 | tokens stack[token] 15 | } 16 | 17 | func ParseFile(fileName string) (*Template, error) { 18 | contents, err := os.ReadFile(fileName) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | return parseBytes(contents) 24 | } 25 | 26 | func ParseString(contents string) (*Template, error) { 27 | return parseBytes([]byte(contents)) 28 | } 29 | 30 | func parseBytes(contents []byte) (*Template, error) { 31 | p := newParser(contents) 32 | 33 | err := p.parse() 34 | 35 | return p.template, err 36 | } 37 | 38 | func newParser(contents []byte) *parser { 39 | rootNode := NewRootNode() 40 | 41 | nodes := stack[parsingNode]{rootNode} 42 | 43 | p := &parser{ 44 | lexer: newLexer(contents), 45 | template: &Template{ 46 | Root: rootNode, 47 | }, 48 | n: rootNode, 49 | nodes: nodes, 50 | } 51 | 52 | return p 53 | } 54 | 55 | func (p *parser) nextToken() { 56 | token := p.lexer.nextToken() 57 | p.tokens.push(token) 58 | } 59 | 60 | func (p *parser) parse() error { 61 | p.nextToken() 62 | for { 63 | if err := p.n.parse(p); err != nil { 64 | return err 65 | } 66 | if p.token.Type() == tEOF { 67 | break 68 | } 69 | } 70 | return nil 71 | } 72 | 73 | func (p *parser) peek() token { 74 | return p.tokens.peek() 75 | } 76 | 77 | func (p *parser) next() token { 78 | p.token = p.tokens.pop() 79 | p.nextToken() 80 | return p.token 81 | } 82 | 83 | func (p *parser) backToType(typ nodeType) error { 84 | for p.nodes.peek().Type() != typ { 85 | if p.nodes.peek().Type() == nRoot { 86 | return fmt.Errorf("unexpected: node has no parent of type %s", typ) 87 | } 88 | p.nodes.pop() 89 | } 90 | p.n = p.nodes.peek() 91 | return nil 92 | } 93 | 94 | func (p *parser) backToIndent(indent int) error { 95 | for { 96 | if p.nodes.peek().Type() == nRoot { 97 | return fmt.Errorf("unexpected: node has no parent with indent %d", indent) 98 | } 99 | if p.nodes.peek().Indent() <= indent { 100 | break 101 | } 102 | p.nodes.pop() 103 | } 104 | p.n = p.nodes.peek() 105 | return nil 106 | } 107 | 108 | func (p *parser) backToParent() error { 109 | if p.nodes.peek().Type() == nRoot { 110 | return fmt.Errorf("unexpected: node has no parent %d", p.nodes.peek().Type()) 111 | } 112 | p.nodes.pop() 113 | p.n = p.nodes.peek() 114 | return nil 115 | } 116 | 117 | func (p *parser) addNode(n parsingNode) { 118 | p.addChild(n) 119 | p.nodes.push(n) 120 | p.n = n 121 | } 122 | 123 | func (p *parser) addChild(n nodeBase) { 124 | p.n.AddChild(n) 125 | } 126 | -------------------------------------------------------------------------------- /compiler/render_test.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/sergi/go-diff/diffmatchpatch" 10 | 11 | "github.com/stackus/goht" 12 | "github.com/stackus/goht/compiler/testdata" 13 | ) 14 | 15 | func TestRender(t *testing.T) { 16 | tests := map[string]struct { 17 | template goht.Template 18 | htmlFile string 19 | }{ 20 | "package": { 21 | template: testdata.PackageTest(), 22 | htmlFile: "package", 23 | }, 24 | "imports": { 25 | template: testdata.ImportsTest(), 26 | htmlFile: "imports", 27 | }, 28 | "elements": { 29 | template: testdata.ElementsTest(), 30 | htmlFile: "elements", 31 | }, 32 | "attributes": { 33 | template: testdata.AttributesTest(), 34 | htmlFile: "attributes", 35 | }, 36 | "newlines": { 37 | template: testdata.NewlinesTest(), 38 | htmlFile: "newlines", 39 | }, 40 | "interpolation": { 41 | template: testdata.InterpolationTest(), 42 | htmlFile: "interpolation", 43 | }, 44 | "comments": { 45 | template: testdata.CommentsTest(), 46 | htmlFile: "comments", 47 | }, 48 | "conditionals.true": { 49 | template: testdata.ConditionalsTest(true), 50 | htmlFile: "conditionals.true", 51 | }, 52 | "conditionals.false": { 53 | template: testdata.ConditionalsTest(false), 54 | htmlFile: "conditionals.false", 55 | }, 56 | "filters": { 57 | template: testdata.FiltersTest(), 58 | htmlFile: "filters", 59 | }, 60 | "object references": { 61 | template: testdata.ObjectReferencesTest(), 62 | htmlFile: "obj_references", 63 | }, 64 | "whitespace": { 65 | template: testdata.WhitespaceTest(), 66 | htmlFile: "whitespace", 67 | }, 68 | "render": { 69 | template: testdata.RenderTest(), 70 | htmlFile: "rendering", 71 | }, 72 | "slots": { 73 | template: testdata.SlotTest(), 74 | htmlFile: "slots", 75 | }, 76 | "slots with defaults": { 77 | template: testdata.SlotWithDefaultTest(), 78 | htmlFile: "slots_with_defaults", 79 | }, 80 | "without children": { 81 | template: testdata.ChildrenTest("passed-in"), 82 | htmlFile: "without_children", 83 | }, 84 | "nesting": { 85 | template: testdata.NestedRenderTest(), 86 | htmlFile: "nesting", 87 | }, 88 | "slim template": { 89 | template: testdata.SlimTemplate(), 90 | htmlFile: "slim_template", 91 | }, 92 | "ego template": { 93 | template: testdata.EgoTemplate(), 94 | htmlFile: "ego_template", 95 | }, 96 | } 97 | for name, tt := range tests { 98 | t.Run(name, func(t *testing.T) { 99 | var gotW bytes.Buffer 100 | err := tt.template.Render(context.Background(), &gotW) 101 | if err != nil { 102 | t.Errorf("error generating template: %v", err) 103 | return 104 | } 105 | 106 | got := gotW.Bytes() 107 | goldenFileName := filepath.Join("testdata", tt.htmlFile+".html") 108 | want, err := goldenFile(t, goldenFileName, got, *update) 109 | if err != nil { 110 | t.Errorf("error reading golden file: %v", err) 111 | return 112 | } 113 | 114 | if bytes.Equal(want, got) { 115 | return 116 | } 117 | 118 | dmp := diffmatchpatch.New() 119 | diffs := dmp.DiffMain(string(want), string(got), true) 120 | if len(diffs) > 1 { 121 | t.Errorf("diff:\n%s", dmp.DiffPrettyText(diffs)) 122 | } 123 | }) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /compiler/source_map.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type Position struct { 8 | Line int 9 | Col int 10 | } 11 | 12 | type Range struct { 13 | From Position 14 | To Position 15 | } 16 | 17 | type SourceMap struct { 18 | SourceLinesToTarget map[int]map[int]Position 19 | TargetLinesToSource map[int]map[int]Position 20 | } 21 | 22 | // Add will create a new source map entry from the Goht template to the generated Go code. 23 | // 24 | // The IDEs will be using zero-based line and column numbers, so we need to convert them 25 | // from the one-based line and column numbers that we've generated during the parsing of the 26 | // Goht template. 27 | // 28 | // When we parse the length of a line we will create mappings for the entire len() because 29 | // the IDEs and LSPs will use the character AFTER the last character in the range as the 30 | // end position. 31 | // For example, "foo" will have a start column of 0 and an end column value of 3, not 2. 32 | func (sm *SourceMap) Add(t token, destRange Range) { 33 | lines := strings.Split(t.lit, "\n") 34 | for lineIndex, line := range lines { 35 | srcLine := t.line + lineIndex - 1 36 | tgtLine := destRange.From.Line + lineIndex - 1 37 | 38 | var srcCol, tgtCol int 39 | if lineIndex == 0 { 40 | srcCol += t.col - 1 41 | tgtCol += destRange.From.Col - 1 42 | } 43 | 44 | if _, ok := sm.SourceLinesToTarget[srcLine]; !ok { 45 | sm.SourceLinesToTarget[srcLine] = make(map[int]Position) 46 | } 47 | if _, ok := sm.TargetLinesToSource[tgtLine]; !ok { 48 | sm.TargetLinesToSource[tgtLine] = make(map[int]Position) 49 | } 50 | 51 | for colIndex := 0; colIndex <= len(line); colIndex++ { 52 | sm.SourceLinesToTarget[srcLine][srcCol+colIndex] = Position{Line: tgtLine, Col: tgtCol + colIndex} 53 | sm.TargetLinesToSource[tgtLine][tgtCol+colIndex] = Position{Line: srcLine, Col: srcCol + colIndex} 54 | } 55 | } 56 | } 57 | 58 | func (sm *SourceMap) SourcePositionFromTarget(line, col int) (Position, bool) { 59 | if _, ok := sm.TargetLinesToSource[line]; !ok { 60 | return Position{}, false 61 | } 62 | if _, ok := sm.TargetLinesToSource[line][col]; !ok { 63 | return Position{}, false 64 | } 65 | return sm.TargetLinesToSource[line][col], true 66 | } 67 | 68 | func (sm *SourceMap) TargetPositionFromSource(line, col int) (Position, bool) { 69 | if _, ok := sm.SourceLinesToTarget[line]; !ok { 70 | return Position{}, false 71 | } 72 | if _, ok := sm.SourceLinesToTarget[line][col]; !ok { 73 | return Position{}, false 74 | } 75 | return sm.SourceLinesToTarget[line][col], true 76 | } 77 | -------------------------------------------------------------------------------- /compiler/template_test.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "bytes" 5 | "go/format" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/sergi/go-diff/diffmatchpatch" 11 | ) 12 | 13 | func TestTemplate_Generate(t *testing.T) { 14 | tests := map[string]struct { 15 | templateFile string 16 | }{ 17 | "package": { 18 | templateFile: "package", 19 | }, 20 | "imports": { 21 | templateFile: "imports", 22 | }, 23 | "elements": { 24 | templateFile: "elements", 25 | }, 26 | "attributes": { 27 | templateFile: "attributes", 28 | }, 29 | "newlines": { 30 | templateFile: "newlines", 31 | }, 32 | "interpolation": { 33 | templateFile: "interpolation", 34 | }, 35 | "comments": { 36 | templateFile: "comments", 37 | }, 38 | "conditionals": { 39 | templateFile: "conditionals", 40 | }, 41 | "filters": { 42 | templateFile: "filters", 43 | }, 44 | "object references": { 45 | templateFile: "obj_references", 46 | }, 47 | "whitespace": { 48 | templateFile: "whitespace", 49 | }, 50 | "render": { 51 | templateFile: "rendering", 52 | }, 53 | } 54 | for name, tt := range tests { 55 | t.Run(name, func(t *testing.T) { 56 | fileName := filepath.Join("testdata", tt.templateFile+".goht") 57 | contents, err := os.ReadFile(fileName) 58 | if err != nil { 59 | t.Errorf("error reading file: %v", err) 60 | return 61 | } 62 | var tpl *Template 63 | tpl, err = ParseString(string(contents)) 64 | if err != nil { 65 | t.Errorf("error parsing template: %v", err) 66 | return 67 | } 68 | 69 | var gotW bytes.Buffer 70 | err = tpl.Generate(&gotW) 71 | if err != nil { 72 | t.Errorf("error generating template: %v", err) 73 | return 74 | } 75 | 76 | var got []byte 77 | got, err = format.Source(gotW.Bytes()) 78 | if err != nil { 79 | t.Errorf("error formatting source: %v", err) 80 | return 81 | } 82 | 83 | goldenFileName := filepath.Join("testdata", tt.templateFile+".goht.go") 84 | want, err := goldenFile(t, goldenFileName, got, *update) 85 | if err != nil { 86 | t.Errorf("error reading golden file: %v", err) 87 | return 88 | } 89 | 90 | if bytes.Equal(want, got) { 91 | return 92 | } 93 | 94 | dmp := diffmatchpatch.New() 95 | diffs := dmp.DiffMain(string(want), string(got), true) 96 | if len(diffs) > 1 { 97 | t.Errorf("diff:\n%s", dmp.DiffPrettyText(diffs)) 98 | } 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /compiler/testdata/attributes.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | @goht AttributesTest() { 4 | .boolean{disabled} Disabled Content 5 | - condition := false 6 | .boolean{disabled? #{condition}} Conditional Content 7 | - attrs := map[string]string{"a": "b"} 8 | .attributes{@attributes: @{attrs}} 9 | - fizz := "buzz" 10 | .multiline{ 11 | foo: "bar", 12 | fizz: #{fizz}, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /compiler/testdata/attributes.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package testdata 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | func AttributesTest() goht.Template { 11 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 12 | __buf, __isBuf := __w.(goht.Buffer) 13 | if !__isBuf { 14 | __buf = goht.GetBuffer() 15 | defer goht.ReleaseBuffer(__buf) 16 | } 17 | var __children goht.Template 18 | ctx, __children = goht.PopChildren(ctx) 19 | _ = __children 20 | if _, __err = __buf.WriteString("
Disabled Content
\n"); __err != nil { 21 | return 22 | } 23 | condition := false 24 | if _, __err = __buf.WriteString("
Conditional Content
\n"); __err != nil { 33 | return 34 | } 35 | attrs := map[string]string{"a": "b"} 36 | if _, __err = __buf.WriteString("
\n"); __err != nil { 48 | return 49 | } 50 | fizz := "buzz" 51 | if _, __err = __buf.WriteString("
\n"); __err != nil { 58 | return 59 | } 60 | if !__isBuf { 61 | _, __err = __w.Write(__buf.Bytes()) 62 | } 63 | return 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /compiler/testdata/attributes.html: -------------------------------------------------------------------------------- 1 |
Disabled Content
2 |
Conditional Content
3 |
4 |
5 | -------------------------------------------------------------------------------- /compiler/testdata/comments.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | @goht CommentsTest() { 4 | %p before 5 | / this is a HTML comment 6 | %p after 7 | -# this is a Haml comment 8 | %p last 9 | %div --- 10 | %p before 11 | / 12 | this is a HTML comment 13 | %p after 14 | -# 15 | this is a Haml comment 16 | %p last 17 | } 18 | -------------------------------------------------------------------------------- /compiler/testdata/comments.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package testdata 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | func CommentsTest() goht.Template { 11 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 12 | __buf, __isBuf := __w.(goht.Buffer) 13 | if !__isBuf { 14 | __buf = goht.GetBuffer() 15 | defer goht.ReleaseBuffer(__buf) 16 | } 17 | var __children goht.Template 18 | ctx, __children = goht.PopChildren(ctx) 19 | _ = __children 20 | if _, __err = __buf.WriteString("

before

\n\n

after

\n

last

\n
---
\n

before

\n\n

after

\n

last

\n"); __err != nil { 21 | return 22 | } 23 | if !__isBuf { 24 | _, __err = __w.Write(__buf.Bytes()) 25 | } 26 | return 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /compiler/testdata/comments.html: -------------------------------------------------------------------------------- 1 |

before

2 | 3 |

after

4 |

last

5 |
---
6 |

before

7 | 10 |

after

11 |

last

12 | -------------------------------------------------------------------------------- /compiler/testdata/conditionals.false.html: -------------------------------------------------------------------------------- 1 |

before

2 |

after

3 | -------------------------------------------------------------------------------- /compiler/testdata/conditionals.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | @goht ConditionalsTest(v bool) { 4 | %p before 5 | - if v 6 | %p the condition was true 7 | %p after 8 | } 9 | -------------------------------------------------------------------------------- /compiler/testdata/conditionals.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package testdata 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | func ConditionalsTest(v bool) goht.Template { 11 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 12 | __buf, __isBuf := __w.(goht.Buffer) 13 | if !__isBuf { 14 | __buf = goht.GetBuffer() 15 | defer goht.ReleaseBuffer(__buf) 16 | } 17 | var __children goht.Template 18 | ctx, __children = goht.PopChildren(ctx) 19 | _ = __children 20 | if _, __err = __buf.WriteString("

before

\n"); __err != nil { 21 | return 22 | } 23 | if v { 24 | if _, __err = __buf.WriteString("

the condition was true

\n"); __err != nil { 25 | return 26 | } 27 | } 28 | if _, __err = __buf.WriteString("

after

\n"); __err != nil { 29 | return 30 | } 31 | if !__isBuf { 32 | _, __err = __w.Write(__buf.Bytes()) 33 | } 34 | return 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /compiler/testdata/conditionals.true.html: -------------------------------------------------------------------------------- 1 |

before

2 |

the condition was true

3 |

after

4 | -------------------------------------------------------------------------------- /compiler/testdata/ego_template.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | @ego EgoTemplate() { 4 | 5 | 6 | 7 | Hello World 8 | 9 | 10 |

Hello World

11 | <% for i := 0; i < 10; i++ { -%> 12 |

Iteration: <%=%d i %>

13 | <%- } -%> 14 | <% if true { -%> 15 |

Condition is true

16 | <%- } else if false { -%> 17 |

Condition is false

18 | <%- } -%> 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /compiler/testdata/ego_template.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package testdata 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | func EgoTemplate() goht.Template { 11 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 12 | __buf, __isBuf := __w.(goht.Buffer) 13 | if !__isBuf { 14 | __buf = goht.GetBuffer() 15 | defer goht.ReleaseBuffer(__buf) 16 | } 17 | var __children goht.Template 18 | ctx, __children = goht.PopChildren(ctx) 19 | _ = __children 20 | if _, __err = __buf.WriteString("\n\n\t\n\t\tHello World\n\t\n\t\n\t\t

Hello World

\n\t\t"); __err != nil { 21 | return 22 | } 23 | for i := 0; i < 10; i++ { 24 | if _, __err = __buf.WriteString("

Iteration: "); __err != nil { 25 | return 26 | } 27 | var __var1 string 28 | if __var1, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%d", i))); __err != nil { 29 | return 30 | } 31 | if _, __err = __buf.WriteString(__var1); __err != nil { 32 | return 33 | } 34 | if _, __err = __buf.WriteString("

"); __err != nil { 35 | return 36 | } 37 | } 38 | if true { 39 | if _, __err = __buf.WriteString("

Condition is true

"); __err != nil { 40 | return 41 | } 42 | } else if false { 43 | if _, __err = __buf.WriteString("

Condition is false

"); __err != nil { 44 | return 45 | } 46 | } 47 | if _, __err = __buf.WriteString("\n\n"); __err != nil { 48 | return 49 | } 50 | if !__isBuf { 51 | _, __err = __w.Write(__buf.Bytes()) 52 | } 53 | return 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /compiler/testdata/ego_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World 5 | 6 | 7 |

Hello World

8 |

Iteration: 0

Iteration: 1

Iteration: 2

Iteration: 3

Iteration: 4

Iteration: 5

Iteration: 6

Iteration: 7

Iteration: 8

Iteration: 9

Condition is true

9 | 10 | -------------------------------------------------------------------------------- /compiler/testdata/elements.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | @goht ElementsTest() { 4 | !!! 5 | %html 6 | %body 7 | #main.wrapper 8 | %article 9 | %h1 Article Title 10 | %p Article content 11 | %footer 12 | %p Article footer 13 | %footer.wrapper 14 | %p Footer content 15 | } 16 | -------------------------------------------------------------------------------- /compiler/testdata/elements.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package testdata 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | func ElementsTest() goht.Template { 11 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 12 | __buf, __isBuf := __w.(goht.Buffer) 13 | if !__isBuf { 14 | __buf = goht.GetBuffer() 15 | defer goht.ReleaseBuffer(__buf) 16 | } 17 | var __children goht.Template 18 | ctx, __children = goht.PopChildren(ctx) 19 | _ = __children 20 | if _, __err = __buf.WriteString("\n\n\n
\n
\n

Article Title

\n
\n

Article content

\n\n
\n\n\n\n"); __err != nil { 21 | return 22 | } 23 | if !__isBuf { 24 | _, __err = __w.Write(__buf.Bytes()) 25 | } 26 | return 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /compiler/testdata/elements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |

Article Title

7 |
8 |

Article content

9 | 12 |
13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /compiler/testdata/filters.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | @goht FiltersTest() { 4 | - str := "Interpolated text" 5 | %p 6 | :plain 7 | Plain text 8 | #{str} 9 | %p 10 | :plain 11 | Indented Plain text 12 | #{str} 13 | %p 14 | :escaped 15 | Escaped text 16 | #{str} 17 | %p 18 | :preserve 19 | Preserved text 20 | #{str} 21 | .nesting 22 | :javascript 23 | console.log("#{str}"); 24 | if (true) { 25 | console.log("#{str}"); 26 | } 27 | - color := "red" 28 | :css 29 | .red { 30 | color: #{color}; 31 | } 32 | .blue { 33 | color: blue; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /compiler/testdata/filters.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package testdata 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | func FiltersTest() goht.Template { 11 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 12 | __buf, __isBuf := __w.(goht.Buffer) 13 | if !__isBuf { 14 | __buf = goht.GetBuffer() 15 | defer goht.ReleaseBuffer(__buf) 16 | } 17 | var __children goht.Template 18 | ctx, __children = goht.PopChildren(ctx) 19 | _ = __children 20 | str := "Interpolated text" 21 | if _, __err = __buf.WriteString("

\nPlain text\n"); __err != nil { 22 | return 23 | } 24 | var __var1 string 25 | if __var1, __err = goht.CaptureErrors(str); __err != nil { 26 | return 27 | } 28 | if _, __err = __buf.WriteString(__var1); __err != nil { 29 | return 30 | } 31 | if _, __err = __buf.WriteString("\n

\n

\n\tIndented Plain text\n\t"); __err != nil { 32 | return 33 | } 34 | var __var2 string 35 | if __var2, __err = goht.CaptureErrors(str); __err != nil { 36 | return 37 | } 38 | if _, __err = __buf.WriteString(__var2); __err != nil { 39 | return 40 | } 41 | if _, __err = __buf.WriteString("\n

\n

\nEscaped <em>text</em>\n"); __err != nil { 42 | return 43 | } 44 | var __var3 string 45 | if __var3, __err = goht.CaptureErrors(goht.EscapeString(str)); __err != nil { 46 | return 47 | } 48 | if _, __err = __buf.WriteString(__var3); __err != nil { 49 | return 50 | } 51 | if _, __err = __buf.WriteString("\n

\n

\nPreserved text "); __err != nil { 52 | return 53 | } 54 | var __var4 string 55 | if __var4, __err = goht.CaptureErrors(str); __err != nil { 56 | return 57 | } 58 | if _, __err = __buf.WriteString(__var4); __err != nil { 59 | return 60 | } 61 | if _, __err = __buf.WriteString(" \n

\n
\n
\n"); __err != nil { 82 | return 83 | } 84 | color := "red" 85 | if _, __err = __buf.WriteString(""); __err != nil { 96 | return 97 | } 98 | if !__isBuf { 99 | _, __err = __w.Write(__buf.Bytes()) 100 | } 101 | return 102 | }) 103 | } 104 | -------------------------------------------------------------------------------- /compiler/testdata/filters.html: -------------------------------------------------------------------------------- 1 |

2 | Plain text 3 | Interpolated text 4 |

5 |

6 | Indented Plain text 7 | Interpolated text 8 |

9 |

10 | Escaped <em>text</em> 11 | Interpolated <em>text</em> 12 |

13 |

14 | Preserved text Interpolated text 15 |

16 |
17 |
23 | -------------------------------------------------------------------------------- /compiler/testdata/imports.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | @goht ImportsTest() { 8 | - s := fmt.Sprintf("Hello, %s!", "world") 9 | %p= s 10 | } 11 | -------------------------------------------------------------------------------- /compiler/testdata/imports.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package testdata 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | import ( 10 | "fmt" 11 | ) 12 | 13 | func ImportsTest() goht.Template { 14 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 15 | __buf, __isBuf := __w.(goht.Buffer) 16 | if !__isBuf { 17 | __buf = goht.GetBuffer() 18 | defer goht.ReleaseBuffer(__buf) 19 | } 20 | var __children goht.Template 21 | ctx, __children = goht.PopChildren(ctx) 22 | _ = __children 23 | s := fmt.Sprintf("Hello, %s!", "world") 24 | if _, __err = __buf.WriteString("

"); __err != nil { 25 | return 26 | } 27 | var __var1 string 28 | if __var1, __err = goht.CaptureErrors(goht.EscapeString(s)); __err != nil { 29 | return 30 | } 31 | if _, __err = __buf.WriteString(__var1); __err != nil { 32 | return 33 | } 34 | if _, __err = __buf.WriteString("

\n"); __err != nil { 35 | return 36 | } 37 | if !__isBuf { 38 | _, __err = __w.Write(__buf.Bytes()) 39 | } 40 | return 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /compiler/testdata/imports.html: -------------------------------------------------------------------------------- 1 |

Hello, world!

2 | -------------------------------------------------------------------------------- /compiler/testdata/interpolation.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | var foo = "bar" 4 | 5 | @goht InterpolationTest() { 6 | %p This is #{foo} 7 | } 8 | -------------------------------------------------------------------------------- /compiler/testdata/interpolation.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package testdata 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | var foo = "bar" 11 | 12 | func InterpolationTest() goht.Template { 13 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 14 | __buf, __isBuf := __w.(goht.Buffer) 15 | if !__isBuf { 16 | __buf = goht.GetBuffer() 17 | defer goht.ReleaseBuffer(__buf) 18 | } 19 | var __children goht.Template 20 | ctx, __children = goht.PopChildren(ctx) 21 | _ = __children 22 | if _, __err = __buf.WriteString("

This is "); __err != nil { 23 | return 24 | } 25 | var __var1 string 26 | if __var1, __err = goht.CaptureErrors(goht.EscapeString(foo)); __err != nil { 27 | return 28 | } 29 | if _, __err = __buf.WriteString(__var1); __err != nil { 30 | return 31 | } 32 | if _, __err = __buf.WriteString("

\n"); __err != nil { 33 | return 34 | } 35 | if !__isBuf { 36 | _, __err = __w.Write(__buf.Bytes()) 37 | } 38 | return 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /compiler/testdata/interpolation.html: -------------------------------------------------------------------------------- 1 |

This is bar

2 | -------------------------------------------------------------------------------- /compiler/testdata/nesting.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
first
4 |
second
5 |
6 |
7 | -------------------------------------------------------------------------------- /compiler/testdata/newlines.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | @goht NewlinesTest() { 4 | .first 5 | .second Content 6 | .third 7 | } 8 | -------------------------------------------------------------------------------- /compiler/testdata/newlines.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package testdata 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | func NewlinesTest() goht.Template { 11 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 12 | __buf, __isBuf := __w.(goht.Buffer) 13 | if !__isBuf { 14 | __buf = goht.GetBuffer() 15 | defer goht.ReleaseBuffer(__buf) 16 | } 17 | var __children goht.Template 18 | ctx, __children = goht.PopChildren(ctx) 19 | _ = __children 20 | if _, __err = __buf.WriteString("
\n
Content
\n
\n"); __err != nil { 21 | return 22 | } 23 | if !__isBuf { 24 | _, __err = __w.Write(__buf.Bytes()) 25 | } 26 | return 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /compiler/testdata/newlines.html: -------------------------------------------------------------------------------- 1 |
2 |
Content
3 |
4 | -------------------------------------------------------------------------------- /compiler/testdata/obj_references.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | type ObjRef string 4 | 5 | func (ObjRef) ObjectID() string { 6 | return "123" 7 | } 8 | 9 | func (ObjRef) ObjectClass() string { 10 | return "objref" 11 | } 12 | 13 | @goht ObjectReferencesTest() { 14 | - o := ObjRef("") 15 | %p[o] Unprefixed 16 | %p[o, "prefixed"] Prefixed 17 | } 18 | -------------------------------------------------------------------------------- /compiler/testdata/obj_references.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package testdata 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | type ObjRef string 11 | 12 | func (ObjRef) ObjectID() string { 13 | return "123" 14 | } 15 | 16 | func (ObjRef) ObjectClass() string { 17 | return "objref" 18 | } 19 | 20 | func ObjectReferencesTest() goht.Template { 21 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 22 | __buf, __isBuf := __w.(goht.Buffer) 23 | if !__isBuf { 24 | __buf = goht.GetBuffer() 25 | defer goht.ReleaseBuffer(__buf) 26 | } 27 | var __children goht.Template 28 | ctx, __children = goht.PopChildren(ctx) 29 | _ = __children 30 | o := ObjRef("") 31 | if _, __err = __buf.WriteString("Unprefixed

\nPrefixed

\n"); __err != nil { 64 | return 65 | } 66 | if !__isBuf { 67 | _, __err = __w.Write(__buf.Bytes()) 68 | } 69 | return 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /compiler/testdata/obj_references.html: -------------------------------------------------------------------------------- 1 |

Unprefixed

2 |

Prefixed

3 | -------------------------------------------------------------------------------- /compiler/testdata/package.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | @goht PackageTest() { 4 | This is a package test. 5 | } 6 | -------------------------------------------------------------------------------- /compiler/testdata/package.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package testdata 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | func PackageTest() goht.Template { 11 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 12 | __buf, __isBuf := __w.(goht.Buffer) 13 | if !__isBuf { 14 | __buf = goht.GetBuffer() 15 | defer goht.ReleaseBuffer(__buf) 16 | } 17 | var __children goht.Template 18 | ctx, __children = goht.PopChildren(ctx) 19 | _ = __children 20 | if _, __err = __buf.WriteString("This is a package test.\n"); __err != nil { 21 | return 22 | } 23 | if !__isBuf { 24 | _, __err = __w.Write(__buf.Bytes()) 25 | } 26 | return 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /compiler/testdata/package.html: -------------------------------------------------------------------------------- 1 | This is a package test. 2 | -------------------------------------------------------------------------------- /compiler/testdata/rendering.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | @haml ChildrenTest(v string) { 4 | .passed-in= v 5 | .children 6 | =@children 7 | .after After children 8 | } 9 | 10 | @haml RenderTest() { 11 | .parent 12 | .local This is local content 13 | =@render ChildrenTest("This is passed-in content") 14 | .child This is child content 15 | .after After parent 16 | } 17 | 18 | @haml WrapperTest() { 19 | .wrapper 20 | .list 21 | =@children 22 | } 23 | 24 | @haml WrappedTest(v string) { 25 | .item= v 26 | } 27 | 28 | @haml NestedRenderTest() { 29 | =@render WrapperTest() 30 | =@render WrappedTest("first") 31 | =@render WrappedTest("second") 32 | } 33 | 34 | @haml SlotTest() { 35 | .wrapper 36 | =@slot first 37 | =@slot second 38 | =@slot third 39 | } 40 | 41 | @haml SlotWithDefaultTest() { 42 | .wrapper 43 | =@slot first 44 | %p Default first 45 | =@slot second 46 | %p Default second 47 | =@slot third 48 | %p Default third 49 | } 50 | -------------------------------------------------------------------------------- /compiler/testdata/rendering.html: -------------------------------------------------------------------------------- 1 |
2 |
This is local content
3 |
This is passed-in content
4 |
5 |
This is child content
6 |
7 |
After children
8 |
After parent
9 |
10 | -------------------------------------------------------------------------------- /compiler/testdata/slim_template.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | @slim SlimTemplate() { 4 | doctype 5 | html{lang:"en"} 6 | head 7 | title Hello World 8 | body 9 | p Hello World 10 | - for i := 0; i < 10; i++ 11 | p Iteration: #{%d i} 12 | - if true 13 | p Condition is true 14 | - else if false 15 | p Condition is false 16 | } 17 | -------------------------------------------------------------------------------- /compiler/testdata/slim_template.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package testdata 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | func SlimTemplate() goht.Template { 11 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 12 | __buf, __isBuf := __w.(goht.Buffer) 13 | if !__isBuf { 14 | __buf = goht.GetBuffer() 15 | defer goht.ReleaseBuffer(__buf) 16 | } 17 | var __children goht.Template 18 | ctx, __children = goht.PopChildren(ctx) 19 | _ = __children 20 | if _, __err = __buf.WriteString("Hello World

Hello World

"); __err != nil { 21 | return 22 | } 23 | for i := 0; i < 10; i++ { 24 | if _, __err = __buf.WriteString("

Iteration: "); __err != nil { 25 | return 26 | } 27 | var __var1 string 28 | if __var1, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%d", i))); __err != nil { 29 | return 30 | } 31 | if _, __err = __buf.WriteString(__var1); __err != nil { 32 | return 33 | } 34 | if _, __err = __buf.WriteString("

"); __err != nil { 35 | return 36 | } 37 | } 38 | if true { 39 | if _, __err = __buf.WriteString("

Condition is true

"); __err != nil { 40 | return 41 | } 42 | } else if false { 43 | if _, __err = __buf.WriteString("

Condition is false

"); __err != nil { 44 | return 45 | } 46 | } 47 | if _, __err = __buf.WriteString("\n"); __err != nil { 48 | return 49 | } 50 | if !__isBuf { 51 | _, __err = __w.Write(__buf.Bytes()) 52 | } 53 | return 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /compiler/testdata/slim_template.html: -------------------------------------------------------------------------------- 1 | Hello World

Hello World

Iteration: 0

Iteration: 1

Iteration: 2

Iteration: 3

Iteration: 4

Iteration: 5

Iteration: 6

Iteration: 7

Iteration: 8

Iteration: 9

Condition is true

2 | -------------------------------------------------------------------------------- /compiler/testdata/slots.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /compiler/testdata/slots_with_defaults.html: -------------------------------------------------------------------------------- 1 |
2 |

Default first

3 |

Default second

4 |

Default third

5 |
6 | -------------------------------------------------------------------------------- /compiler/testdata/whitespace.goht: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | @goht WhitespaceTest() { 4 | %p 5 | %span Content 6 | %span> 7 | Outer 8 | %span Content 9 | %p 10 | %span Content 11 | %span< 12 | Inner 13 | %span Content 14 | %p 15 | %span Content 16 | %span<> 17 | Both 18 | %span Content 19 | } 20 | -------------------------------------------------------------------------------- /compiler/testdata/whitespace.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package testdata 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | func WhitespaceTest() goht.Template { 11 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 12 | __buf, __isBuf := __w.(goht.Buffer) 13 | if !__isBuf { 14 | __buf = goht.GetBuffer() 15 | defer goht.ReleaseBuffer(__buf) 16 | } 17 | var __children goht.Template 18 | ctx, __children = goht.PopChildren(ctx) 19 | _ = __children 20 | if _, __err = __buf.WriteString("

\nContent\n>☢~\nOuter\n~☢<Content\n

\n

\nContent\n~☢<\nInner\n>☢~\nContent\n

\n

\nContent\n>☢~~☢<\nBoth\n>☢~~☢<Content\n

\n"); __err != nil { 21 | return 22 | } 23 | if !__isBuf { 24 | _, __err = __w.Write(__buf.Bytes()) 25 | } 26 | return 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /compiler/testdata/whitespace.html: -------------------------------------------------------------------------------- 1 |

2 | Content 3 | Outer 4 | Content 5 |

6 |

7 | Content 8 | Inner 9 | Content 10 |

11 |

12 | ContentBothContent 13 |

14 | -------------------------------------------------------------------------------- /compiler/testdata/without_children.html: -------------------------------------------------------------------------------- 1 |
passed-in
2 |
3 |
4 |
After children
5 | -------------------------------------------------------------------------------- /compiler/tokens.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type tokenType int 8 | 9 | type token struct { 10 | typ tokenType 11 | lit string 12 | line int 13 | col int 14 | } 15 | 16 | const ( 17 | // General tokens 18 | tEOF tokenType = iota 19 | tError 20 | tRoot 21 | tNewLine 22 | tPackage 23 | tImport 24 | tGoCode 25 | 26 | // Template tokens 27 | tTemplateStart 28 | tTemplateEnd 29 | 30 | // Goht/Haml tokens 31 | tDoctype 32 | tTag 33 | tId 34 | tClass 35 | tObjectRef 36 | tAttrName 37 | tAttrOperator 38 | tAttrEscapedValue 39 | tAttrDynamicValue 40 | tIndent 41 | tComment 42 | tRubyComment 43 | tVoidTag 44 | tKeepNewlines 45 | tNukeInnerWhitespace 46 | tNukeOuterWhitespace 47 | tAddWhitespaceBefore 48 | tAddWhitespaceAfter 49 | tRawText 50 | tEscapedText 51 | tDynamicText 52 | tPlainText 53 | tPreserveText 54 | tUnescaped 55 | tScript 56 | tSilentScript 57 | tRenderCommand 58 | tChildrenCommand 59 | tSlotCommand 60 | tAttributesCommand 61 | tFilterStart 62 | tFilterEnd 63 | ) 64 | 65 | func (t tokenType) String() string { 66 | switch t { 67 | case tEOF: 68 | return "EOF" 69 | case tError: 70 | return "Error" 71 | case tRoot: 72 | return "Root" 73 | case tNewLine: 74 | return "NewLine" 75 | case tPackage: 76 | return "Package" 77 | case tImport: 78 | return "Import" 79 | case tGoCode: 80 | return "GoCode" 81 | case tTemplateStart: 82 | return "TemplateStart" 83 | case tTemplateEnd: 84 | return "TemplateEnd" 85 | case tDoctype: 86 | return "Doctype" 87 | case tTag: 88 | return "Tag" 89 | case tId: 90 | return "Id" 91 | case tClass: 92 | return "Class" 93 | case tObjectRef: 94 | return "ObjectRef" 95 | case tAttrName: 96 | return "AttrName" 97 | case tAttrOperator: 98 | return "AttrOperator" 99 | case tAttrEscapedValue: 100 | return "AttrEscapedValue" 101 | case tAttrDynamicValue: 102 | return "AttrDynamicValue" 103 | case tIndent: 104 | return "Indent" 105 | case tComment: 106 | return "Comment" 107 | case tRubyComment: 108 | return "RubyComment" 109 | case tVoidTag: 110 | return "VoidTag" 111 | case tKeepNewlines: 112 | return "KeepNewlines" 113 | case tNukeInnerWhitespace: 114 | return "NukeInnerWhitespace" 115 | case tNukeOuterWhitespace: 116 | return "NukeOuterWhitespace" 117 | case tAddWhitespaceBefore: 118 | return "AddWhitespaceBefore" 119 | case tAddWhitespaceAfter: 120 | return "AddWhitespaceAfter" 121 | case tRawText: 122 | return "RawText" 123 | case tEscapedText: 124 | return "EscapedText" 125 | case tDynamicText: 126 | return "DynamicText" 127 | case tPlainText: 128 | return "PlainText" 129 | case tPreserveText: 130 | return "PreserveText" 131 | case tUnescaped: 132 | return "Unescaped" 133 | case tScript: 134 | return "Script" 135 | case tSilentScript: 136 | return "SilentScript" 137 | case tRenderCommand: 138 | return "RenderCommand" 139 | case tChildrenCommand: 140 | return "ChildrenCommand" 141 | case tSlotCommand: 142 | return "SlotCommand" 143 | case tAttributesCommand: 144 | return "AttributesCommand" 145 | case tFilterStart: 146 | return "FilterStart" 147 | case tFilterEnd: 148 | return "FilterEnd" 149 | default: 150 | return "!Unknown!" 151 | } 152 | } 153 | 154 | func (t token) Type() tokenType { 155 | return t.typ 156 | } 157 | 158 | func (t token) Lit() string { 159 | return t.lit 160 | } 161 | 162 | func (t token) Line() int { 163 | return t.line 164 | } 165 | 166 | func (t token) Col() int { 167 | return t.col 168 | } 169 | 170 | func (t token) String() string { 171 | if t.typ != tError && len(t.lit) > 30 { 172 | return fmt.Sprintf("%s[%d:%d]: %q", t.typ, t.line, t.col, string([]rune(t.lit)[:30])+"...") 173 | } 174 | return fmt.Sprintf("%s[%d:%d]: %q", t.typ, t.line, t.col, t.lit) 175 | } 176 | -------------------------------------------------------------------------------- /compiler/tokens_test.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_TokenStringer(t *testing.T) { 8 | tests := map[string]struct { 9 | typ tokenType 10 | want string 11 | }{ 12 | "tEOF": { 13 | typ: tEOF, 14 | want: "EOF", 15 | }, 16 | "tError": { 17 | typ: tError, 18 | want: "Error", 19 | }, 20 | "tRoot": { 21 | typ: tRoot, 22 | want: "Root", 23 | }, 24 | "tNewLine": { 25 | typ: tNewLine, 26 | want: "NewLine", 27 | }, 28 | "tPackage": { 29 | typ: tPackage, 30 | want: "Package", 31 | }, 32 | "tImport": { 33 | typ: tImport, 34 | want: "Import", 35 | }, 36 | "tGoCode": { 37 | typ: tGoCode, 38 | want: "GoCode", 39 | }, 40 | "tDoctype": { 41 | typ: tDoctype, 42 | want: "Doctype", 43 | }, 44 | "tTag": { 45 | typ: tTag, 46 | want: "Tag", 47 | }, 48 | "tId": { 49 | typ: tId, 50 | want: "Id", 51 | }, 52 | "tClass": { 53 | typ: tClass, 54 | want: "Class", 55 | }, 56 | "tObjectRef": { 57 | typ: tObjectRef, 58 | want: "ObjectRef", 59 | }, 60 | "tAttrName": { 61 | typ: tAttrName, 62 | want: "AttrName", 63 | }, 64 | "tAttrOperator": { 65 | typ: tAttrOperator, 66 | want: "AttrOperator", 67 | }, 68 | "tAttrEscapedValue": { 69 | typ: tAttrEscapedValue, 70 | want: "AttrEscapedValue", 71 | }, 72 | "tAttrDynamicValue": { 73 | typ: tAttrDynamicValue, 74 | want: "AttrDynamicValue", 75 | }, 76 | "tIndent": { 77 | typ: tIndent, 78 | want: "Indent", 79 | }, 80 | "tComment": { 81 | typ: tComment, 82 | want: "Comment", 83 | }, 84 | "tRubyComment": { 85 | typ: tRubyComment, 86 | want: "RubyComment", 87 | }, 88 | "tVoidTag": { 89 | typ: tVoidTag, 90 | want: "VoidTag", 91 | }, 92 | "tNukeInnerWhitespace": { 93 | typ: tNukeInnerWhitespace, 94 | want: "NukeInnerWhitespace", 95 | }, 96 | "tNukeOuterWhitespace": { 97 | typ: tNukeOuterWhitespace, 98 | want: "NukeOuterWhitespace", 99 | }, 100 | "tEscapedText": { 101 | typ: tEscapedText, 102 | want: "EscapedText", 103 | }, 104 | "tDynamicText": { 105 | typ: tDynamicText, 106 | want: "DynamicText", 107 | }, 108 | "tPlainText": { 109 | typ: tPlainText, 110 | want: "PlainText", 111 | }, 112 | "tPreserveText": { 113 | typ: tPreserveText, 114 | want: "PreserveText", 115 | }, 116 | "tUnescaped": { 117 | typ: tUnescaped, 118 | want: "Unescaped", 119 | }, 120 | "tScript": { 121 | typ: tScript, 122 | want: "Script", 123 | }, 124 | "tSilentScript": { 125 | typ: tSilentScript, 126 | want: "SilentScript", 127 | }, 128 | "tRenderCommand": { 129 | typ: tRenderCommand, 130 | want: "RenderCommand", 131 | }, 132 | "tChildrenCommand": { 133 | typ: tChildrenCommand, 134 | want: "ChildrenCommand", 135 | }, 136 | "tAttributesCommand": { 137 | typ: tAttributesCommand, 138 | want: "AttributesCommand", 139 | }, 140 | "tFilterStart": { 141 | typ: tFilterStart, 142 | want: "FilterStart", 143 | }, 144 | "tFilterEnd": { 145 | typ: tFilterEnd, 146 | want: "FilterEnd", 147 | }, 148 | "tUnknown": { 149 | typ: tokenType(999), 150 | want: "!Unknown!", 151 | }, 152 | } 153 | 154 | for name, tt := range tests { 155 | t.Run(name, func(t *testing.T) { 156 | got := tt.typ.String() 157 | if got != tt.want { 158 | t.Errorf("got %q, want %q", got, tt.want) 159 | } 160 | }) 161 | } 162 | } 163 | 164 | func Test_TokenString(t *testing.T) { 165 | tests := map[string]struct { 166 | token token 167 | want string 168 | }{ 169 | "short": { 170 | token: token{typ: tGoCode, lit: "short", line: 0, col: 0}, 171 | want: "GoCode[0:0]: \"short\"", 172 | }, 173 | "long": { 174 | token: token{typ: tGoCode, lit: "func HomePage(props HomePageProps) {", line: 10, col: 20}, 175 | want: "GoCode[10:20]: \"func HomePage(props HomePagePr...\"", 176 | }, 177 | } 178 | for name, tt := range tests { 179 | t.Run(name, func(t *testing.T) { 180 | got := tt.token.String() 181 | if got != tt.want { 182 | t.Errorf("got %q, want %q", got, tt.want) 183 | } 184 | }) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /compiler/utils.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | type stack[T any] []T 4 | 5 | func (s *stack[T]) push(item T) { 6 | *s = append(*s, item) 7 | } 8 | 9 | func (s *stack[T]) pop() (item T) { 10 | if len(*s) == 0 { 11 | return 12 | } 13 | item = (*s)[len(*s)-1] 14 | *s = (*s)[:len(*s)-1] 15 | return item 16 | } 17 | 18 | func (s *stack[T]) peek() (item T) { 19 | if len(*s) == 0 { 20 | return 21 | } 22 | return (*s)[len(*s)-1] 23 | } 24 | 25 | type OrderedMap[T any] struct { 26 | keys []string 27 | values map[string]T 28 | } 29 | 30 | func NewOrderedMap[T any]() *OrderedMap[T] { 31 | return &OrderedMap[T]{ 32 | keys: []string{}, 33 | values: map[string]T{}, 34 | } 35 | } 36 | 37 | func (m *OrderedMap[T]) Len() int { 38 | return len(m.keys) 39 | } 40 | 41 | func (m *OrderedMap[T]) Set(key string, value T) { 42 | if _, ok := m.values[key]; ok { 43 | m.values[key] = value 44 | return 45 | } 46 | m.keys = append(m.keys, key) 47 | m.values[key] = value 48 | } 49 | 50 | func (m *OrderedMap[T]) Get(key string) (value T, ok bool) { 51 | value, ok = m.values[key] 52 | return 53 | } 54 | 55 | func (m *OrderedMap[T]) Delete(key string) { 56 | delete(m.values, key) 57 | for i, k := range m.keys { 58 | if k == key { 59 | m.keys = append(m.keys[:i], m.keys[i+1:]...) 60 | return 61 | } 62 | } 63 | } 64 | 65 | func (m *OrderedMap[T]) Range(f func(key string, value T) (bool, error)) error { 66 | for _, key := range m.keys { 67 | if ok, err := f(key, m.values[key]); err != nil { 68 | return err 69 | } else if !ok { 70 | break 71 | } 72 | } 73 | 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /docs/goht_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackus/goht/83ec781290f01db90bb84f996ad3b8d4c1819f73/docs/goht_header.png -------------------------------------------------------------------------------- /docs/goht_header_html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackus/goht/83ec781290f01db90bb84f996ad3b8d4c1819f73/docs/goht_header_html.png -------------------------------------------------------------------------------- /docs/vscode_ide_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackus/goht/83ec781290f01db90bb84f996ad3b8d4c1819f73/docs/vscode_ide_example.png -------------------------------------------------------------------------------- /examples/attributes/additional.goht: -------------------------------------------------------------------------------- 1 | package attributes 2 | 3 | // Additional attributes may be added to an element using the `@attributes` 4 | // command. This command accepts a list of additional attributes in the 5 | // following formats: 6 | // - A map[string]bool - Any attribute with a true value is added as is. 7 | // - A map[string]string - Any attribute with a non-empty string value is 8 | // added as is. 9 | 10 | var boolAttrs = map[string]bool{ 11 | "disabled": true, 12 | "checked": false, 13 | } 14 | var strAttrs = map[string]string{ 15 | "type": "checkbox", 16 | "value": "foo", 17 | } 18 | 19 | @goht AttributesCmd() { 20 | %input{ 21 | @attributes: #{boolAttrs, strAttrs}, 22 | } 23 | } 24 | 25 | @haml HamlAttributesCmd() { 26 | %input{ 27 | @attributes: #{boolAttrs, strAttrs}, 28 | } 29 | } 30 | 31 | @slim SlimAttributesCmd() { 32 | input{ 33 | @attributes: #{boolAttrs, strAttrs}, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/attributes/additional.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package attributes 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | // Additional attributes may be added to an element using the `@attributes` 11 | // command. This command accepts a list of additional attributes in the 12 | // following formats: 13 | // - A map[string]bool - Any attribute with a true value is added as is. 14 | // - A map[string]string - Any attribute with a non-empty string value is 15 | // added as is. 16 | 17 | var boolAttrs = map[string]bool{ 18 | "disabled": true, 19 | "checked": false, 20 | } 21 | var strAttrs = map[string]string{ 22 | "type": "checkbox", 23 | "value": "foo", 24 | } 25 | 26 | func AttributesCmd() goht.Template { 27 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 28 | __buf, __isBuf := __w.(goht.Buffer) 29 | if !__isBuf { 30 | __buf = goht.GetBuffer() 31 | defer goht.ReleaseBuffer(__buf) 32 | } 33 | var __children goht.Template 34 | ctx, __children = goht.PopChildren(ctx) 35 | _ = __children 36 | if _, __err = __buf.WriteString(""); __err != nil { 48 | return 49 | } 50 | if !__isBuf { 51 | _, __err = __w.Write(__buf.Bytes()) 52 | } 53 | return 54 | }) 55 | } 56 | 57 | func HamlAttributesCmd() goht.Template { 58 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 59 | __buf, __isBuf := __w.(goht.Buffer) 60 | if !__isBuf { 61 | __buf = goht.GetBuffer() 62 | defer goht.ReleaseBuffer(__buf) 63 | } 64 | var __children goht.Template 65 | ctx, __children = goht.PopChildren(ctx) 66 | _ = __children 67 | if _, __err = __buf.WriteString(""); __err != nil { 79 | return 80 | } 81 | if !__isBuf { 82 | _, __err = __w.Write(__buf.Bytes()) 83 | } 84 | return 85 | }) 86 | } 87 | 88 | func SlimAttributesCmd() goht.Template { 89 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 90 | __buf, __isBuf := __w.(goht.Buffer) 91 | if !__isBuf { 92 | __buf = goht.GetBuffer() 93 | defer goht.ReleaseBuffer(__buf) 94 | } 95 | var __children goht.Template 96 | ctx, __children = goht.PopChildren(ctx) 97 | _ = __children 98 | if _, __err = __buf.WriteString("\n"); __err != nil { 110 | return 111 | } 112 | if !__isBuf { 113 | _, __err = __w.Write(__buf.Bytes()) 114 | } 115 | return 116 | }) 117 | } 118 | -------------------------------------------------------------------------------- /examples/attributes/classes.goht: -------------------------------------------------------------------------------- 1 | package attributes 2 | 3 | // The class attribute is a bit special. You will often find yourself 4 | // working with not one, but several classes. This is why repeating 5 | // use of the class `.` operator is allowed. You may also run into 6 | // situations where you want to add a class conditionally or want to 7 | // assign a number of classes all at once. 8 | // If you provide a dynamic value to the class attribute, it will be 9 | // interpreted as a parameter list. 10 | // The types of parameters that are allowed are: 11 | // - `string` - will be added as a class if it is not blank 12 | // - `[]string` - each non-blank string will be added as a class 13 | // - `map[string]bool` - each key with a true value will be added 14 | // If you have any dynamic sources for a class, from an object 15 | // reference, or from the class attribute, they will be merged and 16 | // deduplicated. 17 | // If you have all static values for your classes, then they are 18 | // rendered as-is avoiding any extra processing. 19 | 20 | var myClassList = []string{"foo", "bar"} 21 | var myOptionalClasses = map[string]bool{ 22 | "baz": true, 23 | "qux": false, 24 | } 25 | 26 | @goht Classes() { 27 | %p.fizz.buzz{class: #{myClassList, myOptionalClasses}} 28 | } 29 | 30 | @haml HamlClasses() { 31 | %p.fizz.buzz{class: #{myClassList, myOptionalClasses}} 32 | } 33 | 34 | @slim SlimClasses() { 35 | p.fizz.buzz{class: #{myClassList, myOptionalClasses}} 36 | } 37 | -------------------------------------------------------------------------------- /examples/attributes/classes.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package attributes 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | // The class attribute is a bit special. You will often find yourself 11 | // working with not one, but several classes. This is why repeating 12 | // use of the class `.` operator is allowed. You may also run into 13 | // situations where you want to add a class conditionally or want to 14 | // assign a number of classes all at once. 15 | // If you provide a dynamic value to the class attribute, it will be 16 | // interpreted as a parameter list. 17 | // The types of parameters that are allowed are: 18 | // - `string` - will be added as a class if it is not blank 19 | // - `[]string` - each non-blank string will be added as a class 20 | // - `map[string]bool` - each key with a true value will be added 21 | // If you have any dynamic sources for a class, from an object 22 | // reference, or from the class attribute, they will be merged and 23 | // deduplicated. 24 | // If you have all static values for your classes, then they are 25 | // rendered as-is avoiding any extra processing. 26 | 27 | var myClassList = []string{"foo", "bar"} 28 | var myOptionalClasses = map[string]bool{ 29 | "baz": true, 30 | "qux": false, 31 | } 32 | 33 | func Classes() goht.Template { 34 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 35 | __buf, __isBuf := __w.(goht.Buffer) 36 | if !__isBuf { 37 | __buf = goht.GetBuffer() 38 | defer goht.ReleaseBuffer(__buf) 39 | } 40 | var __children goht.Template 41 | ctx, __children = goht.PopChildren(ctx) 42 | _ = __children 43 | if _, __err = __buf.WriteString("

\n"); __err != nil { 55 | return 56 | } 57 | if !__isBuf { 58 | _, __err = __w.Write(__buf.Bytes()) 59 | } 60 | return 61 | }) 62 | } 63 | 64 | func HamlClasses() goht.Template { 65 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 66 | __buf, __isBuf := __w.(goht.Buffer) 67 | if !__isBuf { 68 | __buf = goht.GetBuffer() 69 | defer goht.ReleaseBuffer(__buf) 70 | } 71 | var __children goht.Template 72 | ctx, __children = goht.PopChildren(ctx) 73 | _ = __children 74 | if _, __err = __buf.WriteString("

\n"); __err != nil { 86 | return 87 | } 88 | if !__isBuf { 89 | _, __err = __w.Write(__buf.Bytes()) 90 | } 91 | return 92 | }) 93 | } 94 | 95 | func SlimClasses() goht.Template { 96 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 97 | __buf, __isBuf := __w.(goht.Buffer) 98 | if !__isBuf { 99 | __buf = goht.GetBuffer() 100 | defer goht.ReleaseBuffer(__buf) 101 | } 102 | var __children goht.Template 103 | ctx, __children = goht.PopChildren(ctx) 104 | _ = __children 105 | if _, __err = __buf.WriteString("

\n"); __err != nil { 117 | return 118 | } 119 | if !__isBuf { 120 | _, __err = __w.Write(__buf.Bytes()) 121 | } 122 | return 123 | }) 124 | } 125 | -------------------------------------------------------------------------------- /examples/attributes/general.goht: -------------------------------------------------------------------------------- 1 | package attributes 2 | 3 | // Goht supports the Ruby 1.9 hash style of attributes. The other styles 4 | // such as HTML style, or Ruby rocket style are not supported. This should 5 | // not be a problem as the Ruby 1.9 style is very similar to the style used 6 | // by Go for maps. 7 | 8 | @goht StaticAttrs() { 9 | %p{class: "foo", id: "bar"} This is a paragraph. 10 | } 11 | 12 | @haml HamlStaticAttrs() { 13 | %p{class: "foo", id: "bar"} This is a paragraph. 14 | } 15 | 16 | @slim SlimStaticAttrs() { 17 | p{class: "foo", id: "bar"} This is a paragraph. 18 | } 19 | 20 | // You can also use dynamic values for your attributes. Dynamic attribute 21 | // values share the same syntax as the interpolated values. A hash and a 22 | // pair of curly braces. 23 | 24 | var myDynamicValue = "foo" 25 | 26 | @goht DynamicAttrs() { 27 | %p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. 28 | } 29 | 30 | @haml HamlDynamicAttrs() { 31 | %p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. 32 | } 33 | 34 | @slim SlimDynamicAttrs() { 35 | p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. 36 | } 37 | 38 | // There are times when you have a lot of attributes and you want to keep 39 | // your lines short. You can break up your attributes into multiple lines 40 | // without any additional syntax. 41 | // You may include a comma after the last attribute if you wish but it is 42 | // not required. 43 | 44 | @goht MultilineAttrs() { 45 | %p{ 46 | class: #{myDynamicValue}, 47 | id: "bar", 48 | } This is a paragraph. 49 | } 50 | 51 | @haml HamlMultilineAttrs() { 52 | %p{ 53 | class: #{myDynamicValue}, 54 | id: "bar", 55 | } This is a paragraph. 56 | } 57 | 58 | @slim SlimMultilineAttrs() { 59 | p{ 60 | class: #{myDynamicValue}, 61 | id: "bar", 62 | } This is a paragraph. 63 | } 64 | 65 | // You may include as much whitespace as you wish between the attribute, 66 | // operator, value, and attribute separator. The following are all valid. 67 | 68 | @goht WhitespaceAttrs() { 69 | %p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. 70 | %p{class:#{myDynamicValue},id:"bar"} This is a paragraph. 71 | %p{class: #{myDynamicValue},id: "bar"} This is a paragraph. 72 | %p{class :#{myDynamicValue}, id : "bar"} This is a paragraph. 73 | } 74 | 75 | @haml HamlWhitespaceAttrs() { 76 | %p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. 77 | %p{class:#{myDynamicValue},id:"bar"} This is a paragraph. 78 | %p{class: #{myDynamicValue},id: "bar"} This is a paragraph. 79 | %p{class :#{myDynamicValue}, id : "bar"} This is a paragraph. 80 | } 81 | 82 | @slim SlimWhitespaceAttrs() { 83 | p{class: #{myDynamicValue}, id: "bar"} This is a paragraph. 84 | p{class:#{myDynamicValue},id:"bar"} This is a paragraph. 85 | p{class: #{myDynamicValue},id: "bar"} This is a paragraph. 86 | p{class :#{myDynamicValue}, id : "bar"} This is a paragraph. 87 | } 88 | 89 | // The dynamic attribute values may also include formatting rules just like 90 | // the interpolated values. The attribute values are always evaluated as 91 | // strings and are always rendered inside double quotes in the final HTML. 92 | 93 | var intVar = 10 94 | 95 | @goht FormattedValue() { 96 | %textarea{rows: #{%d intVar}, cols: "80"} 97 | } 98 | 99 | @haml HamlFormattedValue() { 100 | %textarea{rows: #{%d intVar}, cols: "80"} 101 | } 102 | 103 | @slim SlimFormattedValue() { 104 | textarea{rows: #{%d intVar}, cols: "80"} 105 | } 106 | -------------------------------------------------------------------------------- /examples/attributes/names.goht: -------------------------------------------------------------------------------- 1 | package attributes 2 | 3 | // For most attribute names you can include the name in the list 4 | // of attributes just as you expect it to appear in the HTML. Names 5 | // that contain alphanumeric characters, dashes (-), and 6 | // underscores (_) are all acceptable as-is. 7 | 8 | @goht SimpleNames() { 9 | %a{ 10 | href: "https://github.com/stackus/goht", 11 | data-foo: "bar", 12 | odd_name: "baz", 13 | _: "I'm a _hyperscript attribute!" 14 | } Goht 15 | } 16 | 17 | @haml HamlSimpleNames() { 18 | %a{ 19 | href: "https://github.com/stackus/goht", 20 | data-foo: "bar", 21 | odd_name: "baz", 22 | _: "I'm a _hyperscript attribute!" 23 | } Goht 24 | } 25 | 26 | @slim SlimSimpleNames() { 27 | a{ 28 | href: "https://github.com/stackus/goht", 29 | data-foo: "bar", 30 | odd_name: "baz", 31 | _: "I'm a _hyperscript attribute!" 32 | } Goht 33 | } 34 | 35 | // For more complex names, such as data attributes, you can use 36 | // enclose the name in in double quotes or backticks. 37 | // - Names that start with an at sign (@). 38 | // - Names that contain a colon (:). 39 | // - Names that contain a question mark (?). 40 | // The names will be rendered into the HTML without the quotes. 41 | 42 | @goht ComplexNames() { 43 | %a{ 44 | href: "https://github.com/stackus/goht", 45 | `:class`: "show ? '' : 'hidden'", 46 | `@click`: "show = !show", 47 | } Goht 48 | } 49 | 50 | @haml HamlComplexNames() { 51 | %a{ 52 | href: "https://github.com/stackus/goht", 53 | `:class`: "show ? '' : 'hidden'", 54 | `@click`: "show = !show", 55 | } Goht 56 | } 57 | 58 | @slim SlimComplexNames() { 59 | a{ 60 | href: "https://github.com/stackus/goht", 61 | `:class`: "show ? '' : 'hidden'", 62 | `@click`: "show = !show", 63 | } Goht 64 | } 65 | -------------------------------------------------------------------------------- /examples/attributes/optional.goht: -------------------------------------------------------------------------------- 1 | package attributes 2 | 3 | // Attributes may conditionally appear in the output. For example, you may 4 | // want to add the `disabled` attribute to a button if a variable is true. 5 | // When the same variable is false, you want the attribute to be omitted. 6 | // A special operator `?` is used to conditionally add an attribute. It is 7 | // used in place of the colon. 8 | // A dynamic attribute value that evaluates to a boolean must be used with 9 | // the conditional operator. This means that boolean values, not strings, 10 | // are expected. Statements such as `#{true}` or `#{foo == "bar"}` are 11 | // valid too. 12 | 13 | var disabled = true 14 | 15 | var foo = "bar" 16 | 17 | @goht ConditionalAttrs() { 18 | %button{disabled? #{disabled}} Click me! 19 | %button{disabled? #{foo == "bar"}} Click me! 20 | } 21 | 22 | @haml HamlConditionalAttrs() { 23 | %button{disabled? #{disabled}} Click me! 24 | %button{disabled? #{foo == "bar"}} Click me! 25 | } 26 | 27 | @slim SlimConditionalAttrs() { 28 | button{disabled? #{disabled}} Click me! 29 | button{disabled? #{foo == "bar"}} Click me! 30 | } 31 | -------------------------------------------------------------------------------- /examples/attributes/optional.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package attributes 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | // Attributes may conditionally appear in the output. For example, you may 11 | // want to add the `disabled` attribute to a button if a variable is true. 12 | // When the same variable is false, you want the attribute to be omitted. 13 | // A special operator `?` is used to conditionally add an attribute. It is 14 | // used in place of the colon. 15 | // A dynamic attribute value that evaluates to a boolean must be used with 16 | // the conditional operator. This means that boolean values, not strings, 17 | // are expected. Statements such as `#{true}` or `#{foo == "bar"}` are 18 | // valid too. 19 | 20 | var disabled = true 21 | 22 | var foo = "bar" 23 | 24 | func ConditionalAttrs() goht.Template { 25 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 26 | __buf, __isBuf := __w.(goht.Buffer) 27 | if !__isBuf { 28 | __buf = goht.GetBuffer() 29 | defer goht.ReleaseBuffer(__buf) 30 | } 31 | var __children goht.Template 32 | ctx, __children = goht.PopChildren(ctx) 33 | _ = __children 34 | if _, __err = __buf.WriteString("Click me!\nClick me!\n"); __err != nil { 51 | return 52 | } 53 | if !__isBuf { 54 | _, __err = __w.Write(__buf.Bytes()) 55 | } 56 | return 57 | }) 58 | } 59 | 60 | func HamlConditionalAttrs() goht.Template { 61 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 62 | __buf, __isBuf := __w.(goht.Buffer) 63 | if !__isBuf { 64 | __buf = goht.GetBuffer() 65 | defer goht.ReleaseBuffer(__buf) 66 | } 67 | var __children goht.Template 68 | ctx, __children = goht.PopChildren(ctx) 69 | _ = __children 70 | if _, __err = __buf.WriteString("Click me!\nClick me!\n"); __err != nil { 87 | return 88 | } 89 | if !__isBuf { 90 | _, __err = __w.Write(__buf.Bytes()) 91 | } 92 | return 93 | }) 94 | } 95 | 96 | func SlimConditionalAttrs() goht.Template { 97 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 98 | __buf, __isBuf := __w.(goht.Buffer) 99 | if !__isBuf { 100 | __buf = goht.GetBuffer() 101 | defer goht.ReleaseBuffer(__buf) 102 | } 103 | var __children goht.Template 104 | ctx, __children = goht.PopChildren(ctx) 105 | _ = __children 106 | if _, __err = __buf.WriteString("Click me!Click me!\n"); __err != nil { 123 | return 124 | } 125 | if !__isBuf { 126 | _, __err = __w.Write(__buf.Bytes()) 127 | } 128 | return 129 | }) 130 | } 131 | -------------------------------------------------------------------------------- /examples/commands/children.goht: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | // Any template can be included into another template; assuming that 4 | // you have not created a circular reference, this will not cause the 5 | // compiler to loop but will instead cause the generated code to 6 | // run into errors. 7 | // When you are creating a template that will be included into another 8 | // template, you can use the `@children` command to indicate where any 9 | // optional content from the calling template should be inserted. 10 | // The `@children` command is used in combination with the rendering 11 | // code syntax `=`. 12 | 13 | @goht ChildrenExample() { 14 | %p 15 | The following was passed in from the calling template: 16 | = @children 17 | } 18 | 19 | @haml HamlChildrenExample() { 20 | %p 21 | The following was passed in from the calling template: 22 | = @children 23 | } 24 | 25 | @slim SlimChildrenExample() { 26 | p 27 | The following was passed in from the calling template: 28 | = @children 29 | } 30 | -------------------------------------------------------------------------------- /examples/commands/children.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package commands 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | // Any template can be included into another template; assuming that 11 | // you have not created a circular reference, this will not cause the 12 | // compiler to loop but will instead cause the generated code to 13 | // run into errors. 14 | // When you are creating a template that will be included into another 15 | // template, you can use the `@children` command to indicate where any 16 | // optional content from the calling template should be inserted. 17 | // The `@children` command is used in combination with the rendering 18 | // code syntax `=`. 19 | 20 | func ChildrenExample() goht.Template { 21 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 22 | __buf, __isBuf := __w.(goht.Buffer) 23 | if !__isBuf { 24 | __buf = goht.GetBuffer() 25 | defer goht.ReleaseBuffer(__buf) 26 | } 27 | var __children goht.Template 28 | ctx, __children = goht.PopChildren(ctx) 29 | _ = __children 30 | if _, __err = __buf.WriteString("

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

\n"); __err != nil { 37 | return 38 | } 39 | if !__isBuf { 40 | _, __err = __w.Write(__buf.Bytes()) 41 | } 42 | return 43 | }) 44 | } 45 | 46 | func HamlChildrenExample() goht.Template { 47 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 48 | __buf, __isBuf := __w.(goht.Buffer) 49 | if !__isBuf { 50 | __buf = goht.GetBuffer() 51 | defer goht.ReleaseBuffer(__buf) 52 | } 53 | var __children goht.Template 54 | ctx, __children = goht.PopChildren(ctx) 55 | _ = __children 56 | if _, __err = __buf.WriteString("

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

\n"); __err != nil { 63 | return 64 | } 65 | if !__isBuf { 66 | _, __err = __w.Write(__buf.Bytes()) 67 | } 68 | return 69 | }) 70 | } 71 | 72 | func SlimChildrenExample() goht.Template { 73 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 74 | __buf, __isBuf := __w.(goht.Buffer) 75 | if !__isBuf { 76 | __buf = goht.GetBuffer() 77 | defer goht.ReleaseBuffer(__buf) 78 | } 79 | var __children goht.Template 80 | ctx, __children = goht.PopChildren(ctx) 81 | _ = __children 82 | if _, __err = __buf.WriteString("

following was passed in from the calling template:"); __err != nil { 83 | return 84 | } 85 | if __err = __children.Render(ctx, __buf); __err != nil { 86 | return 87 | } 88 | if _, __err = __buf.WriteString("

\n"); __err != nil { 89 | return 90 | } 91 | if !__isBuf { 92 | _, __err = __w.Write(__buf.Bytes()) 93 | } 94 | return 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /examples/commands/render.goht: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | // You include other templates using the `@render` command. It takes 4 | // the name of the template to render. 5 | // The `@render` command is used in combination with the rendering 6 | // code syntax `=`. 7 | 8 | @goht RenderExample() { 9 | %p= @render ChildrenExample() 10 | %p the other template was rendered above. 11 | } 12 | 13 | @haml HamlRenderExample() { 14 | %p= @render ChildrenExample() 15 | %p the other template was rendered above. 16 | } 17 | 18 | @slim SlimRenderExample() { 19 | p= @render ChildrenExample() 20 | p the other template was rendered above. 21 | } 22 | 23 | // You may also include nested content to be rendered by the template. 24 | // You do not need to include any opening or closing braces when you 25 | // are passing content on to be rendered by another template. 26 | 27 | @goht RenderWithChildrenExample() { 28 | %p The other template will be rendered below. 29 | = @render ChildrenExample() 30 | %span this content will be rendered by the other template. 31 | } 32 | 33 | @haml HamlRenderWithChildrenExample() { 34 | %p The other template will be rendered below. 35 | = @render ChildrenExample() 36 | %span this content will be rendered by the other template. 37 | } 38 | 39 | @slim SlimRenderWithChildrenExample() { 40 | p The other template will be rendered below. 41 | = @render ChildrenExample() 42 | span this content will be rendered by the other template. 43 | } 44 | -------------------------------------------------------------------------------- /examples/commands/slots.goht: -------------------------------------------------------------------------------- 1 | package commands 2 | // GoHT templates can be built with a slot based architecture. 3 | // This allows for a more modular approach to building templates. 4 | // 5 | // Any Template can be used as slotted content. 6 | // 7 | // The following two "Name" templates can be rendered like normal in the 8 | // application code with something like: 9 | // 10 | // err := AdminName("SuperAdminUser").Render(ctx, w) 11 | 12 | @haml Name(userName string) { 13 | .user 14 | %span= userName 15 | } 16 | 17 | @haml AdminName(adminName string) { 18 | .admin 19 | %span= adminName 20 | } 21 | 22 | // In your application code, you can also slot in either of the two "Name" 23 | // templates by passing in the template as an optional third argument to 24 | // the `Render` call for the main "parent" template. 25 | // 26 | // err := SlotTemplate().Render(ctx, w, 27 | // AdminName("SuperAdminUser").Slot("name"), 28 | // ) 29 | // 30 | // All we did was swap out a call to `Render()` with a call to `Slot()` to 31 | // reuse an existing template as a slotted template. We need to pass in the 32 | // name of the slot we want to use, which in this case is "name". 33 | // 34 | // The following SlotTemplate has no idea what content will ultimately be 35 | // passed in. If it receives a Template for its `name` slot, it will render the 36 | // templates content into that slot. 37 | 38 | // Slots can also have default content. A slot that does not receive any content 39 | // will not be rendered in the final output. However, if we provide default content 40 | // then we can have a fallback to use if no content is passed in. 41 | 42 | @slim SlotTemplate() { 43 | .name-content 44 | =@slot name 45 | .actions 46 | =@slot actions 47 | | No available actions. 48 | } 49 | 50 | @ego SlotWithDefaultTemplate() { 51 |
52 | <%@slot name { -%> 53 | No user name has been set. 54 | <%- } -%> 55 |
56 |
57 | <%@slot actions { -%> 58 | No actions have been set. 59 | <%- } -%> 60 |
61 | } 62 | 63 | // When you pass in a template to a slot, that template can also have its own 64 | // slots. This is done by adding one templates to the optional second parameter 65 | // to the `Slot` method. 66 | // 67 | // err := Layout(layoutProps).Render(ctx, w, 68 | // Sidebar(sidebarProps).Slot("sidebar"), 69 | // Header(headerProps).Slot("header"), 70 | // UserDetailsPage(userProps).Slot("main", 71 | // LastActionResults(resultsProps).Slot("notifications"), 72 | // ), 73 | // Footer(footerProps).Slot("footer"), 74 | // ) 75 | 76 | // 77 | // The above example shows how you can pass in a template to a slot, and that 78 | // template can also have its own slots. 79 | -------------------------------------------------------------------------------- /examples/comments/html.goht: -------------------------------------------------------------------------------- 1 | package comments 2 | 3 | // HTML comments can be included and will be added to the rendered 4 | // output. 5 | // HTML comments are added using the forward slash at the beginning 6 | // of the line 7 | 8 | @goht HtmlComments() { 9 | %p This is a paragraph 10 | / This is a HTML comment 11 | } 12 | 13 | @haml HamlHtmlComments() { 14 | %p This is a paragraph 15 | / This is a HTML comment 16 | } 17 | 18 | // HTML comments in the Slim syntax use "/!" to indicate the start of 19 | // the comment. 20 | 21 | @slim SlimHtmlComments() { 22 | p This is a paragraph 23 | /! This is a HTML comment 24 | } 25 | 26 | // You may also use them to comment out nested elements. This does 27 | // not stop the nested elements from being parsed, just from being 28 | // displayed. 29 | 30 | @goht HtmlCommentsNested() { 31 | %p This is a paragraph 32 | / 33 | %p This is a paragraph that is commented out 34 | } 35 | 36 | @haml HamlHtmlCommentsNested() { 37 | %p This is a paragraph 38 | / 39 | %p This is a paragraph that is commented out 40 | } 41 | 42 | @slim SlimHtmlCommentsNested() { 43 | p This is a paragraph 44 | /! 45 | p This is a paragraph that is commented out 46 | } 47 | -------------------------------------------------------------------------------- /examples/comments/html.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package comments 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | // HTML comments can be included and will be added to the rendered 11 | // output. 12 | // HTML comments are added using the forward slash at the beginning 13 | // of the line 14 | 15 | func HtmlComments() goht.Template { 16 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 17 | __buf, __isBuf := __w.(goht.Buffer) 18 | if !__isBuf { 19 | __buf = goht.GetBuffer() 20 | defer goht.ReleaseBuffer(__buf) 21 | } 22 | var __children goht.Template 23 | ctx, __children = goht.PopChildren(ctx) 24 | _ = __children 25 | if _, __err = __buf.WriteString("

This is a paragraph

\n\n"); __err != nil { 26 | return 27 | } 28 | if !__isBuf { 29 | _, __err = __w.Write(__buf.Bytes()) 30 | } 31 | return 32 | }) 33 | } 34 | 35 | func HamlHtmlComments() goht.Template { 36 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 37 | __buf, __isBuf := __w.(goht.Buffer) 38 | if !__isBuf { 39 | __buf = goht.GetBuffer() 40 | defer goht.ReleaseBuffer(__buf) 41 | } 42 | var __children goht.Template 43 | ctx, __children = goht.PopChildren(ctx) 44 | _ = __children 45 | if _, __err = __buf.WriteString("

This is a paragraph

\n\n"); __err != nil { 46 | return 47 | } 48 | if !__isBuf { 49 | _, __err = __w.Write(__buf.Bytes()) 50 | } 51 | return 52 | }) 53 | } 54 | 55 | // HTML comments in the Slim syntax use "/!" to indicate the start of 56 | // the comment. 57 | 58 | func SlimHtmlComments() goht.Template { 59 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 60 | __buf, __isBuf := __w.(goht.Buffer) 61 | if !__isBuf { 62 | __buf = goht.GetBuffer() 63 | defer goht.ReleaseBuffer(__buf) 64 | } 65 | var __children goht.Template 66 | ctx, __children = goht.PopChildren(ctx) 67 | _ = __children 68 | if _, __err = __buf.WriteString("

This is a paragraph

\n"); __err != nil { 69 | return 70 | } 71 | if !__isBuf { 72 | _, __err = __w.Write(__buf.Bytes()) 73 | } 74 | return 75 | }) 76 | } 77 | 78 | // You may also use them to comment out nested elements. This does 79 | // not stop the nested elements from being parsed, just from being 80 | // displayed. 81 | 82 | func HtmlCommentsNested() goht.Template { 83 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 84 | __buf, __isBuf := __w.(goht.Buffer) 85 | if !__isBuf { 86 | __buf = goht.GetBuffer() 87 | defer goht.ReleaseBuffer(__buf) 88 | } 89 | var __children goht.Template 90 | ctx, __children = goht.PopChildren(ctx) 91 | _ = __children 92 | if _, __err = __buf.WriteString("

This is a paragraph

\n\n"); __err != nil { 93 | return 94 | } 95 | if !__isBuf { 96 | _, __err = __w.Write(__buf.Bytes()) 97 | } 98 | return 99 | }) 100 | } 101 | 102 | func HamlHtmlCommentsNested() goht.Template { 103 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 104 | __buf, __isBuf := __w.(goht.Buffer) 105 | if !__isBuf { 106 | __buf = goht.GetBuffer() 107 | defer goht.ReleaseBuffer(__buf) 108 | } 109 | var __children goht.Template 110 | ctx, __children = goht.PopChildren(ctx) 111 | _ = __children 112 | if _, __err = __buf.WriteString("

This is a paragraph

\n\n"); __err != nil { 113 | return 114 | } 115 | if !__isBuf { 116 | _, __err = __w.Write(__buf.Bytes()) 117 | } 118 | return 119 | }) 120 | } 121 | 122 | func SlimHtmlCommentsNested() goht.Template { 123 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 124 | __buf, __isBuf := __w.(goht.Buffer) 125 | if !__isBuf { 126 | __buf = goht.GetBuffer() 127 | defer goht.ReleaseBuffer(__buf) 128 | } 129 | var __children goht.Template 130 | ctx, __children = goht.PopChildren(ctx) 131 | _ = __children 132 | if _, __err = __buf.WriteString("

This is a paragraph

\n"); __err != nil { 133 | return 134 | } 135 | if !__isBuf { 136 | _, __err = __w.Write(__buf.Bytes()) 137 | } 138 | return 139 | }) 140 | } 141 | -------------------------------------------------------------------------------- /examples/comments/rubystyle.goht: -------------------------------------------------------------------------------- 1 | package comments 2 | 3 | // You may use ruby style comments to completely remove a line or 4 | // even a block of nested elements. 5 | // This is accomplished by using the `-#` syntax. 6 | // This is useful for removing elements that are only used for 7 | // documentation purposes. 8 | // The nested elements commented out with this syntax will be 9 | // not the parsed by the compiler and will not be included in the 10 | // output. 11 | 12 | @goht RubyStyle() { 13 | %p This is the only paragraph in the output. 14 | -# %p This comment is removed from the output. 15 | } 16 | 17 | @haml HamlRubyStyle() { 18 | %p This is the only paragraph in the output. 19 | -# %p This comment is removed from the output. 20 | } 21 | 22 | // In the Slim syntax "RubyStyle" comments use a "/" to indicate the 23 | // start of the comment 24 | 25 | @slim SlimRubyStyle() { 26 | p This is the only paragraph in the output. 27 | / p This comment is removed from the output. 28 | } 29 | 30 | // Ruby style comments can comment nested content. 31 | 32 | @goht RubyStyleNested() { 33 | %p This is the only paragraph in the output. 34 | -# 35 | %p This paragraph is removed. 36 | %%% broken syntax is no problem. 37 | } 38 | 39 | @haml HamlRubyStyleNested() { 40 | %p This is the only paragraph in the output. 41 | -# 42 | %p This paragraph is removed. 43 | %%% broken syntax is no problem. 44 | } 45 | 46 | @slim SlimRubyStyleNested() { 47 | p This is the only paragraph in the output. 48 | / 49 | p This paragraph is removed. 50 | %%% broken syntax is no problem. 51 | } 52 | -------------------------------------------------------------------------------- /examples/comments/rubystyle.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package comments 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | // You may use ruby style comments to completely remove a line or 11 | // even a block of nested elements. 12 | // This is accomplished by using the `-#` syntax. 13 | // This is useful for removing elements that are only used for 14 | // documentation purposes. 15 | // The nested elements commented out with this syntax will be 16 | // not the parsed by the compiler and will not be included in the 17 | // output. 18 | 19 | func RubyStyle() goht.Template { 20 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 21 | __buf, __isBuf := __w.(goht.Buffer) 22 | if !__isBuf { 23 | __buf = goht.GetBuffer() 24 | defer goht.ReleaseBuffer(__buf) 25 | } 26 | var __children goht.Template 27 | ctx, __children = goht.PopChildren(ctx) 28 | _ = __children 29 | if _, __err = __buf.WriteString("

This is the only paragraph in the output.

\n"); __err != nil { 30 | return 31 | } 32 | if !__isBuf { 33 | _, __err = __w.Write(__buf.Bytes()) 34 | } 35 | return 36 | }) 37 | } 38 | 39 | func HamlRubyStyle() goht.Template { 40 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 41 | __buf, __isBuf := __w.(goht.Buffer) 42 | if !__isBuf { 43 | __buf = goht.GetBuffer() 44 | defer goht.ReleaseBuffer(__buf) 45 | } 46 | var __children goht.Template 47 | ctx, __children = goht.PopChildren(ctx) 48 | _ = __children 49 | if _, __err = __buf.WriteString("

This is the only paragraph in the output.

\n"); __err != nil { 50 | return 51 | } 52 | if !__isBuf { 53 | _, __err = __w.Write(__buf.Bytes()) 54 | } 55 | return 56 | }) 57 | } 58 | 59 | // In the Slim syntax "RubyStyle" comments use a "/" to indicate the 60 | // start of the comment 61 | 62 | func SlimRubyStyle() goht.Template { 63 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 64 | __buf, __isBuf := __w.(goht.Buffer) 65 | if !__isBuf { 66 | __buf = goht.GetBuffer() 67 | defer goht.ReleaseBuffer(__buf) 68 | } 69 | var __children goht.Template 70 | ctx, __children = goht.PopChildren(ctx) 71 | _ = __children 72 | if _, __err = __buf.WriteString("

This is the only paragraph in the output.

\n"); __err != nil { 73 | return 74 | } 75 | if !__isBuf { 76 | _, __err = __w.Write(__buf.Bytes()) 77 | } 78 | return 79 | }) 80 | } 81 | 82 | // Ruby style comments can comment nested content. 83 | 84 | func RubyStyleNested() goht.Template { 85 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 86 | __buf, __isBuf := __w.(goht.Buffer) 87 | if !__isBuf { 88 | __buf = goht.GetBuffer() 89 | defer goht.ReleaseBuffer(__buf) 90 | } 91 | var __children goht.Template 92 | ctx, __children = goht.PopChildren(ctx) 93 | _ = __children 94 | if _, __err = __buf.WriteString("

This is the only paragraph in the output.

\n"); __err != nil { 95 | return 96 | } 97 | if !__isBuf { 98 | _, __err = __w.Write(__buf.Bytes()) 99 | } 100 | return 101 | }) 102 | } 103 | 104 | func HamlRubyStyleNested() goht.Template { 105 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 106 | __buf, __isBuf := __w.(goht.Buffer) 107 | if !__isBuf { 108 | __buf = goht.GetBuffer() 109 | defer goht.ReleaseBuffer(__buf) 110 | } 111 | var __children goht.Template 112 | ctx, __children = goht.PopChildren(ctx) 113 | _ = __children 114 | if _, __err = __buf.WriteString("

This is the only paragraph in the output.

\n"); __err != nil { 115 | return 116 | } 117 | if !__isBuf { 118 | _, __err = __w.Write(__buf.Bytes()) 119 | } 120 | return 121 | }) 122 | } 123 | 124 | func SlimRubyStyleNested() goht.Template { 125 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 126 | __buf, __isBuf := __w.(goht.Buffer) 127 | if !__isBuf { 128 | __buf = goht.GetBuffer() 129 | defer goht.ReleaseBuffer(__buf) 130 | } 131 | var __children goht.Template 132 | ctx, __children = goht.PopChildren(ctx) 133 | _ = __children 134 | if _, __err = __buf.WriteString("

This is the only paragraph in the output.

\n"); __err != nil { 135 | return 136 | } 137 | if !__isBuf { 138 | _, __err = __w.Write(__buf.Bytes()) 139 | } 140 | return 141 | }) 142 | } 143 | -------------------------------------------------------------------------------- /examples/doctype/doctype.goht: -------------------------------------------------------------------------------- 1 | package doctype 2 | 3 | // Adds a doctype to the top of the page 4 | // HTML5 doctype is the default 5 | @goht Doctype() { 6 | !!! 7 | } 8 | 9 | @haml HamlDoctype() { 10 | !!! 11 | } 12 | 13 | @slim SlimDoctype() { 14 | doctype 15 | } 16 | -------------------------------------------------------------------------------- /examples/doctype/doctype.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package doctype 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | // Adds a doctype to the top of the page 11 | // HTML5 doctype is the default 12 | func Doctype() goht.Template { 13 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 14 | __buf, __isBuf := __w.(goht.Buffer) 15 | if !__isBuf { 16 | __buf = goht.GetBuffer() 17 | defer goht.ReleaseBuffer(__buf) 18 | } 19 | var __children goht.Template 20 | ctx, __children = goht.PopChildren(ctx) 21 | _ = __children 22 | if _, __err = __buf.WriteString("\n"); __err != nil { 23 | return 24 | } 25 | if !__isBuf { 26 | _, __err = __w.Write(__buf.Bytes()) 27 | } 28 | return 29 | }) 30 | } 31 | 32 | func HamlDoctype() goht.Template { 33 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 34 | __buf, __isBuf := __w.(goht.Buffer) 35 | if !__isBuf { 36 | __buf = goht.GetBuffer() 37 | defer goht.ReleaseBuffer(__buf) 38 | } 39 | var __children goht.Template 40 | ctx, __children = goht.PopChildren(ctx) 41 | _ = __children 42 | if _, __err = __buf.WriteString("\n"); __err != nil { 43 | return 44 | } 45 | if !__isBuf { 46 | _, __err = __w.Write(__buf.Bytes()) 47 | } 48 | return 49 | }) 50 | } 51 | 52 | func SlimDoctype() goht.Template { 53 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 54 | __buf, __isBuf := __w.(goht.Buffer) 55 | if !__isBuf { 56 | __buf = goht.GetBuffer() 57 | defer goht.ReleaseBuffer(__buf) 58 | } 59 | var __children goht.Template 60 | ctx, __children = goht.PopChildren(ctx) 61 | _ = __children 62 | if _, __err = __buf.WriteString("\n"); __err != nil { 63 | return 64 | } 65 | if !__isBuf { 66 | _, __err = __w.Write(__buf.Bytes()) 67 | } 68 | return 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /examples/filters/css.goht: -------------------------------------------------------------------------------- 1 | package filters 2 | 3 | // You can include CSS into your templates using the `css` filter. Inside 4 | // this filter you may include interpolated values. 5 | 6 | var color = "red" 7 | 8 | @goht Css() { 9 | :css 10 | .color { 11 | color: #{color}; 12 | } 13 | } 14 | 15 | @haml HamlCss() { 16 | :css 17 | .color { 18 | color: #{color}; 19 | } 20 | } 21 | 22 | @slim SlimCss() { 23 | :css 24 | .color { 25 | color: #{color}; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/filters/css.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package filters 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | // You can include CSS into your templates using the `css` filter. Inside 11 | // this filter you may include interpolated values. 12 | 13 | var color = "red" 14 | 15 | func Css() goht.Template { 16 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 17 | __buf, __isBuf := __w.(goht.Buffer) 18 | if !__isBuf { 19 | __buf = goht.GetBuffer() 20 | defer goht.ReleaseBuffer(__buf) 21 | } 22 | var __children goht.Template 23 | ctx, __children = goht.PopChildren(ctx) 24 | _ = __children 25 | if _, __err = __buf.WriteString(""); __err != nil { 36 | return 37 | } 38 | if !__isBuf { 39 | _, __err = __w.Write(__buf.Bytes()) 40 | } 41 | return 42 | }) 43 | } 44 | 45 | func HamlCss() goht.Template { 46 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 47 | __buf, __isBuf := __w.(goht.Buffer) 48 | if !__isBuf { 49 | __buf = goht.GetBuffer() 50 | defer goht.ReleaseBuffer(__buf) 51 | } 52 | var __children goht.Template 53 | ctx, __children = goht.PopChildren(ctx) 54 | _ = __children 55 | if _, __err = __buf.WriteString(""); __err != nil { 66 | return 67 | } 68 | if !__isBuf { 69 | _, __err = __w.Write(__buf.Bytes()) 70 | } 71 | return 72 | }) 73 | } 74 | 75 | func SlimCss() goht.Template { 76 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 77 | __buf, __isBuf := __w.(goht.Buffer) 78 | if !__isBuf { 79 | __buf = goht.GetBuffer() 80 | defer goht.ReleaseBuffer(__buf) 81 | } 82 | var __children goht.Template 83 | ctx, __children = goht.PopChildren(ctx) 84 | _ = __children 85 | if _, __err = __buf.WriteString("\n"); __err != nil { 96 | return 97 | } 98 | if !__isBuf { 99 | _, __err = __w.Write(__buf.Bytes()) 100 | } 101 | return 102 | }) 103 | } 104 | -------------------------------------------------------------------------------- /examples/filters/javascript.goht: -------------------------------------------------------------------------------- 1 | package filters 2 | 3 | // You can include JavaScript into your templates using the JavaScript 4 | // filter `:javascript`. You may include interpolation values within 5 | // the JavaScript code to have them replaced with values at render time. 6 | 7 | var name = "Bob" 8 | 9 | @goht JavaScript() { 10 | :javascript 11 | console.log("Hello #{name}!"); 12 | } 13 | 14 | @haml HamlJavaScript() { 15 | :javascript 16 | console.log("Hello #{name}!"); 17 | } 18 | 19 | @slim SlimJavaScript() { 20 | :javascript 21 | console.log("Hello #{name}!"); 22 | } 23 | -------------------------------------------------------------------------------- /examples/filters/javascript.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package filters 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | // You can include JavaScript into your templates using the JavaScript 11 | // filter `:javascript`. You may include interpolation values within 12 | // the JavaScript code to have them replaced with values at render time. 13 | 14 | var name = "Bob" 15 | 16 | func JavaScript() goht.Template { 17 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 18 | __buf, __isBuf := __w.(goht.Buffer) 19 | if !__isBuf { 20 | __buf = goht.GetBuffer() 21 | defer goht.ReleaseBuffer(__buf) 22 | } 23 | var __children goht.Template 24 | ctx, __children = goht.PopChildren(ctx) 25 | _ = __children 26 | if _, __err = __buf.WriteString(""); __err != nil { 37 | return 38 | } 39 | if !__isBuf { 40 | _, __err = __w.Write(__buf.Bytes()) 41 | } 42 | return 43 | }) 44 | } 45 | 46 | func HamlJavaScript() goht.Template { 47 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 48 | __buf, __isBuf := __w.(goht.Buffer) 49 | if !__isBuf { 50 | __buf = goht.GetBuffer() 51 | defer goht.ReleaseBuffer(__buf) 52 | } 53 | var __children goht.Template 54 | ctx, __children = goht.PopChildren(ctx) 55 | _ = __children 56 | if _, __err = __buf.WriteString(""); __err != nil { 67 | return 68 | } 69 | if !__isBuf { 70 | _, __err = __w.Write(__buf.Bytes()) 71 | } 72 | return 73 | }) 74 | } 75 | 76 | func SlimJavaScript() goht.Template { 77 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 78 | __buf, __isBuf := __w.(goht.Buffer) 79 | if !__isBuf { 80 | __buf = goht.GetBuffer() 81 | defer goht.ReleaseBuffer(__buf) 82 | } 83 | var __children goht.Template 84 | ctx, __children = goht.PopChildren(ctx) 85 | _ = __children 86 | if _, __err = __buf.WriteString("\n"); __err != nil { 97 | return 98 | } 99 | if !__isBuf { 100 | _, __err = __w.Write(__buf.Bytes()) 101 | } 102 | return 103 | }) 104 | } 105 | -------------------------------------------------------------------------------- /examples/filters/text.goht: -------------------------------------------------------------------------------- 1 | package filters 2 | 3 | // The `:plain` filter can be used to display a large amount of text 4 | // without any parsing. Lines may begin with Haml syntax and it will 5 | // be ignored. 6 | // Variable interpolation is still performed. 7 | 8 | @goht Plain() { 9 | %p 10 | :plain 11 | This is plain text. It
will
be displayed as HTML. 12 | #{"This
\"will\"
be interpolated with HTML intact."} 13 | } 14 | 15 | @haml HamlPlain() { 16 | %p 17 | :plain 18 | This is plain text. It
will
be displayed as HTML. 19 | #{"This
\"will\"
be interpolated with HTML intact."} 20 | } 21 | 22 | @goht Escaped() { 23 | %p 24 | :escaped 25 | This is escaped text. It
will not
be displayed as HTML. 26 | #{"This
\"will not\"
be interpolated with HTML intact."} 27 | } 28 | 29 | @haml HamlEscaped() { 30 | %p 31 | :escaped 32 | This is escaped text. It
will not
be displayed as HTML. 33 | #{"This
\"will not\"
be interpolated with HTML intact."} 34 | } 35 | 36 | @goht Preserve() { 37 | %p 38 | :preserve 39 | This is preserved text. It
will
be displayed as HTML. 40 | #{"This
\"will\"
be interpolated with HTML intact."} 41 | } 42 | 43 | @haml HamlPreserve() { 44 | %p 45 | :preserve 46 | This is preserved text. It
will
be displayed as HTML. 47 | #{"This
\"will\"
be interpolated with HTML intact."} 48 | } 49 | -------------------------------------------------------------------------------- /examples/go/code.goht: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | // You may include any Go code outside the Goht templates. It will 4 | // be included in the generated file as-is. 5 | // The included code, or any Go code can then be called from within the 6 | // Goht templates using the various code calling syntax's. 7 | 8 | func sayHello() string { 9 | return "Hello, world!" 10 | } 11 | 12 | @goht ExecuteCode() { 13 | - foo := sayHello() 14 | %p= foo 15 | } 16 | 17 | @haml HamlExecuteCode() { 18 | - foo := sayHello() 19 | %p= foo 20 | } 21 | 22 | @slim SlimExecuteCode() { 23 | - foo := sayHello() 24 | p= foo 25 | } 26 | 27 | @goht RenderCode() { 28 | %p= sayHello() 29 | } 30 | 31 | @haml HamlRenderCode() { 32 | %p= sayHello() 33 | } 34 | 35 | @slim SlimRenderCode() { 36 | p= sayHello() 37 | } 38 | -------------------------------------------------------------------------------- /examples/go/doc.goht: -------------------------------------------------------------------------------- 1 | // Package example is an examples package for the Goht language. 2 | package example 3 | 4 | @goht Doc() { 5 | .doc An example of package documentation. 6 | } 7 | 8 | @haml HamlDoc() { 9 | .doc An example of package documentation. 10 | } 11 | 12 | @slim SlimDoc() { 13 | .doc An example of package documentation. 14 | } 15 | -------------------------------------------------------------------------------- /examples/go/doc.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package example 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | // Package example is an examples package for the Goht language. 11 | 12 | func Doc() goht.Template { 13 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 14 | __buf, __isBuf := __w.(goht.Buffer) 15 | if !__isBuf { 16 | __buf = goht.GetBuffer() 17 | defer goht.ReleaseBuffer(__buf) 18 | } 19 | var __children goht.Template 20 | ctx, __children = goht.PopChildren(ctx) 21 | _ = __children 22 | if _, __err = __buf.WriteString("
An example of package documentation.
\n"); __err != nil { 23 | return 24 | } 25 | if !__isBuf { 26 | _, __err = __w.Write(__buf.Bytes()) 27 | } 28 | return 29 | }) 30 | } 31 | 32 | func HamlDoc() goht.Template { 33 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 34 | __buf, __isBuf := __w.(goht.Buffer) 35 | if !__isBuf { 36 | __buf = goht.GetBuffer() 37 | defer goht.ReleaseBuffer(__buf) 38 | } 39 | var __children goht.Template 40 | ctx, __children = goht.PopChildren(ctx) 41 | _ = __children 42 | if _, __err = __buf.WriteString("
An example of package documentation.
\n"); __err != nil { 43 | return 44 | } 45 | if !__isBuf { 46 | _, __err = __w.Write(__buf.Bytes()) 47 | } 48 | return 49 | }) 50 | } 51 | 52 | func SlimDoc() goht.Template { 53 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 54 | __buf, __isBuf := __w.(goht.Buffer) 55 | if !__isBuf { 56 | __buf = goht.GetBuffer() 57 | defer goht.ReleaseBuffer(__buf) 58 | } 59 | var __children goht.Template 60 | ctx, __children = goht.PopChildren(ctx) 61 | _ = __children 62 | if _, __err = __buf.WriteString("
An example of package documentation.
\n"); __err != nil { 63 | return 64 | } 65 | if !__isBuf { 66 | _, __err = __w.Write(__buf.Bytes()) 67 | } 68 | return 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /examples/go/imports.goht: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import "strings" 4 | 5 | import ( 6 | fmt "fmt" 7 | ) 8 | 9 | // Just like in Go files you can specify imports that are needed 10 | // by your Go code or Go code inlined into the Goht template. 11 | // Any imports that you specify here will be combined with the 12 | // imports used by the Goht compiler itself. Duplicate imports 13 | // will be removed. 14 | 15 | @goht ImportExample() { 16 | %p= fmt.Sprintf("Hello, %s!", strings.TrimSuffix("World!", "!")) 17 | } 18 | 19 | @haml HamlImportExample() { 20 | %p= fmt.Sprintf("Hello, %s!", strings.TrimSuffix("World!", "!")) 21 | } 22 | 23 | @slim SlimImportExample() { 24 | p= fmt.Sprintf("Hello, %s!", strings.TrimSuffix("World!", "!")) 25 | } 26 | -------------------------------------------------------------------------------- /examples/go/imports.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package example 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | import ( 10 | fmt "fmt" 11 | "strings" 12 | ) 13 | 14 | // Just like in Go files you can specify imports that are needed 15 | // by your Go code or Go code inlined into the Goht template. 16 | // Any imports that you specify here will be combined with the 17 | // imports used by the Goht compiler itself. Duplicate imports 18 | // will be removed. 19 | 20 | func ImportExample() goht.Template { 21 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 22 | __buf, __isBuf := __w.(goht.Buffer) 23 | if !__isBuf { 24 | __buf = goht.GetBuffer() 25 | defer goht.ReleaseBuffer(__buf) 26 | } 27 | var __children goht.Template 28 | ctx, __children = goht.PopChildren(ctx) 29 | _ = __children 30 | if _, __err = __buf.WriteString("

"); __err != nil { 31 | return 32 | } 33 | var __var1 string 34 | if __var1, __err = goht.CaptureErrors(goht.EscapeString(fmt.Sprintf("Hello, %s!", strings.TrimSuffix("World!", "!")))); __err != nil { 35 | return 36 | } 37 | if _, __err = __buf.WriteString(__var1); __err != nil { 38 | return 39 | } 40 | if _, __err = __buf.WriteString("

\n"); __err != nil { 41 | return 42 | } 43 | if !__isBuf { 44 | _, __err = __w.Write(__buf.Bytes()) 45 | } 46 | return 47 | }) 48 | } 49 | 50 | func HamlImportExample() goht.Template { 51 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 52 | __buf, __isBuf := __w.(goht.Buffer) 53 | if !__isBuf { 54 | __buf = goht.GetBuffer() 55 | defer goht.ReleaseBuffer(__buf) 56 | } 57 | var __children goht.Template 58 | ctx, __children = goht.PopChildren(ctx) 59 | _ = __children 60 | if _, __err = __buf.WriteString("

"); __err != nil { 61 | return 62 | } 63 | var __var1 string 64 | if __var1, __err = goht.CaptureErrors(goht.EscapeString(fmt.Sprintf("Hello, %s!", strings.TrimSuffix("World!", "!")))); __err != nil { 65 | return 66 | } 67 | if _, __err = __buf.WriteString(__var1); __err != nil { 68 | return 69 | } 70 | if _, __err = __buf.WriteString("

\n"); __err != nil { 71 | return 72 | } 73 | if !__isBuf { 74 | _, __err = __w.Write(__buf.Bytes()) 75 | } 76 | return 77 | }) 78 | } 79 | 80 | func SlimImportExample() goht.Template { 81 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 82 | __buf, __isBuf := __w.(goht.Buffer) 83 | if !__isBuf { 84 | __buf = goht.GetBuffer() 85 | defer goht.ReleaseBuffer(__buf) 86 | } 87 | var __children goht.Template 88 | ctx, __children = goht.PopChildren(ctx) 89 | _ = __children 90 | if _, __err = __buf.WriteString("

"); __err != nil { 91 | return 92 | } 93 | var __var1 string 94 | if __var1, __err = goht.CaptureErrors(goht.EscapeString(fmt.Sprintf("Hello, %s!", strings.TrimSuffix("World!", "!")))); __err != nil { 95 | return 96 | } 97 | if _, __err = __buf.WriteString(__var1); __err != nil { 98 | return 99 | } 100 | if _, __err = __buf.WriteString("

\n"); __err != nil { 101 | return 102 | } 103 | if !__isBuf { 104 | _, __err = __w.Write(__buf.Bytes()) 105 | } 106 | return 107 | }) 108 | } 109 | -------------------------------------------------------------------------------- /examples/go/inlining.goht: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // You can include Go code to handle conditional and loop statements 8 | // in your Goht templates. 9 | 10 | var isAdmin = true 11 | 12 | @goht Conditional() { 13 | .actions 14 | - if isAdmin { 15 | %button< 16 | Edit content 17 | - } else { 18 | %button Login 19 | - } 20 | %button View content 21 | } 22 | 23 | @haml HamlConditional() { 24 | .actions 25 | - if isAdmin { 26 | %button< 27 | Edit content 28 | - } else { 29 | %button Login 30 | - } 31 | %button View content 32 | } 33 | 34 | @slim SlimConditional() { 35 | .actions 36 | - if isAdmin { 37 | button 38 | Edit content 39 | - } else { 40 | button Login 41 | - } 42 | button View content 43 | } 44 | 45 | // However, we are using Haml and so we're into shortcuts. We can 46 | // continue to write out the brackets or we can use the shorthand 47 | // syntax. 48 | // The short hand form simply drops the opening and closing brackets 49 | // for statements that wrap around children. 50 | // Shorthand statements include: 51 | // for, if, else, else if, switch 52 | 53 | @goht ShorthandConditional() { 54 | .actions 55 | - if isAdmin 56 | %button< 57 | Edit content 58 | - else 59 | %button Login 60 | %button View content 61 | } 62 | 63 | @haml HamlShorthandConditional() { 64 | .actions 65 | - if isAdmin 66 | %button< 67 | Edit content 68 | - else 69 | %button Login 70 | %button View content 71 | } 72 | 73 | @slim SlimShorthandConditional() { 74 | .actions 75 | - if isAdmin 76 | button 77 | |Edit content 78 | - else 79 | button Login 80 | button View content 81 | } 82 | 83 | // With a switch statement, we can use the case and default keywords 84 | // but we will need to nest these statements if we're using the 85 | // shorthand syntax. (win some, lose some) 86 | 87 | @goht ShorthandSwitch() { 88 | .actions 89 | - switch isAdmin 90 | - case true: 91 | %button< 92 | Edit content 93 | - case false: 94 | %button Login 95 | %button View content 96 | } 97 | 98 | @haml HamlShorthandSwitch() { 99 | .actions 100 | - switch isAdmin 101 | - case true: 102 | %button< 103 | Edit content 104 | - case false: 105 | %button Login 106 | %button View content 107 | } 108 | 109 | @slim SlimShorthandSwitch() { 110 | .actions 111 | - switch isAdmin 112 | - case true: 113 | button 114 | |Edit content 115 | - case false: 116 | button Login 117 | button View content 118 | } 119 | 120 | // Haml supported splitting long code lines across multiple lines 121 | // using a comma. Each line that continues the statement must be indented 122 | // one additional level. To keep continuing the statement, you can 123 | // end the line with a comma. 124 | 125 | @haml HamlLongStatement() { 126 | .actions 127 | - action := longType{\ 128 | title: "Edit content", 129 | actions: "Edit content", 130 | } 131 | = fmt.Sprintf("Title: %s", 132 | action.title, 133 | ) 134 | - if action.title == "Edit content"\ 135 | && action.actions == "Edit content" 136 | %p= action.actions 137 | } 138 | 139 | // Slim supports splitting the control code across multiple lines which 140 | // is useful for long statements. The additional lines must be indented 141 | // one additional level. 142 | // 143 | // Ending a statement with a backslash or a comma will let you break 144 | // long statements across multiple lines. 145 | 146 | type longType struct { 147 | title string 148 | actions string 149 | } 150 | 151 | @slim SlimLongStatement() { 152 | .actions 153 | - action := longType{\ 154 | title: "Edit content", 155 | actions: "Edit content", 156 | } 157 | p=action.actions 158 | = fmt.Sprintf("Title: %s", 159 | action.title, 160 | ) 161 | } 162 | -------------------------------------------------------------------------------- /examples/go/interpolation.goht: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | // Variables and calls to functions that return values can be interpolated 4 | // into the Goht templates. 5 | 6 | var someVar = "Hello" 7 | 8 | @goht InterpolateCode() { 9 | %p #{someVar}, World! 10 | } 11 | 12 | @haml HamlInterpolateCode() { 13 | %p #{someVar}, World! 14 | } 15 | 16 | @slim SlimInterpolateCode() { 17 | p #{someVar}, World! 18 | } 19 | 20 | // Interpolation is not done within Go code or within a string literal. 21 | @goht NoInterpolation() { 22 | %p Do the following; No interpolation is necessary. 23 | %p= someVar + ", World!" 24 | %p= "No interpolation is #{performed} here." 25 | } 26 | 27 | @haml HamlNoInterpolation() { 28 | %p Do the following; No interpolation is necessary. 29 | %p= someVar + ", World!" 30 | %p= "No interpolation is #{performed} here." 31 | } 32 | 33 | @slim SlimNoInterpolation() { 34 | p Do the following; No interpolation is necessary. 35 | p= someVar + ", World!" 36 | p= "No interpolation is #{performed} here." 37 | } 38 | 39 | // Because the interpolation and tag id share the same starting character, 40 | // a `#` you will need to escape the interpolation with a backslash when it 41 | // is the first character of a line. 42 | // This is only necessary when it is the first character of a line and not 43 | // when it is the first character of text following a tag. 44 | 45 | @goht EscapeInterpolation() { 46 | \#{someVar}, World! 47 | %p #{someVar}, World! 48 | } 49 | 50 | @haml HamlEscapeInterpolation() { 51 | \#{someVar}, World! 52 | %p #{someVar}, World! 53 | } 54 | 55 | // There are also times when you want to ignore the interpolation and just 56 | // print the text. This is also handled with the backslash. 57 | // This can be done at the start of a line, after a tag or even mid-text. 58 | // 59 | // You will need to use two backslashes to escape the interpolation when 60 | // it is at the start of a line. This is because the first backslash is 61 | // triggering the parser to not interpret the next character as any 62 | // kind of special character. 63 | // This is also how you would escape a tag, id, or class character at the 64 | // start of a line. 65 | // 66 | // All three of the following uses of "someVar" are not interpolated, and 67 | // will render as "#{someVar}" in the final HTML. 68 | 69 | @goht IgnoreInterpolation() { 70 | \\#{someVar}, World! 71 | %p 72 | \\#{someVar}, World! 73 | A greeting: \#{someVar}, World! 74 | \. this line begins with a period 75 | \# this line begins with a hash 76 | \% this line begins with a percent 77 | } 78 | 79 | @haml HamlIgnoreInterpolation() { 80 | \\#{someVar}, World! 81 | %p 82 | \\#{someVar}, World! 83 | A greeting: \#{someVar}, World! 84 | \. this line begins with a period 85 | \# this line begins with a hash 86 | \% this line begins with a percent 87 | } 88 | -------------------------------------------------------------------------------- /examples/go/package.goht: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | // Just like in a Go file, you may include a package statement at the 4 | // top of your file. This is optional, but if you do include it, it 5 | // must be the first line of the file. If you do not specify a package, 6 | // the generated code will use "main" as the package name. 7 | 8 | @goht PackageExample() { 9 | } 10 | -------------------------------------------------------------------------------- /examples/go/package.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package example 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | // Just like in a Go file, you may include a package statement at the 11 | // top of your file. This is optional, but if you do include it, it 12 | // must be the first line of the file. If you do not specify a package, 13 | // the generated code will use "main" as the package name. 14 | 15 | func PackageExample() goht.Template { 16 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 17 | __buf, __isBuf := __w.(goht.Buffer) 18 | if !__isBuf { 19 | __buf = goht.GetBuffer() 20 | defer goht.ReleaseBuffer(__buf) 21 | } 22 | var __children goht.Template 23 | ctx, __children = goht.PopChildren(ctx) 24 | _ = __children 25 | if !__isBuf { 26 | _, __err = __w.Write(__buf.Bytes()) 27 | } 28 | return 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /examples/go/receivers.goht: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | type User struct { 4 | Name string 5 | Age int 6 | } 7 | 8 | // GoHT templates can be created as methods on any type that allows for method definitions 9 | // 10 | // The following will create a Details method on the User type. It will be called like so: 11 | // 12 | // user := User{Name: "John", Age: 30} 13 | // user.Details().Render(ctx, w)) 14 | // 15 | // It can also be called on using the `@render` command in a GoHT template: 16 | // 17 | // @render u.Details() 18 | 19 | @goht (u User) Details() { 20 | .name User name: #{u.Name} 21 | .age 22 | User Age: 23 | !=%d u.Age 24 | } 25 | 26 | @haml (u User) HamlDetails() { 27 | .name User name: #{u.Name} 28 | .age 29 | User Age: 30 | !=%d u.Age 31 | } 32 | 33 | @slim (u User) SlimDetails() { 34 | .name User name: #{u.Name} 35 | .age 36 | |User Age: 37 | =%d u.Age 38 | } 39 | -------------------------------------------------------------------------------- /examples/go/receivers.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package example 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | type User struct { 11 | Name string 12 | Age int 13 | } 14 | 15 | // GoHT templates can be created as methods on any type that allows for method definitions 16 | // 17 | // The following will create a Details method on the User type. It will be called like so: 18 | // 19 | // user := User{Name: "John", Age: 30} 20 | // user.Details().Render(ctx, w)) 21 | // 22 | // It can also be called on using the `@render` command in a GoHT template: 23 | // 24 | // @render u.Details() 25 | 26 | func (u User) Details() goht.Template { 27 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 28 | __buf, __isBuf := __w.(goht.Buffer) 29 | if !__isBuf { 30 | __buf = goht.GetBuffer() 31 | defer goht.ReleaseBuffer(__buf) 32 | } 33 | var __children goht.Template 34 | ctx, __children = goht.PopChildren(ctx) 35 | _ = __children 36 | if _, __err = __buf.WriteString("
User name: "); __err != nil { 37 | return 38 | } 39 | var __var1 string 40 | if __var1, __err = goht.CaptureErrors(goht.EscapeString(u.Name)); __err != nil { 41 | return 42 | } 43 | if _, __err = __buf.WriteString(__var1); __err != nil { 44 | return 45 | } 46 | if _, __err = __buf.WriteString("
\n
\nUser Age:\n"); __err != nil { 47 | return 48 | } 49 | var __var2 string 50 | if __var2, __err = goht.CaptureErrors(goht.FormatString("%d", u.Age)); __err != nil { 51 | return 52 | } 53 | if _, __err = __buf.WriteString(__var2); __err != nil { 54 | return 55 | } 56 | if _, __err = __buf.WriteString("\n
\n"); __err != nil { 57 | return 58 | } 59 | if !__isBuf { 60 | _, __err = __w.Write(__buf.Bytes()) 61 | } 62 | return 63 | }) 64 | } 65 | 66 | func (u User) HamlDetails() goht.Template { 67 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 68 | __buf, __isBuf := __w.(goht.Buffer) 69 | if !__isBuf { 70 | __buf = goht.GetBuffer() 71 | defer goht.ReleaseBuffer(__buf) 72 | } 73 | var __children goht.Template 74 | ctx, __children = goht.PopChildren(ctx) 75 | _ = __children 76 | if _, __err = __buf.WriteString("
User name: "); __err != nil { 77 | return 78 | } 79 | var __var1 string 80 | if __var1, __err = goht.CaptureErrors(goht.EscapeString(u.Name)); __err != nil { 81 | return 82 | } 83 | if _, __err = __buf.WriteString(__var1); __err != nil { 84 | return 85 | } 86 | if _, __err = __buf.WriteString("
\n
\nUser Age:\n"); __err != nil { 87 | return 88 | } 89 | var __var2 string 90 | if __var2, __err = goht.CaptureErrors(goht.FormatString("%d", u.Age)); __err != nil { 91 | return 92 | } 93 | if _, __err = __buf.WriteString(__var2); __err != nil { 94 | return 95 | } 96 | if _, __err = __buf.WriteString("\n
\n"); __err != nil { 97 | return 98 | } 99 | if !__isBuf { 100 | _, __err = __w.Write(__buf.Bytes()) 101 | } 102 | return 103 | }) 104 | } 105 | 106 | func (u User) SlimDetails() goht.Template { 107 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 108 | __buf, __isBuf := __w.(goht.Buffer) 109 | if !__isBuf { 110 | __buf = goht.GetBuffer() 111 | defer goht.ReleaseBuffer(__buf) 112 | } 113 | var __children goht.Template 114 | ctx, __children = goht.PopChildren(ctx) 115 | _ = __children 116 | if _, __err = __buf.WriteString("
User name: "); __err != nil { 117 | return 118 | } 119 | var __var1 string 120 | if __var1, __err = goht.CaptureErrors(goht.EscapeString(u.Name)); __err != nil { 121 | return 122 | } 123 | if _, __err = __buf.WriteString(__var1); __err != nil { 124 | return 125 | } 126 | if _, __err = __buf.WriteString("
User Age:"); __err != nil { 127 | return 128 | } 129 | var __var2 string 130 | if __var2, __err = goht.CaptureErrors(goht.EscapeString(goht.FormatString("%d", u.Age))); __err != nil { 131 | return 132 | } 133 | if _, __err = __buf.WriteString(__var2); __err != nil { 134 | return 135 | } 136 | if _, __err = __buf.WriteString("
\n"); __err != nil { 137 | return 138 | } 139 | if !__isBuf { 140 | _, __err = __w.Write(__buf.Bytes()) 141 | } 142 | return 143 | }) 144 | } 145 | -------------------------------------------------------------------------------- /examples/hello/world.goht: -------------------------------------------------------------------------------- 1 | package hello 2 | // An example of several Haml features supported by Goht. 3 | 4 | var terms = []string{"foo", "bar", "baz", "fizz", "buzz", "quux"} 5 | 6 | @goht termsWrapper(term string) { 7 | %p=@children 8 | %p And it was passed in as well #{term} 9 | } 10 | 11 | @haml hamlTermsWrapper(term string) { 12 | %p=@children 13 | %p And it was passed in as well #{term} 14 | } 15 | 16 | @slim slimTermsWrapper(term string) { 17 | p=@children 18 | p And it was passed in as well #{term} 19 | } 20 | 21 | @goht World() { 22 | !!! 23 | %html{lang: "en"} 24 | %head 25 | %meta{charset: "utf-8"} 26 | %title Hello World 27 | :css 28 | body { 29 | color: white; 30 | font-family: sans-serif; 31 | background-color: #333; 32 | } 33 | .term { 34 | font-weight: bold; 35 | color: #99f; 36 | } 37 | %body 38 | %h1 Hello World 39 | %p the following will loop a slice of strings and will pass each string into a child template 40 | - for _, term := range terms 41 | =@render termsWrapper(term) 42 | %p.term= term 43 | } 44 | 45 | @haml HamlWorld() { 46 | !!! 47 | %html{lang: "en"} 48 | %head 49 | %meta{charset: "utf-8"} 50 | %title Hello World 51 | :css 52 | body { 53 | color: white; 54 | font-family: sans-serif; 55 | background-color: #333; 56 | } 57 | .term { 58 | font-weight: bold; 59 | color: #99f; 60 | } 61 | %body 62 | %h1 Hello World 63 | %div the following will loop a slice of strings and will pass each string into a child template 64 | - for _, term := range terms 65 | =@render hamlTermsWrapper(term) 66 | %p.term= term 67 | } 68 | 69 | @slim SlimWorld() { 70 | doctype 71 | html{lang: "en"} 72 | head 73 | meta{charset: "utf-8"} 74 | title Hello World 75 | :css 76 | body { 77 | color: white; 78 | font-family: sans-serif; 79 | background-color: #333; 80 | } 81 | .term { 82 | font-weight: bold; 83 | color: #99f; 84 | } 85 | body 86 | h1 Hello World 87 | p the following will loop a slice of strings and will pass each string into a child template 88 | - for _, term := range terms 89 | =@render slimTermsWrapper(term) 90 | p.term= term 91 | } 92 | 93 | @ego EgoWorld() { 94 | 95 | 96 | 97 | 98 | Hello World 99 | 110 | 111 | 112 |

Hello World

113 |

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

114 | <% for _, term := range terms { -%> 115 | <%@render termsWrapper(term) { -%> 116 |

<%= term %>

117 | <%- } -%> 118 | <%- } -%> 119 | 120 | 121 | } 122 | -------------------------------------------------------------------------------- /examples/indents/tabs.goht: -------------------------------------------------------------------------------- 1 | package indents 2 | 3 | // You may use tabs to indent the Haml templates. 4 | // Goht does not change how indenting works in Haml. If you want 5 | // to use tabs, you must use tabs consistently. 6 | // Do not mix tabs and spaces in a single template. You would be 7 | // asking for trouble IMO but you may use tabs in one template and 8 | // spaces in another. 9 | 10 | @goht UsingTabs() { 11 | !!! 12 | %html{lang: "en"} 13 | %head 14 | %meta{charset: "utf-8"} 15 | %title 16 | Hello World 17 | %body 18 | %h1 19 | Hello World 20 | %p 21 | Hello World 22 | } 23 | 24 | @haml HamlUsingTabs() { 25 | !!! 26 | %html{lang: "en"} 27 | %head 28 | %meta{charset: "utf-8"} 29 | %title 30 | Hello World 31 | %body 32 | %h1 33 | Hello World 34 | %p 35 | Hello World 36 | } 37 | 38 | @slim SlimUsingTabs() { 39 | doctype 40 | html{lang: "en"} 41 | head 42 | meta{charset: "utf-8"} 43 | title 44 | |Hello World 45 | body 46 | h1 47 | |Hello World 48 | p 49 | |Hello World 50 | } 51 | -------------------------------------------------------------------------------- /examples/indents/tabs.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package indents 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | // You may use tabs to indent the Haml templates. 11 | // Goht does not change how indenting works in Haml. If you want 12 | // to use tabs, you must use tabs consistently. 13 | // Do not mix tabs and spaces in a single template. You would be 14 | // asking for trouble IMO but you may use tabs in one template and 15 | // spaces in another. 16 | 17 | func UsingTabs() goht.Template { 18 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 19 | __buf, __isBuf := __w.(goht.Buffer) 20 | if !__isBuf { 21 | __buf = goht.GetBuffer() 22 | defer goht.ReleaseBuffer(__buf) 23 | } 24 | var __children goht.Template 25 | ctx, __children = goht.PopChildren(ctx) 26 | _ = __children 27 | 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 { 28 | return 29 | } 30 | if !__isBuf { 31 | _, __err = __w.Write(__buf.Bytes()) 32 | } 33 | return 34 | }) 35 | } 36 | 37 | func HamlUsingTabs() goht.Template { 38 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 39 | __buf, __isBuf := __w.(goht.Buffer) 40 | if !__isBuf { 41 | __buf = goht.GetBuffer() 42 | defer goht.ReleaseBuffer(__buf) 43 | } 44 | var __children goht.Template 45 | ctx, __children = goht.PopChildren(ctx) 46 | _ = __children 47 | 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 { 48 | return 49 | } 50 | if !__isBuf { 51 | _, __err = __w.Write(__buf.Bytes()) 52 | } 53 | return 54 | }) 55 | } 56 | 57 | func SlimUsingTabs() goht.Template { 58 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 59 | __buf, __isBuf := __w.(goht.Buffer) 60 | if !__isBuf { 61 | __buf = goht.GetBuffer() 62 | defer goht.ReleaseBuffer(__buf) 63 | } 64 | var __children goht.Template 65 | ctx, __children = goht.PopChildren(ctx) 66 | _ = __children 67 | if _, __err = __buf.WriteString("Hello World

Hello World

Hello World

\n"); __err != nil { 68 | return 69 | } 70 | if !__isBuf { 71 | _, __err = __w.Write(__buf.Bytes()) 72 | } 73 | return 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /examples/tags/general.goht: -------------------------------------------------------------------------------- 1 | package tags 2 | // You may specify the type of tag that you want created with `%`. 3 | 4 | @goht SpecifyTag() { 5 | %p This is a paragraph tag. 6 | %main This is a main tag. 7 | } 8 | 9 | @haml HamlSpecifyTag() { 10 | %p This is a paragraph tag. 11 | %main This is a main tag. 12 | } 13 | 14 | @slim SlimSpecifyTag() { 15 | p This is a paragraph tag. 16 | main This is a main tag. 17 | } 18 | 19 | // You may also let the tag default to a `div` when using the id or 20 | // class syntax's, `#` and `.` respectively. 21 | 22 | @goht DefaultToDivs() { 23 | #main This is a div tag with an id of `main`. 24 | .main This is a div tag with a class of `main`. 25 | } 26 | 27 | @haml HamlDefaultToDivs() { 28 | #main This is a div tag with an id of `main`. 29 | .main This is a div tag with a class of `main`. 30 | } 31 | 32 | @slim SlimDefaultToDivs() { 33 | #main This is a div tag with an id of `main`. 34 | .main This is a div tag with a class of `main`. 35 | } 36 | 37 | // The three may also be combined. The `%` must come first, followed 38 | // by either the `#` or `.`. The `#` and `.` may be in any order. 39 | 40 | @goht Combined() { 41 | %p#main This is a paragraph tag with an id of `main`. 42 | %main.main This is a main tag with a class of `main`. 43 | .main#main This is a div tag with an id and class of `main`. 44 | %p.main#main This is a paragraph tag with an id and class of `main`. 45 | } 46 | 47 | @haml HamlCombined() { 48 | %p#main This is a paragraph tag with an id of `main`. 49 | %main.main This is a main tag with a class of `main`. 50 | .main#main This is a div tag with an id and class of `main`. 51 | %p.main#main This is a paragraph tag with an id and class of `main`. 52 | } 53 | 54 | @slim SlimCombined() { 55 | p#main This is a paragraph tag with an id of `main`. 56 | main.main This is a main tag with a class of `main`. 57 | .main#main This is a div tag with an id and class of `main`. 58 | p.main#main This is a paragraph tag with an id and class of `main`. 59 | } 60 | 61 | // The class operator may be repeated to add multiple classes. 62 | // Repeating the id operator will result in the id being overwritten 63 | // but will not throw an error. 64 | 65 | @goht MultipleClasses() { 66 | .main.main2 This is a div tag with two classes, `main` and `main2`. 67 | #main#main2 This is a div tag with an id of `main2`. 68 | } 69 | 70 | @haml HamlMultipleClasses() { 71 | .main.main2 This is a div tag with two classes, `main` and `main2`. 72 | #main#main2 This is a div tag with an id of `main2`. 73 | } 74 | 75 | @slim SlimMultipleClasses() { 76 | .main.main2 This is a div tag with two classes, `main` and `main2`. 77 | #main#main2 This is a div tag with an id of `main2`. 78 | } 79 | -------------------------------------------------------------------------------- /examples/tags/inline.goht: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | // Slim allows you to inline tags for those times when you really 4 | // want to keep things concise. 5 | 6 | @slim SlimInlineTags() { 7 | ul 8 | li: a.first First Item 9 | li: a.second First Item 10 | li: a.third First Item 11 | } 12 | -------------------------------------------------------------------------------- /examples/tags/inline.goht.go: -------------------------------------------------------------------------------- 1 | // Code generated by GoHT v0.8.1 - DO NOT EDIT. 2 | // https://github.com/stackus/goht 3 | 4 | package tags 5 | 6 | import "context" 7 | import "io" 8 | import "github.com/stackus/goht" 9 | 10 | // Slim allows you to inline tags for those times when you really 11 | // want to keep things concise. 12 | 13 | func SlimInlineTags() goht.Template { 14 | return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) { 15 | __buf, __isBuf := __w.(goht.Buffer) 16 | if !__isBuf { 17 | __buf = goht.GetBuffer() 18 | defer goht.ReleaseBuffer(__buf) 19 | } 20 | var __children goht.Template 21 | ctx, __children = goht.PopChildren(ctx) 22 | _ = __children 23 | if _, __err = __buf.WriteString("\n"); __err != nil { 24 | return 25 | } 26 | if !__isBuf { 27 | _, __err = __w.Write(__buf.Bytes()) 28 | } 29 | return 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /examples/tags/objectreference.goht: -------------------------------------------------------------------------------- 1 | package tags 2 | // In HAML it is possible to include an object in the tag 3 | // declaration to make the addition of classes and ids easier. 4 | // This is done by using a pair of square brackets after the 5 | // tag, id, or class declaration. 6 | // 7 | // The object used will need to implement one or both of the 8 | // following methods: 9 | // - ObjectID() string 10 | // - ObjectClass() string 11 | 12 | type Foo struct { 13 | ID string 14 | } 15 | 16 | func (f *Foo) ObjectID() string { 17 | return f.ID 18 | } 19 | 20 | func (f *Foo) ObjectClass() string { 21 | return "foo" 22 | } 23 | 24 | // if obj below has the id "bar" then the output will be: 25 | //
Foo article
26 | 27 | @goht ObjectRefs(obj Foo) { 28 | %article[obj] Foo article 29 | } 30 | 31 | @haml HamlObjectRefs(obj Foo) { 32 | %article[obj] Foo article 33 | } 34 | 35 | // You may include a prefix to be used with the id and class. 36 | var prefixVar = "article" 37 | 38 | @goht PrefixedObjectRefs(obj Foo) { 39 | %article[obj, "prefix"] Foo article with id "prefix_foo_bar" and class "prefix_foo" 40 | %article[obj, prefixVar] Foo article with id "article_foo_bar" and class "article_foo" 41 | } 42 | 43 | @haml HamlPrefixedObjectRefs(obj Foo) { 44 | %article[obj, "prefix"] Foo article with id "prefix_foo_bar" and class "prefix_foo" 45 | %article[obj, prefixVar] Foo article with id "article_foo_bar" and class "article_foo" 46 | } 47 | -------------------------------------------------------------------------------- /examples/tags/selfclosing.goht: -------------------------------------------------------------------------------- 1 | package tags 2 | // There are several tags that are self closing, meaning they 3 | // don't have or require a closing tag. For example, the tag 4 | // is used to embed an image in an HTML document. It is self closing 5 | // and has no closing tag. 6 | // Self closing tags may not have any content nested inside them. 7 | // Nesting content inside a self closing tag will cause the parser 8 | // to display an error. 9 | 10 | @goht SelfClosing() { 11 | %img{src: "logo.png", alt: "logo"} 12 | %p 13 | A paragraph is not self closing. 14 | %img{src: "logo.png", alt: "logo"} 15 | } 16 | 17 | @haml HamlSelfClosing() { 18 | %img{src: "logo.png", alt: "logo"} 19 | %p 20 | A paragraph is not self closing. 21 | %img{src: "logo.png", alt: "logo"} 22 | } 23 | 24 | @slim SlimSelfClosing() { 25 | img{src: "logo.png", alt: "logo"} 26 | p 27 | |A paragraph is not self closing. 28 | img{src: "logo.png", alt: "logo"} 29 | } 30 | 31 | // You may also use the self closing tag syntax to create a tag 32 | // that is not self closing. This is useful for creating tags 33 | // that are not known by the parser. 34 | // This is done by adding a forward slash to the end of the tag. 35 | // Known tags: 36 | // "area", "base", "basefont", "br", "col", 37 | // "embed", "frame", "hr", "img", "input", 38 | // "isindex", "keygen", "link", "menuitem", 39 | // "meta", "param", "source", "track", "wbr", 40 | 41 | @goht AlsoSelfClosing() { 42 | %isNowSelfClosing/ 43 | } 44 | 45 | @haml HamlAlsoSelfClosing() { 46 | %isNowSelfClosing/ 47 | } 48 | 49 | @slim SlimAlsoSelfClosing() { 50 | isNowSelfClosing/ 51 | } 52 | -------------------------------------------------------------------------------- /examples/tags/whitespace.goht: -------------------------------------------------------------------------------- 1 | package tags 2 | // Whitespace will be added between tags when using Haml and between 3 | // tags and text if that text is on a new line after the tag. 4 | // 5 | // %p Some text 6 | // The above line will render as: 7 | //

Some text

8 | // 9 | // %p 10 | // Some text 11 | // The above line will render as: 12 | //

13 | // Some text 14 | //

15 | // 16 | // Whitespace can have subtle effects on the final output of the 17 | // rendered HTML, so it is important to understand how it works. 18 | 19 | @goht Whitespace() { 20 | %p This text has no whitespace between it and the tag. 21 | %p 22 | This text has whitespace between it and the tag. 23 | %p This tag has whitespace between it and the tag above. 24 | } 25 | 26 | @haml HamlWhitespace() { 27 | %p This text has no whitespace between it and the tag. 28 | %p 29 | This text has whitespace between it and the tag. 30 | %p This tag has whitespace between it and the tag above. 31 | } 32 | 33 | // Slim does not keep whitespace between tags by default. 34 | 35 | @slim SlimWhitespace() { 36 | p This text has no whitespace between it and the tag. 37 | p 38 | This text has NO whitespace between it and the tag. 39 | p This tag has NO whitespace between it and the tag above. 40 | } 41 | 42 | // You can control the whitespace that will be rendered between tags 43 | // in the final output by using the `>` and <` operators. 44 | // The `>` operator will remove all whitespace outside the tag, and 45 | // the `<` operator will remove all whitespace inside the tag. 46 | // These operators must be placed at the end of the tag but before 47 | // either the `!` or `=` operators. 48 | // You can use either or both of these operators on a tag, when using 49 | // both, the order does not matter. 50 | 51 | @goht RemoveWhitespace() { 52 | %p< 53 | This text has no whitespace between it and the parent tag. 54 | %p 55 | There is whitespace between this text and the parent tag. 56 | %p>< 57 | This text has no whitespace between it and the parent tag. 58 | There is also no whitespace between this tag and the sibling text above it. 59 | Finally, the tag has no whitespace between it and the outer tag. 60 | } 61 | 62 | @haml HamlRemoveWhitespace() { 63 | %p< 64 | This text has no whitespace between it and the parent tag. 65 | %p 66 | There is whitespace between this text and the parent tag. 67 | %p>< 68 | This text has no whitespace between it and the parent tag. 69 | There is also no whitespace between this tag and the sibling text above it. 70 | Finally, the tag has no whitespace between it and the outer tag. 71 | } 72 | 73 | // You can add whitespace between tags by using the `>` and `<` operators. 74 | // The `>` operator will add whitespace after the tag, and the `<` operator 75 | // will add whitespace before the tag. 76 | // 77 | // The operators must be placed at the end of the tag: 78 | // p> Text 79 | // p< Text 80 | // p<> Text 81 | 82 | @slim SlimAddWhitespace() { 83 | div> This tag has whitespace after it. 84 | div 85 | |There is whitespace between this text and the parent tag. 86 | p<> There is whitespace before and after this tag. 87 | } 88 | -------------------------------------------------------------------------------- /examples/testdata/ego/hello_world.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello World 6 | 17 | 18 | 19 |

Hello World

20 |

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

21 |

foo

22 |

And it was passed in as well foo

23 |

bar

24 |

And it was passed in as well bar

25 |

baz

26 |

And it was passed in as well baz

27 |

fizz

28 |

And it was passed in as well fizz

29 |

buzz

30 |

And it was passed in as well buzz

31 |

quux

32 |

And it was passed in as well quux

33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/testdata/haml/attributes_attributesCmd.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/testdata/haml/attributes_classes.html: -------------------------------------------------------------------------------- 1 |

2 | -------------------------------------------------------------------------------- /examples/testdata/haml/attributes_complexNames.html: -------------------------------------------------------------------------------- 1 | Goht 2 | -------------------------------------------------------------------------------- /examples/testdata/haml/attributes_conditionalAttrs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /examples/testdata/haml/attributes_dynamicAttrs.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph.

2 | -------------------------------------------------------------------------------- /examples/testdata/haml/attributes_formattedValue.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/testdata/haml/attributes_multilineAttrs.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph.

2 | -------------------------------------------------------------------------------- /examples/testdata/haml/attributes_simpleNames.html: -------------------------------------------------------------------------------- 1 | Goht 2 | -------------------------------------------------------------------------------- /examples/testdata/haml/attributes_staticAttrs.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph.

2 | -------------------------------------------------------------------------------- /examples/testdata/haml/attributes_whitespaceAttrs.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph.

2 |

This is a paragraph.

3 |

This is a paragraph.

4 |

This is a paragraph.

5 | -------------------------------------------------------------------------------- /examples/testdata/haml/commands_childrenExample.html: -------------------------------------------------------------------------------- 1 |

2 | The following was passed in from the calling template: 3 |

4 | -------------------------------------------------------------------------------- /examples/testdata/haml/commands_renderExample.html: -------------------------------------------------------------------------------- 1 |

2 | The following was passed in from the calling template: 3 |

4 |

5 |

the other template was rendered above.

6 | -------------------------------------------------------------------------------- /examples/testdata/haml/commands_renderWithChildrenExample.html: -------------------------------------------------------------------------------- 1 |

The other template will be rendered below.

2 |

3 | The following was passed in from the calling template: 4 | this content will be rendered by the other template. 5 |

6 | -------------------------------------------------------------------------------- /examples/testdata/haml/comments_htmlComments.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph

2 | 3 | -------------------------------------------------------------------------------- /examples/testdata/haml/comments_htmlCommentsNested.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph

2 | 5 | -------------------------------------------------------------------------------- /examples/testdata/haml/comments_rubyStyle.html: -------------------------------------------------------------------------------- 1 |

This is the only paragraph in the output.

2 | -------------------------------------------------------------------------------- /examples/testdata/haml/comments_rubyStyleNested.html: -------------------------------------------------------------------------------- 1 |

This is the only paragraph in the output.

2 | -------------------------------------------------------------------------------- /examples/testdata/haml/doctype_doctype.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/testdata/haml/example_conditional.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /examples/testdata/haml/example_doc.html: -------------------------------------------------------------------------------- 1 |
An example of package documentation.
2 | -------------------------------------------------------------------------------- /examples/testdata/haml/example_escapeInterpolation.html: -------------------------------------------------------------------------------- 1 | Hello, World! 2 |

Hello, World!

3 | -------------------------------------------------------------------------------- /examples/testdata/haml/example_executeCode.html: -------------------------------------------------------------------------------- 1 |

Hello, world!

2 | -------------------------------------------------------------------------------- /examples/testdata/haml/example_ignoreInterpolation.html: -------------------------------------------------------------------------------- 1 | #{someVar}, World! 2 |

3 | #{someVar}, World! 4 |

5 | A greeting: #{someVar}, World! 6 | . this line begins with a period 7 | # this line begins with a hash 8 | % this line begins with a percent 9 | -------------------------------------------------------------------------------- /examples/testdata/haml/example_importExample.html: -------------------------------------------------------------------------------- 1 |

Hello, World!

2 | -------------------------------------------------------------------------------- /examples/testdata/haml/example_interpolateCode.html: -------------------------------------------------------------------------------- 1 |

Hello, World!

2 | -------------------------------------------------------------------------------- /examples/testdata/haml/example_noInterpolation.html: -------------------------------------------------------------------------------- 1 |

Do the following; No interpolation is necessary.

2 |

Hello, World!

3 |

No interpolation is #{performed} here.

4 | -------------------------------------------------------------------------------- /examples/testdata/haml/example_packageExample.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackus/goht/83ec781290f01db90bb84f996ad3b8d4c1819f73/examples/testdata/haml/example_packageExample.html -------------------------------------------------------------------------------- /examples/testdata/haml/example_renderCode.html: -------------------------------------------------------------------------------- 1 |

Hello, world!

2 | -------------------------------------------------------------------------------- /examples/testdata/haml/example_shorthandConditional.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /examples/testdata/haml/example_shorthandSwitch.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /examples/testdata/haml/example_userDetails.html: -------------------------------------------------------------------------------- 1 |
User name: John
2 |
3 | User Age: 4 | 30 5 |
6 | -------------------------------------------------------------------------------- /examples/testdata/haml/filters_css.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/testdata/haml/filters_escaped.html: -------------------------------------------------------------------------------- 1 |

2 | This is escaped text. It <pre>will not</pre> be displayed as HTML. 3 | This <pre>"will not"</pre> be interpolated with HTML intact. 4 |

5 | -------------------------------------------------------------------------------- /examples/testdata/haml/filters_javascript.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/testdata/haml/filters_plain.html: -------------------------------------------------------------------------------- 1 |

2 | This is plain text. It

will
be displayed as HTML. 3 | This
"will"
be interpolated with HTML intact. 4 |

5 | -------------------------------------------------------------------------------- /examples/testdata/haml/filters_preserve.html: -------------------------------------------------------------------------------- 1 |

2 | This is preserved text. It

will
be displayed as HTML. This
"will"
be interpolated with HTML intact. 3 |

4 | -------------------------------------------------------------------------------- /examples/testdata/haml/formatting_boolExample.html: -------------------------------------------------------------------------------- 1 |

The bool is (true).

2 | -------------------------------------------------------------------------------- /examples/testdata/haml/formatting_floatExample.html: -------------------------------------------------------------------------------- 1 |

The float is (123.456000).

2 |

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

3 |

The float is (123.46) with 2 decimal places.

4 |

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

5 |

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

6 |

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

7 | -------------------------------------------------------------------------------- /examples/testdata/haml/formatting_intExample.html: -------------------------------------------------------------------------------- 1 |

The integer is (123).

2 |

The integer is (1111011) in binary.

3 |

The integer is (173) in octal.

4 |

The integer is (7b) in hex.

5 |

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

6 |

The integer is ({) as a character.

7 | -------------------------------------------------------------------------------- /examples/testdata/haml/formatting_stringExample.html: -------------------------------------------------------------------------------- 1 |

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

2 |

The string is ("Hello") quoted.

3 |

The string is (48656c6c6f) as hex.

4 |

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

5 |

The string is (Hello) as is.

6 |

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

7 |

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

8 |

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

9 |

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

10 | -------------------------------------------------------------------------------- /examples/testdata/haml/hello_world.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World 5 | 16 | 17 |

Hello World

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

foo

20 |

21 |

And it was passed in as well foo

22 |

bar

23 |

24 |

And it was passed in as well bar

25 |

baz

26 |

27 |

And it was passed in as well baz

28 |

fizz

29 |

30 |

And it was passed in as well fizz

31 |

buzz

32 |

33 |

And it was passed in as well buzz

34 |

quux

35 |

36 |

And it was passed in as well quux

37 | 38 | 39 | -------------------------------------------------------------------------------- /examples/testdata/haml/indents_usingTabs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello World 6 | 7 | 8 | 9 |

10 | Hello World 11 |

12 |

13 | Hello World 14 |

15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/testdata/haml/tags_alsoSelfClosing.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/testdata/haml/tags_combined.html: -------------------------------------------------------------------------------- 1 |

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

2 |
This is a main tag with a class of `main`.
3 |
This is a div tag with an id and class of `main`.
4 |

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

5 | -------------------------------------------------------------------------------- /examples/testdata/haml/tags_defaultToDivs.html: -------------------------------------------------------------------------------- 1 |
This is a div tag with an id of `main`.
2 |
This is a div tag with a class of `main`.
3 | -------------------------------------------------------------------------------- /examples/testdata/haml/tags_multipleClasses.html: -------------------------------------------------------------------------------- 1 |
This is a div tag with two classes, `main` and `main2`.
2 |
This is a div tag with an id of `main2`.
3 | -------------------------------------------------------------------------------- /examples/testdata/haml/tags_objectRefs.html: -------------------------------------------------------------------------------- 1 |
Foo article
2 | -------------------------------------------------------------------------------- /examples/testdata/haml/tags_prefixedObjectRefs.html: -------------------------------------------------------------------------------- 1 |
Foo article with id "prefix_foo_bar" and class "prefix_foo"
2 |
Foo article with id "article_foo_bar" and class "article_foo"
3 | -------------------------------------------------------------------------------- /examples/testdata/haml/tags_removeWhitespace.html: -------------------------------------------------------------------------------- 1 |

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

2 |

3 | There is whitespace between this text and the parent tag.

This text has no whitespace between it and the parent tag. 4 | There is also no whitespace between this tag and the sibling text above it. 5 | Finally, the tag has no whitespace between it and the outer tag.

6 | -------------------------------------------------------------------------------- /examples/testdata/haml/tags_selfClosing.html: -------------------------------------------------------------------------------- 1 | logo

2 | A paragraph is not self closing. 3 | logo

4 | -------------------------------------------------------------------------------- /examples/testdata/haml/tags_specifyTag.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph tag.

2 |
This is a main tag.
3 | -------------------------------------------------------------------------------- /examples/testdata/haml/tags_whitespace.html: -------------------------------------------------------------------------------- 1 |

This text has no whitespace between it and the tag.

2 |

3 | This text has whitespace between it and the tag. 4 |

This tag has whitespace between it and the tag above.

5 |

6 | -------------------------------------------------------------------------------- /examples/testdata/haml/unescape_unescapeCode.html: -------------------------------------------------------------------------------- 1 |

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

2 |

This is unescaped HTML.

3 | -------------------------------------------------------------------------------- /examples/testdata/haml/unescape_unescapeInterpolation.html: -------------------------------------------------------------------------------- 1 |

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

2 |

This is is NOT escaped.

3 | -------------------------------------------------------------------------------- /examples/testdata/haml/unescape_unescapeText.html: -------------------------------------------------------------------------------- 1 |

This is HTML.

2 |

This is HTML.

3 | -------------------------------------------------------------------------------- /examples/testdata/slim/attributes_attributesCmd.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/testdata/slim/attributes_classes.html: -------------------------------------------------------------------------------- 1 |

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/attributes_complexNames.html: -------------------------------------------------------------------------------- 1 | Goht 2 | -------------------------------------------------------------------------------- /examples/testdata/slim/attributes_conditionalAttrs.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/testdata/slim/attributes_dynamicAttrs.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph.

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/attributes_formattedValue.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/testdata/slim/attributes_multilineAttrs.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph.

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/attributes_simpleNames.html: -------------------------------------------------------------------------------- 1 | Goht 2 | -------------------------------------------------------------------------------- /examples/testdata/slim/attributes_staticAttrs.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph.

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/attributes_whitespaceAttrs.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph.

This is a paragraph.

This is a paragraph.

This is a paragraph.

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/commands_childrenExample.html: -------------------------------------------------------------------------------- 1 |

following was passed in from the calling template:

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/commands_renderExample.html: -------------------------------------------------------------------------------- 1 |

2 | The following was passed in from the calling template: 3 |

4 |

the other template was rendered above.

5 | -------------------------------------------------------------------------------- /examples/testdata/slim/commands_renderWithChildrenExample.html: -------------------------------------------------------------------------------- 1 |

The other template will be rendered below.

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

4 | 5 | -------------------------------------------------------------------------------- /examples/testdata/slim/comments_htmlComments.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/comments_htmlCommentsNested.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/comments_rubyStyle.html: -------------------------------------------------------------------------------- 1 |

This is the only paragraph in the output.

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/comments_rubyStyleNested.html: -------------------------------------------------------------------------------- 1 |

This is the only paragraph in the output.

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/doctype_doctype.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/testdata/slim/example_conditional.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /examples/testdata/slim/example_doc.html: -------------------------------------------------------------------------------- 1 |
An example of package documentation.
2 | -------------------------------------------------------------------------------- /examples/testdata/slim/example_escapeInterpolation.html: -------------------------------------------------------------------------------- 1 | Hello, World! 2 |

Hello, World!

3 | -------------------------------------------------------------------------------- /examples/testdata/slim/example_executeCode.html: -------------------------------------------------------------------------------- 1 |

Hello, world!

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/example_ignoreInterpolation.html: -------------------------------------------------------------------------------- 1 | #{someVar}, World! 2 |

3 | #{someVar}, World! 4 |

5 | A greeting: #{someVar}, World! 6 | . this line begins with a period 7 | # this line begins with a hash 8 | % this line begins with a percent 9 | -------------------------------------------------------------------------------- /examples/testdata/slim/example_importExample.html: -------------------------------------------------------------------------------- 1 |

Hello, World!

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/example_interpolateCode.html: -------------------------------------------------------------------------------- 1 |

Hello, World!

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/example_noInterpolation.html: -------------------------------------------------------------------------------- 1 |

Do the following; No interpolation is necessary.

Hello, World!

No interpolation is #{performed} here.

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/example_packageExample.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackus/goht/83ec781290f01db90bb84f996ad3b8d4c1819f73/examples/testdata/slim/example_packageExample.html -------------------------------------------------------------------------------- /examples/testdata/slim/example_renderCode.html: -------------------------------------------------------------------------------- 1 |

Hello, world!

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/example_shorthandConditional.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /examples/testdata/slim/example_shorthandSwitch.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /examples/testdata/slim/example_userDetails.html: -------------------------------------------------------------------------------- 1 |
User name: John
User Age:30
2 | -------------------------------------------------------------------------------- /examples/testdata/slim/filters_css.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /examples/testdata/slim/filters_javascript.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /examples/testdata/slim/formatting_boolExample.html: -------------------------------------------------------------------------------- 1 |

The bool is (true).

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/formatting_floatExample.html: -------------------------------------------------------------------------------- 1 |

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.

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/formatting_intExample.html: -------------------------------------------------------------------------------- 1 |

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.

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/formatting_stringExample.html: -------------------------------------------------------------------------------- 1 |

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.

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/hello_world.html: -------------------------------------------------------------------------------- 1 | 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

12 |

bar

And it was passed in as well bar

13 |

baz

And it was passed in as well baz

14 |

fizz

And it was passed in as well fizz

15 |

buzz

And it was passed in as well buzz

16 |

quux

And it was passed in as well quux

17 | 18 | -------------------------------------------------------------------------------- /examples/testdata/slim/indents_usingTabs.html: -------------------------------------------------------------------------------- 1 | Hello World

Hello World

Hello World

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/tags_addWhitespace.html: -------------------------------------------------------------------------------- 1 |
This tag has whitespace after it.
There is whitespace between this text and the parent tag.

There is whitespace before and after this tag.

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/tags_alsoSelfClosing.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/testdata/slim/tags_combined.html: -------------------------------------------------------------------------------- 1 |

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`.

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/tags_defaultToDivs.html: -------------------------------------------------------------------------------- 1 |
This is a div tag with an id of `main`.
This is a div tag with a class of `main`.
2 | -------------------------------------------------------------------------------- /examples/testdata/slim/tags_inlineTags.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/testdata/slim/tags_multipleClasses.html: -------------------------------------------------------------------------------- 1 |
This is a div tag with two classes, `main` and `main2`.
This is a div tag with an id of `main2`.
2 | -------------------------------------------------------------------------------- /examples/testdata/slim/tags_selfClosing.html: -------------------------------------------------------------------------------- 1 | logo

A paragraph is not self closing.logo

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/tags_specifyTag.html: -------------------------------------------------------------------------------- 1 |

This is a paragraph tag.

This is a main tag.
2 | -------------------------------------------------------------------------------- /examples/testdata/slim/tags_whitespace.html: -------------------------------------------------------------------------------- 1 |

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.

2 | -------------------------------------------------------------------------------- /examples/testdata/slim/unescape_unescapeCode.html: -------------------------------------------------------------------------------- 1 |

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

This is NOT escaped HTML.

2 | -------------------------------------------------------------------------------- /examples/unescaping/unescape.goht: -------------------------------------------------------------------------------- 1 | package unescape 2 | 3 | // The text from variables are assumed to need HTML escaping. If you 4 | // want to include raw HTML, or you have already processed the variable 5 | // then you will need to unescape the text. This is accomplished with 6 | // the `!` operator. 7 | // Use care when using the unescape operator. You should only use it 8 | // when you are sure that the text is safe. If you are unsure, then 9 | // you should use the default escaping. 10 | 11 | @goht UnescapeCode() { 12 | %p= "This is NOT unescaped HTML. (Ampersands everywhere!)" 13 | %p!= "This is unescaped HTML." 14 | } 15 | 16 | @haml HamlUnescapeCode() { 17 | %p= "This is NOT unescaped HTML. (Ampersands everywhere!)" 18 | %p!= "This is unescaped HTML." 19 | } 20 | 21 | @slim SlimUnescapeCode() { 22 | p= "This is is escaped HTML. (Ampersands everywhere!)" 23 | p== "This is NOT escaped HTML." 24 | } 25 | 26 | // It can also affect the interpolated values. 27 | 28 | @goht UnescapeInterpolation() { 29 | - var html = "is" 30 | %p This #{html} is escaped. (Ampersands everywhere!) 31 | %p! This #{html} is NOT escaped. 32 | } 33 | 34 | @haml HamlUnescapeInterpolation() { 35 | - var html = "is" 36 | %p This #{html} is escaped. (Ampersands everywhere!) 37 | %p! This #{html} is NOT escaped. 38 | } 39 | 40 | // The plain text that you write into your Goht templates will not be 41 | // altered by the addition of the `!` operator. It is expected that this 42 | // text has already been HTML escaped properly. 43 | 44 | @goht UnescapeText() { 45 | %p This is HTML. 46 | %p! This is HTML. 47 | } 48 | 49 | @haml HamlUnescapeText() { 50 | %p This is HTML. 51 | %p! This is HTML. 52 | } 53 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/stackus/goht 2 | 3 | go 1.21.4 4 | 5 | require ( 6 | github.com/cenkalti/backoff/v4 v4.2.1 7 | github.com/charmbracelet/log v0.3.1 8 | github.com/rs/zerolog v1.32.0 9 | github.com/sergi/go-diff v1.3.1 10 | github.com/spf13/cobra v1.8.0 11 | github.com/stackus/errors v0.1.5 12 | go.lsp.dev/jsonrpc2 v0.10.0 13 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 14 | ) 15 | 16 | require ( 17 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 18 | github.com/charmbracelet/lipgloss v0.9.1 // indirect 19 | github.com/go-logfmt/logfmt v0.6.0 // indirect 20 | github.com/golang/protobuf v1.4.2 // indirect 21 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 22 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 23 | github.com/mattn/go-colorable v0.1.13 // indirect 24 | github.com/mattn/go-isatty v0.0.19 // indirect 25 | github.com/mattn/go-runewidth v0.0.15 // indirect 26 | github.com/muesli/reflow v0.3.0 // indirect 27 | github.com/muesli/termenv v0.15.2 // indirect 28 | github.com/rivo/uniseg v0.2.0 // indirect 29 | github.com/segmentio/asm v1.1.3 // indirect 30 | github.com/segmentio/encoding v0.3.4 // indirect 31 | github.com/spf13/pflag v1.0.5 // indirect 32 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect 33 | golang.org/x/sys v0.13.0 // indirect 34 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 35 | google.golang.org/grpc v1.38.0 // indirect 36 | google.golang.org/protobuf v1.25.0 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /helpers.go: -------------------------------------------------------------------------------- 1 | package goht 2 | 3 | // If returns the trueValue if the condition is true, otherwise falseValue. 4 | func If(condition bool, trueValue, falseValue string) string { 5 | if condition { 6 | return trueValue 7 | } 8 | return falseValue 9 | } 10 | -------------------------------------------------------------------------------- /internal/logging/logged_stream.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "time" 8 | 9 | "github.com/rs/zerolog" 10 | "go.lsp.dev/jsonrpc2" 11 | ) 12 | 13 | // Majority of this logging has been lifted from: github.com/golang/tools/internal/lsp/protocol/log.go 14 | 15 | type LoggedStream struct { 16 | Label string 17 | jsonrpc2.Stream 18 | Logger zerolog.Logger 19 | } 20 | 21 | var _ jsonrpc2.Stream = (*LoggedStream)(nil) 22 | 23 | type req struct { 24 | method string 25 | start time.Time 26 | } 27 | 28 | type mapped struct { 29 | mu sync.Mutex 30 | clientCalls map[string]req 31 | serverCalls map[string]req 32 | } 33 | 34 | var maps = &mapped{ 35 | sync.Mutex{}, 36 | make(map[string]req), 37 | make(map[string]req), 38 | } 39 | 40 | func (s LoggedStream) logMsg(msg jsonrpc2.Message, isRead bool) { 41 | direction, pastTense := "Received", "Received" 42 | get, set := maps.client, maps.setServer 43 | if isRead { 44 | direction, pastTense = "Sending", "Sent" 45 | get, set = maps.server, maps.setClient 46 | } 47 | if msg == nil { 48 | return 49 | } 50 | tm := time.Now() 51 | 52 | var logMsg string 53 | args := map[string]any{} 54 | 55 | switch msg := msg.(type) { 56 | case *jsonrpc2.Call: 57 | id := fmt.Sprint(msg.ID()) 58 | logMsg = fmt.Sprintf("[%s] %s request '%s - (%s)'.", s.Label, direction, msg.Method(), id) 59 | args["params"] = string(msg.Params()) 60 | set(id, req{method: msg.Method(), start: tm}) 61 | case *jsonrpc2.Notification: 62 | logMsg = fmt.Sprintf("[%s] %s notification '%s'.", s.Label, direction, msg.Method()) 63 | args["params"] = string(msg.Params()) 64 | case *jsonrpc2.Response: 65 | id := fmt.Sprint(msg.ID()) 66 | if err := msg.Err(); err != nil { 67 | s.Logger.Error().Err(err).Msgf("[%s] %s #%s", s.Label, pastTense, id) 68 | return 69 | } 70 | cc := get(id) 71 | elapsed := tm.Sub(cc.start) 72 | logMsg = fmt.Sprintf("[%s] %s response '%s - (%s)' in %dms.", s.Label, direction, cc.method, id, elapsed/time.Millisecond) 73 | args["result"] = string(msg.Result()) 74 | } 75 | s.Logger.Info().Fields(args).Msg(logMsg) 76 | } 77 | 78 | func (s LoggedStream) Read(ctx context.Context) (jsonrpc2.Message, int64, error) { 79 | msg, count, err := s.Stream.Read(ctx) 80 | s.logMsg(msg, true) 81 | return msg, count, err 82 | } 83 | 84 | func (s LoggedStream) Write(ctx context.Context, msg jsonrpc2.Message) (int64, error) { 85 | count, err := s.Stream.Write(ctx, msg) 86 | s.logMsg(msg, false) 87 | return count, err 88 | } 89 | 90 | // these 4 methods are each used exactly once, but it seemed 91 | // better to have the encapsulation rather than ad hoc mutex 92 | // code in 4 places 93 | func (m *mapped) client(id string) req { 94 | m.mu.Lock() 95 | defer m.mu.Unlock() 96 | v := m.clientCalls[id] 97 | delete(m.clientCalls, id) 98 | return v 99 | } 100 | 101 | func (m *mapped) server(id string) req { 102 | m.mu.Lock() 103 | defer m.mu.Unlock() 104 | v := m.serverCalls[id] 105 | delete(m.serverCalls, id) 106 | return v 107 | } 108 | 109 | func (m *mapped) setClient(id string, r req) { 110 | m.mu.Lock() 111 | defer m.mu.Unlock() 112 | m.clientCalls[id] = r 113 | } 114 | 115 | func (m *mapped) setServer(id string, r req) { 116 | m.mu.Lock() 117 | defer m.mu.Unlock() 118 | m.serverCalls[id] = r 119 | } 120 | -------------------------------------------------------------------------------- /internal/logging/logger.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | 8 | "github.com/rs/zerolog" 9 | ) 10 | 11 | func NewLogger(w io.Writer, level zerolog.Level) zerolog.Logger { 12 | return zerolog.New(zerolog.ConsoleWriter{ 13 | Out: w, 14 | TimeFormat: "2006-01-02 15:04:05.000", 15 | FormatLevel: func(i any) string { 16 | return strings.ToUpper(fmt.Sprintf("[%-5s]", i)) 17 | }, 18 | FormatFieldName: func(i any) string { 19 | return fmt.Sprintf("\n\t%s:", i) 20 | }, 21 | NoColor: true, 22 | }).Level(level).With().Timestamp().Logger() 23 | } 24 | -------------------------------------------------------------------------------- /internal/protocol/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /internal/protocol/tsdocument_changes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package protocol 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | ) 11 | 12 | // DocumentChanges is a union of a file edit and directory rename operations 13 | // for package renaming feature. At most one field of this struct is non-nil. 14 | type DocumentChanges struct { 15 | TextDocumentEdit *TextDocumentEdit 16 | RenameFile *RenameFile 17 | } 18 | 19 | func (d *DocumentChanges) UnmarshalJSON(data []byte) error { 20 | var m map[string]interface{} 21 | 22 | if err := json.Unmarshal(data, &m); err != nil { 23 | return err 24 | } 25 | 26 | if _, ok := m["textDocument"]; ok { 27 | d.TextDocumentEdit = new(TextDocumentEdit) 28 | return json.Unmarshal(data, d.TextDocumentEdit) 29 | } 30 | 31 | d.RenameFile = new(RenameFile) 32 | return json.Unmarshal(data, d.RenameFile) 33 | } 34 | 35 | func (d *DocumentChanges) MarshalJSON() ([]byte, error) { 36 | if d.TextDocumentEdit != nil { 37 | return json.Marshal(d.TextDocumentEdit) 38 | } else if d.RenameFile != nil { 39 | return json.Marshal(d.RenameFile) 40 | } 41 | return nil, fmt.Errorf("Empty DocumentChanges union value") 42 | } 43 | -------------------------------------------------------------------------------- /internal/protocol/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package protocol 6 | 7 | import ( 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | // inDir checks whether path is in the file tree rooted at dir. 13 | // It checks only the lexical form of the file names. 14 | // It does not consider symbolic links. 15 | // 16 | // Copied from go/src/cmd/go/internal/search/search.go. 17 | func inDir(dir, path string) bool { 18 | pv := strings.ToUpper(filepath.VolumeName(path)) 19 | dv := strings.ToUpper(filepath.VolumeName(dir)) 20 | path = path[len(pv):] 21 | dir = dir[len(dv):] 22 | switch { 23 | default: 24 | return false 25 | case pv != dv: 26 | return false 27 | case len(path) == len(dir): 28 | if path == dir { 29 | return true 30 | } 31 | return false 32 | case dir == "": 33 | return path != "" 34 | case len(path) > len(dir): 35 | if dir[len(dir)-1] == filepath.Separator { 36 | if path[:len(dir)] == dir { 37 | return path[len(dir):] != "" 38 | } 39 | return false 40 | } 41 | if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir { 42 | if len(path) == len(dir)+1 { 43 | return true 44 | } 45 | return path[len(dir)+1:] != "" 46 | } 47 | return false 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /internal/proxy/client.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/rs/zerolog" 9 | 10 | "github.com/stackus/goht/compiler" 11 | "github.com/stackus/goht/internal/protocol" 12 | ) 13 | 14 | type Client struct { 15 | protocol.Client 16 | smc *SourceMapCache 17 | dc *DiagnosticsCache 18 | logger zerolog.Logger 19 | } 20 | 21 | var _ protocol.Client = (*Client)(nil) 22 | 23 | func NewClient(c protocol.Client, smc *SourceMapCache, dc *DiagnosticsCache, logger zerolog.Logger) *Client { 24 | return &Client{ 25 | Client: c, 26 | smc: smc, 27 | dc: dc, 28 | logger: logger, 29 | } 30 | } 31 | 32 | func (c *Client) PublishDiagnostics(ctx context.Context, params *protocol.PublishDiagnosticsParams) error { 33 | logger := c.logger.With(). 34 | Str("uri", string(params.URI)). 35 | Int("count", len(params.Diagnostics)). 36 | Logger() 37 | 38 | logger.Debug().Msg("SERVER -> CLIENT: PublishDiagnostics") 39 | 40 | _, gohtURI := toGohtURI(params.URI) 41 | sm, ok := c.smc.Get(string(gohtURI)) 42 | if !ok { 43 | c.logger.Warn().Msgf("unable to complete because the sourcemap for %q doesn't exist in the cache, has the didOpen notification been sent yet", gohtURI) 44 | return fmt.Errorf("unable to complete because the sourcemap for %q doesn't exist in the cache, has the didOpen notification been sent yet", gohtURI) 45 | } 46 | 47 | // update the uri to the original 48 | params.URI = gohtURI 49 | for i, diagnostic := range params.Diagnostics { 50 | var start, end compiler.Position 51 | start, ok = sm.SourcePositionFromTarget(int(diagnostic.Range.Start.Line), int(diagnostic.Range.Start.Character)) 52 | if !ok { 53 | logger.Warn().Any("diagnostic", diagnostic).Msg("unable to map start position") 54 | continue 55 | } 56 | 57 | if diagnostic.Range.Start.Line == diagnostic.Range.End.Line { 58 | length := diagnostic.Range.End.Character - diagnostic.Range.Start.Character 59 | diagnostic.Range.Start.Line = uint32(start.Line) 60 | diagnostic.Range.Start.Character = uint32(start.Col) 61 | diagnostic.Range.End.Line = uint32(start.Line) 62 | diagnostic.Range.End.Character = uint32(start.Col) + length 63 | params.Diagnostics[i] = diagnostic 64 | continue 65 | } 66 | 67 | end, ok = sm.SourcePositionFromTarget(int(diagnostic.Range.End.Line), int(diagnostic.Range.End.Character)) 68 | if !ok { 69 | logger.Warn().Any("diagnostic", diagnostic).Msg("unable to map end position") 70 | continue 71 | } 72 | 73 | diagnostic.Range.Start.Line = uint32(start.Line) 74 | diagnostic.Range.Start.Character = uint32(start.Col) 75 | diagnostic.Range.End.Line = uint32(end.Line) 76 | diagnostic.Range.End.Character = uint32(end.Col) 77 | params.Diagnostics[i] = diagnostic 78 | } 79 | params.Diagnostics = c.dc.WithGohtDiagnostics(string(gohtURI), params.Diagnostics) 80 | return c.Client.PublishDiagnostics(ctx, params) 81 | } 82 | 83 | const doNotEditMessage = "Do not edit this file!" 84 | 85 | func (c *Client) ShowMessage(ctx context.Context, params *protocol.ShowMessageParams) error { 86 | c.logger.Info().Str("message", params.Message).Msg("SERVER -> CLIENT: ShowMessage") 87 | // Why is this message ignored? 88 | // Because gopls will return a message to the client to not edit the file when it contains 89 | // a header with "DO NOT EDIT". We include such a header in our generated Go files because 90 | // 1. they are generated, and 2. we include them in the files gopls sees because that causes 91 | // gopls to not do certain things like checking the position of the cursor in the file. This 92 | // particular check is a problem because it causes gopls to throw "column is beyond end of line" 93 | // errors for just about any interaction the user has with their IDE. 94 | if strings.HasPrefix(params.Message, doNotEditMessage) { 95 | return nil 96 | } 97 | return c.Client.ShowMessage(ctx, params) 98 | } 99 | -------------------------------------------------------------------------------- /internal/proxy/diagnostics_cache.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/stackus/goht/internal/protocol" 7 | ) 8 | 9 | type DiagnosticsCache struct { 10 | gohtDiagnostics map[string][]protocol.Diagnostic 11 | goDiagnostics map[string][]protocol.Diagnostic 12 | mu sync.Mutex 13 | } 14 | 15 | func NewDiagnosticsCache() *DiagnosticsCache { 16 | return &DiagnosticsCache{ 17 | gohtDiagnostics: make(map[string][]protocol.Diagnostic), 18 | goDiagnostics: make(map[string][]protocol.Diagnostic), 19 | } 20 | } 21 | 22 | func (dc *DiagnosticsCache) WithGohtDiagnostics(uri string, diagnostics []protocol.Diagnostic) []protocol.Diagnostic { 23 | if diagnostics == nil { 24 | diagnostics = make([]protocol.Diagnostic, 0) 25 | } 26 | dc.mu.Lock() 27 | defer dc.mu.Unlock() 28 | dc.goDiagnostics[uri] = diagnostics 29 | if dc.gohtDiagnostics[uri] == nil { 30 | dc.gohtDiagnostics[uri] = make([]protocol.Diagnostic, 0) 31 | } 32 | return append(dc.gohtDiagnostics[uri], diagnostics...) 33 | } 34 | 35 | func (dc *DiagnosticsCache) WithGoDiagnostics(uri string, diagnostics []protocol.Diagnostic) []protocol.Diagnostic { 36 | if diagnostics == nil { 37 | diagnostics = make([]protocol.Diagnostic, 0) 38 | } 39 | dc.mu.Lock() 40 | defer dc.mu.Unlock() 41 | dc.gohtDiagnostics[uri] = diagnostics 42 | if dc.goDiagnostics[uri] == nil { 43 | dc.goDiagnostics[uri] = make([]protocol.Diagnostic, 0) 44 | } 45 | return append(dc.goDiagnostics[uri], diagnostics...) 46 | } 47 | 48 | func (dc *DiagnosticsCache) ClearGohtDiagnostics(uri string) { 49 | dc.mu.Lock() 50 | defer dc.mu.Unlock() 51 | dc.gohtDiagnostics[uri] = make([]protocol.Diagnostic, 0) 52 | } 53 | -------------------------------------------------------------------------------- /internal/proxy/document.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/stackus/goht/internal/protocol" 8 | ) 9 | 10 | type Document struct { 11 | lines []string 12 | } 13 | 14 | var _ fmt.Stringer = (*Document)(nil) 15 | 16 | func NewDocument(text string) *Document { 17 | return &Document{ 18 | lines: strings.Split(text, "\n"), 19 | } 20 | } 21 | 22 | func (d *Document) String() string { 23 | return strings.Join(d.lines, "\n") 24 | } 25 | 26 | func (d *Document) Apply(r *protocol.Range, text string) { 27 | lines := strings.Split(text, "\n") 28 | switch { 29 | case d.isWholeDocument(r): 30 | d.lines = lines 31 | case d.isInsert(r) && text != "": 32 | d.insert(int(r.Start.Line), int(r.Start.Character), lines) 33 | case d.isReplace(r) && text == "": 34 | d.delete(int(r.Start.Line), int(r.Start.Character), int(r.End.Line), int(r.End.Character)) 35 | case d.isReplace(r) && text != "": 36 | d.overwrite(int(r.Start.Line), int(r.Start.Character), int(r.End.Line), int(r.End.Character), lines) 37 | } 38 | } 39 | 40 | func (d *Document) isWholeDocument(r *protocol.Range) bool { 41 | if r == nil { 42 | return true 43 | } 44 | if r.Start.Line != 0 || r.Start.Character != 0 { 45 | return false 46 | } 47 | l, c := len(d.lines), len(d.lines[len(d.lines)-1]) 48 | return r.End.Line >= uint32(l) || r.End.Character >= uint32(c) 49 | } 50 | 51 | func (d *Document) isInsert(r *protocol.Range) bool { 52 | return r.Start.Line == r.End.Line && r.Start.Character == r.End.Character 53 | } 54 | 55 | func (d *Document) insert(line, col int, lines []string) { 56 | before := d.lines[line][:col] 57 | after := d.lines[line][col:] 58 | d.lines[line] = before + lines[0] 59 | if len(lines) > 1 { 60 | d.lines = append(d.lines[:line+1], append(lines[1:], d.lines[:line+1]...)...) 61 | } 62 | d.lines[line+len(lines)-1] = lines[len(lines)-1] + after 63 | } 64 | 65 | func (d *Document) isReplace(r *protocol.Range) bool { 66 | return r.Start.Line != r.End.Line || r.Start.Character != r.End.Character 67 | } 68 | 69 | func (d *Document) delete(startLine, startCol, endLine, endCol int) { 70 | if startLine == endLine { 71 | d.lines[startLine] = d.lines[startLine][:startCol] + d.lines[startLine][endCol:] 72 | return 73 | } 74 | d.lines[startLine] = d.lines[startLine][:startCol] 75 | d.lines[endLine] = d.lines[endLine][endCol:] 76 | d.lines = append(d.lines[:startLine+1], d.lines[endLine:]...) 77 | } 78 | 79 | func (d *Document) overwrite(startLine, startCol, endLine, endCol int, lines []string) { 80 | if startLine == endLine { 81 | d.lines[startLine] = d.lines[startLine][:startCol] + lines[0] + d.lines[startLine][endCol:] 82 | return 83 | } 84 | d.lines[startLine] = d.lines[startLine][:startCol] + lines[0] 85 | d.lines[endLine] = lines[len(lines)-1] + d.lines[endLine][endCol:] 86 | d.lines = append(d.lines[:startLine+1], append(lines[1:], d.lines[endLine:]...)...) 87 | } 88 | -------------------------------------------------------------------------------- /internal/proxy/document_contents.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/stackus/goht/internal/protocol" 8 | ) 9 | 10 | type DocumentContents struct { 11 | documents map[string]*Document 12 | mu sync.Mutex 13 | } 14 | 15 | func NewDocumentContents() *DocumentContents { 16 | return &DocumentContents{ 17 | documents: make(map[string]*Document), 18 | } 19 | } 20 | 21 | func (dc *DocumentContents) Set(uri string, d *Document) { 22 | dc.mu.Lock() 23 | defer dc.mu.Unlock() 24 | dc.documents[uri] = d 25 | } 26 | 27 | func (dc *DocumentContents) Get(uri string) (d *Document, ok bool) { 28 | dc.mu.Lock() 29 | defer dc.mu.Unlock() 30 | d, ok = dc.documents[uri] 31 | return 32 | } 33 | 34 | func (dc *DocumentContents) Delete(uri string) { 35 | dc.mu.Lock() 36 | defer dc.mu.Unlock() 37 | delete(dc.documents, uri) 38 | } 39 | 40 | func (dc *DocumentContents) URIs() (uris []string) { 41 | dc.mu.Lock() 42 | defer dc.mu.Unlock() 43 | uris = make([]string, len(dc.documents)) 44 | var i int 45 | for k := range dc.documents { 46 | uris[i] = k 47 | i++ 48 | } 49 | return uris 50 | } 51 | 52 | func (dc *DocumentContents) Apply(uri string, changes []protocol.TextDocumentContentChangeEvent) (*Document, error) { 53 | dc.mu.Lock() 54 | defer dc.mu.Unlock() 55 | d, ok := dc.documents[uri] 56 | if !ok { 57 | return nil, fmt.Errorf("document %q not found", uri) 58 | } 59 | 60 | for _, change := range changes { 61 | d.Apply(change.Range, change.Text) 62 | } 63 | 64 | return d, nil 65 | } 66 | -------------------------------------------------------------------------------- /internal/proxy/file_names.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/stackus/goht/internal/protocol" 7 | ) 8 | 9 | // toGohtURI converts a Goht Go URI to a Goht URI. 10 | // 11 | // (e.g. "file:///path/to/file.goht.go" -> "file:///path/to/file.goht") 12 | func toGohtURI(uri protocol.DocumentURI) (bool, protocol.DocumentURI) { 13 | if !isGohtGoURI(uri) { 14 | return false, "" 15 | } 16 | return true, uri[:len(uri)-3] 17 | } 18 | 19 | // toGohtGoURI converts a Goht URI to a Goht Go URI. 20 | // 21 | // (e.g. "file:///path/to/file.goht" -> "file:///path/to/file.goht.go") 22 | func toGohtGoURI(uri protocol.DocumentURI) (bool, protocol.DocumentURI) { 23 | if !isGohtURI(uri) { 24 | return false, "" 25 | } 26 | return true, uri + ".go" 27 | } 28 | 29 | func isGohtURI(uri protocol.DocumentURI) bool { 30 | return strings.HasSuffix(string(uri), ".goht") 31 | } 32 | 33 | func isGohtGoURI(uri protocol.DocumentURI) bool { 34 | return strings.HasSuffix(string(uri), ".goht.go") 35 | } 36 | -------------------------------------------------------------------------------- /internal/proxy/source_map_cache.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/stackus/goht/compiler" 7 | ) 8 | 9 | type SourceMapCache struct { 10 | sourceMaps map[string]*compiler.SourceMap 11 | mu sync.Mutex 12 | } 13 | 14 | func NewSourceMapCache() *SourceMapCache { 15 | return &SourceMapCache{ 16 | sourceMaps: make(map[string]*compiler.SourceMap), 17 | } 18 | } 19 | 20 | func (smc *SourceMapCache) Set(uri string, sourceMap *compiler.SourceMap) { 21 | smc.mu.Lock() 22 | defer smc.mu.Unlock() 23 | smc.sourceMaps[uri] = sourceMap 24 | } 25 | 26 | func (smc *SourceMapCache) Get(uri string) (sourceMap *compiler.SourceMap, ok bool) { 27 | smc.mu.Lock() 28 | defer smc.mu.Unlock() 29 | sourceMap, ok = smc.sourceMaps[uri] 30 | return 31 | } 32 | 33 | func (smc *SourceMapCache) Delete(uri string) { 34 | smc.mu.Lock() 35 | defer smc.mu.Unlock() 36 | delete(smc.sourceMaps, uri) 37 | } 38 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package goht 2 | 3 | import ( 4 | _ "embed" 5 | "strings" 6 | ) 7 | 8 | //go:embed .version 9 | var version string 10 | 11 | func Version() string { 12 | return strings.TrimSpace(strings.TrimPrefix(version, "VERSION=")) 13 | } 14 | --------------------------------------------------------------------------------