Passed
Push — master ( 203740...2c4c0a )
by Kacper
04:22
created

Rule   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 147
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 15
Bugs 0 Features 0
Metric Value
wmc 27
c 15
b 0
f 0
lcom 2
cbo 3
dl 0
loc 147
ccs 81
cts 81
cp 1
rs 10

8 Methods

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