{{announcement.body}}
{{announcement.title}}

Implement a Command-Line Shell by Using Command Dispatcher in Python

DZone 's Guide to

Implement a Command-Line Shell by Using Command Dispatcher in Python

Learn more about implementing a command-line Shell using the CommandDispatcher in Python.

· Open Source Zone ·
Free Resource

Let's implement a command Shell by using a command dispatcher. The objective is to have an event loop, which is to dispatch input commands and implement some handlers who are going to handle those commands. And, we don't want to change in the event loop whenever some new command comes in future. So, our design should be scalable enough to support adding new commands without affecting the event loop.

Decorator in Python

I hope you have seen Timeit implementation using Decorator.

Event Loop (Command Dispatcher)

class CommandDispatch:
    def __init__(self):
        # list of commands
        self.commands = {}

    """
    For registering commands
    """
    def for_command(self, cmd):
        def wrap(fn):
            self.commands[cmd] = fn
        return wrap

    """
    For registering invalid command handler
    """
    def invalid(self, fn):
        self.invalidfn = fn

    """
    For registering input method
    """
    def input(self, fn):
        self.inputfn = fn

    """
    Main event loop
    """
    def run(self):
        while True:
            args = self.inputfn()
            self.commands.get(args[0], self.invalidfn)(*args)


The above class implements a Decorator pattern and provides an event loop. And, some methods to register method handlers.

Command Handlers

"""
     A command_dispatch module with class CommandDispatch
     such that the following example program that implements a
     rudimentary command-line shell works.
"""

from command_dispatch import CommandDispatch

shell = CommandDispatch()

@shell.for_command("list")
def list_directory(*args):
    from os import listdir
    if len(args) < 2:
        args += ".",
    for path in args[1:]:
        print("{}:".format(path))
        print("\n".join(listdir(path)))

@shell.for_command("whoami")
def show_user(*args):
    from getpass import getuser
    print(getuser())

@shell.for_command("date")
def print_date(*args):
    from time import ctime
    print(ctime())

@shell.for_command("pwd")
def show_curr_dir(*args):
    import os
    print(os.getcwd())

@shell.for_command("exit")
def exit_shell(*args):
    exit(0)

@shell.for_command("hostname")
def show_hostname(*args):
    from os import uname
    print(uname().nodename)

@shell.invalid
def invalid_command(*args):
    print("Invalid command - ", args[0])


@shell.input
def get_input():
    # import rlcompleter
    return input("PyShell> ").split()

if __name__ == '__main__':
    shell.run()


Here, we are instantiating our event loop. And, using that instance, registering different command handlers. For example:

@shell.for_command("exit")
def exit_shell(*args):
    exit(0)


We are registering a command handler for command: exit. It gets registered in our  CommandDispatcher’s list: commands. After executing this file. The dictionary in CommandDispatcher looks like:

{
    'list': <function list_directory>, 
    'whoami': <function show_user>, 
    'date': <function print_date>, 
    'pwd': <function show_curr_dir>, 
    'exit': <function exit_shell>, 
    'hostname': <function show_hostname>
}


So, when we ran our program. At first, it runs the run method of  CommandDispatcher’s class. And, in the infinite loop, it calls the input function — and, whatever argument it returns. We are checking in the dictionary: commands. If it is found, we are calling that method by passing args, or else calling invalidfn.

def run(self):
    while True:
        args = self.inputfn()
        self.commands.get(args[0], self.invalidfn)(*args)


GitHub

The code can be found at GyanBlog Github repository.

Let me know in the comment section if there is any confusion. Hope it helps!

Topics:
python ,decorator design pattern ,shell ,command-line ,open source ,decorator

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}