Passed
Push — test ( 4fb8ad...c1378c )
by Tom
03:03
created

Pipelines::referencesDefault()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 3
nop 1
dl 0
loc 21
ccs 10
cts 10
cp 1
crap 3
rs 9.9
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
11
/**
12
 * Class Pipelines
13
 *
14
 * @package Ktomk\Pipelines\File
15
 */
16
class Pipelines
17
{
18
    /**
19
     * @var array
20
     */
21
    private $array;
22
    /**
23
     * @var null|File
24
     */
25
    private $file;
26
27
    /**
28
     * @var array
29
     */
30
    private $pipelines;
31
32 18
    public function __construct(array $array, File $file = null)
33
    {
34 18
        $this->file = $file;
35
36 18
        $this->pipelines = $this->parsePipelineReferences($array);
37
38 14
        $this->array = $array;
39 14
    }
40
41
    /**
42
     * @return null|Pipeline
43
     */
44 2
    public function getDefault()
45
    {
46 2
        return $this->getById('default');
47
    }
48
49
    /**
50
     * returns the id of the default pipeline in file or null if there is none
51
     *
52
     * @return null|string
53
     */
54 2
    public function getIdDefault()
55
    {
56 2
        $id = 'default';
57
58 2
        if (!isset($this->pipelines[$id])) {
59 1
            return null;
60
        }
61
62 1
        return $id;
63
    }
64
65
    /**
66
     * @return array
67
     */
68 3
    public function getPipelineIds()
69
    {
70 3
        return array_keys($this->pipelines);
71
    }
72
73
    /**
74
     * @param string $id
75
     *
76
     * @throws InvalidArgumentException
77
     * @throws ParseException
78
     *
79
     * @return null|Pipeline
80
     */
81 9
    public function getById($id)
82
    {
83 9
        if (!ReferenceTypes::isValidId($id)) {
84 1
            throw new InvalidArgumentException(sprintf("Invalid id '%s'", $id));
85
        }
86
87 8
        if (!isset($this->pipelines[$id])) {
88 1
            return null;
89
        }
90
91 7
        $ref = $this->pipelines[$id];
92 7
        if ($ref[2] instanceof Pipeline) {
93 2
            return $ref[2];
94
        }
95
96
        // bind to instance if yet an array
97 7
        if (!is_array($ref[2])) {
98 1
            throw new ParseException(sprintf('%s: named pipeline required', $id));
99
        }
100 6
        $pipeline = new Pipeline($this->file, $ref[2]);
0 ignored issues
show
Bug introduced by
It seems like $this->file can also be of type null; however, parameter $file of Ktomk\Pipelines\File\Pipeline::__construct() does only seem to accept Ktomk\Pipelines\File\File, 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

100
        $pipeline = new Pipeline(/** @scrutinizer ignore-type */ $this->file, $ref[2]);
Loading history...
101 6
        $ref[2] = $pipeline;
102
103 6
        return $pipeline;
104
    }
105
106
    /**
107
     * get id of a pipeline
108
     *
109
     * @param Pipeline $pipeline
110
     *
111
     * @return null|string
112
     */
113 2
    public function getId(Pipeline $pipeline)
114
    {
115 2
        foreach ($this->pipelines as $id => $reference) {
116 2
            if ($pipeline === $reference[2]) {
117 1
                return $id;
118
            }
119
        }
120
121 1
        return null;
122
    }
123
124
    /**
125
     * Searches a reference
126
     *
127
     * @param Reference $reference
128
     *
129
     * @throws ParseException
130
     * @throws \UnexpectedValueException
131
     * @throws InvalidArgumentException
132
     *
133
     * @return null|Pipeline
134
     */
135 7
    public function searchReference(Reference $reference)
136
    {
137 7
        if (null === $type = $reference->getPipelinesType()) {
138 2
            return $this->getDefault();
139
        }
140
141 5
        return $this->searchTypeReference($type, $reference->getName());
142
    }
143
144
    /**
145
     * Searches a reference within type, returns found one, if
146
     * none is found, the default pipeline or null if there is
147
     * no default pipeline.
148
     *
149
     * @param string $type of pipeline, can be branches, tags or bookmarks
150
     * @param null|string $reference
151
     *
152
     * @throws ParseException
153
     * @throws \UnexpectedValueException
154
     * @throws InvalidArgumentException
155
     *
156
     * @return null|Pipeline
157
     */
158 8
    public function searchTypeReference($type, $reference)
159
    {
160 8
        $id = $this->searchIdByTypeReference($type, $reference);
161
162 7
        return null !== $id ? $this->getById($id) : null;
163
    }
164
165
    /**
166
     * Searches the pipeline that matches the reference
167
     *
168
     * @param Reference $reference
169
     *
170
     * @throws \UnexpectedValueException
171
     * @throws InvalidArgumentException
172
     *
173
     * @return null|string id if found, null otherwise
174
     */
175 1
    public function searchIdByReference(Reference $reference)
176
    {
177 1
        if (null === $reference->getType()) {
178 1
            return $this->getIdDefault();
179
        }
180
181 1
        return $this->searchIdByTypeReference(
182 1
            $reference->getPipelinesType(),
183 1
            $reference->getName()
184
        );
185
    }
186
187
    /**
188
     * @return File
189
     */
190 1
    public function getFile()
191
    {
192 1
        if ($this->file instanceof File) {
193 1
            return $this->file;
194
        }
195
196 1
        throw new \BadMethodCallException('Unassociated node');
197
    }
198
199
    /**
200
     * @throws InvalidArgumentException
201
     * @throws ParseException
202
     *
203
     * @return array|Pipeline[]
204
     */
205 2
    public function getPipelines()
206
    {
207 2
        $pipelines = array();
208
209 2
        foreach ($this->getPipelineIds() as $id) {
210 2
            if (!ReferenceTypes::isValidId($id)) {
211 1
                throw new ParseException(sprintf("invalid pipeline id '%s'", $id));
212
            }
213 1
            $pipelines[$id] = $this->getById($id);
214
        }
215
216 1
        return $pipelines;
217
    }
218
219
    /**
220
     * quick validation of pipeline sections (default pipeline + sections)
221
     *
222
     * there must be at least one pipeline in the file
223
     *
224
     * NOTE: the check is incomplete, as it assumes any section contains at
225
     *       least one pipeline which is unchecked
226
     *
227
     * @param array $array
228
     */
229 18
    private function parseValidatePipelines(array $array)
230
    {
231 18
        $sections = ReferenceTypes::getSections();
232 18
        $count = 0;
233 18
        foreach ($sections as $section) {
234 18
            if (isset($array[$section])) {
235 12
                $count++;
236
            }
237
        }
238 18
        if (!$count && !isset($array['default'])) {
239 2
            $middle = implode(', ', array_slice($sections, 0, -1));
240
241 2
            throw new ParseException("'pipelines' requires at least a default, ${middle} or custom section");
242
        }
243
    }
244
245
    /**
246
     * Index all pipelines as references array map by id
247
     *
248
     * a reference is array($section (or default), $pattern (or null for default), &$arrayFileParseData)
249
     * references are keyed by their pipeline id
250
     *
251
     * @param array $array
252
     *
253
     * @throws ParseException
254
     *
255
     * @return array
256
     */
257
    private function parsePipelineReferences(array &$array)
258
    {
259 18
        $this->parseValidatePipelines($array);
260
261 16
        $references = $this->referencesDefault($array);
262
263 15
        $references = $this->referencesAddSections($references, $array);
264
265 14
        return $references;
266
    }
267
268
    /**
269
     * create references by default pipeline
270
     *
271
     * @param array $array
272
     *
273
     * @return array
274
     */
275
    private function referencesDefault(array &$array)
276
    {
277 16
        $references = array();
278
279 16
        $default = 'default';
280
281 16
        if (!isset($array[$default])) {
282 5
            return $references;
283
        }
284
285 11
        if (!is_array($array[$default])) {
286 1
            throw new ParseException("'${default}' requires a list of steps");
287
        }
288
289 10
        $references[$default] = array(
290 10
            $default,
291
            null,
292 10
            &$array[$default],
293
        );
294
295 10
        return $references;
296
    }
297
298
    /**
299
     * add section pipelines to references
300
     *
301
     * @param array $references
302
     * @param array $array
303
     *
304
     * @return array
305
     */
306
    private function referencesAddSections(array $references, array &$array)
307
    {
308
        // reference section pipelines
309 15
        $sections = ReferenceTypes::getSections();
310
311 15
        foreach ($array as $section => $refs) {
312 15
            if (!in_array($section, $sections, true)) {
313 10
                continue; // not a section for references
314
            }
315 12
            if (!is_array($refs)) {
316 1
                throw new ParseException("'${section}' requires a list");
317
            }
318 11
            foreach ($refs as $pattern => $pipeline) {
319 11
                $references["${section}/${pattern}"] = array(
320 11
                    (string)$section,
321 11
                    (string)$pattern,
322 11
                    &$array[$section][$pattern],
323
                );
324
            }
325
        }
326
327 14
        return $references;
328
    }
329
330
    /**
331
     * @param null|string $type
332
     * @param null|string $reference
333
     *
334
     * @throws \UnexpectedValueException
335
     * @throws InvalidArgumentException
336
     *
337
     * @return null|string
338
     */
339
    private function searchIdByTypeReference($type, $reference)
340
    {
341 5
        if (!ReferenceTypes::isPatternSection($type)) {
342 1
            throw new InvalidArgumentException(sprintf('Invalid type %s', var_export($type, true)));
343
        }
344
345 4
        list($resolve, $result) = $this->searchIdNonPatternMatch($type, $reference);
346 4
        if ($resolve) {
347 4
            return  $result;
348
        }
349
350 2
        list($resolve, $result) = $this->searchIdPattern($type, $reference);
351
352
        # fall-back to default pipeline on no match
353 2
        return $resolve ? $result : $this->getIdDefault();
354
    }
355
356
    /**
357
     * @param string|null $section
358
     * @param $reference
359
     *
360
     * @return array
361
     */
362
    private function searchIdNonPatternMatch($section, $reference)
363
    {
364
        # section is n/a, fall back to default pipeline
365 4
        if (!isset($this->array[$section])) {
366 2
            return array(true, $this->getIdDefault());
367
        }
368
369
        # check for direct (non-pattern) match
370 3
        if (isset($this->array[$section][$reference])) {
371 2
            return array(true, "${section}/${reference}");
372
        }
373
374 2
        return array(false, null);
375
    }
376
377
    /**
378
     * get entry with largest pattern to match
379
     *
380
     * @param string $section
381
     * @param string $reference
382
     *
383
     * @return array
384
     */
385
    private function searchIdPattern($section, $reference)
386
    {
387 2
        $patterns = array_keys($this->array[$section]);
388
389 2
        $match = '';
390 2
        foreach ($patterns as $pattern) {
391 2
            $pattern = (string)$pattern;
392 2
            $result = Glob::match($pattern, $reference);
393 2
            if ($result && (strlen($pattern) > strlen($match))) {
394 1
                $match = $pattern;
395
            }
396
        }
397
398 2
        return array('' !== $match, "${section}/${match}");
399
    }
400
}
401