Passed
Push — master ( 5d9eaa...8dfc1c )
by Mihail
12:13
created

get_git_changeset()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3.0175

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 19
c 2
b 0
f 1
dl 0
loc 25
ccs 14
cts 16
cp 0.875
rs 9.6333
cc 3
nc 3
nop 0
crap 3.0175
1
<?php declare(strict_types=1);
2
3
const INVALID_VERSION_ARRAY = ['0.0.0', '0', '0'];
4
const SEMVER_REGEX = '/^([0-9]+\.[0-9]+\.[0-9]+)(?:\-([a-z0-9-]+(?:\.[a-z0-9-]+)*))?(?:\+([a-z0-9-]+(?:\.[a-z0-9-]+)*))?$/i';
5
6
/**
7
 * Returns the Koded version number from VERSION file.
8
 *
9
 * @param array $version
10
 * @return string SemVer-compliant version
11
 * @see http://semver.org/
12
 */
13
function get_version(array $version = []): string
14
{
15 13
    $v = get_complete_version($version);
16
    // Most common format (X.Y.Z)
17 13
    if (empty($v[1]) && empty($v[2])) {
18 2
        return $v[0];
19
    }
20
    // Special case when pre-release is ALPHA and build is empty
21 11
    if ('alpha' === strtolower($v[1]) && empty($v[2])) {
22 1
        $v[2] = get_git_changeset();
23 1
        return sprintf('%s-%s+%s', ...$v);
24
    }
25 10
    if (empty($v[2])) {
26 4
        return sprintf('%s-%s', ...$v);
27
    }
28 6
    if (empty($v[1])) {
29 2
        $v[1] = $v[2];
30 2
        return sprintf('%s+%s', ...$v);
31
    }
32 4
    return sprintf('%s-%s+%s', ...$v);
33
}
34
35
/**
36
 * @internal
37
 *
38
 * Returns the version parts in array.
39
 *
40
 * @param string $version
41
 * @return array If version is not parsed by the semver rules, returns 0-filled array
42
 */
43
function get_version_array(string $version): array
44
{
45 46
    if (!preg_match(SEMVER_REGEX, trim($version), $match)) {
46 5
        return INVALID_VERSION_ARRAY;
47
    }
48 41
    array_shift($match);
49 41
    $match = array_replace(INVALID_VERSION_ARRAY, $match);
50 41
    return array_map(fn($v) => empty($v) ? '0' : $v, $match);
51
}
52
53
/**
54
 * @internal
55
 *
56
 * Returns the array version of the Koded version.
57
 * Checks the correctness of the provided version array.
58
 *
59
 * @param array $version
60
 * @return array Koded segmented version as array
61
 */
62
function get_complete_version(array $version): array
63
{
64 16
    if (empty($version)) {
65 2
        $version = match (true) {
66 2
            defined('VERSION') && is_array(VERSION) => get_version_array(join('-', array_filter(VERSION))),
0 ignored issues
show
Bug introduced by
The constant VERSION was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
67 2
            is_file($version = __DIR__ . '/../../../VERSION'), // project dir relative to /vendor
68 2
            is_file($version = getcwd() . '/VERSION') => get_version_array(@file_get_contents($version)),
0 ignored issues
show
Bug introduced by
It seems like @file_get_contents($version) can also be of type false; however, parameter $version of get_version_array() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

68
            is_file($version = getcwd() . '/VERSION') => get_version_array(/** @scrutinizer ignore-type */ @file_get_contents($version)),
Loading history...
69
            default => INVALID_VERSION_ARRAY
70
        };
71
    }
72 16
    assert(3 === count($version), 'version array should have exactly 3 parts');
0 ignored issues
show
Bug introduced by
It seems like $version can also be of type string; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

72
    assert(3 === count(/** @scrutinizer ignore-type */ $version), 'version array should have exactly 3 parts');
Loading history...
73 16
    assert('' !== $version[1], 'pre-release is empty, should be zero or valid identifier');
74 16
    assert('' !== $version[2], 'build-metadata is empty, should be zero or valid identifier');
75 16
    return $version;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $version could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
76
}
77
78
/**
79
 * @internal
80
 *
81
 * Returns the the major version from VERSION file.
82
 *
83
 * @param array $version
84
 * @return int The major version
85
 */
86
function get_major_version(array $version): int
87
{
88 1
    return (int)get_complete_version($version)[0];
89
}
90
91
/**
92
 * @internal
93
 *
94
 * The result is the UTC timestamp of the changeset in "YmDHis" format.
95
 * This value is not guaranteed to be unique, but it is sufficient
96
 * for generating the development version numbers.
97
 *
98
 * @return string Returns the numeric identifier of the latest GIT changeset,
99
 * or root directory modification time on failure
100
 */
101
function get_git_changeset(): string
102
{
103 1
    $cwd = getcwd() . '/.';
104 1
    $format = 'YmdHis';
105 1
    $gitlog = proc_open('git log --pretty=format:%ct --quiet -l HEAD', [
106 1
        ['pipe', 'r'],
107
        ['pipe', 'w'],
108
        ['pipe', 'w'],
109
    ], $pipes, $cwd);
110 1
    if (false === is_resource($gitlog)) {
111
        return date($format, filemtime($cwd));
112
    }
113 1
    stream_set_blocking($pipes[2], false);
114 1
    $timestamp = stream_get_contents($pipes[1]);
115 1
    $timestamp = explode(PHP_EOL, $timestamp)[0] ?? '';
116
    // cleanup; avoid a deadlock
117 1
    fclose($pipes[0]);
118 1
    fclose($pipes[1]);
119 1
    fclose($pipes[2]);
120 1
    proc_close($gitlog);
121 1
    if (empty($timestamp)) {
122 1
        return date($format, filemtime($cwd));
123
    }
124
    // UNIX timestamps are stored in UTC
125
    return date_create_immutable('@' . $timestamp)->format($format);
126
}
127