Passed
Push — test ( 1f6f72...276f1a )
by Tom
02:41
created

File::validateType()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 2
rs 10
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
     * @param string $path
38
     *
39
     * @throws ParseException
40
     *
41
     * @return File
42
     */
43 16
    public static function createFromFile($path)
44
    {
45 16
        $result = Yaml::file($path);
46 16
        if (null === $result) {
47 1
            throw new ParseException(sprintf('YAML error: %s; verify the file contains valid YAML', $path));
48
        }
49
50 15
        return new self($result);
51
    }
52
53
    /**
54
     * File constructor.
55
     *
56
     * @param array $array
57
     *
58
     * @throws ParseException
59
     */
60 31
    public function __construct(array $array)
61
    {
62
        // quick validation: pipelines require
63 31
        if (!isset($array['pipelines']) || !is_array($array['pipelines'])) {
64 1
            throw new ParseException("Missing required property 'pipelines'");
65
        }
66
67
        // quick validation: image name
68 30
        Image::validate($array);
69
70 28
        $this->pipelines = $this->parsePipelineReferences($array['pipelines']);
71
72 25
        $this->array = $array;
73 25
    }
74
75
    /**
76
     * @throws ParseException
77
     *
78
     * @return Image
79
     */
80 3
    public function getImage()
81
    {
82 3
        $imageData = isset($this->array['image'])
83 1
            ? $this->array['image']
84 3
            : self::DEFAULT_IMAGE;
85
86 3
        return new Image($imageData);
87
    }
88
89
    /**
90
     * @return array|int
91
     */
92 2
    public function getClone()
93
    {
94 2
        return isset($this->array['clone'])
95 1
            ? $this->array['clone']
96 2
            : self::DEFAULT_CLONE;
97
    }
98
99
    /**
100
     * @throws InvalidArgumentException
101
     *
102
     * @return null|Pipeline
103
     */
104 4
    public function getDefault()
105
    {
106 4
        return $this->getById('default');
107
    }
108
109
    /**
110
     * returns the id of the default pipeline in file or null if there is none
111
     *
112
     * @return null|string
113
     */
114 2
    public function getIdDefault()
115
    {
116 2
        $id = 'default';
117
118 2
        if (!isset($this->pipelines[$id])) {
119 1
            return null;
120
        }
121
122 1
        return $id;
123
    }
124
125
    /**
126
     * Searches the pipeline that matches the reference
127
     *
128
     * @param Reference $reference
129
     *
130
     * @throws \UnexpectedValueException
131
     * @throws InvalidArgumentException
132
     *
133
     * @return null|string id if found, null otherwise
134
     */
135 1
    public function searchIdByReference(Reference $reference)
136
    {
137 1
        if (null === $reference->getType()) {
138 1
            return $this->getIdDefault();
139
        }
140
141 1
        return $this->searchIdByTypeReference(
142 1
            $reference->getPipelinesType(),
143 1
            $reference->getName()
144
        );
145
    }
146
147
    /**
148
     * Searches a reference
149
     *
150
     * @param Reference $reference
151
     *
152
     * @throws ParseException
153
     * @throws \UnexpectedValueException
154
     * @throws InvalidArgumentException
155
     *
156
     * @return null|Pipeline
157
     */
158 7
    public function searchReference(Reference $reference)
159
    {
160 7
        if (null === $type = $reference->getPipelinesType()) {
161 2
            return $this->getDefault();
162
        }
163
164 5
        return $this->searchTypeReference($type, $reference->getName());
165
    }
166
167
    /**
168
     * Searches a reference within type, returns found one, if
169
     * none is found, the default pipeline or null if there is
170
     * no default pipeline.
171
     *
172
     * @param string $type of pipeline, can be branches, tags or bookmarks
173
     * @param null|string $reference
174
     *
175
     * @throws ParseException
176
     * @throws \UnexpectedValueException
177
     * @throws InvalidArgumentException
178
     *
179
     * @return null|Pipeline
180
     */
181 8
    public function searchTypeReference($type, $reference)
182
    {
183 8
        $id = $this->searchIdByTypeReference($type, $reference);
184
185 7
        return null !== $id ? $this->getById($id) : null;
186
    }
187
188
    /**
189
     * @return array
190
     */
191 3
    public function getPipelineIds()
192
    {
193 3
        return array_keys($this->pipelines);
194
    }
195
196
    /**
197
     * @throws InvalidArgumentException
198
     * @throws ParseException
199
     *
200
     * @return array|Pipeline[]
201
     */
202 2
    public function getPipelines()
203
    {
204 2
        $pipelines = array();
205
206 2
        foreach ($this->getPipelineIds() as $id) {
207 2
            if (!References::isValidId($id)) {
208 1
                throw new ParseException(sprintf("invalid pipeline id '%s'", $id));
209
            }
210 1
            $pipelines[$id] = $this->getById($id);
211
        }
212
213 1
        return $pipelines;
214
    }
215
216
    /**
217
     * @param string $id
218
     *
219
     * @throws InvalidArgumentException
220
     * @throws ParseException
221
     *
222
     * @return null|Pipeline
223
     */
224 12
    public function getById($id)
225
    {
226 12
        if (!References::isValidId($id)) {
227 1
            throw new InvalidArgumentException(sprintf("Invalid id '%s'", $id));
228
        }
229
230 11
        if (!isset($this->pipelines[$id])) {
231 2
            return null;
232
        }
233
234 9
        $ref = $this->pipelines[$id];
235 9
        if ($ref[2] instanceof Pipeline) {
236 2
            return $ref[2];
237
        }
238
239
        // bind to instance if yet an array
240 9
        if (!is_array($ref[2])) {
241 1
            throw new ParseException(sprintf('%s: named pipeline required', $id));
242
        }
243 8
        $pipeline = new Pipeline($this, $ref[2]);
244 8
        $ref[2] = $pipeline;
245
246 8
        return $pipeline;
247
    }
248
249
    /**
250
     * get id of a pipeline
251
     *
252
     * @param Pipeline $pipeline
253
     *
254
     * @return null|string
255
     */
256 2
    public function getIdFrom(Pipeline $pipeline)
257
    {
258 2
        foreach ($this->pipelines as $id => $reference) {
259 2
            if ($pipeline === $reference[2]) {
260 1
                return $id;
261
            }
262
        }
263
264 1
        return null;
265
    }
266
267
    /**
268
     * @param null|string $type
269
     * @param null|string $reference
270
     *
271
     * @throws \UnexpectedValueException
272
     * @throws InvalidArgumentException
273
     *
274
     * @return null|string
275
     */
276 5
    private function searchIdByTypeReference($type, $reference)
277
    {
278 5
        if (!References::isPatternSection($type)) {
279 1
            throw new InvalidArgumentException(sprintf('Invalid type %s', var_export($type, true)));
280
        }
281
282 4
        if (!isset($this->array['pipelines'][$type])) {
283 2
            return $this->getIdDefault();
284
        }
285 3
        $array = &$this->array['pipelines'][$type];
286
287
        # check for direct (non-pattern) match
288 3
        if (isset($array[$reference])) {
289 2
            return "${type}/${reference}";
290
        }
291
292
        # get entry with largest pattern to match
293 2
        $patterns = array_keys($array);
294 2
        unset($array);
295
296 2
        $match = '';
297 2
        foreach ($patterns as $pattern) {
298 2
            $pattern = (string)$pattern;
299 2
            $result = Glob::match($pattern, $reference);
300 2
            if ($result && (strlen($pattern) > strlen($match))) {
301 1
                $match = $pattern;
302
            }
303
        }
304 2
        if ('' !== $match) {
305 1
            return "${type}/${match}";
306
        }
307
308 1
        return $this->getIdDefault();
309
    }
310
311
    /**
312
     * Index all pipelines as references array map
313
     *
314
     * @param array $array
315
     *
316
     * @throws ParseException
317
     *
318
     * @return array
319
     */
320
    private function parsePipelineReferences(array &$array)
321
    {
322
        // quick validation: pipeline sections
323 28
        $sections = References::getSections();
324 28
        $count = 0;
325 28
        foreach ($sections as $section) {
326 28
            if (isset($array[$section])) {
327 21
                $count++;
328
            }
329
        }
330 28
        if (!$count && !isset($array['default'])) {
331 1
            $middle = implode(', ', array_slice($sections, 0, -1));
332
333 1
            throw new ParseException("'pipelines' requires at least a default, ${middle} or custom section");
334
        }
335
336 27
        $references = array();
337
338
        // default pipeline
339 27
        $section = 'default';
340 27
        if (isset($array[$section])) {
341 15
            if (!is_array($array[$section])) {
342 1
                throw new ParseException("'${section}' requires a list of steps");
343
            }
344 14
            $references[$section] = array(
345 14
                $section,
346
                null,
347 14
                &$array[$section],
348
            );
349
        }
350
351
        // section pipelines
352 26
        foreach ($array as $section => $refs) {
353 26
            if (!in_array($section, $sections, true)) {
354 14
                continue; // not a section for references
355
            }
356 21
            if (!is_array($refs)) {
357 1
                throw new ParseException("'${section}' requires a list");
358
            }
359 20
            foreach ($refs as $pattern => $pipeline) {
360 18
                $references["${section}/${pattern}"] = array(
361 18
                    (string)$section,
362 18
                    (string)$pattern,
363 18
                    &$array[$section][$pattern],
364
                );
365
            }
366
        }
367
368 25
        return $references;
369
    }
370
}
371