twig 1.2.1 → 1.3
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/HISTORY.md +14 -1
- data/README.md +11 -5
- data/bin/twig +2 -2
- data/bin/twig-gh-open +3 -2
- data/bin/twig-gh-open-issue +2 -1
- data/lib/twig.rb +13 -12
- data/lib/twig/cli.rb +77 -33
- data/lib/twig/display.rb +55 -33
- data/lib/twig/github.rb +10 -7
- data/lib/twig/options.rb +59 -7
- data/lib/twig/util.rb +4 -0
- data/lib/twig/version.rb +1 -1
- data/spec/twig/cli_spec.rb +106 -13
- data/spec/twig/display_spec.rb +131 -23
- data/spec/twig/github_spec.rb +299 -0
- data/spec/twig/options_spec.rb +108 -7
- data/spec/twig/util_spec.rb +24 -0
- data/spec/twig_spec.rb +41 -27
- data/twig.gemspec +4 -3
- metadata +26 -9
@@ -0,0 +1,299 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Twig::GithubRepo do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@github_https_url = 'https://github.com/rondevera/twig.git'
|
7
|
+
# Read-only or read/write
|
8
|
+
@github_git_read_only_url = 'git://github.com/rondevera/twig.git'
|
9
|
+
@github_ssh_read_write_url = 'git@github.com:rondevera/twig.git'
|
10
|
+
|
11
|
+
@generic_https_url = 'https://example.com/rondevera/twig.git'
|
12
|
+
@generic_git_read_only_url = 'git://example.com/rondevera/twig.git'
|
13
|
+
@generic_ssh_read_write_url = 'git@example.com:rondevera/twig.git'
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#initialize' do
|
17
|
+
it 'runs the given block' do
|
18
|
+
Twig.stub(:repo?) { true }
|
19
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @github_ssh_read_write_url }
|
20
|
+
Twig::GithubRepo.any_instance.stub(:username) { 'username' }
|
21
|
+
Twig::GithubRepo.any_instance.stub(:repository) { 'repository' }
|
22
|
+
|
23
|
+
block_has_run = false
|
24
|
+
Twig::GithubRepo.new do |gh_repo|
|
25
|
+
block_has_run = true
|
26
|
+
end
|
27
|
+
|
28
|
+
block_has_run.should be_true
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'aborts if this is not a Git repo' do
|
32
|
+
Twig.stub(:repo?) { false }
|
33
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @github_ssh_read_write_url }
|
34
|
+
Twig::GithubRepo.any_instance.stub(:username) { 'username' }
|
35
|
+
Twig::GithubRepo.any_instance.stub(:repository) { 'repository' }
|
36
|
+
Twig::GithubRepo.any_instance.should_receive(:abort) do |message|
|
37
|
+
message.should include('not a git repository')
|
38
|
+
end
|
39
|
+
|
40
|
+
Twig::GithubRepo.new { |gh_repo| } # Do nothing
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'aborts if the repo origin URL is empty' do
|
44
|
+
Twig.stub(:repo?) { true }
|
45
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { '' }
|
46
|
+
Twig::GithubRepo.any_instance.stub(:username) { 'username' }
|
47
|
+
Twig::GithubRepo.any_instance.stub(:repository) { 'repository' }
|
48
|
+
Twig::GithubRepo.any_instance.should_receive(:abort_for_non_github_repo)
|
49
|
+
|
50
|
+
Twig::GithubRepo.new { |gh_repo| } # Do nothing
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'aborts if the repo username is empty' do
|
54
|
+
Twig.stub(:repo?) { true }
|
55
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @github_ssh_read_write_url }
|
56
|
+
Twig::GithubRepo.any_instance.stub(:username) { '' }
|
57
|
+
Twig::GithubRepo.any_instance.stub(:repository) { 'repository' }
|
58
|
+
Twig::GithubRepo.any_instance.should_receive(:abort_for_non_github_repo)
|
59
|
+
|
60
|
+
Twig::GithubRepo.new { |gh_repo| } # Do nothing
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'aborts if the repo name is empty' do
|
64
|
+
Twig.stub(:repo?) { true }
|
65
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @github_ssh_read_write_url }
|
66
|
+
Twig::GithubRepo.any_instance.stub(:username) { 'username' }
|
67
|
+
Twig::GithubRepo.any_instance.stub(:repository) { '' }
|
68
|
+
Twig::GithubRepo.any_instance.should_receive(:abort_for_non_github_repo)
|
69
|
+
|
70
|
+
Twig::GithubRepo.new { |gh_repo| } # Do nothing
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'aborts if the repo is not hosted by GitHub' do
|
74
|
+
Twig.stub(:repo?) { true }
|
75
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @generic_ssh_read_write_url }
|
76
|
+
Twig::GithubRepo.any_instance.stub(:username) { 'username' }
|
77
|
+
Twig::GithubRepo.any_instance.stub(:repository) { 'repository' }
|
78
|
+
Twig::GithubRepo.any_instance.should_receive(:abort_for_non_github_repo)
|
79
|
+
|
80
|
+
Twig::GithubRepo.new { |gh_repo| } # Do nothing
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#origin_url' do
|
85
|
+
before :each do
|
86
|
+
Twig.stub(:repo?) { true }
|
87
|
+
Twig::GithubRepo.any_instance.stub(:username) { 'username' }
|
88
|
+
Twig::GithubRepo.any_instance.stub(:repository) { 'repository' }
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'gets the origin URL from the repo config' do
|
92
|
+
origin_url = @github_ssh_read_write_url
|
93
|
+
Twig.should_receive(:run).
|
94
|
+
with('git config remote.origin.url').once { origin_url }
|
95
|
+
|
96
|
+
Twig::GithubRepo.new do |gh_repo|
|
97
|
+
2.times { gh_repo.origin_url }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '#origin_url_parts' do
|
103
|
+
before :each do
|
104
|
+
Twig.stub(:repo?) { true }
|
105
|
+
Twig::GithubRepo.any_instance.stub(:username) { 'username' }
|
106
|
+
Twig::GithubRepo.any_instance.stub(:repository) { 'repository' }
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'splits the origin URL into useful parts' do
|
110
|
+
origin_url = @github_ssh_read_write_url
|
111
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { origin_url }
|
112
|
+
|
113
|
+
origin_url_parts = nil
|
114
|
+
Twig::GithubRepo.new do |gh_repo|
|
115
|
+
origin_url_parts = gh_repo.origin_url_parts
|
116
|
+
end
|
117
|
+
|
118
|
+
origin_url_parts.should == %w[
|
119
|
+
git@github.com
|
120
|
+
rondevera
|
121
|
+
twig.git
|
122
|
+
]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe '#github_repo?' do
|
127
|
+
before :each do
|
128
|
+
Twig.stub(:repo?) { true }
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'with a GitHub HTTPS URL' do
|
132
|
+
before :each do
|
133
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @github_https_url }
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'returns true' do
|
137
|
+
is_github_repo = nil
|
138
|
+
Twig::GithubRepo.new do |gh_repo|
|
139
|
+
is_github_repo = gh_repo.github_repo?
|
140
|
+
end
|
141
|
+
|
142
|
+
is_github_repo.should be_true
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'with a GitHub Git read-only URL' do
|
147
|
+
before :each do
|
148
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @github_git_read_only_url }
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'returns true' do
|
152
|
+
is_github_repo = nil
|
153
|
+
Twig::GithubRepo.new do |gh_repo|
|
154
|
+
is_github_repo = gh_repo.github_repo?
|
155
|
+
end
|
156
|
+
|
157
|
+
is_github_repo.should be_true
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'with a GitHub SSH read/write URL' do
|
162
|
+
before :each do
|
163
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @github_ssh_read_write_url }
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'returns true' do
|
167
|
+
is_github_repo = nil
|
168
|
+
Twig::GithubRepo.new do |gh_repo|
|
169
|
+
is_github_repo = gh_repo.github_repo?
|
170
|
+
end
|
171
|
+
|
172
|
+
is_github_repo.should be_true
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'with a generic HTTPS URL' do
|
177
|
+
before :each do
|
178
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @generic_https_url }
|
179
|
+
Twig::GithubRepo.any_instance.stub(:abort_for_non_github_repo)
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'returns false' do
|
183
|
+
is_github_repo = nil
|
184
|
+
Twig::GithubRepo.new do |gh_repo|
|
185
|
+
is_github_repo = gh_repo.github_repo?
|
186
|
+
end
|
187
|
+
|
188
|
+
is_github_repo.should be_false
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'with a generic Git read-only URL' do
|
193
|
+
before :each do
|
194
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @generic_git_read_only_url }
|
195
|
+
Twig::GithubRepo.any_instance.stub(:abort_for_non_github_repo)
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'returns false' do
|
199
|
+
is_github_repo = nil
|
200
|
+
Twig::GithubRepo.new do |gh_repo|
|
201
|
+
is_github_repo = gh_repo.github_repo?
|
202
|
+
end
|
203
|
+
|
204
|
+
is_github_repo.should be_false
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
context 'with a generic SSH read/write URL' do
|
209
|
+
before :each do
|
210
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @generic_ssh_read_write_url }
|
211
|
+
Twig::GithubRepo.any_instance.stub(:abort_for_non_github_repo)
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'returns false' do
|
215
|
+
is_github_repo = nil
|
216
|
+
Twig::GithubRepo.new do |gh_repo|
|
217
|
+
is_github_repo = gh_repo.github_repo?
|
218
|
+
end
|
219
|
+
|
220
|
+
is_github_repo.should be_false
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
describe '#username' do
|
226
|
+
before :each do
|
227
|
+
Twig.stub(:repo?) { true }
|
228
|
+
Twig::GithubRepo.any_instance.stub(:repository) { 'repository' }
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'gets the username for a HTTPS repo' do
|
232
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @github_ssh_read_write_url }
|
233
|
+
username = nil
|
234
|
+
Twig::GithubRepo.new do |gh_repo|
|
235
|
+
username = gh_repo.username
|
236
|
+
end
|
237
|
+
|
238
|
+
username.should == 'rondevera'
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'gets the username for a Git read-only repo' do
|
242
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @github_git_read_only_url }
|
243
|
+
username = nil
|
244
|
+
Twig::GithubRepo.new do |gh_repo|
|
245
|
+
username = gh_repo.username
|
246
|
+
end
|
247
|
+
|
248
|
+
username.should == 'rondevera'
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'gets the username for a SSH read/write repo' do
|
252
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @github_ssh_read_write_url }
|
253
|
+
username = nil
|
254
|
+
Twig::GithubRepo.new do |gh_repo|
|
255
|
+
username = gh_repo.username
|
256
|
+
end
|
257
|
+
|
258
|
+
username.should == 'rondevera'
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
describe '#repository' do
|
263
|
+
before :each do
|
264
|
+
Twig.stub(:repo?) { true }
|
265
|
+
Twig::GithubRepo.any_instance.stub(:username) { 'repository' }
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'gets the repo name for a HTTPS repo' do
|
269
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @github_https_url }
|
270
|
+
repository = nil
|
271
|
+
Twig::GithubRepo.new do |gh_repo|
|
272
|
+
repository = gh_repo.repository
|
273
|
+
end
|
274
|
+
|
275
|
+
repository.should == 'twig'
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'gets the repo name for a Git read-only repo' do
|
279
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @github_git_read_only_url }
|
280
|
+
repository = nil
|
281
|
+
Twig::GithubRepo.new do |gh_repo|
|
282
|
+
repository = gh_repo.repository
|
283
|
+
end
|
284
|
+
|
285
|
+
repository.should == 'twig'
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'gets the repo name for a SSH read/write repo' do
|
289
|
+
Twig::GithubRepo.any_instance.stub(:origin_url) { @github_ssh_read_write_url }
|
290
|
+
repository = nil
|
291
|
+
Twig::GithubRepo.new do |gh_repo|
|
292
|
+
repository = gh_repo.repository
|
293
|
+
end
|
294
|
+
|
295
|
+
repository.should == 'twig'
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
data/spec/twig/options_spec.rb
CHANGED
@@ -12,7 +12,7 @@ describe Twig::Options do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
it 'reads and sets a single option' do
|
15
|
-
@twig.stub(:
|
15
|
+
@twig.stub(:all_branch_names => ['test'])
|
16
16
|
file = double('file')
|
17
17
|
File.should_receive(:readable?).with(Twig::CONFIG_FILE).and_return(true)
|
18
18
|
File.should_receive(:open).with(Twig::CONFIG_FILE).and_yield(file)
|
@@ -25,18 +25,23 @@ describe Twig::Options do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
it 'reads and sets multiple options' do
|
28
|
-
@twig.stub(:
|
28
|
+
@twig.stub(:all_branch_names => ['test'])
|
29
29
|
file = double('file')
|
30
30
|
File.should_receive(:readable?).with(Twig::CONFIG_FILE).and_return(true)
|
31
31
|
File.should_receive(:open).with(Twig::CONFIG_FILE).and_yield(file)
|
32
32
|
file.should_receive(:read).and_return([
|
33
|
+
# Filtering branches:
|
33
34
|
'branch: test',
|
34
|
-
'header-style: green bold',
|
35
35
|
'max-days-old: 30.5',
|
36
36
|
'except-branch: test-except-branch',
|
37
37
|
'only-branch: test-only-branch',
|
38
38
|
'except-foo: test-except-foo',
|
39
|
-
'only-foo: test-only-foo'
|
39
|
+
'only-foo: test-only-foo',
|
40
|
+
|
41
|
+
# Displaying branches:
|
42
|
+
'header-style: green bold',
|
43
|
+
'reverse: true',
|
44
|
+
'foo-width: 4'
|
40
45
|
].join("\n"))
|
41
46
|
|
42
47
|
# Check preconditions
|
@@ -46,6 +51,8 @@ describe Twig::Options do
|
|
46
51
|
@twig.options[:max_days_old].should be_nil
|
47
52
|
@twig.options[:property_except].should be_nil
|
48
53
|
@twig.options[:property_only].should be_nil
|
54
|
+
@twig.options[:property_width].should be_nil
|
55
|
+
@twig.options[:reverse].should be_nil
|
49
56
|
|
50
57
|
@twig.read_config_file!
|
51
58
|
|
@@ -61,6 +68,8 @@ describe Twig::Options do
|
|
61
68
|
:branch => /test-only-branch/,
|
62
69
|
:foo => /test-only-foo/
|
63
70
|
}
|
71
|
+
@twig.options[:property_width].should == { :foo => 4 }
|
72
|
+
@twig.options[:reverse].should be_true
|
64
73
|
end
|
65
74
|
|
66
75
|
it 'skips comments' do
|
@@ -70,7 +79,8 @@ describe Twig::Options do
|
|
70
79
|
file.should_receive(:read).and_return([
|
71
80
|
'# max-days-old: 40',
|
72
81
|
'max-days-old: 30',
|
73
|
-
'# max-days-old: 20'
|
82
|
+
'# max-days-old: 20',
|
83
|
+
' # foo-width: 4'
|
74
84
|
].join("\n"))
|
75
85
|
@twig.options[:max_days_old].should be_nil # Precondition
|
76
86
|
|
@@ -113,7 +123,7 @@ describe Twig::Options do
|
|
113
123
|
|
114
124
|
it 'succeeds' do
|
115
125
|
branch_name = 'foo'
|
116
|
-
@twig.should_receive(:
|
126
|
+
@twig.should_receive(:all_branch_names).and_return(%[foo bar])
|
117
127
|
|
118
128
|
@twig.set_option(:branch, branch_name)
|
119
129
|
|
@@ -122,7 +132,7 @@ describe Twig::Options do
|
|
122
132
|
|
123
133
|
it 'fails if the branch is unknown' do
|
124
134
|
branch_name = 'foo'
|
125
|
-
@twig.should_receive(:
|
135
|
+
@twig.should_receive(:all_branch_names).and_return([])
|
126
136
|
@twig.should_receive(:abort) do |message|
|
127
137
|
message.should include(%{branch "#{branch_name}" could not be found})
|
128
138
|
end
|
@@ -173,6 +183,37 @@ describe Twig::Options do
|
|
173
183
|
@twig.options[:property_only].should == { :branch => /important_prefix_/ }
|
174
184
|
end
|
175
185
|
|
186
|
+
it 'sets a :property_width option' do
|
187
|
+
width = 10
|
188
|
+
@twig.should_receive(:set_property_width_option).with(width)
|
189
|
+
|
190
|
+
@twig.set_option(:property_width, width)
|
191
|
+
end
|
192
|
+
|
193
|
+
context 'when setting a :reverse option' do
|
194
|
+
before :each do
|
195
|
+
@twig.options[:reverse].should be_nil # Precondition
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'sets the option to true when input is truthy' do
|
199
|
+
input = 'yes'
|
200
|
+
Twig::Util.should_receive(:truthy?).with(input).and_call_original
|
201
|
+
|
202
|
+
@twig.set_option(:reverse, input)
|
203
|
+
|
204
|
+
@twig.options[:reverse].should be_true
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'sets the option to false when input is not truthy' do
|
208
|
+
input = 'blargh'
|
209
|
+
Twig::Util.should_receive(:truthy?).with(input).and_call_original
|
210
|
+
|
211
|
+
@twig.set_option(:reverse, input)
|
212
|
+
|
213
|
+
@twig.options[:reverse].should be_false
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
176
217
|
it 'sets an :unset_property option' do
|
177
218
|
@twig.options[:unset_property].should be_nil # Precondition
|
178
219
|
@twig.set_option(:unset_property, 'unwanted_property')
|
@@ -265,6 +306,66 @@ describe Twig::Options do
|
|
265
306
|
end
|
266
307
|
end
|
267
308
|
|
309
|
+
describe '#set_property_width_option' do
|
310
|
+
before :each do
|
311
|
+
@twig.options[:property_width].should be_nil # Precondition
|
312
|
+
end
|
313
|
+
|
314
|
+
it 'succeeds' do
|
315
|
+
@twig.set_option(:property_width, :foo => '20', :bar => '40')
|
316
|
+
@twig.options[:property_width].should == { :foo => 20, :bar => 40 }
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'fails if width is not numeric' do
|
320
|
+
width = 'blargh'
|
321
|
+
@twig.should_receive(:abort) do |message|
|
322
|
+
message.should include("`--branch-width=#{width}` is invalid")
|
323
|
+
abort # Original behavior, but don't show message in test output
|
324
|
+
end
|
325
|
+
|
326
|
+
begin
|
327
|
+
@twig.set_option(:property_width, :branch => width)
|
328
|
+
rescue SystemExit => exception
|
329
|
+
end
|
330
|
+
|
331
|
+
@twig.options[:property_width].should be_nil
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'fails if width is below minimum value' do
|
335
|
+
min_width = Twig::Options::MIN_PROPERTY_WIDTH
|
336
|
+
width = min_width - 1
|
337
|
+
@twig.should_receive(:abort) do |message|
|
338
|
+
message.should include("`--x-width=#{width}` is too low. ")
|
339
|
+
message.should include("The minimum is #{min_width}.")
|
340
|
+
abort
|
341
|
+
end
|
342
|
+
|
343
|
+
begin
|
344
|
+
@twig.set_option(:property_width, :x => width)
|
345
|
+
rescue SystemExit => exception
|
346
|
+
end
|
347
|
+
|
348
|
+
@twig.options[:property_width].should be_nil
|
349
|
+
end
|
350
|
+
|
351
|
+
it 'fails if width is below width of property name' do
|
352
|
+
property_name = :foobarbaz
|
353
|
+
width = property_name.to_s.size - 1
|
354
|
+
@twig.should_receive(:abort) do |message|
|
355
|
+
message.should include("`--#{property_name}-width=#{width}` is too low. ")
|
356
|
+
message.should include(%{The minimum is 9 (width of "#{property_name}")})
|
357
|
+
abort
|
358
|
+
end
|
359
|
+
|
360
|
+
begin
|
361
|
+
@twig.set_option(:property_width, property_name => width)
|
362
|
+
rescue SystemExit => exception
|
363
|
+
end
|
364
|
+
|
365
|
+
@twig.options[:property_width].should be_nil
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
268
369
|
describe '#unset_option' do
|
269
370
|
it 'unsets an option' do
|
270
371
|
@twig.set_option(:max_days_old, 1)
|