More updates to stop using dev or alpha or beta versions.
[yaffs-website] / vendor / consolidation / robo / src / Task / Filesystem / FlattenDir.php
1 <?php
2
3 namespace Robo\Task\Filesystem;
4
5 use Robo\Result;
6 use Robo\Exception\TaskException;
7 use Symfony\Component\Finder\Finder;
8
9 /**
10  * Searches for files in a nested directory structure and copies them to
11  * a target directory with or without the parent directories. The task was
12  * inspired by [gulp-flatten](https://www.npmjs.com/package/gulp-flatten).
13  *
14  * Example directory structure:
15  *
16  * ```
17  * └── assets
18  *     ├── asset-library1
19  *     │   ├── README.md
20  *     │   └── asset-library1.min.js
21  *     └── asset-library2
22  *         ├── README.md
23  *         └── asset-library2.min.js
24  * ```
25  *
26  * The following code will search the `*.min.js` files and copy them
27  * inside a new `dist` folder:
28  *
29  * ``` php
30  * <?php
31  * $this->taskFlattenDir(['assets/*.min.js' => 'dist'])->run();
32  * // or use shortcut
33  * $this->_flattenDir('assets/*.min.js', 'dist');
34  * ?>
35  * ```
36  *
37  * You can also define the target directory with an additional method, instead of
38  * key/value pairs. More similar to the gulp-flatten syntax:
39  *
40  * ``` php
41  * <?php
42  * $this->taskFlattenDir(['assets/*.min.js'])
43  *   ->to('dist')
44  *   ->run();
45  * ?>
46  * ```
47  *
48  * You can also append parts of the parent directories to the target path. If you give
49  * the value `1` to the `includeParents()` method, then the top parent will be appended
50  * to the target directory resulting in a path such as `dist/assets/asset-library1.min.js`.
51  *
52  * If you give a negative number, such as `-1` (the same as specifying `array(0, 1)` then
53  * the bottom parent will be appended, resulting in a path such as
54  * `dist/asset-library1/asset-library1.min.js`.
55  *
56  * The top parent directory will always be starting from the relative path to the current
57  * directory. You can override that with the `parentDir()` method. If in the above example
58  * you would specify `assets`, then the top parent directory would be `asset-library1`.
59  *
60  * ``` php
61  * <?php
62  * $this->taskFlattenDir(['assets/*.min.js' => 'dist'])
63  *   ->parentDir('assets')
64  *   ->includeParents(1)
65  *   ->run();
66  * ?>
67  * ```
68  */
69 class FlattenDir extends BaseDir
70 {
71     /**
72      * @var int
73      */
74     protected $chmod = 0755;
75
76     /**
77      * @var int[]
78      */
79     protected $parents = array(0, 0);
80
81     /**
82      * @var string
83      */
84     protected $parentDir = '';
85
86     /**
87      * @var string
88      */
89     protected $to;
90
91     /**
92      * {@inheritdoc}
93      */
94     public function __construct($dirs)
95     {
96         parent::__construct($dirs);
97         $this->parentDir = getcwd();
98     }
99
100     /**
101      * {@inheritdoc}
102      */
103     public function run()
104     {
105         // find the files
106         $files = $this->findFiles($this->dirs);
107
108         // copy the files
109         $this->copyFiles($files);
110
111         $fileNoun = count($files) == 1 ? ' file' : ' files';
112         $this->printTaskSuccess("Copied {count} $fileNoun to {destination}", ['count' => count($files), 'destination' => $this->to]);
113
114         return Result::success($this);
115     }
116
117     /**
118      * Sets the default folder permissions for the destination if it does not exist.
119      *
120      * @link http://en.wikipedia.org/wiki/Chmod
121      * @link http://php.net/manual/en/function.mkdir.php
122      * @link http://php.net/manual/en/function.chmod.php
123      *
124      * @param int $permission
125      *
126      * @return $this
127      */
128     public function dirPermissions($permission)
129     {
130         $this->chmod = (int) $permission;
131
132         return $this;
133     }
134
135     /**
136      * Sets the value from which direction and how much parent dirs should be included.
137      * Accepts a positive or negative integer or an array with two integer values.
138      *
139      * @param int|int[] $parents
140      *
141      * @return $this
142      *
143      * @throws TaskException
144      */
145     public function includeParents($parents)
146     {
147         if (is_int($parents)) {
148             // if an integer is given check whether it is for top or bottom parent
149             if ($parents >= 0) {
150                 $this->parents[0] = $parents;
151                 return $this;
152             }
153             $this->parents[1] = 0 - $parents;
154             return $this;
155         }
156
157         if (is_array($parents)) {
158             // check if the array has two values no more, no less
159             if (count($parents) == 2) {
160                 $this->parents = $parents;
161                 return $this;
162             }
163         }
164
165         throw new TaskException($this, 'includeParents expects an integer or an array with two values');
166     }
167
168     /**
169      * Sets the parent directory from which the relative parent directories will be calculated.
170      *
171      * @param string $dir
172      *
173      * @return $this
174      */
175     public function parentDir($dir)
176     {
177         if (!$this->fs->isAbsolutePath($dir)) {
178             // attach the relative path to current working directory
179             $dir = getcwd().'/'.$dir;
180         }
181         $this->parentDir = $dir;
182
183         return $this;
184     }
185
186     /**
187      * Sets the target directory where the files will be copied to.
188      *
189      * @param string $target
190      *
191      * @return $this
192      */
193     public function to($target)
194     {
195         $this->to = rtrim($target, '/');
196
197         return $this;
198     }
199
200     /**
201      * @param array $dirs
202      *
203      * @return array|\Robo\Result
204      *
205      * @throws \Robo\Exception\TaskException
206      */
207     protected function findFiles($dirs)
208     {
209         $files = array();
210
211         // find the files
212         foreach ($dirs as $k => $v) {
213             // reset finder
214             $finder = new Finder();
215
216             $dir = $k;
217             $to = $v;
218             // check if target was given with the to() method instead of key/value pairs
219             if (is_int($k)) {
220                 $dir = $v;
221                 if (isset($this->to)) {
222                     $to = $this->to;
223                 } else {
224                     throw new TaskException($this, 'target directory is not defined');
225                 }
226             }
227
228             try {
229                 $finder->files()->in($dir);
230             } catch (\InvalidArgumentException $e) {
231                 // if finder cannot handle it, try with in()->name()
232                 if (strpos($dir, '/') === false) {
233                     $dir = './'.$dir;
234                 }
235                 $parts = explode('/', $dir);
236                 $new_dir = implode('/', array_slice($parts, 0, -1));
237                 try {
238                     $finder->files()->in($new_dir)->name(array_pop($parts));
239                 } catch (\InvalidArgumentException $e) {
240                     return Result::fromException($this, $e);
241                 }
242             }
243
244             foreach ($finder as $file) {
245                 // store the absolute path as key and target as value in the files array
246                 $files[$file->getRealpath()] = $this->getTarget($file->getRealPath(), $to);
247             }
248             $fileNoun = count($files) == 1 ? ' file' : ' files';
249             $this->printTaskInfo("Found {count} $fileNoun in {dir}", ['count' => count($files), 'dir' => $dir]);
250         }
251
252         return $files;
253     }
254
255     /**
256      * @param string $file
257      * @param string $to
258      *
259      * @return string
260      */
261     protected function getTarget($file, $to)
262     {
263         $target = $to.'/'.basename($file);
264         if ($this->parents !== array(0, 0)) {
265             // if the parent is set, create additional directories inside target
266             // get relative path to parentDir
267             $rel_path = $this->fs->makePathRelative(dirname($file), $this->parentDir);
268             // get top parents and bottom parents
269             $parts = explode('/', rtrim($rel_path, '/'));
270             $prefix_dir = '';
271             $prefix_dir .= ($this->parents[0] > 0 ? implode('/', array_slice($parts, 0, $this->parents[0])).'/' : '');
272             $prefix_dir .= ($this->parents[1] > 0 ? implode('/', array_slice($parts, (0 - $this->parents[1]), $this->parents[1])) : '');
273             $prefix_dir = rtrim($prefix_dir, '/');
274             $target = $to.'/'.$prefix_dir.'/'.basename($file);
275         }
276
277         return $target;
278     }
279
280     /**
281      * @param array $files
282      */
283     protected function copyFiles($files)
284     {
285         // copy the files
286         foreach ($files as $from => $to) {
287             // check if target dir exists
288             if (!is_dir(dirname($to))) {
289                 $this->fs->mkdir(dirname($to), $this->chmod);
290             }
291             $this->fs->copy($from, $to);
292         }
293     }
294 }