Completed
Push — master ( 5efdd3...bfd2db )
by Greg
03:21
created

SemVer::version()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
namespace Robo\Task\Development;
3
4
use Robo\Result;
5
use Robo\Contract\TaskInterface;
6
use Robo\Exception\TaskException;
7
8
/**
9
 * Helps to maintain `.semver` file.
10
 *
11
 * ```php
12
 * <?php
13
 * $this->taskSemVer('.semver')
14
 *      ->increment()
15
 *      ->run();
16
 * ?>
17
 * ```
18
 *
19
 */
20
class SemVer implements TaskInterface
21
{
22
    const SEMVER = "---\n:major: %d\n:minor: %d\n:patch: %d\n:special: '%s'\n:metadata: '%s'";
23
24
    const REGEX = "/^\-\-\-\n:major:\s(0|[1-9]\d*)\n:minor:\s(0|[1-9]\d*)\n:patch:\s(0|[1-9]\d*)\n:special:\s'([a-zA-z0-9]*\.?(?:0|[1-9]\d*)?)'\n:metadata:\s'((?:0|[1-9]\d*)?(?:\.[a-zA-z0-9\.]*)?)'/";
25
26
    const REGEX_STRING = '/^(?<major>[0-9]+)\.(?<minor>[0-9]+)\.(?<patch>[0-9]+)(|-(?<special>[0-9a-zA-Z.]+))(|\+(?<metadata>[0-9a-zA-Z.]+))$/';
27
28
    /**
29
     * @var string
30
     */
31
    protected $format = 'v%M.%m.%p%s';
32
33
    /**
34
     * @var string
35
     */
36
    protected $specialSeparator = '-';
37
38
    /**
39
     * @var string
40
     */
41
    protected $metadataSeparator = '+';
42
43
    /**
44
     * @var string
45
     */
46
    protected $path;
47
48
    /**
49
     * @var array
50
     */
51
    protected $version = [
52
        'major' => 0,
53
        'minor' => 0,
54
        'patch' => 0,
55
        'special' => '',
56
        'metadata' => ''
57
    ];
58
59
    /**
60
     * @param string $filename
61
     */
62
    public function __construct($filename = '')
63
    {
64
        $this->path = $filename;
65
66
        if (file_exists($this->path)) {
67
            $semverFileContents = file_get_contents($this->path);
68
            $this->parseFile($semverFileContents);
69
        }
70
    }
71
72
    /**
73
     * @return string
74
     */
75
    public function __toString()
76
    {
77
        $search = ['%M', '%m', '%p', '%s'];
78
        $replace = $this->version + ['extra' => ''];
79
80
        foreach (['special', 'metadata'] as $key) {
81
            if (!empty($replace[$key])) {
82
                $separator = $key . 'Separator';
83
                $replace['extra'] .= $this->{$separator} . $replace[$key];
84
            }
85
            unset($replace[$key]);
86
        }
87
88
        return str_replace($search, $replace, $this->format);
89
    }
90
91
    public function version($version)
92
    {
93
        $this->parseString($version);
94
        return $this;
95
    }
96
97
    /**
98
     * @param string $format
99
     *
100
     * @return $this
101
     */
102
    public function setFormat($format)
103
    {
104
        $this->format = $format;
105
        return $this;
106
    }
107
108
    /**
109
     * @param string $separator
110
     *
111
     * @return $this
112
     */
113
    public function setMetadataSeparator($separator)
114
    {
115
        $this->metadataSeparator = $separator;
116
        return $this;
117
    }
118
119
    /**
120
     * @param string $separator
121
     *
122
     * @return $this
123
     */
124
    public function setPrereleaseSeparator($separator)
125
    {
126
        $this->specialSeparator = $separator;
127
        return $this;
128
    }
129
130
    /**
131
     * @param string $what
132
     *
133
     * @return $this
134
     *
135
     * @throws \Robo\Exception\TaskException
136
     */
137
    public function increment($what = 'patch')
138
    {
139
        switch ($what) {
140
            case 'major':
141
                $this->version['major']++;
142
                $this->version['minor'] = 0;
143
                $this->version['patch'] = 0;
144
                break;
145
            case 'minor':
146
                $this->version['minor']++;
147
                $this->version['patch'] = 0;
148
                break;
149
            case 'patch':
150
                $this->version['patch']++;
151
                break;
152
            default:
153
                throw new TaskException(
154
                    $this,
155
                    'Bad argument, only one of the following is allowed: major, minor, patch'
156
                );
157
        }
158
        return $this;
159
    }
160
161
    /**
162
     * @param string $tag
163
     *
164
     * @return $this
165
     *
166
     * @throws \Robo\Exception\TaskException
167
     */
168
    public function prerelease($tag = 'RC')
169
    {
170
        if (!is_string($tag)) {
171
            throw new TaskException($this, 'Bad argument, only strings allowed.');
172
        }
173
174
        $number = 0;
175
176
        if (!empty($this->version['special'])) {
177
            list($current, $number) = explode('.', $this->version['special']);
178
            if ($tag != $current) {
179
                $number = 0;
180
            }
181
        }
182
183
        $number++;
184
185
        $this->version['special'] = implode('.', [$tag, $number]);
186
        return $this;
187
    }
188
189
    /**
190
     * @param array|string $data
191
     *
192
     * @return $this
193
     */
194
    public function metadata($data)
195
    {
196
        if (is_array($data)) {
197
            $data = implode('.', $data);
198
        }
199
200
        $this->version['metadata'] = $data;
201
        return $this;
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function run()
208
    {
209
        $written = $this->dump();
210
        return new Result($this, (int)($written === false), $this->__toString());
211
    }
212
213
    /**
214
     * @return bool
215
     *
216
     * @throws \Robo\Exception\TaskException
217
     */
218
    protected function dump()
219
    {
220
        if (empty($this->path)) {
221
            return true;
222
        }
223
        extract($this->version);
224
        $semver = sprintf(self::SEMVER, $major, $minor, $patch, $special, $metadata);
225
        if (is_writeable($this->path) === false || file_put_contents($this->path, $semver) === false) {
226
            throw new TaskException($this, 'Failed to write semver file.');
227
        }
228
        return true;
229
    }
230
231
    protected function parseString($semverString)
232
    {
233
        if (!preg_match_all(self::REGEX_STRING, $semverString, $matches)) {
234
            throw new TaskException($this, 'Bad semver value: ' . $semverString);
235
        }
236
237
        $this->version = array_intersect_key($matches, $this->version);
238
        $this->version = array_map(function ($item) {
239
            return $item[0];
240
        }, $this->version);
241
    }
242
243
    /**
244
     * @throws \Robo\Exception\TaskException
245
     */
246
    protected function parseFile($semverFileContents)
247
    {
248
        if (!preg_match_all(self::REGEX, $semverFileContents, $matches)) {
249
            throw new TaskException($this, 'Bad semver file.');
250
        }
251
252
        list(, $major, $minor, $patch, $special, $metadata) = array_map('current', $matches);
253
        $this->version = compact('major', 'minor', 'patch', 'special', 'metadata');
254
    }
255
}
256