Passed
Push — main ( c551fb...edb315 )
by Siad
06:32
created

SizeHelper::findUnitMultiple()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 14
ccs 8
cts 8
cp 1
rs 9.6111
c 0
b 0
f 0
cc 5
nc 7
nop 1
crap 5
1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
declare(strict_types=1);
22
23
namespace Phing\Util;
24
25
use Phing\Exception\BuildException;
26
27
/**
28
 * SizeHelper class.
29
 *
30
 * @author Jawira Portugal <[email protected]>
31
 */
32
class SizeHelper
33
{
34
    public const B = 'B';
35
    public const KILO = 1000;
36
    public const KIBI = 1024;
37
    public const SI = [1 => ['kB', 'kilo', 'kilobyte'],
38
        2 => ['MB', 'mega', 'megabyte'],
39
        3 => ['GB', 'giga', 'gigabyte'],
40
        4 => ['TB', 'tera', 'terabyte'], ];
41
    public const IEC = [0 => [self::B],
42
        1 => ['k', 'Ki', 'KiB', 'kibi', 'kibibyte'],
43
        2 => ['M', 'Mi', 'MiB', 'mebi', 'mebibyte'],
44
        3 => ['G', 'Gi', 'GiB', 'gibi', 'gibibyte'],
45
        4 => ['T', 'Ti', 'TiB', 'tebi', 'tebibyte'], ];
46
47
    /**
48
     * Converts strings like '512K', '0.5G', '50M' to bytes.
49
     */
50 73
    public static function fromHumanToBytes(string $human): float
51
    {
52 73
        [$size, $unit] = self::parseHuman($human);
53 68
        $multiple = self::findUnitMultiple($unit);
54
55 63
        return $size * $multiple;
56
    }
57
58
    /**
59
     * Convert from bytes to any other valid unit.
60
     */
61 62
    public static function fromBytesTo(int $bytes, string $unit): float
62
    {
63 62
        $multiple = self::findUnitMultiple($unit);
64
65 51
        return $bytes / $multiple;
66
    }
67
68
    /**
69
     * Extracts size and unit from strings like '1m', '50M', '100.55K', '2048'.
70
     *
71
     * - The default unit is 'B'.
72
     * - If unit exists then it is returned as-is, even invalid units.
73
     * - This function can also handle scientific notation, e.g. '8e10k'.
74
     * - It can also handle negative values '-1M'.
75
     * - Parsing is not locale aware, this means that '.' (dot) is always used as decimal separator.
76
     *
77
     * @param string $human filesize as a human writes it
78
     *
79
     * @return array{0: float, 1: string} First element is size, and second is the unit
80
     */
81 73
    protected static function parseHuman(string $human): array
82
    {
83
        // no unit, so we assume bytes
84 73
        if (is_numeric($human)) {
85 12
            return [floatval($human), self::B];
86
        }
87 61
        $parsed = sscanf($human, '%f%s');
88 61
        if (empty($parsed[0])) {
89 5
            throw new BuildException("Invalid size '{$human}'");
90
        }
91
92 56
        return $parsed;
93
    }
94
95
    /**
96
     * Finds the value in bytes of a single "unit".
97
     */
98 113
    protected static function findUnitMultiple(string $unit): int
99
    {
100 113
        foreach (self::IEC as $exponent => $choices) {
101 113
            if (in_array(strtolower($unit), array_map('strtolower', $choices))) {
102 76
                return pow(self::KIBI, $exponent);
103
            }
104
        }
105 39
        foreach (self::SI as $exponent => $choices) {
106 39
            if (in_array(strtolower($unit), array_map('strtolower', $choices))) {
107 23
                return pow(self::KILO, $exponent);
108
            }
109
        }
110
111 16
        throw new BuildException("Invalid unit '{$unit}'");
112
    }
113
}
114