Completed
Push — master ( 20b0ec...0fa80a )
by Siad
15:26
created

RSTTask::renderFile()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 31
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 6.0045

Importance

Changes 0
Metric Value
cc 6
eloc 21
c 0
b 0
f 0
nc 5
nop 3
dl 0
loc 31
ccs 19
cts 20
cp 0.95
crap 6.0045
rs 8.9617
1
<?php
2
/**
3
 * reStructuredText rendering task for Phing, the PHP build tool.
4
 *
5
 * PHP version 5
6
 *
7
 * @category Tasks
8
 * @package  phing.tasks.ext
9
 * @author   Christian Weiske <[email protected]>
10
 * @license  LGPL v3 or later http://www.gnu.org/licenses/lgpl.html
11
 * @link     http://www.phing.info/
12
 */
13
14
/**
15
 * reStructuredText rendering task for Phing, the PHP build tool.
16
 *
17
 * PHP version 5
18
 *
19
 * @category Tasks
20
 * @package  phing.tasks.ext
21
 * @author   Christian Weiske <[email protected]>
22
 * @license  LGPL v3 or later http://www.gnu.org/licenses/lgpl.html
23
 * @link     http://www.phing.info/
24
 */
25
class RSTTask extends Task
26
{
27
    use FileSetAware;
28
    use FilterChainAware;
29
30
    /**
31
     * @var string Taskname for logger
32
     */
33
    protected $taskName = 'rST';
34
35
    /**
36
     * Result format, defaults to "html".
37
     *
38
     * @see $supportedFormats for all possible options
39
     *
40
     * @var string
41
     */
42
    protected $format = 'html';
43
44
    /**
45
     * Array of supported output formats
46
     *
47
     * @var array
48
     * @see $format
49
     * @see $targetExt
50
     */
51
    protected static $supportedFormats = [
52
        'html',
53
        'latex',
54
        'man',
55
        'odt',
56
        's5',
57
        'xml'
58
    ];
59
60
    /**
61
     * Maps formats to file extensions
62
     *
63
     * @var array
64
     */
65
    protected static $targetExt = [
66
        'html' => 'html',
67
        'latex' => 'tex',
68
        'man' => '3',
69
        'odt' => 'odt',
70
        's5' => 'html',
71
        'xml' => 'xml',
72
    ];
73
74
    /**
75
     * Input file in rST format.
76
     * Required
77
     *
78
     * @var string
79
     */
80
    protected $file = null;
81
82
    /**
83
     * Additional rst2* tool parameters.
84
     *
85
     * @var string
86
     */
87
    protected $toolParam = null;
88
89
    /**
90
     * Full path to the tool, i.e. /usr/local/bin/rst2html
91
     *
92
     * @var string
93
     */
94
    protected $toolPath = null;
95
96
    /**
97
     * Output file or directory. May be omitted.
98
     * When it ends with a slash, it is considered to be a directory
99
     *
100
     * @var string
101
     */
102
    protected $destination = null;
103
104
    protected $mapperElement = null;
105
106
    /**
107
     * mode to create directories with
108
     *
109
     * @var integer
110
     */
111
    protected $mode = 0;
112
113
    /**
114
     * Only render files whole source files are newer than the
115
     * target files
116
     *
117
     * @var boolean
118
     */
119
    protected $uptodate = false;
120
121
    /**
122
     * @var FileUtils
123
     */
124
    private $fileUtils;
125
126
    /**
127
     * Sets up this object internal stuff. i.e. the default mode.
128
     */
129 25
    public function __construct()
130
    {
131 25
        parent::__construct();
132 25
        $this->mode = 0777 - umask();
133 25
    }
134
135
    /**
136
     * The main entry point method.
137
     *
138
     * @throws BuildException
139
     * @return void
140
     */
141 18
    public function main()
142
    {
143 18
        $tool = $this->getToolPath($this->format);
144 18
        if (count($this->filterChains)) {
145 1
            $this->fileUtils = new FileUtils();
146
        }
147
148 18
        if ($this->file != '') {
149 9
            $file = $this->file;
150 9
            $targetFile = $this->getTargetFile($file, $this->destination);
151 9
            $this->render($tool, $file, $targetFile);
152
153 8
            return;
154
        }
155
156 9
        if (!count($this->filesets)) {
157 1
            throw new BuildException(
158 1
                '"file" attribute or "fileset" subtag required'
159
            );
160
        }
161
162
        // process filesets
163 8
        $mapper = null;
164 8
        if ($this->mapperElement !== null) {
165 2
            $mapper = $this->mapperElement->getImplementation();
166
        }
167
168 8
        $project = $this->getProject();
169 8
        foreach ($this->filesets as $fs) {
170 8
            $ds = $fs->getDirectoryScanner($project);
171 8
            $fromDir = $fs->getDir($project);
172 8
            $srcFiles = $ds->getIncludedFiles();
173
174 8
            foreach ($srcFiles as $src) {
175 8
                $file = new PhingFile($fromDir, $src);
176 8
                if ($mapper !== null) {
177 2
                    $results = $mapper->main($file);
178 2
                    if ($results === null) {
179 1
                        throw new BuildException(
180 1
                            sprintf(
181 1
                                'No filename mapper found for "%s"',
182 1
                                $file
183
                            )
184
                        );
185
                    }
186 1
                    $targetFile = reset($results);
187
                } else {
188 6
                    $targetFile = $this->getTargetFile($file, $this->destination);
189
                }
190 7
                $this->render($tool, $file, $targetFile);
191
            }
192
        }
193 7
    }
194
195
    /**
196
     * Renders a single file and applies filters on it
197
     *
198
     * @param string $tool conversion tool to use
199
     * @param string $source rST source file
200
     * @param string $targetFile target file name
201
     *
202
     * @return void
203
     */
204 16
    protected function render($tool, $source, $targetFile)
205
    {
206 16
        if (count($this->filterChains) == 0) {
207 15
            $this->renderFile($tool, $source, $targetFile);
208 14
            return;
209
        }
210
211 1
        $tmpTarget = tempnam(sys_get_temp_dir(), 'rST-');
212 1
        $this->renderFile($tool, $source, $tmpTarget);
213
214 1
        $this->fileUtils->copyFile(
215 1
            new PhingFile($tmpTarget),
216 1
            new PhingFile($targetFile),
217 1
            $this->getProject(),
218 1
            true,
219 1
            false,
220 1
            $this->filterChains,
221 1
            $this->mode
222
        );
223 1
        unlink($tmpTarget);
224 1
    }
225
226
    /**
227
     * Renders a single file with the rST tool.
228
     *
229
     * @param string $tool conversion tool to use
230
     * @param string $source rST source file
231
     * @param string $targetFile target file name
232
     *
233
     * @return void
234
     *
235
     * @throws BuildException When the conversion fails
236
     */
237 16
    protected function renderFile($tool, $source, $targetFile)
238
    {
239
        if (
240 16
            $this->uptodate
241 1
            && file_exists($targetFile)
242 1
            && filemtime($source) <= filemtime($targetFile)
243
        ) {
244
            //target is up to date
245 1
            return;
246
        }
247
        //work around a bug in php by replacing /./ with /
248 15
        $targetDir = str_replace('/./', '/', dirname($targetFile));
249 15
        if (!is_dir($targetDir)) {
250 4
            $this->log("Creating directory '$targetDir'", Project::MSG_VERBOSE);
251 4
            mkdir($targetDir, $this->mode, true);
252
        }
253
254
        $cmd = $tool
255 15
            . ' --exit-status=2'
256 15
            . ' ' . $this->toolParam
257 15
            . ' ' . escapeshellarg($source)
258 15
            . ' ' . escapeshellarg($targetFile)
259
            . ' 2>&1';
260
261 15
        $this->log('command: ' . $cmd, Project::MSG_VERBOSE);
262 15
        exec($cmd, $arOutput, $retval);
263 15
        if ($retval != 0) {
264 1
            $this->log(implode("\n", $arOutput), Project::MSG_INFO);
265 1
            throw new BuildException('Rendering rST failed');
266
        }
267 14
        $this->log(implode("\n", $arOutput), Project::MSG_DEBUG);
268 14
    }
269
270
    /**
271
     * Finds the rst2* binary path
272
     *
273
     * @param string $format Output format
274
     *
275
     * @return string Full path to rst2$format
276
     *
277
     * @throws BuildException When the tool cannot be found
278
     */
279 24
    protected function getToolPath($format)
280
    {
281 24
        if ($this->toolPath !== null) {
282 1
            return $this->toolPath;
283
        }
284
285 24
        $tool = 'rst2' . $format;
286 24
        $fs = FileSystem::getFileSystem();
287 24
        $path = $fs->which($tool);
288 24
        if (!$path) {
289 1
            throw new BuildException(
290 1
                sprintf('"%s" not found. Install python-docutils.', $tool)
291
            );
292
        }
293
294 24
        return $path;
295
    }
296
297
    /**
298
     * Determines and returns the target file name from the
299
     * input file and the configured destination name.
300
     *
301
     * @param string $file Input file
302
     * @param string $destination Destination file or directory name,
303
     *                            may be null
304
     *
305
     * @return string Target file name
306
     *
307
     * @uses $format
308
     * @uses $targetExt
309
     */
310 15
    public function getTargetFile($file, $destination = null)
311
    {
312
        if (
313 15
            $destination != ''
314 5
            && substr($destination, -1) !== '/'
315 2
            && substr($destination, -1) !== '\\'
316
        ) {
317 2
            return $destination;
318
        }
319
320 13
        if (strtolower(substr($file, -4)) == '.rst') {
321 12
            $file = substr($file, 0, -4);
322
        }
323
324 13
        return $destination . $file . '.' . self::$targetExt[$this->format];
325
    }
326
327
    /**
328
     * The setter for the attribute "file"
329
     *
330
     * @param string $file Path of file to render
331
     *
332
     * @return void
333
     */
334 9
    public function setFile($file)
335
    {
336 9
        $this->file = $file;
337 9
    }
338
339
    /**
340
     * The setter for the attribute "format"
341
     *
342
     * @param string $format Output format
343
     *
344
     * @return void
345
     *
346
     * @throws BuildException When the format is not supported
347
     */
348 6
    public function setFormat($format)
349
    {
350 6
        if (!in_array($format, self::$supportedFormats)) {
351 1
            throw new BuildException(
352 1
                sprintf(
353 1
                    'Invalid output format "%s", allowed are: %s',
354 1
                    $format,
355 1
                    implode(', ', self::$supportedFormats)
356
                )
357
            );
358
        }
359 5
        $this->format = $format;
360 5
    }
361
362
    /**
363
     * The setter for the attribute "destination"
364
     *
365
     * @param string $destination Output file or directory. When it ends
366
     *                            with a slash, it is taken as directory.
367
     *
368
     * @return void
369
     */
370 5
    public function setDestination($destination)
371
    {
372 5
        $this->destination = $destination;
373 5
    }
374
375
    /**
376
     * The setter for the attribute "toolparam"
377
     *
378
     * @param string $param Additional rst2* tool parameters
379
     *
380
     * @return void
381
     */
382 1
    public function setToolparam($param)
383
    {
384 1
        $this->toolParam = $param;
385 1
    }
386
387
    /**
388
     * The setter for the attribute "toolpath"
389
     *
390
     * @param    $path
391
     * @throws   BuildException
392
     * @internal param string $param Full path to tool path, i.e. /usr/local/bin/rst2html
393
     *
394
     * @return void
395
     */
396 3
    public function setToolpath($path)
397
    {
398 3
        if (!file_exists($path)) {
399 2
            $fs = FileSystem::getFileSystem();
400 2
            $fullpath = $fs->which($path);
401 2
            if ($fullpath === false) {
0 ignored issues
show
introduced by
The condition $fullpath === false is always false.
Loading history...
402 1
                throw new BuildException(
403 1
                    'Tool does not exist. Path: ' . $path
404
                );
405
            }
406 1
            $path = $fullpath;
407
        }
408 2
        if (!is_executable($path)) {
409 1
            throw new BuildException(
410 1
                'Tool not executable. Path: ' . $path
411
            );
412
        }
413 1
        $this->toolPath = $path;
414 1
    }
415
416
    /**
417
     * The setter for the attribute "uptodate"
418
     *
419
     * @param string $uptodate True/false
420
     *
421
     * @return void
422
     */
423 1
    public function setUptodate($uptodate)
424
    {
425 1
        $this->uptodate = (bool) $uptodate;
426 1
    }
427
428
    /**
429
     * Nested creator, creates one Mapper for this task
430
     *
431
     * @return Mapper The created Mapper type object
432
     *
433
     * @throws BuildException
434
     */
435 3
    public function createMapper()
436
    {
437 3
        if ($this->mapperElement !== null) {
438 1
            throw new BuildException(
439 1
                'Cannot define more than one mapper',
440 1
                $this->getLocation()
441
            );
442
        }
443 3
        $this->mapperElement = new Mapper($this->project);
444
445 3
        return $this->mapperElement;
446
    }
447
}
448