Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@ This project provides the implementation for Drush site aliases. It is used in D

### Alias naming conventions

Site alias names always begin with a `@`, and typically are divided in two parts: the site name, and the environment name, each separated by a dot. Neither a site name not an enviornment name may contain a dot. An example alias that referenced the `dev` envionment of the site `example` might therefore look something like:
Site alias names always begin with a `@`, and typically are divided in three parts: the alias file location (optional), the site name, and the environment name, each separated by a dot. None of these names may contain a dot. An example alias that referenced the `dev` environment of the site `example` in the `myisp` directory might therefore look something like:
```
@example.dev
@myisp.example.dev
```
The location name is optional. If specified, it will only consider alias files located in directories with the same name as the provided location name. The remainder of the path is immaterial; only the directory that is the immediate parent of the site alias file is relevant. The location name may be omitted, e.g.:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless i'm mistaken, this encourages another level of nesting of aliases. but later in the docs we say that no deep searching is done for alias files. so folks must point explicitly to 'schools' dir?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, with this PR you must explicitly add every new location to your config directory, or the alias will not be found.

We could consider increasing the search depth by one, but for some locations that might be undesirable. We could consider adding a config location /path/to/alias/* to add every directory under a single path. I decided to start with the requirement to add each location explicitly. As you said elsewhere, this is sort of a "Drush for ISPs" feature. Users should only have a handful of ISPs, so unless this feature starts getting used for other purposes, I think the requirement for explicit config is acceptable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note that extra locations may be either inside of sites or next to it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the site-alias project allows deep searching -- it has an API to set the depth of the search. The search depth defaults to current-directory only (no deep searching), and Drush does not set it to allow deeper searching.

```
@example.dev
```
If the location is not specified, then the alias manaager will consider all locations for an applicable site alias file. Note that by default, deep searching is disabled; unless deep searching is enabled, the location name must refer to a directory that is explicitly listed as a location to place site alias files (e.g. in the application's configuration file).

It is also possible to use single-word aliases. These can sometimes be ambiguous; the site alias manager will resolve single-word aliases as follows:

1. `@self` is interpreted to mean the site that has already been selected, or the site that would be selected in the absence of any alias.
Expand All @@ -36,7 +42,7 @@ In the first example, with the site alias appearing before the command name, the

### Alias filenames and locations

It is also up to each individual commandline tool where to search for alias files. Search locations may be added to the SiteAliasManager via an API call. Alias files are only found if they appear immediately inside one of the specified search locations; deep searching is never done.
It is also up to each individual commandline tool where to search for alias files. Search locations may be added to the SiteAliasManager via an API call. By default, alias files are only found if they appear immediately inside one of the specified search locations. Deep searching is only done if explicitly enabled by the application.

Aliases are typically stored in Yaml files, although other formats may also be used if a custom alias data file loader is provided. The extension of the file determines the loader type (.yml for Yaml). The base name of the file, sans its extension, is the site name used to address the alias on the commandline. Site names may not contain periods.

Expand Down
71 changes: 47 additions & 24 deletions src/SiteAliasFileDiscovery.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,16 @@
*/
class SiteAliasFileDiscovery
{
protected $searchLocations = [];
protected $depth = '<= 1';
protected $searchLocations;
protected $locationFilter;
protected $depth;

public function __construct($searchLocations = [], $depth = '<= 1', $locationFilter = null)
{
$this->locationFilter = $locationFilter;
$this->searchLocations = $searchLocations;
$this->depth = $depth;
}

/**
* Add a location that alias files may be found.
Expand All @@ -48,6 +56,10 @@ public function searchLocations()
return $this->searchLocations;
}

public function locationFilter()
{
return $this->locationFilter;
}

/**
* Set the search depth for finding alias files
Expand All @@ -61,6 +73,31 @@ public function depth($depth)
return $this;
}

/**
* Only search for aliases that are in alias files stored in directories
* whose basename or key matches the specified location.
*/
public function filterByLocation($location)
{
if (empty($location)) {
return $this;
}

return new SiteAliasFileDiscovery($this->searchLocations(), $this->depth, $location);
}

/**
* Find an alias file SITENAME.site.yml in one
* of the specified search locations.
*
* @param string $siteName
* @return string[]
*/
public function find($siteName)
{
return $this->searchForAliasFiles("$siteName.site.yml");
}

/**
* Find an alias file SITENAME.site.yml in one
* of the specified search locations.
Expand All @@ -70,7 +107,7 @@ public function depth($depth)
*/
public function findSingleSiteAliasFile($siteName)
{
$matches = $this->searchForAliasFiles("$siteName.site.yml");
$matches = $this->find($siteName);
if (empty($matches)) {
return false;
}
Expand Down Expand Up @@ -135,28 +172,14 @@ protected function searchForAliasFiles($searchPattern)
$path = $file->getRealPath();
$result[] = $path;
}
return $result;
}

/**
* Return a list of all alias files with the specified extension.
*
* @param string $filenameExensions
* @return string[]
*/
protected function searchForAliasFilesKeyedByBasenamePrefix($filenameExensions)
{
if (empty($this->searchLocations)) {
return [];
}
$searchPattern = '*' . $filenameExensions;
$finder = $this->createFinder($searchPattern);
$result = [];
foreach ($finder as $file) {
$path = $file->getRealPath();
$key = $this->extractKey($file->getBasename(), $filenameExensions);
$result[$key] = $path;
// In theory we can use $finder->path() instead. That didn't work well,
// in practice, though; had trouble correctly escaping the path separators.
if (!empty($this->locationFilter)) {
$result = array_filter($result, function ($path) {
return SiteAliasName::locationFromPath($path) === $this->locationFilter;
});
}

return $result;
}

Expand Down
60 changes: 49 additions & 11 deletions src/SiteAliasFileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,12 @@ public function loadAll()
* Return a list of all available alias files. Does not include
* legacy files.
*
* @param string $location Only consider alias files in the specified location.
* @return string[]
*/
public function listAll()
public function listAll($location = '')
{
return $this->discovery()->findAllSingleAliasFiles();
return $this->discovery()->filterByLocation($location)->findAllSingleAliasFiles();
}

/**
Expand All @@ -165,13 +166,42 @@ public function listAll()
*/
public function loadMultiple($sitename)
{
if ($path = $this->discovery()->findSingleSiteAliasFile($sitename)) {
$result = [];
foreach ($this->discovery()->find($sitename) as $path) {
if ($siteData = $this->loadSiteDataFromPath($path)) {
$location = SiteAliasName::locationFromPath($path);
// Convert the raw array into a list of alias records.
return $this->createAliasRecordsFromSiteData($sitename, $siteData);
$result = array_merge(
$result,
$this->createAliasRecordsFromSiteData($sitename, $siteData, $location)
);
}
}
return false;
return $result;
}


/**
* Given a location, return all alias files located there.
*
* @param string $location The location to filter.
* @return AliasRecord[]
*/
public function loadLocation($location)
{
$result = [];
foreach ($this->listAll($location) as $path) {
if ($siteData = $this->loadSiteDataFromPath($path)) {
$location = SiteAliasName::locationFromPath($path);
$sitename = $this->siteNameFromPath($path);
// Convert the raw array into a list of alias records.
$result = array_merge(
$result,
$this->createAliasRecordsFromSiteData($sitename, $siteData, $location)
);
}
}
return $result;
}

/**
Expand All @@ -181,15 +211,15 @@ public function loadMultiple($sitename)
* @param $siteData An associative array of envrionment => site data
* @return AliasRecord[]
*/
protected function createAliasRecordsFromSiteData($sitename, $siteData)
protected function createAliasRecordsFromSiteData($sitename, $siteData, $location = '')
{
$result = [];
if (!is_array($siteData) || empty($siteData)) {
return $result;
}
foreach ($siteData as $envName => $data) {
if (is_array($data)) {
$aliasName = new SiteAliasName($sitename, $envName);
$aliasName = new SiteAliasName($sitename, $envName, $location);

$processor = new ConfigProcessor();
$oneRecord = $this->fetchAliasRecordFromSiteAliasData($aliasName, $processor, $siteData);
Expand Down Expand Up @@ -223,7 +253,8 @@ protected function storeAliasRecordInResut(&$result, AliasRecord $aliasRecord)

/**
* If the alias name is '@sitename', or if it is '@sitename.env', then
* look for a sitename.site.yml file that contains it.
* look for a sitename.site.yml file that contains it. We also handle
* '@location.sitename.env' here as well.
*
* @param SiteAliasName $aliasName
*
Expand All @@ -233,7 +264,9 @@ protected function loadSingleAliasFile(SiteAliasName $aliasName)
{
// Check to see if the appropriate sitename.alias.yml file can be
// found. Return if it cannot.
$path = $this->discovery()->findSingleSiteAliasFile($aliasName->sitename());
$path = $this->discovery()
->filterByLocation($aliasName->location())
->findSingleSiteAliasFile($aliasName->sitename());
if (!$path) {
return false;
}
Expand All @@ -250,8 +283,9 @@ protected function loadSingleAliasFile(SiteAliasName $aliasName)
protected function loadSingleSiteAliasFileAtPath($path)
{
$sitename = $this->siteNameFromPath($path);
$location = SiteAliasName::locationFromPath($path);
if ($siteData = $this->loadSiteDataFromPath($path)) {
return $this->createAliasRecordsFromSiteData($sitename, $siteData);
return $this->createAliasRecordsFromSiteData($sitename, $siteData, $location);
}
return false;
}
Expand All @@ -265,6 +299,10 @@ protected function loadSingleSiteAliasFileAtPath($path)
protected function siteNameFromPath($path)
{
return $this->basenameWithoutExtension($path, '.site.yml');

// OR:
// $filename = basename($path);
// return preg_replace('#\..*##', '', $filename);
}

/**
Expand Down Expand Up @@ -404,7 +442,7 @@ protected function fetchAliasRecordFromSiteAliasData(SiteAliasName $aliasName, C
$processor->add($data[$env]);

// Export the combined data and create an AliasRecord object to manage it.
return new AliasRecord($processor->export($this->referenceData), '@' . $aliasName->sitename(), $env);
return new AliasRecord($processor->export($this->referenceData), '@' . $aliasName->sitenameWithLocation(), $env);
}

/**
Expand Down
22 changes: 17 additions & 5 deletions src/SiteAliasManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,23 @@ public function getMultiple($name)
return false;
}

// Trim off the '@' and load all that match
$result = $this->aliasLoader->loadMultiple(ltrim($name, '@'));
// Trim off the '@'
$trimmedName = ltrim($name, '@');

// If the provided name is a location, return all aliases there
$result = $this->aliasLoader->loadLocation($trimmedName);
if (!empty($result)) {
return $result;
}

// If the provided name is a site, return all environments
$result = $this->aliasLoader->loadMultiple($trimmedName);
if (!empty($result)) {
return $result;
}

// Special checking for @self
if ($name == '@self') {
if ($trimmedName == 'self') {
$self = $this->getSelf();
$result = array_merge(
['@self' => $self],
Expand All @@ -194,8 +206,8 @@ public function getMultiple($name)
*
* @return string[]
*/
public function listAllFilePaths()
public function listAllFilePaths($location = '')
{
return $this->aliasLoader->listAll();
return $this->aliasLoader->listAll($location);
}
}
Loading