diff --git a/README.md b/README.md index 1e8d21c..2b2cfc2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![build and test](https://github.com/mustafaakin/templ8go/actions/workflows/go.yml/badge.svg)](https://github.com/mustafaakin/templ8go/actions/workflows/go.yml) +[![golang ci Lint](https://github.com/mustafaakin/templ8go/actions/workflows/go-lint.yml/badge.svg)](https://github.com/mustafaakin/templ8go/actions/workflows/go-lint.yml) + # templ8go `templ8go` is a small library designed to resolve template strings like `Hello @@ -5,6 +8,8 @@ name stems from blending "template" with "V8" (the JavaScript engine), and "8" phonetically resembling "ate". Sorry for the dad joke, ChatGPT come up with it. +--- + ## Why? While traditional templating engines are great for generating large blocks of @@ -22,6 +27,8 @@ Consider the following examples where `templ8go` shines: These examples showcase the simplicity and power of using JavaScript expressions within templates. +--- + ## Features - **Dynamic Expression Evaluation**: Use JavaScript expressions right within @@ -33,6 +40,8 @@ expressions within templates. - **Security**: It leverages V8, the same Javascript engine that runs Cloudflare workers and Chrome. Though we can add even more hardening. +--- + ## Getting Started ### Installation @@ -58,8 +67,8 @@ import ( func main() { template := "Hello {{ user.name }}" - bindings := map[string]interface{}{ - "user": map[string]interface{}{ + bindings := map[string]any{ + "user": map[string]any{ "name": "Mustafa", }, } @@ -86,8 +95,8 @@ import ( ) func main() { - bindings := map[string]interface{}{ - "user": map[string]interface{}{ + bindings := map[string]any{ + "user": map[string]any{ "name": "Mustafa", }, } @@ -113,6 +122,8 @@ result, err := templ8go.ResolveJSExpression(bindings, "user.name") ... ``` +--- + ## Supported Expressions Since we use V8 engine underneath, many things are possible. @@ -162,6 +173,7 @@ Since we use V8 engine underneath, many things are possible. - Bindings: `{ "user": {"name": "Eve", "isActive": true, "roles": ["admin", "editor"]} }` - Output: `Eve is active and has 2 roles.` +--- ## Development @@ -173,6 +185,8 @@ go test ./... Feel free to submit pull requests or create issues for bugs, features, or suggestions. +--- + ## Contributing Contributions are more than welcome! If you have an idea for an improvement or @@ -182,4 +196,3 @@ submit a pull request. ## License `templ8go` is made available under the MIT License. For more details, see the LICENSE file in the repository. - diff --git a/js.go b/js.go index a56fec5..576244f 100644 --- a/js.go +++ b/js.go @@ -22,7 +22,7 @@ func SetDefaultExecutionTimeout(d time.Duration) { } // ResolveJSExpression handles the resolve operation with a given JS expression and binding data. -func ResolveJSExpression(bindings map[string]interface{}, expression string) (interface{}, error) { +func ResolveJSExpression(bindings map[string]any, expression string) (any, error) { ctx := v8.NewContext() defer ctx.Close() @@ -41,7 +41,7 @@ func ResolveJSExpression(bindings map[string]interface{}, expression string) (in } } - resultChan := make(chan interface{}, 1) + resultChan := make(chan any, 1) errorChan := make(chan error, 1) go func() { @@ -51,7 +51,7 @@ func ResolveJSExpression(bindings map[string]interface{}, expression string) (in return } - var result interface{} + var result any if err := json.Unmarshal([]byte(val.String()), &result); err != nil { errorChan <- fmt.Errorf("failed to unmarshal result: %w", err) return diff --git a/js_test.go b/js_test.go index ea9f0cd..38b4a99 100644 --- a/js_test.go +++ b/js_test.go @@ -11,9 +11,9 @@ import ( func TestResolveJSExpression(t *testing.T) { tests := []struct { name string - bindings map[string]interface{} + bindings map[string]any expression string - expected interface{} + expected any expectErr bool }{ { @@ -24,7 +24,7 @@ func TestResolveJSExpression(t *testing.T) { }, { name: "basic math, multiplication expression with bindings", - bindings: map[string]interface{}{ + bindings: map[string]any{ "a": 10, "b": 5, }, @@ -33,8 +33,8 @@ func TestResolveJSExpression(t *testing.T) { }, { name: "math, addition with complex object", - bindings: map[string]interface{}{ - "obj": map[string]interface{}{ + bindings: map[string]any{ + "obj": map[string]any{ "value": 5, }, }, @@ -55,7 +55,7 @@ func TestResolveJSExpression(t *testing.T) { }, { name: "js Math function example", - bindings: map[string]interface{}{ + bindings: map[string]any{ "x": 10, "y": 20, }, @@ -64,7 +64,7 @@ func TestResolveJSExpression(t *testing.T) { }, { name: "js value compare", - bindings: map[string]interface{}{ + bindings: map[string]any{ "emptyString": "", "nullValue": nil, }, @@ -73,7 +73,7 @@ func TestResolveJSExpression(t *testing.T) { }, { name: "type coercion", - bindings: map[string]interface{}{ + bindings: map[string]any{ "stringValue": "10", "numValue": 10, }, @@ -88,7 +88,7 @@ func TestResolveJSExpression(t *testing.T) { }, { name: "should raise json.Marshal error", - bindings: map[string]interface{}{ + bindings: map[string]any{ "key": make(chan struct{}), }, expression: "", @@ -96,7 +96,7 @@ func TestResolveJSExpression(t *testing.T) { }, { name: "should raise Set error", - bindings: map[string]interface{}{ + bindings: map[string]any{ "key": "while(true){}", }, expression: "", @@ -121,18 +121,18 @@ func TestResolveJSExpression(t *testing.T) { SetDefaultExecutionTimeout(10 * time.Microsecond) defer func() { SetDefaultExecutionTimeout(originalTimeout) }() - _, err := ResolveJSExpression(map[string]interface{}{"a": 1}, "a + 2") + _, err := ResolveJSExpression(map[string]any{"a": 1}, "a + 2") assert.ErrorIs(t, err, ErrResolveJSExpressionExecutionTimeout) }) } func TestResolveJSExpressionBindingsUnchanged(t *testing.T) { - initialBindings := map[string]interface{}{ + initialBindings := map[string]any{ "x": 5, "y": 10, } // Create a deep copy of the initialBindings for comparison after function execution - expectedBindings := make(map[string]interface{}) + expectedBindings := make(map[string]any) for k, v := range initialBindings { expectedBindings[k] = v } diff --git a/template.go b/template.go index c1127f9..3efd492 100644 --- a/template.go +++ b/template.go @@ -14,7 +14,7 @@ var ( ) // ResolveTemplate handles the resolve operation with a given template string and binding data as args. -func ResolveTemplate(args map[string]interface{}, input string) (string, error) { +func ResolveTemplate(args map[string]any, input string) (string, error) { var result strings.Builder leftDelimiter := "{{" rightDelimiter := "}}" diff --git a/template_test.go b/template_test.go index 9a21684..6ae3e34 100644 --- a/template_test.go +++ b/template_test.go @@ -10,14 +10,14 @@ import ( func TestParseTemplateStringSuccess(t *testing.T) { tests := []struct { name string - vars map[string]interface{} + vars map[string]any input string expected string }{ { "Simple substitution", - map[string]interface{}{ - "user": map[string]interface{}{ + map[string]any{ + "user": map[string]any{ "name": "Mustafa", "age": 32, }, @@ -27,8 +27,8 @@ func TestParseTemplateStringSuccess(t *testing.T) { }, { "Arithmetic operation", - map[string]interface{}{ - "user": map[string]interface{}{ + map[string]any{ + "user": map[string]any{ "age": 32, }, }, @@ -37,9 +37,9 @@ func TestParseTemplateStringSuccess(t *testing.T) { }, { "Nested object access", - map[string]interface{}{ - "user": map[string]interface{}{ - "profile": map[string]interface{}{ + map[string]any{ + "user": map[string]any{ + "profile": map[string]any{ "nickname": "Moose", }, }, @@ -49,8 +49,8 @@ func TestParseTemplateStringSuccess(t *testing.T) { }, { "Array access", - map[string]interface{}{ - "favorites": []interface{}{"Pizza", "Ice Cream"}, + map[string]any{ + "favorites": []any{"Pizza", "Ice Cream"}, }, "I love {{ favorites[0] }} and {{ favorites[1] }}.", "I love Pizza and Ice Cream.", @@ -69,8 +69,8 @@ func TestParseTemplateStringSuccess(t *testing.T) { // TestParseTemplateStringError tests error handling. func TestParseTemplateStringError(t *testing.T) { - vars := map[string]interface{}{ - "user": map[string]interface{}{}, + vars := map[string]any{ + "user": map[string]any{}, } // Unmatched expression delimiter @@ -84,29 +84,29 @@ func TestReadmeExamples(t *testing.T) { tests := []struct { name string template string - bindings map[string]interface{} + bindings map[string]any want string }{ { name: "Simple Arithmetic", template: "The sum of 5 and 3 is {{ 5 + 3 }}.", - bindings: map[string]interface{}{}, + bindings: map[string]any{}, want: "The sum of 5 and 3 is 8.", }, { name: "Conditional Greetings", template: "Good {{ hour < 12 ? 'morning' : 'afternoon' }}, {{ user.name }}!", - bindings: map[string]interface{}{ + bindings: map[string]any{ "hour": 9, - "user": map[string]interface{}{"name": "Alice"}, + "user": map[string]any{"name": "Alice"}, }, want: "Good morning, Alice!", }, { name: "Array Operations", template: "Users list: {{ users.map(user => user.name).join(', ') }}", - bindings: map[string]interface{}{ - "users": []map[string]interface{}{ + bindings: map[string]any{ + "users": []map[string]any{ {"name": "Alice"}, {"name": "Bob"}, {"name": "Charlie"}, @@ -117,15 +117,15 @@ func TestReadmeExamples(t *testing.T) { { name: "Object Manipulation", template: "{{ user.firstName }} {{ user.lastName }} is {{ user.age }} years old.", - bindings: map[string]interface{}{ - "user": map[string]interface{}{"firstName": "John", "lastName": "Doe", "age": 28}, + bindings: map[string]any{ + "user": map[string]any{"firstName": "John", "lastName": "Doe", "age": 28}, }, want: "John Doe is 28 years old.", }, { name: "Logical Operations", template: "You are {{ age >= 18 ? 'an adult' : 'a minor' }}.", - bindings: map[string]interface{}{ + bindings: map[string]any{ "age": 20, }, want: "You are an adult.", @@ -133,15 +133,15 @@ func TestReadmeExamples(t *testing.T) { { name: "String Concatenation", template: "{{ 'Hello, ' + user.name + '!'}}", - bindings: map[string]interface{}{ - "user": map[string]interface{}{"name": "Jane"}, + bindings: map[string]any{ + "user": map[string]any{"name": "Jane"}, }, want: "Hello, Jane!", }, { name: "Using JavaScript Functions", template: "Your score is {{ Math.min(score, 100) }}.", - bindings: map[string]interface{}{ + bindings: map[string]any{ "score": 105, }, want: "Your score is 100.", @@ -149,9 +149,9 @@ func TestReadmeExamples(t *testing.T) { { name: "Nested Object Access", template: "Project {{ project.details.name }} is due on {{ project.details.dueDate }}.", - bindings: map[string]interface{}{ - "project": map[string]interface{}{ - "details": map[string]interface{}{"name": "Apollo", "dueDate": "2024-03-01"}, + bindings: map[string]any{ + "project": map[string]any{ + "details": map[string]any{"name": "Apollo", "dueDate": "2024-03-01"}, }, }, want: "Project Apollo is due on 2024-03-01.", @@ -159,8 +159,8 @@ func TestReadmeExamples(t *testing.T) { { name: "Complex Expressions", template: "{{ user.isActive ? user.name + ' is active and has ' + user.roles.length + ' roles' : user.name + ' is not active' }}.", - bindings: map[string]interface{}{ - "user": map[string]interface{}{"name": "Eve", "isActive": true, "roles": []string{"admin", "editor"}}, + bindings: map[string]any{ + "user": map[string]any{"name": "Eve", "isActive": true, "roles": []string{"admin", "editor"}}, }, want: "Eve is active and has 2 roles.", },