diff --git a/README.md b/README.md index 82f7c87..032abe0 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,25 @@ The `initial` hash lists files that should be copied over only if they do not exist in the destination. The key specifies the path to the source file, and the value indicates the path to the destination file. +## Changing destination paths + +By using an associative array for `includes`, destination paths can be specified +that are different than the source paths. For example, if you wanted to install +`example.settings.local.php` into `sites/default/` instead of `sites/`: + +```json +{ + "extra": { + "drupal-scaffold": { + "source": "http://cgit.drupalcode.org/drupal/plain/{path}?h={version}", + "includes": { + "sites/example.settings.local.php": "sites/default/example.settings.local.php" + }, + } + } +} +``` + ## Limitation When using Composer to install or update the Drupal development branch, the diff --git a/src/FileFetcher.php b/src/FileFetcher.php index 4435306..78e6011 100644 --- a/src/FileFetcher.php +++ b/src/FileFetcher.php @@ -29,8 +29,9 @@ public function __construct(RemoteFilesystem $remoteFilesystem, $source, $filena } public function fetch($version, $destination) { - array_walk($this->filenames, function ($filename) use ($version, $destination) { - $url = $this->getUri($filename, $version); + array_walk($this->filenames, function ($filename, $sourceFilename) use ($version, $destination) { + $sourceFilename = is_numeric($sourceFilename) ? $filename : $sourceFilename; + $url = $this->getUri($sourceFilename, $version); $this->fs->ensureDirectoryExists($destination . '/' . dirname($filename)); $this->remoteFilesystem->copy($url, $url, $destination . '/' . $filename); }); diff --git a/src/Handler.php b/src/Handler.php index b6ca659..dce0389 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -103,7 +103,7 @@ public function downloadScaffold() { // Collect options, excludes and settings files. $options = $this->getOptions(); - $files = array_diff($this->getIncludes(), $this->getExcludes()); + $files = $this->getFiles(); // Call any pre-scaffold scripts that may be defined. $dispatcher = new EventDispatcher($this->composer, $this->io); @@ -240,6 +240,10 @@ protected function getPackage($name) { return $this->composer->getRepositoryManager()->getLocalRepository()->findPackage($name, '*'); } + protected function getFiles() { + return array_diff_key($this->getIncludes(), $this->getExcludes()); + } + /** * Retrieve excludes from optional "extra" configuration. * @@ -280,7 +284,15 @@ protected function getNamedOptionList($optionName, $defaultFn) { if (empty($options['omit-defaults'])) { $result = $this->$defaultFn(); } - $result = array_merge($result, (array) $options[$optionName]); + foreach ((array)$options[$optionName] as $sourceFile => $destFile) { + // Allow any option list to be specified as a simple array, or an + // associative array specifying source and destination. Convert + // simple arrays to associative arrays. + if(is_numeric($sourceFile)) { + $sourceFile = $destFile; + } + $result[$sourceFile] = $destFile; + } return $result; } @@ -352,7 +364,7 @@ protected function getIncludesDefault() { } sort($common); - return $common; + return array_combine($common, $common); } /** diff --git a/src/PrestissimoFileFetcher.php b/src/PrestissimoFileFetcher.php index a99aa1d..9ff97d4 100644 --- a/src/PrestissimoFileFetcher.php +++ b/src/PrestissimoFileFetcher.php @@ -40,8 +40,9 @@ public function fetch($version, $destination) { protected function fetchWithPrestissimo($version, $destination) { $requests = []; - array_walk($this->filenames, function ($filename) use ($version, $destination, &$requests) { - $url = $this->getUri($filename, $version); + array_walk($this->filenames, function ($filename, $sourceFilename) use ($version, $destination, &$requests) { + $sourceFilename = is_numeric($sourceFilename) ? $filename : $sourceFilename; + $url = $this->getUri($sourceFilename, $version); $this->fs->ensureDirectoryExists($destination . '/' . dirname($filename)); $requests[] = new CopyRequest($url, $destination . '/' . $filename, false, $this->io, $this->config); }); diff --git a/tests/FetcherTest.php b/tests/FetcherTest.php index 39b3e73..64c8acb 100644 --- a/tests/FetcherTest.php +++ b/tests/FetcherTest.php @@ -91,6 +91,12 @@ public function testFetchVersionSpecific() { $this->assertFileNotExists($this->tmpDir . '/.eslintrc'); } + public function testFetchesToSpecificDestination() { + $fetcher = new FileFetcher(new RemoteFilesystem(new NullIO()), 'http://cgit.drupalcode.org/drupal/plain/{path}?h={version}', ['sites/example.settings.local.php' => 'sites/default/example.settings.local.php']); + $fetcher->fetch('8.2.x', $this->tmpDir); + $this->assertFileExists($this->tmpDir .'/sites/default/example.settings.local.php'); + } + public function testInitialFetch() { $fetcher = new InitialFileFetcher(new RemoteFilesystem(new NullIO()), 'http://cgit.drupalcode.org/drupal/plain/{path}?h={version}', ['sites/default/default.settings.php' => 'sites/default/settings.php']); $fetcher->fetch('8.1.1', $this->tmpDir); diff --git a/tests/HandlerTest.php b/tests/HandlerTest.php new file mode 100644 index 0000000..bb8c186 --- /dev/null +++ b/tests/HandlerTest.php @@ -0,0 +1,234 @@ + '.csslintrc', + '.editorconfig' => '.editorconfig', + '.eslintignore' => '.eslintignore', + '.eslintrc.json' => '.eslintrc.json', + '.gitattributes' => '.gitattributes', + '.htaccess' => '.htaccess', + 'index.php' => 'index.php', + 'robots.txt' => 'robots.txt', + 'sites/default/default.services.yml' => 'sites/default/default.services.yml', + 'sites/default/default.settings.php' => 'sites/default/default.settings.php', + 'sites/development.services.yml' => 'sites/development.services.yml', + 'sites/example.settings.local.php' => 'sites/example.settings.local.php', + 'sites/example.sites.php' => 'sites/example.sites.php', + 'update.php' => 'update.php', + 'web.config' => 'web.config', + ]; + + private function getComposer($drupalVersion, array $extra = []) { + $package = new RootPackage('test', '1.0.0', '1.0.0'); + $package->setExtra($extra); + $composer = new Composer(); + $composer->setPackage($package); + + $io = new NullIO(); + $config = new Config(false); + + $drupalPackage = new Package('drupal/core', $drupalVersion, $drupalVersion); + $localRepository = new WritableArrayRepository(); + $localRepository->addPackage($drupalPackage); + + $repositoryManager = new RepositoryManager($io, $config); + $repositoryManager->setLocalRepository($localRepository); + $composer->setRepositoryManager($repositoryManager); + + return $composer; + } + + public function getGetIncludesTests() { + return [ + [ + '8.4.2', + [], + self::$eightFourTwoIncludes + ], + [ + '8.4.2', + [ + 'drupal-scaffold' => [ + 'includes' => ['.csslintrc'] + ] + ], + self::$eightFourTwoIncludes + ], + [ + '8.4.2', + [ + 'drupal-scaffold' => [ + 'includes' => ['.csslintrc' => 'foo'] + ] + ], + ['.csslintrc' => 'foo'] + self::$eightFourTwoIncludes + ], + ]; + } + + /** + * @dataProvider getGetIncludesTests + */ + public function testGetIncludes($drupalVersion, $extra, $expected) { + $handler = new DummyHandler($this->getComposer($drupalVersion, $extra), new NullIO()); + $actual = $handler->doGetIncludes(); + $this->assertEquals($expected, $actual); + } + + public function getGetInitialTests() { + return [ + ['8.4.2', []] + ]; + } + + /** + * @dataProvider getGetInitialTests + */ + public function testGetInitial($drupalVersion, $expected) { + $io = $this->prophesize(IOInterface::class); + + $handler = new DummyHandler($this->getComposer($drupalVersion), $io->reveal()); + $actual = $handler->doGetInitial(); + $this->assertEquals($expected, $actual); + } + + public function getGetExcludesTests() { + return [ + ['8.4.2', []] + ]; + } + + /** + * @dataProvider getGetExcludesTests + */ + public function testGetExcludes($drupalVersion, $expected) { + $io = $this->prophesize(IOInterface::class); + + $handler = new DummyHandler($this->getComposer($drupalVersion), $io->reveal()); + $actual = $handler->doGetExcludes(); + $this->assertEquals($expected, $actual); + } + + public function getGetFilesTests() { + return [ + [ + '8.4.2', + [], + self::$eightFourTwoIncludes, + 'Default includes are returned if no excludes are specified.' + ], + [ + '8.4.2', + [ + 'drupal-scaffold' => [ + 'excludes' => ['.csslintrc'] + ] + ], + array_diff_key(self::$eightFourTwoIncludes, ['.csslintrc' => NULL]), + 'Excludes are removed from files when they are specified as a simple array', + ], + [ + '8.4.2', + // Nobody will do this, but we want to make sure it doesn't fail. + [ + 'drupal-scaffold' => [ + 'excludes' => [ + '.csslintrc' => 'foo' + ] + ] + ], + array_diff_key(self::$eightFourTwoIncludes, ['.csslintrc' => '']), + 'Excludes are removed from files when they are specified as an associative array', + ], + [ + '8.4.2', + ['drupal-scaffold' => ['omit-defaults' => true]], + [], + 'Defaults can be omitted.', + ], + [ + '8.4.2', + [ + 'drupal-scaffold' => [ + 'omit-defaults' => true, + 'includes' => [ + 'foo' => 'bar', + ], + ] + ], + ['foo' => 'bar'], + 'New includes will be considered' + ], + [ + '8.4.2', + [ + 'drupal-scaffold' => [ + 'omit-defaults' => true, + 'includes' => [ + 'foo' => 'bar', + ], + 'excludes' => ['foo'], + ] + ], + [], + 'New includes will be considered for exclusion' + ] + ]; + } + + /** + * Test that the getFiles method considers excludes. + * + * @dataProvider getGetFilesTests + */ + public function testGetFiles($drupalVersion, $extra, $expected, $message = '') { + $io = $this->prophesize(IOInterface::class); + + $handler = new DummyHandler($this->getComposer($drupalVersion, $extra), $io->reveal()); + $actual = $handler->doGetFiles(); + $this->assertEquals($expected, $actual, $message); + } + +} + +/** + * Extends handler to expose public methods that can be used for testing + * internal behavior. + */ +class DummyHandler extends Handler { + + public function doGetFiles() { + return $this->getFiles(); + } + + public function doGetIncludes() { + return $this->getIncludes(); + } + + public function doGetExcludes() { + return $this->getExcludes(); + } + + public function doGetInitial() { + return $this->getInitial(); + } +}