Completed
Push — master ( d52b96...e2f758 )
by Michiel
11:35
created

AppendTask::setFiltering()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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