Completed
Push — master ( 2d242d...318514 )
by Josh
15:09
created

Ruleset::autoClose()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2017 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
* @see docs/Rules.md
21
*/
22
class Ruleset extends Collection implements ArrayAccess, ConfigProvider
23
{
24
	/**
25
	* @var array Supported rules and the method used to add them
26
	*/
27
	protected $rules = [
28
		'allowChild'                  => 'addTargetedRule',
29
		'allowDescendant'             => 'addTargetedRule',
30
		'autoClose'                   => 'addBooleanRule',
31
		'autoReopen'                  => 'addBooleanRule',
32
		'breakParagraph'              => 'addBooleanRule',
33
		'closeAncestor'               => 'addTargetedRule',
34
		'closeParent'                 => 'addTargetedRule',
35
		'createChild'                 => 'addTargetedRule',
36
		'createParagraphs'            => 'addBooleanRule',
37
		'denyChild'                   => 'addTargetedRule',
38
		'denyDescendant'              => 'addTargetedRule',
39
		'disableAutoLineBreaks'       => 'addBooleanRule',
40
		'enableAutoLineBreaks'        => 'addBooleanRule',
41
		'fosterParent'                => 'addTargetedRule',
42
		'ignoreSurroundingWhitespace' => 'addBooleanRule',
43
		'ignoreTags'                  => 'addBooleanRule',
44
		'ignoreText'                  => 'addBooleanRule',
45
		'isTransparent'               => 'addBooleanRule',
46
		'preventLineBreaks'           => 'addBooleanRule',
47
		'requireParent'               => 'addTargetedRule',
48
		'requireAncestor'             => 'addTargetedRule',
49
		'suspendAutoLineBreaks'       => 'addBooleanRule',
50
		'trimFirstLine'               => 'addBooleanRule'
51
	];
52
53
	/**
54
	* Constructor
55
	*/
56
	public function __construct()
57
	{
58
		$this->clear();
59
	}
60
61
	/**
62
	* Add a rule to this set
63
	*
64
	* @param  string $methodName Rule name
65
	* @param  array  $args       Arguments used to add given rule
66
	* @return self
67
	*/
68
	public function __call($methodName, array $args)
69
	{
70
		if (!isset($this->rules[$methodName]))
71
		{
72
			throw new BadMethodCallException("Undefined method '" . $methodName . "'");
73
		}
74
75
		array_unshift($args, $methodName);
76
		call_user_func_array([$this, $this->rules[$methodName]], $args);
77
78
		return $this;
79
	}
80
81
	//==========================================================================
82
	// ArrayAccess methods
83
	//==========================================================================
84
85
	/**
86
	* Test whether a rule category exists
87
	*
88
	* @param  string $k Rule name, e.g. "allowChild" or "isTransparent"
89
	*/
90
	public function offsetExists($k)
91
	{
92
		return isset($this->items[$k]);
93
	}
94
95
	/**
96
	* Return the content of a rule category
97
	*
98
	* @param  string $k Rule name, e.g. "allowChild" or "isTransparent"
99
	* @return mixed
100
	*/
101
	public function offsetGet($k)
102
	{
103
		return $this->items[$k];
104
	}
105
106
	/**
107
	* Not supported
108
	*/
109
	public function offsetSet($k, $v)
110
	{
111
		throw new RuntimeException('Not supported');
112
	}
113
114
	/**
115
	* Clear a subset of the rules
116
	*
117
	* @see clear()
118
	*
119
	* @param  string $k Rule name, e.g. "allowChild" or "isTransparent"
120
	*/
121
	public function offsetUnset($k)
122
	{
123
		return $this->remove($k);
124
	}
125
126
	//==========================================================================
127
	// Generic methods
128
	//==========================================================================
129
130
	/**
131
	* {@inheritdoc}
132
	*/
133
	public function asConfig()
134
	{
135
		$config = $this->items;
136
137
		// Remove rules that are not needed at parsing time. All of those are resolved when building
138
		// the allowed bitfields
139
		unset($config['allowChild']);
140
		unset($config['allowDescendant']);
141
		unset($config['denyChild']);
142
		unset($config['denyDescendant']);
143
		unset($config['requireParent']);
144
145
		// Pack boolean rules into a bitfield
146
		$bitValues = [
147
			'autoClose'                   => Parser::RULE_AUTO_CLOSE,
148
			'autoReopen'                  => Parser::RULE_AUTO_REOPEN,
149
			'breakParagraph'              => Parser::RULE_BREAK_PARAGRAPH,
150
			'createParagraphs'            => Parser::RULE_CREATE_PARAGRAPHS,
151
			'disableAutoLineBreaks'       => Parser::RULE_DISABLE_AUTO_BR,
152
			'enableAutoLineBreaks'        => Parser::RULE_ENABLE_AUTO_BR,
153
			'ignoreSurroundingWhitespace' => Parser::RULE_IGNORE_WHITESPACE,
154
			'ignoreTags'                  => Parser::RULE_IGNORE_TAGS,
155
			'ignoreText'                  => Parser::RULE_IGNORE_TEXT,
156
			'isTransparent'               => Parser::RULE_IS_TRANSPARENT,
157
			'preventLineBreaks'           => Parser::RULE_PREVENT_BR,
158
			'suspendAutoLineBreaks'       => Parser::RULE_SUSPEND_AUTO_BR,
159
			'trimFirstLine'               => Parser::RULE_TRIM_FIRST_LINE
160
		];
161
162
		$bitfield = 0;
163
		foreach ($bitValues as $ruleName => $bitValue)
164
		{
165
			if (!empty($config[$ruleName]))
166
			{
167
				$bitfield |= $bitValue;
168
			}
169
170
			unset($config[$ruleName]);
171
		}
172
173
		// In order to speed up lookups, we use the tag names as keys
174
		foreach (['closeAncestor', 'closeParent', 'fosterParent'] as $ruleName)
175
		{
176
			if (isset($config[$ruleName]))
177
			{
178
				$targets = array_fill_keys($config[$ruleName], 1);
179
				$config[$ruleName] = new Dictionary($targets);
180
			}
181
		}
182
183
		// Add the bitfield to the config
184
		$config['flags'] = $bitfield;
185
186
		return $config;
187
	}
188
189
	/**
190
	* Merge a set of rules into this collection
191
	*
192
	* @param array|Ruleset $rules     2D array of rule definitions, or instance of Ruleset
193
	* @param bool          $overwrite Whether to overwrite scalar rules (e.g. boolean rules)
194
	*/
195
	public function merge($rules, $overwrite = true)
196
	{
197
		if (!is_array($rules)
198
		 && !($rules instanceof self))
199
		{
200
			throw new InvalidArgumentException('merge() expects an array or an instance of Ruleset');
201
		}
202
203
		foreach ($rules as $action => $value)
204
		{
205
			if (is_array($value))
206
			{
207
				foreach ($value as $tagName)
208
				{
209
					$this->$action($tagName);
210
				}
211
			}
212
			elseif ($overwrite || !isset($this->items[$action]))
213
			{
214
				$this->$action($value);
215
			}
216
		}
217
	}
218
219
	/**
220
	* Remove a specific rule, or all the rules of a given type
221
	*
222
	* @param  string $type    Type of rules to clear
223
	* @param  string $tagName Name of the target tag, or none to remove all rules of given type
224
	* @return void
225
	*/
226
	public function remove($type, $tagName = null)
227
	{
228
		if (preg_match('(^default(?:Child|Descendant)Rule)', $type))
229
		{
230
			throw new RuntimeException('Cannot remove ' . $type);
231
		}
232
233
		if (isset($tagName))
234
		{
235
			$tagName = TagName::normalize($tagName);
236
237
			if (isset($this->items[$type]))
238
			{
239
				// Compute the difference between current list and our one tag name
240
				$this->items[$type] = array_diff(
241
					$this->items[$type],
242
					[$tagName]
243
				);
244
245
				if (empty($this->items[$type]))
246
				{
247
					// If the list is now empty, keep it neat and unset it
248
					unset($this->items[$type]);
249
				}
250
				else
251
				{
252
					// If the list still have names, keep it neat and rearrange keys
253
					$this->items[$type] = array_values($this->items[$type]);
254
				}
255
			}
256
		}
257
		else
258
		{
259
			unset($this->items[$type]);
260
		}
261
	}
262
263
	//==========================================================================
264
	// Rules
265
	//==========================================================================
266
267
	/**
268
	* Add a boolean rule
269
	*
270
	* @param  string $ruleName Name of the rule
271
	* @param  bool   $bool     Whether to enable or disable the rule
272
	* @return self
273
	*/
274
	protected function addBooleanRule($ruleName, $bool = true)
275
	{
276
		if (!is_bool($bool))
277
		{
278
			throw new InvalidArgumentException($ruleName . '() expects a boolean');
279
		}
280
281
		$this->items[$ruleName] = $bool;
282
	}
283
284
	/**
285
	* Add a targeted rule
286
	*
287
	* @param  string $ruleName Name of the rule
288
	* @param  string $tagName  Name of the target tag
289
	* @return self
290
	*/
291
	protected function addTargetedRule($ruleName, $tagName)
292
	{
293
		$this->items[$ruleName][] = TagName::normalize($tagName);
294
	}
295
}