Initial import.
This commit is contained in:
commit
5e2c842434
24 changed files with 3383 additions and 0 deletions
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
* text=auto
|
||||
*.sh text eol=lf
|
||||
*.php text eol=lf
|
||||
*.bat text eol=crlf
|
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
[Tt]humbs.db
|
||||
[Dd]esktop.ini
|
||||
.DS_Store
|
||||
.vscode/
|
||||
.vs/
|
||||
.idea/
|
||||
docs/html/
|
||||
.phpdoc*
|
||||
.phpunit*
|
||||
vendor/
|
30
LICENCE
Normal file
30
LICENCE
Normal file
|
@ -0,0 +1,30 @@
|
|||
Copyright (c) 2023, flashwave <me@flash.moe>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted (subject to the limitations in the disclaimer
|
||||
below) provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
|
||||
THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
41
README.md
Normal file
41
README.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Sasae
|
||||
|
||||
Sasae is a simple wrapper around some of Twig's functionality as well as making `Twig\Environment` a little bit more immutable.
|
||||
|
||||
While it's not a lot of extras, I often implement the added functionality across projects.
|
||||
The source file structure is meant to be similar to Twig's own.
|
||||
|
||||
|
||||
## Requirements and Dependencies
|
||||
|
||||
Sasae currently targets **PHP 8.2**.
|
||||
|
||||
No additional requirements and/or dependencies at this time.
|
||||
|
||||
|
||||
## Versioning
|
||||
|
||||
Sasae versioning will follows the [Semantic Versioning specification v2.0.0](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
Changes to minimum required PHP version, major Twig library releases that cause incompatibilities and other major overhauls to Sasae itself that break compatibility will be reasons for incrementing the major version.
|
||||
Updates to Sasae functionality or adjustments to fit new Twig functionality will cause increment the minor version.
|
||||
Bug fixes and inconsequential Twig library updates will increment the patch version.
|
||||
|
||||
Sasae also depends on Index, but its versioning depends on the minimum PHP version and should thus be fairly inconsequential.
|
||||
|
||||
Previous major versions may be supported for a time with backports depending on what projects of mine still target older versions of PHP.
|
||||
|
||||
The version is stored in the root of the repository in a file called `VERSION` and can be read out within Sasae using `Sasae\SasaeEnvironment::getSasaeVersion()`.
|
||||
|
||||
|
||||
## Contribution
|
||||
|
||||
By submitting code for inclusion in the main Sasae source tree you agree to transfer ownership of the code to the project owner.
|
||||
The contributor will still be attributed for the contributed code, unless they ask for this attribution to be removed.
|
||||
This is to avoid intellectual property rights traps and drama that could lead to blackmail situations.
|
||||
If you do not agree with these terms, you are free to fork off.
|
||||
|
||||
|
||||
## Licencing
|
||||
|
||||
Sasae is available under the BSD 3-Clause Clear License, a full version of which is enclosed in the LICENCE file.
|
1
VERSION
Normal file
1
VERSION
Normal file
|
@ -0,0 +1 @@
|
|||
1.0.0-dev
|
31
composer.json
Normal file
31
composer.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "flashwave/sasae",
|
||||
"description": "A wrapper for Twig with added common functionality.",
|
||||
"type": "library",
|
||||
"license": "bsd-3-clause-clear",
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"twig/twig": "^3.7",
|
||||
"twig/html-extra": "^3.7",
|
||||
"flashwave/index": "dev-master"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^10.2",
|
||||
"phpstan/phpstan": "^1.10"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "flashwave",
|
||||
"email": "packagist@flash.moe",
|
||||
"homepage": "https://flash.moe",
|
||||
"role": "mom"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Sasae\\": "src"
|
||||
}
|
||||
}
|
||||
}
|
2437
composer.lock
generated
Normal file
2437
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
41
phpdoc.xml
Normal file
41
phpdoc.xml
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<phpdocumentor configVersion="3.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://www.phpdoc.org"
|
||||
xsi:noNamespaceSchemaLocation="data/xsd/phpdoc.xsd">
|
||||
<title>Sasae Documentation</title>
|
||||
<template name="default"/>
|
||||
<paths>
|
||||
<output>docs/html</output>
|
||||
</paths>
|
||||
<version number="0.1">
|
||||
<folder>latest</folder>
|
||||
<api format="php">
|
||||
<output>api</output>
|
||||
<visibility>public</visibility>
|
||||
<default-package-name>Sasae</default-package-name>
|
||||
<source dsn=".">
|
||||
<path>src</path>
|
||||
</source>
|
||||
<extensions>
|
||||
<extension>php</extension>
|
||||
</extensions>
|
||||
<ignore hidden="true" symlinks="true">
|
||||
<path>tests/**/*</path>
|
||||
</ignore>
|
||||
<ignore-tags>
|
||||
<ignore-tag>template</ignore-tag>
|
||||
<ignore-tag>template-extends</ignore-tag>
|
||||
<ignore-tag>template-implements</ignore-tag>
|
||||
<ignore-tag>extends</ignore-tag>
|
||||
<ignore-tag>implements</ignore-tag>
|
||||
</ignore-tags>
|
||||
</api>
|
||||
<guide format="rst">
|
||||
<source dsn=".">
|
||||
<path>docs</path>
|
||||
</source>
|
||||
<output>guide</output>
|
||||
</guide>
|
||||
</version>
|
||||
</phpdocumentor>
|
7
phpstan.neon
Normal file
7
phpstan.neon
Normal file
|
@ -0,0 +1,7 @@
|
|||
parameters:
|
||||
level: 9
|
||||
checkUninitializedProperties: true
|
||||
checkImplicitMixed: true
|
||||
checkBenevolentUnionTypes: true
|
||||
paths:
|
||||
- src
|
23
phpunit.xml
Normal file
23
phpunit.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd"
|
||||
colors="true"
|
||||
executionOrder="depends,defects"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
failOnRisky="true"
|
||||
failOnWarning="true"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
requireCoverageMetadata="true"
|
||||
beStrictAboutCoverageMetadata="true">
|
||||
<testsuites>
|
||||
<testsuite name="default">
|
||||
<directory suffix="Test.php">tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<coverage/>
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
35
src/Cache/SasaeFilesystemCache.php
Normal file
35
src/Cache/SasaeFilesystemCache.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
namespace Sasae\Cache;
|
||||
|
||||
use Twig\Cache\FilesystemCache as TwigFilesystemCache;
|
||||
|
||||
/**
|
||||
* Extends Twig's filesystem cache implementation with an alternate constructor.
|
||||
*/
|
||||
class SasaeFilesystemCache extends TwigFilesystemCache {
|
||||
/**
|
||||
* string $path Directory path to store the cache in.
|
||||
* bool $autoReload Whether to refresh the cache if changes are detected.
|
||||
*/
|
||||
public function __construct(string $path, bool $autoReload) {
|
||||
parent::__construct(
|
||||
$path,
|
||||
$autoReload ? TwigFilesystemCache::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 . 'sasae-' . $name;
|
||||
if(!$autoReload)
|
||||
$path .= '-' . $version;
|
||||
|
||||
return new self($path, $autoReload);
|
||||
}
|
||||
}
|
28
src/Extension/SasaeExtension.php
Normal file
28
src/Extension/SasaeExtension.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
namespace Sasae\Extension;
|
||||
|
||||
use Index\ByteFormat;
|
||||
use Index\Environment as NdxEnvironment;
|
||||
use Sasae\SasaeEnvironment;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
use Twig\Extension\AbstractExtension as TwigAbstractExtension;
|
||||
|
||||
/**
|
||||
* Provides version functions and additional functionality implemented in Index.
|
||||
*/
|
||||
class SasaeExtension extends TwigAbstractExtension {
|
||||
public function getFilters() {
|
||||
return [
|
||||
new TwigFilter('format_filesize', ByteFormat::format(...)),
|
||||
];
|
||||
}
|
||||
|
||||
public function getFunctions() {
|
||||
return [
|
||||
new TwigFunction('ndx_version', NdxEnvironment::getIndexVersion(...)),
|
||||
new TwigFunction('sasae_version', SasaeEnvironment::getSasaeVersion(...)),
|
||||
new TwigFunction('twig_version', SasaeEnvironment::getTwigVersion(...)),
|
||||
];
|
||||
}
|
||||
}
|
83
src/Loader/SasaeFilesystemLoader.php
Normal file
83
src/Loader/SasaeFilesystemLoader.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
namespace Sasae\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 SasaeFilesystemLoader 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) !== '';
|
||||
}
|
||||
}
|
90
src/SasaeContext.php
Normal file
90
src/SasaeContext.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
namespace Sasae;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Stringable;
|
||||
use Twig\TemplateWrapper as TwigTemplateWrapper;
|
||||
|
||||
/**
|
||||
* Provides a wrapper of Twig\TemplateWrapper.
|
||||
*/
|
||||
class SasaeContext implements Stringable {
|
||||
/**
|
||||
* @internal
|
||||
* @param array<string, mixed> $vars
|
||||
*/
|
||||
public function __construct(
|
||||
private TwigTemplateWrapper $wrapper,
|
||||
private array $vars = []
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Returns the underlying wrapper instance.
|
||||
*
|
||||
* @return TwigTemplateWrapper
|
||||
*/
|
||||
public function getWrapper(): TwigTemplateWrapper {
|
||||
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> $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();
|
||||
}
|
||||
}
|
192
src/SasaeEnvironment.php
Normal file
192
src/SasaeEnvironment.php
Normal file
|
@ -0,0 +1,192 @@
|
|||
<?php
|
||||
// SasaeEnvironment.php
|
||||
// Created: 2023-08-24
|
||||
// Updated: 2023-08-24
|
||||
|
||||
namespace Sasae;
|
||||
|
||||
use UnexpectedValueException;
|
||||
use Index\Version;
|
||||
use Sasae\Extension\SasaeExtension;
|
||||
use Twig\Environment as TwigEnvironment;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
use Twig\TwigTest;
|
||||
use Twig\Cache\CacheInterface as TwigCacheInterface;
|
||||
use Twig\Extension\ExtensionInterface as TwigExtensionInterface;
|
||||
use Twig\Extra\Html\HtmlExtension as TwigHtmlExtension;
|
||||
use Twig\Loader\LoaderInterface as TwigLoaderInterface;
|
||||
|
||||
/**
|
||||
* Provides a wrapper of Twig\Environment.
|
||||
*/
|
||||
class SasaeEnvironment {
|
||||
private TwigEnvironment $env;
|
||||
|
||||
private static ?string $sasaeVersionString = null;
|
||||
private static ?Version $sasaeVersion = null;
|
||||
private static ?Version $twigVersion = null;
|
||||
|
||||
/**
|
||||
* @param TwigLoaderInterface $loader A template loader instance.
|
||||
* @param ?TwigCacheInterface $cache A caching driver.
|
||||
* @param string $charset Character for templates.
|
||||
* @param bool $debug Debug mode.
|
||||
*/
|
||||
public function __construct(
|
||||
TwigLoaderInterface $loader,
|
||||
?TwigCacheInterface $cache = null,
|
||||
string $charset = 'utf-8',
|
||||
bool $debug = false
|
||||
) {
|
||||
$this->env = new TwigEnvironment($loader, [
|
||||
'debug' => $debug,
|
||||
'cache' => $cache,
|
||||
'charset' => $charset,
|
||||
'strict_variables' => true, // there's no reason to disable this ever
|
||||
]);
|
||||
|
||||
$this->env->addExtension(new TwigHtmlExtension);
|
||||
$this->env->addExtension(new SasaeExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a reference to the underlying Twig Environment.
|
||||
* Things that aren't exposed through Sasae generally have a reason
|
||||
* for being "obfuscated" but go wild if you really want to.
|
||||
*
|
||||
* @return TwigEnvironment
|
||||
*/
|
||||
public function getEnvironment(): TwigEnvironment {
|
||||
return $this->env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if debug mode is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDebug(): bool {
|
||||
return $this->env->isDebug();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an extension.
|
||||
*
|
||||
* @param TwigExtensionInterface $extension
|
||||
*/
|
||||
public function addExtension(TwigExtensionInterface $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 SasaeContext 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 SasaeContext instance.
|
||||
*
|
||||
* @param string $name Name or path of the template.
|
||||
* @param array<string, mixed> $vars Context local variables to add right away.
|
||||
* @return SasaeContext
|
||||
*/
|
||||
public function load(string $name, array $vars = []): SasaeContext {
|
||||
return new SasaeContext($this->env->load($name), $vars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct proxy to TwigEnvironment'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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current version of the Sasae library.
|
||||
*
|
||||
* @return Version
|
||||
*/
|
||||
public static function getSasaeVersion(): Version {
|
||||
if(self::$sasaeVersion === null)
|
||||
self::$sasaeVersion = Version::parse(self::getSasaeVersionString());
|
||||
return self::$sasaeVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current version of the Sasae library as a string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getSasaeVersionString(): string {
|
||||
if(self::$sasaeVersionString === null) {
|
||||
$body = file_get_contents(__DIR__ . '/../VERSION');
|
||||
if($body === false)
|
||||
throw new UnexpectedValueException('Was unable to read VERSION file.');
|
||||
self::$sasaeVersionString = trim($body);
|
||||
}
|
||||
return self::$sasaeVersionString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current version of the Twig library.
|
||||
*
|
||||
* @return Version
|
||||
*/
|
||||
public static function getTwigVersion(): Version {
|
||||
if(self::$twigVersion === null)
|
||||
self::$twigVersion = Version::parse(TwigEnvironment::VERSION);
|
||||
return self::$twigVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current version of the Twig library as a string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTwigVersionString(): string {
|
||||
return TwigEnvironment::VERSION;
|
||||
}
|
||||
}
|
66
tests/SasaeTest.php
Normal file
66
tests/SasaeTest.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Index\Environment;
|
||||
use Index\XString;
|
||||
use Sasae\SasaeContext;
|
||||
use Sasae\SasaeEnvironment;
|
||||
use Sasae\Cache\SasaeFilesystemCache;
|
||||
use Sasae\Extension\SasaeExtension;
|
||||
use Sasae\Loader\SasaeFilesystemLoader;
|
||||
|
||||
/**
|
||||
* @covers SasaeContext
|
||||
* @covers SasaeEnvironment
|
||||
* @covers SasaeExtension
|
||||
* @covers SasaeFilesystemCache
|
||||
* @covers SasaeFilesystemLoader
|
||||
*/
|
||||
final class SasaeTest extends TestCase {
|
||||
public function testEverything(): void {
|
||||
$env = new SasaeEnvironment(
|
||||
new SasaeFilesystemLoader(__DIR__),
|
||||
SasaeFilesystemCache::create('SasaeTest', XString::random(8))
|
||||
);
|
||||
|
||||
$this->assertFalse($env->isDebug());
|
||||
|
||||
$env->addGlobal('global_var', 'Sasae global var');
|
||||
$env->addGlobal('expect', [
|
||||
'ndx_version' => (string)Environment::getIndexVersion(),
|
||||
'sasae_version' => SasaeEnvironment::getSasaeVersionString(),
|
||||
'twig_version' => SasaeEnvironment::getTwigVersionString(),
|
||||
]);
|
||||
|
||||
$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('test-rendered', [
|
||||
'local_var' => 'this var is local',
|
||||
]);
|
||||
|
||||
$this->assertEquals(file_get_contents(__DIR__ . '/test-rendered.html'), $rendered);
|
||||
|
||||
$ctx = $env->load('test-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__ . '/test-loaded-render.html'), $loaded);
|
||||
$this->assertEquals(file_get_contents(__DIR__ . '/test-loaded-string.html'), (string)$ctx);
|
||||
}
|
||||
}
|
13
tests/test-global.twig
Normal file
13
tests/test-global.twig
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% set ndx_version_output = ndx_version() %}
|
||||
{% set sasae_version_output = sasae_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 ~ '".') }}
|
||||
sasae_version {{ sasae_version_output == expect.sasae_version ? 'works!' : ('returned "' ~ sasae_version_output ~ '" instead of "' ~ expect.sasae_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' }}
|
19
tests/test-loaded-render.html
Normal file
19
tests/test-loaded-render.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
Loaded Template render()
|
||||
|
||||
|
||||
ndx_version works!
|
||||
sasae_version works!
|
||||
twig_version works!
|
||||
|
||||
global_var = Sasae global var
|
||||
|
||||
filter:meow
|
||||
func:the
|
||||
test works
|
||||
|
||||
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"
|
19
tests/test-loaded-string.html
Normal file
19
tests/test-loaded-string.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
Loaded Template toString()
|
||||
|
||||
|
||||
ndx_version works!
|
||||
sasae_version works!
|
||||
twig_version works!
|
||||
|
||||
global_var = Sasae global var
|
||||
|
||||
filter:meow
|
||||
func:the
|
||||
test works
|
||||
|
||||
this var is context
|
||||
simple set call
|
||||
applied with fuckery
|
||||
applied without fuckery
|
||||
|
||||
this was called through toString() so local_var isn't defined
|
14
tests/test-loaded.twig
Normal file
14
tests/test-loaded.twig
Normal file
|
@ -0,0 +1,14 @@
|
|||
Loaded Template {{ variant }}
|
||||
|
||||
{% include 'test-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 %}
|
14
tests/test-rendered.html
Normal file
14
tests/test-rendered.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
Rendered Template
|
||||
|
||||
|
||||
ndx_version works!
|
||||
sasae_version works!
|
||||
twig_version works!
|
||||
|
||||
global_var = Sasae global var
|
||||
|
||||
filter:meow
|
||||
func:the
|
||||
test works
|
||||
|
||||
this var is local
|
5
tests/test-rendered.twig
Normal file
5
tests/test-rendered.twig
Normal file
|
@ -0,0 +1,5 @@
|
|||
Rendered Template
|
||||
|
||||
{% include 'test-global' %}
|
||||
|
||||
{{ local_var }}
|
8
tools/precommit.sh
Executable file
8
tools/precommit.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
pushd .
|
||||
cd $(dirname "$0")
|
||||
|
||||
php update-headers.php
|
||||
|
||||
popd
|
172
tools/update-headers.php
Normal file
172
tools/update-headers.php
Normal file
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
// the point of index was so that i wouldn't have to copy things between projects
|
||||
// here i am copying things from index
|
||||
|
||||
date_default_timezone_set('utc');
|
||||
|
||||
function git_changes(): array {
|
||||
$files = [];
|
||||
|
||||
$dir = getcwd();
|
||||
try {
|
||||
chdir(__DIR__ . '/..');
|
||||
|
||||
$output = explode("\n", trim(shell_exec('git status --short --porcelain=v1 --untracked-files=all --no-column')));
|
||||
|
||||
foreach($output as $line) {
|
||||
$line = trim($line);
|
||||
$file = realpath(explode(' ', $line)[1]);
|
||||
$files[] = $file;
|
||||
}
|
||||
|
||||
} finally {
|
||||
chdir($dir);
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
function collect_files(string $directory): array {
|
||||
$files = [];
|
||||
$dir = glob(realpath($directory) . DIRECTORY_SEPARATOR . '*');
|
||||
|
||||
foreach($dir as $file) {
|
||||
if(is_dir($file)) {
|
||||
$files = array_merge($files, collect_files($file));
|
||||
continue;
|
||||
}
|
||||
|
||||
$files[] = $file;
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
echo 'Indexing changed files according to git...' . PHP_EOL;
|
||||
|
||||
$changed = git_changes();
|
||||
|
||||
echo 'Collecting files...' . PHP_EOL;
|
||||
|
||||
$sources = collect_files(__DIR__ . '/../src');
|
||||
$tests = collect_files(__DIR__ . '/../tests');
|
||||
|
||||
$files = array_merge($sources, $tests);
|
||||
|
||||
$topDir = dirname(__DIR__) . DIRECTORY_SEPARATOR;
|
||||
|
||||
$now = date('Y-m-d');
|
||||
|
||||
foreach($files as $file) {
|
||||
echo 'Scanning ' . str_replace($topDir, '', $file) . '...' . PHP_EOL;
|
||||
|
||||
try {
|
||||
$handle = fopen($file, 'rb');
|
||||
|
||||
$checkPHP = trim(fgets($handle)) === '<?php';
|
||||
if(!$checkPHP) {
|
||||
echo 'File is not PHP.' . PHP_EOL;
|
||||
continue;
|
||||
}
|
||||
|
||||
$headerLines = [];
|
||||
|
||||
$expectLine = '// ' . basename($file);
|
||||
$nameLine = trim(fgets($handle));
|
||||
if($nameLine !== $expectLine) {
|
||||
echo ' File name is missing or invalid, queuing update...' . PHP_EOL;
|
||||
$headerLines['name'] = $expectLine;
|
||||
}
|
||||
|
||||
$createdPrefix = '// Created: ';
|
||||
$createdLine = trim(fgets($handle));
|
||||
if(strpos($createdLine, $createdPrefix) !== 0) {
|
||||
echo ' Creation date is missing, queuing update...' . PHP_EOL;
|
||||
$headerLines['created'] = $createdPrefix . $now;
|
||||
}
|
||||
|
||||
$updatedPrefix = '// Updated: ';
|
||||
$updatedLine = trim(fgets($handle));
|
||||
$updatedDate = substr($updatedLine, strlen($updatedPrefix));
|
||||
if(strpos($updatedLine, $updatedPrefix) !== 0 || (in_array($file, $changed) && $updatedDate !== $now)) {
|
||||
echo ' Updated date is inaccurate, queuing update...' . PHP_EOL;
|
||||
$headerLines['updated'] = $updatedPrefix . $now;
|
||||
}
|
||||
|
||||
$blankLine = trim(fgets($handle));
|
||||
if(!empty($blankLine)) {
|
||||
echo ' Trailing newline missing, queuing update...' . PHP_EOL;
|
||||
$headerLines['blank'] = '';
|
||||
}
|
||||
|
||||
if(!empty($headerLines)) {
|
||||
fclose($handle);
|
||||
|
||||
try {
|
||||
$tmpName = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'ndx-uh-' . bin2hex(random_bytes(8)) . '.tmp';
|
||||
copy($file, $tmpName);
|
||||
|
||||
$read = fopen($tmpName, 'rb');
|
||||
$handle = fopen($file, 'wb');
|
||||
|
||||
fwrite($handle, fgets($read));
|
||||
|
||||
$insertAfter = [];
|
||||
|
||||
if(empty($headerLines['name']))
|
||||
fwrite($handle, fgets($read));
|
||||
else {
|
||||
$line = fgets($read);
|
||||
if(strpos($line, '// ') !== 0)
|
||||
$insertAfter[] = $line;
|
||||
|
||||
fwrite($handle, $headerLines['name'] . "\n");
|
||||
}
|
||||
|
||||
if(empty($headerLines['created']))
|
||||
fwrite($handle, fgets($read));
|
||||
else {
|
||||
$line = fgets($read);
|
||||
if(strpos($line, '// Created: ') !== 0)
|
||||
$insertAfter[] = $line;
|
||||
|
||||
fwrite($handle, $headerLines['created'] . "\n");
|
||||
}
|
||||
|
||||
if(empty($headerLines['updated']))
|
||||
fwrite($handle, fgets($read));
|
||||
else {
|
||||
$line = fgets($read);
|
||||
if(strpos($line, '// Updated: ') !== 0)
|
||||
$insertAfter[] = $line;
|
||||
|
||||
fwrite($handle, $headerLines['updated'] . "\n");
|
||||
}
|
||||
|
||||
if(!isset($headerLines['blank']))
|
||||
fwrite($handle, fgets($read));
|
||||
else {
|
||||
$line = fgets($read);
|
||||
if(!empty($line)) {
|
||||
$insertAfter[] = $line;
|
||||
fwrite($handle, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
foreach($insertAfter as $line)
|
||||
fwrite($handle, $line);
|
||||
|
||||
while(($line = fgets($read)) !== false)
|
||||
fwrite($handle, $line);
|
||||
} finally {
|
||||
if(is_resource($read))
|
||||
fclose($read);
|
||||
|
||||
if(is_file($tmpName))
|
||||
unlink($tmpName);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
Reference in a new issue