#!/usr/bin/python -tt # Fedora Developer Shell # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # Authors: Luke Macken # # TODO: # - query pkgdb # - view all branch versions, and easily move versions between branches # - List available bugzillas.. give the ability to view/control all bugs of theirs or for any component in any of the bugzillas # v0.1 release # - fedora-qa!!!! # - push updates to bodhi # - koji building/querying # - query versions of any package, drop into its code, view patches # - Ability to grep mailing lists and even IRC (?!) # - readline support@! # - audit (flawfinder/rats/etc) import os import re import stat import gzip import email import urllib2 import logging import commands from glob import glob from os.path import expanduser, exists, join, dirname, isdir from mailbox import UnixMailbox from datetime import datetime, timedelta __version__ = '0.0.1' __description__ = 'A shell for hacking on the Fedora project' FEDORA_DIR = join(expanduser('~'), 'code', 'fedora') DEVSHELL_DIR = join(expanduser('~'), '.devshell') stack = [] prompt = ['\033[34;1mfedora\033[0m'] modules = {} header = lambda x: "%s %s %s" % ('=' * 2, x, '=' * (76 - len(x))) log = logging.getLogger(__name__) class Module: """ Our parent class for all command modules """ pass def load_modules(): global modules from inspect import isclass log.debug("Loading modules") for f in os.listdir(os.path.abspath('modules')): module_name, ext = os.path.splitext(f) if ext == '.py': exec "from modules import %s as module" % module_name for item in dir(module): obj = getattr(module, item) if item[0] != '_' and isclass(obj): modules[item.lower()] = obj log.info(" * %s" % item) del module def print_docstrings(module=None): """ Print out the docstrings for all methods in the specified module. If no module is specified, then display docstrings for all modules. """ def _print_docstrings(name, module): log.info(header(name)) for prop in filter(lambda x: x[0] != '_', dir(module)): if callable(getattr(module, prop)) and hasattr(module, '__doc__') \ and getattr(module, prop).__doc__: log.info(" |- [%s] %s" % (prop, getattr(module, prop).__doc__.strip())) if not module: for name, module in modules.items(): _print_docstrings(name, module) else: _print_docstrings(str(module.__class__).split('.')[-1], module) def shell(): global stack, prompt while True: try: data = raw_input('/'.join(prompt) + '> ') except (EOFError, KeyboardInterrupt): print break if not data: continue if data in ('quit', 'exit'): break keyword = data.split()[0] args = data.split()[1:] # Show the docstrings to all methods for all loaded modules if keyword == 'help': print_docstrings() # Go up a module in our stack elif keyword in ('up', 'cd..', 'cd ..'): stack = stack[:-1] prompt = prompt[:-1] # Show the docstrings for all methods in our current module elif keyword in ('ls', 'help', '?'): if len(stack): print_docstrings(stack[-1]) else: print_docstrings() # Flush our module stack elif keyword == 'cd': stack = [] prompt = ['\033[34;1mfedora\033[0m'] # iPython. Never leave home without it. elif keyword in ('py', 'python'): os.system("ipython -noconfirm_exit") else: if len(stack): top = stack[-1] else: top = None output, top, params = do_command(data.split(), top) stack += [top] prompt += [top.__class__.__name__] + params class ModuleError(Exception): def __init__(self, reason, error): self.reason = reason self.error = error def load_module(name, data=[]): # stack = [] # prompt = [] top = None try: log.debug("Loading %s.__init__(%s)" % (name, data)) top = modules[name](*data) # stack += [top] # prompt += [name] + data except TypeError, e: log.debug('got type error') log.error("%s: %s" % (name, e)) raise ModuleError('probably bad call to __init__' , e) return top def do_command(data, top=None): # Do some intelligent handling of unknown input. # For the given input 'foo bar', we first check if we have the # module 'foo' loaded, then we push it on the top of our module # stack and check if it has the 'bar' attribute. If so, we call # it with any remaining arguments from collections import deque data = deque(data) log.debug(data) params = [] module = None while len(data): if not top: log.debug('not top') mod = data.popleft() try: module = modules[mod] except KeyError, e: log.error('%s is not a known module' % e.message) return None, None, None log.debug('mod is %s' % mod) params = [] while len(data): log.debug('params so far %s' % params) log.debug('inner loop %s' % data) param = data.popleft() if hasattr(module, param): data.appendleft(param) break params += [param] try: top = module = load_module(mod, params) mod_params = params loaded_module = True except ModuleError, e: log.debug(e.reason) break else: log.debug('is top') params = [] log.debug('params so far %s' % params) param = data.popleft() if hasattr(top, param): log.debug("next property %s found in %s" % (param, top)) top = getattr(top, param) else: log.debug("adding argument: %s" % param) params += [param] output = None if len(params): output = top(*params) if module: return output, module, mod_params else: return output, None, None def main(): global log from optparse import OptionParser parser = OptionParser(usage="%prog [options]", version="%s %s" % ('%prog', __version__), description=__description__) parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='Display verbose debugging output') (opts, args) = parser.parse_args() # Setup our logger sh = logging.StreamHandler() if opts.verbose: log.setLevel(logging.DEBUG) sh.setLevel(logging.DEBUG) format = logging.Formatter("[%(levelname)s] %(message)s") else: log.setLevel(logging.INFO) sh.setLevel(logging.INFO) format = logging.Formatter("%(message)s") sh.setFormatter(format) log.addHandler(sh) if not os.path.isdir(FEDORA_DIR): log.info("Creating %s" % FEDORA_DIR) os.makedirs(FEDORA_DIR) load_modules() shell() if __name__ == '__main__': main()