#!/usr/bin/env python # -*- coding: utf-8 -*- import getopt import sys import search def show_book_or_person(ids, commit_format=False, tmp_file=False): pass # def search_book(): # pass def search_person(): pass def commit(filename=None): pass def edit_book_or_person(ids): filename = show_book_or_person(ids, commit_format=True, tmp_file=True) # run_editor(filename) commit(filename) commands = { 'show': { 'args': [('ids', (1,None))], 'options': ['commit_format', 'tmp_file'], 'fun': show_book_or_person }, 'search': { 'args': [('search_strings', (1,None))], 'options': ['search_description'], 'fun': search.search_book_cmd }, 'search-person': { 'args': [('search_strings', (1,None))], 'options': [], 'fun': search_person }, 'commit': { 'args': [('filename', (0,1))], 'options': [], 'fun': commit }, 'edit': { 'args': [('ids', (1,None))], 'options': [], 'fun': edit_book_or_person } } flags = { 'commit_format': { 'help': 'output data in the format expected by the commit command' }, 'tmp_file': { 'help': 'output data to a new temporary file instead of to stdout' }, 'search_description': { 'help': 'include description field when searching' } } general_options = [] # options applicable to all commands class BadCommandLine(Exception): def __init__(self, msg): Exception.__init__(self, 'Bad command line: ' + msg) def check_command_args(args, command): cmd_decl = commands[command] min_num_args = sum(map(lambda a: a[1][0], cmd_decl['args'])) unlimited = any(map(lambda a: a[1][1] == None, cmd_decl['args'])) if not unlimited: max_num_args = sum(map(lambda a: a[1][1], cmd_decl['args'])) if len(args) < min_num_args: raise BadCommandLine('Too few arguments for command %s (expects at least %d, %d given).' % (command, min_num_args, len(args))) if (not unlimited) and (len(args) < max_num_args): raise BadCommandLine('Too many arguments for command %s (expects at most %d, %d given).' % (command, max_num_args, len(args))) def check_command_opts(opts, command): cmd_decl = commands[command] for opt,val in opts: if ((opt not in cmd_decl['options']) and (opt not in general_options)): raise BadCommandLine('Option %s not applicable to command %s.' % (opt, command)) def assign_command_args(args, command): cmd_decl = commands[command] d = {} i = 0 for param in cmd_decl['args']: pname = param[0] pmin = param[1][0] pmax = param[1][1] if pmax == 1: if i < len(args): d[pname] = args[i] i += 1 else: d[pname] = None else: d[pname] = [] j = 0 while i + j < len(args) and (pmax == None or j < pmax): d[pname].append(args[i + j]) j += 1 i = i + j return d def assign_command_opts(opts, command): d = {} for option, value in opts: if option in flags: d[option] = True else: d[option] = value return d def parse_cmdline(args): def getopt_option_name(internal_name): return internal_name.replace('_', '-') def internal_option_name(getopt_ret_name): return getopt_ret_name[2:].replace('-', '_') option_names = map(getopt_option_name, flags) options, args = getopt.getopt(args, '', option_names) if len(args) == 0: raise BadCommandLine('No command specified.') cmd_name = args[0] if cmd_name not in commands: raise BadCommandLine('Nonexisting command %s.' % cmd_name) cmd_decl = commands[cmd_name] cmd_args = args[1:] cmd_opts = map(lambda (opt,val): (internal_option_name(opt), val), options) check_command_args(cmd_args, cmd_name) check_command_opts(cmd_opts, cmd_name) return { 'command': cmd_name, 'args': combine_dicts(assign_command_args(cmd_args, cmd_name), assign_command_opts(cmd_opts, cmd_name)) } def invoke_command(command, args): cmd_decl = commands[command] cmd_decl['fun'](**args) def combine_dicts(*dicts): res = {} for d in dicts: res.update(d) return res cmdline_parsed = parse_cmdline(sys.argv[1:]) print 'command line parsed to:', cmdline_parsed invoke_command(cmdline_parsed['command'], cmdline_parsed['args'])