createSingleFile.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. #!/usr/bin/php
  2. <?php
  3. // This script can be used to automatically glue all the .php files of Predis
  4. // into a single monolithic script file that can be used without an autoloader,
  5. // just like the other previous versions of the library.
  6. //
  7. // Much of its complexity is due to the fact that we cannot simply join PHP
  8. // files, but namespaces and classes definitions must follow a precise order
  9. // when dealing with subclassing and inheritance.
  10. //
  11. // The current implementation is pretty naïve, but it should do for now.
  12. //
  13. /* -------------------------------------------------------------------------- */
  14. class PredisFile {
  15. const NS_ROOT = 'Predis';
  16. private $_namespaces;
  17. public function __construct() {
  18. $this->_namespaces = array();
  19. }
  20. public static function from($libraryPath) {
  21. $nsroot = self::NS_ROOT;
  22. $predisFile = new PredisFile();
  23. $libIterator = new RecursiveDirectoryIterator("$libraryPath$nsroot");
  24. foreach (new RecursiveIteratorIterator($libIterator) as $classFile) {
  25. if (!$classFile->isFile()) {
  26. continue;
  27. }
  28. $namespace = strtr(str_replace($libraryPath, '', $classFile->getPath()), '/', '\\');
  29. $phpNamespace = $predisFile->getNamespace($namespace);
  30. if ($phpNamespace === false) {
  31. $phpNamespace = new PhpNamespace($namespace);
  32. $predisFile->addNamespace($phpNamespace);
  33. }
  34. $phpClass = new PhpClass($phpNamespace, $classFile);
  35. }
  36. return $predisFile;
  37. }
  38. public function addNamespace(PhpNamespace $namespace) {
  39. if (isset($this->_namespaces[(string)$namespace])) {
  40. throw new InvalidArgumentException("Duplicated namespace");
  41. }
  42. $this->_namespaces[(string)$namespace] = $namespace;
  43. }
  44. public function getNamespaces() {
  45. return $this->_namespaces;
  46. }
  47. public function getNamespace($namespace) {
  48. if (!isset($this->_namespaces[$namespace])) {
  49. return false;
  50. }
  51. return $this->_namespaces[$namespace];
  52. }
  53. public function getClassByFQN($classFqn) {
  54. if (($nsLastPos = strrpos($classFqn, '\\')) !== false) {
  55. $namespace = $this->getNamespace(substr($classFqn, 0, $nsLastPos));
  56. if ($namespace === false) {
  57. return null;
  58. }
  59. $className = substr($classFqn, $nsLastPos + 1);
  60. return $namespace->getClass($className);
  61. }
  62. return null;
  63. }
  64. private function calculateDependencyScores(&$classes, $fqn) {
  65. if (!isset($classes[$fqn])) {
  66. $classes[$fqn] = 0;
  67. }
  68. $classes[$fqn] += 1;
  69. $phpClass = $this->getClassByFQN($fqn);
  70. foreach ($phpClass->getDependencies() as $fqn) {
  71. $this->calculateDependencyScores($classes, $fqn);
  72. }
  73. }
  74. private function getDependencyScores() {
  75. $classes = array();
  76. foreach ($this->getNamespaces() as $phpNamespace) {
  77. foreach ($phpNamespace->getClasses() as $phpClass) {
  78. $this->calculateDependencyScores($classes, $phpClass->getFQN());
  79. }
  80. }
  81. return $classes;
  82. }
  83. private function getOrderedNamespaces($dependencyScores) {
  84. $namespaces = array_fill_keys(array_unique(
  85. array_map(
  86. function($fqn) { return PhpNamespace::extractName($fqn); },
  87. array_keys($dependencyScores)
  88. )
  89. ), 0);
  90. foreach ($dependencyScores as $classFqn => $score) {
  91. $namespaces[PhpNamespace::extractName($classFqn)] += $score;
  92. }
  93. arsort($namespaces);
  94. return array_keys($namespaces);
  95. }
  96. private function getOrderedClasses(PhpNamespace $phpNamespace, $classes) {
  97. $nsClassesFQNs = array_map(
  98. function($cl) { return $cl->getFQN(); },
  99. $phpNamespace->getClasses()
  100. );
  101. $nsOrderedClasses = array();
  102. foreach ($nsClassesFQNs as $nsClassFQN) {
  103. $nsOrderedClasses[$nsClassFQN] = $classes[$nsClassFQN];
  104. }
  105. arsort($nsOrderedClasses);
  106. return array_keys($nsOrderedClasses);
  107. }
  108. public function getPhpCode() {
  109. $buffer = array("<?php\n\n");
  110. $classes = $this->getDependencyScores();
  111. $namespaces = $this->getOrderedNamespaces($classes);
  112. foreach ($namespaces as $namespace) {
  113. $phpNamespace = $this->getNamespace($namespace);
  114. // generate namespace directive
  115. $buffer[] = $phpNamespace->getPhpCode();
  116. $buffer[] = "\n";
  117. // generate use directives
  118. $useDirectives = $phpNamespace->getUseDirectives();
  119. if (count($useDirectives) > 0) {
  120. $buffer[] = $useDirectives->getPhpCode();
  121. $buffer[] = "\n";
  122. }
  123. // generate classes bodies
  124. $nsClasses = $this->getOrderedClasses($phpNamespace, $classes);
  125. foreach ($nsClasses as $classFQN) {
  126. $buffer[] = $this->getClassByFQN($classFQN)->getPhpCode();
  127. $buffer[] = "\n\n";
  128. }
  129. $buffer[] = "/* " . str_repeat("-", 75) . " */";
  130. $buffer[] = "\n\n";
  131. }
  132. return implode($buffer);
  133. }
  134. public function saveTo($outputFile) {
  135. // TODO: add more sanity checks
  136. if ($outputFile === null || $outputFile === '') {
  137. throw new InvalidArgumentException('You must specify a valid output file');
  138. }
  139. file_put_contents($outputFile, $this->getPhpCode());
  140. }
  141. }
  142. class PhpNamespace implements IteratorAggregate {
  143. private $_namespace, $_classes;
  144. public function __construct($namespace) {
  145. $this->_namespace = $namespace;
  146. $this->_classes = array();
  147. $this->_useDirectives = new PhpUseDirectives($this);
  148. }
  149. public static function extractName($fqn) {
  150. $nsSepLast = strrpos($fqn, '\\');
  151. if ($nsSepLast === false) {
  152. return $fqn;
  153. }
  154. $ns = substr($fqn, 0, $nsSepLast);
  155. return $ns !== '' ? $ns : null;
  156. }
  157. public function addClass(PhpClass $class) {
  158. $this->_classes[$class->getName()] = $class;
  159. }
  160. public function getClass($className) {
  161. return $this->_classes[$className];
  162. }
  163. public function getClasses() {
  164. return array_values($this->_classes);
  165. }
  166. public function getIterator() {
  167. return new \ArrayIterator($this->getClasses());
  168. }
  169. public function getUseDirectives() {
  170. return $this->_useDirectives;
  171. }
  172. public function getPhpCode() {
  173. return "namespace $this->_namespace;\n";
  174. }
  175. public function __toString() {
  176. return $this->_namespace;
  177. }
  178. }
  179. class PhpUseDirectives implements Countable, IteratorAggregate {
  180. private $_use, $_aliases, $_namespace;
  181. public function __construct(PhpNamespace $namespace) {
  182. $this->_use = array();
  183. $this->_aliases = array();
  184. $this->_namespace = $namespace;
  185. }
  186. public function add($use, $as = null) {
  187. if (!in_array($use, $this->_use)) {
  188. $this->_use[] = $use;
  189. $this->_aliases[$as ?: PhpClass::extractName($use)] = $use;
  190. }
  191. }
  192. public function getList() {
  193. return $this->_use;
  194. }
  195. public function getIterator() {
  196. return new \ArrayIterator($this->getList());
  197. }
  198. public function getPhpCode() {
  199. $reducer = function($str, $use) { return $str .= "use $use;\n"; };
  200. return array_reduce($this->getList(), $reducer, '');
  201. }
  202. public function getNamespace() {
  203. return $this->_namespace;
  204. }
  205. public function getFQN($className) {
  206. if (($nsSepFirst = strpos($className, '\\')) === false) {
  207. if (isset($this->_aliases[$className])) {
  208. return $this->_aliases[$className];
  209. }
  210. return (string)$this->getNamespace() . "\\$className";
  211. }
  212. if ($nsSepFirst != 0) {
  213. throw new InvalidArgumentException("Partially qualified names are not supported");
  214. }
  215. return $className;
  216. }
  217. public function count() {
  218. return count($this->_use);
  219. }
  220. }
  221. class PhpClass {
  222. private $_namespace, $_file, $_body, $_implements, $_extends, $_name;
  223. public function __construct(PhpNamespace $namespace, SplFileInfo $classFile) {
  224. $this->_namespace = $namespace;
  225. $this->_file = $classFile;
  226. $this->_implements = array();
  227. $this->_extends = array();
  228. $this->extractData();
  229. $namespace->addClass($this);
  230. }
  231. public static function extractName($fqn) {
  232. $nsSepLast = strrpos($fqn, '\\');
  233. if ($nsSepLast === false) {
  234. return $fqn;
  235. }
  236. return substr($fqn, $nsSepLast + 1);
  237. }
  238. private function extractData() {
  239. $useDirectives = $this->getNamespace()->getUseDirectives();
  240. $useExtractor = function($m) use($useDirectives) {
  241. $useDirectives->add(($namespacedPath = $m[1]));
  242. };
  243. $classBuffer = stream_get_contents(fopen($this->getFile()->getPathname(), 'r'));
  244. $classBuffer = preg_replace('/<\?php\s?\\n\s?/', '', $classBuffer);
  245. $classBuffer = preg_replace('/\s?\?>\n?/ms', '', $classBuffer);
  246. $classBuffer = preg_replace('/namespace\s+.*;\s?/', '', $classBuffer);
  247. $classBuffer = preg_replace_callback('/use\s+(.*)(\s+as\s+.*)?;\s?\n?/', $useExtractor, $classBuffer);
  248. $this->_body = trim($classBuffer);
  249. $this->extractHierarchy();
  250. }
  251. private function extractHierarchy() {
  252. $implements = array();
  253. $extends = array();
  254. $extractor = function($iterator, $callback) {
  255. $className = '';
  256. $iterator->seek($iterator->key() + 1);
  257. while ($iterator->valid()) {
  258. $token = $iterator->current();
  259. if (is_string($token)) {
  260. if (preg_match('/\s?,\s?/', $token)) {
  261. $callback(trim($className));
  262. $className = '';
  263. }
  264. else if ($token == '{') {
  265. $callback(trim($className));
  266. return;
  267. }
  268. }
  269. switch ($token[0]) {
  270. case T_NS_SEPARATOR:
  271. $className .= '\\';
  272. break;
  273. case T_STRING:
  274. $className .= $token[1];
  275. break;
  276. case T_IMPLEMENTS:
  277. case T_EXTENDS:
  278. $callback(trim($className));
  279. $iterator->seek($iterator->key() - 1);
  280. return;
  281. }
  282. $iterator->next();
  283. }
  284. };
  285. $tokens = token_get_all("<?php\n" . trim($this->getPhpCode()));
  286. $iterator = new ArrayIterator($tokens);
  287. while ($iterator->valid()) {
  288. $token = $iterator->current();
  289. if (is_string($token)) {
  290. $iterator->next();
  291. continue;
  292. }
  293. switch ($token[0]) {
  294. case T_CLASS:
  295. case T_INTERFACE:
  296. $iterator->seek($iterator->key() + 2);
  297. $tk = $iterator->current();
  298. $this->_name = $tk[1];
  299. break;
  300. case T_IMPLEMENTS:
  301. $extractor($iterator, function($fqn) use (&$implements) {
  302. $implements[] = $fqn;
  303. });
  304. break;
  305. case T_EXTENDS:
  306. $extractor($iterator, function($fqn) use (&$extends) {
  307. $extends[] = $fqn;
  308. });
  309. break;
  310. }
  311. $iterator->next();
  312. }
  313. $this->_implements = $this->guessFQN($implements);
  314. $this->_extends = $this->guessFQN($extends);
  315. }
  316. public function guessFQN($classes) {
  317. $useDirectives = $this->getNamespace()->getUseDirectives();
  318. return array_map(array($useDirectives, 'getFQN'), $classes);
  319. }
  320. public function getImplementedInterfaces($all = false) {
  321. if ($all) {
  322. return $this->_implements;
  323. }
  324. else {
  325. return array_filter(
  326. $this->_implements,
  327. function ($cn) { return strpos($cn, 'Predis\\') === 0; }
  328. );
  329. }
  330. }
  331. public function getExtendedClasses($all = false) {
  332. if ($all) {
  333. return $this->_extemds;
  334. }
  335. else {
  336. return array_filter(
  337. $this->_extends,
  338. function ($cn) { return strpos($cn, 'Predis\\') === 0; }
  339. );
  340. }
  341. }
  342. public function getDependencies($all = false) {
  343. return array_merge(
  344. $this->getImplementedInterfaces($all),
  345. $this->getExtendedClasses($all)
  346. );
  347. }
  348. public function getNamespace() {
  349. return $this->_namespace;
  350. }
  351. public function getFile() {
  352. return $this->_file;
  353. }
  354. public function getName() {
  355. return $this->_name;
  356. }
  357. public function getFQN() {
  358. return (string)$this->getNamespace() . '\\' . $this->_name;
  359. }
  360. public function getPhpCode() {
  361. return $this->_body;
  362. }
  363. public function __toString() {
  364. return "class " . $this->getName() . '{ ... }';
  365. }
  366. }
  367. /* -------------------------------------------------------------------------- */
  368. $predisFile = PredisFile::from(__DIR__ . "/../lib/");
  369. $predisFile->saveTo(isset($argv[1]) ? $argv[1] : PredisFile::NS_ROOT . ".php");