ghi 0.2.0
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.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
|