Page MenuHomeWickedGov Phorge

GenerateEquivset.php
No OneTemporary

Size
6 KB
Referenced Files
None
Subscribers
None

GenerateEquivset.php

<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*/
namespace Wikimedia\Equivset\Command;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Generate Equivset Command.
*/
class GenerateEquivset extends Command {
/**
* @var string
*/
protected string $dataDir;
/**
* @var string
*/
protected string $distDir;
/**
* Generate Equivset
*
* @param string $dataDir Data Directory
* @param string $distDir Distribution Directory
*/
public function __construct( string $dataDir = '', string $distDir = '' ) {
parent::__construct();
$this->dataDir = $dataDir ?: __DIR__ . '/../../data';
$this->distDir = $distDir ?: __DIR__ . '/../../dist';
}
/** {@inheritdoc} */
protected function configure() {
$this->setName( 'generate-equivset' );
$this->setDescription(
'Generate the JSON, PHP, and plain text versions of the equivset in `./dist`'
);
}
/** {@inheritdoc} */
public function execute( InputInterface $input, OutputInterface $output ) {
// phpcs:ignore Generic.PHP.NoSilencedErrors
$fp = @fopen( $this->dataDir . '/equivset.in', 'rb' );
if ( $fp === false ) {
throw new RuntimeException( "Unable to open equivset.in" );
}
$lineNum = 0;
$setsByChar = [];
$sets = [];
$exitStatus = 0;
$lastChar = null;
// phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
while ( ( $line = fgets( $fp ) ) !== false ) {
$lineNum++;
$line = trim( $line );
# Filter comments
if ( !$line || $line[0] == '#' ) {
continue;
}
# Process line
if ( !preg_match(
'/^(?P<hexleft>[0-9A-F]+) +(?P<charleft>.) +=> +' .
'(?:(?P<hexright>[0-9A-F]+) +(?P<charright>.)|(?P<invisible>invisible)|(?P<space>space))$/u',
$line, $m, PREG_UNMATCHED_AS_NULL
)
) {
$output->writeln( "<error>Error: invalid entry at line $lineNum: $line</error>" );
$exitStatus = 1;
continue;
}
$error = false;
if ( mb_chr( hexdec( $m['hexleft'] ) ) !== $m['charleft'] ) {
$actual = strtoupper( dechex( mb_ord( $m['charleft'] ) ) );
$output->writeln( "<error>Error: left number ({$m['hexleft']}) does not match left " .
"character ($actual) at line $lineNum: $line</error>" );
$error = true;
}
if ( isset( $m['invisible'] ) ) {
$m['charright'] = '';
} elseif ( isset( $m['space'] ) ) {
$m['charright'] = ' ';
} elseif ( mb_chr( hexdec( $m['hexright'] ) ) !== $m['charright'] ) {
$actual = strtoupper( dechex( mb_ord( $m['charright'] ) ) );
$output->writeln( "<error>Error: right number ({$m['hexright']}) does not match right " .
"character ($actual) at line $lineNum: $line</error>" );
$error = true;
}
if ( $m['charleft'] === $m['charright'] ) {
$output->writeln( "<error>Error: {$m['hexright']} maps to itself</error>" );
$error = true;
}
if ( isset( $setsByChar[$m['charleft']] ) ) {
$output->writeln( "<error>Error: Duplicate character ({$m['charleft']}) " .
"at line $lineNum: $line</error>" );
$error = true;
}
if ( $lastChar !== null && $m['charleft'] < $lastChar ) {
$output->writeln( "<error>Error: Characters not in order based on hex-value ({$m['charleft']}) " .
"at line $lineNum: $line</error>" );
$error = true;
} else {
$lastChar = $m['charleft'];
}
if ( $error ) {
$exitStatus = 1;
continue;
}
# Find the set for the right character, add a new one if necessary
$setName = $setsByChar[$m['charright']] ?? $m['charright'];
// Circle detected, one edge in every circle is redundant and can just be ignored
if ( $setName === $m['charleft'] ) {
continue;
}
$sets[$setName] ??= [ $setName ];
// When a mapping between two chars exists before one of them gets the final set, a merge is needed
if ( isset( $sets[$m['charleft']] ) ) {
foreach ( $sets[$m['charleft']] as $char ) {
$setsByChar[$char] = $setName;
$sets[$setName][] = $char;
}
unset( $sets[$m['charleft']] );
} else {
$setsByChar[$m['charleft']] = $setName;
$sets[$setName][] = $m['charleft'];
}
}
$header = [
'This file is generated by `bin/console generate-equivset`',
'It contains a map of characters, encoded in UTF-8, such that running',
'strtr() on a string with this map will cause confusable characters to',
'be reduced to a canonical representation.',
];
// JSON
$data = json_encode(
[ '_readme' => implode( ' ', $header ) ] + $setsByChar,
JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
$data = preg_replace( '/^ +/m', "\t", $data );
file_put_contents( $this->distDir . '/equivset.json', $data );
// PHP file
file_put_contents( $this->distDir . '/equivset.php', self::generatePHP( $setsByChar, $header ) );
// Text File.
uksort( $sets, [ self::class, 'compareCodePoints' ] );
touch( $this->distDir . '/equivset.txt' );
$textFile = fopen( $this->distDir . '/equivset.txt', 'w' );
foreach ( $sets as $members ) {
$setName = array_shift( $members );
usort( $members, [ self::class, 'compareCodePoints' ] );
fwrite( $textFile, $setName . ' ' . implode( ' ', $members ) . "\n" );
}
fclose( $textFile );
if ( $exitStatus > 0 ) {
$output->writeln( '<error>Finished with errors</error>' );
} else {
$output->writeln( '<info>Finished</info>' );
}
return $exitStatus;
}
private static function compareCodePoints( string $a, string $b ): int {
if ( $a === '' ) {
return -1;
} elseif ( $b === '' ) {
return 1;
}
return mb_ord( $a ) - mb_ord( $b );
}
/**
* @param string[] $data
* @param string[] $header
* @return string
*/
private static function generatePHP( array $data, array $header ): string {
$s = "<?php\n"
. "// " . implode( "\n// ", $header ) . "\n"
. "return [\n";
foreach ( $data as $key => $value ) {
$s .= "\t" . var_export( (string)$key, true ) . ' => ' . var_export( $value, true ) . ",\n";
}
$s .= "];\n";
return $s;
}
}

File Metadata

Mime Type
text/x-php
Expires
Sat, Oct 3, 22:34 (1 w, 5 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
be/19/4861eae3233602eac7ae5715aac4
Default Alt Text
GenerateEquivset.php (6 KB)

Event Timeline