rleber-textmate 0.9.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|