Skip to content

Commit

Permalink
v2.1.0 - added {% while %} and is string. Removed json_decode
Browse files Browse the repository at this point in the history
  • Loading branch information
marionnewlevant committed Jun 9, 2019
1 parent 8644b70 commit d201d17
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 72 deletions.
30 changes: 21 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,46 @@
# Twig Perversion Changelog

## 2.1.0 - 2019.06.09
### Added
- Added `{% while %}` loop.
- Added `is string` test

### Removed
- Removed `json_decode` filter entirely

### Changed
- Updated to require craftcms/cms `3.1.29` (Where `loop.parent.loop` started to work)

## 2.0.7 - 2019.02.02
## Removed
* Removed json_decode filter for craft version later than 3.1.6
### Removed
- Removed json_decode filter for craft version later than 3.1.6

## 2.0.6 - 2018.09.14
### Added
* Added `<=>` operator. ([spaceship operator](http://php.net/manual/en/migration70.new-features.php#migration70.new-features.spaceship-op))
- Added `<=>` operator. ([spaceship operator](http://php.net/manual/en/migration70.new-features.php#migration70.new-features.spaceship-op))

## 2.0.5 - 2018.08.24
### Added
* Added `===`, and `!==` operators.
- Added `===`, and `!==` operators.

## 2.0.4 - 2018.07.16
### Added
* Added `string`, `float`, `int`, and `bool` typecast filters.
- Added `string`, `float`, `int`, and `bool` typecast filters.

## 2.0.3 - 2018.02.02
### Removed
* Pull support for `get_class` quick before anyone uses it (use Craft's native `className` instead)
- Pull support for `get_class` quick before anyone uses it (use Craft's native `className` instead)

## 2.0.2 - 2018.01.31
### Added
* Added `get_class` filter
- Added `get_class` filter

## 2.0.1 - 2017.12.13
### Added
* Added `array_splice` filter
- Added `array_splice` filter

### Changed
* Updated to require craftcms/cms `3.0.0-RC1`
- Updated to require craftcms/cms `3.0.0-RC1`

## 2.0.0 - 2017.02.02
### Added
Expand Down
125 changes: 79 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

Making twig do things it really shouldn&#39;t. Twig is not intended to be a general purpose programming language, and there are some things that really don't belong in the language. This plugin adds a few of those things anyway.

- `{% break %}`, `{% continue %}`, and `{% return %}` tags
- `{% while %}`, `{% break %}`, `{% continue %}`, and `{% return %}` tags
- `===`, `!==`, and `<=>` operators
- `is numeric` test
- `array_splice` filter
- `string` filter
- `float` filter
- `int` filter
- `bool` filter
- `json_decode` filter (prior to Craft 3.1.6)
- `is numeric` and `is string` tests
- `array_splice`, `string`, `float`, `int`, and `bool` filters

## Requirements

This plugin requires Craft CMS 3.1.29 or later.

## Installation

Expand All @@ -19,44 +18,71 @@ Making twig do things it really shouldn&#39;t. Twig is not intended to be a gene

or

1. Install via the Plugin Store
1. Install via the [Plugin Store](https://plugins.craftcms.com/twig-perversion)

## Using Twig Perversion

### Tags

`{% break %}` to exit a for loop:

{% for straw in haystack %}
{% if straw == needle %}
{% break %}
{% endif %}
{% endfor %}

`{% continue %}` to continue to next iteration:

{% for straw in haystack %}
{% if not isInteresting(straw) %}
{% continue %}
{% endif %}
{# do whatever... #}
{% endfor %}

`{% return value %}` to return a value from a macro:

{% macro foo() %}
{# ... calculate someValue ... #}
{% return someValue %}
{% endmacro %}

`{% return %}` to return the empty string from a macro:

{% macro foo() %}
{# ... do stuff %}
{% return %}
{% endmacro %}

A macro with a `{% return %}` tag will return whatever the return value is (which can be a complex expression). Any other output generated by the macro will be discarded.
- `{% while %}` loop:

```Twig
{% set x = 0 %}
{% while x < 5 %}
{# do whatever... #}
{% set x = x + 1 %}
{% endwhile %}
```

Note that twig's [loop](https://twig.symfony.com/doc/2.x/tags/for.html#the-loop-variable) variables (except for `revindex`, `revindex0`, `last`, and `length`) are available inside of a while loop, as are the `{% break %}` and `{% continue %}` tags.

- `{% break %}` to exit a for loop or while loop:

```Twig
{% for straw in haystack %}
{% if straw == needle %}
{% break %}
{% endif %}
{% endfor %}
{% while true %}
{# do whatever... #}
{% if someCondition %}
{% break %}
{% endif %}
{% endwhile %}
```

- `{% continue %}` to continue to next iteration:

```Twig
{% for straw in haystack %}
{% if not isInteresting(straw) %}
{% continue %}
{% endif %}
{# do whatever... #}
{% endfor %}
```

- `{% return value %}` to return a value from a macro:

```Twig
{% macro foo() %}
{# ... calculate someValue ... #}
{% return someValue %}
{% endmacro %}
```

- `{% return %}` to return the empty string from a macro:

```Twig
{% macro foo() %}
{# ... do stuff %}
{% return %}
{% endmacro %}
```

A macro with a `{% return %}` tag will return whatever the return value is (which can be a complex expression). Any other output generated by the macro will be discarded.

### Operators
- **===** and **!==**
Expand All @@ -72,7 +98,6 @@ A macro with a `{% return %}` tag will return whatever the return value is (whic

Test whether given value is numeric (behaviour like PHP 7 `is_numeric`). (Note that as of PHP 7, hexadecimal strings are not considered numeric)

#### Examples

```Twig
Expand All @@ -84,9 +109,21 @@ A macro with a `{% return %}` tag will return whatever the return value is (whic
{{ '0x539' is numeric ? 'Yes' : 'No'}}
{# No #}
```

- **String**

Test whether given value is a string.

```Twig
{{ '12' is string ? 'Yes' : 'No' }}
{# Yes #}
{{ 12 is string ? 'Yes' : 'No' }}
{# No #}
```


### Filters
- **array_splice**

Expand All @@ -108,8 +145,4 @@ A macro with a `{% return %}` tag will return whatever the return value is (whic

Typecast variable as a boolean.

- **json_decode** (Craft versions before 3.1.6 only)

Decode json string, returning php associative arrays. Uses the PHP [json_decode](http://php.net/manual/en/function.json-decode.php) function

Brought to you by [Marion Newlevant](http://marion.newlevant.com)
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "marionnewlevant/twig-perversion",
"description": "Making twig do things it really shouldn't",
"version": "2.0.7",
"version": "2.1.0",
"type": "craft-plugin",
"keywords": [
"craft", "craftcms", "plugin", "twigperversion"
Expand All @@ -14,7 +14,7 @@
}
],
"require": {
"craftcms/cms": "^3.0.0-RC1"
"craftcms/cms": "^3.1.29"
},
"autoload": {
"psr-4": {
Expand Down
10 changes: 10 additions & 0 deletions src/twigextensions/String_Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php
namespace marionnewlevant\twigperversion\twigextensions;

class String_Test extends \Twig_Node_Expression_Test
{
public function compile(\Twig_Compiler $compiler)
{
$compiler->raw('is_string(')->subcompile($this->getNode('node'))->raw(')');
}
}
19 changes: 4 additions & 15 deletions src/twigextensions/TwigPerversionTwigExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@
/**
* Twig Perversion plugin for Craft CMS 3.x
*
* Making twig do things it really shouldn&#39;t
* Making twig do things it really shouldn't
*
* @link http://marion.newlevant.com
* @copyright Copyright (c) 2017 Marion Newlevant
*/

namespace marionnewlevant\twigperversion\twigextensions;

use marionnewlevant\twigperversion\twigextensions\Break_TokenParser;
use marionnewlevant\twigperversion\twigextensions\Continue_TokenParser;
use marionnewlevant\twigperversion\twigextensions\Return_TokenParser;

/**
*
* @author Marion Newlevant
Expand Down Expand Up @@ -41,19 +37,21 @@ public function getTokenParsers()
new Break_TokenParser(),
new Continue_TokenParser(),
new Return_TokenParser(),
new While_TokenParser(),
];
}

public function getTests()
{
return [
new \Twig_Test('numeric', null, ['node_class' => '\marionnewlevant\twigperversion\twigextensions\Numeric_Test']),
new \Twig_Test('string', null, ['node_class' => '\marionnewlevant\twigperversion\twigextensions\String_Test']),
];
}

public function getFilters()
{
$filters = [
return [
new \Twig_Filter('array_splice', function(array $input, int $offset, int $length = null, $replacement = null) {
if (is_null($length))
{
Expand All @@ -72,15 +70,6 @@ public function getFilters()
new \Twig_SimpleFilter('int', [$this, 'int']),
new \Twig_SimpleFilter('bool', [$this, 'bool']),
];

// in Craft 3.1.6, craft added json_decode filter
if (version_compare(\Craft::$app->getInfo()->version, '3.1.6', '<'))
{
$filters[] = new \Twig_Filter('json_decode', function($str) {
return json_decode($str, true); // return assoc arrays (more twig-like)
});
}
return $filters;
}

public function getOperators()
Expand Down
80 changes: 80 additions & 0 deletions src/twigextensions/While_Node.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

/**
* Represents a while node.
*
* @package TwigPerversion
* @author Marion Newlevant
* @copyright Copyright (c) 2019, Marion Newlevant
* @license MIT
* @link https://github.com/marionnewlevant/craft-twig_perversion
*
*/
namespace marionnewlevant\twigperversion\twigextensions;

use Twig\Node\ForLoopNode;
use Twig\Node\Node;
use Twig\Compiler;

class While_Node extends Node
{
private $loop;

public function __construct(Node $condition, Node $body, int $lineno, string $tag = null)
{
$body = new Node([$body, $this->loop = new ForLoopNode($lineno, $tag)]);
$nodes = [
'body' => $body,
'condition' => $condition
];
parent::__construct($nodes, ['with_loop' => true], $lineno, $tag);
}

/**
* Compiles the node to PHP.
*
* @param Twig_Compiler $compiler
*
* @return void
*
* @see \Twig_Node::compile
*/
public function compile(Compiler $compiler)
{
$compiler
->addDebugInfo($this)
->write("\$context['_parent'] = \$context;\n");

if ($this->getAttribute('with_loop')) {
$compiler
->write("\$context['loop'] = [\n")
->write(" 'parent' => \$context['_parent'],\n")
->write(" 'index0' => 0,\n")
->write(" 'index' => 1,\n")
->write(" 'first' => true,\n")
->write("];\n")
;
}

$this->loop->setAttribute('with_loop', $this->getAttribute('with_loop'));

$compiler
->write('while (')
->subcompile($this->getNode('condition'))
->raw(") {\n")
->indent()
// this is a ForLoopNode, so it updates the loop stuff
->subcompile($this->getNode('body'))
->outdent()
->write("}\n")
;

$compiler->write("\$_parent = \$context['_parent'];\n");

// remove some "private" loop variables (needed for nested loops)
$compiler->write('unset($context[\'_parent\'], $context[\'loop\']);'."\n");

// keep the values set in the inner context for variables defined in the outer context
$compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n");
}
}
Loading

1 comment on commit d201d17

@CatAnonymous
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix: 'break' not in the 'loop' or 'switch' context

Please sign in to comment.