Skip to content

Commit

Permalink
[ISSUE-42] - Implement LitEmoji::usePreset() (#43)
Browse files Browse the repository at this point in the history
* Implemented shortcode presets

* Added test setup to ensure consistent results

* Misc. fixes

* Used LitEmoji::invalidateCache() in usePreset()
  • Loading branch information
joshmcrae authored Sep 11, 2024
1 parent bd221f4 commit 42a3e73
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 43 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (C) 2017-2023 Elvanto Pty Ltd <[email protected]>
Copyright (C) 2017-2024 Elvanto Pty Ltd <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ echo LitEmoji::encodeShortcode('📱');

// Add aliases for custom shortcodes
LitEmoji::config('aliasShortcodes', ['yeah' => 'thumbsup']);
echo LitEmoji::encodeUnicode('Hell :yeah:!');
// 'Hell 👍!'
echo LitEmoji::encodeUnicode('Can do :yeah:!');
// 'Can do 👍!'
```

# Encodings
Expand Down
65 changes: 41 additions & 24 deletions bin/generate-shortcodes-array.php
Original file line number Diff line number Diff line change
@@ -1,46 +1,63 @@
#!/usr/bin/env php
<?php

$presets = [
'cldr',
'emojibase',
'github',
'iamcal',
'joypixels'
];

function normalizeShortcode($shortcode)
{
return str_replace('-', '_', strtolower($shortcode));
}

// Collect available emoji
$data = json_decode(file_get_contents(__DIR__ . '/../vendor/milesj/emojibase/packages/data/en/data.raw.json'), true);
$shortcodes = json_decode(file_get_contents(__DIR__ . '/../vendor/milesj/emojibase/packages/data/en/shortcodes/emojibase.raw.json'), true);

$emojiList = require(__DIR__ . '/../src/emoji.php');
$existingShortcodes = array_map('normalizeShortcode', array_keys($emojiList));
foreach ($presets as $preset) {
$shortcodes = json_decode(file_get_contents(__DIR__ . "/../vendor/milesj/emojibase/packages/data/en/shortcodes/{$preset}.raw.json"), true);
$mapping = __DIR__ . "/../src/{$preset}.php";

foreach ($data as $emoji) {
if (
!isset($shortcodes[$emoji['hexcode']]) ||
!array_key_exists('group', $emoji) // Excludes regional indicator emoji that mess with flags
) {
continue;
if (file_exists($mapping)) {
$emojiList = require($mapping);
} else {
$emojiList = [];
}

if (!is_array($shortcodes[$emoji['hexcode']])) {
$shortcodes[$emoji['hexcode']] = [$shortcodes[$emoji['hexcode']]];
}
$existingShortcodes = array_map('normalizeShortcode', array_keys($emojiList));

foreach ($shortcodes[$emoji['hexcode']] as $shortcode) {
if (in_array(normalizeShortcode($shortcode), $existingShortcodes)) {
foreach ($data as $emoji) {
if (
!isset($shortcodes[$emoji['hexcode']]) ||
!array_key_exists('group', $emoji) // Excludes regional indicator emoji that mess with flags
) {
continue;
}

$emojiList[(string)$shortcode] = $emoji['hexcode'];
if (!is_array($shortcodes[$emoji['hexcode']])) {
$shortcodes[$emoji['hexcode']] = [$shortcodes[$emoji['hexcode']]];
}

foreach ($shortcodes[$emoji['hexcode']] as $shortcode) {
if (in_array(normalizeShortcode($shortcode), $existingShortcodes)) {
continue;
}

$emojiList[(string)$shortcode] = $emoji['hexcode'];
}
}
}

// Order by longest codepoint to ensure replacement of ZWJ emoji first
uasort($emojiList, fn ($a, $b) => strlen($b) <=> strlen($a));
// Order by longest codepoint to ensure replacement of ZWJ emoji first
uasort($emojiList, fn($a, $b) => strlen($b) <=> strlen($a));

// Generate cachable PHP code
$output = [];
foreach ($emojiList as $shortcode => $codepoints) {
$output[] = sprintf("'%s'=>'%s'", $shortcode, $codepoints);
};
// Generate cachable PHP code
$output = [];
foreach ($emojiList as $shortcode => $codepoints) {
$output[] = sprintf("'%s'=>'%s'", $shortcode, $codepoints);
};

file_put_contents('src/emoji.php', sprintf('<?php return [%s];', implode(',', $output)));
file_put_contents("src/{$preset}.php", sprintf('<?php return [%s];', implode(',', $output)));
}
45 changes: 33 additions & 12 deletions src/LitEmoji.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,30 @@

class LitEmoji
{
private static string $preset = 'emojibase';
private static array $shortcodes = [];
private static array $shortcodeCodepoints = [];
private static array $shortcodeEntities = [];
private static array $entityCodepoints = [];
private static array $excludedShortcodes = [];
private static array $aliasedShortcodes = [];

/**
* Switches to a different emoji preset, clearing the shortcode cache.
*
* @param string $preset
* @return void
*/
public static function usePreset(string $preset)
{
if (!file_exists(sprintf('%s/%s.php', __DIR__, $preset))) {
throw new \InvalidArgumentException('Invalid emoji preset.');
}

self::$preset = $preset;
self::invalidateCache();
}

/**
* Converts all unicode emoji and HTML entities to plaintext shortcodes.
*
Expand Down Expand Up @@ -177,21 +194,12 @@ public static function config(string $property, $value): void
}
}

// Invalidate shortcode cache
self::$shortcodes = [];
self::$shortcodeCodepoints = [];
self::$shortcodeEntities = [];
self::$entityCodepoints = [];
self::$excludedShortcodes = [];
self::invalidateCache();
break;
case 'aliasShortcodes':
self::$aliasedShortcodes = (array) $value;

// Invalidate shortcode cache
self::$shortcodes = [];
self::$shortcodeCodepoints = [];
self::$shortcodeEntities = [];
self::$entityCodepoints = [];
self::invalidateCache();
break;
}
}
Expand All @@ -215,7 +223,7 @@ private static function getShortcodes(): array
}

// Skip excluded shortcodes
self::$shortcodes = array_filter(require(__DIR__ . '/emoji.php'), static function($code) {
self::$shortcodes = array_filter(require(sprintf('%s/%s.php', __DIR__, self::$preset)), static function($code) {
return !in_array($code, self::$excludedShortcodes);
}, ARRAY_FILTER_USE_KEY);

Expand Down Expand Up @@ -288,4 +296,17 @@ private static function getShortcodeEntities(): array

return self::$shortcodeEntities;
}

/**
* Invalidates the shortcode cache.
*
* @return void
*/
private static function invalidateCache(): void
{
self::$shortcodes = [];
self::$shortcodeCodepoints = [];
self::$shortcodeEntities = [];
self::$entityCodepoints = [];
}
}
1 change: 1 addition & 0 deletions src/cldr.php

Large diffs are not rendered by default.

File renamed without changes.
1 change: 1 addition & 0 deletions src/github.php

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/iamcal.php

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/joypixels.php

Large diffs are not rendered by default.

21 changes: 17 additions & 4 deletions tests/LitEmojiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@

class LitEmojiTest extends TestCase
{
protected function setUp(): void
{
LitEmoji::config('excludeShortcodes', []);
LitEmoji::usePreset('emojibase');
}

public function testUnicodeToShortcode()
{
$text = LitEmoji::encodeShortcode('My mixtape is 🔥. Made in 🇦🇺!');
Expand Down Expand Up @@ -57,28 +63,35 @@ public function testRemoveEmoji()
public function testConfigExcludeShortcodes()
{
LitEmoji::config('excludeShortcodes', ['mobile', 'android', 'mobile_phone']);
$this->assertEquals(':android:', LitEmoji::encodeShortcode('📱'));
$this->assertEquals(':iphone:', LitEmoji::encodeShortcode('📱'));
}

public function testConfigIncludeShortcodeAliases()
public function testConfigAliasShortcodes()
{
LitEmoji::config('aliasShortcodes', ['thumbs_up' => 'thumbsup', 'yeah' => 'thumbsup']);
$this->assertEquals('👍', LitEmoji::encodeUnicode(':yeah:'));
}

public function testUnicodeMatching()
{
$shortcodes = require __DIR__ . '/../src/emoji.php';
$shortcodes = require(__DIR__ . '/../src/emojibase.php');
$shortcodes = array_flip($shortcodes);

foreach ($shortcodes as $shortcode) {
$unicode = LitEmoji::encodeUnicode(':'.$shortcode.':');
$unicode = LitEmoji::encodeUnicode(':' . $shortcode . ':');
$matched = LitEmoji::unicodeToShortcode($unicode);

$this->assertNotEquals($unicode, $matched);
}
}

public function testPreset()
{
$this->assertEquals('👍', LitEmoji::encodeUnicode(':thumbsup:'));
LitEmoji::usePreset('cldr');
$this->assertEquals('👍', LitEmoji::encodeUnicode(':thumbs_up:'));
}

public function testIssue25()
{
$text = LitEmoji::encodeShortcode('🚀🛒');
Expand Down

0 comments on commit 42a3e73

Please sign in to comment.