initial commit for soko-web
soko-web/pipeline/head This commit looks good

This commit is contained in:
bence committed 2023-04-07 19:32:15 +02:00
commit a17a88e9d4
71 files changed
+7241

No files matched your search

+152
View File
@@ -0,0 +1,152 @@
<?php namespace SokoWeb\View;
class Linker
{
const INLINE_ASSET_LIMIT = 2000;
private string $view;
public function __construct(string $view)
{
$this->view = $view;
}
public function generate(): void
{
$input = ROOT . '/views/' . $this->view . '.php';
$temporaryFiles = [];
$sections = ['externalCss' => '', 'inlineCss' => '', 'externalJs' => '', 'inlineJs' => ''];
$extra = ['', ''];
do {
$parser = new Parser($input);
$fragment = $parser->parse();
$extends = $fragment->getExtends();
$this->generateAssets($fragment, $sections);
$sections = array_merge($sections, $fragment->getSections()); //TODO: detect if section defined multiple times
$extra[0] = $fragment->getExtra()[0] . $extra[0];
$extra[1] = $extra[1] . $fragment->getExtra()[1];
if ($extends === null) {
$this->writeFinal($extra, $input, ROOT . '/cache/views/' . $this->view . '.php');
break;
}
$tmpFile = tempnam(sys_get_temp_dir(), 'mapg-view-');
$temporaryFiles[] = $tmpFile;
$this->extendTemplate($sections, ROOT . '/views/' . $extends . '.php', $tmpFile);
$input = $tmpFile;
} while (true);
foreach ($temporaryFiles as $tmpFile) {
unlink($tmpFile);
}
}
private function extendTemplate(array $sections, string $file, string $output): void
{
$inputFileHandle = fopen($file, 'r');
if (!$inputFileHandle) {
throw new \Exception('Cannot open file ' . $file);
}
$outputFileHandle = fopen($output, 'w');
if (!$outputFileHandle) {
throw new \Exception('Cannot open file ' . $output . 'for writing.');
}
$lineNumber = 0;
while (($line = fgets($inputFileHandle)) !== false) {
++$lineNumber;
if (preg_match('/^\s*@yields\(\'([\w\/]+)\'\)\s*$/', $line, $matches) === 1) {
if (isset($sections[$matches[1]])) {
fwrite($outputFileHandle, $sections[$matches[1]]);
}
} else {
fwrite($outputFileHandle, $line);
}
}
fclose($inputFileHandle);
fclose($outputFileHandle);
}
private function writeFinal(array $extra, string $file, string $output): void
{
$dirname = pathinfo($output, PATHINFO_DIRNAME);
if (!is_dir($dirname)) {
mkdir($dirname, 0755, true);
}
$inputFileHandle = fopen($file, 'r');
if (!$inputFileHandle) {
throw new \Exception('Cannot open file ' . $file);
}
$outputFileHandle = fopen($output, 'w');
if (!$outputFileHandle) {
throw new \Exception('Cannot open file ' . $output . 'for writing.');
}
fwrite($outputFileHandle, $extra[0]);
while (($line = fgets($inputFileHandle)) !== false) {
fwrite($outputFileHandle, $line);
}
fwrite($outputFileHandle, $extra[1]);
fclose($inputFileHandle);
fclose($outputFileHandle);
}
private function generateAssets(ParsedFragment $fragment, array &$sections): void
{
foreach ($fragment->getCss() as $cssFile) {
$asset = $this->parseAsset($cssFile);
if (isset($asset['code'])) {
$sections['inlineCss'] .= '<style>' . PHP_EOL;
$sections['inlineCss'] .= $asset['code'];
$sections['inlineCss'] .= '</style>' . PHP_EOL;
} elseif (isset($asset['file'])) {
$sections['externalCss'] .= '<link href="' . $asset['file'] . '" rel="stylesheet">' . PHP_EOL;
}
}
foreach ($fragment->getJs() as $jsFile) {
$asset = $this->parseAsset($jsFile);
if (isset($asset['code'])) {
$sections['inlineJs'] .= '<script>' . PHP_EOL;
$sections['inlineJs'] .= $asset['code'];
$sections['inlineJs'] .= '</script>' . PHP_EOL;
} elseif (isset($asset['file'])) {
$sections['externalJs'] .= '<script src="' . $asset['file'] . '"></script>' . PHP_EOL;
}
}
}
private function parseAsset(string $asset): array
{
$output = [];
if (preg_match('/^[\w\/\.]+$/', $asset) === 1) {
if (
empty($_ENV['DEV']) &&
filesize(ROOT . '/public/static/' . $asset) < self::INLINE_ASSET_LIMIT
) {
$output['code'] = file_get_contents(ROOT . '/public/static/' . $asset);
} else {
$output['file'] = '<?= $_ENV[\'STATIC_ROOT\'] ?>/' . $asset . '?rev=<?= REVISION ?>';
}
} else {
$output['file'] = $asset;
}
return $output;
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php namespace SokoWeb\View;
class ParsedFragment
{
private ?string $extends;
private array $css;
private array $js;
private array $sections;
private array $extra;
public function __construct(?string $extends, array $css, array $js, array $sections, array $extra)
{
$this->extends = $extends;
$this->css = $css;
$this->js = $js;
$this->sections = $sections;
$this->extra = $extra;
}
public function getExtends(): ?string
{
return $this->extends;
}
public function getCss(): array
{
return $this->css;
}
public function getJs(): array
{
return $this->js;
}
public function getSections(): array
{
return $this->sections;
}
public function getExtra(): array
{
return $this->extra;
}
}
+159
View File
@@ -0,0 +1,159 @@
<?php namespace SokoWeb\View;
class Parser
{
private string $file;
public function __construct(string $file)
{
$this->file = $file;
}
public function parse(): ParsedFragment
{
$sectionOpen = null;
$extraOpen = false;
$extends = null;
$js = [];
$css = [];
$sections = [];
$extra = ['', ''];
$fileHandle = fopen($this->file, 'r');
if (!$fileHandle) {
throw new \Exception('Cannot open file ' . $this->file);
}
$lineNumber = 0;
while (($line = fgets($fileHandle)) !== false) {
++$lineNumber;
if (($cssMatched = $this->matchCss($line)) !== null) {
$css[] = $cssMatched;
continue;
}
if (($jsMatched = $this->matchJs($line)) !== null) {
$js[] = $jsMatched;
continue;
}
if (($extendsMatched = $this->matchExtends($line)) !== null) {
if ($extends !== null) {
throw new \Exception('Error in file ' . $this->file . ' in line ' . $lineNumber . ' - There is already an \'@extends\' declared.');
}
$extends = $extendsMatched;
continue;
}
if (($sectionMatched = $this->matchSection($line)) !== null) {
if ($extends === null) {
throw new \Exception('Error in file ' . $this->file . ' in line ' . $lineNumber . ' - \'@section\' has no meaning if view extends nothing.');
}
if ($sectionOpen !== null) {
throw new \Exception('Parse error in file ' . $this->file . ' in line ' . $lineNumber . ' - A \'@section\' is already open (no \'@endsection\' found).');
}
$sectionOpen = $sectionMatched;
$sections[$sectionOpen] = '';
continue;
}
if ($this->matchEndSection($line)) {
if ($sectionOpen === null) {
throw new \Exception('Parse error in file ' . $this->file . ' in line ' . $lineNumber . ' - Cannot end section until no \'@section\' is open.');
}
$sectionOpen = null;
}
if ($this->matchExtra($line)) {
if ($extraOpen) {
throw new \Exception('Parse error in file ' . $this->file . ' in line ' . $lineNumber . ' - An \'@extra\' is already open (no \'@endextra\' found).');
}
$extraOpen = true;
continue;
}
if ($this->matchEndExtra($line)) {
if (!$extraOpen) {
throw new \Exception('Parse error in file ' . $this->file . ' in line ' . $lineNumber . ' - Cannot end extra until no \'@extra\' is open.');
}
$extraOpen = false;
}
if ($sectionOpen !== null) {
$sections[$sectionOpen] .= $line;
}
if ($extraOpen) {
$offset = $extends === null ? 0 : 1;
$extra[$offset] .= $line;
}
}
fclose($fileHandle);
return new ParsedFragment($extends, $css, $js, $sections, $extra);
}
private function matchCss(string $line): ?string
{
if (preg_match('/^\s*@css\((.*)\)\s*$/', $line, $matches) === 1) {
return $matches[1];
}
return null;
}
private function matchJs(string $line): ?string
{
if (preg_match('/^\s*@js\((.*)\)\s*$/', $line, $matches) === 1) {
return $matches[1];
}
return null;
}
private function matchExtends(string $line): ?string
{
if (preg_match('/^\s*@extends\(([\w\/]+)\)\s*$/', $line, $matches) === 1) {
return $matches[1];
}
return null;
}
private function matchSection(string $line): ?string
{
if (preg_match('/^\s*@section\((\w+)\)\s*$/', $line, $matches) === 1) {
return $matches[1];
}
return null;
}
private function matchEndSection(string $line): bool
{
return preg_match('/^\s*@endsection(?:\(\))?\s*$/', $line) === 1;
}
private function matchExtra(string $line): bool
{
return preg_match('/^\s*@extra(?:\(\))?\s*$/', $line) === 1;
}
private function matchEndExtra(string $line): bool
{
return preg_match('/^\s*@endextra(?:\(\))?\s*$/', $line) === 1;
}
}