Ticket #448: PatchTask.php

File PatchTask.php, 5.9 KB (added by Merkas <mk@…>, 2 years ago)
Line 
1<?php
2/**
3 *  Patches a file by applying a 'diff' file to it
4 *
5 *  Requires "patch" to be on the execution path.
6 *
7 *  Based on Apache Ant PatchTask:
8 *
9 *  Licensed to the Apache Software Foundation (ASF) under one or more
10 *  contributor license agreements.  See the NOTICE file distributed with
11 *  this work for additional information regarding copyright ownership.
12 *  The ASF licenses this file to You under the Apache License, Version 2.0
13 *  (the "License"); you may not use this file except in compliance with
14 *  the License.  You may obtain a copy of the License at
15 *
16 *      http://www.apache.org/licenses/LICENSE-2.0
17 *
18 *  Unless required by applicable law or agreed to in writing, software
19 *  distributed under the License is distributed on an "AS IS" BASIS,
20 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 *  See the License for the specific language governing permissions and
22 *  limitations under the License.
23 *
24 * @author Mikhail Krasilnikov <mk@3wstyle>
25 * @version 0.01
26 * @package phing.tasks.ext
27 */
28require_once 'phing/Task.php';
29
30/**
31 * Patches a file by applying a 'diff' file to it
32 *
33 * Requires "patch" to be on the execution path.
34 *
35 * @package phing.tasks.ext
36 */
37class PatchTask extends Task
38{
39    /**
40     * Base command to be executed
41     * @var string
42     */
43    const CMD = 'patch --batch ';
44
45    /**
46     * File to be patched
47     * @var string
48     */
49    private $originalFile;
50
51    /**
52     * The directory to run the patch command in
53     * @var string
54     */
55    private $directory;
56
57    /**
58     * Patch file
59     *
60     * @var string
61     */
62    private $patchFile;
63
64    /**
65     * Value for a "-p" option
66     * @var int
67     */
68    private $strip = 0;
69
70    /**
71     * Command line arguments for patch binary
72     * @var array
73     */
74    private $cmdArgs = array();
75
76    /**
77     * Halt on error return value from patch invocation.
78     * @var bool
79     */
80    private $failOnError = false;
81
82    /**
83     * The file containing the diff output
84     *
85     * Required.
86     * @param string $file  File containing the diff output
87     * @return void
88     * @throws BuildException if $file not exists
89     */
90    public function setPatchFile($file)
91    {
92        if (!is_file($file))
93            throw new BuildException(sprintf('Patchfile %s doesn\'t exist', $file));
94
95    $this->patchFile = $file;
96    }
97
98    /**
99     * The file to patch
100     *
101     * Optional if it can be inferred from the diff file
102     *
103     * @param string $file  File to patch
104     * @return void
105     */
106    public function setOriginalFile($file)
107    {
108        $this->originalFile = $file;
109    }
110
111    /**
112     * The name of a file to send the output to, instead of patching
113     * the file(s) in place
114     *
115     * Optional.
116     *
117     * @param string $file   File to send the output to
118     * @return void
119     */
120    public function setDestFile($file)
121    {
122        if ($file !== null)
123            $this->cmdArgs []= "-o $file";
124    }
125
126    /**
127     * Flag to create backups
128     *
129     * Optional, default - false
130     *
131     * @param bool $backups  If true create backups
132     * @return void
133     */
134    public function setBackups($backups)
135    {
136        if ($backups)
137            $this->cmdArgs []= '-b';
138    }
139
140    /**
141     * Flag to ignore whitespace differences;
142     *
143     * Default - false
144     *
145     * @param bool $ignore  If true ignore whitespace differences
146     * @return void
147     */
148    public function setIgnoreWhiteSpace($ignore)
149    {
150        if ($ignore)
151            $this->cmdArgs []= '-l';
152    }
153
154    /**
155     * Strip the smallest prefix containing <i>num</i> leading slashes
156     * from filenames.
157     *
158     * patch's <i>-p</i> option.
159     *
160     * @param int $num number of lines to strip
161     * @return void
162     * @throws BuildException if num is < 0, or other errors
163     */
164    public function setStrip($num)
165    {
166        if ($num < 0)
167            throw new BuildException('strip has to be >= 0');
168
169        $this->strip = $num;
170    }
171
172    /**
173     * Work silently unless an error occurs
174     *
175     * Optional, default - false
176     * @param bool $q  If true suppress set the -s option on the patch command
177     * @return void
178     */
179    public function setQuiet($q)
180    {
181        if ($q)
182            $this->cmdArgs []= '-s';
183    }
184
185    /**
186     * Assume patch was created with old and new files swapped
187     *
188     * Optional, default - false
189     *
190     * @param bool $r  If true set the -R option on the patch command
191     * @return void
192     */
193    public function setReverse($r)
194    {
195        if ($r)
196            $this->cmdArgs []= '-R';
197    }
198
199    /**
200     * The directory to run the patch command in
201     *
202     * Defaults to the project's base directory.
203     *
204     * @param string $directory  Directory to run the patch command in
205     * @return void
206     */
207    public function setDir($directory)
208    {
209        $this->directory = $directory;
210    }
211
212    /**
213     * If true, stop the build process if the patch command
214     * exits with an error status.
215     *
216     * The default is "false"
217     *
218     * @param bool $value  "true" if it should halt, otherwise "false"
219     * @return void
220     */
221    public function setFailOnError($value)
222    {
223        $this->failOnError = $value;
224    }
225
226    /**
227     * Main task method
228     *
229     * @return void
230     * @throws BuildException when it all goes a bit pear shaped
231     */
232    public function main()
233    {
234        if ($this->patchFile == null)
235            throw new BuildException('patchfile argument is required');
236
237        // Define patch file
238        $this->cmdArgs []= '-i ' . $this->patchFile;
239        // Define strip factor
240        $this->cmdArgs []= '-p' . $this->strip;
241        // Define original file if specified
242        if ($this->originalFile != null)
243            $this->cmdArgs []= $this->originalFile;
244
245        $cmd = self::CMD . implode(' ', $this->cmdArgs);
246
247/*        if (directory != null) {
248            if (directory.exists() && directory.isDirectory()) {
249                exe.setWorkingDirectory(directory);
250            } else if (!directory.isDirectory()) {
251                throw new BuildException(directory + " is not a directory.",
252                                         getLocation());
253            } else {
254                throw new BuildException("directory " + directory
255                                         + " doesn\'t exist", getLocation());
256            }
257        } else {
258            exe.setWorkingDirectory(getProject().getBaseDir());
259        }
260*/
261        $this->log('Applying patch: ' . $this->patchFile);
262
263        exec($cmd, $output, $exitCode);
264
265        foreach ($output as $line)
266            $this->log($line, Project::MSG_VERBOSE);
267
268        if ($exitCode != 0)
269            throw new BuildException( "Task exited with code $exitCode" );
270
271    }
272}