-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmain.py
186 lines (159 loc) · 5.43 KB
/
main.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# -*- coding: utf-8 -*-
from math import *
import re
import os
try:
import pyperclip
except:
pyperclip = None
try:
import numpy as np
except:
pass
try:
from scipy.special import *
c = binom
except:
pass
from builtins import * # Required for division scipy, also allows for pow to be used with modulus
sqr = lambda x: x ** 2
x = 0
xFilePath = os.environ['TMP'] + os.sep + "wox_pycalc_x.txt"
if os.path.exists(xFilePath):
try:
with open(xFilePath, "r") as xFile:
x = int(xFile.read())
except:
pass
def json_wox(title, subtitle, icon, action=None, action_params=None, action_keep=None):
json = {
'Title': title,
'SubTitle': subtitle,
'IcoPath': icon
}
if action and action_params and action_keep:
json.update({
'JsonRPCAction': {
'method': action,
'parameters': action_params,
'dontHideAfterAction': action_keep
}
})
return json
def copy_to_clipboard(text):
if pyperclip is not None:
pyperclip.copy(text)
else:
# Workaround
cmd = 'echo ' + text.strip() + '| clip'
os.system(cmd)
def write_to_x(result):
x = result
try:
with open(xFilePath, "w") as xFile:
xFile.write(result)
except:
pass
def format_result(result):
if hasattr(result, '__call__'):
# show docstring for other similar methods
raise NameError
if isinstance(result, str):
return result
if isinstance(result, int) or isinstance(result, float):
if int(result) == float(result):
return '{:,}'.format(int(result)).replace(',', ' ')
else:
return '{:,}'.format(round(float(result), 5)).replace(',', ' ')
elif hasattr(result, '__iter__'):
try:
return '[' + ', '.join(list(map(format_result, list(result)))) + ']'
except TypeError:
# check if ndarray
result = result.flatten()
if len(result) > 1:
return '[' + ', '.join(list(map(format_result, result.flatten()))) + ']'
else:
return format_result(np.asscalar(result))
else:
return str(result)
def handle_factorials(query):
# Replace simple factorial
query = re.sub(r'(\b\d+\.?\d*([eE][-+]?\d+)?\b)!',
lambda match: f'factorial({match.group(1)})', query)
i = 2
while i < len(query):
if query[i] == "!" and query[i-1] == ")":
j = i-1
bracket_count = 1
while bracket_count != 0 and j > 0:
j -= 1
if query[j] == ")":
bracket_count += 1
elif query[j] == "(":
bracket_count -= 1
query = query[:j] + f'factorial({query[j+1:i-1]})' +\
(query[i+1:] if i+1 < len(query) else "")
i += 8 # 8 is the difference between factorial(...) and (...)!
i += 1
return query
def handle_pow_xor(query):
return query.replace("^", "**").replace("xor", "^")
def handle_implied_multiplication(query):
return re.sub(r'((?:\.\d+|\b\d+\.\d*|\b\d+)(?:[eE][-+]?\d+)?)\s*(x|pi)\b',
r'(\1*\2)', query)
def calculate(query):
results = []
# filter any special characters at start or end
query = re.sub(r'(^[*/=])|([+\-*/=(]$)', '', query)
query = handle_factorials(query)
query = handle_pow_xor(query)
query = handle_implied_multiplication(query)
try:
result = eval(query)
formatted = format_result(result)
results.append(json_wox(formatted,
'{} = {}'.format(query, result),
'icons/app.png',
'change_query',
[str(result)],
True))
except SyntaxError:
# try to close parentheses
opening_par = query.count('(')
closing_par = query.count(')')
if opening_par > closing_par:
return calculate(query + ')'*(opening_par-closing_par))
else:
# let Wox keep previous result
raise SyntaxError
except NameError:
# try to find docstrings for methods similar to query
glob = set(filter(lambda x: 'Error' not in x and 'Warning' not in x and '_' not in x, globals()))
help = list(sorted(filter(lambda x: query in x, glob)))[:6]
for method in help:
method_eval = eval(method)
method_help = method_eval.__doc__.split('\n')[0] if method_eval.__doc__ else ''
results.append(json_wox(method,
method_help,
'icons/app.png',
'change_query_method',
[str(method)],
True))
if not help:
# let Wox keep previous result
raise NameError
return results
from wox import Wox, WoxAPI
class Calculator(Wox):
def query(self, query):
return calculate(query)
def change_query(self, query):
# change query and copy to clipboard after pressing enter
WoxAPI.change_query(query)
write_to_x(query)
copy_to_clipboard(query)
def change_query_method(self, query):
WoxAPI.change_query(query + '(')
if __name__ == '__main__':
Calculator()