From d3e4d0985a1189d15fb8ed9eb105830c9dc38c4d Mon Sep 17 00:00:00 2001 From: flashwave Date: Fri, 4 Oct 2024 23:39:32 +0000 Subject: [PATCH] Imported Sasae into Index. --- VERSION | 2 +- composer.json | 4 +- composer.lock | 702 +++++++++++++++++- src/Templating/Cache/TplFilesystemCache.php | 39 + .../Extension/TplIndexExtension.php | 28 + src/Templating/Loader/TplFilesystemLoader.php | 87 +++ src/Templating/TplContext.php | 94 +++ src/Templating/TplEnvironment.php | 150 ++++ tests/TemplatingTest-global.twig | 19 + tests/TemplatingTest-loaded-render.html | 23 + tests/TemplatingTest-loaded-string.html | 23 + tests/TemplatingTest-loaded.twig | 14 + tests/TemplatingTest-rendered.html | 18 + tests/TemplatingTest-rendered.twig | 5 + tests/TemplatingTest.php | 67 ++ 15 files changed, 1271 insertions(+), 4 deletions(-) create mode 100644 src/Templating/Cache/TplFilesystemCache.php create mode 100644 src/Templating/Extension/TplIndexExtension.php create mode 100644 src/Templating/Loader/TplFilesystemLoader.php create mode 100644 src/Templating/TplContext.php create mode 100644 src/Templating/TplEnvironment.php create mode 100644 tests/TemplatingTest-global.twig create mode 100644 tests/TemplatingTest-loaded-render.html create mode 100644 tests/TemplatingTest-loaded-string.html create mode 100644 tests/TemplatingTest-loaded.twig create mode 100644 tests/TemplatingTest-rendered.html create mode 100644 tests/TemplatingTest-rendered.twig create mode 100644 tests/TemplatingTest.php diff --git a/VERSION b/VERSION index da0e292..701ed6b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2410.42235 +0.2410.42339 diff --git a/composer.json b/composer.json index e4bd929..0317fdd 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,9 @@ "license": "bsd-3-clause-clear", "require": { "php": ">=8.3", - "ext-mbstring": "*" + "ext-mbstring": "*", + "twig/twig": "^3.14", + "twig/html-extra": "^3.13" }, "require-dev": { "phpunit/phpunit": "^11.2", diff --git a/composer.lock b/composer.lock index 5f457ce..461289e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,706 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "01f698fe09948da2c00f519d842609d2", - "packages": [], + "content-hash": "e79de830674f6cbf86bf7874b086c7e0", + "packages": [ + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/mime", + "version": "v7.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "711d2e167e8ce65b05aea6b258c449671cdd38ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/711d2e167e8ce65b05aea6b258c449671cdd38ff", + "reference": "711d2e167e8ce65b05aea6b258c449671cdd38ff", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v7.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-20T08:28:38+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "twig/html-extra", + "version": "v3.13.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/html-extra.git", + "reference": "8229e750091171c1f11801a525927811c7ac5a7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/8229e750091171c1f11801a525927811c7ac5a7e", + "reference": "8229e750091171c1f11801a525927811c7ac5a7e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/mime": "^5.4|^6.4|^7.0", + "twig/twig": "^3.13|^4.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Twig\\Extra\\Html\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Twig extension for HTML", + "homepage": "https://twig.symfony.com", + "keywords": [ + "html", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/html-extra/tree/v3.13.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-09-03T13:08:40+00:00" + }, + { + "name": "twig/twig", + "version": "v3.14.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php81": "^1.29" + }, + "require-dev": { + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.14.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-09-09T17:55:12+00:00" + } + ], "packages-dev": [ { "name": "myclabs/deep-copy", diff --git a/src/Templating/Cache/TplFilesystemCache.php b/src/Templating/Cache/TplFilesystemCache.php new file mode 100644 index 0000000..8dafdbe --- /dev/null +++ b/src/Templating/Cache/TplFilesystemCache.php @@ -0,0 +1,39 @@ + Environment::VERSION), + ]; + } +} diff --git a/src/Templating/Loader/TplFilesystemLoader.php b/src/Templating/Loader/TplFilesystemLoader.php new file mode 100644 index 0000000..b28721d --- /dev/null +++ b/src/Templating/Loader/TplFilesystemLoader.php @@ -0,0 +1,87 @@ +root = $path; + } + + /** + * Returns the underlying path. + * + * @return string + */ + public function getPath(): string { + return $this->root; + } + + /** @var array */ + private array $absPaths = []; + + private function getAbsolutePath(string $path, bool $throw): string { + $cachePath = $path; + if(array_key_exists($cachePath, $this->absPaths)) + return $this->absPaths[$cachePath]; + + if(pathinfo($path, PATHINFO_EXTENSION) === '') + $path = rtrim($path, '.') . '.twig'; + + $absPath = realpath($this->root . DIRECTORY_SEPARATOR . $path); + + if($absPath === false) { + if(!$throw) + return ''; + + throw new LoaderError(sprintf('Could not find template "%s" in "%s".', $path, $this->root)); + } + + if(!str_starts_with($absPath, $this->root)) { + if(!$throw) + return ''; + + throw new LoaderError(sprintf('Attempting to load "%s" which is outside of the template directory.', $absPath)); + } + + return $this->absPaths[$cachePath] = $absPath; + } + + public function getSourceContext(string $name): Source { + $path = $this->getAbsolutePath($name, true); + $body = file_get_contents($path); + if($body === false) + throw new LoaderError(sprintf('Was unable to read "%s"', $path)); + return new Source($body, $name, $path); + } + + public function getCacheKey(string $name): string { + return $this->getAbsolutePath($name, true); + } + + public function isFresh(string $name, int $time): bool { + return filemtime($this->getAbsolutePath($name, true)) < $time; + } + + public function exists(string $name) { + return $this->getAbsolutePath($name, false) !== ''; + } +} diff --git a/src/Templating/TplContext.php b/src/Templating/TplContext.php new file mode 100644 index 0000000..28b76fb --- /dev/null +++ b/src/Templating/TplContext.php @@ -0,0 +1,94 @@ + $vars + */ + public function __construct( + private TemplateWrapper $wrapper, + private array $vars = [] + ) {} + + /** + * Returns the underlying wrapper instance. + * + * @return TemplateWrapper + */ + public function getWrapper(): TemplateWrapper { + return $this->wrapper; + } + + /** + * Sets a local variable. + * + * $path is evaluated to allow accessing deeper layer arrays without overwriting it entirely. + * + * @param string $path Array path to the variable. + * @param mixed $value Desired value. + */ + public function setVar(string $path, mixed $value): void { + $path = explode('.', $path); + $target = &$this->vars; + $targetName = array_pop($path); + + if(!empty($path)) { + $path = array_reverse($path); + while(($name = array_pop($path)) !== null) { + if(!is_array($target)) + throw new InvalidArgumentException('the $path you\'re attempting to write to conflicts with a non-array type'); + + if(!array_key_exists($name, $target)) + $target[$name] = []; + $target = &$target[$name]; + } + } + + if(!is_array($target)) + throw new InvalidArgumentException('the $path you\'re attempting to write to conflicts with a non-array type'); + + $target[$targetName] = $value; + } + + /** + * Merges a set of variables into the local variable set. + * + * @param array $vars Variables to apply to the set. + */ + public function setVars(array $vars): void { + $this->vars = array_merge($this->vars, $vars); + } + + /** + * Renders the template to a string, taking additional variables that are not commit to local set. + * + * @param array|null $vars Additional local variables, nullable to avoid additional function calls. + * @return string Rendered template. + */ + public function render(?array $vars = null): string { + return $this->wrapper->render( + $vars === null ? $this->vars : array_merge($this->vars, $vars) + ); + } + + /** + * Renders the template to a string. + * + * @return string Rendered template. + */ + public function __toString(): string { + return $this->render(); + } +} diff --git a/src/Templating/TplEnvironment.php b/src/Templating/TplEnvironment.php new file mode 100644 index 0000000..26711db --- /dev/null +++ b/src/Templating/TplEnvironment.php @@ -0,0 +1,150 @@ +|string|null $cache A caching driver. + * @param string $charset Character for templates. + * @param bool $debug Debug mode. + */ + public function __construct( + LoaderInterface|string $loader, + CacheInterface|array|string|null $cache = null, + string $charset = 'utf-8', + bool $debug = false + ) { + if(is_string($loader)) + $loader = new TplFilesystemLoader($loader); + + if(is_array($cache)) { + if(empty($cache)) + throw new InvalidArgumentException('if $cache is an array, it may not be empty'); + $cache = TplFilesystemCache::create(array_shift($cache), array_shift($cache)); + } elseif($cache === null) $cache = false; + + $this->env = new Environment($loader, [ + 'debug' => $debug, + 'cache' => $cache, + 'charset' => $charset, + 'strict_variables' => true, // there's no reason to disable this ever + ]); + + $this->env->addExtension(new HtmlExtension); + $this->env->addExtension(new TplIndexExtension); + } + + /** + * Get a reference to the underlying Twig Environment. + * Things that aren't exposed through Index generally have a reason + * for being "obfuscated" but go wild if you really want to. + * + * @return Environment + */ + public function getEnvironment(): Environment { + return $this->env; + } + + /** + * Returns if debug mode is enabled. + * + * @return bool + */ + public function isDebug(): bool { + return $this->env->isDebug(); + } + + /** + * Registers an extension. + * + * @param ExtensionInterface $extension + */ + public function addExtension(ExtensionInterface $extension): void { + $this->env->addExtension($extension); + } + + /** + * Registers a filter. + * + * @param string $name Name of the filter. + * @param callable $body Body of the filter. + * @param array $options Options, review the TwigFilter file for the options. + */ + public function addFilter(string $name, callable $body, array $options = []): void { + $this->env->addFilter(new TwigFilter($name, $body, $options)); + } + + /** + * Registers a function. + * + * @param string $name Name of the function. + * @param callable $body Body of the function. + * @param array $options Options, review the TwigFunction file for the options. + */ + public function addFunction(string $name, callable $body, array $options = []): void { + $this->env->addFunction(new TwigFunction($name, $body, $options)); + } + + /** + * Registers a twig. + * + * @param string $name Name of the twig. + * @param callable $body Body of the twig. + * @param array $options Options, review the TwigTest file for the options. + */ + public function addTest(string $name, callable $body, array $options = []): void { + $this->env->addTest(new TwigTest($name, $body, $options)); + } + + /** + * Adds a global variable available in any TplContext instance. + * + * @param string $name Name of the variable. + * @param mixed $value Content of the variable. + */ + public function addGlobal(string $name, mixed $value): void { + $this->env->addGlobal($name, $value); + } + + /** + * Loads a template and creates a TplContext instance. + * + * @param string $name Name or path of the template. + * @param array $vars Context local variables to add right away. + * @return TplContext + */ + public function load(string $name, array $vars = []): TplContext { + return new TplContext($this->env->load($name), $vars); + } + + /** + * Direct proxy to Environment's render method. + * + * @param string $name Name or path of the template. + * @param array $vars Local variables to render the template with. + * @return string + */ + public function render(string $name, array $vars = []): string { + return $this->env->render($name, $vars); + } +} diff --git a/tests/TemplatingTest-global.twig b/tests/TemplatingTest-global.twig new file mode 100644 index 0000000..caddb6b --- /dev/null +++ b/tests/TemplatingTest-global.twig @@ -0,0 +1,19 @@ +{% set ndx_version_output = ndx_version() %} +{% set twig_version_output = twig_version() %} + +ndx_version {{ ndx_version_output == expect.ndx_version ? 'works!' : ('returned "' ~ ndx_version_output ~ '" instead of "' ~ expect.ndx_version ~ '".') }} +twig_version {{ twig_version_output == expect.twig_version ? 'works!' : ('returned "' ~ twig_version_output ~ '" instead of "' ~ expect.twig_version ~ '".') }} + +global_var = {{ global_var }} + +{{ 'meow'|test_filter }} +{{ test_function('the') }} +{{ 'test' is test_test ? 'test works' : 'test does not work' }} + +{{ 'Mewow'|data_uri(mime="text/plain") }} +{{ 'Mewow'|data_uri(mime="application/octet-stream") }} + +
yay
diff --git a/tests/TemplatingTest-loaded-render.html b/tests/TemplatingTest-loaded-render.html new file mode 100644 index 0000000..9df23bc --- /dev/null +++ b/tests/TemplatingTest-loaded-render.html @@ -0,0 +1,23 @@ +Loaded Template render() + + +ndx_version works! +twig_version works! + +global_var = global var value + +filter:meow +func:the +test works + +data:text/plain,Mewow +data:application/octet-stream;base64,TWV3b3c= + +
yay
+ +this var is context +simple set call +applied with fuckery +applied without fuckery + + this was called through render() so local_var is set to "this var is local" diff --git a/tests/TemplatingTest-loaded-string.html b/tests/TemplatingTest-loaded-string.html new file mode 100644 index 0000000..11fe73a --- /dev/null +++ b/tests/TemplatingTest-loaded-string.html @@ -0,0 +1,23 @@ +Loaded Template toString() + + +ndx_version works! +twig_version works! + +global_var = global var value + +filter:meow +func:the +test works + +data:text/plain,Mewow +data:application/octet-stream;base64,TWV3b3c= + +
yay
+ +this var is context +simple set call +applied with fuckery +applied without fuckery + + this was called through toString() so local_var isn't defined diff --git a/tests/TemplatingTest-loaded.twig b/tests/TemplatingTest-loaded.twig new file mode 100644 index 0000000..0204489 --- /dev/null +++ b/tests/TemplatingTest-loaded.twig @@ -0,0 +1,14 @@ +Loaded Template {{ variant }} + +{% include 'TemplatingTest-global' %} + +{{ context_var }} +{{ simple_set }} +{{ another.context.var.deep }} +{{ context_var2 }} + +{% if local_var is defined %} + this was called through render() so local_var is set to "{{ local_var }}" +{% else %} + this was called through toString() so local_var isn't defined +{% endif %} diff --git a/tests/TemplatingTest-rendered.html b/tests/TemplatingTest-rendered.html new file mode 100644 index 0000000..3ebb3b4 --- /dev/null +++ b/tests/TemplatingTest-rendered.html @@ -0,0 +1,18 @@ +Rendered Template + + +ndx_version works! +twig_version works! + +global_var = global var value + +filter:meow +func:the +test works + +data:text/plain,Mewow +data:application/octet-stream;base64,TWV3b3c= + +
yay
+ +this var is local diff --git a/tests/TemplatingTest-rendered.twig b/tests/TemplatingTest-rendered.twig new file mode 100644 index 0000000..75694fc --- /dev/null +++ b/tests/TemplatingTest-rendered.twig @@ -0,0 +1,5 @@ +Rendered Template + +{% include 'TemplatingTest-global' %} + +{{ local_var }} diff --git a/tests/TemplatingTest.php b/tests/TemplatingTest.php new file mode 100644 index 0000000..dc95f4b --- /dev/null +++ b/tests/TemplatingTest.php @@ -0,0 +1,67 @@ +assertFalse($env->isDebug()); + + $env->addGlobal('global_var', 'global var value'); + $env->addGlobal('expect', [ + 'ndx_version' => Index::version(), + 'twig_version' => TwigEnvironment::VERSION, + ]); + + $env->addFilter('test_filter', fn($text) => ('filter:' . $text)); + $env->addFunction('test_function', fn($text) => ('func:' . $text)); + $env->addTest('test_test', fn($text) => $text === 'test'); + + $rendered = $env->render('TemplatingTest-rendered', [ + 'local_var' => 'this var is local', + ]); + + $this->assertEquals(file_get_contents(__DIR__ . '/TemplatingTest-rendered.html'), $rendered); + + $ctx = $env->load('TemplatingTest-loaded', [ + 'context_var' => 'this var is context', + 'variant' => 'toString()', + ]); + + $ctx->setVar('simple_set', 'simple set call'); + $ctx->setVar('another.context.var.deep', 'applied with fuckery'); + + $ctx->setVars([ + 'context_var2' => 'applied without fuckery', + ]); + + $loaded = $ctx->render([ + 'local_var' => 'this var is local', + 'variant' => 'render()', + ]); + + $this->assertEquals(file_get_contents(__DIR__ . '/TemplatingTest-loaded-render.html'), $loaded); + $this->assertEquals(file_get_contents(__DIR__ . '/TemplatingTest-loaded-string.html'), (string)$ctx); + } +}