Completed
Push — master ( 202a99...a52b51 )
by Josh
03:58
created

CallbackGenerator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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
		// Rebuild the local vars map to include global vars and computed values
68 3
		$localVars  = array_combine(array_keys($localVars), array_keys($localVars));
69
		$localVars += [
70 3
			'innerText'      => '(tag.getEndTag() ? text.substr(tag.getPos() + tag.getLen(), tag.getEndTag().getPos() - tag.getPos() - tag.getLen()) : "")',
71
			'logger'         => 'logger',
72
			'openTags'       => 'openTags',
73
			'outerText'      => 'text.substr(tag.getPos(), (tag.getEndTag() ? tag.getEndTag().getPos() + tag.getEndTag().getLen() - tag.getPos() : tag.getLen()))',
74
			'registeredVars' => 'registeredVars',
75
			'tagText'        => 'text.substr(tag.getPos(), tag.getLen())',
76
			'text'           => 'text'
77
		];
78
79 3
		$args = [];
80 3
		foreach ($params as $k => $v)
81
		{
82 3
			if (isset($v))
83
			{
84
				// Param by value
85 1
				$args[] = $this->encoder->encode($v);
86
			}
87 2
			elseif (isset($localVars[$k]))
88
			{
89
				// Param by name that matches a local var
90 1
				$args[] = $k;
91
			}
92
			else
93
			{
94 1
				$args[] = 'registeredVars[' . json_encode($k) . ']';
95
			}
96
		}
97
98 3
		return implode(',', $args);
99
	}
100
101
	/**
102
	* Generate a function from a callback config
103
	*
104
	* @param  array $config Callback config
105
	* @param  array $params Param names as keys, param types as values
106
	* @return Code
107
	*/
108 5
	protected function generateFunction(array $config, array $params)
109
	{
110
		// returnFalse() and returnTrue() can be used as-is
111 5
		if ($config['js'] == 'returnFalse' || $config['js'] == 'returnTrue')
112
		{
113 2
			return new Code((string) $config['js']);
114
		}
115
116
		// Add an empty list of params if none is set
117 3
		$config += ['params' => []];
118
119 3
		$src  = $this->getHeader($params);
120 3
		$src .= 'function(' . implode(',', array_keys($params)) . '){';
121 3
		$src .= 'return ' . $this->parenthesizeCallback($config['js']);
122 3
		$src .= '(' . $this->buildCallbackArguments($config['params'], $params) . ');}';
123
124 3
		return new Code($src);
125
	}
126
127
	/**
128
	* Generate a function header for given signature
129
	*
130
	* @param  array  $params Param names as keys, param types as values
131
	* @return string
132
	*/
133 3
	protected function getHeader(array $params)
134
	{
135
		// Prepare the function's header
136 3
		$header = "/**\n";
137 3
		foreach ($params as $paramName => $paramType)
138
		{
139 3
			$header .= '* @param {' . $paramType . '} ' . $paramName . "\n";
140
		}
141 3
		$header .= "*/\n";
142
143 3
		return $header;
144
	}
145
146
	/**
147
	* Replace callbacks in given config array
148
	*
149
	* @param  array    $array  Original config
150
	* @param  string[] $path   Path to callbacks
151
	* @param  array    $params Default params
152
	* @return array            Modified config
153
	*/
154 6
	protected function mapArray(array $array, array $path, array $params)
155
	{
156 6
		$key  = array_shift($path);
157 6
		$keys = ($key === '*') ? array_keys($array) : [$key];
158 6
		foreach ($keys as $key)
159
		{
160 6
			if (!isset($array[$key]))
161
			{
162 6
				continue;
163
			}
164 5
			$array[$key] = (empty($path)) ? $this->generateFunction($array[$key], $params) : $this->mapArray($array[$key], $path, $params);
165
		}
166
167 6
		return $array;
168
	}
169
170
	/**
171
	* Add parentheses to a function literal, if necessary
172
	*
173
	* Will return single vars as-is, and will put anything else between parentheses
174
	*
175
	* @param  string $callback Original callback
176
	* @return string           Modified callback
177
	*/
178 3
	protected function parenthesizeCallback($callback)
179
	{
180 3
		return (preg_match('(^[.\\w]+$)D', $callback)) ? $callback : '(' . $callback  . ')';
181
	}
182
}