3 # plugin_pack.py - Helper script for obtaining info about the plugin pack
4 # Copyright (C) 2008 Gary Kramlich <grim@reaperworld.com>
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
20 """Usage: plugin_pack.py [OPTION...] command
24 -a Load abusive plugins
25 -d Load default plugins
26 -i Load incomplate plugins
29 -p Load purple plugins
30 -P Load pidgin plugins
42 webpage = 'http://plugins.guifications.org/'
45 print >> sys.stderr, msg
59 def __init__(self, directory, name, parser):
62 self.directory = directory
64 self.type = parser.get(name, 'type')
65 self.depends = parser.get(name, 'depends').split()
66 self.provides = parser.get(name, 'provides')
67 self.summary = parser.get(name, 'summary')
68 self.description = parser.get(name, 'description')
69 self.authors = parser.get(name, 'authors').split(',')
70 self.introduced = parser.get(name, 'introduced')
72 if parser.has_option(name, 'notes'):
73 self.notes = parser.get(name, 'notes')
75 if self.type != 'default' and self.type != 'incomplete' and self.type != 'abusive':
76 printerr('\'%s\' has an unknown type of \'%s\'!' % (self.name, self.type))
79 output = 'name: %s\n' % self.name
80 output += 'authors: %s\n' % string.join(self.authors, ', ')
81 output += 'type: %s\n' % self.type
82 output += 'depends: %s\n' % string.join(self.depends, ' ')
83 output += 'provides: %s\n' % self.provides
84 output += 'directory: %s\n' % self.directory
85 output += 'summary: %s\n' % self.summary
86 output += 'description: %s\n' % self.description
89 output += 'notes: %s\n' % self.notes
97 def load_plugins(self, types, depends):
101 if len(depends) == 0:
104 for file in glob.glob('*/plugins.cfg'):
105 parser = ConfigParser.ConfigParser()
109 except ConfigParser.ParsingError, msg:
110 printerr('Failed to parse \'%s\':\n%s' % (file, msg))
113 for plugin in parser.sections():
114 p = Plugin(os.path.dirname(file), plugin, parser)
116 # this is kind of hacky, but if we have types, we check to see
117 # if the type is in list of types to load.
118 if types and not p.type in types:
121 # now we check if the give plugins depends match the search
124 if len(set(depends).intersection(set(p.depends))) == 0:
127 self.plugins[p.provides] = p
129 def list_type(self, type):
132 for name in self.plugins.keys():
133 plugin = self.plugins[name]
134 if plugin.type == type:
141 def list_dep(self, dep):
144 for name in self.plugins.keys():
145 plugin = self.plugins[name]
147 if dep in plugin.depends:
154 def print_names(self, list):
158 names.append(plugin.name)
160 print string.join(names, ',')
162 def default_plugins(self):
163 return self.list_type('default')
165 def abusive_plugins(self):
166 return self.list_type('abusive')
168 def incomplete_plugins(self):
169 return self.list_type('incomplete')
171 def purple_plugins(self):
172 return self.list_dep('purple')
174 def finch_plugins(self):
175 return self.list_dep('finch')
177 def pidgin_plugins(self):
178 return self.list_dep('pidgin')
180 def unique_dirs(self):
182 for name in self.plugins.keys():
183 dirs[self.plugins[name].directory] = 1
190 def help(self, args):
191 """Displays information about other commands"""
193 cmd = self.commands[args[0]]
196 print 'command \'%s\' was not found' % args[0]
198 print '%s' % (self.help.__doc__)
201 print ' help <command>'
203 print 'Available commands:'
205 cmds = self.commands.keys()
208 print ' %s' % (string.join(cmds, ' '))
209 commands['help'] = help
211 def dist_dirs(self, args):
212 """Displays a list of all plugin directories to included in the distribution"""
213 print string.join(self.unique_dirs(), ' ')
214 commands['dist_dirs'] = dist_dirs
216 def build_dirs(self, args):
217 """Displays a list of the plugins that can be built"""
219 printerr('build_dirs expects 2 arguments:')
220 printerr('\ta comma separated list of dependencies')
221 printerr('\ta comma separated list of plugins to build')
224 # store the external depedencies
225 externals = args[0].split(',')
229 # run through the provided dependencies, setting their dependencies to
230 # nothing since we know we already have them
234 # now run through the plugins adding their deps to the dictionary
235 for name in self.plugins.keys():
236 plugin = self.plugins[name]
238 deps[plugin.provides] = plugin.depends
240 # run through the requested plugins and store their plugin instance in check
242 for provides in args[1].split(','):
244 if provides == 'all':
246 for p in self.default_plugins():
247 defaults.append(p.provides)
253 plugin = self.plugins[provides]
254 check.append(plugin.provides)
258 # convert our list of plugins to check into a set to remove dupes
261 # create our list of plugins to build
264 # now define a function to check our deps
265 def has_deps(provides):
266 # don't add anything to build more than once
267 if provides in build:
271 dep_list = deps[provides]
275 # now check the dependencies
279 for d in dep.split('|'):
286 if not has_deps(dep):
289 # make sure the provides isn't an external
290 if not provides in externals:
291 build.append(provides)
293 # everything checks out!
296 # check all the plugins we were told to for their dependencies
300 # now create a list of all directories to build
303 for provides in build:
304 plugin = self.plugins[provides]
306 output.append(plugin.directory)
310 print "%s" % (string.join(output, ','))
311 commands['build_dirs'] = build_dirs
313 def list_plugins(self, args):
314 """Displays a list similiar to 'dpkg -l' about the plugin pack"""
318 # create an array for the widths, we initialize it to the lengths of
319 # the title strings. We ignore summary, since that one shouldn't
323 for p in self.plugins.keys():
324 plugin = self.plugins[p]
326 if plugin.type == 'abusive':
328 elif plugin.type == 'incomplete':
333 if 'finch' in plugin.depends:
335 elif 'pidgin' in plugin.depends:
337 elif 'purple' in plugin.depends:
342 widths[0] = max(widths[0], len(plugin.name))
343 widths[1] = max(widths[1], len(plugin.provides))
344 widths[2] = max(widths[2], len(plugin.summary))
346 data[plugin.provides] = [type, ui, plugin.name, plugin.provides, plugin.summary]
348 print 'Type=Default/Incomplete/Abusive'
349 print '| UI=Finch/Pidgin/puRple/Unknown'
350 print '|/ Name%s Provides%s Summary' % (' ' * (widths[0] - 4), ' ' * (widths[1] - 8))
351 print '++-%s-%s-%s' % ('=' * (widths[0]), '=' * (widths[1]), '=' * (widths[2]))
353 # create the format var
354 fmt = '%%s%%s %%-%ds %%-%ds %%s' % (widths[0], widths[1]) #, widths[2])
356 # now loop through the list again, with everything formatted
362 print fmt % (d[0], d[1], d[2], d[3], d[4])
363 commands['list'] = list_plugins
365 def config_file(self, args):
366 """Outputs the contents for the file to be m4_include()'d from configure"""
367 uniqdirs = self.unique_dirs()
369 # add our --with-plugins option
370 print 'AC_ARG_WITH(plugins,'
371 print ' AC_HELP_STRING([--with-plugins], [what plugins to build]),'
372 print ' ,with_plugins=all)'
373 print 'if test -z $with_plugins ; then'
374 print '\twith_plugins=all'
377 # determine and add our output files
378 print 'PP_DIST_DIRS="%s"' % (string.join(uniqdirs, ' '))
379 print 'AC_SUBST(PP_DIST_DIRS)'
381 print 'AC_CONFIG_FILES(['
383 print '\t%s/Makefile' % (dir)
387 # setup a second call to determine the plugins to be built
388 print 'PP_BUILD=`$PYTHON $srcdir/plugin_pack.py build_dirs $DEPENDENCIES $with_plugins`'
390 print 'PP_BUILD_DIRS=`echo $PP_BUILD | sed \'s/,/\ /g\'`'
391 print 'AC_SUBST(PP_BUILD_DIRS)'
393 print 'PP_PURPLE_BUILD="$PYTHON $srcdir/plugin_pack.py -p show_names $PP_BUILD"'
394 print 'PP_PIDGIN_BUILD="$PYTHON $srcdir/plugin_pack.py -P show_names $PP_BUILD"'
395 print 'PP_FINCH_BUILD="$PYTHON $srcdir/plugin_pack.py -f show_names $PP_BUILD"'
396 commands['config_file'] = config_file
398 def dependency_graph(self, args):
399 """Outputs a graphviz script showing plugin dependencies"""
400 def node_label(plugin):
401 node = plugin.provides.replace('-', '_')
406 def print_plugins(list):
408 node, label = node_label(plugin)
410 print '\t%s[label="%s"];' % (node, label)
413 print '\tlabel="Dependency Graph";'
414 print '\tlabelloc="t";'
415 print '\tsplines=TRUE;'
416 print '\toverlap=FALSE;'
418 print '\tnode[fontname="sans", fontsize="8", style="filled"];'
421 # run through the default plugins
422 print '\t/* default plugins */'
423 print '\tnode[fillcolor="palegreen",shape="tab"];'
424 print_plugins(self.default_plugins())
427 # run through the incomplete plugins
428 print '\t/* incomplete plugins */'
429 print '\tnode[fillcolor="lightyellow1",shape="note"];'
430 print_plugins(self.incomplete_plugins())
433 # run through the abusive plugins
434 print '\t/* abusive plugins */'
435 print '\tnode[fillcolor="lightpink",shape="octagon"];'
436 print_plugins(self.abusive_plugins())
439 # run through again, this time showing the relations
440 print '\t/* dependencies'
441 print '\t * exteranl ones that don\'t have nodes get colored to the following'
443 print '\tnode[fillcolor="powderblue", shape="egg"];'
445 for name in self.plugins.keys():
446 plugin = self.plugins[name]
448 node, label = node_label(plugin)
450 for dep in plugin.depends:
451 dep = dep.replace('-', '_')
452 print '\t%s -> %s;' % (node, dep)
455 commands['dependency_graph'] = dependency_graph
457 def debian_description(self, args):
458 """Outputs the description for the Debian packages"""
459 print 'Description: %d useful plugins for Pidgin, Finch, and Purple' % len(self.plugins)
460 print ' The Plugin Pack is a collection of many simple-yet-useful plugins for Pidgin,'
461 print ' Finch, and Purple. You will find a summary of each plugin below. For more'
462 print ' about an individual plugin, please see %s' % webpage
464 print ' Note: not all %d of these plugins are currently usable' % len(self.plugins)
466 list = self.plugins.keys()
469 plugin = self.plugins[key]
471 print ' %s: %s' % (plugin.name, plugin.summary)
475 print ' Homepage: %s' % webpage
476 commands['debian_description'] = debian_description
478 def show_names(self, args):
479 """Displays the names of the given comma separated list of provides"""
481 if len(args) == 0 or len(args[0]) == 0:
482 printerr('show_names expects a comma separated list of provides')
485 provides = args[0].split(',')
486 if len(provides) == 0:
491 for provide in provides:
492 if not provide in self.plugins:
495 name = self.plugins[provide].name
497 if len(line) + len(name) + 2 > 75:
498 print line.rstrip(',')
501 line += ' %s,' % name
504 print line.rstrip(',')
505 commands['show_names'] = show_names
507 def info(self, args):
508 """Displays all information about the given plugins"""
511 print self.plugins[p].__str__().strip()
513 print 'Failed to find a plugin that provides \'%s\'' % (p)
516 commands['info'] = info
518 def stats(self, args):
519 """Displays stats about the plugin pack"""
522 counts['total'] = len(self.plugins)
523 counts['default'] = len(self.default_plugins())
524 counts['incomplete'] = len(self.incomplete_plugins())
525 counts['abusive'] = len(self.abusive_plugins())
526 counts['purple'] = len(self.purple_plugins())
527 counts['finch'] = len(self.finch_plugins())
528 counts['pidgin'] = len(self.pidgin_plugins())
531 return "%3d (%6.2f%%)" % (val, (float(val) / float(counts['total'])) * 100.0)
533 print "Purple Plugin Pack Stats"
535 print "%d plugins in total" % (counts['total'])
538 print " complete: %s" % (value(counts['default']))
539 print " incomplete: %s" % (value(counts['incomplete']))
540 print " abusive: %s" % (value(counts['abusive']))
543 print " purple: %s" % (value(counts['purple']))
544 print " finch: %s" % (value(counts['finch']))
545 print " pidgin: %s" % (value(counts['pidgin']))
546 commands['stats'] = stats
548 def show_usage(pp, exitcode):
551 cmds = pp.commands.keys()
555 print " %-20s %s" % (cmd, pp.commands[cmd].__doc__)
562 # create our main instance
571 opts, args = getopt.getopt(sys.argv[1:], shortopts)
572 except getopt.error, msg:
578 types.append('abusive')
580 types.append('default')
582 types.append('incomplete')
584 depends.append('finch')
586 depends.append('pidgin')
588 depends.append('purple')
590 # load the plugins that have been requested, if both lists are empty, all
592 pp.load_plugins(types, depends)
601 pp.commands[cmd](pp, args)
603 printerr('\'%s\' command not found' % (cmd))
605 if __name__ == '__main__':
606 # this is a work around when we're called for a directory that isn't the
607 # directory that this file is in. This happens during distcheck, as well
608 # as a few other cases that most people won't use ;)
609 if os.path.dirname(__file__) != '':
610 os.chdir(os.path.dirname(__file__))