diff --git a/sith.py b/sith.py index a27197f8..049f6178 100755 --- a/sith.py +++ b/sith.py @@ -16,8 +16,18 @@ Show recorded exception:: ./sith.py show +Run a specific operation + + ./sith.py run operation /path/to/source/file.py line col + +Where operation is one of completions, goto_assignments, goto_definitions, +usages, or call_signatures. + +Note: Line numbers start at 1; columns start at 0 (this is consistent with +many text editors, including Emacs). + Usage: - sith.py [--pdb|--ipdb|--pudb] [-d] [-m=] [-f] [--record=] random [] + sith.py [--pdb|--ipdb|--pudb] [-d] [-n=] [-f] [--record=] random [] sith.py [--pdb|--ipdb|--pudb] [-d] [-f] [--record=] redo sith.py [--pdb|--ipdb|--pudb] [-d] [-f] run sith.py show [--record=] @@ -65,6 +75,8 @@ class SourceFinder(object): class TestCase(object): def __init__(self, operation, path, line, column, traceback=None): + if operation not in self.operations: + raise ValueError("%s is not a valid operation" % operation) self.operation = operation self.path = path self.line = line @@ -77,12 +89,13 @@ class TestCase(object): dct = json.load(f) return cls(**dct) + operations = [ + 'completions', 'goto_assignments', 'goto_definitions', 'usages', + 'call_signatures'] + @classmethod def generate(cls, file_path): - operations = [ - 'completions', 'goto_assignments', 'goto_definitions', 'usages', - 'call_signatures'] - operation = random.choice(operations) + operation = random.choice(cls.operations) path = random.choice(SourceFinder.files(file_path)) with open(path) as f: @@ -95,12 +108,16 @@ class TestCase(object): column = random.randint(0, len(lines[line - 1])) return cls(operation, path, line, column) - def run(self, debugger, record=None): + def run(self, debugger, record=None, print_result=False): try: with open(self.path) as f: - script = jedi.Script(f.read(), self.line, self.column, - self.path) - getattr(script, self.operation)() + self.file = f.read() + self.script = jedi.Script(self.file, self.line, self.column, + self.path) + self.completions = getattr(self.script, self.operation)() + if print_result: + self.show_location(self.line, self.column) + self.show_operation() except jedi.NotFoundError: pass except Exception: @@ -108,7 +125,7 @@ class TestCase(object): if record is not None: with open(record, 'w') as f: json.dump(self.__dict__, f) - self.show() + self.show_errors() if debugger: einfo = sys.exc_info() pdb = __import__(debugger) @@ -118,7 +135,46 @@ class TestCase(object): pdb.post_mortem(einfo[2]) exit(1) - def show(self): + def show_location(self, lineno, column, show=3): + # Three lines ought to be enough + lower = lineno - show if lineno - show > 0 else 0 + for i, line in enumerate(self.file.split('\n')[lower:lineno]): + print(lower + i + 1, line) + print(' ' * (column + len(str(lineno))), '^') + + def show_operation(self): + print("%s:\n" % self.operation.capitalize()) + getattr(self, 'show_' + self.operation)() + + def show_completions(self): + for completion in self.completions: + print(completion.name) + + # TODO: Support showing the location in other files + + # TODO: Move this printing to the completion objects themselves + def show_usages(self): + for completion in self.completions: + print(completion.description) + if os.path.abspath(completion.module_path) == os.path.abspath(self.path): + self.show_location(completion.line, completion.column) + + def show_call_signatures(self): + for completion in self.completions: + # This is too complicated to print. It really should be + # implemented in str() anyway. + print(completion) + # Can't print the location here because we don't have the module path + + def show_goto_definitions(self): + for completion in self.completions: + print(completion.desc_with_module) + if os.path.abspath(completion.module_path) == os.path.abspath(self.path): + self.show_location(completion.line, completion.column) + + show_goto_assignments = show_goto_definitions + + def show_errors(self): print(self.traceback) print(("Error with running Script(...).{operation}() with\n" "\tpath: {path}\n" @@ -138,13 +194,13 @@ def main(arguments): if arguments['redo'] or arguments['show']: t = TestCase.from_cache(record) if arguments['show']: - t.show() + t.show_errors() else: t.run(debugger) elif arguments['run']: TestCase(arguments[''], arguments[''], int(arguments['']), int(arguments['']) - ).run(debugger) + ).run(debugger, print_result=True) else: for _ in range(int(arguments['--maxtries'])): t = TestCase.generate(arguments[''] or '.')