9 * This source file is subject to the new BSD license that is bundled
10 * with this package in the file LICENSE.
11 * It is also available through the world-wide-web at this URL:
12 * http://phergie.org/license
15 * @package Phergie_Plugin_Help
16 * @author Phergie Development Team <team@phergie.org>
17 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
18 * @license http://phergie.org/license New BSD License
19 * @link http://pear.phergie.org/package/Phergie_Plugin_Help
23 * Provides access to descriptions of plugins and the commands they provide.
26 * @package Phergie_Plugin_Help
27 * @author Phergie Development Team <team@phergie.org>
28 * @license http://phergie.org/license New BSD License
29 * @link http://pear.phergie.org/package/Phergie_Plugin_Help
30 * @uses Phergie_Plugin_Command pear.phergie.org
32 class Phergie_Plugin_Help extends Phergie_Plugin_Abstract
35 * Registry of help data indexed by plugin name
42 * Checks for dependencies.
46 public function onLoad()
48 $this->getPluginHandler()->getPlugin('Command');
52 * Creates a registry of plugin metadata on connect.
56 public function onConnect()
58 $this->populateRegistry();
62 * Creates a registry of plugin metadata.
66 public function populateRegistry()
68 $this->registry = array();
70 foreach ($this->plugins as $plugin) {
71 $class = new ReflectionClass($plugin);
72 $pluginName = strtolower($plugin->getName());
74 // Parse the plugin description
75 $docblock = $class->getDocComment();
76 $annotations = $this->getAnnotations($docblock);
77 if (isset($annotations['pluginDesc'])) {
78 $pluginDesc = implode(' ', $annotations['pluginDesc']);
80 $pluginDesc = $this->parseShortDescription($docblock);
82 $this->registry[$pluginName] = array(
83 'desc' => $pluginDesc,
87 // Parse command method descriptions
88 $methodPrefix = Phergie_Plugin_Command::METHOD_PREFIX;
89 $methodPrefixLength = strlen($methodPrefix);
90 foreach ($class->getMethods() as $method) {
91 if (strpos($method->getName(), $methodPrefix) !== 0) {
95 $cmd = strtolower(substr($method->getName(), $methodPrefixLength));
96 $docblock = $method->getDocComment();
97 $annotations = $this->getAnnotations($docblock);
99 if (isset($annotations['pluginCmd'])) {
100 $cmdDesc = implode(' ', $annotations['pluginCmd']);
102 $cmdDesc = $this->parseShortDescription($docblock);
105 $cmdParams = array();
106 if (!empty($annotations['param'])) {
107 foreach ($annotations['param'] as $param) {
109 if (preg_match('/\h+\$([^\h]+)\h+/', $param, $match)) {
110 $cmdParams[] = $match[1];
115 $this->registry[$pluginName]['cmds'][$cmd] = array(
117 'params' => $cmdParams
121 if (empty($this->registry[$pluginName]['cmds'])) {
122 unset($this->registry[$pluginName]);
128 * Displays a list of plugins with help information available or
129 * commands available for a specific plugin.
131 * @param string $query Optional short name of a plugin for which commands
132 * should be returned or a command; if unspecified, a list of
133 * plugins with help information available is returned
137 public function onCommandHelp($query = null)
139 if ($query == 'refresh') {
140 $this->populateRegistry();
143 $nick = $this->getEvent()->getNick();
144 $delay = $this->getConfig('help.delay', 2);
146 // Handle requests for a plugin list
148 $msg = 'These plugins have help information available: '
149 . implode(', ', array_keys($this->registry));
150 $this->doPrivmsg($nick, $msg);
154 // Handle requests for plugin information
155 $query = strtolower($query);
156 if (isset($this->registry[$query])
157 && empty($this->registry[$query]['cmds'][$query])) {
158 $msg = $query . ' - ' . $this->registry[$query]['desc'];
159 $this->doPrivmsg($nick, $msg);
161 $msg = 'Available commands - '
162 . implode(', ', array_keys($this->registry[$query]['cmds']));
163 $this->doPrivmsg($nick, $msg);
165 if ($this->getConfig('command.prefix')) {
167 = 'Note that these commands must be prefixed with "'
168 . $this->getConfig('command.prefix')
169 . '" (without quotes) when issued in a public channel.';
170 $this->doPrivmsg($nick, $msg);
176 // Handle requests for command information
177 foreach ($this->registry as $plugin => $data) {
178 if (empty($data['cmds'])) {
182 $result = preg_grep('/^' . $query . '$/i', array_keys($data['cmds']));
187 $cmd = $data['cmds'][array_shift($result)];
189 if (!empty($cmd['params'])) {
190 $msg .= ' [' . implode('] [', $cmd['params']) . ']';
192 $msg .= ' - ' . $cmd['desc'];
193 $this->doPrivmsg($nick, $msg);
198 * Parses and returns the short description from a docblock.
200 * @param string $docblock Docblock comment code
202 * @return string Short description (i.e. content from the start of the
203 * docblock up to the first double-newline)
205 protected function parseShortDescription($docblock)
207 $desc = preg_replace(
208 array('#^\h*\*\h*#m', '#^/\*\*\h*\v+\h*#', '#(?:\r?\n){2,}.*#s', '#\s*\v+\s*#'),
209 array('', '', '', ' '),
216 * Taken from PHPUnit/Util/Test.php and modified to fix an issue with
217 * tag content spanning multiple lines.
221 * Copyright (c) 2002-2010, Sebastian Bergmann <sb@sebastian-bergmann.de>.
222 * All rights reserved.
224 * Redistribution and use in source and binary forms, with or without
225 * modification, are permitted provided that the following conditions
228 * * Redistributions of source code must retain the above copyright
229 * notice, this list of conditions and the following disclaimer.
231 * * Redistributions in binary form must reproduce the above copyright
232 * notice, this list of conditions and the following disclaimer in
233 * the documentation and/or other materials provided with the
236 * * Neither the name of Sebastian Bergmann nor the names of his
237 * contributors may be used to endorse or promote products derived
238 * from this software without specific prior written permission.
240 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
241 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
242 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
243 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
244 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
245 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
246 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
247 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
248 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
249 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
250 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
251 * POSSIBILITY OF SUCH DAMAGE.
253 * @param string $docblock docblock to parse
257 protected function getAnnotations($docblock)
259 $annotations = array();
261 $regex = '/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?(?:\*\/|\* @)/ms';
263 if (preg_match_all($regex, $docblock, $matches)) {
264 $numMatches = count($matches[0]);
266 for ($i = 0; $i < $numMatches; ++$i) {
267 $annotation = $matches['value'][$i];
268 $annotation = preg_replace('/\s*\v+\s*\*\s*/', ' ', $annotation);
269 $annotation = rtrim($annotation);
270 $annotations[$matches['name'][$i]][] = $annotation;