Completed
Push — master ( 2e52fe...202a99 )
by Josh
04:00
created

CallbackGenerator::parenthesizeCallback()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2019 The s9e Authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\TextFormatter\Configurator\JavaScript;
9
10
class CallbackGenerator
11
{
12
	/**
13
	* @var array Path to callbacks in keys, callback signature in values
14
	*/
15
	public $callbacks = [
16
		'tags.*.attributes.*.filterChain.*' => [
17
			'attrValue' => '*',
18
			'attrName'  => '!string'
19
		],
20
		'tags.*.filterChain.*' => [
21
			'tag'       => '!Tag',
22
			'tagConfig' => '!Object'
23
		]
24
	];
25
26
	/**
27
	* @var Encoder
28
	*/
29
	protected $encoder;
30
31
	/**
32
	* Constructor
33
	*/
34 6
	public function __construct()
35
	{
36 6
		$this->encoder = new Encoder;
37
	}
38
39
	/**
40
	* Replace all callbacks in given config
41
	*
42
	* @param  array $config Original config
43
	* @return array         Modified config
44
	*/
45 6
	public function replaceCallbacks(array $config)
46
	{
47 6
		foreach ($this->callbacks as $path => $params)
48
		{
49 6
			$config = $this->mapArray($config, explode('.', $path), $params);
50
		}
51
52 6
		return $config;
53
	}
54
55
	/**
56
	* Build the list of arguments used in a callback invocation
57
	*
58
	* @param  array  $params    Callback parameters
59
	* @param  array  $localVars Known vars from the calling scope
60
	* @return string            JavaScript code
61
	*/
62 3
	protected function buildCallbackArguments(array $params, array $localVars)
63
	{
64
		// Remove 'parser' as a parameter, since there's no such thing in JavaScript
65 3
		unset($params['parser']);
66
67
		// Add global vars to the list of vars in scope
68 3
		$localVars += ['logger' => 1, 'openTags' => 1, 'registeredVars' => 1, 'text' => 1];
69
70
		// Add computed values
71
		$dynamicVars = [
72 3
			'innerText' => '(tag.getEndTag() ? text.substr(tag.getPos() + tag.getLen(), tag.getEndTag().getPos() - tag.getPos() - tag.getLen()) : "")',
73
			'outerText' => 'text.substr(tag.getPos(), (tag.getEndTag() ? tag.getEndTag().getPos() + tag.getEndTag().getLen() - tag.getPos() : tag.getLen()))',
74
			'tagText'   => 'text.substr(tag.getPos(), tag.getLen())'
75
		];
76
77 3
		$args = [];
78 3
		foreach ($params as $k => $v)
79
		{
80 3
			if (isset($v))
81
			{
82
				// Param by value
83 1
				$args[] = $this->encoder->encode($v);
84
			}
85 2
			elseif (isset($localVars[$k]))
86
			{
87
				// Param by name that matches a local var
88 1
				$args[] = $k;
89
			}
90 1
			elseif (isset($dynamicVars[$k]))
91
			{
92
				$args[] = $dynamicVars[$k];
93
			}
94
			else
95
			{
96 1
				$args[] = 'registeredVars[' . json_encode($k) . ']';
97
			}
98
		}
99
100 3
		return implode(',', $args);
101
	}
102
103
	/**
104
	* Generate a function from a callback config
105
	*
106
	* @param  array $config Callback config
107
	* @param  array $params Param names as keys, param types as values
108
	* @return Code
109
	*/
110 5
	protected function generateFunction(array $config, array $params)
111
	{
112
		// returnFalse() and returnTrue() can be used as-is
113 5
		if ($config['js'] == 'returnFalse' || $config['js'] == 'returnTrue')
114
		{
115 2
			return new Code((string) $config['js']);
116
		}
117
118
		// Add an empty list of params if none is set
119 3
		$config += ['params' => []];
120
121 3
		$src  = $this->getHeader($params);
122 3
		$src .= 'function(' . implode(',', array_keys($params)) . '){';
123 3
		$src .= 'return ' . $this->parenthesizeCallback($config['js']);
124 3
		$src .= '(' . $this->buildCallbackArguments($config['params'], $params) . ');}';
125
126 3
		return new Code($src);
127
	}
128
129
	/**
130
	* Generate a function header for given signature
131
	*
132
	* @param  array  $params Param names as keys, param types as values
133
	* @return string
134
	*/
135 3
	protected function getHeader(array $params)
136
	{
137
		// Prepare the function's header
138 3
		$header = "/**\n";
139 3
		foreach ($params as $paramName => $paramType)
140
		{
141 3
			$header .= '* @param {' . $paramType . '} ' . $paramName . "\n";
142
		}
143 3
		$header .= "*/\n";
144
145 3
		return $header;
146
	}
147
148
	/**
149
	* Replace callbacks in given config array
150
	*
151
	* @param  array    $array  Original config
152
	* @param  string[] $path   Path to callbacks
153
	* @param  array    $params Default params
154
	* @return array            Modified config
155
	*/
156 6
	protected function mapArray(array $array, array $path, array $params)
157
	{
158 6
		$key  = array_shift($path);
159 6
		$keys = ($key === '*') ? array_keys($array) : [$key];
160 6
		foreach ($keys as $key)
161
		{
162 6
			if (!isset($array[$key]))
163
			{
164 6
				continue;
165
			}
166 5
			$array[$key] = (empty($path)) ? $this->generateFunction($array[$key], $params) : $this->mapArray($array[$key], $path, $params);
167
		}
168
169 6
		return $array;
170
	}
171
172
	/**
173
	* Add parentheses to a function literal, if necessary
174
	*
175
	* Will return single vars as-is, and will put anything else between parentheses
176
	*
177
	* @param  string $callback Original callback
178
	* @return string           Modified callback
179
	*/
180 3
	protected function parenthesizeCallback($callback)
181
	{
182 3
		return (preg_match('(^[.\\w]+$)D', $callback)) ? $callback : '(' . $callback  . ')';
183
	}
184
}