Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Helpers
On top of the [built-in functionality in Handlebars](https://github.com/xp-forge/handlebars), this library includes the following essential helpers:

* `encode`: Performs URL-encoding
* `json`: Performs JSON encoding, pretty-printing when given `format=true`.
* `equals`: Tests arguments for equality
* `contains`: Tests whether a string or array contains a certain value
* `size`: Returns string length or array size
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"xp-forge/frontend": "^7.0 | ^6.0",
"xp-forge/handlebars": "^10.0 | ^9.3",
"xp-forge/yaml": "^9.0 | ^8.0 | ^7.0 | ^6.0",
"xp-forge/json": "^6.0 | ^5.0",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not add a new dependency, xp-forge/json was already required by xp-forge/frontend.

"php": ">=7.4.0"
},
"require-dev" : {
Expand Down
13 changes: 12 additions & 1 deletion src/main/php/web/frontend/helpers/Essentials.class.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<?php namespace web\frontend\helpers;

use Countable;
use text\json\{StringOutput, WrappedFormat};
use util\data\Marshalling;

/** Built-in and automatically loaded essentials */
class Essentials extends Extension {

Expand All @@ -8,6 +12,13 @@ public function helpers() {
yield 'encode' => function($in, $context, $options) {
return rawurlencode($options[0] ?? '');
};
yield 'json' => function($in, $context, $options) {
static $marshalling, $format;

$s= new StringOutput(($options['format'] ?? false) ? ($format??= new WrappedFormat(' ')) : null);
$s->write(($marshalling??= new Marshalling())->marshal($options[0] ?? null));
return $s->bytes();
};
yield 'equals' => function($in, $context, $options) {
return (int)(($options[0] ?? null) === ($options[1] ?? null));
};
Expand All @@ -23,7 +34,7 @@ public function helpers() {
yield 'size' => function($in, $context, $options) {
if (!isset($options[0])) {
return 0;
} else if ($options[0] instanceof \Countable || is_array($options[0])) {
} else if ($options[0] instanceof Countable || is_array($options[0])) {
return sizeof($options[0]);
} else {
return strlen($options[0]);
Expand Down
52 changes: 51 additions & 1 deletion src/test/php/web/frontend/unittest/EssentialsTest.class.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
<?php namespace web\frontend\unittest;

use ArrayIterator, Countable;
use test\{Assert, Test, Values};

class EssentialsTest extends HandlebarsTest {

/** @return iterable */
private function objects() {
yield [['hello' => ['World', true]]];
yield [new class() { public $hello= ['World', true]; }];
}

/** @return iterable */
private function iterables() {
yield [(function() { yield 1; yield 2; yield 3; })()];
yield [new ArrayIterator([1, 2, 3])];
}

#[Test]
public function url_encode() {
Assert::equals(
Expand All @@ -12,6 +25,43 @@ public function url_encode() {
);
}

#[Test]
public function json_string() {
Assert::equals(
'let str = "He said \\"hello \\u4e16\\u754c!\\"\\n";',
$this->transform('let str = {{&json input}};', ['input' => 'He said "hello 世界!"'."\n"])
);
}

#[Test, Values(from: 'objects')]
public function json_object($object) {
Assert::equals(
'let obj = {"hello":["World",true]};',
$this->transform('let obj = {{&json input}};', ['input' => $object])
);
}

#[Test, Values(from: 'iterables')]
public function json_iterable($iterable) {
Assert::equals(
'let it = [1,2,3];',
$this->transform('let it = {{&json input}};', ['input' => $iterable])
);
}

#[Test]
public function formatted_json() {
Assert::equals(
"{\n \"hello\": [\"World\", true]\n}",
$this->transform('{{&json input format=true}}', ['input' => ['hello' => ['World', true]]])
);
}

#[Test, Values([['</script>', '"<\\/script>"'], ['// END', '"\\/\\/ END"'], [['tag' => '</a>'], '{"tag":"<\\/a>"}']])]
public function forward_slashes_escaped($input, $expected) {
Assert::equals($expected, $this->transform('{{&json input}}', ['input' => $input]));
}

#[Test, Values(['{{equals "A" "A"}}', '{{equals "A" a}}', '{{equals a a}}'])]
public function are_equal($template) {
Assert::equals('1', $this->transform($template, ['a' => 'A']));
Expand Down Expand Up @@ -44,7 +94,7 @@ public function size($expr, $expected) {
'test' => 'Test',
'numbers' => [1, 2, 3],
'sizes' => ['S' => 12.99, 'M' => 13.99],
'count' => new class() implements \Countable { public function count(): int { return 1; } },
'count' => new class() implements Countable { public function count(): int { return 1; } },
'empty' => [],
]));
}
Expand Down
Loading