3 namespace Robo\Task\Filesystem;
6 use Robo\Exception\TaskException;
7 use Symfony\Component\Finder\Finder;
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).
14 * Example directory structure:
20 * │ └── asset-library1.min.js
23 * └── asset-library2.min.js
26 * The following code will search the `*.min.js` files and copy them
27 * inside a new `dist` folder:
31 * $this->taskFlattenDir(['assets/*.min.js' => 'dist'])->run();
33 * $this->_flattenDir('assets/*.min.js', 'dist');
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:
42 * $this->taskFlattenDir(['assets/*.min.js'])
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`.
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`.
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`.
62 * $this->taskFlattenDir(['assets/*.min.js' => 'dist'])
63 * ->parentDir('assets')
69 class FlattenDir extends BaseDir
74 protected $chmod = 0755;
79 protected $parents = array(0, 0);
84 protected $parentDir = '';
94 public function __construct($dirs)
96 parent::__construct($dirs);
97 $this->parentDir = getcwd();
103 public function run()
106 $files = $this->findFiles($this->dirs);
109 $this->copyFiles($files);
111 $fileNoun = count($files) == 1 ? ' file' : ' files';
112 $this->printTaskSuccess("Copied {count} $fileNoun to {destination}", ['count' => count($files), 'destination' => $this->to]);
114 return Result::success($this);
118 * Sets the default folder permissions for the destination if it does not exist.
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
124 * @param int $permission
128 public function dirPermissions($permission)
130 $this->chmod = (int) $permission;
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.
139 * @param int|int[] $parents
143 * @throws TaskException
145 public function includeParents($parents)
147 if (is_int($parents)) {
148 // if an integer is given check whether it is for top or bottom parent
150 $this->parents[0] = $parents;
153 $this->parents[1] = 0 - $parents;
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;
165 throw new TaskException($this, 'includeParents expects an integer or an array with two values');
169 * Sets the parent directory from which the relative parent directories will be calculated.
175 public function parentDir($dir)
177 if (!$this->fs->isAbsolutePath($dir)) {
178 // attach the relative path to current working directory
179 $dir = getcwd().'/'.$dir;
181 $this->parentDir = $dir;
187 * Sets the target directory where the files will be copied to.
189 * @param string $target
193 public function to($target)
195 $this->to = rtrim($target, '/');
203 * @return array|\Robo\Result
205 * @throws \Robo\Exception\TaskException
207 protected function findFiles($dirs)
212 foreach ($dirs as $k => $v) {
214 $finder = new Finder();
218 // check if target was given with the to() method instead of key/value pairs
221 if (isset($this->to)) {
224 throw new TaskException($this, 'target directory is not defined');
229 $finder->files()->in($dir);
230 } catch (\InvalidArgumentException $e) {
231 // if finder cannot handle it, try with in()->name()
232 if (strpos($dir, '/') === false) {
235 $parts = explode('/', $dir);
236 $new_dir = implode('/', array_slice($parts, 0, -1));
238 $finder->files()->in($new_dir)->name(array_pop($parts));
239 } catch (\InvalidArgumentException $e) {
240 return Result::fromException($this, $e);
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);
248 $fileNoun = count($files) == 1 ? ' file' : ' files';
249 $this->printTaskInfo("Found {count} $fileNoun in {dir}", ['count' => count($files), 'dir' => $dir]);
256 * @param string $file
261 protected function getTarget($file, $to)
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, '/'));
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);
281 * @param array $files
283 protected function copyFiles($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);
291 $this->fs->copy($from, $to);