createSingleFile.php 14 KB

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