Completed
Branch Scrutinizer (3da711)
by Josh
03:32
created

Ruleset::offsetSet()   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 2
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
	* Constructor
78
	*/
79 80
	public function __construct()
80
	{
81 80
		$this->clear();
82
	}
83
84
	/**
85
	* Add a rule to this set
86
	*
87
	* @param  string $methodName Rule name
88
	* @param  array  $args       Arguments used to add given rule
89
	* @return self
90
	*/
91 76
	public function __call($methodName, array $args)
92
	{
93 76
		if (!isset($this->rules[$methodName]))
94
		{
95 1
			throw new BadMethodCallException("Undefined method '" . $methodName . "'");
96
		}
97
98 75
		array_unshift($args, $methodName);
99 75
		call_user_func_array([$this, $this->rules[$methodName]], $args);
100
101 53
		return $this;
102
	}
103
104
	//==========================================================================
105
	// ArrayAccess methods
106
	//==========================================================================
107
108
	/**
109
	* Test whether a rule category exists
110
	*
111
	* @param  string $k Rule name, e.g. "allowChild" or "isTransparent"
112
	*/
113 2
	public function offsetExists($k)
114
	{
115 2
		return isset($this->items[$k]);
116
	}
117
118
	/**
119
	* Return the content of a rule category
120
	*
121
	* @param  string $k Rule name, e.g. "allowChild" or "isTransparent"
122
	* @return mixed
123
	*/
124 1
	public function offsetGet($k)
125
	{
126 1
		return $this->items[$k];
127
	}
128
129
	/**
130
	* Not supported
131
	*/
132 1
	public function offsetSet($k, $v)
133
	{
134 1
		throw new RuntimeException('Not supported');
135
	}
136
137
	/**
138
	* Clear a subset of the rules
139
	*
140
	* @see clear()
141
	*
142
	* @param  string $k Rule name, e.g. "allowChild" or "isTransparent"
143
	*/
144 1
	public function offsetUnset($k)
145
	{
146 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...
147
	}
148
149
	//==========================================================================
150
	// Generic methods
151
	//==========================================================================
152
153
	/**
154
	* {@inheritdoc}
155
	*/
156 6
	public function asConfig()
157
	{
158 6
		$config = $this->items;
159
160
		// Remove rules that are not needed at parsing time. All of those are resolved when building
161
		// the allowed bitfields
162 6
		unset($config['allowChild']);
163 6
		unset($config['allowDescendant']);
164 6
		unset($config['denyChild']);
165 6
		unset($config['denyDescendant']);
166 6
		unset($config['requireParent']);
167
168
		// Pack boolean rules into a bitfield
169
		$bitValues = [
170 6
			'autoClose'                   => Parser::RULE_AUTO_CLOSE,
171
			'autoReopen'                  => Parser::RULE_AUTO_REOPEN,
172
			'breakParagraph'              => Parser::RULE_BREAK_PARAGRAPH,
173
			'createParagraphs'            => Parser::RULE_CREATE_PARAGRAPHS,
174
			'disableAutoLineBreaks'       => Parser::RULE_DISABLE_AUTO_BR,
175
			'enableAutoLineBreaks'        => Parser::RULE_ENABLE_AUTO_BR,
176
			'ignoreSurroundingWhitespace' => Parser::RULE_IGNORE_WHITESPACE,
177
			'ignoreTags'                  => Parser::RULE_IGNORE_TAGS,
178
			'ignoreText'                  => Parser::RULE_IGNORE_TEXT,
179
			'isTransparent'               => Parser::RULE_IS_TRANSPARENT,
180
			'preventLineBreaks'           => Parser::RULE_PREVENT_BR,
181
			'suspendAutoLineBreaks'       => Parser::RULE_SUSPEND_AUTO_BR,
182
			'trimFirstLine'               => Parser::RULE_TRIM_FIRST_LINE
183
		];
184
185 6
		$bitfield = 0;
186 6
		foreach ($bitValues as $ruleName => $bitValue)
187
		{
188 6
			if (!empty($config[$ruleName]))
189
			{
190 3
				$bitfield |= $bitValue;
191
			}
192
193 6
			unset($config[$ruleName]);
194
		}
195
196
		// In order to speed up lookups, we use the tag names as keys
197 6
		foreach (['closeAncestor', 'closeParent', 'fosterParent'] as $ruleName)
198
		{
199 6
			if (isset($config[$ruleName]))
200
			{
201 2
				$targets = array_fill_keys($config[$ruleName], 1);
202 2
				$config[$ruleName] = new Dictionary($targets);
203
			}
204
		}
205
206
		// Add the bitfield to the config
207 6
		$config['flags'] = $bitfield;
208
209 6
		return $config;
210
	}
211
212
	/**
213
	* Merge a set of rules into this collection
214
	*
215
	* @param array|Ruleset $rules     2D array of rule definitions, or instance of Ruleset
216
	* @param bool          $overwrite Whether to overwrite scalar rules (e.g. boolean rules)
217
	*/
218 7
	public function merge($rules, $overwrite = true)
219
	{
220 7
		if (!is_array($rules)
221 7
		 && !($rules instanceof self))
0 ignored issues
show
introduced by
$rules is always a sub-type of self.
Loading history...
222
		{
223 1
			throw new InvalidArgumentException('merge() expects an array or an instance of Ruleset');
224
		}
225
226 6
		foreach ($rules as $action => $value)
227
		{
228 6
			if (is_array($value))
229
			{
230 3
				foreach ($value as $tagName)
231
				{
232 3
					$this->$action($tagName);
233
				}
234
			}
235 4
			elseif ($overwrite || !isset($this->items[$action]))
236
			{
237 3
				$this->$action($value);
238
			}
239
		}
240
	}
241
242
	/**
243
	* Remove a specific rule, or all the rules of a given type
244
	*
245
	* @param  string $type    Type of rules to clear
246
	* @param  string $tagName Name of the target tag, or none to remove all rules of given type
247
	* @return void
248
	*/
249 8
	public function remove($type, $tagName = null)
250
	{
251 8
		if (preg_match('(^default(?:Child|Descendant)Rule)', $type))
252
		{
253 2
			throw new InvalidArgumentException('Cannot remove ' . $type);
254
		}
255
256 6
		if (isset($tagName))
257
		{
258 4
			$tagName = TagName::normalize($tagName);
259
260 4
			if (isset($this->items[$type]))
261
			{
262
				// Compute the difference between current list and our one tag name
263 4
				$this->items[$type] = array_diff(
264 4
					$this->items[$type],
265 4
					[$tagName]
266
				);
267
268 4
				if (empty($this->items[$type]))
269
				{
270
					// If the list is now empty, keep it neat and unset it
271 1
					unset($this->items[$type]);
272
				}
273
				else
274
				{
275
					// If the list still have names, keep it neat and rearrange keys
276 4
					$this->items[$type] = array_values($this->items[$type]);
277
				}
278
			}
279
		}
280
		else
281
		{
282 2
			unset($this->items[$type]);
283
		}
284
	}
285
286
	//==========================================================================
287
	// Rules
288
	//==========================================================================
289
290
	/**
291
	* Add a boolean rule
292
	*
293
	* @param  string $ruleName Name of the rule
294
	* @param  bool   $bool     Whether to enable or disable the rule
295
	* @return self
296
	*/
297 33
	protected function addBooleanRule($ruleName, $bool = true)
298
	{
299 33
		if (!is_bool($bool))
0 ignored issues
show
introduced by
The condition is_bool($bool) is always true.
Loading history...
300
		{
301 13
			throw new InvalidArgumentException($ruleName . '() expects a boolean');
302
		}
303
304 20
		$this->items[$ruleName] = $bool;
305
	}
306
307
	/**
308
	* Add a targeted rule
309
	*
310
	* @param  string $ruleName Name of the rule
311
	* @param  string $tagName  Name of the target tag
312
	* @return self
313
	*/
314 44
	protected function addTargetedRule($ruleName, $tagName)
315
	{
316 44
		$this->items[$ruleName][] = TagName::normalize($tagName);
317
	}
318
}