Add GNU readline-like support.

This commit is contained in:
Ian Adam Naval 2015-02-26 16:30:48 -05:00
parent 490c9f5900
commit 6f04d50ea0
3 changed files with 106 additions and 53 deletions

79
console.py Normal file
View File

@ -0,0 +1,79 @@
import atexit
import code
import os
import readline
import shlex
from commands import registered_cmds
import example_cmd
DEFAULT_HISTORY_FILE = "~/.psh_history"
def parse_cmd(potential_cmd):
"""Evaluates a potential command. If it exists in the list of
registered commands, we return a string that would call the
constructor for that command. If it does not exist, we wrap the
name of the command with the RawCommand class.
:return: A string that when evaluated by Python's `eval` feature
would build an object of the correct type
"""
args = potential_cmd.strip().split(' ')
cmd_name = args[0]
if args:
args = args[1:]
if cmd_name not in registered_cmds:
return "RawCommand({})".format(shlex.split(potential_cmd))
else:
return "{0}({1})".format(cmd_name,str(args))
def parse_cmds(raw_input_line):
"""Parses command objects out of a single | separated string"""
potential_cmds = raw_input_line.split('|')
cmds = [parse_cmd(cmd) for cmd in potential_cmds]
return cmds
class HistoryConsole(code.InteractiveConsole):
"""Stolen from https://docs.python.org/2/library/readline.html
Modified for the purposes of this project to handle special
bash-like parsing"""
def __init__(self, locals=None, filename="<console>",
histfile=os.path.expanduser(DEFAULT_HISTORY_FILE)):
code.InteractiveConsole.__init__(self, locals, filename)
self.init_history(histfile)
def init_history(self, histfile):
readline.parse_and_bind("tab: complete")
if hasattr(readline, "read_history_file"):
try:
readline.read_history_file(histfile)
except IOError:
pass
atexit.register(self.save_history, histfile)
def raw_input(self, prompt=""):
"""Gets a single line of input for the REPL, and returns some valid
Python for the rest of the REPL to evaluate. It's the "R" in REPL.
If the line begins with ">", we just strip it and evaluate as valid
Python."""
raw_input_line = input(prompt)
if raw_input_line[0] == '>':
return raw_input_line[1:]
else:
cmds = parse_cmds(raw_input_line)
cmds.append("Printer()")
mangled_input = cmds[0]
for cmd in cmds[1:]:
mangled_input += ".chain(" + cmd + ")"
mangled_input += ".call()"
print("[DEBUG]: evaluating Python: ", mangled_input)
return mangled_input
def save_history(self, histfile):
readline.write_history_file(histfile)

59
main.py
View File

@ -1,11 +1,8 @@
from commands import registered_cmds
from raw_commands import RawCommand
from formatters import Printer
from example_cmd import example_cmd, echo
import os import os
import os.path import os.path
import shlex
from formatters import *
from raw_commands import RawCommand
# Load all of the commands in the path into the global namespace as raw # Load all of the commands in the path into the global namespace as raw
# commands. # commands.
@ -17,54 +14,10 @@ for path in os.environ['PATH'].split(':'):
globals()[binary] = RawCommand(binary) globals()[binary] = RawCommand(binary)
def parse_cmd(potential_cmd):
"""Evaluates a potential command. If it exists in the list of
registered commands, we return a string that would call the
constructor for that command. If it does not exist, we wrap the
name of the command with the RawCommand class.
:return: A string that when evaluated by Python's `eval` feature
would build an object of the correct type
"""
args = potential_cmd.strip().split(' ')
cmd_name = args[0]
if args:
args = args[1:]
if cmd_name not in registered_cmds:
return "RawCommand({})".format(shlex.split(potential_cmd))
else:
return "{0}({1})".format(cmd_name,str(args))
def parse_cmds(raw_input_line):
"""Parses command objects out of a single | separated string"""
potential_cmds = raw_input_line.split('|')
cmds = [parse_cmd(cmd) for cmd in potential_cmds]
return cmds
def handle_input(prompt=""):
"""Gets a single line of input for the REPL, and returns some valid
Python for the rest of the REPL to evaluate. It's the "R" in REPL.
If the line begins with ">", we just strip it and evaluate as valid
Python."""
raw_input_line = input(prompt)
if raw_input_line[0] == '>':
return raw_input_line[1:]
else:
cmds = parse_cmds(raw_input_line)
cmds.append("Printer()")
mangled_input = cmds[0]
for cmd in cmds[1:]:
mangled_input += ".chain(" + cmd + ")"
mangled_input += ".call()"
print("[DEBUG]: evaluating Python: ", mangled_input)
return mangled_input
def main(): def main():
import code from console import HistoryConsole
code.interact("Augmented Unix Userland", handle_input, globals()) console = HistoryConsole(globals())
console.interact("Augmented Unix Userland")
if __name__ == '__main__': if __name__ == '__main__':

21
tree.py Normal file
View File

@ -0,0 +1,21 @@
class TreeNode(object):
"""A tree node holds some data and has iterable children."""
def __init__(self, data):
"""Initializes a data. The data can be any type, but it usually
is an ordered dictionary."""
self.parent = None
self.data = data
self.children = []
def add_child(self, child):
"""Adds a child to the node."""
child.parent = self
self.children.apppend(child)
def children(self):
"""Returns an iterable generator for all the children nodes."""
def children_generator():
for child in self.children:
yield child
return children_generator()