Skip to content

Commit

Permalink
Add relationship between countries and currencies (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
PrinsFrank authored Dec 1, 2023
1 parent 76efd31 commit 9c64486
Show file tree
Hide file tree
Showing 22 changed files with 969 additions and 18 deletions.
1 change: 1 addition & 0 deletions .php-cs-fixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
'array_syntax' => ['syntax' => 'short'],
'binary_operator_spaces' => ['default' => 'align_single_space'],
'no_unused_imports' => true,
'array_indentation' => true,
]
)->setFinder(
PhpCsFixer\Finder::create()
Expand Down
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ erDiagram
Language }o..|{ Script: ""
Country }|--o{ CountryGroup: ""
Country }|--o{ CountryCallingCode: ""
Country }o..o{ Currency: ""
Country }o--o{ Currency: ""
Country }|--o{ InternationalCallPrefix: ""
Currency }|--o| CurrencySymbol: ""
LanguageTag ||--o{ LanguageTag: ""
Expand Down Expand Up @@ -151,6 +151,7 @@ CountryAlpha3::from('NLD')->getInternationalCallPrefix(); // Internationa
CountryAlpha3::from('NLD')->getInternationalCallPrefix()->value; // '00'

CountryAlpha3::from('NLD')->getFlagEmoji(); // '🇳🇱' (This might not be displayed correctly in this readme if you're on windows, see 'https://prinsfrank.nl/2021/01/25/Non-existing-flag-emojis-on-windows to make these flag emojis visible for Windows users.')
CountryAlpha3::from('NLD')->getCurrenciesAlpha3(); // [CurrencyAlpha3::Euro]

public function foo(CountryAlpha2 $countryAlpha2) {} // Use spec as typehint to enforce valid value

Expand Down Expand Up @@ -216,6 +217,8 @@ $valueAlpha2->getInternationalCallPrefix(); // InternationalCallPrefix::_
$valueAlpha2->getInternationalCallPrefix()->value; // '00'

$valueAlpha2::from('NLD')->getFlagEmoji(); // '🇳🇱' (This might not be displayed correctly in this readme if you're on windows, see 'https://prinsfrank.nl/2021/01/25/Non-existing-flag-emojis-on-windows to make these flag emojis visible for Windows users.')

$valueAlpha2->getCurrenciesAlpha3(); // [CurrencyAlpha3::Euro]
```

### CountryAlpha3
Expand All @@ -239,7 +242,9 @@ $valueAlpha3->getCountryCallingCodes()[0]->value; // 31
$valueAlpha3->getInternationalCallPrefix(); // InternationalCallPrefix::_00
$valueAlpha3->getInternationalCallPrefix()->value; // '00'

$valueAlpha3::from('NLD')->getFlagEmoji(); // '🇳🇱' (This might not be displayed correctly in this readme if you're on windows, see 'https://prinsfrank.nl/2021/01/25/Non-existing-flag-emojis-on-windows to make these flag emojis visible for Windows users.')
$valueAlpha3->getFlagEmoji(); // '🇳🇱' (This might not be displayed correctly in this readme if you're on windows, see 'https://prinsfrank.nl/2021/01/25/Non-existing-flag-emojis-on-windows to make these flag emojis visible for Windows users.')

$valueAlpha3->getCurrenciesAlpha3(); // [CurrencyAlpha3::Euro]
```

### CountryNumeric
Expand All @@ -264,7 +269,9 @@ $valueNumeric->getCountryCallingCodes()[0]->value; // 31
$valueNumeric->getInternationalCallPrefix(); // InternationalCallPrefix::_00
$valueNumeric->getInternationalCallPrefix()->value; // '00'

$valueNumeric::from('NLD')->getFlagEmoji(); // '🇳🇱' (This might not be displayed correctly in this readme if you're on windows, see 'https://prinsfrank.nl/2021/01/25/Non-existing-flag-emojis-on-windows to make these flag emojis visible for Windows users.')
$valueNumeric->getFlagEmoji(); // '🇳🇱' (This might not be displayed correctly in this readme if you're on windows, see 'https://prinsfrank.nl/2021/01/25/Non-existing-flag-emojis-on-windows to make these flag emojis visible for Windows users.')

$valueNumeric->getCurrenciesAlpha3(); // [CurrencyAlpha3::Euro]
```

### CountryName
Expand All @@ -286,7 +293,9 @@ $valueName->getCountryCallingCodes()[0]->value; // 31
$valueName->getInternationalCallPrefix(); // InternationalCallPrefix::_00
$valueName->getInternationalCallPrefix()->value; // '00'

$valueName::from('NLD')->getFlagEmoji(); // '🇳🇱' (This might not be displayed correctly in this readme if you're on windows, see 'https://prinsfrank.nl/2021/01/25/Non-existing-flag-emojis-on-windows to make these flag emojis visible for Windows users.')
$valueName->getFlagEmoji(); // '🇳🇱' (This might not be displayed correctly in this readme if you're on windows, see 'https://prinsfrank.nl/2021/01/25/Non-existing-flag-emojis-on-windows to make these flag emojis visible for Windows users.')

$valueName->getCurrenciesAlpha3(); // [CurrencyAlpha3::Euro]
```

</details>
Expand Down Expand Up @@ -344,6 +353,8 @@ CurrencySymbol::forCurrency(CurrencyName::Euro); // CurrencySymbol::Eu
CurrencySymbol::forCurrency(CurrencyNumeric::from('978')); // CurrencySymbol::Euro
CurrencyAlpha3::Euro; // CurrencyAlpha3::Euro

CurrencyAlpha3::Norwegian_Krone->getCountriesAlpha2(); // [CountryAlpha2::Bouvet_Island, CountryAlpha2::Norway, CountryAlpha2::Svalbard_Jan_Mayen]

public function foo(CurrencyAlpha3 $currencyAlpha3) {} // Use spec as typehint to enforce valid value

```
Expand Down Expand Up @@ -404,6 +415,8 @@ $valueName = $currencyAlpha3->toCurrencyName(); // CurrencyName::Euro
$valueName = $currencyAlpha3->toCurrencyName()->value; // 'Euro'
$valueSymbol = $currencyAlpha3->getSymbol(); // CurrencySymbol::Euro
$valueSymbol = $currencyAlpha3->getSymbol()->value; // '€'
$countries = $currencyAlpha2->getCountriesAlpha2(); // [CountryAlpha2::Bouvet_Island, CountryAlpha2::Norway, CountryAlpha2::Svalbard_Jan_Mayen]

```

### CurrencyNumeric
Expand All @@ -419,6 +432,7 @@ $valueName = $currencyNumeric->toCurrencyName(); // CurrencyName::Euro
$valueName = $currencyNumeric->toCurrencyName()->value; // 'Euro'
$valueSymbol = $currencyNumeric->getSymbol(); // CurrencySymbol::Euro
$valueSymbol = $currencyNumeric->getSymbol()->value; // '€'
$countries = $currencyNumeric->getCountriesAlpha2(); // [CountryAlpha2::Bouvet_Island, CountryAlpha2::Norway, CountryAlpha2::Svalbard_Jan_Mayen]
```

### CurrencySymbol
Expand All @@ -439,6 +453,7 @@ $name = $currencyName->name; // 'Euro'
$value = $currencyName->value; // 'Euro'
$valueAlpha3 = $currencyName->toCurrencyAlpha3(); // CurrencyAlpha3::Euro
$valueAlpha3 = $currencyName->toCurrencyNumeric(); // CurrencyNumeric::Euro
$countries = $currencyName->getCountriesAlpha2(); // [CountryAlpha2::Bouvet_Island, CountryAlpha2::Norway, CountryAlpha2::Svalbard_Jan_Mayen]
```


Expand Down
34 changes: 30 additions & 4 deletions dev/DataSource/Mapping/CurrencyMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
use DOMDocument;
use DOMElement;
use DOMXPath;
use PrinsFrank\Standards\BackedEnum;
use PrinsFrank\Standards\Country\CountryAlpha2;
use PrinsFrank\Standards\Country\Groups\EuroZone;
use PrinsFrank\Standards\Currency\CurrencyAlpha3;
use PrinsFrank\Standards\Currency\CurrencyName;
use PrinsFrank\Standards\Currency\CurrencyNumeric;
use PrinsFrank\Standards\Dev\DataSource\Sorting\KeyWithDeprecatedTagsSeparateSorting;
use PrinsFrank\Standards\Dev\DataSource\Sorting\SortingInterface;
use PrinsFrank\Standards\Dev\DataTarget\EnumCase;
use PrinsFrank\Standards\Dev\DataTarget\EnumFile;
use PrinsFrank\Standards\Dev\DataTarget\EnumMethod;
use PrinsFrank\Standards\Dev\DataTarget\NameNormalizer;
use PrinsFrank\Standards\Dev\DomElementNotFoundException;
use Symfony\Component\Panther\Client;
use Symfony\Component\Panther\DomCrawler\Crawler;
Expand Down Expand Up @@ -62,9 +67,13 @@ public static function toDataSet(Client $client, Crawler $crawler): array
*/
public static function toEnumMapping(array $dataSet): array
{
$currencyAlpha3Enum = new EnumFile(CurrencyAlpha3::class);
$currencyNameEnum = new EnumFile(CurrencyName::class);
$currencyNumericEnum = new EnumFile(CurrencyNumeric::class);
$currencyNameEnum = new EnumFile(CurrencyName::class);
$currencyNumericEnum = new EnumFile(CurrencyNumeric::class);
$getCountriesAlpha2Method = new EnumMethod('getCountriesAlpha2', 'array', '[]');
$currencyAlpha3Enum = (new EnumFile(CurrencyAlpha3::class))->addMethod($getCountriesAlpha2Method);

$getCurrenciesMethod = new EnumMethod('getCurrenciesAlpha3', 'array', '[]');
$countryAlpha2 = (new EnumFile(CountryAlpha2::class))->addMethod($getCurrenciesMethod);
foreach ($dataSet as $dataRow) {
if (($dataRow->Ccy ?? null) === null) {
continue;
Expand All @@ -80,9 +89,26 @@ public static function toEnumMapping(array $dataSet): array
if ($dataRow->CcyNbr !== null) {
$currencyNumericEnum->addCase(new EnumCase($currencyName, $dataRow->CcyNbr));
}

$countryName = NameNormalizer::normalize(mb_convert_case($dataRow->CtryNm, MB_CASE_TITLE));
if (str_starts_with($countryName, 'Zz') === true || in_array($dataRow->Ccy, [CurrencyAlpha3::SDR_Special_Drawing_Right->value, CurrencyAlpha3::ADB_Unit_of_Account->value, CurrencyAlpha3::Sucre->value], true) === true) {
continue;
}

if ($countryName === 'European_Union') {
foreach (EuroZone::allAlpha2() as $euroZoneCountry) {
$getCurrenciesMethod->addMapping('self::' . $euroZoneCountry->name, 'CurrencyAlpha3::' . NameNormalizer::normalize($currencyName));
$getCountriesAlpha2Method->addMapping('self::' . NameNormalizer::normalize($currencyName), 'CountryAlpha2::' . $euroZoneCountry->name);
}
} else {
$countryName = (BackedEnum::fromKey(CountryAlpha2::class, str_replace(['_The', '_And_', '_Plurinational_State_Of', '_Keeling', '_Of', '_D_ivoire', '_Malvinas', 'Mcdonald', '_Islamic_Republic', 'Isle_Man', 'People_s_', '_Federated_States', 'Moldova_Republic', '_and_Tristan', '_Da_', '_Part', '_and_Grenadines', '_and_Jan', '_Province_China', '_United_Republic', 'Turkiye', '_Great_Britain_and_Northern_Ireland', 'Minor_', '_America', '_Bolivarian_Republic', '_U_s'], ['', '_and_', '', '', '', '_d_Ivoire', '', 'McDonald', '', 'Isle_of_Man', 'Peoples_', '', 'Moldova', '_Tristan', '_da_', '_part', '_and_the_Grenadines', '_Jan', '_Province_of_China', '', 'Turkey', '', '', '_of_America', '', '_U_S'], $countryName))->name);

$getCurrenciesMethod->addMapping('self::' . $countryName, 'CurrencyAlpha3::' . NameNormalizer::normalize($currencyName));
$getCountriesAlpha2Method->addMapping('self::' . NameNormalizer::normalize($currencyName), 'CountryAlpha2::' . $countryName);
}
}

return [$currencyAlpha3Enum, $currencyNameEnum, $currencyNumericEnum];
return [$currencyAlpha3Enum, $currencyNameEnum, $currencyNumericEnum, $countryAlpha2];
}

public static function getSorting(): SortingInterface
Expand Down
37 changes: 37 additions & 0 deletions dev/DataTarget/EnumFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use BackedEnum;
use PrinsFrank\Standards\Dev\DataSource\Sorting\SortingInterface;
use PrinsFrank\Standards\Dev\EnumNotFoundException;
use RuntimeException;

class EnumFile
{
Expand All @@ -14,6 +15,9 @@ class EnumFile
/** @var EnumCase[] */
private array $cases = [];

/** @var EnumMethod[] */
private array $methods = [];

/** @param class-string<BackedEnum> $fqn */
public function __construct(
public readonly string $fqn
Expand All @@ -28,6 +32,18 @@ public function addCase(EnumCase $enumCase): self
return $this;
}

public function addMethod(EnumMethod $method): self
{
$this->methods[] = $method;

return $this;
}

public function hasCases(): bool
{
return count($this->cases) > 0;
}

public function hasCaseWithValue(string|int $value): bool
{
foreach ($this->cases as $case) {
Expand Down Expand Up @@ -78,4 +94,25 @@ public function writeCases(SortingInterface $sorting): self

return $this->putContent($newEnumContent);
}

public function writeMethods(): void
{
foreach ($this->methods as $method) {
$enumContent = $this->getContent();
$anchor = ' public function ' . $method->name . '()';
$startExistingMethod = mb_strpos($enumContent, $anchor);
$lastClosingTag = mb_strrpos($enumContent, '}');
if ($lastClosingTag === false) {
throw new RuntimeException('Couldn\'t locate closing tag');
}

if ($startExistingMethod !== false) {
$newEnumContent = mb_substr($enumContent, 0, $startExistingMethod) . $method->__toString() . mb_substr($enumContent, $lastClosingTag);
} else {
$newEnumContent = mb_substr($enumContent, 0, $lastClosingTag) . $method->__toString() . mb_substr($enumContent, $lastClosingTag);
}

$this->putContent($newEnumContent);
}
}
}
58 changes: 58 additions & 0 deletions dev/DataTarget/EnumMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);

namespace PrinsFrank\Standards\Dev\DataTarget;

class EnumMethod
{
/** @var array<int|string, list<string>> */
private array $mapping = [];

public function __construct(
public readonly string $name,
public readonly string $returnType,
public readonly ?string $default,
) {
}

public function addMapping(string $from, string $to): void
{
if (array_key_exists($from, $this->mapping) && in_array($to, $this->mapping[$from], true) === true) {
return;
}

$this->mapping[$from][] = $to;
}

public function __toString(): string
{
$mappingString = '';
$sortedMapping = $this->mapping;
ksort($sortedMapping);

foreach ($sortedMapping as $key => $values) {
sort($values);

if (count($values) <= 1) {
$mappingString .= $key . ' => [' . implode(',', $values) . '],' . PHP_EOL;

continue;
}

$mappingString .= $key . ' => [' . PHP_EOL . implode(',' . PHP_EOL, $values) . PHP_EOL . '],' . PHP_EOL;
}

if ($this->default !== null) {
$mappingString .= 'default => ' . $this->default;
}

return <<<EOD
public function {$this->name}(): {$this->returnType}
{
return match(\$this) {
{$mappingString}
};
}
EOD;
}
}
14 changes: 10 additions & 4 deletions dev/SpecUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PrinsFrank\Standards\Dev\DataSource\DataSourceMappingProvider;
use PrinsFrank\Standards\Dev\DataSource\Mapping\Mapping;
use PrinsFrank\Standards\Dev\DataTarget\EnumCase;
use PrinsFrank\Standards\Dev\DataTarget\EnumFile;
use PrinsFrank\Standards\InvalidArgumentException;
use Symfony\Component\Panther\Client;
use Throwable;
Expand All @@ -30,15 +31,20 @@ public static function update(Event $event): void
$event->getIO()->writeRaw('Updating from mapping "' . $mapping . '"');
$crawler = ($client = Client::createFirefoxClient())->request('GET', $mapping::url());

/** @var EnumFile $enumFile */
foreach ($mapping::toEnumMapping($mapping::toDataSet($client, $crawler)) as $enumFile) {
$event->getIO()->writeRaw('Updating contents of enum "' . $enumFile->path . '"');
foreach ($enumFile->fqn::cases() as $existingCase) {
if ($enumFile->hasCaseWithValue($existingCase->value) === false) {
$enumFile->addCase(new EnumCase($existingCase->name, $existingCase->value, true));
if ($enumFile->hasCases() === true) {
foreach ($enumFile->fqn::cases() as $existingCase) {
if ($enumFile->hasCaseWithValue($existingCase->value) === false) {
$enumFile->addCase(new EnumCase($existingCase->name, $existingCase->value, true));
}
}

$enumFile->writeCases($mapping::getSorting());
}

$enumFile->writeCases($mapping::getSorting());
$enumFile->writeMethods();
}
}
}
Expand Down
Loading

0 comments on commit 9c64486

Please sign in to comment.