Passed
Push — test ( 3544e7...181b61 )
by Tom
02:31
created

File::getById()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

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

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