Passed
Push — test ( 72c582...a158b4 )
by Tom
02:24
created

File::validateImage()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 7
nop 1
dl 0
loc 25
ccs 15
cts 15
cp 1
crap 7
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
/* this file is part of pipelines */
4
5
namespace Ktomk\Pipelines;
6
7
use InvalidArgumentException;
8
use Ktomk\Pipelines\File\BbplMatch;
9
use Ktomk\Pipelines\File\Image;
10
use Ktomk\Pipelines\File\ParseException;
11
use Ktomk\Pipelines\Runner\Reference;
12
13
/**
14
 * Bitbucket Pipelines file
15
 */
16
class File
17
{
18
    const FILE_NAME = 'bitbucket-pipelines.yml';
19
20
    const DEFAULT_IMAGE = 'atlassian/default-image:latest';
21
22
    /**
23
     * default clone depth
24
     */
25
    const DEFAULT_CLONE = 50;
26
27
    /**
28
     * @var array
29
     */
30
    private $array;
31
32
    /**
33
     * @var array
34
     */
35
    private $pipelines;
36
37
    /**
38
     * @var array
39
     */
40
    private static $sections = array('branches', 'tags', 'bookmarks', 'pull-requests', 'custom');
41
42
    /**
43
     * File constructor.
44
     *
45
     * @param array $array
46
     * @throws \Ktomk\Pipelines\File\ParseException
47
     */
48 23
    public function __construct(array $array)
49
    {
50
        // quick validation: pipelines require
51 23
        if (!isset($array['pipelines']) || !is_array($array['pipelines'])) {
52 1
            ParseException::__("Missing required property 'pipelines'");
53
        }
54
55
        // quick validation: image name
56 22
        Image::validate($array);
57
58 20
        $this->pipelines = $this->parsePipelineReferences($array['pipelines']);
59
60 17
        $this->array = $array;
61 17
    }
62
63
    /**
64
     * @param $path
65
     * @throws \Ktomk\Pipelines\File\ParseException
66
     * @return File
67
     */
68 8
    public static function createFromFile($path)
69
    {
70 8
        $result = Yaml::file($path);
71 8
        if (!$result) {
72 1
            ParseException::__(sprintf('YAML error: %s; verify the file contains valid YAML', $path));
73
        }
74
75 7
        return new self($result);
76
    }
77
78
    /**
79
     * @throws \Ktomk\Pipelines\File\ParseException
80
     * @return Image
81
     */
82 3
    public function getImage()
83
    {
84 3
        $imageData = isset($this->array['image'])
85 1
            ? $this->array['image']
86 3
            : self::DEFAULT_IMAGE;
87
88 3
        return new Image($imageData);
89
    }
90
91 2
    public function getClone()
92
    {
93 2
        return isset($this->array['clone'])
94 1
            ? $this->array['clone']
95 2
            : self::DEFAULT_CLONE;
96
    }
97
98
    /**
99
     * @throws InvalidArgumentException
100
     * @return null|Pipeline
101
     */
102 4
    public function getDefault()
103
    {
104 4
        return $this->getById('default');
105
    }
106
107
    /**
108
     * returns the id of the default pipeline in file or null if there is none
109
     *
110
     * @return null|string
111
     */
112 2
    public function getIdDefault()
113
    {
114 2
        $id = 'default';
115
116 2
        if (!isset($this->pipelines[$id])) {
117 1
            return null;
118
        }
119
120 1
        return $id;
121
    }
122
123
    /**
124
     * Searches the pipeline that matches the reference
125
     *
126
     * @param Reference $reference
127
     * @throws \UnexpectedValueException
128
     * @throws InvalidArgumentException
129
     * @return null|string id if found, null otherwise
130
     */
131 1
    public function searchIdByReference(Reference $reference)
132
    {
133 1
        if (null === $reference->getType()) {
134 1
            return $this->getIdDefault();
135
        }
136
137 1
        return $this->searchIdByTypeReference(
138 1
            $reference->getPipelinesType(),
139 1
            $reference->getName()
140
        );
141
    }
142
143
    /**
144
     * Searches a reference
145
     *
146
     * @param Reference $reference
147
     * @throws \Ktomk\Pipelines\File\ParseException
148
     * @throws \UnexpectedValueException
149
     * @throws InvalidArgumentException
150
     * @return null|Pipeline
151
     */
152 7
    public function searchReference(Reference $reference)
153
    {
154 7
        if (null === $type = $reference->getPipelinesType()) {
155 2
            return $this->getDefault();
156
        }
157
158 5
        return $this->searchTypeReference($type, $reference->getName());
159
    }
160
161
    /**
162
     * Searches a reference within type, returns found one, if
163
     * none is found, the default pipeline or null if there is
164
     * no default pipeline.
165
     *
166
     * @param string $type of pipeline, can be branches, tags or bookmarks
167
     * @param string $reference
168
     * @throws \Ktomk\Pipelines\File\ParseException
169
     * @throws \UnexpectedValueException
170
     * @throws InvalidArgumentException
171
     * @return null|Pipeline
172
     */
173 8
    public function searchTypeReference($type, $reference)
174
    {
175 8
        $id = $this->searchIdByTypeReference($type, $reference);
176
177 7
        return null !== $id ? $this->getById($id) : null;
178
    }
179
180
    /**
181
     * @return array
182
     */
183 3
    public function getPipelineIds()
184
    {
185 3
        return array_keys($this->pipelines);
186
    }
187
188
    /**
189
     * @throws InvalidArgumentException
190
     * @throws ParseException
191
     * @return array|Pipeline[]
192
     */
193 2
    public function getPipelines()
194
    {
195 2
        $pipelines = array();
196
197 2
        foreach ($this->getPipelineIds() as $id) {
198 2
            if (!$this->isIdValid($id)) {
199 1
                ParseException::__(sprintf("invalid pipeline id '%s'", $id));
200
            }
201 1
            $pipelines[$id] = $this->getById($id);
202
        }
203
204 1
        return $pipelines;
205
    }
206
207
    /**
208
     * @param string $id
209
     * @throws InvalidArgumentException
210
     * @throws ParseException
211
     * @return null|Pipeline
212
     */
213 11
    public function getById($id)
214
    {
215 11
        if (!$this->isIdValid($id)) {
216 1
            throw new InvalidArgumentException(sprintf("Invalid id '%s'", $id));
217
        }
218
219 10
        if (!isset($this->pipelines[$id])) {
220 2
            return null;
221
        }
222
223 8
        $ref = $this->pipelines[$id];
224 8
        if ($ref[2] instanceof Pipeline) {
225 2
            return $ref[2];
226
        }
227
228
        // bind to instance if yet an array
229 8
        if (!is_array($ref[2])) {
230 1
            ParseException::__(sprintf('%s: named pipeline required', $id));
231
        }
232 7
        $pipeline = new Pipeline($this, $ref[2]);
233 7
        $ref[2] = $pipeline;
234
235 7
        return $pipeline;
236
    }
237
238 2
    public function getIdFrom(Pipeline $pipeline)
239
    {
240 2
        foreach ($this->pipelines as $id => $reference) {
241 2
            if ($pipeline === $reference[2]) {
242 2
                return $id;
243
            }
244
        }
245
246 1
        return null;
247
    }
248
249 12
    private function isIdValid($id)
250
    {
251 12
        return (bool)preg_match('~^(default|(' . implode('|', self::$sections) . ')/[^\x00-\x1F\x7F-\xFF]*)$~', $id);
252
    }
253
254
    /**
255
     * @param $type
256
     * @param $reference
257
     * @throws \UnexpectedValueException
258
     * @throws InvalidArgumentException
259
     * @return null|string
260
     */
261 5
    private function searchIdByTypeReference($type, $reference)
262
    {
263 5
        $this->validateType($type);
264
265 4
        if (!isset($this->array['pipelines'][$type])) {
266 2
            return $this->getIdDefault();
267
        }
268 3
        $array = &$this->array['pipelines'][$type];
269
270
        # check for direct (non-pattern) match
271 3
        if (isset($array[$reference])) {
272 2
            return "${type}/${reference}";
273
        }
274
275
        # get entry with largest pattern to match
276 2
        $patterns = array_keys($array);
277 2
        unset($array);
278
279 2
        $match = null;
280 2
        foreach ($patterns as $pattern) {
281 2
            $result = BbplMatch::match($pattern, $reference);
282 2
            if ($result and (null === $match or strlen($pattern) > strlen($match))) {
0 ignored issues
show
Bug introduced by
$match of type void is incompatible with the type string expected by parameter $string of strlen(). ( Ignorable by Annotation )

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

282
            if ($result and (null === $match or strlen($pattern) > strlen(/** @scrutinizer ignore-type */ $match))) {
Loading history...
283 2
                $match = $pattern;
284
            }
285
        }
286 2
        if (null !== $match) {
287 1
            return "${type}/${match}";
288
        }
289
290 1
        return $this->getIdDefault();
291
    }
292
293
    /**
294
     * @param array $array
295
     * @throws \Ktomk\Pipelines\File\ParseException
296
     * @return array
297
     */
298
    private function parsePipelineReferences(array &$array)
299
    {
300
        // quick validation: pipeline sections
301 20
        $sections = self::$sections;
302 20
        $count = 0;
303 20
        foreach ($sections as $section) {
304 20
            if (isset($array[$section])) {
305 20
                $count++;
306
            }
307
        }
308 20
        if (!$count && !isset($array['default'])) {
309 1
            $middle = implode(', ', array_slice($sections, 0, -1));
310 1
            ParseException::__("'pipelines' requires at least a default, ${middle} or custom section");
311
        }
312
313 19
        $references = array();
314
315 19
        $section = 'default';
316 19
        if (isset($array[$section])) {
317 12
            if (!is_array($array[$section])) {
318 1
                ParseException::__("'${section}' requires a list of steps");
319
            }
320 11
            $references[$section] = array(
321 11
                $section,
322
                null,
323 11
                &$array[$section],
324
            );
325
        }
326
327 18
        foreach ($array as $section => $refs) {
328 18
            if (!in_array($section, $sections, true)) {
329 11
                continue;
330
            }
331 14
            if (!is_array($refs)) {
332 1
                ParseException::__("'${section}' requires a list");
333
            }
334 13
            foreach ($refs as $pattern => $pipeline) {
335 11
                $references["${section}/${pattern}"] = array(
336 11
                    $section,
337 11
                    $pattern,
338 13
                    &$array[$section][$pattern],
339
                );
340
            }
341
        }
342
343 17
        return $references;
344
    }
345
346
    /**
347
     * @param $type
348
     * @throws InvalidArgumentException
349
     */
350
    private function validateType($type)
351
    {
352 5
        $scopes = array_slice(self::$sections, 0, 4);
353 5
        if (!in_array($type, $scopes, true)) {
354 1
            throw new InvalidArgumentException(sprintf("Invalid type '%s'", $type));
355
        }
356 4
    }
357
}
358