ghi 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +180 -0
- data/Manifest.txt +14 -0
- data/README.rdoc +92 -0
- data/bin/ghi +6 -0
- data/lib/ghi.rb +55 -0
- data/lib/ghi/api.rb +106 -0
- data/lib/ghi/cli.rb +608 -0
- data/lib/ghi/issue.rb +29 -0
- data/spec/ghi/api_spec.rb +194 -0
- data/spec/ghi/cli_spec.rb +258 -0
- data/spec/ghi/issue_spec.rb +26 -0
- data/spec/ghi_spec.rb +91 -0
- metadata +70 -0
data/lib/ghi/issue.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class GHI::Issue
|
2
|
+
attr_reader :number, :title, :body, :votes, :state, :user, :created_at,
|
3
|
+
:updated_at
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@number = options["number"]
|
7
|
+
@title = options["title"]
|
8
|
+
@body = options["body"]
|
9
|
+
@votes = options["votes"]
|
10
|
+
@state = options["state"]
|
11
|
+
@user = options["user"]
|
12
|
+
@created_at = options["created_at"]
|
13
|
+
@updated_at = options["updated_at"]
|
14
|
+
end
|
15
|
+
|
16
|
+
#-
|
17
|
+
# REFACTOR: This code is duplicated from cli.rb:gets_from_editor.
|
18
|
+
#+
|
19
|
+
def ==(other_issue)
|
20
|
+
case other_issue
|
21
|
+
when Array
|
22
|
+
other_title = other_issue.first.strip
|
23
|
+
other_body = other_issue[1..-1].join.sub(/\b\n\b/, " ").strip
|
24
|
+
title == other_title && body == other_body
|
25
|
+
else
|
26
|
+
super other_issue
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require "ghi"
|
2
|
+
require "ghi/api"
|
3
|
+
require "ghi/issue"
|
4
|
+
|
5
|
+
ISSUES_YAML = <<-YAML
|
6
|
+
---
|
7
|
+
issues:
|
8
|
+
- number: 1
|
9
|
+
votes: 0
|
10
|
+
created_at: 2009-04-17 14:55:33 -07:00
|
11
|
+
body: my sweet, sweet issue
|
12
|
+
title: new issue
|
13
|
+
updated_at: 2009-04-17 14:55:33 -07:00
|
14
|
+
user: schacon
|
15
|
+
state: open
|
16
|
+
- number: 2
|
17
|
+
votes: 0
|
18
|
+
created_at: 2009-04-17 15:16:47 -07:00
|
19
|
+
body: the body of a second issue
|
20
|
+
title: another issue
|
21
|
+
updated_at: 2009-04-17 15:16:47 -07:00
|
22
|
+
user: schacon
|
23
|
+
state: open
|
24
|
+
YAML
|
25
|
+
|
26
|
+
ISSUE_YAML = <<-YAML
|
27
|
+
---
|
28
|
+
issue:
|
29
|
+
number: 1
|
30
|
+
votes: 0
|
31
|
+
created_at: 2009-04-17 14:55:33 -07:00
|
32
|
+
body: my sweet, sweet issue
|
33
|
+
title: new issue
|
34
|
+
updated_at: 2009-04-17 14:55:33 -07:00
|
35
|
+
user: schacon
|
36
|
+
state: open
|
37
|
+
YAML
|
38
|
+
|
39
|
+
LABELS_YAML = <<-YAML
|
40
|
+
---
|
41
|
+
labels:
|
42
|
+
- testing
|
43
|
+
- test_label
|
44
|
+
YAML
|
45
|
+
|
46
|
+
COMMENT_YAML = <<-YAML
|
47
|
+
---
|
48
|
+
comment:
|
49
|
+
comment: this is amazing
|
50
|
+
status: saved
|
51
|
+
YAML
|
52
|
+
|
53
|
+
describe GHI::API do
|
54
|
+
it "should require user and repo" do
|
55
|
+
proc { GHI::API.new(nil, nil) }.should raise_error(GHI::API::InvalidConnection)
|
56
|
+
proc { GHI::API.new("u", nil) }.should raise_error(GHI::API::InvalidConnection)
|
57
|
+
proc { GHI::API.new(nil, "r") }.should raise_error(GHI::API::InvalidConnection)
|
58
|
+
proc { GHI::API.new("u", "r") }.should_not raise_error(GHI::API::InvalidConnection)
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "requests" do
|
62
|
+
before :all do
|
63
|
+
@api = GHI::API.new "stephencelis", "ghi"
|
64
|
+
GHI.stub!(:login).and_return "stephencelis"
|
65
|
+
GHI.stub!(:token).and_return "token"
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should substitute url tokens" do
|
69
|
+
@api.send(:url, :open).should ==
|
70
|
+
"http://github.com/api/v2/yaml/issues/open/stephencelis/ghi"
|
71
|
+
@api.send(:url, :show, 1).should ==
|
72
|
+
"http://github.com/api/v2/yaml/issues/show/stephencelis/ghi/1"
|
73
|
+
@api.send(:url, :search, :open, "me").should ==
|
74
|
+
"http://github.com/api/v2/yaml/issues/search/stephencelis/ghi/open/me"
|
75
|
+
@api.send(:url, "label/add", "me").should ==
|
76
|
+
"http://github.com/api/v2/yaml/issues/label/add/stephencelis/ghi/me"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should process gets" do
|
80
|
+
url = "http://github.com/api/v2/yaml/issues/open/stephencelis/ghi"
|
81
|
+
query = "?login=stephencelis&token=d1cd249db48d51c9847cbf2b291f5ae9"
|
82
|
+
@api.stub!(:url).and_return url
|
83
|
+
URI.should_receive(:parse).once.with(url + query).and_return("mock")
|
84
|
+
Net::HTTP.should_receive(:get).once.with("mock").and_return ISSUES_YAML
|
85
|
+
@api.list
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should process posts" do
|
89
|
+
url = "http://github.com/api/v2/yaml/issues/open/stephencelis/ghi"
|
90
|
+
query = { :login => "stephencelis",
|
91
|
+
:token => "d1cd249db48d51c9847cbf2b291f5ae9",
|
92
|
+
:title => "Title",
|
93
|
+
:body => "Body" }
|
94
|
+
@api.stub!(:url).and_return url
|
95
|
+
r = mock(Net::HTTPRequest)
|
96
|
+
r.should_receive(:body).once.and_return ISSUE_YAML
|
97
|
+
URI.should_receive(:parse).once.with(url).and_return "u"
|
98
|
+
Net::HTTP.should_receive(:post_form).once.with("u", query).and_return r
|
99
|
+
@api.open "Title", "Body"
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should search open by default" do
|
103
|
+
@api.should_receive(:url).with(:search, :open, "me").and_return "u"
|
104
|
+
Net::HTTP.stub!(:get).and_return ISSUES_YAML
|
105
|
+
issues = @api.search "me"
|
106
|
+
issues.should be_an_instance_of(Array)
|
107
|
+
issues.each { |issue| issue.should be_an_instance_of(GHI::Issue) }
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should search closed" do
|
111
|
+
@api.should_receive(:url).with(:search, :closed, "me").and_return "u"
|
112
|
+
Net::HTTP.stub!(:get).and_return ISSUES_YAML
|
113
|
+
@api.search "me", :closed
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should list open by default" do
|
117
|
+
@api.should_receive(:url).with(:list, :open).and_return "u"
|
118
|
+
Net::HTTP.stub!(:get).and_return ISSUES_YAML
|
119
|
+
issues = @api.list
|
120
|
+
issues.should be_an_instance_of(Array)
|
121
|
+
issues.each { |issue| issue.should be_an_instance_of(GHI::Issue) }
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should list closed" do
|
125
|
+
@api.should_receive(:url).with(:list, :closed).and_return "u"
|
126
|
+
Net::HTTP.stub!(:get).and_return ISSUES_YAML
|
127
|
+
@api.list :closed
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should show" do
|
131
|
+
@api.should_receive(:url).with(:show, 1).and_return "u"
|
132
|
+
Net::HTTP.stub!(:get).and_return ISSUE_YAML
|
133
|
+
@api.show(1).should be_an_instance_of(GHI::Issue)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should open" do
|
137
|
+
@api.should_receive(:url).with(:open).and_return "u"
|
138
|
+
response = mock(Net::HTTPRequest)
|
139
|
+
response.stub!(:body).and_return ISSUE_YAML
|
140
|
+
Net::HTTP.stub!(:post_form).and_return response
|
141
|
+
@api.open("Title", "Body").should be_an_instance_of(GHI::Issue)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should edit" do
|
145
|
+
@api.should_receive(:url).with(:edit, 1).and_return "u"
|
146
|
+
response = mock(Net::HTTPRequest)
|
147
|
+
response.stub!(:body).and_return ISSUE_YAML
|
148
|
+
Net::HTTP.stub!(:post_form).and_return response
|
149
|
+
@api.edit(1, "Title", "Body").should be_an_instance_of(GHI::Issue)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should close" do
|
153
|
+
@api.should_receive(:url).with(:close, 1).and_return "u"
|
154
|
+
response = mock(Net::HTTPRequest)
|
155
|
+
response.stub!(:body).and_return ISSUE_YAML
|
156
|
+
Net::HTTP.stub!(:post_form).and_return response
|
157
|
+
@api.close(1).should be_an_instance_of(GHI::Issue)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should reopen" do
|
161
|
+
@api.should_receive(:url).with(:reopen, 1).and_return "u"
|
162
|
+
response = mock(Net::HTTPRequest)
|
163
|
+
response.stub!(:body).and_return ISSUE_YAML
|
164
|
+
Net::HTTP.stub!(:post_form).and_return response
|
165
|
+
@api.reopen(1).should be_an_instance_of(GHI::Issue)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should add labels" do
|
169
|
+
@api.should_receive(:url).with("label/add", 1, "l").and_return "u"
|
170
|
+
response = mock(Net::HTTPRequest)
|
171
|
+
response.stub!(:body).and_return LABELS_YAML
|
172
|
+
Net::HTTP.stub!(:post_form).and_return response
|
173
|
+
@api.add_label(1, "l").should be_an_instance_of(Array)
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should remove labels" do
|
177
|
+
@api.should_receive(:url).with("label/remove", 1, "l").and_return "u"
|
178
|
+
response = mock(Net::HTTPRequest)
|
179
|
+
response.stub!(:body).and_return LABELS_YAML
|
180
|
+
Net::HTTP.stub!(:post_form).and_return response
|
181
|
+
@api.remove_label(1, "l").should be_an_instance_of(Array)
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should comment" do
|
185
|
+
@api.should_receive(:url).with(:comment, 1).and_return "u"
|
186
|
+
URI.stub!(:parse).and_return "u"
|
187
|
+
response = mock(Net::HTTPRequest)
|
188
|
+
response.stub!(:body).and_return COMMENT_YAML
|
189
|
+
Net::HTTP.should_receive(:post_form).with("u",
|
190
|
+
hash_including(:comment => "Comment")).and_return response
|
191
|
+
@api.comment(1, "Comment").should be_an_instance_of(Hash)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,258 @@
|
|
1
|
+
require "ghi"
|
2
|
+
require "ghi/api"
|
3
|
+
require "ghi/cli"
|
4
|
+
|
5
|
+
describe GHI::CLI::Executable do
|
6
|
+
before :each do
|
7
|
+
@cli = GHI::CLI::Executable.new
|
8
|
+
@cli.stub!(:api).and_return(mock(GHI::API))
|
9
|
+
$stdout.stub! :close_write
|
10
|
+
IO.stub!(:popen).and_return $stdout
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "parsing" do
|
14
|
+
describe "with well-formed arguments" do
|
15
|
+
before :each do
|
16
|
+
@user, @repo = "localuser", "ghi"
|
17
|
+
@action = @state = @number = @term =
|
18
|
+
@title = @body = @tag = @comment = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
after :each do
|
22
|
+
@cli.should_receive(@action)
|
23
|
+
@cli.parse! @args
|
24
|
+
@cli.action.should == @action
|
25
|
+
@cli.state.should == (@state || :open)
|
26
|
+
@cli.number.should == @number
|
27
|
+
@cli.search_term.should == @term
|
28
|
+
@cli.title.should == @title
|
29
|
+
@cli.body.should == @body
|
30
|
+
@cli.user.should == @user
|
31
|
+
@cli.repo.should == @repo
|
32
|
+
@cli.tag.should == @tag
|
33
|
+
if @commenting
|
34
|
+
@cli.should be_commenting
|
35
|
+
else
|
36
|
+
@cli.should_not be_commenting
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should always parse -r" do
|
41
|
+
@args = ["-rremoteuser/remoterepo", "-l"]
|
42
|
+
@action, @state, @user, @repo = :list, :open, "remoteuser", "remoterepo"
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "inside a repository" do
|
46
|
+
after :each do
|
47
|
+
@cli.stub!(:`).and_return "stub@github.com:localuser/ghi.git"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should parse -l as list open" do
|
51
|
+
@args = ["-l"]
|
52
|
+
@action, @state = :list, :open
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should parse -lo as list open" do
|
56
|
+
@args = ["-lo"]
|
57
|
+
@action, @state = :list, :open
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should parse -lc as list closed" do
|
61
|
+
@args = ["-lc"]
|
62
|
+
@action, @state = :list, :closed
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should parse -l2 as show issue 2" do
|
66
|
+
@args = ["-l2"]
|
67
|
+
@action, @number = :show, 2
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should parse -l 'term' as search open for 'term'" do
|
71
|
+
@args = ["-l", "term"]
|
72
|
+
@action, @state, @term = :search, :open, "term"
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should parse -o as open new issue" do
|
76
|
+
@args = ["-o"]
|
77
|
+
@action = :open
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should parse -o 'New Issue' as open issue with title 'New Issue'" do
|
81
|
+
@args = ["-o", "New Issue"]
|
82
|
+
@action, @title = :open, "New Issue"
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should parse -om 'New Issue' as open issue with'New Issue'" do
|
86
|
+
@args = ["-om", "New Issue"]
|
87
|
+
@action, @title = :open, "New Issue"
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should parse -o 'New Issue' -m as open 'New Issue' in $EDITOR" do
|
91
|
+
@args = ["-o", "New Issue", "-m"]
|
92
|
+
@action, @title, @commenting = :open, "New Issue", true
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should parse -o 'Issue' -m 'Body' as open 'Issue' with 'Body'" do
|
96
|
+
@args = ["-o", "Issue", "-m", "Body"]
|
97
|
+
@action, @title, @body = :open, "Issue", "Body"
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should parse -o2 as reopen issue 2" do
|
101
|
+
@args = ["-o2"]
|
102
|
+
@action, @number = :reopen, 2
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should parse -o2 -m as reopen issue 2 with a comment" do
|
106
|
+
@args = ["-o2", "-m"]
|
107
|
+
@action, @number, @commenting = :reopen, 2, true
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should parse -o2 -m 'Comment' as reopen issue 2" do
|
111
|
+
@args = ["-o2", "-m", "Comment"]
|
112
|
+
@action, @number, @body = :reopen, 2, "Comment"
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should parse -ol as list open" do
|
116
|
+
@args = ["-ol"]
|
117
|
+
@action, @state = :list, :open
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should parse -ou as return open issues url" do
|
121
|
+
@args = ["-ou"]
|
122
|
+
@action = :url # Should state be :open?
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should parse -cl as list closed" do
|
126
|
+
@args = ["-cl"]
|
127
|
+
@action, @state = :list, :closed
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should parse -c2 as close issue 2" do
|
131
|
+
@args = ["-c2"]
|
132
|
+
@action, @number = :close, 2
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should parse -c2 -m as close issue 2 with a comment" do
|
136
|
+
@args = ["-c2", "-m"]
|
137
|
+
@action, @number, @commenting = :close, 2, true
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should parse -c2 -m 'Fixed' as close issue 2 with 'Fixed'" do
|
141
|
+
@args = ["-c2", "-m", "Fixed"]
|
142
|
+
@action, @number, @body = :close, 2, "Fixed"
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should parse -m2 -c as close issue 2 with a comment" do
|
146
|
+
@args = ["-m2", "-c"]
|
147
|
+
@action, @number, @commenting = :close, 2, true
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should parse -cu as return closed issues url" do
|
151
|
+
@args = ["-cu"]
|
152
|
+
@action, @state = :url, :closed
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should parse -e2 as edit issue 2" do
|
156
|
+
@args = ["-e2"]
|
157
|
+
@action, @number = :edit, 2
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should parse -rlocalrepo as localuser/localrepo" do
|
161
|
+
GHI.stub!(:login).and_return "localuser"
|
162
|
+
@args = ["-rlocalrepo", "-l"]
|
163
|
+
@action, @state, @user, @repo = :list, :open, "localuser", "localrepo"
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should parse -rremoteuser/remoterepo as remoteuser/remoterepo" do
|
167
|
+
@args = ["-rremoteuser/remoterepo", "-l"]
|
168
|
+
@action, @state, @user, @repo = :list, :open, "remoteuser", "remoterepo"
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should parse -rremoteuser/remoterepo as a later argument" do
|
172
|
+
@args = ["-1", "-rremoteuser/remoterepo"]
|
173
|
+
@action, @number, @user, @repo = :show, 1, "remoteuser", "remoterepo"
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should parse -t2 'tag' as label issue 2 with 'tag'" do
|
177
|
+
@args = ["-t2", "tag"]
|
178
|
+
@action, @number, @tag = :label, 2, "tag"
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should parse -d2 'tag' as remove label 'tag' from issue 2" do
|
182
|
+
@args = ["-d2", "tag"]
|
183
|
+
@action, @number, @tag = :unlabel, 2, "tag"
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should parse -m2 as comment on issue 2" do
|
187
|
+
@args = ["-m2"]
|
188
|
+
@action, @number, @commenting = :comment, 2, true
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should parse -m2 'Comment' as comment 'Comment' on issue 2" do
|
192
|
+
@args = ["-m2", "Comment"]
|
193
|
+
@action, @number, @body = :comment, 2, "Comment"
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should parse -u as return open issues url" do
|
197
|
+
@args = ["-u"]
|
198
|
+
@action = :url # Should state be :open?
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should parse -uo as return open issues url" do
|
202
|
+
@args = ["-uo"]
|
203
|
+
@action = :url # Should state be :open?
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should parse -uc as return open issues url" do
|
207
|
+
@args = ["-uc"]
|
208
|
+
@action, @state = :url, :closed
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should parse -u2 as return issue 2 url" do
|
212
|
+
@args = ["-u2"]
|
213
|
+
@action, @number = :url, 2
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should parse -uu as return unread issues url" do
|
217
|
+
@args = ["-uu"]
|
218
|
+
@action, @state = :url, :unread
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "with malformed arguments" do
|
224
|
+
before :each do
|
225
|
+
@cli.should_receive :warn
|
226
|
+
@cli.should_receive(:exit).with 1
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should exit with -e" do
|
230
|
+
@cli.should_receive :puts
|
231
|
+
@cli.parse! ["-e"]
|
232
|
+
end
|
233
|
+
|
234
|
+
it "should exit with -e 'invalid'" do
|
235
|
+
@cli.should_receive :puts
|
236
|
+
@cli.parse! ["-e", "invalid"]
|
237
|
+
end
|
238
|
+
|
239
|
+
it "should exit with -c as list closed" do
|
240
|
+
@cli.parse! ["-c"]
|
241
|
+
end
|
242
|
+
|
243
|
+
it "should exit with -r" do
|
244
|
+
@cli.should_receive :puts
|
245
|
+
@cli.parse! ["-r"]
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should exit with -t" do
|
249
|
+
@cli.should_receive :puts
|
250
|
+
@cli.parse! ["-t"]
|
251
|
+
end
|
252
|
+
|
253
|
+
it "should exit with -t2" do
|
254
|
+
@cli.parse! ["-t2"]
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|