Completed
Push — master ( c5e3e6...e65eb6 )
by Vitaly
02:14
created

Module   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 163
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 16
Bugs 2 Features 5
Metric Value
wmc 18
c 16
b 2
f 5
lcom 1
cbo 3
dl 0
loc 163
ccs 11
cts 11
cp 1
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A prepare() 0 18 3
A finished() 0 13 2
A parseVariables() 0 18 4
A parseMixins() 0 17 4
A analyzer() 0 11 2
A compiler() 0 21 3
1
<?php
2
namespace samsonphp\less;
3
4
use samson\core\ExternalModule;
5
use samsonphp\event\Event;
6
use samsonphp\resource\Router;
7
8
/**
9
 * SamsonPHP LESS compiler module.
10
 *
11
 * TODO: Nested mixin parsing to remove file name mixin hack
12
 * TODO: Switch to independent generic file system manager
13
 *
14
 * @author Vitaly Iegorov <[email protected]>
15
 */
16
class Module extends ExternalModule
17
{
18
    /** LESS variable declaration pattern */
19
    const P_VARIABLE_DECLARATION = '/^\s*\@(?<name>[^\s:]+)\s*\:\s*(?<value>[^;]+);/m';
20 2
    /** LESS mixin declaration pattern */
21
    const P_MIXIN_DECLARATION = '/^\s*\.(?<name>[^\s(:]+)\s*(?<params>\([^)]+\))\s*(?<code>\{[^}]+\})/m';
22 2
23
    /** @var string Path to cached mixins & variables */
24 2
    public $cachedLESS;
25
26 2
    /** @var \lessc LESS compiler */
27
    protected $less;
28
29
    /** @var array Collection of LESS variables */
30
    protected $variables = [];
31
32
    /** @var array Collection of LESS mixins */
33
    protected $mixins = [];
34
35
    /** @var string Cached LESS code */
36
    protected $lessCode;
37
38 2
    /** SamsonFramework load preparation stage handler */
39
    public function prepare()
40
    {
41 2
        Event::subscribe(Router::E_RESOURCE_PRELOAD, [$this, 'analyzer']);
42
        Event::subscribe(Router::E_RESOURCE_COMPILE, [$this, 'compiler']);
43
        Event::subscribe(Router::E_FINISHED, [$this, 'finished']);
44 2
45 2
        $this->less = new \lessc;
46
47
        // Create path to LESS
48 1
        $this->cachedLESS = strlen($this->cachedLESS) ? $this->cachedLESS : $this->cache_path.'mixins.less';
49
50 1
        // Read cached less mixins and variables
51 1
        if (file_exists($this->cachedLESS)) {
52
            $this->lessCode = file_get_contents($this->cachedLESS);
53
        }
54
55
        return true;
56
    }
57
58
    /**
59
     * Create LESS variables and mixins cache file.
60
     */
61
    public function finished()
62
    {
63
        $this->lessCode .= implode("\n", $this->variables) . "\n"
64
            . implode("\n", $this->mixins) . "\n";
65
66
        // Create cache path
67
        $path = dirname($this->cachedLESS);
68
        if (!file_exists($path)) {
69
            mkdir($path, 0777, true);
70
        }
71
72
        file_put_contents($this->cachedLESS, $this->lessCode);
73
    }
74
75
    /**
76
     * Parse LESS variable definition and remove them from code.
77
     *
78
     * @param string $content Source code for parsing LESS variables
79
     *
80
     * @return string Parsed code without LESS variables
81
     */
82
    protected function parseVariables($content)
83
    {
84
        // Find variable declaration
85
        if (preg_match_all(self::P_VARIABLE_DECLARATION, $content, $matches)) {
86
            // Gather variables in collection key => value
87
            for ($i = 0, $max = count($matches['name']); $i < $max; $i++) {
88
                // Check for duplicates
89
                if (!array_key_exists($matches['name'][$i], $this->variables)) {
90
                    // Store variable by name => definition
91
                    $this->variables[$matches['name'][$i]] = $matches[0][$i];
92
                    // Remove variable declaration from content
93
                    $content = str_replace($matches[0][$i], '', $content);
94
                }
95
            }
96
        }
97
98
        return $content;
99
    }
100
101
    /**
102
     * Parse LESS mixins definition and remove them from code.
103
     *
104
     * @param string $resource LESS resource path
105
     * @param string $content Source code for parsing LESS mixins
106
     *
107
     * @return string Parsed code without LESS mixins
108
     */
109
    protected function parseMixins($resource, $content)
110
    {
111
        // TODO: Hack that files with mixin should be separated and have "mixin" in their name
112
        if (strpos($resource, 'mixin') !== false) {
113
            $this->mixins[$resource] = $content;
114
            // As we consider whole file has only mixins - clear content
115
            $content = '';
116
        } elseif (preg_match_all(self::P_MIXIN_DECLARATION, $content, $matches)) {
117
            // Gather variables in collection key => value
118
            for ($i = 0, $max = count($matches[0]); $i < $max; $i++) {
119
                $this->mixins[$matches['name'][$i]] = $matches[0][$i];
120
                $content = str_replace($matches[0][$i], '', $content);
121
            }
122
        }
123
124
        return $content;
125
    }
126
127
    /**
128
     * LESS resource analyzer.
129
     *
130
     * @param string $resource  Resource full path
131
     * @param string $extension Resource extension
132
     * @param string $content LESS code
133
     *
134
     * @return array Variables and mixins collection
135
     */
136
    public function analyzer($resource, $extension, &$content)
137
    {
138
        if ($extension === 'less') {
139
            $content = $this->parseVariables($content);
140
            $content = $this->parseMixins($resource, $content);
141
142
            return [$this->variables, $this->mixins];
143
        }
144
145
        return [];
146
    }
147
148
    /**
149
     * LESS resource compiler.
150
     *
151
     * @param string $resource  Resource full path
152
     * @param string $extension Resource extension
153
     * @param string $content   Compiled output resource content
154
     *
155
     * @throws \Exception
156
     */
157
    public function compiler($resource, &$extension, &$content)
158
    {
159
        if ($extension === 'less') {
160
            try {
161
                // Read updated CSS resource file and compile it with mixins
162
                $content = $this->less->compile(
163
                    implode("\n", $this->variables) . "\n"
164
                    . implode("\n", $this->mixins) . "\n"
165
                    . $this->lessCode
166
                    . $content
167
                );
168
169
                // Switch extension
170
                $extension = 'css';
171
            } catch (\Exception $e) {
172
                //$errorFile = 'cache/error_resourcer'.microtime(true).'.less';
173
                //file_put_contents($errorFile, $output);
174
                throw new \Exception('Failed compiling LESS in "' . $resource . '":' . "\n" . $e->getMessage());
175
            }
176
        }
177
    }
178
}
179