rleber-textmate 0.9.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.markdown +47 -0
- data/bin/textmate +7 -0
- data/lib/textmate.rb +14 -0
- data/lib/textmate/bundle.rb +13 -0
- data/lib/textmate/commands.rb +280 -0
- data/lib/textmate/main.rb +511 -0
- data/lib/textmate/repository.rb +29 -0
- data/spec/list_spec.rb +526 -0
- data/spec/locations_spec.rb +91 -0
- data/spec/spec_helper.rb +394 -0
- metadata +94 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe TextMateBundleManager do
|
4
|
+
describe "locations" do
|
5
|
+
|
6
|
+
def run_locations
|
7
|
+
@location_list = capture(:stdout) { TextMateBundleManager.start(["locations"]) }.split("\n").map{|line| line.strip.split(/\s{2,}/)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def locations_for_zone(zone)
|
11
|
+
@location_list.select {|l| l[0] == zone}.map{|l| l[1]}
|
12
|
+
end
|
13
|
+
|
14
|
+
context "with stubbed locations" do
|
15
|
+
before :each do
|
16
|
+
stub_github_user "foo"
|
17
|
+
stub_remote_locations
|
18
|
+
stub_local_locations
|
19
|
+
run_locations
|
20
|
+
end
|
21
|
+
|
22
|
+
it "lists locations beginning with column titles" do
|
23
|
+
@location_list.first.should == %w{Zone Short Name Location}
|
24
|
+
end
|
25
|
+
|
26
|
+
it "lists all the locations" do
|
27
|
+
@location_list.shift
|
28
|
+
@location_list.size.should == 9 # 4 "standard" overridden remote locations, plus 5 "standard" overridden local locations
|
29
|
+
end
|
30
|
+
|
31
|
+
it "lists all location information" do
|
32
|
+
@location_list[1].size.should == 4
|
33
|
+
end
|
34
|
+
|
35
|
+
it "lists local locations before remote ones" do
|
36
|
+
list = @location_list[1..-1].map {|l| l[0]}
|
37
|
+
compressed_list = []
|
38
|
+
last_element = nil
|
39
|
+
list.each do |l|
|
40
|
+
compressed_list << l if l != last_element
|
41
|
+
last_element = l
|
42
|
+
end
|
43
|
+
compressed_list.should == %w{Local Remote}
|
44
|
+
end
|
45
|
+
|
46
|
+
it "lists all local locations from highest to lowest precedence" do
|
47
|
+
locations_for_zone('Local').should == %w{user user_p system system_p app}
|
48
|
+
end
|
49
|
+
|
50
|
+
it "lists all remote locations from highest to lowest precedence" do
|
51
|
+
locations_for_zone('Remote').should == %w{personal github trunk review}
|
52
|
+
end
|
53
|
+
|
54
|
+
it "lists the correct location fields" do
|
55
|
+
@location_list[1].should == %w{Local user User User\ path}
|
56
|
+
end
|
57
|
+
|
58
|
+
it "always has 4 columns" do
|
59
|
+
@location_list.should always_have_n_columns(4)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "when github_user is not defined" do
|
64
|
+
before :each do
|
65
|
+
stub_github_user nil
|
66
|
+
run_locations
|
67
|
+
end
|
68
|
+
|
69
|
+
it "does not include a personal github location" do
|
70
|
+
@location_list.map{|l| l[1]}.should_not include('personal')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "when github_user is defined" do
|
75
|
+
before :each do
|
76
|
+
stub_github_user "foo"
|
77
|
+
run_locations
|
78
|
+
end
|
79
|
+
|
80
|
+
it "includes a personal github location" do
|
81
|
+
@location_list.map{|l| l[1]}.should include('personal')
|
82
|
+
end
|
83
|
+
|
84
|
+
it "sets github url to include 'foo'" do
|
85
|
+
l = @location_list.find {|l| l[1] == "personal" }
|
86
|
+
l.should_not be_nil
|
87
|
+
l[3].should match(/foo/)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,394 @@
|
|
1
|
+
$TESTING=true
|
2
|
+
$:.push File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
|
4
|
+
require 'rspec'
|
5
|
+
require 'textmate'
|
6
|
+
require 'pp'
|
7
|
+
require 'mocha'
|
8
|
+
|
9
|
+
RSPEC_REMOTE_LOCATIONS = [
|
10
|
+
{:name => :'Personal', :short => :personal, :scm => :github, :path => 'Personal path'},
|
11
|
+
{:name => :'GitHub', :short => :github, :scm => :github, :path => 'Github path'},
|
12
|
+
{:name => :'Macromates Trunk', :short => :trunk, :scm => :svn, :path => 'Trunk path'},
|
13
|
+
{:name => :'Macromates Review', :short => :review, :scm => :svn, :path => 'Macromates Review path'},
|
14
|
+
]
|
15
|
+
|
16
|
+
RSPEC_LOCAL_LOCATIONS = [
|
17
|
+
{:name => :User, :short=>:user, :path=>"User path"},
|
18
|
+
{:name => :'User Pristine', :short=>:user_p, :path=>"User Pristine path"},
|
19
|
+
{:name => :System, :short=>:system, :path=>'System path'},
|
20
|
+
{:name => :'System Pristine', :short=>:system_p, :path=>'System Pristine path'},
|
21
|
+
{:name => :Application, :short=>:app, :path=>"Application path"},
|
22
|
+
]
|
23
|
+
|
24
|
+
RSPEC_REMOTE_BUNDLES = {
|
25
|
+
}
|
26
|
+
|
27
|
+
|
28
|
+
RSPEC_LOCAL_BUNDLES = {
|
29
|
+
:user => [
|
30
|
+
{:name=>"user_bundle1"},
|
31
|
+
{:name=>"user_bundle2"},
|
32
|
+
]
|
33
|
+
}
|
34
|
+
|
35
|
+
class String
|
36
|
+
def blank?
|
37
|
+
!!(self =~ /^\s*$/)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# A hack:
|
42
|
+
# Disable Thor [WARNING] Attempted to create task "..." without usage or description
|
43
|
+
class Mocha::AnyInstanceMethod
|
44
|
+
alias original_define_new_method define_new_method
|
45
|
+
def define_new_method
|
46
|
+
if stubbee <= Thor
|
47
|
+
stubbee.class_eval(%{
|
48
|
+
no_tasks do
|
49
|
+
def #{method}(*args, &block)
|
50
|
+
self.class.any_instance.mocha.method_missing(:#{method}, *args, &block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
}, __FILE__, __LINE__)
|
54
|
+
else
|
55
|
+
original_define_new_method
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# def stub_local_bundles(bundles=nil)
|
61
|
+
# stub_local_locations
|
62
|
+
# bundles ||= RSPEC_LOCAL_BUNDLES
|
63
|
+
#
|
64
|
+
# TextMateBundleManager.class_eval <<-END
|
65
|
+
# private
|
66
|
+
#
|
67
|
+
# def bundle_status_table
|
68
|
+
# @bundle_status_table ||= #{bundles.inspect}
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# def bundle_status(path)
|
72
|
+
# basename = File.basename(path, '.*')
|
73
|
+
# res = {}
|
74
|
+
# bundles = get_local_bundle_details(File.dirname(path))
|
75
|
+
# if bundles
|
76
|
+
# res = bundles.find {|b| File.basename(b[:name], '.*') == basename }
|
77
|
+
# res ||= {}
|
78
|
+
# end
|
79
|
+
# res
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# def get_local_bundle_details(path)
|
83
|
+
# l = find_local_location_by(:path, path)
|
84
|
+
# return [] unless l
|
85
|
+
# bundles = bundle_status_table[l[:short]]
|
86
|
+
# return [] unless bundles
|
87
|
+
# bundles
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# def get_local_bundles(path)
|
91
|
+
# get_local_bundle_details(path).map {|b| b[:name] + '.tmbundle'}
|
92
|
+
# end
|
93
|
+
# END
|
94
|
+
# end
|
95
|
+
|
96
|
+
RSpec.configure do |config|
|
97
|
+
config.mock_framework = :mocha
|
98
|
+
|
99
|
+
def capture(stream)
|
100
|
+
begin
|
101
|
+
stream = stream.to_s
|
102
|
+
eval "$#{stream} = StringIO.new"
|
103
|
+
yield
|
104
|
+
result = eval("$#{stream}").string
|
105
|
+
ensure
|
106
|
+
eval("$#{stream} = #{stream.upcase}")
|
107
|
+
end
|
108
|
+
|
109
|
+
result
|
110
|
+
end
|
111
|
+
alias :silence :capture
|
112
|
+
|
113
|
+
def stub_github_user(user)
|
114
|
+
TextMateBundleManager.any_instance.stubs(:current_github_user).returns(user)
|
115
|
+
end
|
116
|
+
|
117
|
+
def stub_local_locations(location_specs=nil)
|
118
|
+
location_specs ||= RSPEC_LOCAL_LOCATIONS
|
119
|
+
TextMateBundleManager.any_instance.stubs(:local_locations).returns(location_specs)
|
120
|
+
location_specs
|
121
|
+
end
|
122
|
+
|
123
|
+
def stub_remote_locations(location_specs=nil)
|
124
|
+
location_specs ||= RSPEC_REMOTE_LOCATIONS
|
125
|
+
TextMateBundleManager.any_instance.stubs(:remote_locations).returns(location_specs)
|
126
|
+
location_specs
|
127
|
+
end
|
128
|
+
|
129
|
+
def bundle_details_by_path(locations, bundles)
|
130
|
+
locations.inject({}) do |hsh, l|
|
131
|
+
location_bundles = bundles[l[:short]]
|
132
|
+
hsh[l[:path]] = location_bundles if location_bundles && location_bundles.size > 0
|
133
|
+
hsh
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def bundle_status_table_for(locations, bundles)
|
138
|
+
bundle_status_table = {}
|
139
|
+
bundle_details_by_path(locations, bundles).each do |path, bundle_detail_array|
|
140
|
+
bundle_detail_array.each do |bundle_detail|
|
141
|
+
name = bundle_detail[:name]
|
142
|
+
name += '.tmbundle' unless name =~ /\./
|
143
|
+
bundle_status_table[File.join(path, name)] = bundle_detail
|
144
|
+
end
|
145
|
+
end
|
146
|
+
bundle_status_table
|
147
|
+
end
|
148
|
+
|
149
|
+
def bundle_names_by_path(locations, bundles)
|
150
|
+
bundle_names_table = {}
|
151
|
+
bundle_details_by_path(locations, bundles).each do |path, bundle_detail_array|
|
152
|
+
bundle_names_table[path] = bundle_detail_array.map do |b|
|
153
|
+
name = b[:name]
|
154
|
+
name += '.tmbundle' unless name =~ /\./
|
155
|
+
name
|
156
|
+
end
|
157
|
+
end
|
158
|
+
bundle_names_table
|
159
|
+
end
|
160
|
+
|
161
|
+
def stub_remote_bundles(locations=nil,bundles=nil)
|
162
|
+
locs = stub_remote_locations(locations)
|
163
|
+
bundles ||= RSPEC_REMOTE_BUNDLES
|
164
|
+
|
165
|
+
# A total hack, but the only way I can figure out to make this work...
|
166
|
+
TextMateBundleManager.class_eval <<-END
|
167
|
+
no_tasks do
|
168
|
+
def get_remote_bundles(path)
|
169
|
+
#{bundle_names_by_path(locs, bundles).inspect}[path] || []
|
170
|
+
end
|
171
|
+
END
|
172
|
+
end
|
173
|
+
|
174
|
+
def stub_local_bundles(locations=nil, bundles=nil)
|
175
|
+
locs = stub_local_locations(locations)
|
176
|
+
bundles ||= RSPEC_LOCAL_BUNDLES
|
177
|
+
|
178
|
+
# A total hack, but the only way I can figure out to make this work...
|
179
|
+
TextMateBundleManager.class_eval <<-END
|
180
|
+
no_tasks do
|
181
|
+
def get_local_bundles(path)
|
182
|
+
#{bundle_names_by_path(locs, bundles).inspect}[path] || []
|
183
|
+
end
|
184
|
+
|
185
|
+
def bundle_status(path)
|
186
|
+
#{bundle_status_table_for(locs, bundles).inspect}[path] || {}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
END
|
190
|
+
end
|
191
|
+
|
192
|
+
module CustomMatchers
|
193
|
+
|
194
|
+
class ArrayRowMatcher
|
195
|
+
def initialize(expected)
|
196
|
+
@expected = expected
|
197
|
+
end
|
198
|
+
|
199
|
+
def matches?(actual)
|
200
|
+
matches_all?(actual)
|
201
|
+
end
|
202
|
+
|
203
|
+
def matches_all?(actual)
|
204
|
+
@actual = actual
|
205
|
+
failed = false
|
206
|
+
actual.each do |row|
|
207
|
+
return false unless matches_row?(row)
|
208
|
+
end
|
209
|
+
return true
|
210
|
+
end
|
211
|
+
|
212
|
+
def matches_row?(row)
|
213
|
+
raise "#{self}#matches_row? not implemented"
|
214
|
+
end
|
215
|
+
|
216
|
+
def format_row_groups(rows)
|
217
|
+
last_row = -1
|
218
|
+
row_groups = []
|
219
|
+
rows.each do |row|
|
220
|
+
if row == last_row + 1
|
221
|
+
if row_groups.size == 0 # Shouldn't happen, but why not?
|
222
|
+
row_groups << [row, row]
|
223
|
+
else
|
224
|
+
last_group = row_groups.pop
|
225
|
+
row_groups << [last_group.first, row]
|
226
|
+
end
|
227
|
+
else
|
228
|
+
row_groups << [row, row]
|
229
|
+
end
|
230
|
+
last_row = row
|
231
|
+
end
|
232
|
+
formatted_groups = row_groups.map do |group|
|
233
|
+
if group.first == group.last
|
234
|
+
group.first.to_s
|
235
|
+
else
|
236
|
+
"#{group.first}..#{group.last}"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
formatted_rows = formatted_groups.join(',')
|
240
|
+
end
|
241
|
+
|
242
|
+
def classify_rows
|
243
|
+
bad_rows = []
|
244
|
+
good_rows = []
|
245
|
+
@actual.each_with_index do |row, i|
|
246
|
+
if matches_row?(row)
|
247
|
+
good_rows << i
|
248
|
+
else
|
249
|
+
bad_rows << i
|
250
|
+
end
|
251
|
+
end
|
252
|
+
[good_rows, bad_rows]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
class AlwaysHasNColumns < ArrayRowMatcher
|
257
|
+
|
258
|
+
def column_counts
|
259
|
+
@actual.map {|row| row.size }
|
260
|
+
end
|
261
|
+
|
262
|
+
def max_columns
|
263
|
+
column_counts.max
|
264
|
+
end
|
265
|
+
|
266
|
+
def min_columns
|
267
|
+
column_counts.min
|
268
|
+
end
|
269
|
+
|
270
|
+
def matches_row?(row)
|
271
|
+
@expected == row.size
|
272
|
+
end
|
273
|
+
|
274
|
+
def failure_message_for_should
|
275
|
+
max = max_columns
|
276
|
+
min = min_columns
|
277
|
+
good_rows, bad_rows = classify_rows
|
278
|
+
if max == min
|
279
|
+
"expected table to have #{@expected} columns. It actually has #{min} columns. Row[0]=#{@actual[0].inspect}\n"
|
280
|
+
elsif bad_rows.size > 0
|
281
|
+
msg = "expected table to have #{@expected} columns. It actually has #{min}..#{max} columns.\n" \
|
282
|
+
"Bad rows: #{format_row_groups(bad_rows)}\n" \
|
283
|
+
"Row[#{bad_rows[0]}] is bad, for instance: #{@actual[bad_rows[0]].inspect}"
|
284
|
+
if good_rows.size > 0
|
285
|
+
good_rows_msg = "\nGood rows: #{format_row_groups(good_rows)}\n" \
|
286
|
+
"Row[#{good_rows[0]}] is good, for instance: #{@actual[good_rows[0]].inspect}"
|
287
|
+
else
|
288
|
+
good_rows_msg = "\nNo good rows"
|
289
|
+
end
|
290
|
+
msg << good_rows_msg
|
291
|
+
else
|
292
|
+
"expected table to have #{@expected} columns. It actually has #{min}..#{max} columns, but I can't find a bad row."
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def failure_message_for_should_not
|
297
|
+
max = max_columns
|
298
|
+
min = min_columns
|
299
|
+
if max == min
|
300
|
+
"expected table not to have #{@expected} columns, but it does. Row[0]=#{@actual[0].inspect}\n"
|
301
|
+
else
|
302
|
+
"expected table not to have #{@expected} columns. It actually has #{min}..#{max} columns, which shouldn't have caused this example to fail."
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def always_have_n_columns(expected)
|
308
|
+
AlwaysHasNColumns.new(expected)
|
309
|
+
end
|
310
|
+
|
311
|
+
class AlwaysMatcher < ArrayRowMatcher
|
312
|
+
|
313
|
+
def matches_row?(row)
|
314
|
+
row.match(@expected)
|
315
|
+
end
|
316
|
+
|
317
|
+
def failure_message_for_should
|
318
|
+
good_rows, bad_rows = classify_rows
|
319
|
+
msg = "expected rows of table to always match #{@expected.inspect}, but it doesn't."
|
320
|
+
if bad_rows.size > 0
|
321
|
+
msg << "\nBad rows: #{format_row_groups(bad_rows)}" \
|
322
|
+
"\nRow[#{bad_rows[0]}] is bad, for instance: #{@actual[bad_rows[0]].inspect}"
|
323
|
+
else
|
324
|
+
msg << "\nNo bad rows."
|
325
|
+
end
|
326
|
+
if good_rows.size > 0
|
327
|
+
msg << "\nGood rows: #{format_row_groups(good_rows)}" \
|
328
|
+
"\nRow[#{good_rows[0]}] is good, for instance: #{@actual[good_rows[0]].inspect}"
|
329
|
+
else
|
330
|
+
msg << "\nNo good rows"
|
331
|
+
end
|
332
|
+
msg
|
333
|
+
end
|
334
|
+
|
335
|
+
def failure_message_for_should_not
|
336
|
+
"expected rows of table to never match #{@expected.inspect}, but they do. Row[0]=#{@actual[0].inspect}"
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def always_match(expected)
|
341
|
+
AlwaysMatcher.new(expected)
|
342
|
+
end
|
343
|
+
|
344
|
+
class AlwaysColumnMatcher < ArrayRowMatcher
|
345
|
+
|
346
|
+
def initialize(n, match)
|
347
|
+
super(match)
|
348
|
+
@n = n
|
349
|
+
end
|
350
|
+
|
351
|
+
def matches_row?(row)
|
352
|
+
# puts "Checking row: #{format_row(row)}"
|
353
|
+
row[@n].match(@expected)
|
354
|
+
end
|
355
|
+
|
356
|
+
def format_row(row)
|
357
|
+
res = []
|
358
|
+
row.each_with_index do |cell, i|
|
359
|
+
res << (i==@n ? "<<#{cell.inspect}>>" : cell.inspect)
|
360
|
+
end
|
361
|
+
"[#{res.join(',')}]"
|
362
|
+
end
|
363
|
+
|
364
|
+
def failure_message_for_should
|
365
|
+
good_rows, bad_rows = classify_rows
|
366
|
+
msg = "expected column #{@n} of table to always match #{@expected.inspect}, but it doesn't."
|
367
|
+
if bad_rows.size > 0
|
368
|
+
msg << "\nBad rows: #{format_row_groups(bad_rows)}" \
|
369
|
+
"\nRow[#{bad_rows[0]}] is bad, for instance: #{format_row(@actual[bad_rows[0]])}"
|
370
|
+
else
|
371
|
+
msg << "\nNo bad rows."
|
372
|
+
end
|
373
|
+
if good_rows.size > 0
|
374
|
+
msg << "\nGood rows: #{format_row_groups(good_rows)}" \
|
375
|
+
"\nRow[#{good_rows[0]}] is good, for instance: #{format_row(@actual[good_rows[0]])}"
|
376
|
+
else
|
377
|
+
msg << "\nNo good rows"
|
378
|
+
end
|
379
|
+
msg
|
380
|
+
end
|
381
|
+
|
382
|
+
def failure_message_for_should_not
|
383
|
+
"expected column #{@n} of table to never match #{@expected.inspect}, but they do. Row[0]=#{format_row(@actual[0])}"
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
def always_match_in_column(n, match)
|
388
|
+
AlwaysColumnMatcher.new(n, match)
|
389
|
+
end
|
390
|
+
|
391
|
+
end
|
392
|
+
config.include(CustomMatchers)
|
393
|
+
|
394
|
+
end
|