Passed
Push — main ( 95b369...5c384e )
by Michiel
17:35 queued 12s
created

IniFileTask::setHaltonerror()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Task\Ext;
22
23
use Phing\Exception\BuildException;
24
use Phing\Project;
25
use Phing\Task;
26
use Phing\Util\StringHelper;
27
use RuntimeException;
28
use Throwable;
29
30
/**
31
 * InifileTask
32
 *
33
 * @author   Ken Guest <[email protected]>
34
 */
35
class IniFileTask extends Task
36
{
37
    /**
38
     * Source file
39
     *
40
     * @var string|null
41
     */
42
    protected $source;
43
44
    /**
45
     * Dest file
46
     *
47
     * @var string|null
48
     */
49
    protected $dest;
50
51
    /**
52
     * Whether to halt phing on error.
53
     *
54
     * @var bool
55
     */
56
    protected $haltonerror = false;
57
58
    /**
59
     * Gets
60
     *
61
     * @var array
62
     */
63
    protected $gets = [];
64
65
    /**
66
     * Sets
67
     *
68
     * @var array
69
     */
70
    protected $sets = [];
71
72
    /**
73
     * Removals
74
     *
75
     * @var array
76
     */
77
    protected $removals = [];
78
79
    /**
80
     * IniFileConfig instance
81
     *
82
     * @var IniFileConfig
83
     */
84
    protected $ini;
85
86
    /**
87
     * Taskname for logger
88
     *
89
     * @var string
90
     */
91
    protected $taskName = 'IniFile';
92
93
    /**
94
     * Verbose
95
     *
96
     * @var bool
97
     */
98
    protected $verbose = false;
99
100
    /**
101
     * Check file to be read from
102
     *
103
     * @param string|null $readFile Filename
104
     *
105
     * @return bool
106
     */
107 8
    public function checkReadFile(?string $readFile): bool
108
    {
109 8
        if (null === $readFile) {
110
            return false;
111
        }
112 8
        if (!file_exists($readFile)) {
113 3
            $msg = "$readFile does not exist.";
114 3
            if ($this->haltonerror) {
115 3
                throw new BuildException($msg);
116
            }
117
            $this->log($msg, Project::MSG_ERR);
118
            return false;
119
        }
120 5
        if (!is_readable($readFile)) {
121
            $msg = "$readFile is not readable.";
122
            if ($this->haltonerror) {
123
                throw new BuildException($msg);
124
            }
125
            $this->log($msg, Project::MSG_ERR);
126
            return false;
127
        }
128 5
        $this->ini->read($readFile);
129 5
        $this->log("Read from $readFile");
130 5
        return true;
131
    }
132
133
    /**
134
     * Check file to write to
135
     *
136
     * @param string $writeFile Filename
137
     *
138
     * @return bool
139
     */
140 5
    public function checkWriteFile(string $writeFile): bool
141
    {
142 5
        if (file_exists($writeFile) && !is_writable($writeFile)) {
143
            $msg = "$writeFile is not writable";
144
            if ($this->haltonerror) {
145
                throw new BuildException($msg);
146
            }
147
            $this->log($msg, Project::MSG_ERR);
148
            return false;
149
        }
150 5
        return true;
151
    }
152
153
    /**
154
     * The main entry point method.
155
     */
156 9
    public function main(): void
157
    {
158 9
        $this->ini = new IniFileConfig();
159 9
        $readFile = null;
160 9
        $writeFile = null;
161
162 9
        if (null !== $this->source && null === $this->dest) {
163 2
            $readFile = $this->source;
164 7
        } elseif (null !== $this->dest && null === $this->source) {
165 1
            $readFile = $this->dest;
166
        } else {
167 6
            $readFile = $this->source;
168
        }
169
170 9
        if (null !== $this->dest) {
171 6
            $writeFile = $this->dest;
172 3
        } elseif (null !== $this->source) {
173 2
            $writeFile = $this->source;
174
        } else {
175 1
            $writeFile = $this->dest;
176
        }
177
178 9
        if ($readFile === null && $writeFile === null) {
179 1
            $msg = "Neither source nor dest is set";
180 1
            if ($this->haltonerror) {
181 1
                throw new BuildException($msg);
182
            }
183
            $this->log($msg, Project::MSG_ERR);
184
            return;
185
        }
186
187 8
        if (!$this->checkReadFile($readFile)) {
188
            return;
189
        }
190
191 5
        if (!$this->checkWriteFile($writeFile)) {
0 ignored issues
show
Bug introduced by
It seems like $writeFile can also be of type null; however, parameter $writeFile of Phing\Task\Ext\IniFileTask::checkWriteFile() does only seem to accept string, 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

191
        if (!$this->checkWriteFile(/** @scrutinizer ignore-type */ $writeFile)) {
Loading history...
192
            return;
193
        }
194
195 5
        $this->enumerateGets();
196 5
        $this->enumerateSets();
197 5
        $this->enumerateRemoves();
198
199 5
        if (count($this->sets) || count($this->removals)) {
200
            try {
201 4
                $this->ini->write($writeFile);
0 ignored issues
show
Bug introduced by
It seems like $writeFile can also be of type null; however, parameter $file of Phing\Task\Ext\IniFileConfig::write() does only seem to accept string, 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

201
                $this->ini->write(/** @scrutinizer ignore-type */ $writeFile);
Loading history...
202 4
                $this->log("Wrote to $writeFile");
203
            } catch (Throwable $ex) {
204
                $msg = $ex->getMessage();
205
                if ($this->haltonerror) {
206
                    throw new BuildException($msg);
207
                }
208
                $this->log($msg, Project::MSG_ERR);
209
            }
210
        }
211
    }
212
213
    /**
214
     * Work through all Get commands.
215
     *
216
     * @return void
217
     */
218 5
    public function enumerateGets(): void
219
    {
220 5
        foreach ($this->gets as $get) {
221 1
            $outProperty = $get->getOutputProperty();
222 1
            $property = $get->getProperty();
223 1
            $section = $get->getSection();
224 1
            $value = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $value is dead and can be removed.
Loading history...
225
226 1
            if ($property === null) {
227
                throw new BuildException("property must be set");
228
            }
229 1
            if ($outProperty === null) {
230
                throw new BuildException("outputproperty must be set");
231
            }
232 1
            if ($section === null) {
233
                throw new BuildException("section must be set");
234
            }
235
            try {
236 1
                $value = $this->ini->get($section, $property);
237
            } catch (RuntimeException $ex) {
238
                $this->logDebugOrMore(
239
                    sprintf(
240
                        '%s: section = %s; key = %s',
241
                        $ex->getMessage(),
242
                        $section,
243
                        $property
244
                    )
245
                );
246
            } finally {
247 1
                if ($value === '') {
248 1
                    $value = $get->getDefault();
249
                }
250
            }
251
252 1
            $project = $this->getProject();
253 1
            $project->setProperty($outProperty, $value);
254 1
            $this->logDebugOrMore(
255 1
                sprintf(
256 1
                    'Set property %s to value \'%s\' read from key %s in section %s',
257 1
                    $outProperty,
258 1
                    $value,
259 1
                    $property,
260 1
                    $section
261 1
                )
262 1
            );
263
        }
264
    }
265
266
    /**
267
     * Work through all Set commands.
268
     *
269
     * @return void
270
     */
271 5
    public function enumerateSets(): void
272
    {
273 5
        foreach ($this->sets as $set) {
274 2
            $value = $set->getValue();
275 2
            $key = $set->getProperty();
276 2
            $section = $set->getSection();
277 2
            $operation = $set->getOperation();
278 2
            if ($value !== null) {
279
                try {
280 2
                    $this->ini->set($section, $key, $value);
281 2
                    $this->logDebugOrMore("[$section] $key set to $value");
282
                } catch (Throwable $ex) {
283
                    $this->log(
284
                        "Error setting value for section '" . $section .
285
                        "', key '" . $key . "'",
286
                        Project::MSG_ERR
287
                    );
288 2
                    $this->logDebugOrMore($ex->getMessage());
289
                }
290
            } elseif ($operation !== null) {
291
                $v = $this->ini->get($section, $key);
292
                // value might be wrapped in quotes with a semicolon at the end
293
                if (!is_numeric($v)) {
294
                    if (preg_match('/^"(\d*)";?$/', $v, $match)) {
295
                        $v = $match[1];
296
                    } elseif (preg_match("/^'(\d*)';?$/", $v, $match)) {
297
                        $v = $match[1];
298
                    } else {
299
                        $this->log(
300
                            "Value $v is not numeric. Skipping $operation operation."
301
                        );
302
                        continue;
303
                    }
304
                }
305
                if ($operation === '+') {
306
                    ++$v;
307
                } elseif ($operation === '-') {
308
                    --$v;
309
                } elseif (($operation !== '-') && ($operation !== '+')) {
310
                    $msg = "Unrecognised operation $operation";
311
                    if ($this->haltonerror) {
312
                        throw new BuildException($msg);
313
                    }
314
                    $this->log($msg, Project::MSG_ERR);
315
                }
316
                try {
317
                    $this->ini->set($section, $key, $v);
318
                    $this->logDebugOrMore("[$section] $key set to $v");
319
                } catch (Throwable $ex) {
320
                    $this->log(
321
                        "Error setting value for section '" . $section .
322
                        "', key '" . $key . "'"
323
                    );
324
                    $this->logDebugOrMore($ex->getMessage());
325
                }
326
            } else {
327
                $this->log(
328
                    "Set: value and operation are both not set",
329
                    Project::MSG_ERR
330
                );
331
            }
332
        }
333
    }
334
335
    /**
336
     * Work through all Remove commands.
337
     *
338
     * @return void
339
     */
340 5
    public function enumerateRemoves(): void
341
    {
342 5
        foreach ($this->removals as $remove) {
343 2
            $key = $remove->getProperty();
344 2
            $section = $remove->getSection();
345 2
            if ($section === '') {
346
                $this->log(
347
                    "Remove: section must be set",
348
                    Project::MSG_ERR
349
                );
350
                continue;
351
            }
352 2
            $this->ini->remove($section, $key);
353 2
            if (($section !== '') && ($key !== '')) {
354 2
                $this->logDebugOrMore(
355 2
                    "$key in section [$section] has been removed."
356 2
                );
357
            } elseif (($section !== '') && ($key === '')) {
358
                $this->logDebugOrMore("[$section] has been removed.");
359
            }
360
        }
361
    }
362
363
    /**
364
     * Set Source property
365
     *
366
     * @param string $source Name of originating ini file to parse
367
     *
368
     * @return void
369
     */
370 7
    public function setSource(string $source): void
371
    {
372 7
        $this->source = $source;
373
    }
374
375
    /**
376
     * Set Dest property
377
     *
378
     * @param string $dest Destination filename to write ini contents to.
379
     *
380
     * @return void
381
     */
382 6
    public function setDest(string $dest): void
383
    {
384 6
        $this->dest = $dest;
385
    }
386
387
    /**
388
     * Set haltonerror attribute.
389
     *
390
     * @param string $halt 'yes', or '1' to halt.
391
     *
392
     * @return void
393
     */
394 8
    public function setHaltonerror(string $halt): void
395
    {
396 8
        $this->haltonerror = StringHelper::booleanValue($halt);
397
    }
398
399
    /**
400
     * Set verbose attribute.
401
     *
402
     * Screech like a Camaar fishwife...
403
     *
404
     * @param boolean $verbose Verbose?
405
     *
406
     * @return void
407
     */
408 2
    public function setVerbose(bool $verbose): void
409
    {
410 2
        $this->verbose = StringHelper::booleanValue($verbose);
411
    }
412
413
    /**
414
     * Create a Get method
415
     *
416
     * @return IniFileGet
417
     */
418 1
    public function createGet(): IniFileGet
419
    {
420 1
        $get = new IniFileGet();
421 1
        $this->gets[] = $get;
422 1
        return $get;
423
    }
424
425
    /**
426
     * Create a Set method
427
     *
428
     * @return IniFileSet
429
     */
430 2
    public function createSet(): IniFileSet
431
    {
432 2
        $set = new IniFileSet();
433 2
        $this->sets[] = $set;
434 2
        return $set;
435
    }
436
437
    /**
438
     * Create a Remove method
439
     *
440
     * @return IniFileRemove
441
     */
442 2
    public function createRemove(): IniFileRemove
443
    {
444 2
        $remove = new IniFileRemove();
445 2
        $this->removals[] = $remove;
446 2
        return $remove;
447
    }
448
449
    /**
450
     * Log message at Debug level. If verbose prop is set, also log it at normal
451
     *
452
     * @param string $message Message to log
453
     *
454
     * @return bool False if message is only logged at debug level.
455
     */
456 5
    public function logDebugOrMore(string $message): bool
457
    {
458 5
        $this->log($message, Project::MSG_DEBUG);
459 5
        if ($this->verbose) {
460 2
            $this->log($message);
461 2
            return true;
462
        }
463 3
        return false;
464
    }
465
}
466