Completed
Push — master ( bdbda4...5925a9 )
by Korvin
06:13
created

Browserslist::getVersionNormalizer()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
crap 2
1
<?php
2
3
namespace Buttress\Browserslist;
4
5
use Buttress\Browserslist\Browser\VersionNormalizer;
6
use Buttress\Browserslist\Query\CustomStats;
7
use Buttress\Browserslist\Query\DirectDriver;
8
use Buttress\BrowsersList\Query\Driver;
9
use Buttress\Browserslist\Query\FirefoxESR;
10
use Buttress\Browserslist\Query\GlobalStats;
11
use Buttress\Browserslist\Query\LastByBrowser;
12
use Buttress\Browserslist\Query\LastVersions;
13
use Buttress\Browserslist\Query\OperaMini;
14
use Buttress\Browserslist\Query\Range;
15
use Buttress\Browserslist\Query\Versions;
16
use Illuminate\Support\Collection;
17
18
class Browserslist
19
{
20
    /** @var Driver[] */
21
    protected $queryDrivers;
22
23
    /** @var \Illuminate\Support\Collection */
24
    protected $aliases;
25
26
    /** @var Collection */
27
    protected $versionAliases;
28
29
    /** @var Collection */
30
    protected $usage;
31
32
    /** @var \Illuminate\Support\Collection */
33
    protected $browsers;
34
35
    /** @var \Illuminate\Support\Collection */
36
    protected $data;
37
38
    /** @var array|string */
39
    protected $defaultQuery;
40
41
    /** @var VersionNormalizer */
42
    protected $normalizer;
43
44 105
    public function __construct($data)
45
    {
46 105
        if (!$data instanceof Collection) {
47 27
            $data = new Collection($data);
48
        }
49
50 105
        $this->defaultQuery = '> 1%, last 2 versions, Firefox ESR';
51 105
        $this->data = $data;
52 105
        $this->versionAliases = new Collection();
53
54 105
        $this->aliases = new Collection([
55 105
            'fx' => 'firefox',
56
            'ff' => 'firefox',
57
            'ios' => 'ios_saf',
58
            'explorer' => 'ie',
59
            'blackberry' => 'bb',
60
            'explorermobile' => 'ie_mob',
61
            'operamini' => 'op_mini',
62
            'operamobile' => 'op_mob',
63
            'chromeandroid' => 'and_chr',
64
            'firefoxandroid' => 'and_ff',
65
            'ucandroid' => 'and_uc'
66
        ]);
67
68 105
        $this->browsers = new Collection(['safari', 'opera', 'ios_saf', 'ie_mob', 'ie', 'edge', 'firefox', 'chrome']);
69 105
        $this->queryDrivers = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array(\Buttress\Browsers...ry\DirectDriver::class) of type array<integer,?> is incompatible with the declared type array<integer,object<But...sersList\Query\Driver>> of property $queryDrivers.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
70
            LastVersions::class,
71
            LastByBrowser::class,
72
            OperaMini::class,
73
            CustomStats::class,
74
            GlobalStats::class,
75
            FirefoxESR::class,
76
            Versions::class,
77
            Range::class,
78
            DirectDriver::class
79
        ];
80 105
    }
81
82
    /**
83
     * Get the usage of a browser or set of browsers
84
     * @param array $browsers
85
     * @param string $country
86
     * @return mixed
87
     */
88 6
    public function coverage(array $browsers, $country = 'global')
89
    {
90 6
        $list = $this;
91
92 6
        if (strtolower($country) !== 'global') {
93
            throw new \InvalidArgumentException('Country specific coverage is not supported.');
94
        }
95
96
        return Collection::make($browsers)->reduce(function ($total, $browser) use ($list) {
97 3
            if (!$usage = array_get($list->getUsage()->get("global"), $browser)) {
98
                $usage = array_get($list->getUsage()->get("global"), preg_replace('/ [\d.]+$/', ' 0', $browser));
99
            }
100
101 3
            return $total + floatval($usage);
102 6
        }, 0);
103
    }
104
105
    /**
106
     * Get the data for a particular browser name
107
     * @param $name
108
     * @return array|null
109
     */
110 69
    public function getDataByBrowser($name)
111
    {
112 69
        $name = strtolower($name);
113 69
        $data = $this->getData();
114 69
        $aliases = $this->getAliases();
115
116 69
        if ($aliases->has($name)) {
117 15
            $name = $aliases[$name];
118
        }
119
120
        $browser = $data->first(function ($browser) use ($name) {
121 69
            return $browser['name'] === $name;
122 69
        });
123
124 69
        if ($browser) {
125 63
            return $browser;
126
        }
127 18
    }
128
129
    /**
130
     * Take a version string and return the best possible match based on browsername and available versions
131
     * @param $browserName
132
     * @param $version
133
     * @return null|string
134
     */
135 39
    public function normalizeVersion($browserName, $version)
136
    {
137 39
        $normalizer = $this->getVersionNormalizer();
138 39
        return $normalizer->normalizeVersion($browserName, $version);
139
    }
140
141
    /**
142
     * Get the version normalizer object
143
     * @return \Buttress\Browserslist\Browser\VersionNormalizer
144
     */
145 39
    protected function getVersionNormalizer()
146
    {
147 39
        if (!$this->normalizer) {
148 39
            $this->normalizer = new VersionNormalizer($this);
149
        }
150
151 39
        return $this->normalizer;
152
    }
153
154
    /**
155
     * Run a query against the Browserlist data
156
     * @param $query
157
     * @return \Illuminate\Support\Collection
158
     * @throws \InvalidArgumentException If a query passed is not recognized
159
     */
160 99
    public function query($query = null)
161
    {
162 99
        if ($query === null) {
163 3
            $query = $this->defaultQuery;
164
        }
165
166 99
        $result = new Collection();
167 99
        foreach ($this->generateQueries($query) as $subQuery) {
168 96
            if ($rejectQuery = $this->shouldReject($subQuery)) {
169 3
                $result = $this->rejectQuery($result, $rejectQuery);
170
            } else {
171 96
                $result = $result->merge($this->handleQuery($subQuery));
172
            }
173
        }
174
175 87
        return $result->unique()->sort($this->queryResultSort())->values();
176
    }
177
178
    /**
179
     * Generator method for retrieving cleaned query strings
180
     * @param $query
181
     * @return \Generator
182
     */
183 99
    private function generateQueries($query)
184
    {
185 99
        if (is_string($query)) {
186 90
            $query = explode(',', $query);
187
        }
188
189 99
        foreach ((array)$query as $queryString) {
190 99
            if ($trimmed = trim($queryString)) {
191 99
                yield $trimmed;
192
            }
193
        }
194 87
    }
195
196
    /**
197
     * @param $query
198
     * @return bool|string
199
     */
200 96
    private function shouldReject($query)
201
    {
202 96
        if (strlen($query) > 4 && substr($query, 0, 4) == 'not ') {
203 3
            return substr($query, 4);
204
        }
205
206 96
        return false;
207
    }
208
209
    /**
210
     * Takes a collection and removes the results of the passed query
211
     * @param \Illuminate\Support\Collection $result
212
     * @param $query
213
     * @return Collection
214
     */
215 3
    private function rejectQuery(Collection $result, $query)
216
    {
217 3
        $without = $this->handleQuery($query);
218
219
        return $result->reject(function ($item) use ($without) {
220 3
            return !!$without->contains($item);
221 3
        });
222
    }
223
224
    /**
225
     * Internal query handling method, this delegates to drivers
226
     * @param $query
227
     * @return \Illuminate\Support\Collection
228
     */
229 96
    private function handleQuery($query)
230
    {
231 96
        foreach ($this->queryDriverGenerator() as $driver) {
232 96
            if ($driver->handlesQuery($query)) {
233 96
                return $driver->query($query, $this);
234
            }
235
        }
236
237 3
        throw new \InvalidArgumentException("Unknown browser query `{$query}`");
238
    }
239
240
    /**
241
     * Inflates drivers as they are iterated over
242
     * @return \Generator
243
     */
244 96
    private function queryDriverGenerator()
245
    {
246 96
        foreach ($this->queryDrivers as $key => $driver) {
247 96
            if (is_string($driver) && class_exists($driver)) {
248 96
                $driver = new $driver;
249 96
                $this->queryDrivers[$key] = $driver;
250 96
                yield $driver;
251
            } else {
252 84
                yield $driver;
253
            }
254
        }
255 3
    }
256
257
    /**
258
     * Result sort closure
259
     *
260
     * Sorts in natural, then reverse natural order.
261
     *
262
     * Example sort order:
263
     * a 10
264
     * a 9
265
     * b 100
266
     * b 60
267
     * z 50
268
     * z 1
269
     *
270
     * @return \Closure
271
     */
272
    private function queryResultSort()
273
    {
274 87
        return function ($a, $b) {
275 48
            $result = strnatcmp($a, $b);
276
277 48
            $lettersOnlyA = preg_replace('/[^a-z]/i', '', $a);
278 48
            $lettersOnlyB = preg_replace('/[^a-z]/i', '', $b);
279
280
            // If the browsers are the same
281 48
            if ($lettersOnlyA === $lettersOnlyB) {
282 45
                return -1 * $result;
283
            }
284
285 21
            return $result;
286 87
        };
287
    }
288
289
    /**
290
     * @return array|string
291
     */
292 3
    public function getDefaultQuery()
293
    {
294 3
        return $this->defaultQuery;
295
    }
296
297
    /**
298
     * @param array|string $query
299
     */
300
    public function setDefaultQuery($query)
301
    {
302
        $this->defaultQuery = $query;
303
    }
304
305
    /**
306
     * @return Collection
307
     */
308 15
    public function getVersionAliases()
309
    {
310 15
        return $this->versionAliases;
311
    }
312
313
    /**
314
     * @param Collection $versionAliases
315
     */
316 78
    public function setVersionAliases(Collection $versionAliases)
317
    {
318 78
        $this->versionAliases = $versionAliases;
319 78
    }
320
321
    /**
322
     * @return Collection
323
     */
324 27
    public function getUsage()
325
    {
326 27
        return $this->usage;
327
    }
328
329
    /**
330
     * @param Collection $usage
331
     */
332 78
    public function setUsage(Collection $usage)
333
    {
334 78
        $this->usage = $usage;
335 78
    }
336
337
    /**
338
     * @return \Illuminate\Support\Collection
339
     */
340 69
    public function getAliases()
341
    {
342 69
        return $this->aliases;
343
    }
344
345
    /**
346
     * @return \Illuminate\Support\Collection
347
     */
348 18
    public function getBrowsers()
349
    {
350 18
        return $this->browsers;
351
    }
352
353
    /**
354
     * @return \Illuminate\Support\Collection
355
     */
356 69
    public function getData()
357
    {
358 69
        return $this->data;
359
    }
360
361
    /**
362
     * Magic method for running a query by invoking the class
363
     * @param array ...$arguments
364
     * @return \Illuminate\Support\Collection
365
     */
366 6
    public function __invoke(...$arguments)
367
    {
368 6
        return $this->query(...$arguments);
369
    }
370
}
371