diff --git a/bin/createSingleFile.php b/bin/createSingleFile.php new file mode 100755 index 00000000..97584912 --- /dev/null +++ b/bin/createSingleFile.php @@ -0,0 +1,436 @@ +#!/usr/bin/php +_namespaces = array(); + } + + public static function from($libraryPath) { + $nsroot = self::NS_ROOT; + $predisFile = new PredisFile(); + $libIterator = new RecursiveDirectoryIterator("$libraryPath$nsroot"); + + foreach (new RecursiveIteratorIterator($libIterator) as $classFile) { + if (!$classFile->isFile()) { + continue; + } + $namespace = strtr(str_replace($libraryPath, '', $classFile->getPath()), '/', '\\'); + $phpNamespace = $predisFile->getNamespace($namespace); + if ($phpNamespace === false) { + $phpNamespace = new PhpNamespace($namespace); + $predisFile->addNamespace($phpNamespace); + } + $phpClass = new PhpClass($phpNamespace, $classFile); + } + return $predisFile; + } + + public function addNamespace(PhpNamespace $namespace) { + if (isset($this->_namespaces[(string)$namespace])) { + throw new InvalidArgumentException("Duplicated namespace"); + } + $this->_namespaces[(string)$namespace] = $namespace; + } + + public function getNamespaces() { + return $this->_namespaces; + } + + public function getNamespace($namespace) { + if (!isset($this->_namespaces[$namespace])) { + return false; + } + return $this->_namespaces[$namespace]; + } + + public function getClassByFQN($classFqn) { + if (($nsLastPos = strrpos($classFqn, '\\')) !== false) { + $namespace = $this->getNamespace(substr($classFqn, 0, $nsLastPos)); + if ($namespace === false) { + return null; + } + $className = substr($classFqn, $nsLastPos + 1); + return $namespace->getClass($className); + } + return null; + } + + private function calculateDependencyScores(&$classes, $fqn) { + if (!isset($classes[$fqn])) { + $classes[$fqn] = 0; + } + $classes[$fqn] += 1; + $phpClass = $this->getClassByFQN($fqn); + foreach ($phpClass->getDependencies() as $fqn) { + $this->calculateDependencyScores($classes, $fqn); + } + } + + private function getDependencyScores() { + $classes = array(); + foreach ($this->getNamespaces() as $phpNamespace) { + foreach ($phpNamespace->getClasses() as $phpClass) { + $this->calculateDependencyScores($classes, $phpClass->getFQN()); + } + } + return $classes; + } + + private function getOrderedNamespaces($dependencyScores) { + $namespaces = array_fill_keys(array_unique( + array_map( + function($fqn) { return PhpNamespace::extractName($fqn); }, + array_keys($dependencyScores) + ) + ), 0); + foreach ($dependencyScores as $classFqn => $score) { + $namespaces[PhpNamespace::extractName($classFqn)] += $score; + } + arsort($namespaces); + return array_keys($namespaces); + } + + private function getOrderedClasses(PhpNamespace $phpNamespace, $classes) { + $nsClassesFQNs = array_map( + function($cl) { return $cl->getFQN(); }, + $phpNamespace->getClasses() + ); + $nsOrderedClasses = array(); + foreach ($nsClassesFQNs as $nsClassFQN) { + $nsOrderedClasses[$nsClassFQN] = $classes[$nsClassFQN]; + } + arsort($nsOrderedClasses); + return array_keys($nsOrderedClasses); + } + + public function getPhpCode() { + $buffer = array("getDependencyScores(); + $namespaces = $this->getOrderedNamespaces($classes); + + foreach ($namespaces as $namespace) { + $phpNamespace = $this->getNamespace($namespace); + + // generate namespace directive + $buffer[] = $phpNamespace->getPhpCode(); + $buffer[] = "\n"; + + // generate use directives + $useDirectives = $phpNamespace->getUseDirectives(); + if (count($useDirectives) > 0) { + $buffer[] = $useDirectives->getPhpCode(); + $buffer[] = "\n"; + } + + // generate classes bodies + $nsClasses = $this->getOrderedClasses($phpNamespace, $classes); + foreach ($nsClasses as $classFQN) { + $buffer[] = $this->getClassByFQN($classFQN)->getPhpCode(); + $buffer[] = "\n\n"; + } + + $buffer[] = "/* " . str_repeat("-", 75) . " */"; + $buffer[] = "\n\n"; + } + return implode($buffer); + } + + public function saveTo($outputFile) { + // TODO: add more sanity checks + if ($outputFile === null || $outputFile === '') { + throw new InvalidArgumentException('You must specify a valid output file'); + } + file_put_contents($outputFile, $this->getPhpCode()); + } +} + +class PhpNamespace implements IteratorAggregate { + private $_namespace, $_classes; + + public function __construct($namespace) { + $this->_namespace = $namespace; + $this->_classes = array(); + $this->_useDirectives = new PhpUseDirectives($this); + } + + public static function extractName($fqn) { + $nsSepLast = strrpos($fqn, '\\'); + if ($nsSepLast === false) { + return $fqn; + } + $ns = substr($fqn, 0, $nsSepLast); + return $ns !== '' ? $ns : null; + + } + + public function addClass(PhpClass $class) { + $this->_classes[$class->getName()] = $class; + } + + public function getClass($className) { + return $this->_classes[$className]; + } + + public function getClasses() { + return array_values($this->_classes); + } + + public function getIterator() { + return new \ArrayIterator($this->getClasses()); + } + + public function getUseDirectives() { + return $this->_useDirectives; + } + + public function getPhpCode() { + return "namespace $this->_namespace;\n"; + } + + public function __toString() { + return $this->_namespace; + } +} + +class PhpUseDirectives implements Countable, IteratorAggregate { + private $_use, $_aliases, $_namespace; + + public function __construct(PhpNamespace $namespace) { + $this->_use = array(); + $this->_aliases = array(); + $this->_namespace = $namespace; + } + + public function add($use, $as = null) { + if (!in_array($use, $this->_use)) { + $this->_use[] = $use; + $this->_aliases[$as ?: PhpClass::extractName($use)] = $use; + } + } + + public function getList() { + return $this->_use; + } + + public function getIterator() { + return new \ArrayIterator($this->getList()); + } + + public function getPhpCode() { + $reducer = function($str, $use) { return $str .= "use $use;\n"; }; + return array_reduce($this->getList(), $reducer, ''); + } + + public function getNamespace() { + return $this->_namespace; + } + + public function getFQN($className) { + if (($nsSepFirst = strpos($className, '\\')) === false) { + if (isset($this->_aliases[$className])) { + return $this->_aliases[$className]; + } + return (string)$this->getNamespace() . "\\$className"; + } + if ($nsSepFirst != 0) { + throw new InvalidArgumentException("Partially qualified names are not supported"); + } + return $className; + } + + public function count() { + return count($this->_use); + } +} + +class PhpClass { + private $_namespace, $_file, $_body, $_implements, $_extends, $_name; + + public function __construct(PhpNamespace $namespace, SplFileInfo $classFile) { + $this->_namespace = $namespace; + $this->_file = $classFile; + $this->_implements = array(); + $this->_extends = array(); + $this->extractData(); + $namespace->addClass($this); + } + + public static function extractName($fqn) { + $nsSepLast = strrpos($fqn, '\\'); + if ($nsSepLast === false) { + return $fqn; + } + return substr($fqn, $nsSepLast + 1); + } + + private function extractData() { + $useDirectives = $this->getNamespace()->getUseDirectives(); + + $useExtractor = function($m) use($useDirectives) { + $useDirectives->add(($namespacedPath = $m[1])); + }; + + $classBuffer = stream_get_contents(fopen($this->getFile()->getPathname(), 'r')); + + $classBuffer = preg_replace('/<\?php\s?\\n\s?/', '', $classBuffer); + $classBuffer = preg_replace('/\s?\?>\n?/ms', '', $classBuffer); + $classBuffer = preg_replace('/namespace\s+.*;\s?/', '', $classBuffer); + $classBuffer = preg_replace_callback('/use\s+(.*)(\s+as\s+.*)?;\s?\n?/', $useExtractor, $classBuffer); + + $this->_body = trim($classBuffer); + + $this->extractHierarchy(); + } + + private function extractHierarchy() { + $implements = array(); + $extends = array(); + + $extractor = function($iterator, $callback) { + $className = ''; + $iterator->seek($iterator->key() + 1); + while ($iterator->valid()) { + $token = $iterator->current(); + if (is_string($token)) { + if (preg_match('/\s?,\s?/', $token)) { + $callback(trim($className)); + $className = ''; + } + else if ($token == '{') { + $callback(trim($className)); + return; + } + } + switch ($token[0]) { + case T_NS_SEPARATOR: + $className .= '\\'; + break; + case T_STRING: + $className .= $token[1]; + break; + case T_IMPLEMENTS: + case T_EXTENDS: + $callback(trim($className)); + $iterator->seek($iterator->key() - 1); + return; + } + $iterator->next(); + } + }; + + $tokens = token_get_all("getPhpCode())); + + $iterator = new ArrayIterator($tokens); + while ($iterator->valid()) { + $token = $iterator->current(); + if (is_string($token)) { + $iterator->next(); + continue; + } + + switch ($token[0]) { + case T_CLASS: + case T_INTERFACE: + $iterator->seek($iterator->key() + 2); + $tk = $iterator->current(); + $this->_name = $tk[1]; + break; + case T_IMPLEMENTS: + $extractor($iterator, function($fqn) use (&$implements) { + $implements[] = $fqn; + }); + break; + case T_EXTENDS: + $extractor($iterator, function($fqn) use (&$extends) { + $extends[] = $fqn; + }); + break; + } + $iterator->next(); + } + + $this->_implements = $this->guessFQN($implements); + $this->_extends = $this->guessFQN($extends); + } + + public function guessFQN($classes) { + $useDirectives = $this->getNamespace()->getUseDirectives(); + return array_map(array($useDirectives, 'getFQN'), $classes); + } + + public function getImplementedInterfaces($all = false) { + if ($all) { + return $this->_implements; + } + else { + return array_filter( + $this->_implements, + function ($cn) { return strpos($cn, 'Predis\\') === 0; } + ); + } + } + + public function getExtendedClasses($all = false) { + if ($all) { + return $this->_extemds; + } + else { + return array_filter( + $this->_extends, + function ($cn) { return strpos($cn, 'Predis\\') === 0; } + ); + } + } + + public function getDependencies($all = false) { + return array_merge( + $this->getImplementedInterfaces($all), + $this->getExtendedClasses($all) + ); + } + + public function getNamespace() { + return $this->_namespace; + } + + public function getFile() { + return $this->_file; + } + + public function getName() { + return $this->_name; + } + + public function getFQN() { + return (string)$this->getNamespace() . '\\' . $this->_name; + } + + public function getPhpCode() { + return $this->_body; + } + + public function __toString() { + return "class " . $this->getName() . '{ ... }'; + } +} + +/* -------------------------------------------------------------------------- */ + +$predisFile = PredisFile::from(__DIR__ . "/../lib/"); +$predisFile->saveTo(isset($argv[1]) ? $argv[1] : PredisFile::NS_ROOT . ".php");