forked from noklesta/SublimeRailsNav
/
SublimeRailsNav.py
364 lines (304 loc) · 15.1 KB
/
SublimeRailsNav.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
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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
import os
import re
import sublime
import sublime_plugin
from recursive_glob import rglob
from lib.inflector import *
class RailsMixin:
def get_setting(self, key):
settings = None
view = self.window.active_view()
if view:
settings = self.window.active_view().settings()
if settings and settings.has('SublimeRailsNav') and key in settings.get('SublimeRailsNav'):
# Get project-specific setting
dirs = settings.get('SublimeRailsNav')[key]
else:
# Get user-specific or default setting
settings = sublime.load_settings('SublimeRailsNav.sublime-settings')
dirs = settings.get(key)
return dirs
def show_files(self, dirs, file_pattern='\.rb$'):
paths = self.construct_glob_paths(dirs)
self.find_files(paths, file_pattern)
view = self.window.active_view()
if view:
current_file = view.file_name()
if self.is_listing_current_file_group(current_file):
self.remove_from_list(current_file)
else:
self.move_related_files_to_top(current_file)
start_index = len(self.root) + 1
# Need to add a couple of spaces to avoid getting the file names cut off
relative_paths = map(lambda x: x[start_index:] + ' ', self.files)
self.window.show_quick_panel(relative_paths, self.file_selected)
def rails_root(self):
# Look for a Gemfile first, since that should always be found in the
# root directory of a Rails 3 project. If no Gemfile is found, we
# might have a Rails 2 (or earlier) project, so look for a Rakefile
# instead. However, since Rakefiles may be found in subdirectories as
# well, in that case we also check for a number for additional
# standard Rails directories.
for root_indicator in ['Gemfile', 'Rakefile']:
folders = self.window.folders()
if len(folders) == 0:
return False
directory = folders[0]
while directory:
if os.path.exists(os.path.join(directory, root_indicator)):
if root_indicator == 'Gemfile':
return directory
else:
looks_like_root = True
for additional_dir in ['app', 'config', 'lib', 'vendor']:
if not (os.path.exists(os.path.join(directory, additional_dir))):
looks_like_root = False
break
if looks_like_root:
return directory
parent = os.path.realpath(os.path.join(directory, os.path.pardir))
if parent == directory:
# /.. == /
break
directory = parent
return False
def construct_glob_paths(self, dirs):
paths = []
for dir in dirs:
paths.append(os.path.join(self.root, *dir))
return paths
def file_selected(self, selected_index):
if selected_index != -1:
self.window.open_file(self.files[selected_index])
def find_files(self, paths, file_pattern):
self.files = []
for path in paths:
self.files.extend(rglob(path, file_pattern))
def remove_from_list(self, current_file):
# First check to see if the current file is in the list. For instance,
# if the current file is under vendor/assets/javascripts and we did
# not include that among the javascript locations that we list,
# the current file will not in fact be there.
if current_file in self.files:
self.files.remove(current_file)
pass
def move_related_files_to_top(self, current_file):
related_file_name_pattern = self.construct_related_file_name_pattern(current_file)
if related_file_name_pattern:
for file in self.files:
if re.search(related_file_name_pattern, file):
i = self.files.index(file)
self.files.insert(0, self.files.pop(i))
class RailsCommandBase(sublime_plugin.WindowCommand, RailsMixin):
MODEL_DIR = os.path.join('app', 'models')
CONTROLLER_DIR = os.path.join('app', 'controllers')
VIEW_DIR = os.path.join('app', 'views')
HELPER_DIR = os.path.join('app', 'helpers')
FIXTURE_DIR = os.path.join('test', 'fixtures')
def setup(self):
self.root = self.rails_root()
if not self.root:
sublime.error_message('No Rails root directory found. Not a Rails application?')
return False
if os.path.isdir(os.path.join(self.root, 'spec')):
# RSpec seems to be installed, so ignore the 'test' dir and search for specs
self.test_type = 'spec'
self.model_test_dir = os.path.join('spec', 'models')
self.controller_test_dir = os.path.join('spec', 'controllers')
self.view_test_dir = os.path.join('spec', 'views')
self.helper_test_dir = os.path.join('spec', 'helpers')
else:
# No RSpec, so use the standard 'test' dir
self.test_type = 'test'
self.model_test_dir = os.path.join('test', 'unit')
self.controller_test_dir = os.path.join('test', 'functional')
self.helper_test_dir = os.path.join('test', 'unit', 'helpers')
return True
def construct_related_file_name_pattern(self, current_file):
pass
class ListRailsModelsCommand(RailsCommandBase):
def run(self):
if not self.setup():
return
self.show_files([['app', 'models']])
def construct_related_file_name_pattern(self, current_file):
if self.CONTROLLER_DIR in current_file:
m = re.search(r'(\w+)_controller\.\w+$', current_file)
singular = Inflector().singularize(m.group(1))
pattern = re.sub(self.CONTROLLER_DIR, self.MODEL_DIR, current_file)
pattern = re.sub(r'\w+_controller(\.\w+$)', '%s\g<1>' % singular, pattern)
return pattern
elif self.FIXTURE_DIR in current_file:
m = re.search(r'(\w+)\.yml$', current_file)
singular = Inflector().singularize(m.group(1))
pattern = re.sub(self.FIXTURE_DIR, self.MODEL_DIR, current_file)
pattern = re.sub(r'\w+.yml$', '%s.rb' % singular, pattern)
return pattern
elif self.model_test_dir in current_file:
pattern = re.sub(self.model_test_dir, self.MODEL_DIR, current_file)
pattern = re.sub(r'(_%s)(.\w+)$' % self.test_type, '\g<2>', pattern)
return pattern
else:
return None
def is_listing_current_file_group(self, current_file):
return os.path.join('app', 'models') in current_file
class ListRailsControllersCommand(RailsCommandBase):
def run(self):
if not self.setup():
return
self.show_files([['app', 'controllers']])
def construct_related_file_name_pattern(self, current_file):
if self.MODEL_DIR in current_file:
m = re.search(r'(\w+)\.\w+$', current_file)
plural = Inflector().pluralize(m.group(1))
pattern = re.sub(self.MODEL_DIR, self.CONTROLLER_DIR, current_file)
pattern = re.sub(r'\w+(\.\w+)$', '%s_controller\g<1>' % plural, pattern)
return pattern
elif self.VIEW_DIR in current_file:
pattern = re.sub(self.VIEW_DIR, self.CONTROLLER_DIR, current_file)
pattern = re.sub(os.path.join('', r'(\w+)', r'[\w\.]+$'), '\g<1>_controller', pattern)
return pattern
if self.HELPER_DIR in current_file:
pattern = re.sub(self.HELPER_DIR, self.CONTROLLER_DIR, current_file)
pattern = re.sub(r'_helper\.rb$', '_controller\.rb', pattern)
return pattern
elif self.controller_test_dir in current_file:
pattern = re.sub(self.controller_test_dir, self.CONTROLLER_DIR, current_file)
pattern = re.sub(r'(_%s)(.\w+)$' % self.test_type, '\g<2>', pattern)
return pattern
else:
return None
def is_listing_current_file_group(self, current_file):
return os.path.join('app', 'controllers') in current_file
class ListRailsViewsCommand(RailsCommandBase):
def run(self):
if not self.setup():
return
self.show_files([['app', 'views']], '\.(?:erb|haml|slim)$')
def construct_related_file_name_pattern(self, current_file):
if self.CONTROLLER_DIR in current_file:
pattern = re.sub(self.CONTROLLER_DIR, self.VIEW_DIR, current_file)
pattern = re.sub(r'(\w+)_controller\.\w+$', '\g<1>' + os.sep, pattern)
return pattern
elif self.test_type == 'test' and self.controller_test_dir in current_file:
# With Test::Unit, view tests are found in the controller test
# file, so the best we can do is to show all views for the
# controller associated with the currently active controller test
# at the top of the list.
pattern = re.sub(self.controller_test_dir, self.VIEW_DIR, current_file)
pattern = re.sub(r'(\w+)_controller_test\.rb$', '\g<1>' + os.sep, pattern)
return pattern
elif self.test_type == 'spec' and self.view_test_dir in current_file:
# RSpec uses separate view specs, so here we can show the
# particular view associated with the currently active spec at the
# top of the list.
pattern = re.sub(self.view_test_dir, self.VIEW_DIR, current_file)
pattern = re.sub(r'(\w+)\.[\w\.]+$', '\g<1>\.', pattern)
return pattern
else:
return None
def is_listing_current_file_group(self, current_file):
return os.path.join('app', 'views') in current_file
class ListRailsHelpersCommand(RailsCommandBase):
def run(self):
if not self.setup():
return
self.show_files([['app', 'helpers']])
def construct_related_file_name_pattern(self, current_file):
if self.CONTROLLER_DIR in current_file:
pattern = re.sub(self.CONTROLLER_DIR, self.HELPER_DIR, current_file)
pattern = re.sub(r'_controller\.rb$', '_helper\.rb', pattern)
return pattern
elif self.helper_test_dir in current_file:
pattern = re.sub(self.helper_test_dir, self.HELPER_DIR, current_file)
pattern = re.sub(r'(_%s)(.\w+)$' % self.test_type, '\g<2>', pattern)
return pattern
else:
return None
def is_listing_current_file_group(self, current_file):
return os.path.join('app', 'helpers') in current_file
class ListRailsFixturesCommand(RailsCommandBase):
def run(self):
if not self.setup():
return
self.show_files([['test', 'fixtures']], '\.yml$')
def construct_related_file_name_pattern(self, current_file):
if self.MODEL_DIR in current_file:
m = re.search(r'(\w+)\.rb$', current_file)
plural = Inflector().pluralize(m.group(1))
pattern = re.sub(self.MODEL_DIR, self.FIXTURE_DIR, current_file)
pattern = re.sub(r'\w+\.rb$', r'%s\.yml' % plural, pattern)
return pattern
elif self.model_test_dir in current_file:
m = re.search(r'(\w+)_%s\.rb$' % self.test_type, current_file)
plural = Inflector().pluralize(m.group(1))
pattern = re.sub(self.model_test_dir, self.FIXTURE_DIR, current_file)
pattern = re.sub(r'(\w+)_%s\.rb$' % self.test_type, r'%s\.yml' % plural, pattern)
return pattern
elif self.controller_test_dir in current_file:
m = re.search(r'(\w+)_controller_%s\.rb$' % self.test_type, current_file)
plural = Inflector().pluralize(m.group(1))
pattern = re.sub(self.controller_test_dir, self.FIXTURE_DIR, current_file)
pattern = re.sub(r'(\w+)_controller_%s\.rb$' % self.test_type, r'%s\.yml' % plural, pattern)
return pattern
else:
return None
def is_listing_current_file_group(self, current_file):
return os.path.join('test', 'fixtures') in current_file
class ListRailsTestsCommand(RailsCommandBase):
def run(self):
if not self.setup():
return
self.show_files([[self.test_type]])
def construct_related_file_name_pattern(self, current_file):
if self.MODEL_DIR in current_file:
pattern = re.sub(self.MODEL_DIR, self.model_test_dir, current_file)
pattern = re.sub(r'(\.\w+)$', '_%s\g<1>' % self.test_type, pattern)
return pattern
elif self.CONTROLLER_DIR in current_file:
pattern = re.sub(self.CONTROLLER_DIR, self.controller_test_dir, current_file)
pattern = re.sub(r'(\.\w+)$', '_%s\g<1>' % self.test_type, pattern)
return pattern
elif self.VIEW_DIR in current_file:
if self.test_type == 'spec':
# RSpec uses separate view specs
pattern = re.sub(self.VIEW_DIR, self.view_test_dir, current_file)
pattern = re.sub(r'(\w+)\.[\w\.]+$', r'\g<1>[\w\.]*_spec\.rb', pattern)
else:
# Test::Unit puts view tests in the controller test file
pattern = re.sub(self.VIEW_DIR, self.controller_test_dir, current_file)
pattern = re.sub(r'(\w+)%s[\w\.]+$' % os.sep, '\g<1>_controller_test.rb', pattern)
return pattern
elif self.HELPER_DIR in current_file:
pattern = re.sub(self.HELPER_DIR, self.helper_test_dir, current_file)
pattern = re.sub(r'\.rb$', r'_%s\.rb' % self.test_type, pattern)
return pattern
elif self.FIXTURE_DIR in current_file:
m = re.search(r'(\w+)\.yml$', current_file)
singular = Inflector().singularize(m.group(1))
pattern = re.sub(self.FIXTURE_DIR, r'(?:%s|%s)' % (self.model_test_dir, self.controller_test_dir), current_file)
pattern = re.sub(r'(\w+)\.yml$', r'(?:\g<1>_controller|%s)_%s\.rb' % (singular, self.test_type), pattern)
return pattern
elif 'config/routes.rb' in current_file and self.test_type == 'spec':
pattern = os.path.join(self.root, 'spec', 'routing', '.+_routing_spec.rb')
return pattern
else:
return None
def is_listing_current_file_group(self, current_file):
return os.path.join(self.root, self.test_type) in current_file and not self.FIXTURE_DIR in current_file
class ListRailsJavascriptsCommand(RailsCommandBase):
def run(self):
if not self.setup():
return
dirs = self.get_setting('javascript_locations')
self.show_files(dirs, '\.(?:js|coffee|erb)$')
def is_listing_current_file_group(self, current_file):
return 'javascripts' in current_file
class ListRailsStylesheetsCommand(RailsCommandBase):
def run(self):
if not self.setup():
return
dirs = self.get_setting('stylesheet_locations')
self.show_files(dirs, '\.(?:css|scss|less)$')
def is_listing_current_file_group(self, current_file):
return 'stylesheets' in current_file