| 1 |
from crash.console import * |
|---|
| 2 |
from cly.symbols import * |
|---|
| 3 |
import cly.parser, cly.rlext |
|---|
| 4 |
from cly.parser import Parser, Result |
|---|
| 5 |
import os |
|---|
| 6 |
import os.path |
|---|
| 7 |
import inspect |
|---|
| 8 |
import pydoc |
|---|
| 9 |
import sys |
|---|
| 10 |
import types |
|---|
| 11 |
import re |
|---|
| 12 |
import readline |
|---|
| 13 |
from fnmatch import fnmatch |
|---|
| 14 |
|
|---|
| 15 |
""" |
|---|
| 16 |
|
|---|
| 17 |
A collection of functions useful in conjunction with CLY. |
|---|
| 18 |
|
|---|
| 19 |
""" |
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 22 |
__all__ = [ "file_completer", "introspect_grammar", "format_help", "execute_result", "execute_command", "interact", "interact_loop", ] |
|---|
| 23 |
|
|---|
| 24 |
# XXX Used by the interactive functions XXX |
|---|
| 25 |
__cli_inject_text = '' |
|---|
| 26 |
__completion_candidates = [] |
|---|
| 27 |
__parser = None |
|---|
| 28 |
|
|---|
| 29 |
class file_completer: |
|---|
| 30 |
"""File tab completion for CLY. |
|---|
| 31 |
Usage: |
|---|
| 32 |
... COMPLETE : cly.util.file_completer('*.py') |
|---|
| 33 |
""" |
|---|
| 34 |
def __init__(self, *args): |
|---|
| 35 |
self.__args = args |
|---|
| 36 |
|
|---|
| 37 |
def __call__(self, ctx, sofar): |
|---|
| 38 |
return self.__complete(sofar, *self.__args) |
|---|
| 39 |
|
|---|
| 40 |
def __complete(self, sofar, pattern = '*', show_hidden = False): |
|---|
| 41 |
base = sofar |
|---|
| 42 |
remainder = '' |
|---|
| 43 |
files = [] |
|---|
| 44 |
# Is the current text a directory? |
|---|
| 45 |
if not os.path.exists(base): |
|---|
| 46 |
remainder = os.path.basename(base) |
|---|
| 47 |
base = os.path.dirname(base) |
|---|
| 48 |
if os.path.isdir(base or '.'): |
|---|
| 49 |
files = os.listdir(base or '.') |
|---|
| 50 |
files.sort() |
|---|
| 51 |
# Filter file list |
|---|
| 52 |
files = [os.path.join(base, file) for file in files if \ |
|---|
| 53 |
(not show_hidden and file[0] != '.') \ |
|---|
| 54 |
and file.startswith(remainder) \ |
|---|
| 55 |
and (os.path.isdir(os.path.join(base, file)) or fnmatch(file, pattern)) \ |
|---|
| 56 |
] |
|---|
| 57 |
files = [os.path.isdir(file) and file + '/' or file for file in files] |
|---|
| 58 |
if len(files) == 1 and not os.path.isdir(files[0]): |
|---|
| 59 |
return [files[0] + ' '] |
|---|
| 60 |
return files |
|---|
| 61 |
|
|---|
| 62 |
def dump_traceback(exception): |
|---|
| 63 |
import traceback |
|---|
| 64 |
from StringIO import StringIO |
|---|
| 65 |
out = StringIO() |
|---|
| 66 |
traceback.print_exc(file = out) |
|---|
| 67 |
error(str(exception), "\n^N^1", out.getvalue()) |
|---|
| 68 |
raise exception |
|---|
| 69 |
|
|---|
| 70 |
def introspect_grammar(instance, split_names = True): |
|---|
| 71 |
""" Generate a CLY grammar from a class instance. |
|---|
| 72 |
|
|---|
| 73 |
Methods in the form "def some_command(self, arg): ..." are converted to |
|---|
| 74 |
the grammar: |
|---|
| 75 |
|
|---|
| 76 |
'some' : { |
|---|
| 77 |
HELP : 'some', |
|---|
| 78 |
'command' : { |
|---|
| 79 |
HELP : pydoc.getdoc(instance.some_command), |
|---|
| 80 |
'.+' : { |
|---|
| 81 |
VAR : 's:arg', |
|---|
| 82 |
ACTION : instance.some_command, |
|---|
| 83 |
}, |
|---|
| 84 |
}, |
|---|
| 85 |
}, |
|---|
| 86 |
|
|---|
| 87 |
CamelCase function names are also translated in a similar fashion. |
|---|
| 88 |
|
|---|
| 89 |
In addition, class members that are also instances of classes, will be |
|---|
| 90 |
inserted into the grammar in a similar fashion. |
|---|
| 91 |
""" |
|---|
| 92 |
grammar = {} |
|---|
| 93 |
namere = re.compile(r'''([A-Z]{2,}|[A-Z]+[a-z]*|[a-z]+)''') |
|---|
| 94 |
for fullname, method in inspect.getmembers(instance, lambda m: type(m) is types.InstanceType or inspect.ismethod(m)): |
|---|
| 95 |
if fullname[0] == '_': |
|---|
| 96 |
continue |
|---|
| 97 |
|
|---|
| 98 |
if split_names: |
|---|
| 99 |
name = [x.lower() for x in namere.findall(' '.join(fullname.split('_')))] |
|---|
| 100 |
else: |
|---|
| 101 |
name = [ fullname ] |
|---|
| 102 |
# Merge generated name into grammar |
|---|
| 103 |
def merge(grammar, name): |
|---|
| 104 |
if not name: |
|---|
| 105 |
return grammar |
|---|
| 106 |
return merge(grammar.setdefault(name[0], { |
|---|
| 107 |
HELP : name[0], |
|---|
| 108 |
}), name[1:]) |
|---|
| 109 |
g = merge(grammar, name) |
|---|
| 110 |
|
|---|
| 111 |
label = 'execute_%s' % fullname |
|---|
| 112 |
|
|---|
| 113 |
g[HELP] = pydoc.getdoc(method) or name[-1] |
|---|
| 114 |
g[LABEL] = label |
|---|
| 115 |
|
|---|
| 116 |
if type(method) is types.InstanceType: |
|---|
| 117 |
g.update(introspect_grammar(method, split_names = split_names)) |
|---|
| 118 |
continue |
|---|
| 119 |
|
|---|
| 120 |
# Insert args |
|---|
| 121 |
argspec = inspect.getargspec(method) |
|---|
| 122 |
args = argspec[0] |
|---|
| 123 |
if args and args[0] == 'self': |
|---|
| 124 |
args = args[1:] |
|---|
| 125 |
if argspec[3] is not None: |
|---|
| 126 |
defaults = dict(zip(args[-len(argspec[3]):], argspec[3])) |
|---|
| 127 |
else: |
|---|
| 128 |
defaults = {} |
|---|
| 129 |
|
|---|
| 130 |
def generate_argg(arg): |
|---|
| 131 |
help = '%s (^B%s^N)' % (arg, arg in defaults and '%s' % str(defaults[arg]) or '^Urequired') |
|---|
| 132 |
return help, { |
|---|
| 133 |
UNLESS_VAR : arg, |
|---|
| 134 |
VAR : arg, |
|---|
| 135 |
HELP : ('<%s>' % arg, help), |
|---|
| 136 |
JUMP : label, |
|---|
| 137 |
} |
|---|
| 138 |
|
|---|
| 139 |
if len(args) == 1: |
|---|
| 140 |
arg = args[0] |
|---|
| 141 |
help, argg = generate_argg(arg) |
|---|
| 142 |
g['.*'] = argg |
|---|
| 143 |
else: |
|---|
| 144 |
for arg in args: |
|---|
| 145 |
help, argg = generate_argg(arg) |
|---|
| 146 |
g[arg] = { |
|---|
| 147 |
UNLESS_VAR : arg, |
|---|
| 148 |
HELP : help, |
|---|
| 149 |
'.*' : argg, |
|---|
| 150 |
} |
|---|
| 151 |
|
|---|
| 152 |
class validate_args: |
|---|
| 153 |
def __init__(self, args, defaults): |
|---|
| 154 |
from copy import copy |
|---|
| 155 |
self.args = args |
|---|
| 156 |
self.defaults = defaults |
|---|
| 157 |
|
|---|
| 158 |
def __call__(self, ctx): |
|---|
| 159 |
for arg in self.args: |
|---|
| 160 |
if arg not in self.defaults and arg not in ctx: |
|---|
| 161 |
return False |
|---|
| 162 |
return True |
|---|
| 163 |
|
|---|
| 164 |
g[ACTION] = { |
|---|
| 165 |
IF : validate_args(args, defaults), |
|---|
| 166 |
ACTION : method, |
|---|
| 167 |
HELP : pydoc.getdoc(method) or 'Execute command', |
|---|
| 168 |
} |
|---|
| 169 |
|
|---|
| 170 |
return grammar |
|---|
| 171 |
|
|---|
| 172 |
def __cli_completion(text, state): |
|---|
| 173 |
global __completion_candidates |
|---|
| 174 |
line = readline.get_line_buffer()[0:readline.get_begidx()] |
|---|
| 175 |
ctx = None |
|---|
| 176 |
try: |
|---|
| 177 |
result = __parser.parse(line) |
|---|
| 178 |
if not state: |
|---|
| 179 |
__completion_candidates = result.candidates(text) |
|---|
| 180 |
if __completion_candidates: |
|---|
| 181 |
return __completion_candidates.pop() |
|---|
| 182 |
return None |
|---|
| 183 |
except cly.parser.Error: |
|---|
| 184 |
return None |
|---|
| 185 |
except Exception, e: |
|---|
| 186 |
print |
|---|
| 187 |
dump_traceback(e) |
|---|
| 188 |
cly.rlext.force_redisplay() |
|---|
| 189 |
|
|---|
| 190 |
def __cli_injector(): |
|---|
| 191 |
try: |
|---|
| 192 |
global __cli_inject_text |
|---|
| 193 |
readline.insert_text(__cli_inject_text) |
|---|
| 194 |
__cli_inject_text = '' |
|---|
| 195 |
except e: |
|---|
| 196 |
dump_traceback(e) |
|---|
| 197 |
|
|---|
| 198 |
def __generate_help(parser, line, cursor_offset, need_linefeed = False): |
|---|
| 199 |
try: |
|---|
| 200 |
result = parser.parse(line) |
|---|
| 201 |
# We are not at the end of the line |
|---|
| 202 |
if result.token: |
|---|
| 203 |
readline.insert_text("?") |
|---|
| 204 |
return False |
|---|
| 205 |
if result.context.parsed_tokens and result.context.parsed_tokens[-1].start() >= cursor_offset: |
|---|
| 206 |
print result.context.parsed_tokens[-1].start() |
|---|
| 207 |
return False |
|---|
| 208 |
if need_linefeed: |
|---|
| 209 |
print |
|---|
| 210 |
format_help(result.help()) |
|---|
| 211 |
return True |
|---|
| 212 |
except Exception, e: |
|---|
| 213 |
dump_traceback(e) |
|---|
| 214 |
|
|---|
| 215 |
def __cli_help(key, count): |
|---|
| 216 |
global __parser |
|---|
| 217 |
try: |
|---|
| 218 |
if __generate_help(__parser, readline.get_line_buffer(), cly.rlext.cursor(), True): |
|---|
| 219 |
cly.rlext.force_redisplay() |
|---|
| 220 |
except Exception, e: |
|---|
| 221 |
print |
|---|
| 222 |
try: |
|---|
| 223 |
dump_traceback(e) |
|---|
| 224 |
finally: |
|---|
| 225 |
cly.rlext.force_redisplay() |
|---|
| 226 |
raise |
|---|
| 227 |
|
|---|
| 228 |
def format_help(help): |
|---|
| 229 |
""" Format the output of cly.parser.Result.help() in a useful way. """ |
|---|
| 230 |
header, help, footer = help |
|---|
| 231 |
length = 0 |
|---|
| 232 |
for group in help: |
|---|
| 233 |
for h in help[group]: |
|---|
| 234 |
if len(h[0]) > length: |
|---|
| 235 |
length = len(h[0]) |
|---|
| 236 |
if header: |
|---|
| 237 |
cprint(wraptoterm(header)) |
|---|
| 238 |
print |
|---|
| 239 |
count = 0 |
|---|
| 240 |
groups = len(help.keys()) |
|---|
| 241 |
last_group = last_order = None |
|---|
| 242 |
for order, group in sorted(help, lambda a, b: cmp(a[0], b[0])): |
|---|
| 243 |
subcount = 0 |
|---|
| 244 |
for h in help[(order, group)]: |
|---|
| 245 |
if last_order != None and order != last_order: |
|---|
| 246 |
last_order = order |
|---|
| 247 |
|
|---|
| 248 |
cprint(wraptoterm(" ^B%s^N %s" % (h[0] + (length - len(h[0])) * ' ', h[1]), subsequent_indent = ' ' * (length + 3))) |
|---|
| 249 |
last_order = order |
|---|
| 250 |
count += 1 |
|---|
| 251 |
if count != groups: |
|---|
| 252 |
print |
|---|
| 253 |
if footer: |
|---|
| 254 |
print |
|---|
| 255 |
cprint(wraptoterm(footer)) |
|---|
| 256 |
|
|---|
| 257 |
def execute_result(result): |
|---|
| 258 |
""" Execute the Result object passed. """ |
|---|
| 259 |
if result.state == Result.NOP: |
|---|
| 260 |
pass |
|---|
| 261 |
elif result.state == Result.OK: |
|---|
| 262 |
result() |
|---|
| 263 |
else: |
|---|
| 264 |
if result.token: |
|---|
| 265 |
error("%s (near '%s')^N" % (result.message, result.token.group(0))) |
|---|
| 266 |
else: |
|---|
| 267 |
error(result.message) |
|---|
| 268 |
|
|---|
| 269 |
def execute_command(parser, command): |
|---|
| 270 |
""" Parse and execute the given command, returning the Result. """ |
|---|
| 271 |
__parser = parser |
|---|
| 272 |
command = re.sub(r'^\s+|\s+$', '', command) |
|---|
| 273 |
if command == '': |
|---|
| 274 |
return Result(Result.NOP) |
|---|
| 275 |
|
|---|
| 276 |
# Looking for help? |
|---|
| 277 |
if command[-1] == '?': |
|---|
| 278 |
command = re.sub(r'\?\s*$', '', command) |
|---|
| 279 |
__generate_help(parser, command, len(command)) |
|---|
| 280 |
__cli_inject_text = command |
|---|
| 281 |
return Result(Result.NOP) |
|---|
| 282 |
else: |
|---|
| 283 |
result = parser.parse(command) |
|---|
| 284 |
execute_result(result) |
|---|
| 285 |
return result |
|---|
| 286 |
|
|---|
| 287 |
def interact(parser, prompt = 'cly> ', default_text=''): |
|---|
| 288 |
""" Input one command from the user and return the Result of the executed |
|---|
| 289 |
command. """ |
|---|
| 290 |
global __parser, __cli_inject_text |
|---|
| 291 |
|
|---|
| 292 |
__cli_inject_text = default_text |
|---|
| 293 |
# - and : are acceptable "word" characters |
|---|
| 294 |
readline.set_completer_delims('`~!#$%^&*()=+[{]}\|;\'",<>? \t') |
|---|
| 295 |
readline.set_completer(__cli_completion) |
|---|
| 296 |
readline.set_startup_hook(__cli_injector) |
|---|
| 297 |
|
|---|
| 298 |
# Use custom readline extensions not in 2.4 |
|---|
| 299 |
cly.rlext.bind_key(ord('?'), __cli_help) |
|---|
| 300 |
|
|---|
| 301 |
while True: |
|---|
| 302 |
__parser = parser |
|---|
| 303 |
command = '' |
|---|
| 304 |
try: |
|---|
| 305 |
command = raw_input(prompt) |
|---|
| 306 |
except KeyboardInterrupt: |
|---|
| 307 |
print |
|---|
| 308 |
continue |
|---|
| 309 |
except EOFError: |
|---|
| 310 |
print |
|---|
| 311 |
return Result(Result.ENDOFINPUT) |
|---|
| 312 |
|
|---|
| 313 |
return execute_command(parser, command) |
|---|
| 314 |
|
|---|
| 315 |
def interact_loop(grammar, prompt='cly> ', |
|---|
| 316 |
history=os.path.expanduser('~/.clyhistory'), |
|---|
| 317 |
user_context=None, history_length=500): |
|---|
| 318 |
""" Repeatedly read and execute commands from the user. """ |
|---|
| 319 |
try: |
|---|
| 320 |
readline.set_history_length(history_length) |
|---|
| 321 |
readline.read_history_file(history) |
|---|
| 322 |
except: |
|---|
| 323 |
pass |
|---|
| 324 |
parser = Parser(grammar, user_context) |
|---|
| 325 |
try: |
|---|
| 326 |
while interact(parser, prompt).state != Result.ENDOFINPUT: |
|---|
| 327 |
pass |
|---|
| 328 |
finally: |
|---|
| 329 |
try: |
|---|
| 330 |
readline.write_history_file(history) |
|---|
| 331 |
except: |
|---|
| 332 |
pass |
|---|