Passed
Push — main ( b7649a...398f47 )
by Michiel
06:32
created

RSTTask::setDestination()   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
eloc 1
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
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
namespace Phing\Task\Optional;
15
16
use Phing\Exception\BuildException;
17
use Phing\Io\FileSystem;
18
use Phing\Io\FileUtils;
19
use Phing\Io\File;
20
use Phing\Project;
21
use Phing\Task;
22
use Phing\Type\Element\FileSetAware;
23
use Phing\Type\Element\FilterChainAware;
24
use Phing\Type\Mapper;
25
26
/**
27
 * reStructuredText rendering task for Phing, the PHP build tool.
28
 *
29
 * PHP version 5
30
 *
31
 * @category Tasks
32
 * @package  phing.tasks.ext
33
 * @author   Christian Weiske <[email protected]>
34
 * @license  LGPL v3 or later http://www.gnu.org/licenses/lgpl.html
35
 * @link     http://www.phing.info/
36
 */
37
class RSTTask extends Task
38
{
39
    use FileSetAware;
40
    use FilterChainAware;
41
42
    /**
43
     * @var string Taskname for logger
44
     */
45
    protected $taskName = 'rST';
46
47
    /**
48
     * Result format, defaults to "html".
49
     *
50
     * @see $supportedFormats for all possible options
51
     *
52
     * @var string
53
     */
54
    protected $format = 'html';
55
56
    /**
57
     * Array of supported output formats
58
     *
59
     * @var array
60
     * @see $format
61
     * @see $targetExt
62
     */
63
    protected static $supportedFormats = [
64
        'html',
65
        'latex',
66
        'man',
67
        'odt',
68
        's5',
69
        'xml'
70
    ];
71
72
    /**
73
     * Maps formats to file extensions
74
     *
75
     * @var array
76
     */
77
    protected static $targetExt = [
78
        'html' => 'html',
79
        'latex' => 'tex',
80
        'man' => '3',
81
        'odt' => 'odt',
82
        's5' => 'html',
83
        'xml' => 'xml',
84
    ];
85
86
    /**
87
     * Input file in rST format.
88
     * Required
89
     *
90
     * @var string
91
     */
92
    protected $file = null;
93
94
    /**
95
     * Additional rst2* tool parameters.
96
     *
97
     * @var string
98
     */
99
    protected $toolParam = null;
100
101
    /**
102
     * Full path to the tool, i.e. /usr/local/bin/rst2html
103
     *
104
     * @var string
105
     */
106
    protected $toolPath = null;
107
108
    /**
109
     * Output file or directory. May be omitted.
110
     * When it ends with a slash, it is considered to be a directory
111
     *
112
     * @var string
113
     */
114
    protected $destination = null;
115
116
    protected $mapperElement = null;
117
118
    /**
119
     * mode to create directories with
120
     *
121
     * @var integer
122
     */
123
    protected $mode = 0;
124
125
    /**
126
     * Only render files whole source files are newer than the
127
     * target files
128
     *
129
     * @var boolean
130
     */
131
    protected $uptodate = false;
132
133
    /**
134
     * @var FileUtils
135
     */
136
    private $fileUtils;
137
138
    /**
139
     * Sets up this object internal stuff. i.e. the default mode.
140
     */
141 1
    public function __construct()
142
    {
143 1
        parent::__construct();
144 1
        $this->mode = 0777 - umask();
145 1
    }
146
147
    /**
148
     * The main entry point method.
149
     *
150
     * @return void
151
     * @throws BuildException
152
     */
153
    public function main()
154
    {
155
        $tool = $this->getToolPath($this->format);
156
        if (count($this->filterChains)) {
157
            $this->fileUtils = new FileUtils();
158
        }
159
160
        if ($this->file != '') {
161
            $file = $this->file;
162
            $targetFile = $this->getTargetFile($file, $this->destination);
163
            $this->render($tool, $file, $targetFile);
164
165
            return;
166
        }
167
168
        if (!count($this->filesets)) {
169
            throw new BuildException(
170
                '"file" attribute or "fileset" subtag required'
171
            );
172
        }
173
174
        // process filesets
175
        $mapper = null;
176
        if ($this->mapperElement !== null) {
177
            $mapper = $this->mapperElement->getImplementation();
178
        }
179
180
        $project = $this->getProject();
181
        foreach ($this->filesets as $fs) {
182
            $ds = $fs->getDirectoryScanner($project);
183
            $fromDir = $fs->getDir($project);
184
            $srcFiles = $ds->getIncludedFiles();
185
186
            foreach ($srcFiles as $src) {
187
                $file = new File($fromDir, $src);
188
                if ($mapper !== null) {
189
                    $results = $mapper->main($file);
190
                    if ($results === null) {
191
                        throw new BuildException(
192
                            sprintf(
193
                                'No filename mapper found for "%s"',
194
                                $file
195
                            )
196
                        );
197
                    }
198
                    $targetFile = reset($results);
199
                } else {
200
                    $targetFile = $this->getTargetFile($file, $this->destination);
201
                }
202
                $this->render($tool, $file, $targetFile);
203
            }
204
        }
205
    }
206
207
    /**
208
     * Renders a single file and applies filters on it
209
     *
210
     * @param string $tool conversion tool to use
211
     * @param string $source rST source file
212
     * @param string $targetFile target file name
213
     *
214
     * @return void
215
     */
216
    protected function render($tool, $source, $targetFile)
217
    {
218
        if (count($this->filterChains) == 0) {
219
            $this->renderFile($tool, $source, $targetFile);
220
            return;
221
        }
222
223
        $tmpTarget = tempnam($this->fileUtils::getTempDir(), 'rST-');
224
        $this->renderFile($tool, $source, $tmpTarget);
225
226
        $this->fileUtils->copyFile(
227
            new File($tmpTarget),
228
            new File($targetFile),
229
            $this->getProject(),
230
            true,
231
            false,
232
            $this->filterChains,
233
            $this->mode
234
        );
235
        unlink($tmpTarget);
236
    }
237
238
    /**
239
     * Renders a single file with the rST tool.
240
     *
241
     * @param string $tool conversion tool to use
242
     * @param string $source rST source file
243
     * @param string $targetFile target file name
244
     *
245
     * @return void
246
     *
247
     * @throws BuildException When the conversion fails
248
     */
249
    protected function renderFile($tool, $source, $targetFile)
250
    {
251
        if (
252
            $this->uptodate
253
            && file_exists($targetFile)
254
            && filemtime($source) <= filemtime($targetFile)
255
        ) {
256
            //target is up to date
257
            return;
258
        }
259
        //work around a bug in php by replacing /./ with /
260
        $targetDir = str_replace('/./', '/', dirname($targetFile));
261
        if (!is_dir($targetDir)) {
262
            $this->log("Creating directory '$targetDir'", Project::MSG_VERBOSE);
263
            mkdir($targetDir, $this->mode, true);
264
        }
265
266
        $cmd = $tool
267
            . ' --exit-status=2'
268
            . ' ' . $this->toolParam
269
            . ' ' . escapeshellarg($source)
270
            . ' ' . escapeshellarg($targetFile)
271
            . ' 2>&1';
272
273
        $this->log('command: ' . $cmd, Project::MSG_VERBOSE);
274
        exec($cmd, $arOutput, $retval);
275
        if ($retval != 0) {
276
            $this->log(implode("\n", $arOutput), Project::MSG_INFO);
277
            throw new BuildException('Rendering rST failed');
278
        }
279
        $this->log(implode("\n", $arOutput), Project::MSG_DEBUG);
280
    }
281
282
    /**
283
     * Finds the rst2* binary path
284
     *
285
     * @param string $format Output format
286
     *
287
     * @return string Full path to rst2$format
288
     *
289
     * @throws BuildException When the tool cannot be found
290
     */
291
    protected function getToolPath($format)
292
    {
293
        if ($this->toolPath !== null) {
294
            return $this->toolPath;
295
        }
296
297
        $tool = 'rst2' . $format;
298
        $fs = FileSystem::getFileSystem();
299
        $path = $fs->which($tool);
300
        if (!$path) {
301
            throw new BuildException(
302
                sprintf('"%s" not found. Install python-docutils.', $tool)
303
            );
304
        }
305
306
        return $path;
307
    }
308
309
    /**
310
     * Determines and returns the target file name from the
311
     * input file and the configured destination name.
312
     *
313
     * @param string $file Input file
314
     * @param string $destination Destination file or directory name,
315
     *                            may be null
316
     *
317
     * @return string Target file name
318
     *
319
     * @uses $format
320
     * @uses $targetExt
321
     */
322
    public function getTargetFile($file, $destination = null)
323
    {
324
        if (
325
            $destination != ''
326
            && substr($destination, -1) !== '/'
0 ignored issues
show
Bug introduced by
It seems like $destination can also be of type null; however, parameter $string of substr() 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

326
            && substr(/** @scrutinizer ignore-type */ $destination, -1) !== '/'
Loading history...
327
            && substr($destination, -1) !== '\\'
328
        ) {
329
            return $destination;
330
        }
331
332
        if (strtolower(substr($file, -4)) == '.rst') {
333
            $file = substr($file, 0, -4);
334
        }
335
336
        return $destination . $file . '.' . self::$targetExt[$this->format];
337
    }
338
339
    /**
340
     * The setter for the attribute "file"
341
     *
342
     * @param string $file Path of file to render
343
     *
344
     * @return void
345
     */
346
    public function setFile($file)
347
    {
348
        $this->file = $file;
349
    }
350
351
    /**
352
     * The setter for the attribute "format"
353
     *
354
     * @param string $format Output format
355
     *
356
     * @return void
357
     *
358
     * @throws BuildException When the format is not supported
359
     */
360
    public function setFormat($format)
361
    {
362
        if (!in_array($format, self::$supportedFormats)) {
363
            throw new BuildException(
364
                sprintf(
365
                    'Invalid output format "%s", allowed are: %s',
366
                    $format,
367
                    implode(', ', self::$supportedFormats)
368
                )
369
            );
370
        }
371
        $this->format = $format;
372
    }
373
374
    /**
375
     * The setter for the attribute "destination"
376
     *
377
     * @param string $destination Output file or directory. When it ends
378
     *                            with a slash, it is taken as directory.
379
     *
380
     * @return void
381
     */
382
    public function setDestination($destination)
383
    {
384
        $this->destination = $destination;
385
    }
386
387
    /**
388
     * The setter for the attribute "toolparam"
389
     *
390
     * @param string $param Additional rst2* tool parameters
391
     *
392
     * @return void
393
     */
394
    public function setToolparam($param)
395
    {
396
        $this->toolParam = $param;
397
    }
398
399
    /**
400
     * The setter for the attribute "toolpath"
401
     *
402
     * @param    $path
403
     * @return void
404
     * @throws   BuildException
405
     * @internal param string $param Full path to tool path, i.e. /usr/local/bin/rst2html
406
     *
407
     */
408
    public function setToolpath($path)
409
    {
410
        if (!file_exists($path)) {
411
            $fs = FileSystem::getFileSystem();
412
            $fullpath = $fs->which($path);
413
            if ($fullpath === false) {
0 ignored issues
show
introduced by
The condition $fullpath === false is always false.
Loading history...
414
                throw new BuildException(
415
                    'Tool does not exist. Path: ' . $path
416
                );
417
            }
418
            $path = $fullpath;
419
        }
420
        if (!is_executable($path)) {
421
            throw new BuildException(
422
                'Tool not executable. Path: ' . $path
423
            );
424
        }
425
        $this->toolPath = $path;
426
    }
427
428
    /**
429
     * The setter for the attribute "uptodate"
430
     *
431
     * @param string $uptodate True/false
432
     *
433
     * @return void
434
     */
435
    public function setUptodate($uptodate)
436
    {
437
        $this->uptodate = (bool) $uptodate;
438
    }
439
440
    /**
441
     * Nested creator, creates one Mapper for this task
442
     *
443
     * @return Mapper The created Mapper type object
444
     *
445
     * @throws BuildException
446
     */
447 1
    public function createMapper()
448
    {
449 1
        if ($this->mapperElement !== null) {
450 1
            throw new BuildException(
451 1
                'Cannot define more than one mapper',
452 1
                $this->getLocation()
453
            );
454
        }
455 1
        $this->mapperElement = new Mapper($this->project);
456
457 1
        return $this->mapperElement;
458
    }
459
}
460