Skip to content

Commit

Permalink
Add a mechanism to ignore some patterns in gotemplate/razor (#259)
Browse files Browse the repository at this point in the history
* Add a mechanism to ignore some patterns in gotemplate/razor

* Add an example of usage

* Fix a typo

* Updated submodule docs/themes/book

* Add some documentation

* Update the version of hugo used to generate the documentation

* Change version of submodule on the latest official version instead of the last commit

* Update the Hugo version to the latest

* Remove nagging errors during documentation generation
  • Loading branch information
jocgir authored Oct 2, 2024
1 parent e36f53e commit 94e5107
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
if: github.ref == 'refs/heads/master'
uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3
with:
hugo-version: "0.82.0"
hugo-version: "0.135.0"
extended: true

- name: Build Documentation
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Setup Hugo
uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3
with:
hugo-version: "0.82.0"
hugo-version: "0.135.0"
extended: true

- name: Test Documentation
Expand Down
Empty file added docs/.hugo_build.lock
Empty file.
2 changes: 1 addition & 1 deletion docs/themes/book
Submodule book updated 233 files
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func runGotemplate() (exitCode int) {
ignoreMissingImport = run.Flag("ignore-missing-import", "Exit with code 0 even if import does not exist").Bool()
ignoreMissingSource = run.Flag("ignore-missing-source", "Exit with code 0 even if source does not exist").Bool()
ignoreMissingPaths = run.Flag("ignore-missing-paths", "Exit with code 0 even if import or source do not exist").Bool()
ignoreRazor = run.Flag("ignore-razor", "Do not consider the list of excluded Razor name as razor expression").PlaceHolder("regex").Strings()
templates = run.Arg("templates", "Template files or commands to process").Strings()

list = app.Command("list", "Get detailed help on gotemplate functions").NoAutoShortcut()
Expand Down Expand Up @@ -257,6 +258,10 @@ func runGotemplate() (exitCode int) {
}
t.TempFolder(tempFolder)

if len(*ignoreRazor) > 0 {
t.IgnoreRazorExpression(*ignoreRazor...)
}

if command == list.FullCommand() {
if !(*listFunctions || *listTemplates) {
// If neither list functions or templates is selected, we default to list functions
Expand Down
1 change: 1 addition & 0 deletions render-doc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ function cleanup {
trap cleanup EXIT
go build -o gotemplate

export GOTEMPLATE_NO_STDIN=1
CONTENT_FOLDER="docs/content"
DOC_FOLDER="$CONTENT_FOLDER/docs"
export COLUMNS=108
Expand Down
6 changes: 6 additions & 0 deletions template/razor.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ func (t *Template) applyRazor(content []byte) (result []byte, changed bool) {
}
t.ensureInit()

for _, ignoredExpr := range t.ignoredRazorExpr {
content = regexp.MustCompile(t.delimiters[2]+ignoredExpr).ReplaceAllFunc(content, func(match []byte) []byte {
return []byte(strings.Replace(string(match), t.delimiters[2], literalAt, 1))
})
}

for _, r := range replacementsInit[fmt.Sprint(t.delimiters)] {
printDebugInfo(r, string(content))
if r.parser == nil {
Expand Down
74 changes: 74 additions & 0 deletions template/razor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,47 @@ func TestBase(t *testing.T) {
}
}

func TestIgnoredRazorExpression(t *testing.T) {
t.Parallel()
tests := []struct {
name string
code string
context map[string]interface{}
ignored []string
want string
}{
{
"Sha256",
"Hello @var1 @sha256",
map[string]interface{}{"var1": "world"},
[]string{"sha256"},
"Hello world @sha256",
},
{
"Real Sha256",
"Hello @var1 public.ecr.aws/lambda/python:3.12-arm64@sha256:335461dca279eede475193ac3cfda992d2f7e632710f8d92cbb4fb6f439abc06",
map[string]interface{}{"var1": "world"},
[]string{"sha256"},
"Hello world public.ecr.aws/lambda/python:3.12-arm64@sha256:335461dca279eede475193ac3cfda992d2f7e632710f8d92cbb4fb6f439abc06",
},
{
"Regex",
"Hello @var1 @this_var_is_not_a_razor_one",
map[string]interface{}{"var1": "world"},
[]string{"\\w*not_a_razor\\w+"},
"Hello world @this_var_is_not_a_razor_one",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
template := MustNewTemplate(".", tt.context, "", nil)
template.IgnoreRazorExpression(tt.ignored...)
got, _ := template.ProcessContent(tt.code, tt.name)
assert.Equal(t, tt.want, string(got))
})
}
}

func TestInvocation(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -559,3 +600,36 @@ func TestReservedKeywords(t *testing.T) {
})
}
}

var log = multilogger.New("example")

func ExampleTemplate_IgnoreRazorExpression() {
code := []string{
"Hello, @Name! From @Author",
"This @variable should not be changed.",
"Neither than @thisOne or @thatOne",
`And this @function("text", 1) won't be invoked while @add(2, 3) will be`,
}

context := map[string]string{
"Name": "There",
"Author": "Obi-Wan Kenobi",
}
template := MustNewTemplate(".", context, "", nil)
template.IgnoreRazorExpression(
"variable", // Work with full variable name
`th(is|at)One`, // or with a regular expression
`function`, // or with a function
)
result, err := template.ProcessContent(strings.Join(code, "\n"), "Internal example")
if err != nil {
log.Fatalf("execution failed: %s", err)
}
fmt.Println(result)

// Output:
// Hello, There! From Obi-Wan Kenobi
// This @variable should not be changed.
// Neither than @thisOne or @thatOne
// And this @function("text", 1) won't be invoked while 5 will be
}
111 changes: 92 additions & 19 deletions template/template.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,55 @@
// Package template provides extended functionalities for the base Go template library.
// It includes additional features such as context management, custom delimiters, and
// environment variable configurations for template processing.
//
// The package imports several other packages to enhance its capabilities:
// - fmt, os, filepath, reflect, strings, sync, and text/template from the standard library.
// - collections and utils from github.com/coveooss/gotemplate/v3.
// - multicolor from github.com/coveooss/multilogger/color.
//
// The package defines several constants for environment variables that can override default behaviors:
// - EnvAcceptNoValue: Allows the template processor to accept variables with no value without throwing an error.
// - EnvStrictErrorCheck: Enables strict error checking.
// - EnvSubstitutes: Specifies regex replacements.
// - EnvDebug: Enables debug mode.
// - EnvExtensionPath: Specifies the path for template extensions.
// - EnvInternalLogLevel: Sets the internal log level.
// - EnvLogLevel: Sets the template log level.
//
// The Template struct extends the base template functionalities and includes fields for:
// - tempFolder: Temporary folder used by the template.
// - substitutes: List of regex replacements.
// - context: Template context.
// - constantKeys: List of constant keys.
// - delimiters: List of delimiters.
// - parent: Parent template.
// - folder: Template folder.
// - children: Map of child templates.
// - aliases: Function table map for aliases.
// - functions: Function table map for functions.
// - options: Set of template options.
// - optionsEnabled: Set of enabled template options.
// - ignoredRazorExpr: List of ignored Razor expressions.
//
// The package provides several functions and methods for template management:
// - IsRazor: Determines if the supplied code contains Razor code.
// - IsCode: Determines if the supplied code contains template code.
// - NewTemplate: Creates a new Template object with default initialization.
// - MustNewTemplate: Creates a new Template object and panics if an error occurs.
// - TempFolder: Sets the temporary folder for the template.
// - GetNewContext: Returns a distinct context for each folder.
// - LeftDelim: Returns the left delimiter.
// - RightDelim: Returns the right delimiter.
// - RazorDelim: Returns the Razor delimiter.
// - SetOption: Sets a template option after initialization.
// - initExtension: Initializes template extensions.
// - init: Initializes a new template with the same attributes as the current context.
// - setConstant: Sets a constant value in the template context.
// - importTemplates: Imports templates from another template.
// - Add: Adds a value to the template context.
// - Merge: Merges multiple values into the template context.
// - Context: Returns the template context as a dictionary.
// - IgnoreRazorExpression: Ignores specified Razor expressions.
package template

import (
Expand All @@ -22,21 +74,24 @@ var templateMutex sync.Mutex
// Template let us extend the functionalities of base go template library.
type Template struct {
*template.Template
tempFolder string
substitutes []utils.RegexReplacer
context interface{}
constantKeys []interface{}
delimiters []string
parent *Template
folder string
children map[string]*Template
aliases funcTableMap
functions funcTableMap
options OptionsSet
optionsEnabled OptionsSet
tempFolder string
substitutes []utils.RegexReplacer
context interface{}
constantKeys []interface{}
delimiters []string
parent *Template
folder string
children map[string]*Template
aliases funcTableMap
functions funcTableMap
options OptionsSet
optionsEnabled OptionsSet
ignoredRazorExpr []string
}

// Environment variables that could be defined to override default behaviors.
// EnvAcceptNoValue is an environment variable that, when set, allows the template
// processor to accept variables with no value without throwing an error.
const (
EnvAcceptNoValue = "GOTEMPLATE_NO_VALUE"
EnvStrictErrorCheck = "GOTEMPLATE_STRICT_ERROR"
Expand Down Expand Up @@ -186,16 +241,22 @@ func (t *Template) IsRazor(code string) bool {
return strings.Contains(code, t.RazorDelim()) && !strings.Contains(code, noGoTemplate) && !strings.Contains(code, noRazor)
}

// LeftDelim returns the left delimiter.
// LeftDelim returns the left delimiter used in the template.
// It retrieves the first element from the delimiters slice.
func (t *Template) LeftDelim() string { return t.delimiters[0] }

// RightDelim returns the right delimiter.
// RightDelim returns the right delimiter used in the template.
// It retrieves the second element from the delimiters slice.
func (t *Template) RightDelim() string { return t.delimiters[1] }

// RazorDelim returns the razor delimiter.
// RazorDelim returns the third delimiter from the Template's delimiters slice.
// This delimiter is typically used for Razor-style templating.
func (t *Template) RazorDelim() string { return t.delimiters[2] }

// SetOption allows setting of template option after initialization.
// SetOption sets the specified option to the given boolean value in the Template's options map.
// Parameters:
// - option: The option to be set.
// - value: The boolean value to set for the specified option.
func (t *Template) SetOption(option Options, value bool) { t.options[option] = value }

func (t *Template) isTemplate(file string) bool {
Expand Down Expand Up @@ -290,21 +351,33 @@ func (t *Template) importTemplates(source *Template) {
}

// Add allows adding a value to the template context.
// The context must be a dictionnary to use that function, otherwise, it will panic.
// The context must be a dictionary to use that function, otherwise, it will panic.
func (t *Template) Add(key string, value interface{}) {
collections.AsDictionary(t.context).Add(key, value)
}

// Merge allows adding multiple values to the template context.
// The context and values must both be dictionnary to use that function, otherwise, it will panic.
// The context and values must both be dictionary to use that function, otherwise, it will panic.
func (t *Template) Merge(values interface{}) {
collections.AsDictionary(t.context).Add(key, collections.AsDictionary(values))
}

// Context returns the template context as a dictionnary if possible, otherwise, it returns null.
// Context returns the context of the Template as a collections.IDictionary.
// It provides access to the underlying data structure that holds the template's context.
func (t *Template) Context() (result collections.IDictionary) {
if result, _ = collections.TryAsDictionary(t.context); result == nil {
result = collections.CreateDictionary()
}
return
}

// IgnoreRazorExpression sets the list of Razor expressions to be ignored by the template.
// This method accepts a variadic number of string arguments, allowing multiple expressions
// to be specified at once.
//
// Parameters:
//
// expr: A variadic list of strings representing the Razor expressions to ignore (can also be a regular expression).
func (t *Template) IgnoreRazorExpression(expr ...string) {
t.ignoredRazorExpr = expr
}

0 comments on commit 94e5107

Please sign in to comment.