summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2022-08-29 15:02:31 +0200
committerJo-Philipp Wich <jo@mein.io>2022-08-29 15:02:31 +0200
commit344fa9e69da43ecdc4d8f7768d85d42639352405 (patch)
tree156657bbf8ceb57d7b9af8a6a9f92d829263e2ee
parent89452b20e5073feb28b294a707342ef144f4b5f0 (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.md17
-rw-r--r--lib.c7
-rw-r--r--tests/custom/03_stdlib/36_render74
3 files changed, 72 insertions, 26 deletions
diff --git a/README.md b/README.md
index f09c71d..fba3177 100644
--- a/README.md
+++ b/README.md
@@ -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])`
diff --git a/lib.c b/lib.c
index d5e02a5..5d76cc8 100644
--- a/lib.c
+++ b/lib.c
@@ -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 --