ExecTask::getPath()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 13
ccs 0
cts 7
cp 0
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 12
1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Task\System;
22
23
use InvalidArgumentException;
24
use Phing\Exception\BuildException;
25
use Phing\Io\File;
26
use Phing\Io\FileUtils;
27
use Phing\Io\IOException;
28
use Phing\Phing;
29
use Phing\Project;
30
use Phing\Task;
31
use Phing\Task\System\Condition\OsCondition;
32
use Phing\Task\System\Element\LogLevelAware;
33
use Phing\Type\Commandline;
34
use Phing\Type\CommandlineArgument;
35
use Phing\Type\Environment;
36
use Phing\Type\EnvVariable;
37
use Phing\Type\Path;
38
use Phing\Util\StringHelper;
39
40
/**
41
 * Executes a command on the shell.
42
 *
43
 * @author  Andreas Aderhold <[email protected]>
44
 * @author  Hans Lellelid <[email protected]>
45
 * @author  Christian Weiske <[email protected]>
46
 */
47
class ExecTask extends Task
48
{
49
    use LogLevelAware;
50
51
    public const INVALID = PHP_INT_MAX;
52
53
    /**
54
     * Command to be executed.
55
     *
56
     * @var string
57
     */
58
    protected $realCommand;
59
60
    /**
61
     * Commandline managing object.
62
     *
63
     * @var Commandline
64
     */
65
    protected $commandline;
66
67
    /**
68
     * Working directory.
69
     *
70
     * @var File
71
     */
72
    protected $dir;
73
74
    protected $currdir;
75
76
    /**
77
     * Operating system.
78
     *
79
     * @var string
80
     */
81
    protected $os;
82
83
    /**
84
     * Whether to escape shell command using escapeshellcmd().
85
     *
86
     * @var bool
87
     */
88
    protected $escape = false;
89
90
    /**
91
     * Where to direct output.
92
     *
93
     * @var File
94
     */
95
    protected $output;
96
97
    /**
98
     * Whether to use PHP's passthru() function instead of exec().
99
     *
100
     * @var bool
101
     */
102
    protected $passthru = false;
103
104
    /**
105
     * Whether to log returned output as MSG_INFO instead of MSG_VERBOSE.
106
     *
107
     * @var bool
108
     */
109
    protected $logOutput = false;
110
111
    /**
112
     * Where to direct error output.
113
     *
114
     * @var File
115
     */
116
    protected $error;
117
118
    /**
119
     * If spawn is set then [unix] programs will redirect stdout and add '&'.
120
     *
121
     * @var bool
122
     */
123
    protected $spawn = false;
124
125
    /**
126
     * Property name to set with return value from exec call.
127
     *
128
     * @var string
129
     */
130
    protected $returnProperty;
131
132
    /**
133
     * Property name to set with output value from exec call.
134
     *
135
     * @var string
136
     */
137
    protected $outputProperty;
138
139
    /**
140
     * Whether to check the return code.
141
     *
142
     * @var bool
143
     */
144
    protected $checkreturn = false;
145
146
    private $exitValue = self::INVALID;
147
148
    private $osFamily;
149
    private $executable;
150
    private $resolveExecutable = false;
151
    private $searchPath = false;
152
    private $env;
153
154
    /**
155
     * @throws BuildException
156
     */
157 91
    public function __construct()
158
    {
159 91
        parent::__construct();
160 91
        $this->commandline = new Commandline();
161 91
        $this->env = new Environment();
162
    }
163
164
    /**
165
     * Main method: wraps execute() command.
166
     *
167
     * @throws BuildException
168
     */
169 36
    public function main()
170
    {
171 36
        if (!$this->isValidOs()) {
172 1
            return null;
173
        }
174
175
        // Suggest Task instead of executable
176 34
        if ($this->executable && ($hint = $this->findHint($this->executable))) {
177 13
            $this->log($hint, Project::MSG_VERBOSE);
178
        }
179
180
        try {
181 34
            $this->commandline->setExecutable($this->resolveExecutable($this->executable, $this->searchPath));
182
        } catch (IOException | \InvalidArgumentException $e) {
183
            throw new BuildException($e);
184
        }
185
186 34
        $this->prepare();
187 32
        $this->buildCommand();
188 32
        [$return, $output] = $this->executeCommand();
189 32
        $this->cleanup($return, $output);
190
191 31
        return $return;
192
    }
193
194
    /**
195
     * @param int $exitValue
196
     *
197
     * @return bool
198
     */
199 1
    public function isFailure($exitValue = null)
200
    {
201 1
        if (null === $exitValue) {
202
            $exitValue = $this->getExitValue();
203
        }
204
205 1
        return 0 !== $exitValue;
206
    }
207
208
    /**
209
     * Query the exit value of the process.
210
     *
211
     * @return int the exit value or self::INVALID if no exit value has
212
     *             been received
213
     */
214
    public function getExitValue(): int
215
    {
216
        return $this->exitValue;
217
    }
218
219
    /**
220
     * The executable to use.
221
     *
222
     * @param bool|string $value String or string-compatible (e.g. w/ __toString()).
223
     */
224 89
    public function setExecutable($value): void
225
    {
226 89
        if (is_bool($value)) {
227 3
            $value = true === $value ? 'true' : 'false';
228
        }
229 89
        $this->executable = $value;
230 89
        $this->commandline->setExecutable($value);
231
    }
232
233
    /**
234
     * Whether to use escapeshellcmd() to escape command.
235
     *
236
     * @param bool $escape If the command shall be escaped or not
237
     */
238 7
    public function setEscape(bool $escape): void
239
    {
240 7
        $this->escape = $escape;
241
    }
242
243
    /**
244
     * Specify the working directory for executing this command.
245
     *
246
     * @param File $dir Working directory
247
     */
248 9
    public function setDir(File $dir): void
249
    {
250 9
        $this->dir = $dir;
251
    }
252
253
    /**
254
     * Specify OS (or multiple OS) that must match in order to execute this command.
255
     *
256
     * @param string $os Operating system string (e.g. "Linux")
257
     */
258 6
    public function setOs($os): void
259
    {
260 6
        $this->os = (string) $os;
261
    }
262
263
    /**
264
     * List of operating systems on which the command may be executed.
265
     */
266
    public function getOs(): string
267
    {
268
        return $this->os;
269
    }
270
271
    /**
272
     * Restrict this execution to a single OS Family.
273
     *
274
     * @param string $osFamily the family to restrict to
275
     */
276 2
    public function setOsFamily($osFamily): void
277
    {
278 2
        $this->osFamily = strtolower($osFamily);
279
    }
280
281
    /**
282
     * Restrict this execution to a single OS Family.
283
     */
284
    public function getOsFamily()
285
    {
286
        return $this->osFamily;
287
    }
288
289
    /**
290
     * File to which output should be written.
291
     *
292
     * @param File $f Output log file
293
     */
294 6
    public function setOutput(File $f): void
295
    {
296 6
        $this->output = $f;
297
    }
298
299
    /**
300
     * File to which error output should be written.
301
     *
302
     * @param File $f Error log file
303
     */
304 4
    public function setError(File $f): void
305
    {
306 4
        $this->error = $f;
307
    }
308
309
    /**
310
     * Whether to use PHP's passthru() function instead of exec().
311
     *
312
     * @param bool $passthru If passthru shall be used
313
     */
314 11
    public function setPassthru(bool $passthru): void
315
    {
316 11
        $this->passthru = $passthru;
317
    }
318
319
    /**
320
     * Whether to log returned output as MSG_INFO instead of MSG_VERBOSE.
321
     *
322
     * @param bool $logOutput If output shall be logged visibly
323
     */
324 1
    public function setLogoutput($logOutput): void
325
    {
326 1
        $this->logOutput = $logOutput;
327
    }
328
329
    /**
330
     * Whether to suppress all output and run in the background.
331
     *
332
     * @param bool $spawn If the command is to be run in the background
333
     */
334 4
    public function setSpawn($spawn): void
335
    {
336 4
        $this->spawn = $spawn;
337
    }
338
339
    /**
340
     * Whether to check the return code.
341
     *
342
     * @param bool $checkreturn If the return code shall be checked
343
     */
344 17
    public function setCheckreturn($checkreturn): void
345
    {
346 17
        $this->checkreturn = $checkreturn;
347
    }
348
349
    /**
350
     * The name of property to set to return value from exec() call.
351
     *
352
     * @param string $prop Property name
353
     */
354 5
    public function setReturnProperty($prop): void
355
    {
356 5
        $this->returnProperty = $prop;
357
    }
358
359
    /**
360
     * The name of property to set to output value from exec() call.
361
     *
362
     * @param string $prop Property name
363
     */
364 8
    public function setOutputProperty($prop): void
365
    {
366 8
        $this->outputProperty = $prop;
367
    }
368
369
    /**
370
     * Add an environment variable to the launched process.
371
     *
372
     * @param EnvVariable $var new environment variable
373
     */
374 2
    public function addEnv(EnvVariable $var)
375
    {
376 2
        $this->env->addVariable($var);
377
    }
378
379
    /**
380
     * Creates a nested <arg> tag.
381
     *
382
     * @return CommandlineArgument Argument object
383
     */
384 58
    public function createArg()
385
    {
386 58
        return $this->commandline->createArgument();
387
    }
388
389
    /**
390
     * Set whether to attempt to resolve the executable to a file.
391
     *
392
     * @param bool $resolveExecutable if true, attempt to resolve the
393
     *                                path of the executable
394
     */
395
    public function setResolveExecutable($resolveExecutable): void
396
    {
397
        $this->resolveExecutable = $resolveExecutable;
398
    }
399
400
    /**
401
     * Set whether to search nested, then
402
     * system PATH environment variables for the executable.
403
     *
404
     * @param bool $searchPath if true, search PATHs
405
     */
406
    public function setSearchPath($searchPath): void
407
    {
408
        $this->searchPath = $searchPath;
409
    }
410
411
    /**
412
     * Indicates whether to attempt to resolve the executable to a
413
     * file.
414
     *
415
     * @return bool the resolveExecutable flag
416
     */
417
    public function getResolveExecutable(): bool
418
    {
419
        return $this->resolveExecutable;
420
    }
421
422
    /**
423
     * Prepares the command building and execution, i.e.
424
     * changes to the specified directory.
425
     *
426
     * @throws BuildException
427
     */
428 34
    protected function prepare()
429
    {
430 34
        if (null === $this->dir) {
431 31
            $this->dir = $this->getProject()->getBasedir();
432
        }
433
434 34
        if (null === $this->commandline->getExecutable()) {
0 ignored issues
show
introduced by
The condition null === $this->commandline->getExecutable() is always false.
Loading history...
435 1
            throw new BuildException(
436 1
                'ExecTask: Please provide "executable"'
437 1
            );
438
        }
439
440
        // expand any symbolic links first
441
        try {
442 33
            if (!$this->dir->getCanonicalFile()->exists()) {
443 1
                throw new BuildException(
444 1
                    "The directory '" . (string) $this->dir . "' does not exist"
445 1
                );
446
            }
447 32
            if (!$this->dir->getCanonicalFile()->isDirectory()) {
448 32
                throw new BuildException(
449 32
                    "'" . (string) $this->dir . "' is not a directory"
450 32
                );
451
            }
452 1
        } catch (IOException $e) {
453
            throw new BuildException(
454
                "'" . (string) $this->dir . "' is not a readable directory"
455
            );
456
        }
457 32
        $this->currdir = getcwd();
458 32
        @chdir($this->dir->getPath());
459
460 32
        $this->commandline->setEscape($this->escape);
461
    }
462
463
    /**
464
     * Builds the full command to execute and stores it in $command.
465
     *
466
     * @throws BuildException
467
     *
468
     * @uses   $command
469
     */
470 32
    protected function buildCommand()
471
    {
472 32
        $this->realCommand = '';
473
474 32
        if (null !== $this->error) {
475 1
            $this->realCommand .= ' 2> ' . escapeshellarg($this->error->getPath());
476 1
            $this->log(
477 1
                'Writing error output to: ' . $this->error->getPath(),
478 1
                $this->logLevel
479 1
            );
480
        }
481
482 32
        if (null !== $this->output) {
483 1
            $this->realCommand .= ' 1> ' . escapeshellarg($this->output->getPath());
484 1
            $this->log(
485 1
                'Writing standard output to: ' . $this->output->getPath(),
486 1
                $this->logLevel
487 1
            );
488 31
        } elseif ($this->spawn) {
489 1
            $this->realCommand .= ' 1>/dev/null';
490 1
            $this->log('Sending output to /dev/null', $this->logLevel);
491
        }
492
493
        // If neither output nor error are being written to file
494
        // then we'll redirect error to stdout so that we can dump
495
        // it to screen below.
496
497 32
        if (null === $this->output && null === $this->error && false === $this->passthru) {
498 29
            $this->realCommand .= ' 2>&1';
499
        }
500
501
        // we ignore the spawn bool for windows
502 32
        if ($this->spawn) {
503 1
            $this->realCommand .= ' &';
504
        }
505
506 32
        $envString = '';
507 32
        $environment = $this->env->getVariables();
508 32
        if (null !== $environment) {
0 ignored issues
show
introduced by
The condition null !== $environment is always true.
Loading history...
509 2
            foreach ($environment as $variable) {
510 2
                if ($this->isPath($variable)) {
511
                    continue;
512
                }
513 2
                $this->log('Setting environment variable: ' . $variable, Project::MSG_VERBOSE);
514 2
                if (OsCondition::isOS(OsCondition::FAMILY_WINDOWS)) {
515
                    $envString .= 'set ' . $variable . '& ';
516
                } else {
517 2
                    $envString .= 'export ' . $variable . '; ';
518
                }
519
            }
520
        }
521
522 32
        $this->realCommand = $envString . $this->commandline . $this->realCommand;
523
    }
524
525
    /**
526
     * Executes the command and returns return code and output.
527
     *
528
     * @throws BuildException
529
     *
530
     * @return array array(return code, array with output)
531
     */
532 50
    protected function executeCommand()
533
    {
534 50
        $cmdl = $this->realCommand;
535
536 50
        $this->log('Executing command: ' . $cmdl, $this->logLevel);
537
538 50
        $output = [];
539 50
        $return = null;
540
541 50
        if ($this->passthru) {
542 2
            passthru($cmdl, $return);
543
        } else {
544 48
            exec($cmdl, $output, $return);
545
        }
546
547 50
        return [$return, $output];
548
    }
549
550
    /**
551
     * Runs all tasks after command execution:
552
     * - change working directory back
553
     * - log output
554
     * - verify return value.
555
     *
556
     * @param int   $return Return code
557
     * @param array $output Array with command output
558
     *
559
     * @throws BuildException
560
     */
561 32
    protected function cleanup($return, $output): void
562
    {
563 32
        if (null !== $this->dir) {
564 32
            @chdir($this->currdir);
565
        }
566
567 32
        $outloglevel = $this->logOutput ? Project::MSG_INFO : Project::MSG_VERBOSE;
568 32
        foreach ($output as $line) {
569 25
            $this->log($line, $outloglevel);
570
        }
571
572 32
        $this->maybeSetReturnPropertyValue($return);
573
574 32
        if ($this->outputProperty) {
575 4
            $this->project->setProperty(
576 4
                $this->outputProperty,
577 4
                implode("\n", $output)
578 4
            );
579
        }
580
581 32
        $this->setExitValue($return);
582
583 32
        if (0 !== $return) {
584 15
            if ($this->checkreturn) {
585 1
                throw new BuildException($this->getTaskType() . ' returned: ' . $return, $this->getLocation());
586
            }
587 14
            $this->log('Result: ' . $return, Project::MSG_ERR);
588
        }
589
    }
590
591
    /**
592
     * Set the exit value.
593
     *
594
     * @param int $value exit value of the process
595
     */
596 32
    protected function setExitValue($value): void
597
    {
598 32
        $this->exitValue = $value;
599
    }
600
601 50
    protected function maybeSetReturnPropertyValue(int $return)
602
    {
603 50
        if ($this->returnProperty) {
604 3
            $this->getProject()->setNewProperty($this->returnProperty, $return);
605
        }
606
    }
607
608
    /**
609
     * Is this the OS the user wanted?
610
     *
611
     * @return bool.
0 ignored issues
show
Documentation Bug introduced by
The doc comment bool. at position 0 could not be parsed: Unknown type name 'bool.' at position 0 in bool..
Loading history...
612
     *               <ul>
613
     *               <li>
614
     *               <li><code>true</code> if the os and osfamily attributes are null.</li>
615
     *               <li><code>true</code> if osfamily is set, and the os family and must match
616
     *               that of the current OS, according to the logic of
617
     *               {@link Os#isOs(String, String, String, String)}, and the result of the
618
     *               <code>os</code> attribute must also evaluate true.
619
     *               </li>
620
     *               <li>
621
     *               <code>true</code> if os is set, and the system.property os.name
622
     *               is found in the os attribute,</li>
623
     *               <li><code>false</code> otherwise.</li>
624
     *               </ul>
625
     */
626 55
    protected function isValidOs(): bool
627
    {
628
        //hand osfamily off to OsCondition class, if set
629 55
        if (null !== $this->osFamily && !OsCondition::isFamily($this->osFamily)) {
630
            return false;
631
        }
632
        //the Exec OS check is different from Os.isOs(), which
633
        //probes for a specific OS. Instead it searches the os field
634
        //for the current os.name
635 54
        $myos = Phing::getProperty('os.name');
636 54
        $this->log('Current OS is ' . $myos, Project::MSG_VERBOSE);
637 54
        if ((null !== $this->os) && (false === strpos($this->os, $myos))) {
638
            // this command will be executed only on the specified OS
639 2
            $this->log(
640 2
                'This OS, ' . $myos
641 2
                . ' was not found in the specified list of valid OSes: ' . $this->os,
642 2
                Project::MSG_VERBOSE
643 2
            );
644
645 2
            return false;
646
        }
647
648 52
        return true;
649
    }
650
651
    /**
652
     * The method attempts to figure out where the executable is so that we can feed
653
     * the full path. We first try basedir, then the exec dir, and then
654
     * fallback to the straight executable name (i.e. on the path).
655
     *
656
     * @param string $exec           the name of the executable
657
     * @param bool   $mustSearchPath if true, the executable will be looked up in
658
     *                               the PATH environment and the absolute path
659
     *                               is returned
660
     *
661
     * @throws BuildException
662
     * @throws IOException
663
     *
664
     * @return string the executable as a full path if it can be determined
665
     */
666 34
    protected function resolveExecutable($exec, $mustSearchPath): ?string
667
    {
668 34
        if (!$this->resolveExecutable) {
669 34
            return $exec;
670
        }
671
        // try to find the executable
672
        $executableFile = $this->getProject()->resolveFile($exec);
673
        if ($executableFile->exists()) {
674
            return $executableFile->getAbsolutePath();
675
        }
676
        // now try to resolve against the dir if given
677
        if (null !== $this->dir) {
678
            $executableFile = (new FileUtils())->resolveFile($this->dir, $exec);
679
            if ($executableFile->exists()) {
680
                return $executableFile->getAbsolutePath();
681
            }
682
        }
683
        // couldn't find it - must be on path
684
        if ($mustSearchPath) {
685
            $p = null;
686
            $environment = $this->env->getVariables();
687
            if (null !== $environment) {
0 ignored issues
show
introduced by
The condition null !== $environment is always true.
Loading history...
688
                foreach ($environment as $env) {
689
                    if ($this->isPath($env)) {
690
                        $p = new Path($this->getProject(), $this->getPath($env));
691
692
                        break;
693
                    }
694
                }
695
            }
696
            if (null === $p) {
697
                $p = new Path($this->getProject(), getenv('path'));
698
            }
699
            if (null !== $p) {
700
                $dirs = $p->listPaths();
701
                foreach ($dirs as $dir) {
702
                    $executableFile = (new FileUtils())->resolveFile(new File($dir), $exec);
703
                    if ($executableFile->exists()) {
704
                        return $executableFile->getAbsolutePath();
705
                    }
706
                }
707
            }
708
        }
709
710
        return $exec;
711
    }
712
713 2
    private function isPath($line)
714
    {
715 2
        return StringHelper::startsWith('PATH=', $line) || StringHelper::startsWith('Path=', $line);
716
    }
717
718
    private function getPath($value)
719
    {
720
        if (is_string($value)) {
721
            return StringHelper::substring($value, strlen('PATH='));
722
        }
723
724
        if (is_array($value)) {
725
            $p = $value['PATH'];
726
727
            return $p ?? $value['Path'];
728
        }
729
730
        throw new InvalidArgumentException('$value should be of type array or string.');
731
    }
732
733
    /**
734
     * Give a Task as an alternative to executable
735
     */
736 28
    public function findHint(string $executable): ?string
737
    {
738
        switch ($executable) {
739 28
            case '/usr/bin/mkdir':
740 28
            case 'mkdir':
741
                $hint = 'Consider using MkdirTask https://www.phing.info/guide/chunkhtml/MkdirTask.html';
742
                break;
743 28
            case '/usr/bin/touch':
744 28
            case 'touch':
745
                $hint = 'Consider using TouchTask https://www.phing.info/guide/chunkhtml/TouchTask.html';
746
                break;
747 28
            case '/usr/bin/truncate':
748 28
            case 'truncate':
749
                $hint = 'Consider using TruncateTask https://www.phing.info/guide/chunkhtml/TruncateTask.html';
750
                break;
751 28
            case '/usr/bin/xsltproc':
752 28
            case 'xsltproc':
753
                $hint = 'Consider using XsltTask https://www.phing.info/guide/chunkhtml/XsltTask.html';
754
                break;
755 28
            case '/usr/bin/chmod':
756 28
            case 'chmod':
757
                $hint = 'Consider using ChmodTask https://www.phing.info/guide/chunkhtml/ChmodTask.html';
758
                break;
759 28
            case '/usr/bin/chown':
760 28
            case 'chown':
761
                $hint = 'Consider using ChownTask https://www.phing.info/guide/chunkhtml/ChownTask.html';
762
                break;
763 28
            case '/usr/bin/mv':
764 28
            case 'mv':
765
                $hint = 'Consider using MoveTask https://www.phing.info/guide/chunkhtml/MoveTask.html';
766
                break;
767 28
            case 'sed':
768
                $hint = 'Consider using ReplaceTokens filter https://www.phing.info/guide/chunkhtml/ReplaceTokens.html';
769
                break;
770 28
            case '/usr/bin/rmdir':
771 28
            case 'rmdir':
772 28
            case '/usr/bin/rm':
773 28
            case 'rm':
774 28
            case '/usr/bin/unlink':
775 28
            case 'unlink':
776
                $hint = 'Consider using DeleteTask https://www.phing.info/guide/chunkhtml/DeleteTask.html';
777
                break;
778 28
            case '/usr/bin/sleep':
779 28
            case 'sleep':
780 1
                $hint = 'Consider using SleepTask https://www.phing.info/guide/chunkhtml/SleepTask.html';
781 1
                break;
782 27
            case '/usr/local/bin':
783 27
            case 'ln':
784
                $hint = 'Consider using SymlinkTask https://www.phing.info/guide/chunkhtml/SymlinkTask.html';
785
                break;
786 27
            case '/usr/bin/wget':
787 27
            case 'wget':
788
                $hint = 'Consider using HttpGetTask https://www.phing.info/guide/chunkhtml/HttpGetTask.html';
789
                break;
790 27
            case '/usr/bin/curl':
791 27
            case 'curl':
792 1
                $hint = 'Consider using HttpRequestTask https://www.phing.info/guide/chunkhtml/HttpRequestTask.html';
793 1
                break;
794 26
            case '/usr/bin/xdg-open':
795 26
            case 'xdg-open':
796 26
            case 'wslview':
797 26
            case 'open':
798 26
            case 'start':
799
                $hint = 'Consider using OpenTask https://www.phing.info/guide/chunkhtml/OpenTask.html';
800
                break;
801 26
            case '/usr/bin/zip':
802 26
            case 'zip':
803
                $hint = 'Consider using ZipTask https://www.phing.info/guide/chunkhtml/ZipTask.html';
804
                break;
805 26
            case '/usr/bin/unzip':
806 26
            case 'unzip':
807
                $hint = 'Consider using UnzipTask https://www.phing.info/guide/chunkhtml/UnzipTask.html';
808
                break;
809 26
            case '/usr/bin/tar':
810 26
            case 'tar':
811
                $hint = 'Consider using TarTask https://www.phing.info/guide/chunkhtml/TarTask.html';
812
                break;
813 26
            case '/usr/bin/echo':
814 26
            case 'echo':
815 11
                $hint = 'Consider using EchoTask https://www.phing.info/guide/chunkhtml/EchoTask.html';
816 11
                break;
817
            default:
818 15
                $hint = null;
819 15
                break;
820
        }
821
822 28
        return $hint;
823
    }
824
}
825