Passed
Push — master ( aeeab5...454cf7 )
by Kacper
02:57
created

Rule::everywhere()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 11
ccs 8
cts 8
cp 1
rs 9.4285
cc 2
eloc 6
nc 2
nop 0
crap 2
1
<?php
2
/**
3
 * Highlighter
4
 *
5
 * Copyright (C) 2015, Some right reserved.
6
 *
7
 * @author Kacper "Kadet" Donat <[email protected]>
8
 *
9
 * Contact with author:
10
 * Xmpp: [email protected]
11
 * E-mail: [email protected]
12
 *
13
 * From Kadet with love.
14
 */
15
16
namespace Kadet\Highlighter\Parser;
17
18
use Kadet\Highlighter\Language\Language;
19
use Kadet\Highlighter\Matcher\MatcherInterface;
20
use Kadet\Highlighter\Parser\Token\Token;
21
22
/**
23
 * Class Rule
24
 *
25
 * @package Kadet\Highlighter\Parser
26
 *
27
 * @property Language              $language
28
 * @property Language              $inject
29
 * @property integer               $priority
30
 * @property string                $type
31
 * @property TokenFactoryInterface $factory
32
 *
33
 */
34
class Rule
35
{
36
    const CONTEXT_IN        = 1;
37
    const CONTEXT_NOT_IN    = 2;
38
    const CONTEXT_IN_ONE_OF = 4;
39
    const CONTEXT_EXACTLY   = 8;
40
    const CONTEXT_ON_TOP    = 16;
41
42
    private $_matcher;
43
    private $_context = [];
44
45
    private $_default = true;
46
47
    private $_options;
48
    private $_validator;
49
50
    /**
51
     * @param MatcherInterface|null $matcher
52
     * @param array                 $options
53
     */
54 62
    public function __construct(MatcherInterface $matcher = null, array $options = [])
55
    {
56 62
        $this->_matcher = $matcher;
57
58
        // Default options:
59 62
        $options = array_merge([
60 62
            'context'  => [],
61 62
            'priority' => 1,
62 62
            'language' => false,
63 62
            'factory'  => new TokenFactory(Token::class),
64 62
        ], $options);
65
66 62
        $this->setContext($options['context']);
67 62
        $this->_options = $options;
68
69 62
        $this->factory->setRule($this);
70 62
    }
71
72 62
    public function setContext($rules)
73
    {
74 62
        if (is_callable($rules)) {
75 18
            $this->_validator = $rules;
76 18
        } else {
77 55
            $this->_context = [];
78 55
            foreach ($rules as $key => $rule) {
79 7
                list($plain, $type)     = $this->_getContextRule($rule);
80 7
                $this->_context[$plain] = $type;
81 55
            }
82
        }
83 62
    }
84
85 7
    private function _getContextRule($rule)
86
    {
87
        $types = [
88 7
            '!' => self::CONTEXT_NOT_IN,
89 7
            '+' => self::CONTEXT_IN,
90 7
            '*' => self::CONTEXT_IN_ONE_OF,
91 7
            '@' => self::CONTEXT_EXACTLY,
92
            '^' => self::CONTEXT_ON_TOP
93 7
        ];
94
95 7
        if (!isset($types[$rule[0]])) {
96 3
            return [$rule, self::CONTEXT_IN];
97
        }
98
99 5
        $type = 0;
100 5
        $pos  = 0;
101 5
        foreach (str_split($rule) as $pos => $char) {
102 5
            if (!isset($types[$char])) {
103 4
                break;
104
            }
105
106 5
            if ($types[$char] == self::CONTEXT_IN_ONE_OF) {
107 2
                $this->_default = false;
108 2
            }
109
110 5
            $type |= $types[$char];
111 5
        }
112
113 5
        return [substr($rule, $pos), $type];
114
    }
115
116
    /**
117
     * @param $source
118
     *
119
     * @return Token[]
120
     */
121 13
    public function match($source)
122
    {
123 13
        return $this->_matcher !== null ? $this->_matcher->match($source, $this->factory) : [];
124
    }
125
126 22
    public function validate($context, array $additional = [])
127
    {
128 22
        if (is_callable($this->_validator)) {
129 13
            $validator = $this->_validator;
130
131 13
            return $validator($context, $additional);
132
        } else {
133 19
            return $this->_validate($context, array_merge($additional, $this->_context));
134
        }
135
    }
136
137 19
    private function _validate($context, $rules)
138
    {
139 19
        if (empty($rules)) {
140 14
            return count($context) === 0;
141
        }
142
143 7
        $result = $this->_default;
144
145 7
        reset($rules);
146 7
        while (list($rule, $type) = each($rules)) {
147 7
            $matched = !($type & self::CONTEXT_EXACTLY) ?
148 7
                !empty(preg_grep('/^'.preg_quote($rule).'(\.\w+)*/iS', $context)) :
149 7
                in_array($rule, $context, true);
150
151 7
            if ($type & self::CONTEXT_NOT_IN) {
152 2
                if ($matched) {
153 2
                    return false;
154
                }
155 1
                $result = true;
156 7
            } elseif ($type & self::CONTEXT_IN) {
157 5
                if (!$matched) {
158 2
                    return false;
159
                }
160 5
                $result = true;
161
162 5
                $this->_unsetUnnecessaryRules($rule, $rules);
163 6
            } elseif ($type & self::CONTEXT_IN_ONE_OF) {
164 1
                if ($matched) {
165 1
                    $result = true;
166 1
                    $this->_unsetUnnecessaryRules($rule, $rules);
167 1
                }
168 1
            }
169 7
        }
170
171 6
        return $result;
172
    }
173
174 6
    private function _unsetUnnecessaryRules($rule, &$required)
175
    {
176 6
        if (strpos($rule, '.') !== false) {
177
            foreach (array_filter(array_keys($this->_context), function ($key) use ($rule) {
178 1
                return fnmatch($key . '.*', $rule);
179 1
            }) as $remove) {
180 1
                unset($required[$remove]);
181 1
            }
182 1
        }
183 6
    }
184
185 62
    public function __get($option)
186
    {
187 62
        return isset($this->_options[$option]) ? $this->_options[$option] : null;
188
    }
189
190 11
    public function __set($option, $value)
191
    {
192 11
        return $this->_options[$option] = $value;
193
    }
194
195 17
    public static function everywhere()
196
    {
197 17
        static $callable;
198 17
        if (!$callable) {
199 12
            $callable = function () {
200 12
                return true;
201 1
            };
202 1
        }
203
204 17
        return $callable;
205
    }
206
}
207