From 43e03c125dcf0413ee9ab844ced3583c91894253 Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Tue, 4 Sep 2018 02:44:57 -0400 Subject: php: add composer.json, mv luigi-template.php src/ --- php/composer.json | 38 +++++ php/luigi-template.php | 393 --------------------------------------------- php/src/luigi-template.php | 393 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 431 insertions(+), 393 deletions(-) create mode 100644 php/composer.json delete mode 100644 php/luigi-template.php create mode 100644 php/src/luigi-template.php diff --git a/php/composer.json b/php/composer.json new file mode 100644 index 0000000..a00d0d4 --- /dev/null +++ b/php/composer.json @@ -0,0 +1,38 @@ +{ + "name": "pablotron/luigi-template", + "description": "String emplate library.", + "homepage": "https://github.com/pablotron/luigi-template", + "license": "MIT", + + "authors": [{ + "name": "Paul Duncan", + "email": "luigi-template@pablotron.org", + "homepage": "https://pablotron.org/", + "role": "developer" + }], + + "require": { + "php": "^7" + }, + + "require-dev": { + "phpunit/phpunit": "^7", + "phpdocumentor/phpdocumentor": "2.*" + }, + + "autoload": { + "psr-4": { + "Luigi\\": "src/" + } + }, + + "scripts": { + "test": [ + "phpunit --bootstrap vendor/autoload.php tests" + ], + + "docs": [ + "phpdoc -d src -t docs --template=responsive-twig --title=LuigiTemplate" + ] + } +} diff --git a/php/luigi-template.php b/php/luigi-template.php deleted file mode 100644 index 69e3fb8..0000000 --- a/php/luigi-template.php +++ /dev/null @@ -1,393 +0,0 @@ -type = $type; - $this->name = $name; - parent::__construct("unknown $type: $name"); - } -}; - -final class UnknownTemplateError extends UnknownTypeError { - public function __construct(string $name) { - parent::__construct('template', $name); - } -}; - -final class UnknownFilterError extends UnknownTypeError { - public function __construct(string $name) { - parent::__construct('filter', $name); - } -}; - -final class UnknownKeyError extends UnknownTypeError { - public function __construct(string $name) { - parent::__construct('key', $name); - } -}; - -final class MissingFilterParameterError extends Error { - public $filter_name; - - public function __construct(string $filter_name) { - $this->filter_name = $filter_name; - parent::__construct("missing required filter parameter for filter $filter_name"); - } -}; - -final class InvalidTemplateError extends Error { - public $template; - - public function __construct(string $template) { - $this->template = $template; - parent::__construct("invalid template: $template"); - } -}; - -namespace Luigi; - -final class RunContext { - public $args, - $filters; - - public function __construct(array $args, array $filters) { - $this->args = $args; - $this->filters = $filters; - } -}; - -final class TemplateFilter { - private $name, - $args; - - public function __construct(string $name, array $args) { - $this->name = $name; - $this->args = $args; - } - - public function run($v, array &$args, array &$filters) { - if (!isset($filters[$this->name])) { - throw new UnknownFilterError($this->name); - } - - # get callback - $cb = $filters[$this->name]; - - # invoke callback, return result - return $cb($v, $this->args, $args); - } -}; - -namespace Luigi\Parser; - -use Luigi\RunContext; -use Luigi - -abstract class Token { - public function run(array $args, array $filters) : string; -}; - -final class LiteralToken extends Token { - private $val; - - public function __construct(string $val) { - $this->val = $val; - } - - public function run(array &$args, array &$filters) : string { - return $this->val; - } -}; - -final class FilterToken extends Token { - private $key, - $filters; - - public function __construct(string $key, array $filters) { - $this->key = $key; - $this->filters = $filters; - } - - public function run(array &$args, array &$filters) : string { - if (!isset($args[$this->key])) { - throw new UnknownKeyError($this->key); - } - - # get initial value - $r = $args[$this->key]; - - if ($this->filters && count($this->filters) > 0) { - # pass value through filters - $r = array_reduce($this->filters, function($r, $f) use (&$args, &$filters) { - return $f->run($r, $args, $filters); - }, $r); - } - - # return result - return $r; - } -}; - -const TOKEN_RE = '/ - # match opening brace - %\{ - - # match optional whitespace - \s* - - # match key - (?[^\s\|\}]+) - - # match filter(s) - (?(\s*\|(\s*[^\s\|\}]+)+)*) - - # match optional whitespace - \s* - - # match closing brace - \} - - # or match up all non-% chars or a single % char - | (?[^%]* | %) -/mx'; - -const FILTER_RE = '/ - # match filter name - (?\S+) - - # match filter arguments (optional) - (?(\s*\S+)*) - - # optional trailing whitespace - \s* -/mx'; - -const DELIM_FILTERS_RE = '/\s*\|\s*/m'; - -const DELIM_ARGS_RE = '/\s+/m'; - -function parse_filters(string $filters) : array { - # split into individual filters - $r = []; - - foreach (preg_split(DELIM_FILTERS_RE, $filters) as $f) { - # trim whitespace - $f = trim($f); - - # skip empty filters - if (!$f) - continue; - - # match filter - $md = []; - if (!preg_match(FILTER_RE, $f, $md)) { - throw new UnknownFilterError($f); - } - - # add filter to results - $r[] = new TemplateFilter($md['name'], (count($md) > 2) ? preg_split( - DELIM_ARGS_RE, - trim($md['args']) - ) : []); - } - - # return results - return $r; -} - -function parse_template(string $template) : array { - # build list of matches - $matches = []; - $num_matches = preg_match_all(TOKEN_RE, $template, $matches, PREG_SET_ORDER); - - # check for error - if ($num_matches === false) { - throw new InvalidTemplateError($template); - } - - # map matches to tokens - return array_map(function($m) { - if ($m['key'] !== '') { - # filter token - return new FilterToken($m['key'], parse_filters($m['filters'])); - } else { - # literal token - return new LiteralToken($m['text']); - } - }, $matches); -} - -namespace Luigi; - -public $FILTERS = [ - 'h' => function($s) { - return htmlspecialchars($v, ENT_QUOTES); - }, - - 'u' => function($s) { - return urlencode($v); - }, - - 'json' => function($v) { - return json_encode($v); - }, - - 'hash' => function($v, $args) { - if (count($args) !== 1) { - throw new MissingFilterParameterError('hash'); - } - - return hash($args[0], $v); - }, - - 'base64' => function($v) { - return base64_encode($v); - }, - - 'nl2br' => function($v) { - return nl2br($v); - }, - - 'uc' => function($v) { - return strtoupper($v); - }, - - 'lc' => function($v) { - return strtolower($v); - }, - - 'trim' => function($v) { - return trim($v); - }, - - 'rtrim' => function($v) { - return rtrim($v); - }, - - 'ltrim' => function($v) { - return ltrim($v); - }, - - 's' => function($v) { - return ($v == 1) ? '' : 's'; - }, - - 'strlen' => function($v) { - return strlen($v); - }, - - 'count' => function($v) { - return count($v); - }, - - 'key' => function($v, $args) { - if (count($args) !== 1) { - throw new MissingFilterParameterError('key'); - } - - # get key - $key = $args[0]; - - # make sure key exists - if (!isset($v[$key])) { - throw new UnknownKeyError($key); - } - - # return key - return $v[$key]; - }, -]; - -final class Template { - private $template, - $filters, - $tokens; - - public function __construct( - string $template, - array $custom_filters = [] - ) { - global $FILTERS; - - $this->template = $template; - $this->filters = count($custom_filters) ? $custom_filters : $FILTERS; - - # parse template into list of tokens - $this->tokens = Parser\parse_template($template); - } - - public function run(array $args = []) : string { - # php sucks - $me = $this; - - return join('', array_map(function($token) use ($me, $args) { - return $token->run($args, $this->filters); - }, $this->tokens)); - } - - public static function once($str, $args = [], array $filters = []) { - $t = new Template($str, $filters); - return $t->run($args); - } -}; - -public function run( - string $template, - array $args = [], - array $filters = [] -) : string { - $t = new Template($template, $filters); - return $t->run($args); -} - -final class Cache implements \ArrayAccess { - private $templates, - $filters, - $lut = []; - - public function __construct(array $templates, array $filters = []) { - $this->templates = $templates; - $this->filters = $filters; - } - - public function offsetExists($key) : bool { - return isset($this->templates[$key]); - } - - public function offsetGet($key) { - if (isset($this->lut[$key])) { - return $this->lut[$key]; - } else if (isset($this->templates[$key]) { - $this->lut[$key] = new Template($this->templates[$key], $this->filters); - return $this->lut[$key]; - } else { - throw new UnknownTemplateError($key); - } - } - - public function offsetUnset($key) { - delete($this->lut[$key]); - delete($this->templates[$key]); - } - - public function offsetSet($key, $val) : bool { - delete($this->lut[$key]); - $this->templates[$key] = $val; - return isset($this->templates[$key]); - } - - - public function run(string $key, array $args = []) : string { - return $this->offsetGet($key)->run($args); - } -}; diff --git a/php/src/luigi-template.php b/php/src/luigi-template.php new file mode 100644 index 0000000..69e3fb8 --- /dev/null +++ b/php/src/luigi-template.php @@ -0,0 +1,393 @@ +type = $type; + $this->name = $name; + parent::__construct("unknown $type: $name"); + } +}; + +final class UnknownTemplateError extends UnknownTypeError { + public function __construct(string $name) { + parent::__construct('template', $name); + } +}; + +final class UnknownFilterError extends UnknownTypeError { + public function __construct(string $name) { + parent::__construct('filter', $name); + } +}; + +final class UnknownKeyError extends UnknownTypeError { + public function __construct(string $name) { + parent::__construct('key', $name); + } +}; + +final class MissingFilterParameterError extends Error { + public $filter_name; + + public function __construct(string $filter_name) { + $this->filter_name = $filter_name; + parent::__construct("missing required filter parameter for filter $filter_name"); + } +}; + +final class InvalidTemplateError extends Error { + public $template; + + public function __construct(string $template) { + $this->template = $template; + parent::__construct("invalid template: $template"); + } +}; + +namespace Luigi; + +final class RunContext { + public $args, + $filters; + + public function __construct(array $args, array $filters) { + $this->args = $args; + $this->filters = $filters; + } +}; + +final class TemplateFilter { + private $name, + $args; + + public function __construct(string $name, array $args) { + $this->name = $name; + $this->args = $args; + } + + public function run($v, array &$args, array &$filters) { + if (!isset($filters[$this->name])) { + throw new UnknownFilterError($this->name); + } + + # get callback + $cb = $filters[$this->name]; + + # invoke callback, return result + return $cb($v, $this->args, $args); + } +}; + +namespace Luigi\Parser; + +use Luigi\RunContext; +use Luigi + +abstract class Token { + public function run(array $args, array $filters) : string; +}; + +final class LiteralToken extends Token { + private $val; + + public function __construct(string $val) { + $this->val = $val; + } + + public function run(array &$args, array &$filters) : string { + return $this->val; + } +}; + +final class FilterToken extends Token { + private $key, + $filters; + + public function __construct(string $key, array $filters) { + $this->key = $key; + $this->filters = $filters; + } + + public function run(array &$args, array &$filters) : string { + if (!isset($args[$this->key])) { + throw new UnknownKeyError($this->key); + } + + # get initial value + $r = $args[$this->key]; + + if ($this->filters && count($this->filters) > 0) { + # pass value through filters + $r = array_reduce($this->filters, function($r, $f) use (&$args, &$filters) { + return $f->run($r, $args, $filters); + }, $r); + } + + # return result + return $r; + } +}; + +const TOKEN_RE = '/ + # match opening brace + %\{ + + # match optional whitespace + \s* + + # match key + (?[^\s\|\}]+) + + # match filter(s) + (?(\s*\|(\s*[^\s\|\}]+)+)*) + + # match optional whitespace + \s* + + # match closing brace + \} + + # or match up all non-% chars or a single % char + | (?[^%]* | %) +/mx'; + +const FILTER_RE = '/ + # match filter name + (?\S+) + + # match filter arguments (optional) + (?(\s*\S+)*) + + # optional trailing whitespace + \s* +/mx'; + +const DELIM_FILTERS_RE = '/\s*\|\s*/m'; + +const DELIM_ARGS_RE = '/\s+/m'; + +function parse_filters(string $filters) : array { + # split into individual filters + $r = []; + + foreach (preg_split(DELIM_FILTERS_RE, $filters) as $f) { + # trim whitespace + $f = trim($f); + + # skip empty filters + if (!$f) + continue; + + # match filter + $md = []; + if (!preg_match(FILTER_RE, $f, $md)) { + throw new UnknownFilterError($f); + } + + # add filter to results + $r[] = new TemplateFilter($md['name'], (count($md) > 2) ? preg_split( + DELIM_ARGS_RE, + trim($md['args']) + ) : []); + } + + # return results + return $r; +} + +function parse_template(string $template) : array { + # build list of matches + $matches = []; + $num_matches = preg_match_all(TOKEN_RE, $template, $matches, PREG_SET_ORDER); + + # check for error + if ($num_matches === false) { + throw new InvalidTemplateError($template); + } + + # map matches to tokens + return array_map(function($m) { + if ($m['key'] !== '') { + # filter token + return new FilterToken($m['key'], parse_filters($m['filters'])); + } else { + # literal token + return new LiteralToken($m['text']); + } + }, $matches); +} + +namespace Luigi; + +public $FILTERS = [ + 'h' => function($s) { + return htmlspecialchars($v, ENT_QUOTES); + }, + + 'u' => function($s) { + return urlencode($v); + }, + + 'json' => function($v) { + return json_encode($v); + }, + + 'hash' => function($v, $args) { + if (count($args) !== 1) { + throw new MissingFilterParameterError('hash'); + } + + return hash($args[0], $v); + }, + + 'base64' => function($v) { + return base64_encode($v); + }, + + 'nl2br' => function($v) { + return nl2br($v); + }, + + 'uc' => function($v) { + return strtoupper($v); + }, + + 'lc' => function($v) { + return strtolower($v); + }, + + 'trim' => function($v) { + return trim($v); + }, + + 'rtrim' => function($v) { + return rtrim($v); + }, + + 'ltrim' => function($v) { + return ltrim($v); + }, + + 's' => function($v) { + return ($v == 1) ? '' : 's'; + }, + + 'strlen' => function($v) { + return strlen($v); + }, + + 'count' => function($v) { + return count($v); + }, + + 'key' => function($v, $args) { + if (count($args) !== 1) { + throw new MissingFilterParameterError('key'); + } + + # get key + $key = $args[0]; + + # make sure key exists + if (!isset($v[$key])) { + throw new UnknownKeyError($key); + } + + # return key + return $v[$key]; + }, +]; + +final class Template { + private $template, + $filters, + $tokens; + + public function __construct( + string $template, + array $custom_filters = [] + ) { + global $FILTERS; + + $this->template = $template; + $this->filters = count($custom_filters) ? $custom_filters : $FILTERS; + + # parse template into list of tokens + $this->tokens = Parser\parse_template($template); + } + + public function run(array $args = []) : string { + # php sucks + $me = $this; + + return join('', array_map(function($token) use ($me, $args) { + return $token->run($args, $this->filters); + }, $this->tokens)); + } + + public static function once($str, $args = [], array $filters = []) { + $t = new Template($str, $filters); + return $t->run($args); + } +}; + +public function run( + string $template, + array $args = [], + array $filters = [] +) : string { + $t = new Template($template, $filters); + return $t->run($args); +} + +final class Cache implements \ArrayAccess { + private $templates, + $filters, + $lut = []; + + public function __construct(array $templates, array $filters = []) { + $this->templates = $templates; + $this->filters = $filters; + } + + public function offsetExists($key) : bool { + return isset($this->templates[$key]); + } + + public function offsetGet($key) { + if (isset($this->lut[$key])) { + return $this->lut[$key]; + } else if (isset($this->templates[$key]) { + $this->lut[$key] = new Template($this->templates[$key], $this->filters); + return $this->lut[$key]; + } else { + throw new UnknownTemplateError($key); + } + } + + public function offsetUnset($key) { + delete($this->lut[$key]); + delete($this->templates[$key]); + } + + public function offsetSet($key, $val) : bool { + delete($this->lut[$key]); + $this->templates[$key] = $val; + return isset($this->templates[$key]); + } + + + public function run(string $key, array $args = []) : string { + return $this->offsetGet($key)->run($args); + } +}; -- cgit v1.2.3