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

Module::parseMixins()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
ccs 0
cts 0
cp 0
rs 9.2
cc 4
eloc 9
nc 3
nop 2
crap 20
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