Imported Sasae into Index.

This commit is contained in:
Pachira 2024-10-04 23:39:32 +00:00
parent 82c23fb15a
commit d3e4d0985a
15 changed files with 1271 additions and 4 deletions

View file

@ -1 +1 @@
0.2410.42235
0.2410.42339

View file

@ -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",

702
composer.lock generated
View file

@ -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",

View file

@ -0,0 +1,39 @@
<?php
// TplFilesystemCache.php
// Created: 2024-08-04
// Updated: 2024-10-04
namespace Index\Templating\Cache;
use Twig\Cache\FilesystemCache;
/**
* Extends Twig's filesystem cache implementation with an alternate constructor.
*/
class TplFilesystemCache extends FilesystemCache {
/**
* @param string $path Directory path to store the cache in.
* @param bool $autoReload Whether to refresh the cache if changes are detected.
*/
public function __construct(string $path, bool $autoReload) {
parent::__construct(
$path,
$autoReload ? FilesystemCache::FORCE_BYTECODE_INVALIDATION : 0
);
}
/**
* Creates an instance of the filesystem cacher in the system temporary path based on project name and version.
*
* @param string $name Name of the project in a format the filesystem will be happy with.
* @param ?string $version Version of the project in a format the filesystem will be happy with or null to enable auto reload.
*/
public static function create(string $name, ?string $version): self {
$autoReload = $version === null;
$path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'ndx-' . $name;
if(!$autoReload)
$path .= '-' . $version;
return new self($path, $autoReload);
}
}

View file

@ -0,0 +1,28 @@
<?php
// TplIndexExtension.php
// Created: 2024-08-04
// Updated: 2024-10-04
namespace Index\Templating\Extension;
use Index\{ByteFormat,Index};
use Twig\{Environment,TwigFilter,TwigFunction};
use Twig\Extension\AbstractExtension;
/**
* Provides version functions and additional functionality implemented in Index.
*/
class TplIndexExtension extends AbstractExtension {
public function getFilters() {
return [
new TwigFilter('format_filesize', ByteFormat::format(...)),
];
}
public function getFunctions() {
return [
new TwigFunction('ndx_version', Index::version(...)),
new TwigFunction('twig_version', fn() => Environment::VERSION),
];
}
}

View file

@ -0,0 +1,87 @@
<?php
// TplFilesystemLoader.php
// Created: 2024-08-04
// Updated: 2024-10-04
namespace Index\Templating\Loader;
use InvalidArgumentException;
use Twig\Source;
use Twig\Error\LoaderError;
use Twig\Loader\LoaderInterface;
/**
* Provides a simpler Filesystem loader with mechanisms like namespaces omitted.
*/
class TplFilesystemLoader implements LoaderInterface {
private string $root;
/**
* @param string $path Base path to the templates directory.
*/
public function __construct(string $path) {
$path = realpath($path);
if($path === false)
throw new InvalidArgumentException('$path does not exist.');
$this->root = $path;
}
/**
* Returns the underlying path.
*
* @return string
*/
public function getPath(): string {
return $this->root;
}
/** @var array<string, string> */
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) !== '';
}
}

View file

@ -0,0 +1,94 @@
<?php
// TplContext.php
// Created: 2024-08-04
// Updated: 2024-10-04
namespace Index\Templating;
use InvalidArgumentException;
use Stringable;
use Twig\TemplateWrapper;
/**
* Provides a wrapper of Twig\TemplateWrapper.
*/
class TplContext implements Stringable {
/**
* @internal
* @param array<string, mixed> $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<string, mixed> $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<string, mixed>|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();
}
}

View file

@ -0,0 +1,150 @@
<?php
// TplEnvironment.php
// Created: 2023-08-24
// Updated: 2024-10-04
namespace Index\Templating;
use InvalidArgumentException;
use UnexpectedValueException;
use Index\Templating\Cache\TplFilesystemCache;
use Index\Templating\Extension\TplIndexExtension;
use Index\Templating\Loader\TplFilesystemLoader;
use Twig\{Environment,TwigFilter,TwigFunction,TwigTest};
use Twig\Cache\CacheInterface;
use Twig\Extension\ExtensionInterface;
use Twig\Extra\Html\HtmlExtension;
use Twig\Loader\LoaderInterface;
/**
* Provides a wrapper of Twig\Environment.
*/
class TplEnvironment {
private Environment $env;
/**
* @param LoaderInterface|string $loader A template loader instance or a path.
* @param CacheInterface|array<string>|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<string, mixed> $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<string, mixed> $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<string, mixed> $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<string, mixed> $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<string, mixed> $vars Local variables to render the template with.
* @return string
*/
public function render(string $name, array $vars = []): string {
return $this->env->render($name, $vars);
}
}

View file

@ -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") }}
<div class="{{ html_classes('always', {
'shows-up': true,
'doesnt': false,
}) }}">yay</div>

View file

@ -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=
<div class="always shows-up">yay</div>
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"

View file

@ -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=
<div class="always shows-up">yay</div>
this var is context
simple set call
applied with fuckery
applied without fuckery
this was called through toString() so local_var isn't defined

View file

@ -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 %}

View file

@ -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=
<div class="always shows-up">yay</div>
this var is local

View file

@ -0,0 +1,5 @@
Rendered Template
{% include 'TemplatingTest-global' %}
{{ local_var }}

67
tests/TemplatingTest.php Normal file
View file

@ -0,0 +1,67 @@
<?php
// TemplatingTest.php
// Created: 2024-08-04
// Updated: 2024-10-04
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Index\{Index,XString};
use Index\Templating\{TplContext,TplEnvironment};
use Index\Templating\Cache\TplFilesystemCache;
use Index\Templating\Extension\TplIndexExtension;
use Index\Templating\Loader\TplFilesystemLoader;
use Twig\Environment as TwigEnvironment;
#[CoversClass(TplContext::class)]
#[CoversClass(TplEnvironment::class)]
#[CoversClass(TplFilesystemCache::class)]
#[CoversClass(TplIndexExtension::class)]
#[CoversClass(TplFilesystemLoader::class)]
final class TemplatingTest extends TestCase {
public function testEverything(): void {
$env = new TplEnvironment(
__DIR__,
['IndexTplTest', XString::random(8)]
);
$this->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);
}
}