diff options
author | Jo-Philipp Wich <jo@mein.io> | 2022-08-29 15:02:31 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2022-08-29 15:02:31 +0200 |
commit | 344fa9e69da43ecdc4d8f7768d85d42639352405 (patch) | |
tree | 156657bbf8ceb57d7b9af8a6a9f92d829263e2ee | |
parent | 89452b20e5073feb28b294a707342ef144f4b5f0 (diff) |
lib: extend render() to support function values
Extend the `render()` function to accept a function value as first argument,
which allows running arbitrary ucode functions and capturing their output.
This is especially useful in conjunction with `loadfile()` or `loadstring()`
to dynamically compile templates and rendering their output into a string.
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r-- | README.md | 17 | ||||
-rw-r--r-- | lib.c | 7 | ||||
-rw-r--r-- | tests/custom/03_stdlib/36_render | 74 |
3 files changed, 72 insertions, 26 deletions
@@ -1254,11 +1254,18 @@ an invalid value was passed, otherwise `true`. Raise an exception with the given `message` parameter if the value in `cond` is not truish. When `message` is omitted, the default value is `Assertion failed`. -#### 6.56. `render(path[, scope])` - -Like `include()` but capture output of included file as string and return it. - -See `include()` for details on scoping. +#### 6.56. `render(path_or_func[, scope_or_fnarg1 [, fnarg2 [, ...]]])` + +When invoked with a string value as first argument, the function acts like +like `include()` but captures the output of the included file as string and +returns the captured contents. The second argument is treated as scope. See +`include()` for details on scoping. + +When invoked with a function value as first argument, `render()` calls the +given function and passes all subsequent arguments to it. Any output +(through print(), template text instructions and the like) produced by the +called function is captured and returned as string. The return value of the +called function is discarded. #### 6.57. `regexp(source[, flags])` @@ -2484,8 +2484,13 @@ uc_render(uc_vm_t *vm, size_t nargs) prev = vm->output; vm->output = mem; + /* execute function */ + if (ucv_is_callable(uc_fn_arg(0))) + (void) uc_vm_call(vm, false, nargs - 1); + /* execute include */ - (void) uc_include_common(vm, nargs, false); + else + (void) uc_include_common(vm, nargs, false); /* restore previous VM output */ vm->output = prev; diff --git a/tests/custom/03_stdlib/36_render b/tests/custom/03_stdlib/36_render index 55a1105..aa2a27b 100644 --- a/tests/custom/03_stdlib/36_render +++ b/tests/custom/03_stdlib/36_render @@ -1,6 +1,7 @@ -The `render()` function executes the specified path as ucode script, -optionally setting a different execution scope for the invoked file, -and captures the produced output in a string. +When invoked with a file path argment, the `render()` function executes +the specified path as ucode script, optionally setting a different +execution scope for the invoked file, and captures the produced output +in a string. If the specified path is relative, it is treated as being relative to the source file currently being executed or the current working directory in @@ -14,7 +15,11 @@ Throws an exception if the given path could not be found or opened. Throws an exception if the given file could not be compiled. -Returns a string containing the captured output of the executed file. +When invoked with a function value, `render()` invokes the function, passes +all remaining arugments to it and captures any produces output in a string. + +Returns a string containing the captured output of the executed file or +function. -- Testcase -- {% @@ -69,16 +74,16 @@ An invalid path value triggers an exception. -- Testcase -- {% - include(true); + render(true); %} -- End -- -- Expect stderr -- Type error: Passed filename is not a string -In line 2, byte 14: +In line 2, byte 13: - ` include(true);` - Near here ------^ + ` render(true);` + Near here -----^ -- End -- @@ -88,16 +93,16 @@ An invalid scope value triggers an exception. -- Testcase -- {% - include("test", true); + render("test", true); %} -- End -- -- Expect stderr -- Type error: Passed scope value is not an object -In line 2, byte 22: +In line 2, byte 21: - ` include("test", true);` - Near here --------------^ + ` render("test", true);` + Near here -------------^ -- End -- @@ -107,16 +112,16 @@ A not found file triggers an exception. -- Testcase -- {% - include("files/doesnotexist.uc"); + render("files/doesnotexist.uc"); %} -- End -- -- Expect stderr -- Runtime error: Include file not found -In line 2, byte 33: +In line 2, byte 32: - ` include("files/doesnotexist.uc");` - Near here -------------------------^ + ` render("files/doesnotexist.uc");` + Near here ------------------------^ -- End -- @@ -126,7 +131,7 @@ A compilation error in the file triggers an exception. -- Testcase -- {% - include("files/broken.uc"); + render("files/broken.uc"); %} -- End -- @@ -146,10 +151,39 @@ Runtime error: Unable to compile source file './files/broken.uc': | ` return {` | Near here --^ -In line 2, byte 27: +In line 2, byte 26: + + ` render("files/broken.uc");` + Near here ------------------^ + + +-- End -- + + +Rendering a function value will capture it's output. + +-- Testcase -- +{% + name = "world"; - ` include("files/broken.uc");` - Near here -------------------^ + printf("%.J\n", [ + render(print, "Test"), + render(loadstring("Hello, {{ name }}!")), + render(function(name) { + include("files/greeting.uc", { name }) + }, "Bob") + ]); +%} +-- End -- +-- File greeting.uc -- +Hello, {{ name }} +-- End -- +-- Expect stdout -- +[ + "Test", + "Hello, world!", + "Hello, Bob\n" +] -- End -- |