Completed
Push — master ( bc194a...d90c8b )
by Siad
17:01
created

RSTTask::render()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 14
nc 2
nop 3
dl 0
loc 20
ccs 15
cts 15
cp 1
crap 2
rs 9.7998
c 0
b 0
f 0
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 16
        if ($this->uptodate && file_exists($targetFile)
240 1
            && filemtime($source) <= filemtime($targetFile)
241
        ) {
242
            //target is up to date
243 1
            return;
244
        }
245
        //work around a bug in php by replacing /./ with /
246 15
        $targetDir = str_replace('/./', '/', dirname($targetFile));
247 15
        if (!is_dir($targetDir)) {
248 4
            $this->log("Creating directory '$targetDir'", Project::MSG_VERBOSE);
249 4
            mkdir($targetDir, $this->mode, true);
250
        }
251
252
        $cmd = $tool
253 15
            . ' --exit-status=2'
254 15
            . ' ' . $this->toolParam
255 15
            . ' ' . escapeshellarg($source)
256 15
            . ' ' . escapeshellarg($targetFile)
257
            . ' 2>&1';
258
259 15
        $this->log('command: ' . $cmd, Project::MSG_VERBOSE);
260 15
        exec($cmd, $arOutput, $retval);
261 15
        if ($retval != 0) {
262 1
            $this->log(implode("\n", $arOutput), Project::MSG_INFO);
263 1
            throw new BuildException('Rendering rST failed');
264
        }
265 14
        $this->log(implode("\n", $arOutput), Project::MSG_DEBUG);
266 14
    }
267
268
    /**
269
     * Finds the rst2* binary path
270
     *
271
     * @param string $format Output format
272
     *
273
     * @return string Full path to rst2$format
274
     *
275
     * @throws BuildException When the tool cannot be found
276
     */
277 24
    protected function getToolPath($format)
278
    {
279 24
        if ($this->toolPath !== null) {
280 1
            return $this->toolPath;
281
        }
282
283 24
        $tool = 'rst2' . $format;
284 24
        $fs = FileSystem::getFileSystem();
285 24
        $path = $fs->which($tool);
286 24
        if (!$path) {
287 1
            throw new BuildException(
288 1
                sprintf('"%s" not found. Install python-docutils.', $tool)
289
            );
290
        }
291
292 24
        return $path;
293
    }
294
295
    /**
296
     * Determines and returns the target file name from the
297
     * input file and the configured destination name.
298
     *
299
     * @param string $file Input file
300
     * @param string $destination Destination file or directory name,
301
     *                            may be null
302
     *
303
     * @return string Target file name
304
     *
305
     * @uses $format
306
     * @uses $targetExt
307
     */
308 15
    public function getTargetFile($file, $destination = null)
309
    {
310 15
        if ($destination != ''
311 5
            && substr($destination, -1) !== '/'
312 2
            && substr($destination, -1) !== '\\'
313
        ) {
314 2
            return $destination;
315
        }
316
317 13
        if (strtolower(substr($file, -4)) == '.rst') {
318 12
            $file = substr($file, 0, -4);
319
        }
320
321 13
        return $destination . $file . '.' . self::$targetExt[$this->format];
322
    }
323
324
    /**
325
     * The setter for the attribute "file"
326
     *
327
     * @param string $file Path of file to render
328
     *
329
     * @return void
330
     */
331 9
    public function setFile($file)
332
    {
333 9
        $this->file = $file;
334 9
    }
335
336
    /**
337
     * The setter for the attribute "format"
338
     *
339
     * @param string $format Output format
340
     *
341
     * @return void
342
     *
343
     * @throws BuildException When the format is not supported
344
     */
345 6
    public function setFormat($format)
346
    {
347 6
        if (!in_array($format, self::$supportedFormats)) {
348 1
            throw new BuildException(
349 1
                sprintf(
350 1
                    'Invalid output format "%s", allowed are: %s',
351 1
                    $format,
352 1
                    implode(', ', self::$supportedFormats)
353
                )
354
            );
355
        }
356 5
        $this->format = $format;
357 5
    }
358
359
    /**
360
     * The setter for the attribute "destination"
361
     *
362
     * @param string $destination Output file or directory. When it ends
363
     *                            with a slash, it is taken as directory.
364
     *
365
     * @return void
366
     */
367 5
    public function setDestination($destination)
368
    {
369 5
        $this->destination = $destination;
370 5
    }
371
372
    /**
373
     * The setter for the attribute "toolparam"
374
     *
375
     * @param string $param Additional rst2* tool parameters
376
     *
377
     * @return void
378
     */
379 1
    public function setToolparam($param)
380
    {
381 1
        $this->toolParam = $param;
382 1
    }
383
384
    /**
385
     * The setter for the attribute "toolpath"
386
     *
387
     * @param    $path
388
     * @throws   BuildException
389
     * @internal param string $param Full path to tool path, i.e. /usr/local/bin/rst2html
390
     *
391
     * @return void
392
     */
393 3
    public function setToolpath($path)
394
    {
395 3
        if (!file_exists($path)) {
396 2
            $fs = FileSystem::getFileSystem();
397 2
            $fullpath = $fs->which($path);
398 2
            if ($fullpath === false) {
0 ignored issues
show
introduced by
The condition $fullpath === false is always false.
Loading history...
399 1
                throw new BuildException(
400 1
                    'Tool does not exist. Path: ' . $path
401
                );
402
            }
403 1
            $path = $fullpath;
404
        }
405 2
        if (!is_executable($path)) {
406 1
            throw new BuildException(
407 1
                'Tool not executable. Path: ' . $path
408
            );
409
        }
410 1
        $this->toolPath = $path;
411 1
    }
412
413
    /**
414
     * The setter for the attribute "uptodate"
415
     *
416
     * @param string $uptodate True/false
417
     *
418
     * @return void
419
     */
420 1
    public function setUptodate($uptodate)
421
    {
422 1
        $this->uptodate = (bool) $uptodate;
423 1
    }
424
425
    /**
426
     * Nested creator, creates one Mapper for this task
427
     *
428
     * @return Mapper The created Mapper type object
429
     *
430
     * @throws BuildException
431
     */
432 3
    public function createMapper()
433
    {
434 3
        if ($this->mapperElement !== null) {
435 1
            throw new BuildException(
436 1
                'Cannot define more than one mapper',
437 1
                $this->getLocation()
438
            );
439
        }
440 3
        $this->mapperElement = new Mapper($this->project);
441
442 3
        return $this->mapperElement;
443
    }
444
}
445