AppendTask::checkFilename()   B
last analyzed

Complexity

Conditions 9
Paths 8

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 9.2733

Importance

Changes 0
Metric Value
eloc 20
c 0
b 0
f 0
dl 0
loc 33
ccs 17
cts 20
cp 0.85
rs 8.0555
cc 9
nc 8
nop 2
crap 9.2733
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\System;
22
23
use Exception;
24
use Phing\Exception\BuildException;
25
use Phing\Io\File;
26
use Phing\Io\FileReader;
27
use Phing\Io\FileUtils;
28
use Phing\Io\FileWriter;
29
use Phing\Io\IOException;
30
use Phing\Io\LogWriter;
31
use Phing\Io\Reader;
32
use Phing\Io\StringReader;
33
use Phing\Io\Writer;
34
use Phing\Project;
35
use Phing\Task;
36
use Phing\Task\System\Append\TextElement;
37
use Phing\Type\Element\FileListAware;
38
use Phing\Type\Element\FileSetAware;
39
use Phing\Type\Element\FilterChainAware;
40
use Phing\Type\FileSet;
41
use Phing\Type\Path;
42
use Phing\Util\Register;
43
44
/**
45
 *  Appends text, contents of a file or set of files defined by a filelist to a destination file.
46
 *
47
 * <code>
48
 * <append text="And another thing\n" destfile="badthings.log"/>
49
 * </code>
50
 * OR
51
 * <code>
52
 * <append file="header.html" destfile="fullpage.html"/>
53
 * <append file="body.html" destfile="fullpage.html"/>
54
 * <append file="footer.html" destfile="fullpage.html"/>
55
 * </code>
56
 * OR
57
 * <code>
58
 * <append destfile="${process.outputfile}">
59
 *    <filterchain>
60
 *        <xsltfilter style="${process.stylesheet}">
61
 *            <param name="mode" expression="${process.xslt.mode}"/>
62
 *            <param name="file_name" expression="%{task.append.current_file.basename}"/> <!-- Example of using a RegisterSlot variable -->
63
 *        </xsltfilter>
64
 *    </filterchain>
65
 *     <filelist dir="book/" listfile="book/PhingGuide.book"/>
66
 * </append>
67
 * </code>
68
 */
69
class AppendTask extends Task
70
{
71
    use FileListAware;
72
    use FileSetAware;
73
    use FilterChainAware;
74
75
    /**
76
     * Append stuff to this file.
77
     */
78
    private $to;
79
80
    /**
81
     * Explicit file to append.
82
     */
83
    private $file;
84
85
    /**
86
     * Text to append. (cannot be used in conjunction w/ files or filesets).
87
     */
88
    private $text;
89
90
    private $filtering = true;
91
92
    /**
93
     * @var TextElement
94
     */
95
    private $header;
96
97
    /**
98
     * @var TextElement
99
     */
100
    private $footer;
101
102
    private $append = true;
103
104
    private $fixLastLine = false;
105
106
    private $overwrite = true;
107
108
    private $eolString;
109
110
    private $skipSanitize = false;
111
112
    public function setFiltering(bool $filtering): void
113
    {
114
        $this->filtering = $filtering;
115
    }
116
117
    /**
118
     * @param bool $overwrite
119
     */
120 1
    public function setOverwrite($overwrite): void
121
    {
122 1
        $this->overwrite = $overwrite;
123
    }
124
125
    /**
126
     * The more conventional naming for method to set destination file.
127
     */
128 12
    public function setDestFile(File $f): void
129
    {
130 12
        $this->to = $f;
131
    }
132
133
    /**
134
     * Sets the behavior when the destination exists. If set to
135
     * <code>true</code> the task will append the stream data an
136
     * {@link Appendable} resource; otherwise existing content will be
137
     * overwritten. Defaults to <code>false</code>.
138
     *
139
     * @param bool $append if true append output
140
     */
141 1
    public function setAppend(bool $append): void
142
    {
143 1
        $this->append = $append;
144
    }
145
146
    /**
147
     * Specify the end of line to find and to add if
148
     * not present at end of each input file. This attribute
149
     * is used in conjunction with fixlastline.
150
     *
151
     * @param string $crlf the type of new line to add -
152
     *                     cr, mac, lf, unix, crlf, or dos
153
     */
154 1
    public function setEol($crlf): void
155
    {
156 1
        $s = $crlf;
157 1
        if ('cr' === $s || 'mac' === $s) {
158 1
            $this->eolString = "\r";
159
        } elseif ('lf' === $s || 'unix' === $s) {
160
            $this->eolString = "\n";
161
        } elseif ('crlf' === $s || 'dos' === $s) {
162
            $this->eolString = "\r\n";
163
        } else {
164
            $this->eolString = $this->getProject()->getProperty('line.separator');
165
        }
166
    }
167
168
    /**
169
     * Sets specific file to append.
170
     */
171
    public function setFile(File $f): void
172
    {
173
        $this->file = $f;
174
    }
175
176 9
    public function createPath(): Path
177
    {
178 9
        $path = new Path($this->getProject());
179 9
        $this->filesets[] = $path;
180
181 9
        return $path;
182
    }
183
184
    /**
185
     * Sets text to append.  (cannot be used in conjunction w/ files or filesets).
186
     */
187 1
    public function setText(string $txt): void
188
    {
189 1
        $this->text = $txt;
190
    }
191
192
    /**
193
     * Sets text to append. Supports CDATA.
194
     */
195 11
    public function addText(string $txt): void
196
    {
197 11
        $this->text .= $txt;
198
    }
199
200 2
    public function addHeader(TextElement $headerToAdd): void
201
    {
202 2
        $this->header = $headerToAdd;
203
    }
204
205 1
    public function addFooter(TextElement $footerToAdd): void
206
    {
207 1
        $this->footer = $footerToAdd;
208
    }
209
210
    /**
211
     * Append line.separator to files that do not end
212
     * with a line.separator, default false.
213
     *
214
     * @param bool $fixLastLine if true make sure each input file has
215
     *                          new line on the concatenated stream
216
     */
217 2
    public function setFixLastLine(bool $fixLastLine): void
218
    {
219 2
        $this->fixLastLine = $fixLastLine;
220
    }
221
222 1
    public function setSkipSanitize(bool $skipSanitize): void
223
    {
224 1
        $this->skipSanitize = $skipSanitize;
225
    }
226
227
    /**
228
     * Append the file(s).
229
     *
230
     * {@inheritdoc}
231
     */
232 16
    public function main()
233
    {
234 16
        $this->validate();
235
236
        try {
237 15
            if (null !== $this->to) {
238
                // create a file writer to append to "to" file.
239 12
                $writer = new FileWriter($this->to, $this->append);
240
            } else {
241 6
                $writer = new LogWriter($this);
242
            }
243
244 14
            if (null !== $this->text) {
245
                // simply append the text
246 11
                if ($this->to instanceof File) {
247 9
                    $this->log('Appending string to ' . $this->to->getPath());
248
                }
249
250 11
                $text = $this->text;
251 11
                if ($this->filtering) {
252 11
                    $fr = $this->getFilteredReader(new StringReader($text));
253 11
                    $text = $fr->read();
254
                }
255
256 11
                $text = $this->appendHeader($text);
257 11
                $text = $this->appendFooter($text);
258 11
                $writer->write($text);
259
            } else {
260
                // append explicitly-specified file
261 10
                if (null !== $this->file) {
262
                    try {
263
                        $this->appendFile($writer, $this->file);
264
                    } catch (Exception $ioe) {
265
                        $this->log(
266
                            'Unable to append contents of file ' . $this->file->getAbsolutePath() . ': ' . $ioe->getMessage(),
267
                            Project::MSG_WARN
268
                        );
269
                    }
270
                }
271
272
                // append any files in filesets
273 10
                foreach ($this->filesets as $fs) {
274
                    try {
275 10
                        if ($fs instanceof Path) {
276 9
                            $files = $fs->listPaths();
277 9
                            $this->appendFiles($writer, $files);
278 1
                        } elseif ($fs instanceof FileSet) {
279 1
                            $files = $fs->getDirectoryScanner($this->project)->getIncludedFiles();
280 8
                            $this->appendFiles($writer, $files, $fs->getDir($this->project));
281
                        }
282 2
                    } catch (BuildException $be) {
283 2
                        if (false === strpos($be->getMessage(), 'is the same as the output file')) {
284 1
                            $this->log($be->getMessage(), Project::MSG_WARN);
285
                        } else {
286 2
                            throw new BuildException($be->getMessage());
287
                        }
288
                    } catch (IOException $ioe) {
289
                        throw new BuildException($ioe);
290
                    }
291
                }
292
293 13
                foreach ($this->filelists as $list) {
294
                    $dir = $list->getDir($this->project);
295
                    $files = $list->getFiles($this->project);
296
                    foreach ($files as $file) {
297
                        $this->appendFile($writer, new File($dir, $file));
298
                    }
299
                }
300
            }
301 2
        } catch (Exception $e) {
302 2
            throw new BuildException($e);
303
        }
304
305 13
        $writer->close();
306
    }
307
308 11
    private function appendHeader($string): string
309
    {
310 11
        $result = $string;
311 11
        if (null !== $this->header) {
312 2
            $header = $this->header->getValue();
313 2
            if ($this->header->filtering) {
314 1
                $fr = $this->getFilteredReader(new StringReader($header));
315 1
                $header = $fr->read();
316
            }
317
318 2
            $result = $header . $string;
319
        }
320
321 11
        return $result;
322
    }
323
324 11
    private function appendFooter($string): string
325
    {
326 11
        $result = $string;
327 11
        if (null !== $this->footer) {
328 1
            $footer = $this->footer->getValue();
329 1
            if ($this->footer->filtering) {
330
                $fr = $this->getFilteredReader(new StringReader($footer));
331
                $footer = $fr->read();
332
            }
333
334 1
            $result = $string . $footer;
335
        }
336
337 11
        return $result;
338
    }
339
340 16
    private function validate(): void
341
    {
342 16
        if (!$this->skipSanitize) {
343 16
            $this->sanitizeText();
344
        }
345
346 16
        if (null === $this->file && null === $this->text && 0 === count($this->filesets) && 0 === count($this->filelists)) {
347 1
            throw new BuildException('You must specify a file, use a filelist/fileset, or specify a text value.');
348
        }
349
350 15
        if (null !== $this->text && (null !== $this->file || count($this->filesets) > 0)) {
351
            throw new BuildException('Cannot use text attribute in conjunction with file or fileset');
352
        }
353
354 15
        if (!$this->eolString) {
355 15
            $this->eolString = $this->getProject()->getProperty('line.separator');
356
        }
357
    }
358
359 16
    private function sanitizeText(): void
360
    {
361 16
        if (null !== $this->text && '' === trim($this->text)) {
362
            $this->text = null;
363
        }
364
    }
365
366 11
    private function getFilteredReader(Reader $r)
367
    {
368 11
        return FileUtils::getChainedReader($r, $this->filterChains, $this->getProject());
369
    }
370
371
    /**
372
     * Append an array of files in a directory.
373
     *
374
     * @param Writer    $writer the FileWriter that is appending to target file
375
     * @param array     $files  array of files to delete; can be of zero length
376
     * @param File|null $dir    directory to work from
377
     */
378 9
    private function appendFiles(Writer $writer, $files, ?File $dir = null): void
379
    {
380 9
        if (!empty($files)) {
381 9
            $this->log(
382 9
                'Attempting to append ' . count(
383 9
                    $files
384 9
                ) . ' files' . (null !== $dir ? ', using basedir ' . $dir->getPath() : '')
385 9
            );
386 9
            $basenameSlot = Register::getSlot('task.append.current_file');
387 9
            $pathSlot = Register::getSlot('task.append.current_file.path');
388 9
            foreach ($files as $file) {
389
                try {
390 9
                    if (!$this->checkFilename($file, $dir)) {
391 1
                        continue;
392
                    }
393
394 7
                    if (null !== $dir) {
395
                        $file = is_string($file) ? new File($dir->getPath(), $file) : $file;
396
                    } else {
397 7
                        $file = is_string($file) ? new File($file) : $file;
398
                    }
399 7
                    $basenameSlot->setValue($file);
400 7
                    $pathSlot->setValue($file->getPath());
401 7
                    $this->appendFile($writer, $file);
402 1
                } catch (IOException $ioe) {
403
                    $this->log(
404
                        'Unable to append contents of file ' . $file . ': ' . $ioe->getMessage(),
405
                        Project::MSG_WARN
406
                    );
407 1
                } catch (\InvalidArgumentException $npe) {
408
                    $this->log(
409
                        'Unable to append contents of file ' . $file . ': ' . $npe->getMessage(),
410
                        Project::MSG_WARN
411
                    );
412
                }
413
            }
414
        }
415
    }
416
417 9
    private function checkFilename($filename, $dir = null): bool
418
    {
419 9
        if (null !== $dir) {
420
            $f = new File($dir, $filename);
421
        } else {
422 9
            $f = new File($filename);
423
        }
424
425 9
        if (!$f->exists()) {
426 1
            $this->log('File ' . (string) $f . ' does not exist.', Project::MSG_ERR);
427
428 1
            return false;
429
        }
430 8
        if (null !== $this->to && $f->equals($this->to)) {
431 1
            throw new BuildException(
432 1
                'Input file "'
433 1
                . $f . '" '
434 1
                . 'is the same as the output file.'
435 1
            );
436
        }
437
438
        if (
439 7
            null !== $this->to
440 7
            && !$this->overwrite
441 7
            && $this->to->exists()
442 7
            && $f->lastModified() > $this->to->lastModified()
443
        ) {
444
            $this->log((string) $this->to . ' is up-to-date.', Project::MSG_VERBOSE);
445
446
            return false;
447
        }
448
449 7
        return true;
450
    }
451
452
    /**
453
     * @throws IOException
454
     */
455 7
    private function appendFile(Writer $writer, File $f): void
456
    {
457 7
        $in = $this->getFilteredReader(new FileReader($f));
458
459 7
        $text = '';
460 7
        while (-1 !== ($buffer = $in->read())) { // -1 indicates EOF
461 7
            $text .= $buffer;
462
        }
463 7
        if ($this->fixLastLine && ("\n" !== $text[strlen($text) - 1] || "\r" !== $text[strlen($text) - 1])) {
464 2
            $text .= $this->eolString;
465
        }
466
467 7
        $text = $this->appendHeader($text);
468 7
        $text = $this->appendFooter($text);
469 7
        $writer->write($text);
470 7
        if ($f instanceof File && $this->to instanceof File) {
471 4
            $this->log('Appending contents of ' . $f->getPath() . ' to ' . $this->to->getPath());
472
        }
473
    }
474
}
475