stephencelis-ghi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ === 0.0.1 / 2009-04-21
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
data/Manifest.txt ADDED
@@ -0,0 +1,14 @@
1
+ History.rdoc
2
+ Manifest.txt
3
+ MIT-LICENSE
4
+ Rakefile
5
+ README.rdoc
6
+ bin/ghi
7
+ lib/ghi/api.rb
8
+ lib/ghi/cli.rb
9
+ lib/ghi/issue.rb
10
+ lib/ghi.rb
11
+ spec/ghi/api_spec.rb
12
+ spec/ghi/cli_spec.rb
13
+ spec/ghi/issue_spec.rb
14
+ spec/ghi_spec.rb
data/README.rdoc ADDED
@@ -0,0 +1,28 @@
1
+ = ghi
2
+
3
+ http://github.com/stephencelis/ghi
4
+
5
+
6
+ GitHub Issues on the command line. Use your `$EDITOR`, not your browser.
7
+
8
+ == HOW?
9
+
10
+ Get:
11
+
12
+ % gem install stephencelis-ghi --source=http://gems.github.com
13
+
14
+
15
+ Set (http://github.com/blog/180-local-github-config):
16
+
17
+ % git config --global github.user username
18
+ % git config --global github.token 6ef8395fecf207165f1a82178ae1b984
19
+
20
+
21
+ Go:
22
+
23
+ % ghi
24
+ Usage: ghi [options]
25
+ -l, --list, --show [number]
26
+ -o, --open, --reopen [number]
27
+ -c, --closed, --close [number]
28
+ -e, --edit [number]
data/bin/ghi ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "ghi/cli"
4
+ GHI::CLI.new
data/lib/ghi.rb ADDED
@@ -0,0 +1,11 @@
1
+ module GHI
2
+ VERSION = "0.0.1"
3
+
4
+ def self.login
5
+ `git config --get github.user`.chomp
6
+ end
7
+
8
+ def self.token
9
+ `git config --get github.token`.chomp
10
+ end
11
+ end
data/lib/ghi/api.rb ADDED
@@ -0,0 +1,84 @@
1
+ require "net/http"
2
+ require "yaml"
3
+
4
+ class GHI::API
5
+ class InvalidConnection < StandardError
6
+ end
7
+
8
+ class ResponseError < StandardError
9
+ end
10
+
11
+ API_URL = "http://github.com/api/v2/yaml/issues/:action/:user/:repo"
12
+
13
+ attr_reader :user, :repo
14
+
15
+ def initialize(user, repo)
16
+ raise InvalidConnection if user.nil? || repo.nil?
17
+ @user, @repo = user, repo
18
+ end
19
+
20
+ def list(state = :open)
21
+ res = get :list, state
22
+ raise ResponseError, res if res["issues"].nil?
23
+ res["issues"].map { |attrs| GHI::Issue.new(attrs) }
24
+ end
25
+
26
+ def show(number)
27
+ res = get :show, number
28
+ raise ResponseError, res if res["issue"].nil?
29
+ GHI::Issue.new res["issue"]
30
+ end
31
+
32
+ def open(title, body)
33
+ res = post(:open, :title => title, :body => body)
34
+ raise ResponseError, res if res["issue"].nil?
35
+ GHI::Issue.new res["issue"]
36
+ end
37
+
38
+ def edit(number, title, body)
39
+ res = post(:edit, number, :title => title, :body => body)
40
+ raise ResponseError, res if res["issue"].nil?
41
+ GHI::Issue.new res["issue"]
42
+ end
43
+
44
+ def close(number)
45
+ res = post :close, number
46
+ raise ResponseError, res if res["issue"].nil?
47
+ GHI::Issue.new res["issue"]
48
+ end
49
+
50
+ def reopen(number)
51
+ res = post :reopen, number
52
+ raise ResponseError, res if res["issue"].nil?
53
+ GHI::Issue.new res["issue"]
54
+ end
55
+
56
+ private
57
+
58
+ def get(*args)
59
+ res = Net::HTTP.get URI.parse(url(*args) + auth(true))
60
+ YAML.load res
61
+ end
62
+
63
+ def post(*args)
64
+ params = args.last.is_a?(Hash) ? args.pop : {}
65
+ params.update auth
66
+ res = Net::HTTP.post_form URI.parse(url(*args)), params
67
+ YAML.load res.body
68
+ end
69
+
70
+ def auth(query = false)
71
+ if query
72
+ "?login=#{GHI.login}&token=#{GHI.token}"
73
+ else
74
+ { :login => GHI.login, :token => GHI.token }
75
+ end
76
+ end
77
+
78
+ def url(action, option = nil)
79
+ @url ||= API_URL.sub(":user", user).sub(":repo", repo)
80
+ uri = @url.sub ":action", action.to_s
81
+ uri += "/#{option}" unless option.nil?
82
+ uri
83
+ end
84
+ end
data/lib/ghi/cli.rb ADDED
@@ -0,0 +1,214 @@
1
+ require "optparse"
2
+ require "tempfile"
3
+ require "ghi"
4
+ require "ghi/api"
5
+ require "ghi/issue"
6
+
7
+ class GHI::CLI
8
+ def initialize
9
+ `git config --get remote.origin.url`.match %r{([^:/]+)/([^/]+).git$}
10
+ @api = GHI::API.new *(@user, @repo = $1, $2)
11
+
12
+ option_parser.parse!(ARGV)
13
+ case options[:action]
14
+ when :list then list(options[:state])
15
+ when :show then show(options[:number])
16
+ when :open then open(options[:title])
17
+ when :edit then edit(options[:number])
18
+ when :close then close(options[:number])
19
+ when :reopen then reopen(options[:number])
20
+ else puts option_parser
21
+ end
22
+ rescue GHI::API::InvalidConnection
23
+ warn "#{File.basename $0}: not a GitHub repo"
24
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
25
+ warn "#{File.basename $0}: #{e.message}"
26
+ end
27
+
28
+ private
29
+
30
+ def options
31
+ @options ||= {}
32
+ end
33
+
34
+ def option_parser
35
+ @option_parser ||= OptionParser.new { |opts|
36
+ opts.banner = "Usage: #{File.basename $0} [options]"
37
+
38
+ opts.on("-l", "--list", "--show [number]") do |v|
39
+ options[:action] = :list
40
+ case v
41
+ when nil, /^o/
42
+ options[:state] = :open
43
+ when /^\d+$/
44
+ options[:action] = :show
45
+ options[:number] = v.to_i
46
+ when /^c/
47
+ options[:state] = :closed
48
+ else
49
+ raise OptionParser::InvalidOption
50
+ end
51
+ end
52
+
53
+ opts.on("-o", "--open", "--reopen [number]") do |v|
54
+ options[:action] = :open
55
+ case v
56
+ when /^\d+$/
57
+ options[:action] = :reopen
58
+ options[:number] = v.to_i
59
+ when /^l/
60
+ options[:action] = :list
61
+ options[:state] = :open
62
+ else
63
+ options[:title] = v
64
+ end
65
+ end
66
+
67
+ opts.on("-c", "--closed", "--close [number]") do |v|
68
+ case v
69
+ when /^\d+$/
70
+ options[:action] = :close
71
+ options[:number] = v.to_i
72
+ when /^l/
73
+ options[:action] = :list
74
+ options[:state] = :closed
75
+ else
76
+ raise OptionParser::InvalidOption
77
+ end
78
+ end
79
+
80
+ opts.on("-e", "--edit [number]") do |v|
81
+ case v
82
+ when /^\d+$/
83
+ options[:action] = :edit
84
+ options[:state] = :closed
85
+ options[:number] = v.to_i
86
+ else
87
+ raise OptionParser::MissingArgument
88
+ end
89
+ end
90
+
91
+ opts.on("-V", "--version") do
92
+ puts "#{File.basename($0)}: v#{GHI::VERSION}"
93
+ exit
94
+ end
95
+
96
+ opts.on("-h", "--help") do
97
+ puts opts
98
+ exit
99
+ end
100
+ }
101
+ end
102
+
103
+ def list(state)
104
+ issues = @api.list state
105
+ puts "# #{state.to_s.capitalize} issues on #@user/#@repo"
106
+ if issues.empty?
107
+ puts "none"
108
+ else
109
+ puts issues.map { |i| " #{i.number.to_s.rjust(3)}: #{i.title[0,72]}" }
110
+ end
111
+ end
112
+
113
+ def show(number)
114
+ issue = @api.show number
115
+ puts <<-BODY
116
+ #{issue.number}: #{issue.title} [#{issue.state}]
117
+
118
+ votes: #{issue.votes}
119
+ created_at: #{issue.created_at}
120
+ updated_at: #{issue.updated_at}
121
+
122
+ #{issue.body}
123
+
124
+ -- #{issue.user}
125
+ BODY
126
+ end
127
+
128
+ def open(title)
129
+ edit = ENV["VISUAL"] || ENV["EDITOR"] || "vi"
130
+ temp = Tempfile.open("open-issue-")
131
+ temp.write <<-BODY
132
+ #{"#{title}\n" unless title.nil?}
133
+ # Please explain the issue. The first line will be used as the title.
134
+ # Lines with "#" will be ignored, and empty issues will not be filed.
135
+ # All line breaks will be honored in accordance with GFM:
136
+ #
137
+ # http://github.github.com/github-flavored-markdown
138
+ #
139
+ # On #@user/#@repo:
140
+ #
141
+ # user: #{GHI.login}
142
+ BODY
143
+ temp.rewind
144
+ system "#{edit} #{temp.path}"
145
+ lines = File.readlines(temp.path).find_all { |l| !l.match(/^#/) }
146
+ temp.close!
147
+ if lines.to_s =~ /\A\s*\Z/
148
+ warn "can't file empty issue"
149
+ exit 1
150
+ else
151
+ title = lines.shift.strip
152
+ body = lines.join.sub(/\b\n\b/, " ").strip
153
+ issue = @api.open title, body
154
+ puts " Opened issue #{issue.number}: #{issue.title[0,58]}"
155
+ end
156
+ end
157
+
158
+ def edit(number)
159
+ edit = ENV["VISUAL"] || ENV["EDITOR"] || "vi"
160
+ begin
161
+ temp = Tempfile.open("open-issue-")
162
+ issue = @api.show number
163
+ temp.write <<-BODY
164
+ #{issue.title}#{"\n\n" + issue.body unless issue.body.to_s.strip == ""}
165
+ # Please explain the issue. The first line will be used as the title.
166
+ # Lines with "#" will be ignored, and empty issues will not be filed.
167
+ # All line breaks will be honored in accordance with GFM:
168
+ #
169
+ # http://github.github.com/github-flavored-markdown
170
+ #
171
+ # On #@user/#@repo:
172
+ #
173
+ # number: #{issue.number}
174
+ # user: #{issue.user}
175
+ # votes: #{issue.votes}
176
+ # state: #{issue.state}
177
+ # created at: #{issue.created_at}
178
+ BODY
179
+ if issue.updated_at > issue.created_at
180
+ temp.write "# updated at: #{issue.updated_at}"
181
+ end
182
+ temp.rewind
183
+ system "#{edit} #{temp.path}"
184
+ lines = File.readlines(temp.path)
185
+ if temp.readlines == lines
186
+ warn "no change"
187
+ exit 1
188
+ else
189
+ lines.reject! { |l| l.match(/^#/) }
190
+ if lines.to_s =~ /\A\s*\Z/
191
+ warn "can't file empty issue"
192
+ exit 1
193
+ else
194
+ title = lines.shift.strip
195
+ body = lines.join.sub(/\b\n\b/, " ").strip
196
+ issue = @api.edit(number, title, body)
197
+ puts " Updated issue #{issue.number}: #{issue.title[0,58]}"
198
+ end
199
+ end
200
+ ensure
201
+ temp.close!
202
+ end
203
+ end
204
+
205
+ def close(number)
206
+ issue = @api.close number
207
+ puts " Closed issue #{issue.number}: #{issue.title[0,58]}"
208
+ end
209
+
210
+ def reopen(number)
211
+ issue = @api.reopen number
212
+ puts " Reopened issue #{issue.number}: #{issue.title[0,56]}"
213
+ end
214
+ end
data/lib/ghi/issue.rb ADDED
@@ -0,0 +1,15 @@
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
+ end
@@ -0,0 +1,13 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + "/../lib")
2
+ require "ghi"
3
+ require "ghi/api"
4
+ include GHI
5
+
6
+ describe GHI::API do
7
+ it "should require user and repo" do
8
+ proc { API.new(nil, nil) }.should raise_error(API::InvalidConnection)
9
+ proc { API.new("u", nil) }.should raise_error(API::InvalidConnection)
10
+ proc { API.new(nil, "r") }.should raise_error(API::InvalidConnection)
11
+ proc { API.new("u", "r") }.should_not raise_error(API::InvalidConnection)
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + "/../lib")
2
+ require "ghi"
3
+ require "ghi/cli"
4
+ include GHI
5
+
6
+ describe GHI::CLI do
7
+ end
@@ -0,0 +1,7 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + "/../lib")
2
+ require "ghi"
3
+ require "ghi/issue"
4
+ include GHI
5
+
6
+ describe GHI::Issue do
7
+ end
data/spec/ghi_spec.rb ADDED
@@ -0,0 +1,14 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + "/../lib")
2
+ require "ghi"
3
+
4
+ describe GHI do
5
+ it "should return login" do
6
+ GHI.stub!(:`).and_return "stephencelis\n"
7
+ GHI.login.should == "stephencelis"
8
+ end
9
+
10
+ it "should return token" do
11
+ GHI.stub!(:`).and_return "da39a3ee5e6b4b0d3255bfef95601890\n"
12
+ GHI.token.should == "da39a3ee5e6b4b0d3255bfef95601890"
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stephencelis-ghi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Stephen Celis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-20 00:00:00 -07:00
13
+ default_executable: ghi
14
+ dependencies: []
15
+
16
+ description: GitHub Issues on the command line. Use your `$EDITOR`, not your browser.
17
+ email:
18
+ - stephen@stephencelis.com
19
+ executables:
20
+ - ghi
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - History.rdoc
25
+ - Manifest.txt
26
+ - README.rdoc
27
+ files:
28
+ - History.rdoc
29
+ - Manifest.txt
30
+ - README.rdoc
31
+ - bin/ghi
32
+ - lib/ghi/api.rb
33
+ - lib/ghi/cli.rb
34
+ - lib/ghi/issue.rb
35
+ - lib/ghi.rb
36
+ - spec/ghi/api_spec.rb
37
+ - spec/ghi/cli_spec.rb
38
+ - spec/ghi/issue_spec.rb
39
+ - spec/ghi_spec.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/stephencelis/ghi
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --main
45
+ - README.rdoc
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project: ghi
63
+ rubygems_version: 1.2.0
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: GitHub Issues on the command line
67
+ test_files: []
68
+