OSDN Git Service

Remove PHP closing tag
[ethna/ethna.git] / class / Ethna_PearWrapper.php
1 <?php
2 // vim: foldmethod=marker
3 /**
4  *  Ethna_PearWrapper.php
5  *
6  *  @author     ICHII Takashi <ichii386@schweetheart.jp>
7  *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
8  *  @package    Ethna
9  *  @version    $Id$
10  */
11
12 require_once 'PEAR.php';
13 require_once 'PEAR/Config.php';
14 require_once 'PEAR/Command.php';
15 require_once 'PEAR/PackageFile.php';
16
17 // {{{ Ethna_PearWrapper
18 /**
19  *  wrapper class for PEAR_Command
20  *  This class should be instantiated in ethna handler.
21  *
22  *  @author     ICHII Takashi <ichii386@schweetheart.jp>
23  *  @access     public
24  *  @package    Ethna
25  */
26 class Ethna_PearWrapper
27 {
28     // {{{ properties
29     /**#@+
30      *  @access     private
31      */
32
33     /** @var    string  channel url of ethna repositry */
34     var $channel;
35
36     /** @var    string  target, 'master' or 'local' */
37     var $target;
38
39     /** @var    object  controller object collesponding to the target */
40     var $target_ctl;
41
42     /** @var    object  PEAR_Config     PEAR_Config object */
43     var $config;
44
45     /** @var    object  PEAR_Registry   PEAR_Registry object */
46     var $registry;
47
48     /** @var    object  PEAR_Frontend   PEAR_Frontend(_CLI) object */
49     var $ui;
50
51     /** @var    array   options for pearcmd */
52     var $_pearopt;
53
54     /**#@-*/
55     // }}}
56
57     // {{{ constructor, initializer
58     /**
59      *  Ethna_PearWrapper constructor
60      *
61      *  @access public
62      */
63     function Ethna_PearWrapper()
64     {
65         $this->channel = null;
66         $this->config = null;
67         $this->registry = null;
68         $this->ui = null;
69         $this->target = null;
70         $this->target_ctl = null;
71     }
72
73     /**
74      *  setup PEAR_Config and so on.
75      *
76      *  @param  string      $target     whether 'master' or 'local'
77      *  @param  string|null $app_dir    local application directory.
78      *  @param  string|null $channel    channel for the package repository.
79      *  @return true|Ethna_Error
80      */
81     function &init($target, $app_dir = null, $channel = null)
82     {
83         $true = true;
84         if ($target == 'master') {
85             $this->target = 'master';
86         } else {
87             // default target is 'local'.
88             $this->target = 'local';
89         }
90
91         // setup PEAR_Frontend
92         PEAR_Command::setFrontendType('CLI');
93         $this->ui =& PEAR_Command::getFrontendObject();
94
95         // set PEAR's error handling
96         // TODO: if PEAR/Command/Install.php is newer than 1.117, displayError goes well.
97         PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array(&$this->ui, 'displayFatalError'));
98
99         // set channel
100         $master_setting = Ethna_Handle::getMasterSetting('repositry');
101         if ($channel !== null) {
102             $this->channel = $channel;
103         } else if (isset($master_setting["channel_{$target}"])) {
104             $this->channel = $master_setting["channel_{$target}"];
105         } else {
106             $this->channel = 'pear.ethna.jp';
107         }
108
109         // set target controller
110         if ($target == 'master') {
111             $this->target_ctl =& Ethna_Handle::getEthnaController();
112         } else {
113             $this->target_ctl =& Ethna_Handle::getAppController($app_dir);
114         }
115         if (Ethna::isError($this->target_ctl)) {
116             return $this->target_ctl;
117         }
118
119         // setup PEAR_Config
120         if ($target == 'master') {
121             $ret =& $this->_setMasterConfig();
122         } else {
123             $ret =& $this->_setLocalConfig();
124         }
125         if (Ethna::isError($ret)) {
126             return $ret;
127         }
128         $this->ui->setConfig($this->config);
129
130         // setup PEAR_Registry
131         $this->registry =& $this->config->getRegistry();
132
133         return $true;
134     }
135
136     /**
137      *  config for master.
138      *
139      *  @return true|Ethna_Error
140      *  @access private 
141      */
142     function &_setMasterConfig()
143     {
144         $true = true;
145
146         // setup config
147         $this->config =& PEAR_Config::singleton();
148
149         // setup channel
150         $reg =& $this->config->getRegistry();
151         if ($reg->channelExists($this->channel) == false) {
152             $ret =& $this->doChannelDiscover();
153             if (Ethna::isError($ret)) {
154                 return $ret;
155             }
156         }
157
158         return $true;
159     }
160
161     /**
162      *  config for local.
163      *
164      *  @return true|Ethna_Error
165      *  @access private 
166      */
167     function &_setLocalConfig()
168     {
169         $true = true;
170
171         // determine dirs
172         $base = $this->target_ctl->getBaseDir();
173         $bin  = $this->target_ctl->getDirectory('bin');
174         $tmp  = $this->target_ctl->getDirectory('tmp');
175         $dirs = array(
176                 'php_dir'       => "{$base}/skel",
177                 'bin_dir'       => "{$bin}",
178                 'cache_dir'     => "{$tmp}/.pear/cache",
179                 'download_dir'  => "{$tmp}/.pear/download",
180                 'temp_dir'      => "{$tmp}/.pear/temp",
181                 'doc_dir'       => "{$tmp}/.pear/doc",
182                 'ext_dir'       => "{$tmp}/.pear/ext",
183                 'data_dir'      => "{$tmp}/.pear/data",
184                 'test_dir'      => "{$tmp}/.pear/test",
185                 );
186
187         // mkdir
188         foreach ($dirs as $key => $dir) {
189             if (is_dir($dir) == false) {
190                 Ethna_Util::mkdir($dir, 0755);
191             }
192         }
193
194         $pearrc = "{$base}/skel/.pearrc";
195         $this->config =& PEAR_Config::singleton($pearrc);
196
197         // read local .pearrc if exists.
198         if (is_file($pearrc) && is_readable($pearrc)) {
199             $this->config->readConfigFile($pearrc);
200         }
201
202         // set dirs to config
203         foreach ($dirs as $key => $dir) {
204             $this->config->set($key, $dir);
205         }
206
207         // setup channel
208         $reg =& $this->config->getRegistry();
209         if ($reg->channelExists($this->channel) == false) {
210             $ret =& $this->doChannelDiscover();
211             if (Ethna::isError($ret)) {
212                 return $ret;
213             }
214         }
215         $this->config->set('default_channel', $this->channel);
216
217         // write local .pearrc
218         $this->config->writeConfigFile();
219
220         return $true;
221     }
222     // }}}
223
224     // {{{ doClearCache
225     /**
226      *  do clear-cache
227      *
228      *  @return true|Ethna_Error
229      */
230     function &doClearCache()
231     {
232         $true = true;
233         $r =& $this->_run('clear-cache', array(), array());
234         if (PEAR::isError($r)) {
235             return $r;
236         }
237         return $true;
238     }
239     // }}}
240
241     // {{{ doChannelDiscover
242     /**
243      *  do channel-discover
244      *
245      *  @return true|Ethna_Error
246      */
247     function &doChannelDiscover()
248     {
249         $true = true;
250         $r =& $this->_run('channel-discover', array(), array($this->channel));
251         if (PEAR::isError($r)) {
252             return $r;
253         }
254         return $true;
255     }
256     // }}}
257
258     // {{{ isChannelExists
259     /**
260      *  whether channel discovered or not
261      *
262      *  @return bool
263      */
264     function isChannelExists()
265     {
266         return $this->registry->channelExists($this->channel);
267     }
268     // }}}
269
270     // {{{ doChannelUpdate
271     /**
272      *  do channel-update
273      *
274      *  @return true|Ethna_Error
275      */
276     function &doChannelUpdate()
277     {
278         $true = true;
279         if ($this->isChannelExists() == false) {
280             $r =& $this->doChannelDiscover();
281             if (PEAR::isError($r)) {
282                 return $r;
283             }
284         }
285         $r =& $this->_run('channel-update', array(), array($this->channel));
286         if (PEAR::isError($r)) {
287             return $r;
288         }
289         return $true;
290     }
291     // }}}
292
293     // {{{ _doInstallOrUpgrade
294     /**
295      *  do install
296      *
297      *  @param  string  $command    'install' or 'upgrade'
298      *  @param  string  $package    package string
299      *  @return true|Ethna_Error
300      *  @access private 
301      */
302     function &_doInstallOrUpgrade($command, $package)
303     {
304         $true = true;
305         $r =& $this->_run($command, array(), array($package));
306         if (PEAR::isError($r)) {
307             return $r;
308         }
309         return $true;
310     }
311     // }}}
312         
313     // {{{ doInstall
314     /**
315      *  do install
316      *
317      *  @param  string  $pkg_name   package name.
318      *  @param  string  $state      package state.
319      *  @return true|Ethna_Error
320      */
321     function &doInstall($pkg_name, $state = null)
322     {
323         $pkg = "{$this->channel}/{$pkg_name}";
324         if ($state !== null) {
325             $pkg = "{$pkg}-{$state}";
326         }
327         $r =& $this->_doInstallOrUpgrade('install', $pkg); 
328         return $r;
329     }
330     // }}}
331
332     // {{{ doInstallFromTgz
333     /**
334      *  do install from local tgz file
335      *
336      *  @param  string  $pkg_file   package filename
337      *  @return true|Ethna_Error
338      */
339     function &doInstallFromTgz($pkg_file)
340     {
341         $r =& $this->_doInstallOrUpgrade('install', $pkg_file); 
342         return $r;
343     }
344     // }}}
345
346     // {{{ doUpgrade
347     /**
348      *  do upgrade
349      *
350      *  @param  string  $pkg_name   package name.
351      *  @param  string  $state      package state.
352      *  @return true|Ethna_Error
353      */
354     function &doUpgrade($pkg_name, $state = null)
355     {
356         $pkg = "{$this->channel}/{$pkg_name}";
357         if ($state !== null) {
358             $pkg = "{$pkg}-{$state}";
359         }
360         $r =& $this->_doInstallOrUpgrade('upgrade', $pkg);
361         return $r;
362     }
363     // }}}
364
365     // {{{ doUpgradeFromTgz
366     /**
367      *  do upgrade from local tgz file
368      *
369      *  @param  string  $pkg_file   package filename
370      *  @return true|Ethna_Error
371      */
372     function &doUpgradeFromTgz($pkg_file)
373     {
374         $r =& $this->_doInstallOrUpgrade('upgrade', $pkg_file); 
375         return $r;
376     }
377     // }}}
378
379     // {{{ isInstalled
380     /**
381      *  check package installed
382      *
383      *  @param  string  $package package name
384      *  @return bool
385      */
386     function isInstalled($package)
387     {
388         return $this->registry->packageExists($package, $this->channel);
389     }
390     // }}}
391
392     // {{{ getVersion
393     /**
394      *  get package version
395      *
396      *  @param  string  $package package name
397      *  @return string  version string
398      */
399     function getVersion($package)
400     {
401         $pobj =& $this->registry->getPackage($package, $this->channel);
402         return $pobj->getVersion();
403     }
404     // }}}
405
406     // {{{ getState
407     /**
408      *  get package version
409      *
410      *  @param  string  $package package name
411      *  @return string  version string
412      */
413     function getState($package)
414     {
415         $pobj =& $this->registry->getPackage($package, $this->channel);
416         return $pobj->getState();
417     }
418     // }}}
419
420     // {{{ doUninstall
421     /**
422      *  do uninstall (packages installed with ethna command)
423      *
424      *  @return true|Ethna_Error
425      */
426     function &doUninstall($package)
427     {
428         $true = true;
429         if ($this->isInstalled($package) == false) {
430             return Ethna::raiseNotice("{$this->channel}/{$package} is not installed.");
431         }
432         $r =& $this->_run('uninstall', array(), array("{$this->channel}/{$package}"));
433         if (PEAR::isError($r)) {
434             return $r;
435         }
436         if ($this->isInstalled($package)) {
437             return Ethna::raiseNotice("uninstall failed: {$this->channel}/{$package}");
438         }
439         return $true;
440     }
441     // }}}
442
443     // {{{ getPackageNameFromTgz
444     /**
445      *  get package info from tar/tgz file.
446      *
447      *  @param  string  $filename   package file name.
448      *  @return string  package name
449      *  @access public
450      *  @static
451      */
452     function &getPackageNameFromTgz($filename)
453     {
454         $config =& PEAR_Config::singleton();
455         $packagefile =& new PEAR_PackageFile($config);
456         $info =& $packagefile->fromTgzFile($filename, PEAR_VALIDATE_NORMAL);
457         if (PEAR::isError($info)) {
458             return $info;
459         }
460         $info_array = $info->toArray();
461         return $info_array['name'];
462     }
463     // }}}
464
465     // {{{ getCanonicalPackageName
466     /**
467      *  get canonical package name (case sensitive)
468      *
469      *  @param  string  $package    package name.
470      *  @return string  canonical name
471      *  @access public
472      */
473     function &getCanonicalPackageName($package)
474     {
475         if ($this->isInstalled($package) == false) {
476             return Ethna::raiseNotice("{$this->channel}/{$package} is not installed.");
477         }
478         $pobj =& $this->registry->getPackage($package, $this->channel);
479         $cname = $pobj->getName();
480         return $cname;
481     }
482     // }}}
483
484     // {{{ getInstalledPackageList
485     /**
486      *  get installed package list
487      *
488      *  @return array   installed package list
489      *  @access public
490      */
491     function &getInstalledPackageList()
492     {
493         $ret = array();
494         foreach ($this->registry->listPackages($this->channel) as $pkg) {
495             $ret[] = $this->getCanonicalPackageName($pkg);
496         }
497         return $ret;
498     }
499     // }}}
500
501     // {{{ doInfo
502     /**
503      *  do info (packages installed with ethna command)
504      *
505      *  @param  string  $package    package name.
506      *  @return true|Ethna_Error
507      */
508     function &doInfo($package)
509     {
510         return $this->_run('info', array(), array("{$this->channel}/{$package}"));
511     }
512     // }}}
513
514     // {{{ doRemoteInfo
515     /**
516      *  do info (packages installable with ethna command)
517      *
518      *  @param  string  $package    package name.
519      *  @return true|Ethna_Error
520      */
521     function &doRemoteInfo($package)
522     {
523         return $this->_run('remote-info', array(), array("{$this->channel}/{$package}"));
524     }
525     // }}}
526
527     // {{{ doUpgradeAll
528     /**
529      *  do upgrade-all
530      *
531      *  @return true|Ethna_Error
532      */
533     function &doUpgradeAll()
534     {
535         return $this->_run('upgrade-all', array('channel' => "{$this->channel}"), array());
536     }
537     // }}}
538
539     // {{{ doList
540     /**
541      *  do list (packages installed with ethna command)
542      *
543      *  @return true|Ethna_Error
544      */
545     function &doList()
546     {
547         return $this->_run('list', array('channel' => $this->channel), array());
548     }
549     // }}}
550
551     // {{{ doRemoteList
552     /**
553      *  do remote-list (packages installable with ethna command)
554      *
555      *  @return true|Ethna_Error
556      */
557     function &doRemoteList()
558     {
559         return $this->_run('remote-list', array('channel' => $this->channel), array());
560     }
561     // }}}
562
563     // {{{ subroutines.
564     /**
565      *  run PEAR_Command.
566      *
567      *  @param  string  $command    command name
568      *  @param  array   $options    options
569      *  @param  array   $params     parameters
570      *  @return true|Ethna_Error
571      *  @access private 
572      *  @see PEAR_Command_Common::run, etc.
573      */
574     function &_run($command, $options, $params)
575     {
576         if ($this->config === null) {
577             return Ethna::raiseError('configuration not initialized.');
578         }
579
580         $true = true;
581
582         $cmd =& PEAR_Command::factory($command, $this->config);
583         if (PEAR::isError($cmd)) {
584             return $cmd;
585         }
586
587         // pear command options
588         if (is_array($this->_pearopt) && count($this->_pearopt) > 0) {
589             $pearopts = $this->_getPearOpt($cmd, $command, $this->_pearopt);
590             $options = array_merge($pearopts, $options);
591         }
592
593         $ret = $cmd->run($command, $options, $params);
594         if (PEAR::isError($ret)) {
595             return $ret;
596         }
597
598         return $true;
599     }
600
601     /**
602      *  provide yes-or-no dialog.
603      *
604      *  @return bool
605      *  @access public
606      */
607     function confirmDialog($message, $default = 'yes')
608     {
609         $ret = $this->ui->userConfirm($message);
610         return $ret;
611     }
612
613     /**
614      *  provide table layout
615      *
616      *  @param  array   $headline   headline
617      *  @param  array   $rows       rows which have the same size as headline's.
618      *  @access public
619      */
620     function displayTable($caption, $headline, $rows)
621     {
622         // spacing
623         foreach (array_keys($headline) as $k) {
624             $headline[$k] = sprintf('% -8s', $headline[$k]);
625         }
626
627         $data = array('caption'  => $caption,
628                       'border'   => true,
629                       'headline' => $headline,
630                       'data'     => $rows);
631         $this->ui->outputData($data);
632     }
633
634     /**
635      *  (experimental)
636      *  @access public
637      */
638     function setPearOpt($pearopt)
639     {
640         $this->_pearopt = $pearopt;
641     }
642
643     /**
644      *  (experimental)
645      *  @return array
646      */
647     function _getPearOpt(&$cmd_obj, $cmd_str, $opt_array)
648     {
649         $short_args = $long_args = null;
650         PEAR_Command::getGetOptArgs($cmd_str, $short_args, $long_args);
651         $opt = new Ethna_Getopt();
652         $opt_arg = $opt->getopt($opt_array, $short_args, $long_args);
653         if (Ethna::isError($opt_arg)) return array();
654         $opts = array();
655         foreach ($opt_arg[0] as $tmp) {
656             list($opt, $val) = $tmp;
657             if ($val === null) $val = true;
658             if (strlen($opt) == 1) {
659                 $cmd_opts = $cmd_obj->getOptions($cmd_str);
660                 foreach ($cmd_opts as $o => $d) {
661                     if (isset($d['shortopt']) && $d['shortopt'] == $opt) {
662                         $opts[$o] = $val;
663                     }
664                 }
665             } else {
666                 if (substr($opt, 0, 2) == '--') $opts[substr($opt, 2)] = $val;
667             }
668         }
669         return $opts;
670     }
671                 
672
673     // }}}
674 }
675 // }}}
676