buildModelClasses 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. #!/usr/bin/env php
  2. <?php
  3. /*
  4. * This file is part of php-restcord.
  5. *
  6. * (c) Aaron Scherer <aequasi@gmail.com>
  7. *
  8. * This source file is subject to the license that is bundled
  9. * with this source code in the file LICENSE
  10. */
  11. function recursiveRemoveDirectory($directory)
  12. {
  13. foreach (glob("{$directory}/*") as $file) {
  14. if (is_dir($file)) {
  15. recursiveRemoveDirectory($file);
  16. } else {
  17. unlink($file);
  18. }
  19. }
  20. rmdir($directory);
  21. }
  22. $path = __DIR__.'/../src/Model';
  23. try {
  24. recursiveRemoveDirectory($path);
  25. } catch (\Exception $e) {
  26. }
  27. mkdir($path, 02775, true);
  28. $path = realpath($path);
  29. require __DIR__.'/../vendor/autoload.php';
  30. use gossi\codegen\generator\CodeGenerator;
  31. use gossi\codegen\model\PhpClass;
  32. use gossi\codegen\model\PhpMethod;
  33. use gossi\codegen\model\PhpProperty;
  34. use Symfony\Component\Console\Application;
  35. use Symfony\Component\Console\Input\InputArgument;
  36. use Symfony\Component\Console\Input\InputInterface;
  37. use Symfony\Component\Console\Output\OutputInterface;
  38. $loader = new Twig_Loader_Filesystem(__DIR__.'/../src/Resources/');
  39. $twig = new Twig_Environment($loader, ['debug' => true]);
  40. $twig->addExtension(new Twig_Extension_Debug());
  41. $license = <<<EOF
  42. <?php
  43. /*
  44. * Copyright 2017 Aaron Scherer
  45. *
  46. * This source file is subject to the license that is bundled
  47. * with this source code in the file LICENSE
  48. *
  49. * @package restcord/restcord
  50. * @copyright Aaron Scherer 2017
  51. * @license MIT
  52. */
  53. \n
  54. EOF;
  55. /** @noinspection PhpUnhandledExceptionInspection */
  56. (new Application('Build Model Classes', '1.0.0'))
  57. ->register('buildModelClasses')
  58. ->addArgument('version', InputArgument::REQUIRED, 'Version to build')
  59. ->setCode(
  60. function (InputInterface $input, OutputInterface $output) use ($twig, $license, $path) {
  61. $style = new \Symfony\Component\Console\Style\SymfonyStyle($input, $output);
  62. $style->title("Building Model Classes for: ".$input->getArgument('version'));
  63. $definition = \GuzzleHttp\json_decode(
  64. file_get_contents(
  65. __DIR__.'/../src/Resources/service_description-v'.$input->getArgument('version').'.json'
  66. ),
  67. true
  68. );
  69. $generator = new CodeGenerator();
  70. foreach ($definition['models'] as $resource => $models) {
  71. foreach ($models as $name => $model) {
  72. $resource = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $resource)));
  73. $name = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name)));
  74. $class = new PhpClass();
  75. $class->setQualifiedName('RestCord\\Model\\'.ucwords($resource).'\\'.ucwords($name));
  76. $class->setDescription(ucwords($name)." Model");
  77. $class->setProperties(
  78. array_map(
  79. function (&$name, $property) use ($class, $resource, $model) {
  80. $optional = strpos($name, '?') !== false;
  81. $name = lcfirst(str_replace([' ', '?'], '', str_replace('-', '_', $name)));
  82. $prop = new PhpProperty($name);
  83. $prop->setType(normalizeType($name, $resource, $property).($optional ? "|null" : ""));
  84. $prop->setDescription($property['description']);
  85. if (isset($property['default'])) {
  86. $prop->setValue($property['default']);
  87. }
  88. return $prop;
  89. },
  90. array_keys($model['properties']),
  91. $model['properties']
  92. )
  93. );
  94. addTraits($class, $resource, $name);
  95. $constructor = new PhpMethod('__construct');
  96. $constructor->addSimpleParameter('content', 'array', null);
  97. $constructor->setBody(
  98. <<<EOF
  99. if (null === \$content) {
  100. return;
  101. }
  102. foreach (\$content as \$key => \$value) {
  103. \$key = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', \$key))));
  104. if (property_exists(\$this, \$key)) {
  105. \$this->{\$key} = \$value;
  106. }
  107. }
  108. EOF
  109. );
  110. $constructor->setVisibility('public');
  111. $class->setMethod($constructor);
  112. @mkdir($path.'/'.ucwords($resource), 02775, true);
  113. file_put_contents(
  114. $path.'/'.ucwords($resource).'/'.ucwords($name).'.php',
  115. $license.$generator->generate($class)
  116. );
  117. }
  118. }
  119. $style->success('Finished. Classes built in: '.realpath($path));
  120. }
  121. )
  122. ->getApplication()
  123. ->setDefaultCommand('buildModelClasses', true)
  124. ->run();
  125. function normalizeType(string $name, string $resource, array $property): string
  126. {
  127. $type = $property['type'];
  128. if ($name === 'user' && $resource === 'Guild') {
  129. $type = "user/user";
  130. }
  131. switch ($type) {
  132. default:
  133. if (strpos($type, '/') !== false || strpos($type, 'Array<') === 0) {
  134. $array = false;
  135. if (stripos($type, 'Array') !== false) {
  136. $array = true;
  137. $type = str_replace(['Array<', '>'], '', $type);
  138. }
  139. if ($type === 'snowflake') {
  140. return 'int[]';
  141. }
  142. $cls = "\\RestCord\\Model\\";
  143. $tmp = explode('/', $type);
  144. $cls .= ucwords($tmp[0])."\\";
  145. $clean = str_replace(['-object', '-'], ['', ' '], $tmp[1]);
  146. $clean = ucwords($clean);
  147. $clean = str_replace(' ', '', $clean);
  148. $fullClass = $cls.$clean.($array ? '[]' : '');
  149. $fullClass = mapBadDocs($fullClass);
  150. return $fullClass;
  151. }
  152. return $type;
  153. case 'array':
  154. if (strpos($property['description'], "ids") !== false) {
  155. return "int[]";
  156. }
  157. return "array";
  158. case 'ISO8601 timestamp':
  159. case 'ISO8601':
  160. return '\DateTimeImmutable';
  161. case 'datetime':
  162. return '\DateTime';
  163. case 'snowflake':
  164. case 'integer':
  165. case 'timestamp':
  166. return 'int';
  167. case 'object':
  168. return 'array';
  169. case 'boolean':
  170. return 'bool';
  171. }
  172. }
  173. function isTransformType(string $type): bool
  174. {
  175. $nonTransform = ['string', 'array'];
  176. return array_search($type, $nonTransform) === false;
  177. }
  178. function mapBadDocs(string $cls): string
  179. {
  180. switch ($cls) {
  181. case '\RestCord\Model\User\DmChannel':
  182. return '\RestCord\Model\Channel\DmChannel';
  183. case '\RestCord\Model\Channel\Invite':
  184. case '\RestCord\Model\Guild\Invite':
  185. return '\RestCord\Model\Invite\Invite';
  186. case '\RestCord\Model\Guild\GuildChannel':
  187. return '\RestCord\Model\Channel\GuildChannel';
  188. case '\RestCord\Model\Guild\User':
  189. case '\RestCord\Model\Channel\User':
  190. return '\RestCord\Model\User\User';
  191. case 'ISO8601':
  192. case '\RestCord\Model\Channel\ISO8601':
  193. return '\DateTimeImmutable';
  194. default:
  195. return $cls;
  196. }
  197. return $cls;
  198. }
  199. function addTraits(PhpClass $class, string $resource, string $name)
  200. {
  201. if ($resource === 'User' && $name === 'User') {
  202. $class->addUseStatement(\RestCord\Traits\AvatarTrait::class)->addTrait("AvatarTrait");
  203. }
  204. if ($resource === 'Guild' && $name === 'Guild') {
  205. $class->addUseStatement(\RestCord\Traits\IconTrait::class)->addTrait("IconTrait");
  206. $class->addUseStatement(\RestCord\Traits\SplashTrait::class)->addTrait("SplashTrait");
  207. }
  208. }