Passed
Push — master ( fd984e...abf0be )
by Josh
04:08
created

Ruleset::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
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\Collections;
9
10
use ArrayAccess;
11
use BadMethodCallException;
12
use InvalidArgumentException;
13
use RuntimeException;
14
use s9e\TextFormatter\Configurator\ConfigProvider;
15
use s9e\TextFormatter\Configurator\JavaScript\Dictionary;
16
use s9e\TextFormatter\Configurator\Validators\TagName;
17
use s9e\TextFormatter\Parser;
18
19
/**
20
* @method void allowChild(string $tagName)
21
* @method void allowDescendant(string $tagName)
22
* @method void autoClose(bool $bool = true)
23
* @method void autoReopen(bool $bool = true)
24
* @method void breakParagraph(bool $bool = true)
25
* @method void closeAncestor(string $tagName)
26
* @method void closeParent(string $tagName)
27
* @method void createChild(string $tagName)
28
* @method void createParagraphs(bool $bool = true)
29
* @method void denyChild(string $tagName)
30
* @method void denyDescendant(string $tagName)
31
* @method void disableAutoLineBreaks(bool $bool = true)
32
* @method void enableAutoLineBreaks(bool $bool = true)
33
* @method void fosterParent(string $tagName)
34
* @method void ignoreSurroundingWhitespace(bool $bool = true)
35
* @method void ignoreTags(bool $bool = true)
36
* @method void ignoreText(bool $bool = true)
37
* @method void isTransparent(bool $bool = true)
38
* @method void preventLineBreaks(bool $bool = true)
39
* @method void requireParent(string $tagName)
40
* @method void requireAncestor(string $tagName)
41
* @method void suspendAutoLineBreaks(bool $bool = true)
42
* @method void trimFirstLine(bool $bool = true)
43
* @see /docs/Rules.md
44
*/
45
class Ruleset extends Collection implements ArrayAccess, ConfigProvider
46
{
47
	/**
48
	* @var array Supported rules and the method used to add them
49
	*/
50
	protected $rules = [
51
		'allowChild'                  => 'addTargetedRule',
52
		'allowDescendant'             => 'addTargetedRule',
53
		'autoClose'                   => 'addBooleanRule',
54
		'autoReopen'                  => 'addBooleanRule',
55
		'breakParagraph'              => 'addBooleanRule',
56
		'closeAncestor'               => 'addTargetedRule',
57
		'closeParent'                 => 'addTargetedRule',
58
		'createChild'                 => 'addTargetedRule',
59
		'createParagraphs'            => 'addBooleanRule',
60
		'denyChild'                   => 'addTargetedRule',
61
		'denyDescendant'              => 'addTargetedRule',
62
		'disableAutoLineBreaks'       => 'addBooleanRule',
63
		'enableAutoLineBreaks'        => 'addBooleanRule',
64
		'fosterParent'                => 'addTargetedRule',
65
		'ignoreSurroundingWhitespace' => 'addBooleanRule',
66
		'ignoreTags'                  => 'addBooleanRule',
67
		'ignoreText'                  => 'addBooleanRule',
68
		'isTransparent'               => 'addBooleanRule',
69
		'preventLineBreaks'           => 'addBooleanRule',
70
		'requireParent'               => 'addTargetedRule',
71
		'requireAncestor'             => 'addTargetedRule',
72
		'suspendAutoLineBreaks'       => 'addBooleanRule',
73
		'trimFirstLine'               => 'addBooleanRule'
74
	];
75
76
	/**
77
	* Add a rule to this set
78
	*
79
	* @param  string $methodName Rule name
80
	* @param  array  $args       Arguments used to add given rule
81
	* @return self
82
	*/
83 76
	public function __call($methodName, array $args)
84
	{
85 76
		if (!isset($this->rules[$methodName]))
86
		{
87 1
			throw new BadMethodCallException("Undefined method '" . $methodName . "'");
88
		}
89
90 75
		array_unshift($args, $methodName);
91 75
		call_user_func_array([$this, $this->rules[$methodName]], $args);
92
93 53
		return $this;
94
	}
95
96
	//==========================================================================
97
	// ArrayAccess methods
98
	//==========================================================================
99
100
	/**
101
	* Test whether a rule category exists
102
	*
103
	* @param  string $k Rule name, e.g. "allowChild" or "isTransparent"
104
	*/
105 2
	public function offsetExists($k)
106
	{
107 2
		return isset($this->items[$k]);
108
	}
109
110
	/**
111
	* Return the content of a rule category
112
	*
113
	* @param  string $k Rule name, e.g. "allowChild" or "isTransparent"
114
	* @return mixed
115
	*/
116 1
	public function offsetGet($k)
117
	{
118 1
		return $this->items[$k];
119
	}
120
121
	/**
122
	* Not supported
123
	*/
124 1
	public function offsetSet($k, $v)
125
	{
126 1
		throw new RuntimeException('Not supported');
127
	}
128
129
	/**
130
	* Clear a subset of the rules
131
	*
132
	* @see clear()
133
	*
134
	* @param  string $k Rule name, e.g. "allowChild" or "isTransparent"
135
	*/
136 1
	public function offsetUnset($k)
137
	{
138 1
		return $this->remove($k);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->remove($k) targeting s9e\TextFormatter\Config...tions\Ruleset::remove() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
139
	}
140
141
	//==========================================================================
142
	// Generic methods
143
	//==========================================================================
144
145
	/**
146
	* {@inheritdoc}
147
	*/
148 6
	public function asConfig()
149
	{
150 6
		$config = $this->items;
151
152
		// Remove rules that are not needed at parsing time. All of those are resolved when building
153
		// the allowed bitfields
154 6
		unset($config['allowChild']);
155 6
		unset($config['allowDescendant']);
156 6
		unset($config['denyChild']);
157 6
		unset($config['denyDescendant']);
158 6
		unset($config['requireParent']);
159
160
		// Pack boolean rules into a bitfield
161
		$bitValues = [
162 6
			'autoClose'                   => Parser::RULE_AUTO_CLOSE,
163
			'autoReopen'                  => Parser::RULE_AUTO_REOPEN,
164
			'breakParagraph'              => Parser::RULE_BREAK_PARAGRAPH,
165
			'createParagraphs'            => Parser::RULE_CREATE_PARAGRAPHS,
166
			'disableAutoLineBreaks'       => Parser::RULE_DISABLE_AUTO_BR,
167
			'enableAutoLineBreaks'        => Parser::RULE_ENABLE_AUTO_BR,
168
			'ignoreSurroundingWhitespace' => Parser::RULE_IGNORE_WHITESPACE,
169
			'ignoreTags'                  => Parser::RULE_IGNORE_TAGS,
170
			'ignoreText'                  => Parser::RULE_IGNORE_TEXT,
171
			'isTransparent'               => Parser::RULE_IS_TRANSPARENT,
172
			'preventLineBreaks'           => Parser::RULE_PREVENT_BR,
173
			'suspendAutoLineBreaks'       => Parser::RULE_SUSPEND_AUTO_BR,
174
			'trimFirstLine'               => Parser::RULE_TRIM_FIRST_LINE
175
		];
176
177 6
		$bitfield = 0;
178 6
		foreach ($bitValues as $ruleName => $bitValue)
179
		{
180 6
			if (!empty($config[$ruleName]))
181
			{
182 3
				$bitfield |= $bitValue;
183
			}
184
185 6
			unset($config[$ruleName]);
186
		}
187
188
		// In order to speed up lookups, we use the tag names as keys
189 6
		foreach (['closeAncestor', 'closeParent', 'fosterParent'] as $ruleName)
190
		{
191 6
			if (isset($config[$ruleName]))
192
			{
193 2
				$targets = array_fill_keys($config[$ruleName], 1);
194 2
				$config[$ruleName] = new Dictionary($targets);
195
			}
196
		}
197
198
		// Add the bitfield to the config
199 6
		$config['flags'] = $bitfield;
200
201 6
		return $config;
202
	}
203
204
	/**
205
	* Merge a set of rules into this collection
206
	*
207
	* @param array|Ruleset $rules     2D array of rule definitions, or instance of Ruleset
208
	* @param bool          $overwrite Whether to overwrite scalar rules (e.g. boolean rules)
209
	*/
210 7
	public function merge($rules, $overwrite = true)
211
	{
212 7
		if (!is_array($rules)
213 7
		 && !($rules instanceof self))
0 ignored issues
show
introduced by
$rules is always a sub-type of self.
Loading history...
214
		{
215 1
			throw new InvalidArgumentException('merge() expects an array or an instance of Ruleset');
216
		}
217
218 6
		foreach ($rules as $action => $value)
219
		{
220 6
			if (is_array($value))
221
			{
222 3
				foreach ($value as $tagName)
223
				{
224 3
					$this->$action($tagName);
225
				}
226
			}
227 4
			elseif ($overwrite || !isset($this->items[$action]))
228
			{
229 3
				$this->$action($value);
230
			}
231
		}
232
	}
233
234
	/**
235
	* Remove a specific rule, or all the rules of a given type
236
	*
237
	* @param  string $type    Type of rules to clear
238
	* @param  string $tagName Name of the target tag, or none to remove all rules of given type
239
	* @return void
240
	*/
241 8
	public function remove($type, $tagName = null)
242
	{
243 8
		if (preg_match('(^default(?:Child|Descendant)Rule)', $type))
244
		{
245 2
			throw new InvalidArgumentException('Cannot remove ' . $type);
246
		}
247
248 6
		if (isset($tagName))
249
		{
250 4
			$tagName = TagName::normalize($tagName);
251
252 4
			if (isset($this->items[$type]))
253
			{
254
				// Compute the difference between current list and our one tag name
255 4
				$this->items[$type] = array_diff(
256 4
					$this->items[$type],
257 4
					[$tagName]
258
				);
259
260 4
				if (empty($this->items[$type]))
261
				{
262
					// If the list is now empty, keep it neat and unset it
263 1
					unset($this->items[$type]);
264
				}
265
				else
266
				{
267
					// If the list still have names, keep it neat and rearrange keys
268 4
					$this->items[$type] = array_values($this->items[$type]);
269
				}
270
			}
271
		}
272
		else
273
		{
274 2
			unset($this->items[$type]);
275
		}
276
	}
277
278
	//==========================================================================
279
	// Rules
280
	//==========================================================================
281
282
	/**
283
	* Add a boolean rule
284
	*
285
	* @param  string $ruleName Name of the rule
286
	* @param  bool   $bool     Whether to enable or disable the rule
287
	* @return self
288
	*/
289 33
	protected function addBooleanRule($ruleName, $bool = true)
290
	{
291 33
		if (!is_bool($bool))
0 ignored issues
show
introduced by
The condition is_bool($bool) is always true.
Loading history...
292
		{
293 13
			throw new InvalidArgumentException($ruleName . '() expects a boolean');
294
		}
295
296 20
		$this->items[$ruleName] = $bool;
297
	}
298
299
	/**
300
	* Add a targeted rule
301
	*
302
	* @param  string $ruleName Name of the rule
303
	* @param  string $tagName  Name of the target tag
304
	* @return self
305
	*/
306 44
	protected function addTargetedRule($ruleName, $tagName)
307
	{
308 44
		$this->items[$ruleName][] = TagName::normalize($tagName);
309
	}
310
}