Passed
Push — master ( 7783b2...f247da )
by Kacper
02:40
created

Rule   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 171
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 17
Bugs 0 Features 0
Metric Value
wmc 32
c 17
b 0
f 0
lcom 2
cbo 3
dl 0
loc 171
ccs 95
cts 95
cp 1
rs 9.6

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 1
A setContext() 0 13 3
B _getContextRule() 0 29 5
A match() 0 4 2
A validate() 0 9 2
C _validate() 0 37 11
A _unsetUnnecessaryRules() 0 10 3
A __get() 0 4 2
A __set() 0 4 1
A everywhere() 0 10 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
19
use Kadet\Highlighter\Language\Language;
20
use Kadet\Highlighter\Matcher\MatcherInterface;
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
41
    private $_matcher;
42
    private $_context = [];
43
44
    private $_default = true;
45
46
    private $_options;
47
    private $_validator;
48
49
    /**
50
     * @param MatcherInterface|null $matcher
51
     * @param array                 $options
52
     */
53 62
    public function __construct(MatcherInterface $matcher = null, array $options = [])
54
    {
55 62
        $this->_matcher = $matcher;
56
57
        // Default options:
58 62
        $options = array_merge([
59 62
            'context'  => [],
60 62
            'priority' => 1,
61 62
            'language' => false,
62 62
            'factory'  => new TokenFactory('\Kadet\Highlighter\Parser\Token'),
63 62
        ], $options);
64
65 62
        $this->setContext($options['context']);
66 62
        $this->_options = $options;
67
68 62
        $this->factory->setRule($this);
69 62
    }
70
71 62
    public function setContext($rules)
72
    {
73 62
        if(is_callable($rules)) {
74 18
            $this->_validator = $rules;
75 18
        } else {
76 55
            $this->_context = [];
77 55
            foreach ($rules as $key => $rule) {
78 7
                list($plain, $type) = $this->_getContextRule($rule);
79 7
                $this->_context[$plain] = $type;
80 55
            }
81
        }
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 7
        ];
93
94 7
        if (!isset($types[$rule[0]])) {
95 3
            return [$rule, self::CONTEXT_IN];
96
        }
97
98 5
        $type = 0;
99 5
        $pos = 0;
100 5
        foreach (str_split($rule) as $pos => $char) {
101 5
            if (!isset($types[$char])) {
102 4
                break;
103
            }
104
105 5
            if ($types[$char] == self::CONTEXT_IN_ONE_OF) {
106 2
                $this->_default = false;
107 2
            }
108
109 5
            $type |= $types[$char];
110 5
        }
111
112 5
        return [substr($rule, $pos), $type];
113
    }
114
115
    /**
116
     * @param $source
117
     *
118
     * @return Token[]
119
     */
120 13
    public function match($source)
121
    {
122 13
        return $this->_matcher !== null ? $this->_matcher->match($source, $this->factory) : [];
123
    }
124
125 22
    public function validate($context, array $additional = [])
126
    {
127 22
        if(is_callable($this->_validator)) {
128 13
            $validator = $this->_validator;
129 13
            return $validator($context, $additional);
130
        } else {
131 19
            return $this->_validate($context, array_merge($additional, $this->_context));
132
        }
133
    }
134
135 19
    private function _validate($context, $rules) {
136 19
        if (empty($rules)) {
137 14
            return count($context) === 0;
138
        }
139
140 7
        $result = $this->_default;
141
142 7
        reset($rules);
143 7
        while (list($rule, $type) = each($rules)) {
144 7
            $matched = !($type & self::CONTEXT_EXACTLY) ?
145
                count(array_filter($context, function ($a) use ($rule) {
146 7
                    return $a === $rule || fnmatch($rule . '.*', $a);
147 7
                })) > 0 :
148 7
                in_array($rule, $context, true);
149
150 7
            if ($type & self::CONTEXT_NOT_IN) {
151 2
                if ($matched) {
152 2
                    return false;
153
                }
154 1
                $result = true;
155 7
            } elseif ($type & self::CONTEXT_IN) {
156 5
                if (!$matched) {
157 2
                    return false;
158
                }
159 5
                $result = true;
160
161 5
                $this->_unsetUnnecessaryRules($rule, $rules);
162 6
            } elseif ($type & self::CONTEXT_IN_ONE_OF) {
163 1
                if ($matched) {
164 1
                    $result = true;
165 1
                    $this->_unsetUnnecessaryRules($rule, $rules);
166 1
                }
167 1
            }
168 7
        }
169
170 6
        return $result;
171
    }
172
173 6
    private function _unsetUnnecessaryRules($rule, &$required)
174
    {
175 6
        if (strpos($rule, '.') !== false) {
176
            foreach (array_filter(array_keys($this->_context), function ($key) use ($rule) {
177 1
                return fnmatch($key . '.*', $rule);
178 1
            }) as $remove) {
179 1
                unset($required[$remove]);
180 1
            }
181 1
        }
182 6
    }
183
184 62
    public function __get($option)
185
    {
186 62
        return isset($this->_options[$option]) ? $this->_options[$option] : null;
187
    }
188
189 11
    public function __set($option, $value)
190
    {
191 11
        return $this->_options[$option] = $value;
192
    }
193
194 17
    public static function everywhere() {
195 17
        static $callable;
196 17
        if(!$callable) {
197 12
            $callable = function() {
198 12
                return true;
199 1
            };
200 1
        }
201
202 17
        return $callable;
203
    }
204
}