more refactoring; now the package hook starts to make sense
authorMichael Wallner <mike@php.net>
Fri, 27 Mar 2015 19:08:44 +0000 (20:08 +0100)
committerMichael Wallner <mike@php.net>
Fri, 27 Mar 2015 19:34:49 +0000 (20:34 +0100)
15 files changed:
Makefile
bin/pharext
src/pharext/Cli/Args.php
src/pharext/Cli/Command.php
src/pharext/ExecCmd.php
src/pharext/Installer.php
src/pharext/Packager.php
src/pharext/SourceDir.php
src/pharext/SourceDir/Git.php
src/pharext/SourceDir/Pecl.php
src/pharext/SourceDir/Pharext.php [deleted file]
src/pharext/Task/Cleanup.php [new file with mode: 0644]
src/pharext_install.tpl.php [deleted file]
tests/src/pharext/GitSourceDirTest.php
tests/src/pharext/TaskTest.php [new file with mode: 0644]

index 433b800792fb551cda94849949f610b551782e34..4c78e66c8a43c6bd6ba81ca82efe033e10e6813d 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,7 @@ bin/pharext: src/* src/pharext/* src/pharext/*/*
 
 test:
        @echo "Running tests ... "
-       @phpunit tests
+       @php -dphar.readonly=0 `which phpunit` tests
        
 clean:
        rm bin/pharext*
index 911b258b1f47e349d06b92667419046624737b15..c32af327e50927ca82d60750af0b2d3a270e4a62 100755 (executable)
Binary files a/bin/pharext and b/bin/pharext differ
index f0b0498daf521a91dc83e36cb3fa7f20122b3705..38cfaca80e862546f06f989e13d7c9844c6f46cb 100644 (file)
@@ -67,24 +67,27 @@ class Args implements \ArrayAccess
 
        /**
         * Compile the original spec
-        * @param array $spec
+        * @param array|Traversable $spec
         */
-       public function __construct(array $spec = null) {
-               $this->compile($spec);
+       public function __construct($spec = null) {
+               if (is_array($spec) || $spec instanceof Traversable) {
+                       $this->compile($spec);
+               }
+               
        }
        
        /**
         * Compile the original spec
-        * @param array $spec
+        * @param array|Traversable $spec
         * @return pharext\CliArgs self
         */
-       public function compile(array $spec = null) {
-               $this->orig = array_merge($this->orig, (array) $spec);
-               foreach ((array) $spec as $arg) {
+       public function compile($spec) {
+               foreach ($spec as $arg) {
                        if (isset($arg[0])) { 
                                $this->spec["-".$arg[0]] = $arg;
                        }
                        $this->spec["--".$arg[1]] = $arg;
+                       $this->orig[] = $arg;
                }
                return $this;
        }
index 0cc0bb40f2b8649cc06de21d577c7b76728f2a44..6ad2c806b4a4ccabe3602f1b5ed8c836e5ca6423 100644 (file)
@@ -110,15 +110,13 @@ trait Command
         * @see \pharext\Command::error()
         */
        public function error($fmt) {
-               if (!$this->args->quiet) {
-                       if (!isset($fmt)) {
-                               $fmt = "%s\n";
-                               $arg = error_get_last()["message"];
-                       } else {
-                               $arg = array_slice(func_get_args(), 1);
-                       }
-                       vfprintf(STDERR, "ERROR: $fmt", $arg);
+               if (!isset($fmt)) {
+                       $fmt = "%s\n";
+                       $arg = error_get_last()["message"];
+               } else {
+                       $arg = array_slice(func_get_args(), 1);
                }
+               vfprintf(STDERR, "ERROR: $fmt", $arg);
        }
 
        /**
@@ -183,21 +181,16 @@ trait Command
        }
        
        /**
-        * rm -r
-        * @param string $dir
+        * Verbosity
+        * @return boolean
         */
-       private function rm($dir) {
-               foreach (scandir($dir) as $entry) {
-                       if ($entry === "." || $entry === "..") {
-                               continue;
-                       } elseif (is_dir("$dir/$entry")) {
-                               $this->rm("$dir/$entry");
-                       } elseif (!unlink("$dir/$entry")) {
-                               $this->warn(null);
-                       }
-               }
-               if (!rmdir($dir)) {
-                       $this->warn(null);
+       public function verbosity() {
+               if ($this->args->verbose) {
+                       return true;
+               } elseif ($this->args->quiet) {
+                       return false;
+               } else {
+                       return null;
                }
        }
 }
index 84bb70882cd2fcb24351cdd47888679b30bd79be..7e657f3d6f4bab3351988eb754d49c6639d7a032 100644 (file)
@@ -57,16 +57,19 @@ class ExecCmd
        /**
         * Execute a program with escalated privileges handling interactive password prompt
         * @param string $command
-        * @param string $output
-        * @param int $status
+        * @param bool $verbose
+        * @return int exit status
         */
-       private function suExec($command, &$output, &$status) {
+       private function suExec($command, $verbose = null) {
                if (!($proc = proc_open($command, [STDIN,["pipe","w"],["pipe","w"]], $pipes))) {
-                       $status = -1;
+                       $this->status = -1;
                        throw new Exception("Failed to run {$command}");
                }
+               
                $stdout = $pipes[1];
                $passwd = 0;
+               $checks = 10;
+
                while (!feof($stdout)) {
                        $R = [$stdout]; $W = []; $E = [];
                        if (!stream_select($R, $W, $E, null)) {
@@ -74,14 +77,49 @@ class ExecCmd
                        }
                        $data = fread($stdout, 0x1000);
                        /* only check a few times */
-                       if ($passwd++ < 10) {
+                       if ($passwd < $checks) {
+                               $passwd++;
                                if (stristr($data, "password")) {
+                                       $passwd = $checks + 1;
                                        printf("\n%s", $data);
+                                       continue;
+                               }
+                       } elseif ($passwd > $checks) {
+                               /* new line after pw entry */
+                               printf("\n");
+                               $passwd = $checks;
+                       }
+                       
+                       if ($verbose === null) {
+                               print $this->progress($data, 0);
+                       } else {
+                               if ($verbose) {
+                                       printf("%s\n", $data);
                                }
+                               $this->output .= $data;
                        }
-                       $output .= $data;
                }
-               $status = proc_close($proc);
+               if ($verbose === null) {
+                       $this->progress("", PHP_OUTPUT_HANDLER_FINAL);
+               }
+               return $this->status = proc_close($proc);
+       }
+
+       /**
+        * Output handler that displays some progress while soaking output
+        * @param string $string
+        * @param int $flags
+        * @return string
+        */
+       private function progress($string, $flags) {
+               static $c = 0;
+               static $s = ["\\","|","/","-"];
+
+               $this->output .= $string;
+
+               return $flags & PHP_OUTPUT_HANDLER_FINAL
+                       ? "   \r"
+                       : sprintf("  %s\r", $s[$c++ % count($s)]);
        }
 
        /**
@@ -97,7 +135,7 @@ class ExecCmd
                }
                
                if ($this->sudo) {
-                       $this->suExec(sprintf($this->sudo." 2>&1", $exec), $this->output, $this->status);
+                       $this->suExec(sprintf($this->sudo." 2>&1", $exec), $this->verbose);
                } elseif ($this->verbose) {
                        ob_start(function($s) {
                                $this->output .= $s;
@@ -105,6 +143,10 @@ class ExecCmd
                        }, 1);
                        passthru($exec, $this->status);
                        ob_end_flush();
+               } elseif ($this->verbose !== false /* !quiet */) {
+                       ob_start([$this, "progress"], 1);
+                       passthru($exec . " 2>&1", $this->status);
+                       ob_end_flush();
                } else {
                        exec($exec ." 2>&1", $output, $this->status);
                        $this->output = implode("\n", $output);
index 83a40ad9769d4babb16dfd063c27d10f3dd6d5f0..60ca2262ce7316c76c1d911d283dec549fcfadd7 100644 (file)
@@ -55,20 +55,39 @@ class Installer implements Command
        
        private function extract(Phar $phar) {
                $this->debug("Extracting %s ...\n", basename($phar->getPath()));
-               return (new Task\Extract($phar))->run($this->args->verbose);
+               return (new Task\Extract($phar))->run($this->verbosity());
        }
-
+       
        private function hooks(SplObjectStorage $phars) {
-               $hooks = [];
+               $hook = [];
                foreach ($phars as $phar) {
-                       if (isset($phar["pharext_install.php"])) {
-                               $callable = include $phar["pharext_install.php"];
-                               if (is_callable($callable)) {
-                                       $hooks[] = $callable($this);
+                       if (isset($phar["pharext_package.php"])) {
+                               $sdir = include $phar["pharext_package.php"];
+                               if ($sdir instanceof SourceDir) {
+                                       $this->args->compile($sdir->getArgs());
+                                       $hook[] = $sdir;
                                }
                        }
                }
-               return $hooks;
+               return $hook;
+       }
+
+       private function load() {
+               $list = new SplObjectStorage();
+               $phar = new Phar(Phar::running(false));
+               $temp = $this->extract($phar);
+
+               foreach ($phar as $entry) {
+                       $dep_file = $entry->getBaseName();
+                       if (fnmatch("*.ext.phar*", $dep_file)) {
+                               $dep_phar = new Phar("$temp/$dep_file");
+                               $list[$dep_phar] = $this->extract($dep_phar);
+                       }
+               }
+               
+               /* the actual ext.phar at last */
+               $list[$phar] = $temp;
+               return $list;
        }
 
        /**
@@ -77,20 +96,8 @@ class Installer implements Command
         */
        public function run($argc, array $argv) {
                try {
-                       $list = new SplObjectStorage();
-                       $phar = new Phar(Phar::running(false));
-                       $temp = $this->extract($phar);
-
-                       foreach ($phar as $entry) {
-                               $dep_file = $entry->getBaseName();
-                               if (fnmatch("*.ext.phar*", $dep_file)) {
-                                       $dep_phar = new Phar("$temp/$dep_file");
-                                       $list[$dep_phar] = $this->extract($dep_phar);
-                               }
-                       }
-                       /* the actual ext.phar at last */
-                       $list[$phar] = $temp;
-
+                       /* load the phar(s) */
+                       $list = $this->load();
                        /* installer hooks */
                        $hook = $this->hooks($list);
                } catch (\Exception $e) {
@@ -141,10 +148,8 @@ class Installer implements Command
 
                try {
                        /* post process hooks */
-                       foreach ($hook as $callback) {
-                               if (is_callable($callback)) {
-                                       $callback($this);
-                               }
+                       foreach ($hook as $sdir) {
+                               $sdir->setArgs($this->args);
                        }
                } catch (\Exception $e) {
                        $this->error("%s\n", $e->getMessage());
@@ -173,31 +178,27 @@ class Installer implements Command
                // phpize
                $this->info("Running phpize ...\n");
                $phpize = new Task\Phpize($temp, $this->args->prefix, $this->args->{"common-name"});
-               $phpize->run($this->args->verbose);
+               $phpize->run($this->verbosity());
 
                // configure
                $this->info("Running configure ...\n");
                $configure = new Task\Configure($temp, $this->args->configure, $this->args->prefix, $this->args{"common-name"});
-               $configure->run($this->args->verbose);
+               $configure->run($this->verbosity());
 
                // make
                $this->info("Running make ...\n");
                $make = new Task\Make($temp);
-               $make->run($this->args->verbose);
+               $make->run($this->verbosity());
 
                // install
                $this->info("Running make install ...\n");
                $sudo = isset($this->args->sudo) ? $this->args->sudo : null;
                $install = new Task\Make($temp, ["install"], $sudo);
-               $install->run($this->args->verbose);
+               $install->run($this->verbosity());
        }
 
        private function cleanup($temp) {
-               if (is_dir($temp)) {
-                       $this->rm($temp);
-               } elseif (file_exists($temp)) {
-                       unlink($temp);
-               }
+               (new Task\Cleanup($temp))->run();
        }
 
        private function activate($temp) {
@@ -213,7 +214,7 @@ class Installer implements Command
                
                $this->info("Running INI activation ...\n");
                $activate = new Task\Activate($temp, $files, $type, $this->args->prefix, $this->args{"common-name"}, $sudo);
-               if (!$activate->run($this->args->verbose)) {
+               if (!$activate->run($this->verbosity())) {
                        $this->info("Extension already activated ...\n");
                }
        }
index b3be6d09524ccdbb86e60311685689f0adf0a8ee..3cf4f5bfbd3bfa8e7afffd9782f5fd35411cf600 100644 (file)
@@ -5,6 +5,7 @@ namespace pharext;
 use Phar;
 use pharext\Cli\Args as CliArgs;
 use pharext\Cli\Command as CliCommand;
+use pharext\Exception;
 
 /**
  * The extension packaging command executed by bin/pharext
@@ -71,11 +72,7 @@ class Packager implements Command
         */
        function __destruct() {
                foreach ($this->cleanup as $cleanup) {
-                       if (is_dir($cleanup)) {
-                               $this->rm($cleanup);
-                       } elseif (file_exists($cleanup)) {
-                               unlink($cleanup);
-                       }
+                       $cleanup->run();
                }
        }
        
@@ -112,16 +109,7 @@ class Packager implements Command
                         * so e.g. name and version can be overriden and CliArgs 
                         * does not complain about missing arguments
                         */
-                       if ($this->args["source"]) {
-                               $source = $this->localize($this->args["source"]);
-                               if ($this->args["pecl"]) {
-                                       $this->source = new SourceDir\Pecl($this, $source);
-                               } elseif ($this->args["git"]) {
-                                       $this->source = new SourceDir\Git($this, $source);
-                               } else {
-                                       $this->source = new SourceDir\Pharext($this, $source);
-                               }
-                       }
+                       $this->loadSource();
                } catch (\Exception $e) {
                        $errs[] = $e->getMessage();
                }
@@ -159,17 +147,17 @@ class Packager implements Command
                        $task = new Task\GitClone($source);
                } else {
                        $task = new Task\StreamFetch($source, function($bytes_pct) {
-                               $this->debug(" %3d%% [%s>%s] \r",
+                               $this->info(" %3d%% [%s>%s] \r%s",
                                        floor($bytes_pct*100),
                                        str_repeat("=", round(50*$bytes_pct)),
-                                       str_repeat(" ", round(50*(1-$bytes_pct)))
+                                       str_repeat(" ", round(50*(1-$bytes_pct))),
+                                       $bytes_pct == 1 ? "\n":""
                                );
                        });
                }
-               $local = $task->run($this->args->verbose);
-               $this->debug("\n");
+               $local = $task->run($this->verbosity());
 
-               $this->cleanup[] = $local;
+               $this->cleanup[] = new Task\Cleanup($local);
                return $local;
        }
 
@@ -182,9 +170,9 @@ class Packager implements Command
                $this->debug("Extracting %s ...\n", $source);
                
                $task = new Task\Extract($source);
-               $dest = $task->run($this->args->verbose);
+               $dest = $task->run($this->verbosity());
                
-               $this->cleanup[] = $dest;
+               $this->cleanup[] = new Task\Cleanup($dest);
                return $dest;
        }
 
@@ -196,21 +184,47 @@ class Packager implements Command
        private function localize($source) {
                if (!stream_is_local($source)) {
                        $source = $this->download($source);
-                       $this->cleanup[] = $source;
+                       $this->cleanup[] = new Task\Cleanup($source);
                }
                $source = realpath($source);
                if (!is_dir($source)) {
                        $source = $this->extract($source);
-                       $this->cleanup[] = $source;
+                       $this->cleanup[] = new Task\Cleanup($source);
                        
                        if ($this->args->pecl) {
                                $this->debug("Sanitizing PECL dir ...\n");
-                               $source = (new Task\PeclFixup($source))->run($this->args->verbose);
+                               $source = (new Task\PeclFixup($source))->run($this->verbosity());
                        }
                }
                return $source;
        }
 
+       /**
+        * Load the source dir
+        * @throws \pharext\Exception
+        */
+       private function loadSource(){
+               if ($this->args["source"]) {
+                       $source = $this->localize($this->args["source"]);
+
+                       if ($this->args["pecl"]) {
+                               $this->source = new SourceDir\Pecl($source);
+                       } elseif ($this->args["git"]) {
+                               $this->source = new SourceDir\Git($source);
+                       } elseif (is_file("$source/parext_package.php")) {
+                               $this->source = include "$source/pharext_package.php";
+                       }
+
+                       if (!$this->source instanceof SourceDir) {
+                               throw new Exception("Unknown source dir $source");
+                       }
+
+                       foreach ($this->source->getPackageInfo() as $key => $val) {
+                               $this->args->$key = $val;
+                       }
+               }
+       }
+
        /**
         * Creates the extension phar
         */
@@ -233,9 +247,9 @@ class Packager implements Command
                try {
                        if ($this->args->sign) {
                                $this->info("Using private key to sign phar ...\n");
-                               $pass = (new Task\Askpass)->run($this->args->verbose);
+                               $pass = (new Task\Askpass)->run($this->verbosity());
                                $sign = new Task\PharSign($file, $this->args->sign, $pass);
-                               $pkey = $sign->run($this->args->verbose);
+                               $pkey = $sign->run($this->verbosity());
                        }
 
                } catch (\Exception $e) {
@@ -247,13 +261,13 @@ class Packager implements Command
                        try {
                                $gzip = (new Task\PharCompress($file, Phar::GZ))->run();
                                $move = new Task\PharRename($gzip, $this->args->dest, $this->args->name ."-". $this->args->release);
-                               $name = $move->run($this->args->verbose);
+                               $name = $move->run($this->verbosity());
 
                                $this->info("Created gzipped phar %s\n", $name);
 
                                if ($this->args->sign) {
                                        $sign = new Task\PharSign($name, $this->args->sign, $pass);
-                                       $sign->run($this->args->verbose)->exportPublicKey($name.".pubkey");
+                                       $sign->run($this->verbosity())->exportPublicKey($name.".pubkey");
                                }
 
                        } catch (\Exception $e) {
@@ -265,13 +279,13 @@ class Packager implements Command
                        try {
                                $bzip = (new Task\PharCompress($file, Phar::BZ2))->run();
                                $move = new Task\PharRename($bzip, $this->args->dest, $this->args->name ."-". $this->args->release);
-                               $name = $move->run($this->args->verbose);
+                               $name = $move->run($this->verbosity());
 
                                $this->info("Created bzipped phar %s\n", $name);
 
                                if ($this->args->sign) {
                                        $sign = new Task\PharSign($name, $this->args->sign, $pass);
-                                       $sign->run($this->args->verbose)->exportPublicKey($name.".pubkey");
+                                       $sign->run($this->verbosity())->exportPublicKey($name.".pubkey");
                                }
 
                        } catch (\Exception $e) {
@@ -281,7 +295,7 @@ class Packager implements Command
 
                try {
                        $move = new Task\PharRename($file, $this->args->dest, $this->args->name ."-". $this->args->release);
-                       $name = $move->run($this->args->verbose);
+                       $name = $move->run($this->verbosity());
 
                        $this->info("Created executable phar %s\n", $name);
 
index fbdd2f6edaeb6595b7502f6ede4b6b325355a76d..8620668d5215220ba199a069632966c112240b14 100644 (file)
@@ -3,24 +3,31 @@
 namespace pharext;
 
 /**
- * Source directory interface
+ * Source directory interface, which should yield file names to package on traversal
  */
 interface SourceDir extends \Traversable
 {
-       /**
-        * Read the source directory
-        * 
-        * Note: Best practices are for others, but if you want to follow them, do
-        * not put constructors in interfaces. Keep your complaints, I warned you.
-        * 
-        * @param Command $cmd
-        * @param string $path
-        */
-       public function __construct(Command $cmd, $path);
-       
        /**
         * Retrieve the base directory
         * @return string
         */
        public function getBaseDir();
+
+       /**
+        * Retrieve gathered package info
+        * @return array|Traversable
+        */
+       public function getPackageInfo();
+
+       /**
+        * Provide installer command line args
+        * @return array|Traversable
+        */
+       public function getArgs();
+
+       /**
+        * Process installer command line args
+        * @param \pharext\Cli\Args $args
+        */
+       public function setArgs(Cli\Args $args);
 }
index 8e35bf9b3f50fb292501f6c02652f090397685e8..e17a3055c15d1b5fd534e73f3bdbb3f529afbc67 100644 (file)
@@ -3,6 +3,7 @@
 namespace pharext\SourceDir;
 
 use pharext\Command;
+use pharext\Cli\Args;
 use pharext\SourceDir;
 
 /**
@@ -10,12 +11,6 @@ use pharext\SourceDir;
  */
 class Git implements \IteratorAggregate, SourceDir
 {
-       /**
-        * The Packager command
-        * @var pharext\Command
-        */
-       private $cmd;
-       
        /**
         * Base directory
         * @var string
@@ -26,8 +21,7 @@ class Git implements \IteratorAggregate, SourceDir
         * @inheritdoc
         * @see \pharext\SourceDir::__construct()
         */
-       public function __construct(Command $cmd, $path) {
-               $this->cmd = $cmd;
+       public function __construct($path) {
                $this->path = $path;
        }
 
@@ -38,7 +32,29 @@ class Git implements \IteratorAggregate, SourceDir
        public function getBaseDir() {
                return $this->path;
        }
-       
+
+       /**
+        * @inheritdoc
+        * @return array
+        */
+       public function getPackageInfo() {
+               return [];
+       }
+
+       /**
+        * @inheritdoc
+        * @return array
+        */
+       public function getArgs() {
+               return [];
+       }
+
+       /**
+        * @inheritdoc
+        */
+       public function setArgs(Args $args) {
+       }
+
        /**
         * Generate a list of files by `git ls-files`
         * @return Generator
@@ -50,13 +66,7 @@ class Git implements \IteratorAggregate, SourceDir
                        $path = realpath($this->path);
                        while (!feof($pipe)) {
                                if (strlen($file = trim(fgets($pipe)))) {
-                                       if ($this->cmd->getArgs()->verbose) {
-                                               $this->cmd->info("Packaging %s\n", $file);
-                                       }
                                        /* there may be symlinks, so no realpath here */
-                                       if (!file_exists("$path/$file")) {
-                                               $this->cmd->warn("File %s does not exist in %s\n", $file, $path);
-                                       }
                                        yield "$path/$file";
                                }
                        }
@@ -64,7 +74,7 @@ class Git implements \IteratorAggregate, SourceDir
                }
                chdir($pwd);
        }
-       
+
        /**
         * Implements IteratorAggregate
         * @see IteratorAggregate::getIterator()
index 9ca97264c480923817c1b5e1f808954888c8cf33..188ea67e98f57097f5e382c2a5734eab52bcf2fe 100644 (file)
@@ -2,21 +2,16 @@
 
 namespace pharext\SourceDir;
 
-use pharext\Command;
+use pharext\Cli\Args;
 use pharext\Exception;
 use pharext\SourceDir;
+use pharext\Tempfile;
 
 /**
  * A PECL extension source directory containing a v2 package.xml
  */
 class Pecl implements \IteratorAggregate, SourceDir
 {
-       /**
-        * The Packager command
-        * @var pharext\Packager
-        */
-       private $cmd;
-       
        /**
         * The package.xml
         * @var SimpleXmlElement
@@ -28,44 +23,28 @@ class Pecl implements \IteratorAggregate, SourceDir
         * @var string
         */
        private $path;
+
+       /**
+        * The package.xml
+        * @var string
+        */
+       private $file;
        
        /**
         * @inheritdoc
         * @see \pharext\SourceDir::__construct()
         */
-       public function __construct(Command $cmd, $path) {
-               if (realpath("$path/package2.xml")) {
-                       $sxe = simplexml_load_file("$path/package2.xml");
-               } elseif (realpath("$path/package.xml")) {
-                       $sxe = simplexml_load_file("$path/package.xml");
+       public function __construct($path) {
+               if (is_file("$path/package2.xml")) {
+                       $sxe = simplexml_load_file($this->file = "$path/package2.xml");
+               } elseif (is_file("$path/package.xml")) {
+                       $sxe = simplexml_load_file($this->file = "$path/package.xml");
                } else {
                        throw new Exception("Missing package.xml in $path");
                }
-               $sxe->registerXPathNamespace("pecl", $sxe->getDocNamespaces()[""]);
-               
-               $args = $cmd->getArgs();
-               if (!isset($args->name)) {
-                       $name = (string) $sxe->xpath("/pecl:package/pecl:name")[0];
-                       foreach ($args->parse(2, ["--name", $name]) as $error) {
-                               $cmd->warn("%s\n", $error);
-                       }
-               }
                
-               if (!isset($args->release)) {
-                       $release = (string) $sxe->xpath("/pecl:package/pecl:version/pecl:release")[0];
-                       foreach ($args->parse(2, ["--release", $release]) as $error) {
-                               $cmd->warn("%s\n", $error);
-                       }
-               }
-               if (!isset($args->zend)) {
-                       if ($sxe->xpath("/pecl:package/pecl:zendextsrcrelease")) {
-                               foreach ($args->parse(1, ["--zend"]) as $error) {
-                                       $cmd->warn("%s\n", $error);
-                               }
-                       }
-               }
+               $sxe->registerXPathNamespace("pecl", $sxe->getDocNamespaces()[""]);
                
-               $this->cmd = $cmd;
                $this->sxe = $sxe;
                $this->path = $path;
        }
@@ -77,6 +56,58 @@ class Pecl implements \IteratorAggregate, SourceDir
        public function getBaseDir() {
                return $this->path;
        }
+
+       /**
+        * Retrieve gathered package info
+        * @return Generator
+        */
+       public function getPackageInfo() {
+               if (($name = $this->sxe->xpath("/pecl:package/pecl:name"))) {
+                       yield "name" => (string) $name[0];
+               }
+               if (($release = $this->sxe->xpath("/pecl:package/pecl:version/pecl:release"))) {
+                       yield "release" => (string) $release[0];
+               }
+               if ($this->sxe->xpath("/pecl:package/pecl:zendextsrcrelease")) {
+                       yield "zend" => true;
+               }
+       }
+
+       /**
+        * @inheritdoc
+        * @see \pharext\SourceDir::getArgs()
+        */
+       public function getArgs() {
+               $configure = $this->sxe->xpath("/pecl:package/pecl:extsrcrelease/pecl:configureoption");
+               foreach ($configure as $cfg) {
+                       yield [null, $cfg["name"], ucfirst($cfg["prompt"]), Args::OPTARG,
+                               strlen($cfg["default"]) ? $cfg["default"] : null];
+               }
+               $configure = $this->sxe->xpath("/pecl:package/pecl:zendextsrcrelease/pecl:configureoption");
+               foreach ($configure as $cfg) {
+                       yield [null, $cfg["name"], ucfirst($cfg["prompt"]), Args::OPTARG,
+                               strlen($cfg["default"]) ? $cfg["default"] : null];
+               }
+       }
+
+       /**
+        * @inheritdoc
+        * @see \pharext\SourceDir::setArgs()
+        */
+       public function setArgs(Args $args) {
+               $configure = $this->sxe->xpath("/pecl:package/pecl:extsrcrelease/pecl:configureoption");
+               foreach ($configure as $cfg) {
+                       if (isset($args[$cfg["name"]])) {
+                               $args->configure = "--{$cfg["name"]}={$args[$cfg["name"]]}";
+                       }
+               }
+               $configure = $this->sxe->xpath("/pecl:package/pecl:zendextsrcrelease/pecl:configureoption");
+               foreach ($configure as $cfg) {
+                       if (isset($args[$cfg["name"]])) {
+                               $args->configure = "--{$cfg["name"]}={$args[$cfg["name"]]}";
+                       }
+               }
+       }
        
        /**
         * Compute the path of a file by parent dir nodes
@@ -92,20 +123,17 @@ class Pecl implements \IteratorAggregate, SourceDir
        }
 
        /**
-        * Render installer hook
-        * @param array $configure
-        * @return string
+        * Generate a list of files from the package.xml
+        * @return Generator
         */
-       private static function loadHook($configure, $dependencies) {
-               require_once "pharext/Version.php";
-               return include __DIR__."/../../pharext_install.tpl.php";
-       }
+       private function generateFiles() {
+               /* hook  */
+               $temp = tmpfile();
+               fprintf($temp, "<?php\nreturn new %s(__DIR__);\n", get_class($this));
+               rewind($temp);
+               yield "pharext_package.php" => $temp;
 
-       /**
-        * Create installer hook
-        * @return \Generator
-        */
-       private function generateHooks() {
+               /* deps */
                $dependencies = $this->sxe->xpath("/pecl:package/pecl:dependencies/pecl:required/pecl:package");
                foreach ($dependencies as $key => $dep) {
                        if (($glob = glob("{$this->path}/{$dep->name}-*.ext.phar*"))) {
@@ -116,44 +144,13 @@ class Pecl implements \IteratorAggregate, SourceDir
                                        );
                                });
                                yield end($glob);
-                       } else {
-                               unset($dependencies[$key]);
-                       }
-               }
-               $configure = $this->sxe->xpath("/pecl:package/pecl:extsrcrelease/pecl:configureoption");
-               if ($configure) {
-                       $fd = tmpfile();
-                       ob_start(function($s) use($fd){
-                               fwrite($fd, $s);
-                               return null;
-                       });
-                       self::loadHook($configure, $dependencies);
-                       ob_end_flush();
-                       rewind($fd);
-                       yield "pharext_install.php" => $fd;
-               }
-       }
-       
-       /**
-        * Generate a list of files from the package.xml
-        * @return Generator
-        */
-       private function generateFiles() {
-               foreach ($this->generateHooks() as $file => $hook) {
-                       if ($this->cmd->getArgs()->verbose) {
-                               $this->cmd->info("Packaging %s\n", is_scalar($hook) ? $hook : $file);
                        }
-                       yield $file => $hook;
                }
+
+               /* files */
+               yield $this->file;
                foreach ($this->sxe->xpath("//pecl:file") as $file) {
-                       $path = $this->path ."/". $this->dirOf($file) ."/". $file["name"];
-                       if ($this->cmd->getArgs()->verbose) {
-                               $this->cmd->info("Packaging %s\n", substr($path, strlen($this->path)));
-                       }
-                       if (!($realpath = realpath($path))) {
-                               $this->cmd->warn("File %s does not exist", $path);
-                       }
-                       yield $realpath;
+                       yield realpath($this->path ."/". $this->dirOf($file) ."/". $file["name"]);
                }
        }
        
diff --git a/src/pharext/SourceDir/Pharext.php b/src/pharext/SourceDir/Pharext.php
deleted file mode 100644 (file)
index 8ca0901..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-
-namespace pharext\SourceDir;
-
-use pharext\Command;
-use pharext\Exception;
-use pharext\SourceDir;
-
-/**
- * A source directory containing pharext_package.php and eventually pharext_install.php
- */
-class Pharext implements \IteratorAggregate, SourceDir
-{
-       /**
-        * @var pharext\Command
-        */
-       private $cmd;
-       
-       /**
-        * @var string
-        */
-       private $path;
-       
-       /**
-        * @var callable
-        */
-       private $iter;
-
-       /**
-        * @inheritdoc
-        * @see \pharext\SourceDir::__construct()
-        */
-               public function __construct(Command $cmd, $path) {
-               $this->cmd = $cmd;
-               $this->path = $path;
-               
-               $callable = include "$path/pharext_package.php";
-               if (!is_callable($callable)) {
-                       throw new Exception("Package hook did not return a callable");
-               }
-               $this->iter = $callable($cmd, $path);
-       }
-
-       /**
-        * @inheritdoc
-        * @see \pharext\SourceDir::getBaseDir()
-        */
-       public function getBaseDir() {
-               return $this->path;
-       }
-
-       /**
-        * Implements IteratorAggregate
-        * @see IteratorAggregate::getIterator()
-        */
-       public function getIterator() {
-               if (!is_callable($this->iter)) {
-                       return new Git($this->cmd, $this->path);
-               } 
-               return call_user_func($this->iter, $this->cmd, $this->path);
-       } 
-}
diff --git a/src/pharext/Task/Cleanup.php b/src/pharext/Task/Cleanup.php
new file mode 100644 (file)
index 0000000..1263bd9
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+
+namespace pharext\Task;
+
+use pharext\Task;
+
+use FilesystemIterator;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+
+/**
+ * Recursively cleanup FS entries
+ */
+class Cleanup implements Task
+{
+       /**
+        * @var string
+        */
+       private $rm;
+
+       public function __construct($rm) {
+               $this->rm = $rm;
+       }
+
+       /**
+        * @param bool $verbose
+        */
+       public function run($verbose = false) {
+               if (is_dir($this->rm)) {
+                       $rdi = new RecursiveDirectoryIterator($this->rm,
+                               FilesystemIterator::CURRENT_AS_PATHNAME |
+                               FilesystemIterator::SKIP_DOTS);
+                       $rii = new RecursiveIteratorIterator($rdi,
+                               RecursiveIteratorIterator::CHILD_FIRST);
+                       foreach ($rii as $path) {
+                               if ($rii->isDir()) {
+                                       rmdir($path);
+                               } else {
+                                       unlink($path);
+                               }
+                       }
+                       rmdir($this->rm);
+               } else {
+                       @unlink($this->rm);
+               }
+       }
+}
diff --git a/src/pharext_install.tpl.php b/src/pharext_install.tpl.php
deleted file mode 100644 (file)
index f7a356d..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-<?='<?php'?>
-
-/**
- * Generated by pharext v<?=pharext\VERSION?> at <?=date("Y-m-d H:i:s T")?>.
- */
-namespace pharext;
-
-use pharext\Cli\Args as CliArgs;
-
-return function(Installer $installer) {
-       $args = $installer->getArgs();
-       <?php foreach ($configure as $cfg) : ?>
-       
-       $args->compile([[
-               null, 
-               "<?=$cfg["name"]?>", 
-               "<?=ucfirst($cfg["prompt"])?>", 
-               CliArgs::OPTARG,
-               <?php if (strlen($cfg["default"])) : ?>
-               "<?=$cfg["default"]?>"
-               <?php else : ?>
-               NULL
-               <?php endif; ?>
-               
-       ]]);
-       <?php endforeach; ?>
-       
-       return function(Installer $installer) {
-               $args = $installer->getArgs();
-               <?php foreach ($configure as $cfg) : ?>
-               
-               if (isset($args["<?=$cfg["name"]?>"])) {
-                       $args->configure = "--<?=$cfg["name"]?>=".$args["<?=$cfg["name"]?>"];
-               }
-               <?php endforeach; ?>
-               
-       };
-};
index 5e6a989bbc8a9e82034327d522e989f332f41f2e..ac04d4d9292df5c70327f31456399ad52ea0e2fc 100644 (file)
@@ -25,7 +25,7 @@ class GitSourceDirTest extends \PHPUnit_Framework_TestCase
        protected $source;
        
        protected function setUp() {
-               $this->source = new SourceDir\Git(new Cmd, ".");
+               $this->source = new SourceDir\Git(".");
        }
        
        public function testGetBaseDir() {
diff --git a/tests/src/pharext/TaskTest.php b/tests/src/pharext/TaskTest.php
new file mode 100644 (file)
index 0000000..cb11377
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+
+namespace pharext;
+
+require_once __DIR__."/../../autoload.php";
+
+use pharext\Task;
+
+class TaskTest extends \PHPUnit_Framework_TestCase
+{
+       function testGitClone() {
+               $cmd = new Task\GitClone("https://git.php.net/repository/pecl/http/apfd.git");
+               $dir = $cmd->run();
+
+               $this->assertTrue(is_dir("$dir/.git"), "is_dir($dir/.git)");
+
+               (new Task\Cleanup($dir))->run();
+               $this->assertFalse(is_dir($dir), "is_dir($dir)");
+       }
+
+       function testPecl() {
+               $cmd = new Task\StreamFetch("http://pecl.php.net/get/pecl_http", function($pct) use(&$log) {
+                       $log[] = $pct;
+               });
+               $tmp = $cmd->run();
+
+               $this->assertTrue(is_file($tmp), "is_file($tmp)");
+               $this->assertGreaterThan(1, count($log), "1 < count(\$log)");
+               $this->assertContains(0, $log, "in_array(0, \$log)");
+               $this->assertContains(1, $log, "in_array(1, \$log)");
+
+               $cmd = new Task\Extract($tmp);
+               $dir = $cmd->run();
+
+               $this->assertTrue(is_dir($dir), "is_dir($dir)");
+               $this->assertTrue(is_file("$dir/package.xml"), "is_file($dir/package.xml");
+
+               $cmd = new Task\PeclFixup($dir);
+               $new = $cmd->run();
+
+               $this->assertTrue(is_dir($new), "is_dir($new)");
+               $this->assertFalse(is_file("$dir/package.xml"), "is_file($dir/package.xml");
+               $this->assertTrue(is_file("$new/package.xml"), "is_file($new/package.xml");
+
+               (new Task\Cleanup($dir))->run();
+               $this->assertFalse(is_dir($dir), "is_dir($dir)");
+               $this->assertFalse(is_dir($new), "is_dir($new)");
+       }
+
+       function testPackage() {
+               $tmp = (new Task\StreamFetch("http://pecl.php.net/get/json_post/1.0.0", function(){}))->run();
+               $dir = (new Task\Extract($tmp))->run();
+               $new = (new Task\PeclFixup($dir))->run();
+               $src = new SourceDir\Pecl($new);
+               $inf = [
+                       "date" => date("Y-m-d"),
+                       "name" => "json_post",
+                       "release" => "1.0.0",
+                       "license" => file_get_contents($src->getBaseDir()."/LICENSE"),
+                       "stub" => "pharext_installer.php",
+                       "type" => "extension",
+               ];
+               $pkg = (new Task\PharBuild($src, $inf))->run();
+               $gzp = (new Task\PharCompress($pkg, \Phar::GZ))->run();
+               $pkg = (new Task\PharRename($pkg, ".", "json_post-1.0.0"))->run();
+               $gzp = (new Task\PharRename($gzp, ".", "json_post-1.0.0"))->run();
+
+               $this->assertTrue(is_file($pkg), "is_file($pkg)");
+               $this->assertTrue(is_file($gzp), "is_file($gzp)");
+       }
+}