stephencelis-ghi 0.0.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/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
+