Passed
Push — master ( a862c8...fef60d )
by Siad
05:54
created

RSTTask::getTargetFile()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 8
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 15
ccs 0
cts 8
cp 0
crap 30
rs 9.6111
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 1
    public function __construct()
130
    {
131 1
        parent::__construct();
132 1
        $this->mode = 0777 - umask();
133 1
    }
134
135
    /**
136
     * The main entry point method.
137
     *
138
     * @throws BuildException
139
     * @return void
140
     */
141
    public function main()
142
    {
143
        $tool = $this->getToolPath($this->format);
144
        if (count($this->filterChains)) {
145
            $this->fileUtils = new FileUtils();
146
        }
147
148
        if ($this->file != '') {
149
            $file = $this->file;
150
            $targetFile = $this->getTargetFile($file, $this->destination);
151
            $this->render($tool, $file, $targetFile);
152
153
            return;
154
        }
155
156
        if (!count($this->filesets)) {
157
            throw new BuildException(
158
                '"file" attribute or "fileset" subtag required'
159
            );
160
        }
161
162
        // process filesets
163
        $mapper = null;
164
        if ($this->mapperElement !== null) {
165
            $mapper = $this->mapperElement->getImplementation();
166
        }
167
168
        $project = $this->getProject();
169
        foreach ($this->filesets as $fs) {
170
            $ds = $fs->getDirectoryScanner($project);
171
            $fromDir = $fs->getDir($project);
172
            $srcFiles = $ds->getIncludedFiles();
173
174
            foreach ($srcFiles as $src) {
175
                $file = new PhingFile($fromDir, $src);
176
                if ($mapper !== null) {
177
                    $results = $mapper->main($file);
178
                    if ($results === null) {
179
                        throw new BuildException(
180
                            sprintf(
181
                                'No filename mapper found for "%s"',
182
                                $file
183
                            )
184
                        );
185
                    }
186
                    $targetFile = reset($results);
187
                } else {
188
                    $targetFile = $this->getTargetFile($file, $this->destination);
189
                }
190
                $this->render($tool, $file, $targetFile);
191
            }
192
        }
193
    }
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
    protected function render($tool, $source, $targetFile)
205
    {
206
        if (count($this->filterChains) == 0) {
207
            $this->renderFile($tool, $source, $targetFile);
208
            return;
209
        }
210
211
        $tmpTarget = tempnam($this->fileUtils::getTempDir(), 'rST-');
212
        $this->renderFile($tool, $source, $tmpTarget);
213
214
        $this->fileUtils->copyFile(
215
            new PhingFile($tmpTarget),
216
            new PhingFile($targetFile),
217
            $this->getProject(),
218
            true,
219
            false,
220
            $this->filterChains,
221
            $this->mode
222
        );
223
        unlink($tmpTarget);
224
    }
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
    protected function renderFile($tool, $source, $targetFile)
238
    {
239
        if (
240
            $this->uptodate
241
            && file_exists($targetFile)
242
            && filemtime($source) <= filemtime($targetFile)
243
        ) {
244
            //target is up to date
245
            return;
246
        }
247
        //work around a bug in php by replacing /./ with /
248
        $targetDir = str_replace('/./', '/', dirname($targetFile));
249
        if (!is_dir($targetDir)) {
250
            $this->log("Creating directory '$targetDir'", Project::MSG_VERBOSE);
251
            mkdir($targetDir, $this->mode, true);
252
        }
253
254
        $cmd = $tool
255
            . ' --exit-status=2'
256
            . ' ' . $this->toolParam
257
            . ' ' . escapeshellarg($source)
258
            . ' ' . escapeshellarg($targetFile)
259
            . ' 2>&1';
260
261
        $this->log('command: ' . $cmd, Project::MSG_VERBOSE);
262
        exec($cmd, $arOutput, $retval);
263
        if ($retval != 0) {
264
            $this->log(implode("\n", $arOutput), Project::MSG_INFO);
265
            throw new BuildException('Rendering rST failed');
266
        }
267
        $this->log(implode("\n", $arOutput), Project::MSG_DEBUG);
268
    }
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
    protected function getToolPath($format)
280
    {
281
        if ($this->toolPath !== null) {
282
            return $this->toolPath;
283
        }
284
285
        $tool = 'rst2' . $format;
286
        $fs = FileSystem::getFileSystem();
287
        $path = $fs->which($tool);
288
        if (!$path) {
289
            throw new BuildException(
290
                sprintf('"%s" not found. Install python-docutils.', $tool)
291
            );
292
        }
293
294
        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
    public function getTargetFile($file, $destination = null)
311
    {
312
        if (
313
            $destination != ''
314
            && substr($destination, -1) !== '/'
315
            && substr($destination, -1) !== '\\'
316
        ) {
317
            return $destination;
318
        }
319
320
        if (strtolower(substr($file, -4)) == '.rst') {
321
            $file = substr($file, 0, -4);
322
        }
323
324
        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
    public function setFile($file)
335
    {
336
        $this->file = $file;
337
    }
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
    public function setFormat($format)
349
    {
350
        if (!in_array($format, self::$supportedFormats)) {
351
            throw new BuildException(
352
                sprintf(
353
                    'Invalid output format "%s", allowed are: %s',
354
                    $format,
355
                    implode(', ', self::$supportedFormats)
356
                )
357
            );
358
        }
359
        $this->format = $format;
360
    }
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
    public function setDestination($destination)
371
    {
372
        $this->destination = $destination;
373
    }
374
375
    /**
376
     * The setter for the attribute "toolparam"
377
     *
378
     * @param string $param Additional rst2* tool parameters
379
     *
380
     * @return void
381
     */
382
    public function setToolparam($param)
383
    {
384
        $this->toolParam = $param;
385
    }
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
    public function setToolpath($path)
397
    {
398
        if (!file_exists($path)) {
399
            $fs = FileSystem::getFileSystem();
400
            $fullpath = $fs->which($path);
401
            if ($fullpath === false) {
0 ignored issues
show
introduced by
The condition $fullpath === false is always false.
Loading history...
402
                throw new BuildException(
403
                    'Tool does not exist. Path: ' . $path
404
                );
405
            }
406
            $path = $fullpath;
407
        }
408
        if (!is_executable($path)) {
409
            throw new BuildException(
410
                'Tool not executable. Path: ' . $path
411
            );
412
        }
413
        $this->toolPath = $path;
414
    }
415
416
    /**
417
     * The setter for the attribute "uptodate"
418
     *
419
     * @param string $uptodate True/false
420
     *
421
     * @return void
422
     */
423
    public function setUptodate($uptodate)
424
    {
425
        $this->uptodate = (bool) $uptodate;
426
    }
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 1
    public function createMapper()
436
    {
437 1
        if ($this->mapperElement !== null) {
438 1
            throw new BuildException(
439 1
                'Cannot define more than one mapper',
440 1
                $this->getLocation()
441
            );
442
        }
443 1
        $this->mapperElement = new Mapper($this->project);
444
445 1
        return $this->mapperElement;
446
    }
447
}
448