-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathfilterpipes.py
More file actions
274 lines (224 loc) · 8.49 KB
/
filterpipes.py
File metadata and controls
274 lines (224 loc) · 8.49 KB
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# Copyright 2015 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This is not an official Google product.
"""FilterPipes Library.
This module abstracts many of the details for dealing with the
SublimeText API. Several generic commands are provided, which can
be further customized for additional behavior.
Part of the FilterPipes SublimeText Plugin.
github.com/tylerl/FilterPipes
"""
__author__ = 'Tyler Larson [github.com/tylerl/]'
__version__ = '1.1.0'
__license__ = 'Apache 2'
__copyright__ = 'Copyright 2015, Google Inc.'
import subprocess
import sublime
import sublime_plugin
import sys
import re
###############################################################
# Python/Sublime version compatibility
if sys.version_info[0] == 2: # Python 2.x specific (ST2)
PYTHON2=True
def is_str(obj):
return isinstance(obj, basestring)
else: # Python 3.x
PYTHON2=False
def is_str(obj): # Python 3.x specific (ST3)
return isinstance(obj, str)
###############################################################
class FilterPipesCommandBase(sublime_plugin.TextCommand):
"""Generic base for function-based filter commands.
This class is not used directly, but rather inherited to build
a filter plugin that actually does something. Selection and
text replacement logic are handled by this class.
Override filter(self, data) to perform text filter operation.
"""
use_selections = True
errors_on_statusbar = True
report_success = True
report_failure = True
report_nochange = True
def filter(self, data):
"""Perform transformation on document text.
Args:
data: string containing selected text.
Returns:
string containing the desired replacement, or None to
indicate no operation.
"""
return None
def success_message(self):
"""Message to display on status bar if success."""
return 'FilterPipes: success'
def failure_message(self):
"""Message to display on status bar if unsuccessful."""
return 'FilterPipes: command failed'
def nochange_message(self):
"""Message to display on status bar if replacement matches original."""
return 'FilterPipes: No change'
def do_replacements(self, edit):
self.success = False
self.replaced = False
replacements = []
for r in self._regions():
replacement = self._get_replacement(r)
if replacement is None:
continue
if replacement:
replacements.append(replacement)
# replace in reverse order to avoid overlap complications
for replacement in reversed(replacements):
self._commit_replacement(edit, replacement)
msg = None
if not self.success:
if self.report_failure:
msg = self.failure_message()
elif not self.replaced:
if self.report_nochange:
msg = self.nochange_message()
else:
if self.report_success:
msg = self.success_message()
if msg:
sublime.status_message(msg)
def post_init(self):
"""Hook for doing some post-init reconfiguration.
See the IntToInt filter for an example of how this
can be useful.
"""
pass
def apply_settings(self, settings):
for k, v in settings.items():
setattr(self, k, v)
def run(self, edit, **settings):
try:
self.apply_settings(settings)
self.post_init()
self.do_replacements(edit)
except Exception as ex:
if self.errors_on_statusbar:
sublime.status_message(str(ex))
raise
def _get_replacement(self, region):
existing = self.view.substr(region)
filtered = self.filter(existing)
if filtered is None:
return None
self.success = True
if filtered == existing:
return None
self.replaced = True
return (region, filtered)
def _commit_replacement(self, edit, replacement):
region, text = replacement
self.view.replace(edit, region, text)
def _regions(self):
regions = None
if self.use_selections:
regions = [r for r in self.view.sel() if not r.empty()]
if not regions:
regions = [sublime.Region(0, self.view.size())]
return regions
class FilterPipesProcessCommand(FilterPipesCommandBase):
"""Generic base for Process-based filter commands.
Override self.command or self.getcommand() to specify which command to run,
or pass in at run time as a configuration parameter. Set "shell" to true to
execute as a shell command instead of direct process invocation.
"""
command = []
use_selections = True
shell = False
report_failure = False # we do our own failure reporting
expected_returns = [0]
subprocess_args = {}
def _execute_raw(self, command, text):
"""Executes a command and returns stdout, stderr, and return code."""
args = self.subprocess_args or {}
args['shell'] = self.shell
cmd = subprocess.Popen(command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE, **args)
(stdout, stderr) = cmd.communicate(text.encode('UTF-8'))
return (stdout, stderr, cmd.returncode)
def _expect_success(self, command, text):
try:
stdout, stderr, status = self._execute_raw(command, text)
if not self.expected_returns or status in self.expected_returns:
return stdout.decode('UTF-8')
except OSError as e:
stdout, stderr, status = (None, str(e), e.errno)
if self.errors_on_statusbar:
sublime.status_message(
'Error %i executing command [%s]: %s' %
(status, self.get_command_as_str(), stderr))
print(
'Error %i executing command [%s]:\n%s\n' %
(status, self.get_command_as_str(False), stderr))
return None
def filter(self, existing):
return self._expect_success(self.get_command(), existing)
def get_command(self):
return self.command
def get_command_as_str(self, short=True):
c = self.get_command()
if is_str(c):
return c
if short:
return c[0]
return ' '.join(c)
def success_message(self):
return 'Filtered through: %s' % (self.get_command_as_str())
class FilterPipesTranslateCommand(FilterPipesCommandBase):
"""Translates characters from one set to another.
Like the tr shell command.
"""
before = None
after = None
def filter(self, data):
if not self.before or not self.after:
return None
if PYTHON2:
trans = dict(zip([ord(c) for c in self.before], self.after))
else:
trans = str.maketrans(self.before,self.after)
return data.translate(trans)
class FilterPipesRegexCommand(FilterPipesCommandBase):
"""Performs a regular expression replacement.
Because re.sub is magic, replacement can be either a string or a
function that takes a match object.
"""
regex = None
replacement = None
flags = 0
count = 0
lines = False
def filter(self, data):
if self.regex is None or self.replacement is None:
return None
if self.lines:
self.flags |= re.MULTILINE
return re.sub(self.regex, self.replacement, data,
count=self.count, flags=self.flags)
class FilterPipesExecPromptCommand(sublime_plugin.TextCommand):
"""Prompt for a command to filter text through."""
def run(self, edit):
self.view.window().show_input_panel(
'Filter Command:', '', self.on_done, None, None)
def on_done(self, text):
self.view.run_command(
'filter_pipes_process', {'command': text, 'shell': True})