186 lines
8.2 KiB
Python
186 lines
8.2 KiB
Python
#!/usr/bin/env python
|
|
# The command-line interface module creates a interface for
|
|
# interacting with the python program (wikicrawl). This is an implementation
|
|
# of the baker demo shown previously. The user can type in commands to
|
|
# make the program do things.
|
|
|
|
import baker
|
|
import logging
|
|
import readline # Needed for command history <up> and <down> arrows to work
|
|
import sys
|
|
|
|
from . import model
|
|
from . import config
|
|
|
|
# Problem pages:
|
|
# Decision (from politics)
|
|
# Malaysia (goes inside parenthesis)
|
|
|
|
commander = baker.Baker()
|
|
|
|
def main():
|
|
user_interface = InteractiveInterface()
|
|
|
|
if len(sys.argv) > 1: # Command line arguments were passed in
|
|
# command-line when invoking python
|
|
user_interface.run(sys.argv)
|
|
else:
|
|
user_interface.start_command_loop()
|
|
|
|
class InteractiveInterface:
|
|
def __init__(self):
|
|
self.model = model.Model()
|
|
|
|
def run(self, args, main=True):
|
|
"""
|
|
Runs the command-line interface for a single command.
|
|
|
|
If called by InteractiveInterface.run(sys.argv), this method
|
|
will execute the commands and arguments specified on command
|
|
line when running this program. Alternatively, the code could
|
|
pass in a different set of arguments to specify what to do.
|
|
See start_command_loop() for more information.
|
|
"""
|
|
try:
|
|
commander.run(argv=args, main=True, help_on_error=True,
|
|
instance=self)
|
|
except baker.CommandError as ex:
|
|
logging.warn('incorrect user input: %s' % ex)
|
|
commander.usage()
|
|
except baker.TopHelp as ex:
|
|
commander.usage()
|
|
except Exception as ex:
|
|
logging.error('caught general exception!!')
|
|
print(type(ex), ex)
|
|
|
|
def start_command_loop(self):
|
|
"""
|
|
Repeatedly asks the user what command to run until they exit.
|
|
|
|
This method calls InteractiveInterface.run(args) a little bit
|
|
differently. Instead of passing the arguments from the command-line
|
|
that were passed in when invoking the python wikicrawl app,
|
|
this asks the user for a line of textual input and passes
|
|
those strings to run() as the arguments. This way, the user can
|
|
access an interactive shell and repeatedly issue different
|
|
commands while the application is running.
|
|
"""
|
|
commander.usage()
|
|
self.model.open_browser()
|
|
while True:
|
|
print('$ ', end = '') # Display to the user a command prompt
|
|
# The dollar-sign is a common indication
|
|
# of a shell that communicates to the user
|
|
# that we are waiting for their textual
|
|
# input. The end = '' indicates to python
|
|
# to NOT drop to a newline after printing
|
|
# in the terminal. Instead, let the user
|
|
# type their command on the same line as
|
|
# our printed '$ '.
|
|
try:
|
|
inp = input()
|
|
except EOFError: # <ctrl>+D will send "End Line" and exit the command loop
|
|
break
|
|
# Note in arguments (mg):
|
|
# Whenever a program is run in windows or *nix, the operating
|
|
# system passes in the command string that was used to invoke
|
|
# the program. You can append data in that command to configure
|
|
# switches or values going into the program on the fly. For
|
|
# example, you can invoke this wikicrawl app in more than one
|
|
# way. You can of course run "python launcher.py" to run the
|
|
# software but you can also pass in an argument. You can
|
|
# alternatively run "python launcher.py <argument> <argument>..."
|
|
# and the operating system will provide the <argument> values into
|
|
# the process that is running.
|
|
#
|
|
# In a real world use case, many commands provide switches to
|
|
# adjust what the program does. For example,
|
|
#
|
|
# The command:
|
|
# find music -iname "*justin*bieber*"
|
|
# runs the "find" program and asks to find all the filenames that match the
|
|
# pattern *justin*bieber* in the "music" directory.
|
|
# (music, -iname, "*justin*biever*") are argument parameters
|
|
# that are passed into the program. The program is coded to
|
|
# parse and interpret these values and execute differently based
|
|
# on the values passed in. This is one way to pass in information
|
|
# into a running program. Some other ways are to read from a file
|
|
# (such as how we read from settings.py to load the runtime
|
|
# configuration), from something called environment variables
|
|
# (won't get into but another set of values provided to programs
|
|
# from the operating system), or they can be hard-coded into
|
|
# the application.
|
|
#
|
|
# Side note: arguments are not unique to python (almost all
|
|
# programming languages implement arguments), the functionality
|
|
# is defined by the application (some programs require arguments,
|
|
# some are optional, and the syntax for sending in argument
|
|
# parameters are different and defined by the individual programs,
|
|
# and lastly, the first argument sent in is the script name or
|
|
# filename of the script. In our case, the first argument is
|
|
# the string "launcher.py". If the user invoked the command
|
|
# as C:\Users\mguest\launcher.py then the first argument
|
|
# would be C:\Users\mguest\launcher.py.
|
|
|
|
# What this method (start_command_loop()) does is provide a
|
|
# REPL which is a
|
|
# read-eval-print-loop. It repeatedly asks the user for an
|
|
# input (read), evaluates that input into an action (evaluate),
|
|
# give the user some feedback (print), and start the process
|
|
# over again (loop). When you call "python", you are given a python
|
|
# process that gives you a REPL interactive shell. The way
|
|
# this wikicrawl app is implemented gives the user a REPL
|
|
# that has commands to interact with wikipedia pages.
|
|
args = [sys.argv[0], ] + inp.split()
|
|
|
|
# The user can at any point in the command pass the argument
|
|
# switch "--help". If doing this, the command line interface
|
|
# will instead print out the inline documentation associated
|
|
# with this command and quit after doing so. For example,
|
|
# the user can type "python launcher.py do_random_page --help"
|
|
# and the program will spit out the generated documentation
|
|
# for the do_random_page command and run nothing. In our case,
|
|
# this documentation is created by the baker library and will
|
|
# print out the docstring associated with the method. Try it
|
|
# out in your shell (cmd.exe or powershell.exe) by invoking
|
|
# python launcher.py do_random_page --help
|
|
# You will see the program spit out the heredoc below the
|
|
# do_random_page method defined below.
|
|
|
|
if '--help' in args:
|
|
args.remove('--help')
|
|
try:
|
|
print('command usage:')
|
|
commander.usage(args[1])
|
|
return
|
|
except Exception as ex:
|
|
print(type(ex), ex)
|
|
continue
|
|
|
|
self.run(args, main=False)
|
|
|
|
@commander.command
|
|
def do_random_page(self):
|
|
"""
|
|
Instructs the wikicrawl application to play the game on a random
|
|
article.
|
|
"""
|
|
self.model.do_random_page()
|
|
|
|
@commander.command
|
|
def do_n_pages(self, n):
|
|
"""
|
|
Plays the wikicrawl game <n>-times.
|
|
"""
|
|
try:
|
|
n = int(n)
|
|
except ValueError as ex:
|
|
logging.warn('failed to process "%s" as a parameter' % n)
|
|
return False
|
|
for i in range(n):
|
|
self.model.do_random_page()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
|