From 94e5107f4eb250b16eba9cb6da496f0a97e50f66 Mon Sep 17 00:00:00 2001 From: Jo Giroux Date: Wed, 2 Oct 2024 13:37:10 -0400 Subject: [PATCH] Add a mechanism to ignore some patterns in gotemplate/razor (#259) * 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 --- .github/workflows/build.yml | 2 +- .github/workflows/pr.yml | 2 +- docs/.hugo_build.lock | 0 docs/themes/book | 2 +- main.go | 5 ++ render-doc | 1 + template/razor.go | 6 ++ template/razor_test.go | 74 ++++++++++++++++++++++++ template/template.go | 111 ++++++++++++++++++++++++++++++------ 9 files changed, 181 insertions(+), 22 deletions(-) create mode 100644 docs/.hugo_build.lock diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f5ff5c3e..fcb11162 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index a27ba1d6..df111760 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -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 diff --git a/docs/.hugo_build.lock b/docs/.hugo_build.lock new file mode 100644 index 00000000..e69de29b diff --git a/docs/themes/book b/docs/themes/book index 1b6469f7..e104a11f 160000 --- a/docs/themes/book +++ b/docs/themes/book @@ -1 +1 @@ -Subproject commit 1b6469f7b1ec0ea263859c7d4d554524f731b2ee +Subproject commit e104a11f42fbd069aa15606c5f01631b07d7528c diff --git a/main.go b/main.go index 681b9c51..cc99fd1d 100644 --- a/main.go +++ b/main.go @@ -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() @@ -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 diff --git a/render-doc b/render-doc index 95fdb05c..3ecead59 100755 --- a/render-doc +++ b/render-doc @@ -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 diff --git a/template/razor.go b/template/razor.go index 67be5730..a6c4d925 100644 --- a/template/razor.go +++ b/template/razor.go @@ -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 { diff --git a/template/razor_test.go b/template/razor_test.go index 0437aa00..ca2e4eeb 100644 --- a/template/razor_test.go +++ b/template/razor_test.go @@ -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 @@ -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 +} diff --git a/template/template.go b/template/template.go index e90a4f9f..cbef8143 100644 --- a/template/template.go +++ b/template/template.go @@ -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 ( @@ -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" @@ -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 { @@ -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 +}