Merge pull request #3 from Zigazou/master

Source code now conforms to PSR-2
This commit is contained in:
Gnieark 2018-03-24 11:29:35 +01:00 committed by GitHub
commit 71c97e6d91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 567 additions and 229 deletions

View File

@ -8,4 +8,4 @@ php:
before_script:
- curl -sSfL -o ~/.phpenv/versions/hhvm/bin/phpunit https://phar.phpunit.de/phpunit-5.7.phar
script:
- phpunit .
- phpunit --bootstrap autoload.php test/TplBlockTest.php

18
Makefile Executable file
View File

@ -0,0 +1,18 @@
test: test-unit
lint: lint-md lint-cs
# Run every available unit test
test-unit:
vendor/bin/phpunit --bootstrap vendor/autoload.php test
# Run PHP Mess Detector against all the code, settings are in phpmd.xml
lint-md:
vendor/bin/phpmd TplBlock.php text ./phpmd.xml
vendor/bin/phpmd test/TplBlockTest.php text ./phpmd.xml
vendor/bin/phpmd sample/sample.php text ./phpmd.xml
# Run PHP Code Sniffer against all the code, settings are in phpcs.xml
lint-cs:
vendor/bin/phpcs TplBlock.php -s
vendor/bin/phpcs test/TplBlockTest.php -s
vendor/bin/phpcs sample/sample.php -s

283
TplBlock.php Normal file
View File

@ -0,0 +1,283 @@
<?php
/**
* Gniearks TplBlock.
*
* PHP version 5
*
* @category Template
* @package TplBlock
* @author gnieark <gnieark@tinad.fr>
* @license GNU General Public License V3
* @link https://github.com/gnieark/tplBlock/
*/
namespace TplBlock;
/**
* The TplBlock class.
*
* @category Template
* @package TplBlock
* @author gnieark <gnieark@tinad.fr>
* @license GNU General Public License V3
* @link https://github.com/gnieark/tplBlock/
*/
class TplBlock
{
/**
* The string starting a block start.
*
* @var string
*/
const BLOCKSTARTSTART = '<!-- BEGIN ';
/**
* The string ending a block start.
*
* @var string
*/
const BLOCKSTARTEND = ' -->';
/**
* The string starting a block end.
*
* @var string
*/
const BLOCKENDSTART = '<!-- END ';
/**
* The string ending a block end.
*
* @var string
*/
const BLOCKENDEND = ' -->';
/**
* The string starting an enclosure.
*
* @var string
*/
const STARTENCLOSURE = '{{';
/**
* The string ending an enclosure.
*
* @var string
*/
const ENDENCLOSURE = '}}';
/**
* The name of the block.
*
* @var string
*/
public $name = '';
/**
* The array containing the variables used by TplBlock.
*
* @var array
*/
private $vars = [];
/**
* The array containing the sub blocks.
*
* @var array
*/
private $subBlocs = [];
/**
* The regex recognizing that a block is unused.
*
* @var string
*/
private $unusedRegex = "";
/**
* Should we trim?
*
* @var boolean
*/
private $trim = true;
/**
* Initialize TplBlock
*
* The name can be empty only for the top one block.
*
* @param string $name The template name
*/
public function __construct($name = "")
{
// Checks that name is valid.
if ($name !== "" and ! ctype_alnum($name)) {
throw new \UnexpectedValueException(
"Only alpha-numerics chars are allowed on the block name"
);
}
$this->name = $name;
// Build the unused regex.
$this->unusedRegex = '/'
. self::BLOCKSTARTSTART
. ' *([a-z][a-z0-9.]*) *'
. self::BLOCKSTARTEND
. '(.*?)'
. self::BLOCKENDSTART
. ' *\1 *'
. self::BLOCKENDEND
. '/is'
;
}
/**
* Add simple variables
*
* The array must be structured like this:
*
* [ "key" => "value", "key2" => "value2" ]
*
* @param array $vars Variables to add.
*
* @return TplBlock For chaining.
*/
public function addVars(array $vars)
{
$this->vars = array_merge($this->vars, $vars);
return $this;
}
/**
* Add a sub block.
*
* @param TplBlock $bloc The block to add as a sub block.
*
* @return TplBlock For chaining.
*/
public function addSubBlock(TplBlock $bloc)
{
// An unnamed block cannot be a sub block.
if ($bloc->name === "") {
throw new \UnexpectedValueException(
"A sub tpl block can't have an empty name"
);
}
$this->subBlocs[$bloc->name][] = $bloc;
return $this;
}
/**
* Generate the sub block regex.
*
* @param string $prefix The prefix to add to the block name.
* @param string $blocName The block name.
*
* @return string The regex.
*/
private function subBlockRegex($prefix, $blocName)
{
return '/'
. self::BLOCKSTARTSTART
. preg_quote($prefix . $blocName)
. self::BLOCKSTARTEND
. ($this->trim === false ? '' : '(?:\R|)?' )
. '(.*?)'
. ($this->trim === false ? '' : '(?:\R|)?' )
. self::BLOCKENDSTART
. preg_quote($prefix . $blocName)
. self::BLOCKENDEND
. '/is';
}
/**
* Shake the template string and input vars then returns the parsed text.
*
* @param string $str containing the template to parse
* @param string $subBlocsPath optional, for this class internal use.
* The path should look like "bloc.subbloc".
*
* @return string The processed output.
*/
public function applyTplStr($str, $subBlocsPath = "")
{
// Replace all simple vars.
$prefix = $subBlocsPath === "" ? "" : $subBlocsPath . ".";
foreach ($this->vars as $key => $value) {
$str = str_replace(
self::STARTENCLOSURE . $prefix . $key . self::ENDENCLOSURE,
$value,
$str
);
}
// Parse blocs.
foreach ($this->subBlocs as $blocName => $blocsArr) {
$str = preg_replace_callback(
$this->subBlockRegex($prefix, $blocName),
function ($m) use ($blocName, $blocsArr, $prefix) {
$out = "";
foreach ($blocsArr as $bloc) {
// Recursion.
$out .= $bloc->applyTplStr(
$m[1],
$prefix . $blocName
);
}
return $out;
},
$str
);
}
// Delete unused blocs.
$str = preg_replace($this->unusedRegex, "", $str);
return $str;
}
/**
* Load a file, and pass his content to applyTplStr function.
*
* @param string $file The file path of the template to load
*
* @return string The processed output.
*/
public function applyTplFile($file)
{
if (! $tplStr = file_get_contents($file)) {
throw new \UnexpectedValueException("Cannot read given file $file");
}
return $this->applyTplStr($tplStr, "");
}
/**
* Enables trimming.
*
* @return TplBlock For chaining.
*/
public function doTrim()
{
$this->trim = true;
return $this;
}
/**
* Disables trimming.
*
* @return TplBlock For chaining.
*/
public function dontTrim()
{
$this->trim = false;
return $this;
}
}

2
autoload.php Normal file
View File

@ -0,0 +1,2 @@
<?php
require "TplBlock.php";

View File

@ -1,139 +0,0 @@
<?php
class InvalidTemplateException extends UnexpectedValueException{
}
class TplBlock {
/*
* Class TplBlog
* By gnieark https://blog-du-grouik.tinad.fr 2018
* Licenced under the GNU General Public License V3
* https://www.gnu.org/licenses/gpl-3.0.fr.html
*/
const blockStartStart = '<!-- BEGIN ';
const blockStartEnd = ' -->';
const blockEndStart = '<!-- END ';
const blockEndEnd = ' -->';
const startEnclosure = '{{';
const endEnclosure = '}}';
public $name = '';
private $vars = array();
private $subBlocs = array();
private $unusedRegex = "";
/*
* Initialise TplBlock
* Input object name
* Can be empty only for the top one block
*/
public function __construct($name = NULL){
$this->name = $name;
if(!is_null($name) && !ctype_alnum($name)){
throw new InvalidTemplateException("Only alpha-numerics chars are allowed on the block name");
return false;
}
$this->unusedRegex = '/'
. self::blockStartStart
. ' *([a-z][a-z0-9.]*) *'
. self::blockStartEnd
. '(.*?)'
. self::blockEndStart
. ' *\1 *'
. self::blockEndEnd
. '/is'
;
}
/*
* Add simple vars
* Input array structured like:
* {"key":"value","key2":"value2"}
*/
public function add_vars(ARRAY $vars){
$this->vars = array_merge($this->vars,$vars);
}
/*
* add_sub_block
* Input: a TplBlock object.
*/
public function add_sub_block(TplBlock $bloc){
if(is_null($bloc->name) || empty($bloc->name)){
throw new InvalidTemplateException("A sub tpl bloc can't have an empty name");
return false;
}
$this->subBlocs[$bloc->name][] = $bloc;
}
private function subBlockRegex($prefix, $blocName,$trim = true) {
return '/'
. self::blockStartStart
. preg_quote($prefix . $blocName)
. self::blockStartEnd
. (($trim === false)? '' : '(?:\R|)?' )
. '(.*?)'
. (($trim === false)? '' : '(?:\R|)?' )
. self::blockEndStart
. preg_quote($prefix . $blocName)
. self::blockEndEnd
. '/is';
}
/*
* Shake the template string and input vars
* Then returns the parsed text
* Input:
* $str String containing the template to parse
* $subBlocsPath String optional, for this class internal use. The path like "bloc.subbloc"
* $trim Boolean
* if true(default), the potentials Carriages returns beginning
* and ending the bloc are deleted
*/
public function apply_tpl_str($str,$subBlocsPath = "", $trim = true){
//replace all simple vars
$prefix = (empty($subBlocsPath)? "" : $subBlocsPath.".");
foreach($this->vars as $key=>$value){
$str = str_replace(self::startEnclosure . $prefix . $key . self::endEnclosure,
$value,
$str);
}
//parse blocs
foreach($this->subBlocs as $blocName => $blocsArr){
$str = preg_replace_callback(
$this->subBlockRegex($prefix, $blocName, $trim),
function($m) use($blocName,$blocsArr,$prefix, $trim) {
$out = "";
foreach($blocsArr as $bloc){
//recursion
$out.=$bloc->apply_tpl_str( $m[1] , $prefix . $blocName , $trim );
}
return $out;
}
,$str
);
}
// Delete unused blocs
$str = preg_replace($this->unusedRegex, "", $str);
return $str;
}
/*
* load a file, and pass his content to apply_tpl_str function.
*/
public function apply_tpl_file($file, $trim = true){
if(!$tplStr = file_get_contents($file)){
throw new InvalidTemplateException("Cannot read given file ".$file);
return false;
}
return $this->apply_tpl_str($tplStr, null, $trim);
}
}

13
composer.json Normal file
View File

@ -0,0 +1,13 @@
{
"autoload": {
"classmap": [
"./"
]
},
"require-dev": {
"phpunit/phpunit": "5.7",
"phpdocumentor/phpdocumentor": "2.*",
"phpmd/phpmd": "^2.6",
"gamegos/php-code-sniffer": "*"
}
}

47
phpcs.xml Normal file
View File

@ -0,0 +1,47 @@
<?xml version="1.0"?>
<ruleset name="Basic Project Coding Standards">
<rule ref="PSR2" />
<rule ref="Generic">
<!-- Rules of an ancient time... -->
<exclude name="Generic.PHP.ClosingPHPTag.NotFound" />
<exclude name="Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed" />
<exclude name="Generic.Arrays.DisallowShortArraySyntax.Found" />
<exclude name="Generic.Files.EndFileNoNewline.Found" />
<exclude name="Generic.Files.LowercasedFilename.NotFound" />
<exclude name="Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine" />
<!-- These rules are in conflict with PSR2 -->
<exclude name="Generic.Classes.OpeningBraceSameLine.BraceOnNewLine" />
<exclude name="Generic.PHP.UpperCaseConstant.Found" />
<exclude name="Generic.Functions.OpeningFunctionBraceKernighanRitchie.BraceOnNewLine" />
<exclude name="Generic.Formatting.NoSpaceAfterCast.SpaceFound" />
</rule>
<rule ref="PEAR">
<!-- This rule is in conflict with Generic -->
<exclude name="PEAR.WhiteSpace.ScopeIndent.IncorrectExact" />
<!-- This rule is in conflict with PSR2 -->
<exclude name="PEAR.NamingConventions.ValidFunctionName.PrivateNoUnderscore"/>
<exclude name="PEAR.NamingConventions.ValidVariableName.PrivateNoUnderscore" />
</rule>
<!-- Some interesting rules from Squiz -->
<rule ref="Squiz.PHP.NonExecutableCode"/>
<rule ref="Squiz.PHP.CommentedOutCode"/>
<rule ref="Squiz.PHP.DiscouragedFunctions"/>
<rule ref="Squiz.PHP.ForbiddenFunctions"/>
<rule ref="Squiz.Classes.ClassDeclaration"/>
<rule ref="Squiz.ControlStructures.ControlSignature"/>
<rule ref="Squiz.ControlStructures.ForEachLoopDeclaration"/>
<rule ref="Squiz.ControlStructures.ForLoopDeclaration"/>
<rule ref="Squiz.Functions.FunctionDeclaration"/>
<rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing"/>
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"/>
<!-- Our files are located in src and tests directories -->
<file>.</file>
<file>./test</file>
</ruleset>

18
phpmd.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<ruleset name="PHPMD rule set for TemplateEngine"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="
http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>Custom rule set for TemplateEngine</description>
<rule ref="rulesets/cleancode.xml" />
<rule ref="rulesets/codesize.xml" />
<rule ref="rulesets/controversial.xml" />
<rule ref="rulesets/design.xml" />
<rule ref="rulesets/naming.xml" />
<rule ref="rulesets/unusedcode.xml" />
</ruleset>

View File

@ -1,47 +1,77 @@
<?php
/**
* Gniearks TplBlock sample.
*
* PHP version 5
*
* @category Template
* @package TplBlock
* @author gnieark <gnieark@tinad.fr>
* @license GNU General Public License V3
* @link https://github.com/gnieark/tplBlock/
*/
require_once "../vendor/autoload.php";
include("../class.TplBlock.php");
use TplBlock\TplBlock;
$tpl = new TplBlock();
const PRIMES = [ 1, 2, 3, 5, 7, 11 ];
//simples vars
/**
* Find divisors of a number.
*
* It works as long as the number is less than the last PRIMES number squared.
*
* @param int $number The number to find divisors for.
*
* @return array An array of divisors.
*/
function findDivisors(int $number)
{
$divisors = [];
$index = 1;
while ($number > 1 and $index < count(PRIMES)) {
if ($number % PRIMES[$index] != 0) {
$index++;
continue;
}
$tpl->add_vars(array(
$number = $number / PRIMES[$index];
$divisors[] = PRIMES[$index];
}
return $divisors;
}
$variables = [
"name" => "Gnieark",
"title" => "Monsieur",
"firstname" => "Grouik"
)
"firstname" => "Grouik",
];
// Simples vars.
$template = (new TplBlock())->addVars($variables);
// A sub bloc.
foreach (PRIMES as $prime) {
$template->addSubBlock(
(new TplBlock("primes"))->addVars([ "number" => $prime ])
);
$primes = array(1,2,3,5,7,11);
// a sub bloc
foreach($primes as $prime){
$tplPrime = new TplBlock('primes');
$tplPrime->add_vars(array('number' => $prime));
$tpl->add_sub_block($tplPrime);
}
// test sub - sub blocs
for ($i = 2; $i < 121; $i++){
// Find highest number for which we can find divisors.
$lastNumber = pow(PRIMES[count(PRIMES) - 1], 2);
$tplNumber = new TplBlock('number');
$tplNumber->add_vars( array("value" => $i));
$index = 1;
$number = $i;
while ( $number > 1 && $index < count($primes)){
if($number % $primes[$index] == 0){
$number = $number / $primes[$index];
$tplDivisor = new TplBlock("divisor");
$tplDivisor->add_vars( array("value" => $primes[$index]));
$tplNumber->add_sub_block($tplDivisor);
}else{
$index++;
// Test sub - sub blocs.
for ($i = 2; $i <= $lastNumber; $i++) {
$templateNumber = (new TplBlock("number"))->addVars([ "value" => $i ]);
foreach (findDivisors($i) as $divisor) {
$templateNumber->addSubBlock(
(new TplBlock("divisor"))->addVars([ "value" => $divisor ])
);
}
}
$tpl->add_sub_block($tplNumber);
$template->addSubBlock($templateNumber);
}
echo $tpl->apply_tpl_file("tpl.txt");
echo $template->applyTplFile("tpl.txt");

View File

@ -1,45 +1,102 @@
<?php
use PHPUnit\Framework\TestCase;
require_once __DIR__.'/../class.TplBlock.php';
class TplBlockTest extends TestCase{
/**
* @expectedException InvalidTemplateException
/**
* Gniearks TplBlock unit tests.
*
* PHP version 5
*
* @category Template
* @package TplBlock
* @author gnieark <gnieark@tinad.fr>
* @license GNU General Public License V3
* @link https://github.com/gnieark/tplBlock/
*/
public function testSendEmptyNameOnSubFunction(){
$tpl = new TplBlock();
$subTpl = new TplBlock();
$tpl->add_sub_block($subTpl);
namespace TplBlockTest;
use PHPUnit\Framework\TestCase;
use TplBlock\TplBlock;
/**
* The TplBlockTest class.
*
* @category Template
* @package TplBlock
* @author gnieark <gnieark@tinad.fr>
* @license GNU General Public License V3
* @link https://github.com/gnieark/tplBlock/
*/
class TplBlockTest extends TestCase
{
/**
* A template cannot accept a sub template with no name.
*
* @return void
*
* @expectedException UnexpectedValueException
*/
public function testSendEmptyNameOnSubFunction()
{
$template = new TplBlock();
$subTemplate = new TplBlock();
$template->addSubBlock($subTemplate);
}
public function testsimpleVar(){
$tpl = new TplBlock();
$tpl->add_vars(array(
/**
* Verify that variable replacement takes place.
*
* @return void
*/
public function testSimpleVar()
{
$template = new TplBlock();
$variables = [
"name" => "Gnieark",
"title" => "Monsieur",
"firstname" => "Grouik"
)
);
$this->assertEquals("Hello Gnieark", $tpl->apply_tpl_str("Hello {{name}}"));
"firstname" => "Grouik",
];
$actual = $template
->addVars($variables)
->applyTplStr("Hello {{name}}");
$this->assertEquals("Hello Gnieark", $actual);
}
//test from a file
public function testParseFromFile(){
file_put_contents("temp.txt","Hello {{name}}");
$tpl = new TplBlock();
$tpl->add_vars(array(
/**
* Test from a file.
*
* @return void
*/
public function testParseFromFile()
{
file_put_contents("temp.txt", "Hello {{name}}");
$template = new TplBlock();
$variables = [
"name" => "Gnieark",
"title" => "Monsieur",
"firstname" => "Grouik"
)
);
$this->assertEquals("Hello Gnieark", $tpl->apply_tpl_file("temp.txt"));
"firstname" => "Grouik",
];
$actual = $template
->addVars($variables)
->applyTplFile("temp.txt");
$this->assertEquals("Hello Gnieark", $actual);
unlink("temp.txt");
}
//test blocs
public function testBlocs(){
$str = "
/**
* Test blocs.
*
* @return void
*/
public function testBlocs()
{
$model = "
Bhah blah wpooie456
<!-- BEGIN bloc -->
have to be shown
@ -48,29 +105,38 @@ class TplBlockTest extends TestCase{
WONT to be shown
<!-- END blocTwo -->
";
$tpl = new TplBlock();
$tpl2 = new TplBlock("bloc");
$tpl->add_sub_block($tpl2);
$str = $tpl->apply_tpl_str($str);
$this->assertContains('have',$str);
$this->assertFalse(strpos("WONT",$str));
$template = new TplBlock();
$actual = $template
->addSubBlock(new TplBlock("bloc"))
->applyTplStr($model);
$this->assertContains("have", $actual);
$this->assertFalse(strpos("WONT", $actual));
}
//test if error on blocks names WTF
/**
* @expectedException InvalidTemplateException
* Test if error on blocks names WTF.
*
* @return void
*
* @expectedException UnexpectedValueException
*/
public function testIfErrorOnForbiddenName(){
$tpl = new TplBlock("kjsd54 65");
public function testIfErrorOnForbiddenName()
{
new TplBlock("kjsd54 65");
}
//test if error on blocks names WTF
/**
* @expectedException InvalidTemplateException
* Test if error on blocks names WTF.
*
* @return void
*
* @expectedException UnexpectedValueException
*/
public function testIfErrorOnForbiddenNameAgain(){
$tpl = new TplBlock("kjsd54.5");
public function testIfErrorOnForbiddenNameAgain()
{
new TplBlock("kjsd54.5");
}
}