• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JBZoo / Less / 30334252748

20 Jul 2026 08:59PM UTC coverage: 93.75%. Remained the same
30334252748

push

github

web-flow
Merge pull request #12 from JBZoo/release/8.0

8.0.0 — PHP 8.3+ floor & lock-step major

135 of 144 relevant lines covered (93.75%)

61.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.74
/src/Less.php
1
<?php
2

3
/**
4
 * JBZoo Toolbox - Less.
5
 *
6
 * This file is part of the JBZoo Toolbox project.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT
11
 * @copyright  Copyright (C) JBZoo.com, All rights reserved.
12
 * @see        https://github.com/JBZoo/Less
13
 */
14

15
declare(strict_types=1);
16

17
namespace JBZoo\Less;
18

19
use JBZoo\Data\Data;
20
use JBZoo\Utils\Dates;
21
use JBZoo\Utils\FS;
22
use JBZoo\Utils\Sys;
23
use JBZoo\Utils\Url;
24

25
/**
26
 * @psalm-suppress UnusedClass
27
 */
28
final class Less
29
{
30
    private Data   $options;
31
    private Gpeasy $driver;
32

33
    /**
34
     * @var array{
35
     *     force: bool,
36
     *     debug: bool,
37
     *     root_url: ?string,
38
     *     root_path: ?string,
39
     *     global_vars: array,
40
     *     autoload: array,
41
     *     import_paths: array,
42
     *     functions: array,
43
     *     cache_path: string,
44
     *     cache_ttl: int,
45
     * }
46
     */
47
    private array $default = [
48
        'force'        => false,
49
        'debug'        => false, // On/Off Source map for browser debug console
50
        'root_url'     => null,
51
        'root_path'    => null,
52
        'global_vars'  => [],
53
        'autoload'     => [],
54
        'import_paths' => [],
55
        'functions'    => [],
56
        'cache_path'   => './cache',
57
        'cache_ttl'    => Dates::MONTH,  // 30 days
58
    ];
59

60
    public function __construct(array $options = [])
61
    {
62
        $this->options = $this->prepareOptions($options);
84✔
63
        $this->driver  = new Gpeasy($this->options);
84✔
64
    }
65

66
    public function compile(string $lessFile, ?string $basePath = null): string
67
    {
68
        try {
69
            $basePath = $this->prepareBasePath($basePath, \dirname($lessFile));
78✔
70

71
            $cache = new Cache($this->options);
78✔
72
            $cache->setFile($lessFile, $basePath);
78✔
73

74
            $isForce = $this->options->getBool('force');
72✔
75

76
            if ($isForce || $cache->isExpired()) {
72✔
77
                $result = $this->driver->compile($lessFile, $basePath);
72✔
78
                $cache->save($result);
66✔
79
            }
80

81
            $cssPath = $cache->getFile();
66✔
82
        } catch (\Exception $exception) { // Rewrite exception type
12✔
83
            $message = 'JBZoo/Less: ' . $exception->getMessage();
12✔
84
            $trace   = $exception->getTraceAsString();
12✔
85

86
            throw new Exception($message . \PHP_EOL . $trace);
12✔
87
        }
88

89
        return $cssPath;
66✔
90
    }
91

92
    public function setImportPath(string $fullPath, ?string $relPath = null): void
93
    {
94
        $relPath = $relPath === '' || $relPath === null
12✔
95
            ? $this->options->getString('root_url')
6✔
96
            : $relPath;
6✔
97

98
        $this->driver->setImportPath($fullPath, $relPath);
12✔
99
    }
100

101
    /**
102
     * @SuppressWarnings(PHPMD.Superglobals)
103
     */
104
    private function prepareOptions(array $options): Data
105
    {
106
        // Default data for current system
107
        $this->default['root_url']  = Url::root();
84✔
108
        $this->default['root_path'] = Sys::getDocRoot();
84✔
109

110
        $options = \array_merge($this->default, $options);
84✔
111

112
        // Check cache directory
113
        $cachePath = FS::clean((string)$options['cache_path']);
84✔
114
        if ($cachePath === '') {
84✔
115
            throw new Exception('Option "cache_path" is empty!');
×
116
        }
117

118
        if (!FS::isDir($cachePath) && !\mkdir($cachePath, 0755, true) && !\is_dir($cachePath)) {
84✔
119
            throw new \RuntimeException(\sprintf('Directory "%s" was not created', $cachePath));
×
120
        }
121

122
        $options['cache_path'] = FS::real($cachePath);
84✔
123

124
        $rootUrl             = $options['root_url'] ?? '';
84✔
125
        $options['root_url'] = \rtrim((string)$rootUrl, '/');
84✔
126

127
        // Check mixin paths
128
        $lessFile = (array)$options['autoload'];
84✔
129

130
        foreach ($lessFile as $key => $mixin) {
84✔
131
            $lessFile[$key] = FS::real($mixin);
6✔
132
        }
133
        $options['autoload'] = \array_filter($lessFile);
84✔
134

135
        // Check imported paths
136
        $importPaths = [];
84✔
137

138
        foreach ((array)$options['import_paths'] as $path => $uri) {
84✔
139
            $cleanPath = FS::real((string)$path);
6✔
140
            if ($cleanPath !== '' && $cleanPath !== null) {
6✔
141
                $importPaths[$cleanPath] = $uri;
6✔
142
            }
143
        }
144
        $importPaths[(string)$options['root_path']] = $options['root_url']; // Forced add root path in the end of list!
84✔
145

146
        $options['import_paths'] = \array_filter($importPaths);
84✔
147

148
        return new Data($options);
84✔
149
    }
150

151
    private function prepareBasePath(?string $basePath, string $default): string
152
    {
153
        $basePath = $basePath === '' || $basePath === null ? $default : $basePath;
78✔
154

155
        if (!Url::isAbsolute($basePath)) {
78✔
156
            $basePath = \trim($basePath, '\/');
78✔
157
            $basePath = $this->options->getString('root_url') . '/' . $basePath;
78✔
158
        }
159

160
        return $basePath;
78✔
161
    }
162
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc