Browse Source

Code formatting

main
Cédric Belin 1 month ago
parent
commit
652c4f5ef7
31 changed files with 955 additions and 946 deletions
  1. +8
    -3
      .editorconfig
  2. +1
    -1
      .github/workflows/build.yaml
  3. +5
    -5
      .vscode/settings.json
  4. +54
    -54
      composer.json
  5. +2
    -2
      doc/installation.md
  6. +11
    -11
      doc/usage.md
  7. +9
    -5
      etc/mkdocs.yaml
  8. +22
    -22
      etc/phpdoc.xml
  9. +3
    -3
      etc/phpstan.neon
  10. +13
    -13
      etc/phpunit.xml
  11. +1
    -1
      example/main.php
  12. +50
    -50
      src/Cache.php
  13. +44
    -44
      src/Helper.php
  14. +51
    -51
      src/Loader.php
  15. +22
    -22
      src/Logger.php
  16. +99
    -99
      src/ViewRenderer.php
  17. +102
    -102
      src/helpers/Format.php
  18. +72
    -72
      src/helpers/Html.php
  19. +44
    -44
      src/helpers/I18N.php
  20. +58
    -58
      src/helpers/Url.php
  21. +17
    -17
      test/CacheTest.php
  22. +50
    -50
      test/HelperTest.php
  23. +13
    -13
      test/LoaderTest.php
  24. +43
    -43
      test/ViewRendererTest.php
  25. +59
    -59
      test/helpers/FormatTest.php
  26. +54
    -54
      test/helpers/HtmlTest.php
  27. +39
    -39
      test/helpers/I18NTest.php
  28. +5
    -5
      test/index.php
  29. +2
    -2
      tool/clean.ps1
  30. +1
    -1
      tool/doc.ps1
  31. +1
    -1
      tool/upgrade.ps1

+ 8
- 3
.editorconfig View File

@@ -3,11 +3,16 @@ root = true

[*]
charset = utf-8
indent_size = 2
indent_style = space
indent_style = tab
insert_final_newline = true
max_line_length = 200
quote_type = double
tab_width = 2
trim_trailing_whitespace = true

[*.md]
indent_size = 4
trim_trailing_whitespace = false

[*.{yaml,yml}]
indent_size = 2
indent_style = space

+ 1
- 1
.github/workflows/build.yaml View File

@@ -3,7 +3,7 @@ on:
pull_request:
push:
schedule:
- cron: '0 0 1 * *'
- cron: "0 0 1 * *"
jobs:
test:
runs-on: ubuntu-latest


+ 5
- 5
.vscode/settings.json View File

@@ -1,7 +1,7 @@
{
"editor.insertSpaces": true,
"editor.tabSize": 2,
"files.encoding": "utf8",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true
"editor.insertSpaces": false,
"editor.tabSize": 2,
"files.encoding": "utf8",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true
}

+ 54
- 54
composer.json View File

@@ -1,56 +1,56 @@
{
"description": "Mustache templating for the Yii Framework.",
"homepage": "https://docs.belin.io/yii2-mustache",
"license": "MIT",
"name": "cedx/yii2-mustache",
"type": "yii2-extension",
"version": "10.1.0",
"authors": [
{"email": "cedric@belin.io", "homepage": "https://belin.io", "name": "Cédric Belin"}
],
"autoload": {
"psr-4": {"yii\\mustache\\": "src/"}
},
"autoload-dev": {
"psr-4": {"yii\\mustache\\": "test/"}
},
"config": {
"fxp-asset": {"enabled": false},
"optimize-autoloader": true
},
"funding": [
{"type": "patreon", "url": "https://www.patreon.com/cedx"}
],
"keywords": [
"mustache",
"renderer",
"templating",
"view",
"yii2"
],
"repositories": [
{"type": "composer", "url": "https://asset-packagist.org"}
],
"require": {
"php": ">=7.4.0",
"ext-json": "*",
"ext-mbstring": "*",
"ext-pcre": "*",
"mustache/mustache": "^2.13.0",
"psr/log": "^1.1.3",
"yiisoft/yii2": "^2.0.35"
},
"require-dev": {
"cedx/coveralls": "^13.0.0",
"phpstan/phpstan": "^0.12.25",
"phpunit/phpunit": "^9.1.4"
},
"scripts": {
"coverage": "coveralls var/coverage.xml",
"test": "phpunit --configuration=etc/phpunit.xml"
},
"support": {
"docs": "https://api.belin.io/yii2-mustache",
"issues": "https://git.belin.io/cedx/yii2-mustache/issues"
}
"description": "Mustache templating for the Yii Framework.",
"homepage": "https://docs.belin.io/yii2-mustache",
"license": "MIT",
"name": "cedx/yii2-mustache",
"type": "yii2-extension",
"version": "10.1.0",
"authors": [
{"email": "cedric@belin.io", "homepage": "https://belin.io", "name": "Cédric Belin"}
],
"autoload": {
"psr-4": {"yii\\mustache\\": "src/"}
},
"autoload-dev": {
"psr-4": {"yii\\mustache\\": "test/"}
},
"config": {
"fxp-asset": {"enabled": false},
"optimize-autoloader": true
},
"funding": [
{"type": "patreon", "url": "https://www.patreon.com/cedx"}
],
"keywords": [
"mustache",
"renderer",
"templating",
"view",
"yii2"
],
"repositories": [
{"type": "composer", "url": "https://asset-packagist.org"}
],
"require": {
"php": ">=7.4.0",
"ext-json": "*",
"ext-mbstring": "*",
"ext-pcre": "*",
"mustache/mustache": "^2.13.0",
"psr/log": "^1.1.3",
"yiisoft/yii2": "^2.0.35"
},
"require-dev": {
"cedx/coveralls": "^13.0.0",
"phpstan/phpstan": "^0.12.25",
"phpunit/phpunit": "^9.1.4"
},
"scripts": {
"coverage": "coveralls var/coverage.xml",
"test": "phpunit --configuration=etc/phpunit.xml"
},
"support": {
"docs": "https://api.belin.io/yii2-mustache",
"issues": "https://git.belin.io/cedx/yii2-mustache/issues"
}
}

+ 2
- 2
doc/installation.md View File

@@ -15,8 +15,8 @@ composer --version
```

!!! info
If you plan to play with the package sources, you will also need the latest versions of
[PowerShell](https://docs.microsoft.com/en-us/powershell) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material).
If you plan to play with the package sources, you will also need the latest versions of
[PowerShell](https://docs.microsoft.com/en-us/powershell) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material).

## Installing with Composer package manager



+ 11
- 11
doc/usage.md View File

@@ -5,14 +5,14 @@ In order to start using [Mustache](https://mustache.github.io) you need to confi

```php
<?php return [
'components' => [
'view' => [
'class' => 'yii\web\View',
'renderers' => [
'mustache' => 'yii\mustache\ViewRenderer'
]
]
]
"components" => [
"view" => [
"class" => "yii\\web\\View",
"renderers" => [
"mustache" => "yii\\mustache\\ViewRenderer"
]
]
]
];
```

@@ -23,9 +23,9 @@ After it's done you can create templates in files that have the `.mustache` exte
use yii\web\{Controller, Response};

class AppController extends Controller {
function actionIndex(): Response {
return $this->render('template.mustache', ['model' => 'The view model']);
}
function actionIndex(): Response {
return $this->render("template.mustache", ["model" => "The view model"]);
}
}
```



+ 9
- 5
etc/mkdocs.yaml View File

@@ -8,19 +8,23 @@ site_dir: ../www

repo_name: git.belin.io
repo_url: https://git.belin.io/cedx/yii2-mustache
edit_uri: ''
edit_uri: ""

copyright: Copyright &copy; 2014 - 2020 Cédric Belin
extra:
social:
- icon: fontawesome/solid/globe
link: 'https://belin.io'
link: "https://belin.io"
name: Belin.io
- icon: fontawesome/brands/github
link: 'https://github.com/cedx'
link: "https://github.com/cedx"
name: GitHub
- icon: fontawesome/brands/twitter
link: 'https://twitter.com/cedxbelin'
link: "https://twitter.com/cedxbelin"
name: Twitter
- icon: fontawesome/brands/linkedin
link: 'https://linkedin.com/in/cedxbelin'
link: "https://linkedin.com/in/cedxbelin"
name: LinkedIn

markdown_extensions:
- admonition


+ 22
- 22
etc/phpdoc.xml View File

@@ -1,25 +1,25 @@
<?xml version="1.0"?>
<phpdocumentor
configVersion="3"
xmlns="http://www.phpdoc.org"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://docs.phpdoc.org/latest/phpdoc.xsd">
<title>Mustache for Yii</title>
<paths>
<cache>../var/phpdoc</cache>
<output>../doc/api</output>
</paths>
<version number="10.1.0">
<api>
<markers>
<marker>TODO</marker>
</markers>
<source dsn="..">
<path>src</path>
</source>
<visibility>protected</visibility>
<visibility>public</visibility>
</api>
</version>
configVersion="3"
xmlns="http://www.phpdoc.org"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://docs.phpdoc.org/latest/phpdoc.xsd">
<title>Mustache for Yii</title>
<paths>
<cache>../var/phpdoc</cache>
<output>../doc/api</output>
</paths>
<version number="10.1.0">
<api>
<markers>
<marker>TODO</marker>
</markers>
<source dsn="..">
<path>src</path>
</source>
<visibility>protected</visibility>
<visibility>public</visibility>
</api>
</version>
</phpdocumentor>

+ 3
- 3
etc/phpstan.neon View File

@@ -1,4 +1,4 @@
parameters:
autoload_files: [../vendor/yiisoft/yii2/Yii.php]
level: max
paths: [../src, ../test]
autoload_files: [../vendor/yiisoft/yii2/Yii.php]
level: max
paths: [../src, ../test]

+ 13
- 13
etc/phpunit.xml View File

@@ -1,18 +1,18 @@
<?xml version="1.0"?>
<phpunit bootstrap="../test/index.php" cacheResult="false" testdox="true">
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../src</directory>
</whitelist>
</filter>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../src</directory>
</whitelist>
</filter>

<logging>
<log type="coverage-clover" target="../var/coverage.xml"/>
</logging>
<logging>
<log type="coverage-clover" target="../var/coverage.xml"/>
</logging>

<testsuites>
<testsuite name="all">
<directory suffix="Test.php">../test</directory>
</testsuite>
</testsuites>
<testsuites>
<testsuite name="all">
<directory suffix="Test.php">../test</directory>
</testsuite>
</testsuites>
</phpunit>

+ 1
- 1
example/main.php View File

@@ -2,5 +2,5 @@

/** TODO */
function main(): void {
// TODO
// TODO
}

+ 50
- 50
src/Cache.php View File

@@ -6,54 +6,54 @@ use yii\base\{BaseObject, InvalidConfigException};
/** Component used to store compiled views to a cache application component. */
class Cache extends BaseObject implements \Mustache_Cache {

/** @var ViewRenderer The instance used to render the views. */
public ?ViewRenderer $viewRenderer = null;
/**
* Caches and loads a compiled view.
* @param string $key The key identifying the view to be cached.
* @param string $value The view to be cached.
*/
function cache($key, $value): void {
assert(is_string($key) && mb_strlen($key) > 0);
/** @var ViewRenderer $viewRenderer */
$viewRenderer = $this->viewRenderer;
if (!$viewRenderer->enableCaching) eval("?>$value");
else {
/** @var \yii\caching\Cache $cache */
$cache = $viewRenderer->cache;
$cache->set([__CLASS__, $key], $value, $viewRenderer->cachingDuration);
$this->load($key);
}
}
/**
* Initializes this object.
* @throws InvalidConfigException The view renderer is not initialized.
*/
function init(): void {
parent::init();
if (!$this->viewRenderer) throw new InvalidConfigException('The view renderer is not initialized.');
}
/**
* Loads a compiled view from cache.
* @param string $key The key identifying the view to be loaded.
* @return bool `true` if the view was successfully loaded, otherwise `false`.
*/
function load($key): bool {
assert(is_string($key) && mb_strlen($key) > 0);
/** @var ViewRenderer $viewRenderer */
$viewRenderer = $this->viewRenderer;
/** @var \yii\caching\Cache $cache */
$cache = $viewRenderer->cache;
$cacheKey = [__CLASS__, $key];
if (!$viewRenderer->enableCaching || !$cache->exists($cacheKey)) return false;
eval("?>{$cache->get($cacheKey)}");
return true;
}
/** @var ViewRenderer The instance used to render the views. */
public ?ViewRenderer $viewRenderer = null;
/**
* Caches and loads a compiled view.
* @param string $key The key identifying the view to be cached.
* @param string $value The view to be cached.
*/
function cache($key, $value): void {
assert(is_string($key) && mb_strlen($key) > 0);
/** @var ViewRenderer $viewRenderer */
$viewRenderer = $this->viewRenderer;
if (!$viewRenderer->enableCaching) eval("?>$value");
else {
/** @var \yii\caching\Cache $cache */
$cache = $viewRenderer->cache;
$cache->set([__CLASS__, $key], $value, $viewRenderer->cachingDuration);
$this->load($key);
}
}
/**
* Initializes this object.
* @throws InvalidConfigException The view renderer is not initialized.
*/
function init(): void {
parent::init();
if (!$this->viewRenderer) throw new InvalidConfigException("The view renderer is not initialized.");
}
/**
* Loads a compiled view from cache.
* @param string $key The key identifying the view to be loaded.
* @return bool `true` if the view was successfully loaded, otherwise `false`.
*/
function load($key): bool {
assert(is_string($key) && mb_strlen($key) > 0);
/** @var ViewRenderer $viewRenderer */
$viewRenderer = $this->viewRenderer;
/** @var \yii\caching\Cache $cache */
$cache = $viewRenderer->cache;
$cacheKey = [__CLASS__, $key];
if (!$viewRenderer->enableCaching || !$cache->exists($cacheKey)) return false;
eval("?>{$cache->get($cacheKey)}");
return true;
}
}

+ 44
- 44
src/Helper.php View File

@@ -7,48 +7,48 @@ use yii\helpers\{ArrayHelper, Json};
/** Provides the abstract base class for a view helper. */
abstract class Helper extends BaseObject {

/** @var string String used to separate the arguments for helpers supporting the "two arguments" syntax. */
public string $argumentSeparator = ':';
/**
* Initializes this object.
* @throws InvalidConfigException The argument separator is empty.
*/
function init(): void {
parent::init();
if (!mb_strlen($this->argumentSeparator)) throw new InvalidConfigException('The argument separator is empty.');
}
/**
* Returns the output sent by the call of the specified function.
* @param callable $callback The function to invoke.
* @return string The captured output.
*/
protected function captureOutput(callable $callback): string {
ob_start();
call_user_func($callback);
return (string) ob_get_clean();
}
/**
* Parses the arguments of a parameterized helper.
* Arguments can be specified as a single value, or as a string in JSON format.
* @param string $text The section content specifying the helper arguments.
* @param string $defaultArgument The name of the default argument. This is used when the section content provides a plain string instead of a JSON object.
* @param array<string, mixed> $defaultValues The default values of arguments. These are used when the section content does not specify all arguments.
* @return array<string, mixed> The parsed arguments as an associative array.
*/
protected function parseArguments(string $text, string $defaultArgument, array $defaultValues = []): array {
assert(mb_strlen($defaultArgument) > 0);
try {
if (ArrayHelper::isAssociative($json = Json::decode($text))) return ArrayHelper::merge($defaultValues, $json);
throw new InvalidArgumentException('The JSON string has an invalid format.');
}
catch (InvalidArgumentException $e) {
$defaultValues[$defaultArgument] = $text;
return $defaultValues;
}
}
/** @var string String used to separate the arguments for helpers supporting the "two arguments" syntax. */
public string $argumentSeparator = ":";
/**
* Initializes this object.
* @throws InvalidConfigException The argument separator is empty.
*/
function init(): void {
parent::init();
if (!mb_strlen($this->argumentSeparator)) throw new InvalidConfigException("The argument separator is empty.");
}
/**
* Returns the output sent by the call of the specified function.
* @param callable $callback The function to invoke.
* @return string The captured output.
*/
protected function captureOutput(callable $callback): string {
ob_start();
call_user_func($callback);
return (string) ob_get_clean();
}
/**
* Parses the arguments of a parameterized helper.
* Arguments can be specified as a single value, or as a string in JSON format.
* @param string $text The section content specifying the helper arguments.
* @param string $defaultArgument The name of the default argument. This is used when the section content provides a plain string instead of a JSON object.
* @param array<string, mixed> $defaultValues The default values of arguments. These are used when the section content does not specify all arguments.
* @return array<string, mixed> The parsed arguments as an associative array.
*/
protected function parseArguments(string $text, string $defaultArgument, array $defaultValues = []): array {
assert(mb_strlen($defaultArgument) > 0);
try {
if (ArrayHelper::isAssociative($json = Json::decode($text))) return ArrayHelper::merge($defaultValues, $json);
throw new InvalidArgumentException("The JSON string has an invalid format.");
}
catch (InvalidArgumentException $e) {
$defaultValues[$defaultArgument] = $text;
return $defaultValues;
}
}
}

+ 51
- 51
src/Loader.php View File

@@ -7,66 +7,66 @@ use yii\helpers\{FileHelper};
/** Loads views from the file system. */
class Loader extends BaseObject implements \Mustache_Loader {

/** @var ViewRenderer The instance used to render the views. */
public ?ViewRenderer $viewRenderer = null;
/** @var ViewRenderer The instance used to render the views. */
public ?ViewRenderer $viewRenderer = null;

/** @var string[] The loaded views. */
private array $views = [];
/** @var string[] The loaded views. */
private array $views = [];

/**
* Initializes this object.
* @throws InvalidConfigException The view renderer is not initialized.
*/
function init(): void {
parent::init();
if (!$this->viewRenderer) throw new InvalidConfigException('The view renderer is not initialized.');
}
/**
* Initializes this object.
* @throws InvalidConfigException The view renderer is not initialized.
*/
function init(): void {
parent::init();
if (!$this->viewRenderer) throw new InvalidConfigException("The view renderer is not initialized.");
}

/**
* Loads the view with the specified name.
* @param string $name The view name.
* @return string The view contents.
* @throws ViewNotFoundException Unable to locate the view file.
*/
function load($name): string {
assert(is_string($name) && mb_strlen($name) > 0);
/**
* Loads the view with the specified name.
* @param string $name The view name.
* @return string The view contents.
* @throws ViewNotFoundException Unable to locate the view file.
*/
function load($name): string {
assert(is_string($name) && mb_strlen($name) > 0);

static $findViewFile;
if (!isset($findViewFile)) {
$findViewFile = (new \ReflectionClass(View::class))->getMethod('findViewFile');
$findViewFile->setAccessible(true);
}
static $findViewFile;
if (!isset($findViewFile)) {
$findViewFile = (new \ReflectionClass(View::class))->getMethod("findViewFile");
$findViewFile->setAccessible(true);
}

if (!isset($this->views[$name])) {
/** @var ViewRenderer $viewRenderer */
$viewRenderer = $this->viewRenderer;
if (!isset($this->views[$name])) {
/** @var ViewRenderer $viewRenderer */
$viewRenderer = $this->viewRenderer;

/** @var \yii\caching\Cache $cache */
$cache = $viewRenderer->cache;
$cacheKey = [__METHOD__, $name];
/** @var \yii\caching\Cache $cache */
$cache = $viewRenderer->cache;
$cacheKey = [__METHOD__, $name];

if ($viewRenderer->enableCaching && $cache->exists($cacheKey)) $output = $cache->get($cacheKey);
else {
/** @var View $view */
$view = $viewRenderer->view;
$path = $findViewFile->invoke($view, $name, $view->context);
if ($view->theme) {
/** @var \yii\base\Theme $theme */
$theme = $view->theme;
$path = $theme->applyTo($path);
}
if ($viewRenderer->enableCaching && $cache->exists($cacheKey)) $output = $cache->get($cacheKey);
else {
/** @var View $view */
$view = $viewRenderer->view;
$path = $findViewFile->invoke($view, $name, $view->context);
if ($view->theme) {
/** @var \yii\base\Theme $theme */
$theme = $view->theme;
$path = $theme->applyTo($path);
}

$fileInfo = new \SplFileInfo($path);
if (!$fileInfo->isFile()) throw new ViewNotFoundException("The view file does not exist: {$fileInfo->getPathname()}");
$fileInfo = new \SplFileInfo($path);
if (!$fileInfo->isFile()) throw new ViewNotFoundException("The view file does not exist: {$fileInfo->getPathname()}");

$fileObject = new \SplFileObject(FileHelper::localize($fileInfo->getPathname()));
$output = (string) $fileObject->fread($fileObject->getSize());
if ($viewRenderer->enableCaching) $cache->set($cacheKey, $output, $viewRenderer->cachingDuration);
}
$fileObject = new \SplFileObject(FileHelper::localize($fileInfo->getPathname()));
$output = (string) $fileObject->fread($fileObject->getSize());
if ($viewRenderer->enableCaching) $cache->set($cacheKey, $output, $viewRenderer->cachingDuration);
}

$this->views[$name] = $output;
}
$this->views[$name] = $output;
}

return $this->views[$name];
}
return $this->views[$name];
}
}

+ 22
- 22
src/Logger.php View File

@@ -7,28 +7,28 @@ use yii\log\{Logger as YiiLogger};

/** Component used to log messages from the view engine to the application logger. */
class Logger extends BaseObject implements LoggerInterface {
use LoggerTrait;
use LoggerTrait;

/** @var int[] Mappings between Mustache levels and Yii ones. */
private static array $levels = [
LogLevel::ALERT => YiiLogger::LEVEL_ERROR,
LogLevel::CRITICAL => YiiLogger::LEVEL_ERROR,
LogLevel::DEBUG => YiiLogger::LEVEL_TRACE,
LogLevel::EMERGENCY => YiiLogger::LEVEL_ERROR,
LogLevel::ERROR => YiiLogger::LEVEL_ERROR,
LogLevel::INFO => YiiLogger::LEVEL_INFO,
LogLevel::NOTICE => YiiLogger::LEVEL_INFO,
LogLevel::WARNING => YiiLogger::LEVEL_WARNING
];
/** @var int[] Mappings between Mustache levels and Yii ones. */
private static array $levels = [
LogLevel::ALERT => YiiLogger::LEVEL_ERROR,
LogLevel::CRITICAL => YiiLogger::LEVEL_ERROR,
LogLevel::DEBUG => YiiLogger::LEVEL_TRACE,
LogLevel::EMERGENCY => YiiLogger::LEVEL_ERROR,
LogLevel::ERROR => YiiLogger::LEVEL_ERROR,
LogLevel::INFO => YiiLogger::LEVEL_INFO,
LogLevel::NOTICE => YiiLogger::LEVEL_INFO,
LogLevel::WARNING => YiiLogger::LEVEL_WARNING
];

/**
* Logs a message.
* @param int $level The logging level.
* @param string $message The message to be logged.
* @param array<string, mixed> $context The log context.
*/
function log($level, $message, array $context = []): void {
assert(isset(self::$levels[$level]));
\Yii::getLogger()->log($message, self::$levels[$level], __METHOD__);
}
/**
* Logs a message.
* @param int $level The logging level.
* @param string $message The message to be logged.
* @param array<string, mixed> $context The log context.
*/
function log($level, $message, array $context = []): void {
assert(isset(self::$levels[$level]));
\Yii::getLogger()->log($message, self::$levels[$level], __METHOD__);
}
}

+ 99
- 99
src/ViewRenderer.php View File

@@ -11,103 +11,103 @@ use yii\helpers\{ArrayHelper, Html};
*/
class ViewRenderer extends \yii\base\ViewRenderer {

/** @var string|array<string, mixed>|\yii\caching\CacheInterface The cache object or the application component ID of the cache object. */
public $cache = 'cache';
/** @var int The time in seconds that the compiled views can remain valid in cache. If set to `0`, the cache never expires. */
public int $cachingDuration = 0;
/** @var bool Value indicating whether to enable caching view templates. */
public bool $enableCaching = false;
/** @var bool Value indicating whether to enable logging engine messages. */
public bool $enableLogging = false;
/** @var View The view object used to render views. */
public ?View $view = null;
/** @var \Mustache_Engine|null The underlying Mustache template engine. */
private ?\Mustache_Engine $engine = null;
/** @var array<string, mixed> The values prepended to the context stack. */
private array $helpers = [];
/**
* Gets the values prepended to the context stack, so they will be available in any view loaded by this instance.
* @return \Mustache_HelperCollection The list of the values prepended to the context stack.
*/
function getHelpers(): \Mustache_HelperCollection {
return $this->engine ? $this->engine->getHelpers() : new \Mustache_HelperCollection($this->helpers);
}
/** Initializes the application component.*/
function init(): void {
$helpers = [
'app' => \Yii::$app,
'format' => new helpers\Format,
'html' => new helpers\Html,
'i18n' => new helpers\I18N,
'url' => new helpers\Url
];
$options = [
'charset' => \Yii::$app->charset,
'entity_flags' => ENT_QUOTES | ENT_SUBSTITUTE,
'escape' => [Html::class, 'encode'],
'helpers' => ArrayHelper::merge($helpers, $this->helpers),
'partials_loader' => new Loader(['viewRenderer' => $this]),
'strict_callables' => true
];
if ($this->enableCaching) {
/** @var \yii\caching\Cache $cache */
$cache = Instance::ensure($this->cache, \yii\caching\Cache::class);
$this->cache = $cache;
$options['cache'] = new Cache(['viewRenderer' => $this]);
}
if ($this->enableLogging) $options['logger'] = new Logger;
$this->engine = new \Mustache_Engine($options);
parent::init();
}
/**
* Renders a view file.
* @param View $view The view object used for rendering the file.
* @param string $file The view file.
* @param array<string, mixed> $params The parameters to be passed to the view file.
* @return string The rendering result.
*/
function render($view, $file, $params = []): string {
assert($view instanceof View);
assert(is_string($file) && mb_strlen($file) > 0);
$this->view = $view;
/** @var \yii\caching\Cache $cache */
$cache = $this->cache;
$cacheKey = [__METHOD__, $file];
if ($this->enableCaching && $cache->exists($cacheKey)) $output = $cache->get($cacheKey);
else {
$fileObject = new \SplFileObject($file);
$output = (string) $fileObject->fread($fileObject->getSize());
if ($this->enableCaching) $cache->set($cacheKey, $output, $this->cachingDuration);
}
/** @var \Mustache_Engine $engine */
$engine = $this->engine;
$values = ArrayHelper::merge(['this' => $this->view], $params);
return $engine->render($output, $values);
}
/**
* Sets the values to prepend to the context stack, so they will be available in any view loaded by this instance.
* @param array<string, mixed> $value The list of the values to prepend to the context stack.
* @return $this This instance.
*/
function setHelpers(array $value): self {
if ($this->engine) $this->engine->setHelpers($value);
else $this->helpers = $value;
return $this;
}
/** @var string|array<string, mixed>|\yii\caching\CacheInterface The cache object or the application component ID of the cache object. */
public $cache = "cache";
/** @var int The time in seconds that the compiled views can remain valid in cache. If set to `0`, the cache never expires. */
public int $cachingDuration = 0;
/** @var bool Value indicating whether to enable caching view templates. */
public bool $enableCaching = false;
/** @var bool Value indicating whether to enable logging engine messages. */
public bool $enableLogging = false;
/** @var View The view object used to render views. */
public ?View $view = null;
/** @var \Mustache_Engine|null The underlying Mustache template engine. */
private ?\Mustache_Engine $engine = null;
/** @var array<string, mixed> The values prepended to the context stack. */
private array $helpers = [];
/**
* Gets the values prepended to the context stack, so they will be available in any view loaded by this instance.
* @return \Mustache_HelperCollection The list of the values prepended to the context stack.
*/
function getHelpers(): \Mustache_HelperCollection {
return $this->engine ? $this->engine->getHelpers() : new \Mustache_HelperCollection($this->helpers);
}
/** Initializes the application component.*/
function init(): void {
$helpers = [
"app" => \Yii::$app,
"format" => new helpers\Format,
"html" => new helpers\Html,
"i18n" => new helpers\I18N,
"url" => new helpers\Url
];
$options = [
"charset" => \Yii::$app->charset,
"entity_flags" => ENT_QUOTES | ENT_SUBSTITUTE,
"escape" => [Html::class, "encode"],
"helpers" => ArrayHelper::merge($helpers, $this->helpers),
"partials_loader" => new Loader(["viewRenderer" => $this]),
"strict_callables" => true
];
if ($this->enableCaching) {
/** @var \yii\caching\Cache $cache */
$cache = Instance::ensure($this->cache, \yii\caching\Cache::class);
$this->cache = $cache;
$options["cache"] = new Cache(["viewRenderer" => $this]);
}
if ($this->enableLogging) $options["logger"] = new Logger;
$this->engine = new \Mustache_Engine($options);
parent::init();
}
/**
* Renders a view file.
* @param View $view The view object used for rendering the file.
* @param string $file The view file.
* @param array<string, mixed> $params The parameters to be passed to the view file.
* @return string The rendering result.
*/
function render($view, $file, $params = []): string {
assert($view instanceof View);
assert(is_string($file) && mb_strlen($file) > 0);
$this->view = $view;
/** @var \yii\caching\Cache $cache */
$cache = $this->cache;
$cacheKey = [__METHOD__, $file];
if ($this->enableCaching && $cache->exists($cacheKey)) $output = $cache->get($cacheKey);
else {
$fileObject = new \SplFileObject($file);
$output = (string) $fileObject->fread($fileObject->getSize());
if ($this->enableCaching) $cache->set($cacheKey, $output, $this->cachingDuration);
}
/** @var \Mustache_Engine $engine */
$engine = $this->engine;
$values = ArrayHelper::merge(["this" => $this->view], $params);
return $engine->render($output, $values);
}
/**
* Sets the values to prepend to the context stack, so they will be available in any view loaded by this instance.
* @param array<string, mixed> $value The list of the values to prepend to the context stack.
* @return $this This instance.
*/
function setHelpers(array $value): self {
if ($this->engine) $this->engine->setHelpers($value);
else $this->helpers = $value;
return $this;
}
}

+ 102
- 102
src/helpers/Format.php View File

@@ -18,114 +18,114 @@ use yii\mustache\{Helper};
*/
class Format extends Helper {

/**
* Returns a helper function formatting a value as boolean.
* @return \Closure A function formatting a value as boolean.
*/
function getBoolean(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
return $value === null ? $formatter->nullDisplay : Html::encode($formatter->asBoolean($helper->render($value)));
};
}
/**
* Returns a helper function formatting a value as boolean.
* @return \Closure A function formatting a value as boolean.
*/
function getBoolean(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
return $value === null ? $formatter->nullDisplay : Html::encode($formatter->asBoolean($helper->render($value)));
};
}

/**
* Returns a helper function formatting a value as currency number.
* @return \Closure A function formatting a value as currency number.
*/
function getCurrency(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), 'value', ['currency' => null, 'options' => [], 'textOptions' => []]);
return Html::encode($formatter->asCurrency($args['value'], $args['currency'], $args['options'], $args['textOptions']));
};
}
/**
* Returns a helper function formatting a value as currency number.
* @return \Closure A function formatting a value as currency number.
*/
function getCurrency(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), "value", ["currency" => null, "options" => [], "textOptions" => []]);
return Html::encode($formatter->asCurrency($args["value"], $args["currency"], $args["options"], $args["textOptions"]));
};
}

/**
* Returns a helper function formatting a value as date.
* @return \Closure A function formatting a value as date.
*/
function getDate(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), 'value', ['format' => null]);
return Html::encode($formatter->asDate($args['value'], $args['format']));
};
}
/**
* Returns a helper function formatting a value as date.
* @return \Closure A function formatting a value as date.
*/
function getDate(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), "value", ["format" => null]);
return Html::encode($formatter->asDate($args["value"], $args["format"]));
};
}

/**
* Returns a helper function formatting a value as datetime.
* @return \Closure A function formatting a value as datetime.
*/
function getDateTime(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), 'value', ['format' => null]);
return Html::encode($formatter->asDatetime($args['value'], $args['format']));
};
}
/**
* Returns a helper function formatting a value as datetime.
* @return \Closure A function formatting a value as datetime.
*/
function getDateTime(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), "value", ["format" => null]);
return Html::encode($formatter->asDatetime($args["value"], $args["format"]));
};
}

/**
* Returns a helper function formatting a value as decimal number.
* @return \Closure A function formatting a value as decimal number.
*/
function getDecimal(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), 'value', ['decimals' => null, 'options' => [], 'textOptions' => []]);
return Html::encode($formatter->asDecimal($args['value'], $args['decimals'], $args['options'], $args['textOptions']));
};
}
/**
* Returns a helper function formatting a value as decimal number.
* @return \Closure A function formatting a value as decimal number.
*/
function getDecimal(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), "value", ["decimals" => null, "options" => [], "textOptions" => []]);
return Html::encode($formatter->asDecimal($args["value"], $args["decimals"], $args["options"], $args["textOptions"]));
};
}

/**
* Returns a helper function formatting a value as integer number by removing any decimal digits without rounding.
* @return \Closure A function formatting a value as integer number without rounding.
*/
function getInteger(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), 'value', ['options' => [], 'textOptions' => []]);
return Html::encode($formatter->asInteger($args['value'], $args['options'], $args['textOptions']));
};
}
/**
* Returns a helper function formatting a value as integer number by removing any decimal digits without rounding.
* @return \Closure A function formatting a value as integer number without rounding.
*/
function getInteger(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), "value", ["options" => [], "textOptions" => []]);
return Html::encode($formatter->asInteger($args["value"], $args["options"], $args["textOptions"]));
};
}

/**
* Returns a helper function formatting a value as HTML-encoded plain text with newlines converted into breaks.
* @return \Closure A function formatting a value as HTML-encoded text with newlines converted into breaks.
*/
function getNtext(): \Closure {
return fn($value, \Mustache_LambdaHelper $helper) =>
$value === null ? \Yii::$app->getFormatter()->nullDisplay : preg_replace('/\r?\n/', '<br>', Html::encode($helper->render($value)));
}
/**
* Returns a helper function formatting a value as HTML-encoded plain text with newlines converted into breaks.
* @return \Closure A function formatting a value as HTML-encoded text with newlines converted into breaks.
*/
function getNtext(): \Closure {
return fn($value, \Mustache_LambdaHelper $helper) =>
$value === null ? \Yii::$app->getFormatter()->nullDisplay : preg_replace('/\r?\n/', "<br>", Html::encode($helper->render($value)));
}

/**
* Returns a helper function formatting a value as percent number with `%` sign.
* @return \Closure A function formatting a value as percent number.
*/
function getPercent(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), 'value', ['decimals' => null, 'options' => [], 'textOptions' => []]);
return Html::encode($formatter->asPercent($args['value'], $args['decimals'], $args['options'], $args['textOptions']));
};
}
/**
* Returns a helper function formatting a value as percent number with `%` sign.
* @return \Closure A function formatting a value as percent number.
*/
function getPercent(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), "value", ["decimals" => null, "options" => [], "textOptions" => []]);
return Html::encode($formatter->asPercent($args["value"], $args["decimals"], $args["options"], $args["textOptions"]));
};
}

/**
* Returns a helper function formatting a value as time.
* @return \Closure A function formatting a value as time.
*/
function getTime(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), 'value', ['format' => null]);
return Html::encode($formatter->asTime($args['value'], $args['format']));
};
}
/**
* Returns a helper function formatting a value as time.
* @return \Closure A function formatting a value as time.
*/
function getTime(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$formatter = \Yii::$app->getFormatter();
if ($value === null) return $formatter->nullDisplay;
$args = $this->parseArguments($helper->render($value), "value", ["format" => null]);
return Html::encode($formatter->asTime($args["value"], $args["format"]));
};
}
}

+ 72
- 72
src/helpers/Html.php View File

@@ -17,82 +17,82 @@ use yii\widgets\{Spaceless};
*/
class Html extends Helper {

/**
* Returns the tag marking the beginning of an HTML body section.
* @return string The tag marking the beginning of an HTML body section.
*/
function getBeginBody(): string {
/** @var \yii\web\View|null $view */
$view = \Yii::$app->getView();
if (!$view || !$view->hasMethod('beginBody')) return '';
return $this->captureOutput([$view, 'beginBody']);
}
/**
* Returns the tag marking the beginning of an HTML body section.
* @return string The tag marking the beginning of an HTML body section.
*/
function getBeginBody(): string {
/** @var \yii\web\View|null $view */
$view = \Yii::$app->getView();
if (!$view || !$view->hasMethod("beginBody")) return "";
return $this->captureOutput([$view, "beginBody"]);
}

/**
* Returns the tag marking the ending of an HTML body section.
* @return string The tag marking the ending of an HTML body section.
*/
function getEndBody(): string {
/** @var \yii\web\View|null $view */
$view = \Yii::$app->getView();
if (!$view || !$view->hasMethod('endBody')) return '';
return $this->captureOutput([$view, 'endBody']);
}
/**
* Returns the tag marking the ending of an HTML body section.
* @return string The tag marking the ending of an HTML body section.
*/
function getEndBody(): string {
/** @var \yii\web\View|null $view */
$view = \Yii::$app->getView();
if (!$view || !$view->hasMethod("endBody")) return "";
return $this->captureOutput([$view, "endBody"]);
}

/**
* Returns the tag marking the position of an HTML head section.
* @return string The tag marking the position of an HTML head section.
*/
function getHead(): string {
/** @var \yii\web\View|null $view */
$view = \Yii::$app->getView();
if (!$view || !$view->hasMethod('head')) return '';
return $this->captureOutput([$view, 'head']);
}
/**
* Returns the tag marking the position of an HTML head section.
* @return string The tag marking the position of an HTML head section.
*/
function getHead(): string {
/** @var \yii\web\View|null $view */
$view = \Yii::$app->getView();
if (!$view || !$view->hasMethod("head")) return "";
return $this->captureOutput([$view, "head"]);
}

/**
* Returns a function converting Markdown into HTML.
* @return \Closure A function converting Markdown into HTML.
*/
function getMarkdown(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$args = $this->parseArguments($helper->render($value), 'markdown', ['flavor' => Markdown::$defaultFlavor]);
return Markdown::process($args['markdown'], $args['flavor']);
};
}
/**
* Returns a function converting Markdown into HTML.
* @return \Closure A function converting Markdown into HTML.
*/
function getMarkdown(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$args = $this->parseArguments($helper->render($value), "markdown", ["flavor" => Markdown::$defaultFlavor]);
return Markdown::process($args["markdown"], $args["flavor"]);
};
}

/**
* Returns a function converting Markdown into HTML but only parsing inline elements.
* @return \Closure A function converting Markdown into HTML but only parsing inline elements.
*/
function getMarkdownParagraph(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$args = $this->parseArguments($helper->render($value), 'markdown', ['flavor' => Markdown::$defaultFlavor]);
return Markdown::processParagraph($args['markdown'], $args['flavor']);
};
}
/**
* Returns a function converting Markdown into HTML but only parsing inline elements.
* @return \Closure A function converting Markdown into HTML but only parsing inline elements.
*/
function getMarkdownParagraph(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$args = $this->parseArguments($helper->render($value), "markdown", ["flavor" => Markdown::$defaultFlavor]);
return Markdown::processParagraph($args["markdown"], $args["flavor"]);
};
}

/**
* Returns a function removing whitespace characters between HTML tags.
* @return \Closure A function removing whitespaces between HTML tags.
*/
function getSpaceless(): \Closure {
return fn($value, \Mustache_LambdaHelper $helper) => $this->captureOutput(function() use ($helper, $value) {
Spaceless::begin();
echo $helper->render($value);
Spaceless::end();
});
}
/**
* Returns a function removing whitespace characters between HTML tags.
* @return \Closure A function removing whitespaces between HTML tags.
*/
function getSpaceless(): \Closure {
return fn($value, \Mustache_LambdaHelper $helper) => $this->captureOutput(function() use ($helper, $value) {
Spaceless::begin();
echo $helper->render($value);
Spaceless::end();
});
}

/**
* Returns a function setting the view title.
* @return \Closure A function setting the view title.
*/
function getViewTitle(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
/** @var \yii\web\View|null $view */
$view = \Yii::$app->getView();
if ($view && $view->canSetProperty('title')) $view->title = trim($helper->render($value));
};
}
/**
* Returns a function setting the view title.
* @return \Closure A function setting the view title.
*/
function getViewTitle(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
/** @var \yii\web\View|null $view */
$view = \Yii::$app->getView();
if ($view && $view->canSetProperty("title")) $view->title = trim($helper->render($value));
};
}
}

+ 44
- 44
src/helpers/I18N.php View File

@@ -12,54 +12,54 @@ use yii\mustache\{Helper};
*/
class I18N extends Helper {

/** @var string The default message category when no one is supplied. */
public string $defaultCategory = 'app';
/** @var string The default message category when no one is supplied. */
public string $defaultCategory = "app";

/**
* Returns a function translating a message.
* @return \Closure A function translating a message.
*/
function getT(): \Closure {
return $this->getTranslate();
}
/**
* Returns a function translating a message.
* @return \Closure A function translating a message.
*/
function getT(): \Closure {
return $this->getTranslate();
}

/**
* Returns a function translating a message.
* @return \Closure A function translating a message.
* @throws InvalidCallException The specified message has an invalid format.
*/
function getTranslate(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$defaultArgs = [
'category' => $this->defaultCategory,
'language' => null,
'params' => []
];
/**
* Returns a function translating a message.
* @return \Closure A function translating a message.
* @throws InvalidCallException The specified message has an invalid format.
*/
function getTranslate(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$defaultArgs = [
"category" => $this->defaultCategory,
"language" => null,
"params" => []
];

$output = trim($value);
$isJSON = StringHelper::startsWith($output, '{') && StringHelper::endsWith($output, '}');
if ($isJSON) $args = $this->parseArguments($helper->render($value), 'message', $defaultArgs);
else {
$parts = explode($this->argumentSeparator, $output, 2) ?: [];
$length = count($parts);
if (!$length) throw new InvalidCallException('Invalid translation format.');
$output = trim($value);
$isJSON = StringHelper::startsWith($output, "{") && StringHelper::endsWith($output, "}");
if ($isJSON) $args = $this->parseArguments($helper->render($value), "message", $defaultArgs);
else {
$parts = explode($this->argumentSeparator, $output, 2) ?: [];
$length = count($parts);
if (!$length) throw new InvalidCallException("Invalid translation format.");

$args = ArrayHelper::merge($defaultArgs, [
'category' => $length == 1 ? $this->defaultCategory : rtrim($parts[0]),
'message' => $helper->render(ltrim($parts[$length - 1]))
]);
}
$args = ArrayHelper::merge($defaultArgs, [
"category" => $length == 1 ? $this->defaultCategory : rtrim($parts[0]),
"message" => $helper->render(ltrim($parts[$length - 1]))
]);
}

return \Yii::t($args['category'], $args['message'], $args['params'], $args['language']);
};
}
return \Yii::t($args["category"], $args["message"], $args["params"], $args["language"]);
};
}

/**
* Initializes this object.
* @throws InvalidConfigException The argument separator is empty.
*/
function init(): void {
parent::init();
if (!mb_strlen($this->defaultCategory)) throw new InvalidConfigException('The default message category is empty.');
}
/**
* Initializes this object.
* @throws InvalidConfigException The argument separator is empty.
*/
function init(): void {
parent::init();
if (!mb_strlen($this->defaultCategory)) throw new InvalidConfigException("The default message category is empty.");
}
}

+ 58
- 58
src/helpers/Url.php View File

@@ -16,68 +16,68 @@ use yii\mustache\{Helper};
*/
class Url extends Helper {

/**
* Returns a function returning the base URL of the current request.
* @return \Closure A function returning the base URL of the current request.
*/
function getBase(): \Closure {
return fn($value, \Mustache_LambdaHelper $helper) => UrlHelper::base($helper->render($value) ?: false);
}
/**
* Returns a function returning the base URL of the current request.
* @return \Closure A function returning the base URL of the current request.
*/
function getBase(): \Closure {
return fn($value, \Mustache_LambdaHelper $helper) => UrlHelper::base($helper->render($value) ?: false);
}

/**
* Returns the canonical URL of the currently requested page.
* @return string The canonical URL of the currently requested page.
*/
function getCanonical(): string {
return UrlHelper::canonical();
}
/**
* Returns the canonical URL of the currently requested page.
* @return string The canonical URL of the currently requested page.
*/
function getCanonical(): string {
return UrlHelper::canonical();
}

/**
* Returns a function creating a URL by using the current route and the GET parameters.
* @return \Closure A function creating a URL by using the current route and the GET parameters.
*/
function getCurrent(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$args = $this->parseArguments($helper->render($value), 'params', ['scheme' => false]);
return UrlHelper::current($args['params'], $args['scheme']);
};
}
/**
* Returns a function creating a URL by using the current route and the GET parameters.
* @return \Closure A function creating a URL by using the current route and the GET parameters.
*/
function getCurrent(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$args = $this->parseArguments($helper->render($value), "params", ["scheme" => false]);
return UrlHelper::current($args["params"], $args["scheme"]);
};
}

/**
* Returns a function returning the home URL.
* @return \Closure A function returning the home URL.
*/
function getHome(): \Closure {
return fn($value, \Mustache_LambdaHelper $helper) => UrlHelper::home($helper->render($value) ?: false);
}
/**
* Returns a function returning the home URL.
* @return \Closure A function returning the home URL.
*/
function getHome(): \Closure {
return fn($value, \Mustache_LambdaHelper $helper) => UrlHelper::home($helper->render($value) ?: false);
}

/**
* Returns a function returning the URL previously remembered.
* @return \Closure A function returning the URL previously remembered.
*/
function getPrevious(): \Closure {
return fn($value, \Mustache_LambdaHelper $helper) => UrlHelper::previous($helper->render($value) ?: null);
}
/**
* Returns a function returning the URL previously remembered.
* @return \Closure A function returning the URL previously remembered.
*/
function getPrevious(): \Closure {
return fn($value, \Mustache_LambdaHelper $helper) => UrlHelper::previous($helper->render($value) ?: null);
}

/**
* Returns a function creating a URL based on the given parameters.
* @return \Closure A function creating a URL based on the given parameters.
*/
function getTo(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$args = $this->parseArguments($helper->render($value), 'url', ['scheme' => false]);
return UrlHelper::to($args['url'], $args['scheme']);
};
}
/**
* Returns a function creating a URL based on the given parameters.
* @return \Closure A function creating a URL based on the given parameters.
*/
function getTo(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$args = $this->parseArguments($helper->render($value), "url", ["scheme" => false]);
return UrlHelper::to($args["url"], $args["scheme"]);
};
}

/**
* Returns a function creating a URL for the given route.
* @return \Closure A function creating a URL for the given route.
*/
function getToRoute(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$args = $this->parseArguments($helper->render($value), 'route', ['scheme' => false]);
return UrlHelper::toRoute($args['route'], $args['scheme']);
};
}
/**
* Returns a function creating a URL for the given route.
* @return \Closure A function creating a URL for the given route.
*/
function getToRoute(): \Closure {
return function($value, \Mustache_LambdaHelper $helper) {
$args = $this->parseArguments($helper->render($value), "route", ["scheme" => false]);
return UrlHelper::toRoute($args["route"], $args["scheme"]);
};
}
}

+ 17
- 17
test/CacheTest.php View File

@@ -7,24 +7,24 @@ use function PHPUnit\Framework\{assertThat, isFalse, isTrue};
/** @testdox yii\mustache\Cache */
class CacheTest extends TestCase {

/** @var Cache The data context of the tests. */
private Cache $model;
/** @var Cache The data context of the tests. */
private Cache $model;

/** @testdox ->cache() */
function testCache(): void {
// It should evaluate the PHP code put in cache.
$this->model->cache('key', '<?php class YiiMustacheTemplateTestModel {}');
assertThat(class_exists('YiiMustacheTemplateTestModel'), isTrue());
}
/** @testdox ->cache() */
function testCache(): void {
// It should evaluate the PHP code put in cache.
$this->model->cache("key", "<?php class YiiMustacheTemplateTestModel {}");
assertThat(class_exists("YiiMustacheTemplateTestModel"), isTrue());
}

/** @testdox ->load() */
function testLoad(): void {
// It should return `false` for an unknown key.
assertThat($this->model->load('key'), isFalse());
}
/** @testdox ->load() */
function testLoad(): void {
// It should return `false` for an unknown key.
assertThat($this->model->load("key"), isFalse());
}

/** @before This method is called before each test. */
protected function setUp(): void {
$this->model = new Cache(['viewRenderer' => new ViewRenderer]);
}
/** @before This method is called before each test. */
protected function setUp(): void {
$this->model = new Cache(["viewRenderer" => new ViewRenderer]);
}
}

+ 50
- 50
test/HelperTest.php View File

@@ -8,54 +8,54 @@ use function PHPUnit\Framework\{assertThat, equalTo};
/** @testdox yii\mustache\Helper */
class HelperTest extends TestCase {

/** @var \ReflectionClass<Helper> The object used to change the visibility of inaccessible class members. */
private static \ReflectionClass $reflection;
/** @var MockObject The data context of the tests. */
private MockObject $model;
/** @beforeClass This method is called before the first test of this test class is run. */
static function setUpBeforeClass(): void {
self::$reflection = new \ReflectionClass(Helper::class);
}
/** @testdox ->captureOutput() */
function testCaptureOutput(): void {
$method = self::$reflection->getMethod('captureOutput');
$method->setAccessible(true);
// It should return the content of the output buffer.
assertThat($method->invoke($this->model, function() { echo 'Hello World!'; }), equalTo('Hello World!'));
}
/** @testdox ->parseArguments() */
function testParseArguments(): void {
$method = self::$reflection->getMethod('parseArguments');
$method->setAccessible(true);
// It should transform a single value into an array.
$expected = ['foo' => 'FooBar'];
assertThat($method->invoke($this->model, 'FooBar', 'foo'), equalTo($expected));
$expected = ['foo' => 'FooBar', 'bar' => ['baz' => false]];
assertThat($method->invoke($this->model, 'FooBar', 'foo', ['bar' => ['baz' => false]]), equalTo($expected));
// It should transform a JSON string into an array.
$data = '{
"foo": "FooBar",
"bar": {"baz": true}
}';
$expected = ['foo' => 'FooBar', 'bar' => ['baz' => true], 'BarFoo' => [123, 456]];
assertThat($method->invoke($this->model, $data, 'foo', ['BarFoo' => [123, 456]]), equalTo($expected));
$data = '{"foo": [123, 456]}';
$expected = ['foo' => [123, 456], 'bar' => ['baz' => false]];
assertThat($method->invoke($this->model, $data, 'foo', ['bar' => ['baz' => false]]), equalTo($expected));
}
/** @before This method is called before each test. */
protected function setUp(): void {
$this->model = $this->getMockForAbstractClass(Helper::class);
}
/** @var \ReflectionClass<Helper> The object used to change the visibility of inaccessible class members. */
private static \ReflectionClass $reflection;
/** @var MockObject The data context of the tests. */
private MockObject $model;
/** @beforeClass This method is called before the first test of this test class is run. */
static function setUpBeforeClass(): void {
self::$reflection = new \ReflectionClass(Helper::class);
}
/** @testdox ->captureOutput() */
function testCaptureOutput(): void {
$method = self::$reflection->getMethod("captureOutput");
$method->setAccessible(true);
// It should return the content of the output buffer.
assertThat($method->invoke($this->model, function() { echo "Hello World!"; }), equalTo("Hello World!"));
}
/** @testdox ->parseArguments() */
function testParseArguments(): void {
$method = self::$reflection->getMethod("parseArguments");
$method->setAccessible(true);
// It should transform a single value into an array.
$expected = ["foo" => "FooBar"];
assertThat($method->invoke($this->model, "FooBar", "foo"), equalTo($expected));
$expected = ["foo" => "FooBar", "bar" => ["baz" => false]];
assertThat($method->invoke($this->model, "FooBar", "foo", ["bar" => ["baz" => false]]), equalTo($expected));
// It should transform a JSON string into an array.
$data = '{
"foo": "FooBar",
"bar": {"baz": true}
}';
$expected = ["foo" => "FooBar", "bar" => ["baz" => true], "BarFoo" => [123, 456]];
assertThat($method->invoke($this->model, $data, "foo", ["BarFoo" => [123, 456]]), equalTo($expected));
$data = '{"foo": [123, 456]}';
$expected = ["foo" => [123, 456], "bar" => ["baz" => false]];
assertThat($method->invoke($this->model, $data, "foo", ["bar" => ["baz" => false]]), equalTo($expected));
}
/** @before This method is called before each test. */
protected function setUp(): void {
$this->model = $this->getMockForAbstractClass(Helper::class);
}
}

+ 13
- 13
test/LoaderTest.php View File

@@ -7,19 +7,19 @@ use yii\base\{View, ViewNotFoundException};
/** @testdox yii\mustache\Loader */
class LoaderTest extends TestCase {

/** @var Loader The data context of the tests. */
private Loader $model;
/** @var Loader The data context of the tests. */
private Loader $model;

/** @testdox ->load() */
function testLoad(): void {
// It should throw an exception if the view file is not found.
$this->expectException(ViewNotFoundException::class);
$this->model->load('//view');
}
/** @testdox ->load() */
function testLoad(): void {
// It should throw an exception if the view file is not found.
$this->expectException(ViewNotFoundException::class);
$this->model->load("//view");
}

/** @before This method is called before each test. */
protected function setUp(): void {
$viewRenderer = new ViewRenderer(['view' => new View]);
$this->model = new Loader(['viewRenderer' => $viewRenderer]);
}
/** @before This method is called before each test. */
protected function setUp(): void {
$viewRenderer = new ViewRenderer(["view" => new View]);
$this->model = new Loader(["viewRenderer" => $viewRenderer]);
}
}

+ 43
- 43
test/ViewRendererTest.php View File

@@ -8,47 +8,47 @@ use function PHPUnit\Framework\{assertThat, equalTo, isInstanceOf, isTrue};
/** @testdox yii\mustache\ViewRenderer */
class ViewRendererTest extends TestCase {

/** @var ViewRenderer The data context of the tests. */
private ViewRenderer $model;
/** @testdox ->getHelpers() */
function testGetHelpers(): void {
// It should return a Mustache helper collection.
assertThat($this->model->getHelpers(), isInstanceOf(\Mustache_HelperCollection::class));
}
/** @testdox ->render() */
function testRender(): void {
$file = __DIR__.'/fixtures/data.mustache';
// It should remove placeholders when there is no corresponding binding.
$data = [];
[$line1, $line2, $line3, $line4] = preg_split('/\r?\n/', $this->model->render(new View, $file, $data)) ?: [];
assertThat($line1, equalTo('<test></test>'));
assertThat($line2, equalTo('<test></test>'));
assertThat($line3, equalTo('<test></test>'));
assertThat($line4, equalTo('<test>hidden</test>'));
// It should replace placeholders with the proper values when there is a corresponding binding.
$data = ['label' => '"Mustache"', 'show' => true];
[$line1, $line2, $line3, $line4] = preg_split('/\r?\n/', $this->model->render(new View, $file, $data)) ?: [];
assertThat($line1, equalTo('<test>&quot;Mustache&quot;</test>'));
assertThat($line2, equalTo('<test>"Mustache"</test>'));
assertThat($line3, equalTo('<test>visible</test>'));
assertThat($line4, equalTo('<test></test>'));
}
/** @testdox ->setHelpers() */
function testSetHelpers(): void {
// It should allow arrays as input.
$this->model->setHelpers(['var' => 'value']);
$helpers = $this->model->getHelpers();
assertThat($helpers->has('var'), isTrue());
assertThat($helpers->get('var'), equalTo('value'));
}
/** @before This method is called before each test. */
protected function setUp(): void {
$this->model = new ViewRenderer;
}
/** @var ViewRenderer The data context of the tests. */
private ViewRenderer $model;
/** @testdox ->getHelpers() */
function testGetHelpers(): void {
// It should return a Mustache helper collection.
assertThat($this->model->getHelpers(), isInstanceOf(\Mustache_HelperCollection::class));
}
/** @testdox ->render() */
function testRender(): void {
$file = __DIR__."/fixtures/data.mustache";
// It should remove placeholders when there is no corresponding binding.
$data = [];
[$line1, $line2, $line3, $line4] = preg_split('/\r?\n/', $this->model->render(new View, $file, $data)) ?: [];
assertThat($line1, equalTo("<test></test>"));
assertThat($line2, equalTo("<test></test>"));
assertThat($line3, equalTo("<test></test>"));
assertThat($line4, equalTo("<test>hidden</test>"));
// It should replace placeholders with the proper values when there is a corresponding binding.
$data = ["label" => '"Mustache"', "show" => true];
[$line1, $line2, $line3, $line4] = preg_split('/\r?\n/', $this->model->render(new View, $file, $data)) ?: [];
assertThat($line1, equalTo("<test>&quot;Mustache&quot;</test>"));
assertThat($line2, equalTo('<test>"Mustache"</test>'));
assertThat($line3, equalTo("<test>visible</test>"));
assertThat($line4, equalTo("<test></test>"));
}
/** @testdox ->setHelpers() */
function testSetHelpers(): void {
// It should allow arrays as input.
$this->model->setHelpers(["var" => "value"]);
$helpers = $this->model->getHelpers();
assertThat($helpers->has("var"), isTrue());
assertThat($helpers->get("var"), equalTo("value"));
}
/** @before This method is called before each test. */
protected function setUp(): void {
$this->model = new ViewRenderer;
}
}

+ 59
- 59
test/helpers/FormatTest.php View File

@@ -7,72 +7,72 @@ use function PHPUnit\Framework\{assertThat, equalTo, logicalOr};
/** @testdox yii\mustache\helpers\Format */
class FormatTest extends TestCase {

/** @var \Mustache_LambdaHelper The engine used to render strings. */
private \Mustache_LambdaHelper $helper;
/** @var \Mustache_LambdaHelper The engine used to render strings. */
private \Mustache_LambdaHelper $helper;

/** @testdox ->getBoolean() */
function testGetBoolean(): void {
// It should return "No" for a falsy value.
$closure = (new Format)->getBoolean();
assertThat($closure(false, $this->helper), equalTo('No'));
assertThat($closure(0, $this->helper), equalTo('No'));
/** @testdox ->getBoolean() */
function testGetBoolean(): void {
// It should return "No" for a falsy value.
$closure = (new Format)->getBoolean();
assertThat($closure(false, $this->helper), equalTo("No"));
assertThat($closure(0, $this->helper), equalTo("No"));

// It should return "Yes" for a truthy value.
$closure = (new Format)->getBoolean();
assertThat($closure(true, $this->helper), equalTo('Yes'));
assertThat($closure(1, $this->helper), equalTo('Yes'));
}
// It should return "Yes" for a truthy value.
$closure = (new Format)->getBoolean();
assertThat($closure(true, $this->helper), equalTo("Yes"));
assertThat($closure(1, $this->helper), equalTo("Yes"));
}

/** @testdox ->getCurrency() */
function testGetCurrency(): void {
// It should format the specified value as a currency.
$closure = (new Format)->getCurrency();
assertThat($closure('100', $this->helper), logicalOr(equalTo('$100.00'), equalTo('USD 100.00')));
assertThat($closure('{"value": 1234.56, "currency": "EUR"}', $this->helper), logicalOr(equalTo('€1,234.56'), equalTo('EUR 1,234.56')));
}
/** @testdox ->getCurrency() */
function testGetCurrency(): void {
// It should format the specified value as a currency.
$closure = (new Format)->getCurrency();
assertThat($closure("100", $this->helper), logicalOr(equalTo("$100.00"), equalTo("USD 100.00")));
assertThat($closure('{"value": 1234.56, "currency": "EUR"}', $this->helper), logicalOr(equalTo("€1,234.56"), equalTo("EUR 1,234.56")));
}

/** @testdox ->getDate() */
function testGetDate(): void {
// It should format the specified value as a date.
$closure = (new Format)->getDate();
assertThat($closure('1994-11-05T13:15:30Z', $this->helper), equalTo('Nov 5, 1994'));
}
/** @testdox ->getDate() */
function testGetDate(): void {
// It should format the specified value as a date.
$closure = (new Format)->getDate();
assertThat($closure("1994-11-05T13:15:30Z", $this->helper), equalTo("Nov 5, 1994"));
}

/** @testdox ->getDecimal() */
function testGetDecimal(): void {
// It should format the specified value as a decimal number.
$closure = (new Format)->getDecimal();
assertThat($closure('1234.56', $this->helper), equalTo('1,234.56'));
assertThat($closure('{"value": 100, "decimals": 4}', $this->helper), equalTo('100.0000'));
}
/** @testdox ->getDecimal() */
function testGetDecimal(): void {
// It should format the specified value as a decimal number.
$closure = (new Format)->getDecimal();
assertThat($closure("1234.56", $this->helper), equalTo("1,234.56"));
assertThat($closure('{"value": 100, "decimals": 4}', $this->helper), equalTo("100.0000"));
}

/** @testdox ->getInteger() */
function testGetInteger(): void {
// It should format the specified value as an integer number.
$closure = (new Format)->getInteger();
assertThat($closure('100', $this->helper), equalTo('100'));
assertThat($closure('-1234.56', $this->helper), equalTo('-1,234'));
}
/** @testdox ->getInteger() */
function testGetInteger(): void {
// It should format the specified value as an integer number.
$closure = (new Format)->getInteger();
assertThat($closure("100", $this->helper), equalTo("100"));
assertThat($closure("-1234.56", $this->helper), equalTo("-1,234"));
}

/** @testdox ->getNtext() */
function testGetNtext(): void {
// It should replace new lines by "<br>" tags.
$closure = (new Format)->getNtext();
assertThat($closure("Foo\nBar", $this->helper), equalTo('Foo<br>Bar'));
assertThat($closure("Foo\r\nBaz", $this->helper), equalTo('Foo<br>Baz'));
}
/** @testdox ->getNtext() */
function testGetNtext(): void {
// It should replace new lines by "<br>" tags.
$closure = (new Format)->getNtext();
assertThat($closure("Foo\nBar", $this->helper), equalTo("Foo<br>Bar"));
assertThat($closure("Foo\r\nBaz", $this->helper), equalTo("Foo<br>Baz"));
}

/** @testdox ->getPercent() */
function testGetPercent(): void {
// It should format the specified value as a percentage.
$closure = (new Format)->getPercent();
assertThat($closure('0.1', $this->helper), equalTo('10%'));
assertThat($closure('1.23', $this->helper), equalTo('123%'));
}
/** @testdox ->getPercent() */
function testGetPercent(): void {
// It should format the specified value as a percentage.
$closure = (new Format)->getPercent();
assertThat($closure("0.1", $this->helper), equalTo("10%"));
assertThat($closure("1.23", $this->helper), equalTo("123%"));
}

/** @before This method is called before each test. */
protected function setUp(): void {
\Yii::$app->getFormatter()->currencyCode = 'USD';
$this->helper = new \Mustache_LambdaHelper(new \Mustache_Engine, new \Mustache_Context);
}
/** @before This method is called before each test. */
protected function setUp(): void {
\Yii::$app->getFormatter()->currencyCode = "USD";
$this->helper = new \Mustache_LambdaHelper(new \Mustache_Engine, new \Mustache_Context);
}
}

+ 54
- 54
test/helpers/HtmlTest.php View File

@@ -8,69 +8,69 @@ use function PHPUnit\Framework\{assertThat, equalTo, isNull};
/** @testdox yii\mustache\helpers\Html */
class HtmlTest extends TestCase {

/** @var \Mustache_LambdaHelper The engine used to render strings. */
private \Mustache_LambdaHelper $helper;
/** @var \Mustache_LambdaHelper The engine used to render strings. */
private \Mustache_LambdaHelper $helper;

/** @beforeClass This method is called before the first test of this test class is run. */
static function setUpBeforeClass(): void {
\Yii::$app->set('view', View::class);
}
/** @beforeClass This method is called before the first test of this test class is run. */
static function setUpBeforeClass(): void {
\Yii::$app->set("view", View::class);
}