-
Notifications
You must be signed in to change notification settings - Fork 5
/
execute.py
130 lines (101 loc) · 3.67 KB
/
execute.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""
This is basically a polished version of https://github.com/vim/MiniPy,
and I see no license there so AFAIK you need to ask there any time you
even think about doing anything to this source. Or you can just duck
and hope, like I have.
My changes are hereby released completely and irrevocably into the
Public Domain. THE ORIGINAL CODE, THAT OF MINIPY, MAY NOT BE PUBLIC
DOMAIN AND THUS THIS CODE SHOULD NOT BE THOUGHT OF AS PUBLIC DOMAIN.
- Joshua Landau <[email protected]>
"""
import sublime, sublime_plugin
import os, sys
from code import compile_command
from io import StringIO
# For eval
import math, random, itertools, operator, functools, os
from collections import ChainMap
modules = {
"functools": functools,
"itertools": itertools,
"math": math,
"operator": operator,
"os": os,
"random": random,
}
class EvaluateSelectionCommand(sublime_plugin.TextCommand):
"""
Based of Minipy, this evaluates your selected text.
If "execute" is True, it "$"s are replaced with consecutive
numbers (incrementing once per selection) and then it
is run through Python.
If "execute" is False, no evaluating is done but "{}"-
formatters are also passed up to 100 instances of the number;
this allows you to do things like add padding to the number.
"""
def run(self, edit, execute):
old_stdout = sys.stdout
try:
exec_globals = dict(ChainMap(modules.copy(), *map(vars, modules.values())))
exec_locals = {}
# i is the value for each $
for i, region in enumerate(self.view.sel(), start=1):
output = sys.stdout = StringIO()
# Get and substitute "$"s
text = self.view.substr(region)
text = text.replace("$", str(i))
if execute:
# Wrap in case of failure -- a failure should not stop
# later evaluations
try:
to_exec = ""
for line in text.splitlines(keepends=True):
previous_to_exec, to_exec = to_exec, to_exec + line
try:
# Returns None if it is "incomplete", so the except block
# is not triggered. This prevents early execution.
#
# Additionally, this runs by default in "single" mode, which
# is the same thing the REPL uses. We get results for free
# from the automatic printing that goes on
compile_command(to_exec)
except SyntaxError:
# We must have gone too far, execute the previous to_exec
#
# Allow errors to fall though
#
# compile_command is more lenient than compile in "single" mode for
# some reason, so having used it above we have to use it here too.
command = compile_command(previous_to_exec)
# Double-check that it works
if not command:
raise
exec(command, exec_globals, exec_locals)
# This allows some functions, mainly recursive ones, to work better
exec_globals.update(exec_locals)
exec_locals = {}
to_exec = line
if to_exec:
# Allow errors to fall though
#
# compile_command is more lenient than compile in "single" mode for
# some reason, so having used it above we have to use it here too.
command = compile_command(to_exec + "\n")
# Double-check that it works
if not command:
raise SyntaxError
exec(command, exec_globals, exec_locals)
output.seek(0)
text = output.read()
if text.endswith("\n"):
text = text[:-1]
except Exception as e:
print("Evaluate Selection Error:", e, file=old_stdout)
else:
# Don't use .format unless not evalling or you block
# use of .format from within Python
text = text.format(*[i]*100)
# Woo! Commit!
self.view.replace(edit, region, text)
finally:
sys.stdout = old_stdout