|
@@ -0,0 +1,241 @@
|
|
|
+#!/usr/bin/env php
|
|
|
+<?php
|
|
|
+
|
|
|
+/*
|
|
|
+ * This file is part of php-restcord.
|
|
|
+ *
|
|
|
+ * (c) Aaron Scherer <aequasi@gmail.com>
|
|
|
+ *
|
|
|
+ * This source file is subject to the license that is bundled
|
|
|
+ * with this source code in the file LICENSE
|
|
|
+ */
|
|
|
+
|
|
|
+function recursiveRemoveDirectory($directory)
|
|
|
+{
|
|
|
+ foreach (glob("{$directory}/*") as $file) {
|
|
|
+ if (is_dir($file)) {
|
|
|
+ recursiveRemoveDirectory($file);
|
|
|
+ } else {
|
|
|
+ unlink($file);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ rmdir($directory);
|
|
|
+}
|
|
|
+
|
|
|
+$path = __DIR__.'/../src/Model';
|
|
|
+try {
|
|
|
+ recursiveRemoveDirectory($path);
|
|
|
+} catch (\Exception $e) {
|
|
|
+}
|
|
|
+mkdir($path, 02775, true);
|
|
|
+$path = realpath($path);
|
|
|
+
|
|
|
+require __DIR__.'/../vendor/autoload.php';
|
|
|
+
|
|
|
+use gossi\codegen\generator\CodeGenerator;
|
|
|
+use gossi\codegen\model\PhpClass;
|
|
|
+use gossi\codegen\model\PhpMethod;
|
|
|
+use gossi\codegen\model\PhpProperty;
|
|
|
+use Symfony\Component\Console\Application;
|
|
|
+use Symfony\Component\Console\Input\InputArgument;
|
|
|
+use Symfony\Component\Console\Input\InputInterface;
|
|
|
+use Symfony\Component\Console\Output\OutputInterface;
|
|
|
+
|
|
|
+$loader = new Twig_Loader_Filesystem(__DIR__.'/../src/Resources/');
|
|
|
+$twig = new Twig_Environment($loader, ['debug' => true]);
|
|
|
+$twig->addExtension(new Twig_Extension_Debug());
|
|
|
+
|
|
|
+$license = <<<EOF
|
|
|
+<?php
|
|
|
+
|
|
|
+/*
|
|
|
+ * Copyright 2017 Aaron Scherer
|
|
|
+ *
|
|
|
+ * This source file is subject to the license that is bundled
|
|
|
+ * with this source code in the file LICENSE
|
|
|
+ *
|
|
|
+ * @package restcord/restcord
|
|
|
+ * @copyright Aaron Scherer 2017
|
|
|
+ * @license MIT
|
|
|
+ */
|
|
|
+\n
|
|
|
+EOF;
|
|
|
+
|
|
|
+/** @noinspection PhpUnhandledExceptionInspection */
|
|
|
+(new Application('Build Model Classes', '1.0.0'))
|
|
|
+ ->register('buildModelClasses')
|
|
|
+ ->addArgument('version', InputArgument::REQUIRED, 'Version to build')
|
|
|
+ ->setCode(
|
|
|
+ function (InputInterface $input, OutputInterface $output) use ($twig, $license, $path) {
|
|
|
+ $style = new \Symfony\Component\Console\Style\SymfonyStyle($input, $output);
|
|
|
+ $style->title("Building Model Classes for: ".$input->getArgument('version'));
|
|
|
+
|
|
|
+ $definition = \GuzzleHttp\json_decode(
|
|
|
+ file_get_contents(
|
|
|
+ __DIR__.'/../src/Resources/service_description-v'.$input->getArgument('version').'.json'
|
|
|
+ ),
|
|
|
+ true
|
|
|
+ );
|
|
|
+
|
|
|
+ $generator = new CodeGenerator();
|
|
|
+ foreach ($definition['models'] as $resource => $models) {
|
|
|
+ foreach ($models as $name => $model) {
|
|
|
+ $resource = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $resource)));
|
|
|
+ $name = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name)));
|
|
|
+ $class = new PhpClass();
|
|
|
+ $class->setQualifiedName('RestCord\\Model\\'.ucwords($resource).'\\'.ucwords($name));
|
|
|
+ $class->setDescription(ucwords($name)." Model");
|
|
|
+ $class->setProperties(
|
|
|
+ array_map(
|
|
|
+ function (&$name, $property) use ($class, $resource, $model) {
|
|
|
+ $optional = strpos($name, '?') !== false;
|
|
|
+ $name = lcfirst(str_replace([' ', '?'], '', str_replace('-', '_', $name)));
|
|
|
+ $prop = new PhpProperty($name);
|
|
|
+
|
|
|
+ $prop->setType(normalizeType($name, $resource, $property).($optional ? "|null" : ""));
|
|
|
+ $prop->setDescription($property['description']);
|
|
|
+
|
|
|
+ if (isset($property['default'])) {
|
|
|
+ $prop->setValue($property['default']);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $prop;
|
|
|
+ },
|
|
|
+ array_keys($model['properties']),
|
|
|
+ $model['properties']
|
|
|
+ )
|
|
|
+ );
|
|
|
+
|
|
|
+ addTraits($class, $resource, $name);
|
|
|
+
|
|
|
+ $constructor = new PhpMethod('__construct');
|
|
|
+ $constructor->addSimpleParameter('content', 'array', null);
|
|
|
+ $constructor->setBody(
|
|
|
+ <<<EOF
|
|
|
+ if (null === \$content) {
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+foreach (\$content as \$key => \$value) {
|
|
|
+ \$key = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', \$key))));
|
|
|
+ if (property_exists(\$this, \$key)) {
|
|
|
+ \$this->{\$key} = \$value;
|
|
|
+ }
|
|
|
+}
|
|
|
+EOF
|
|
|
+ );
|
|
|
+ $constructor->setVisibility('public');
|
|
|
+ $class->setMethod($constructor);
|
|
|
+
|
|
|
+ @mkdir($path.'/'.ucwords($resource), 02775, true);
|
|
|
+ file_put_contents(
|
|
|
+ $path.'/'.ucwords($resource).'/'.ucwords($name).'.php',
|
|
|
+ $license.$generator->generate($class)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $style->success('Finished. Classes built in: '.realpath($path));
|
|
|
+ }
|
|
|
+ )
|
|
|
+ ->getApplication()
|
|
|
+ ->setDefaultCommand('buildModelClasses', true)
|
|
|
+ ->run();
|
|
|
+
|
|
|
+function normalizeType(string $name, string $resource, array $property): string
|
|
|
+{
|
|
|
+ $type = $property['type'];
|
|
|
+ if ($name === 'user' && $resource === 'Guild') {
|
|
|
+ $type = "user/user";
|
|
|
+ }
|
|
|
+
|
|
|
+ switch ($type) {
|
|
|
+ default:
|
|
|
+ if (strpos($type, '/') !== false || strpos($type, 'Array<') === 0) {
|
|
|
+ $array = false;
|
|
|
+ if (stripos($type, 'Array') !== false) {
|
|
|
+ $array = true;
|
|
|
+ $type = str_replace(['Array<', '>'], '', $type);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($type === 'snowflake') {
|
|
|
+ return 'int[]';
|
|
|
+ }
|
|
|
+
|
|
|
+ $cls = "\\RestCord\\Model\\";
|
|
|
+ $tmp = explode('/', $type);
|
|
|
+ $cls .= ucwords($tmp[0])."\\";
|
|
|
+ $clean = str_replace(['-object', '-'], ['', ' '], $tmp[1]);
|
|
|
+ $clean = ucwords($clean);
|
|
|
+ $clean = str_replace(' ', '', $clean);
|
|
|
+
|
|
|
+ $fullClass = $cls.$clean.($array ? '[]' : '');
|
|
|
+ $fullClass = mapBadDocs($fullClass);
|
|
|
+
|
|
|
+ return $fullClass;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $type;
|
|
|
+ case 'array':
|
|
|
+ if (strpos($property['description'], "ids") !== false) {
|
|
|
+ return "int[]";
|
|
|
+ }
|
|
|
+
|
|
|
+ return "array";
|
|
|
+ case 'ISO8601 timestamp':
|
|
|
+ case 'ISO8601':
|
|
|
+ return '\DateTimeImmutable';
|
|
|
+ case 'datetime':
|
|
|
+ return '\DateTime';
|
|
|
+ case 'snowflake':
|
|
|
+ case 'integer':
|
|
|
+ case 'timestamp':
|
|
|
+ return 'int';
|
|
|
+ case 'object':
|
|
|
+ return 'array';
|
|
|
+ case 'boolean':
|
|
|
+ return 'bool';
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function isTransformType(string $type): bool
|
|
|
+{
|
|
|
+ $nonTransform = ['string', 'array'];
|
|
|
+
|
|
|
+ return array_search($type, $nonTransform) === false;
|
|
|
+}
|
|
|
+
|
|
|
+function mapBadDocs(string $cls): string
|
|
|
+{
|
|
|
+ switch ($cls) {
|
|
|
+ case '\RestCord\Model\User\DmChannel':
|
|
|
+ return '\RestCord\Model\Channel\DmChannel';
|
|
|
+ case '\RestCord\Model\Channel\Invite':
|
|
|
+ case '\RestCord\Model\Guild\Invite':
|
|
|
+ return '\RestCord\Model\Invite\Invite';
|
|
|
+ case '\RestCord\Model\Guild\GuildChannel':
|
|
|
+ return '\RestCord\Model\Channel\GuildChannel';
|
|
|
+ case '\RestCord\Model\Guild\User':
|
|
|
+ case '\RestCord\Model\Channel\User':
|
|
|
+ return '\RestCord\Model\User\User';
|
|
|
+ case 'ISO8601':
|
|
|
+ case '\RestCord\Model\Channel\ISO8601':
|
|
|
+ return '\DateTimeImmutable';
|
|
|
+ default:
|
|
|
+ return $cls;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $cls;
|
|
|
+}
|
|
|
+
|
|
|
+function addTraits(PhpClass $class, string $resource, string $name)
|
|
|
+{
|
|
|
+ if ($resource === 'User' && $name === 'User') {
|
|
|
+ $class->addUseStatement(\RestCord\Traits\AvatarTrait::class)->addTrait("AvatarTrait");
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($resource === 'Guild' && $name === 'Guild') {
|
|
|
+ $class->addUseStatement(\RestCord\Traits\IconTrait::class)->addTrait("IconTrait");
|
|
|
+ $class->addUseStatement(\RestCord\Traits\SplashTrait::class)->addTrait("SplashTrait");
|
|
|
+ }
|
|
|
+}
|