stephencelis-ghi 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/History.rdoc CHANGED
@@ -1,3 +1,15 @@
1
+ === 0.0.4 / 2009-04-24
2
+
3
+ * 1 major enhancement
4
+
5
+ * Cache messages created in $EDITOR.
6
+
7
+ * 2 minor enhancements
8
+
9
+ * Refactoring and cleanup.
10
+ * Change editing messages.
11
+
12
+
1
13
  === 0.0.3 / 2009-04-23
2
14
 
3
15
  * 2 minor enhancements
data/bin/ghi CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "ghi/cli"
4
- GHI::CLI.new
4
+ GHI::CLI::Executable.new
data/lib/ghi/api.rb CHANGED
@@ -2,6 +2,9 @@ require "net/http"
2
2
  require "yaml"
3
3
 
4
4
  class GHI::API
5
+ class InvalidRequest < StandardError
6
+ end
7
+
5
8
  class InvalidConnection < StandardError
6
9
  end
7
10
 
data/lib/ghi/cli.rb CHANGED
@@ -4,234 +4,272 @@ require "ghi"
4
4
  require "ghi/api"
5
5
  require "ghi/issue"
6
6
 
7
- class GHI::CLI
8
- attr_reader :user, :repo, :api, :action, :state, :number, :title,
9
- :search_term
10
-
11
- def initialize
12
- option_parser.parse!(ARGV)
13
-
14
- `git config --get remote.origin.url`.match %r{([^:/]+)/([^/]+).git$}
15
- @user ||= $1
16
- @repo ||= $2
17
- @api = GHI::API.new user, repo
18
-
19
- case action
20
- when :search then search search_term, state
21
- when :list then list state
22
- when :show then show number
23
- when :open then open title
24
- when :edit then edit number
25
- when :close then close number
26
- when :reopen then reopen number
27
- else puts option_parser
28
- end
29
- rescue GHI::API::InvalidConnection
30
- warn "#{File.basename $0}: not a GitHub repo"
31
- rescue GHI::API::ResponseError => e
32
- warn "#{File.basename $0}: #{e.message} (#{user}/#{repo})"
33
- rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
34
- warn "#{File.basename $0}: #{e.message}"
35
- end
7
+ module GHI::CLI #:nodoc:
8
+ module FileHelper
9
+ def launch_editor(file)
10
+ system "#{editor} #{file.path}"
11
+ end
36
12
 
37
- private
38
-
39
- def option_parser
40
- @option_parser ||= OptionParser.new { |opts|
41
- opts.banner = "Usage: #{File.basename $0} [options]"
42
-
43
- opts.on("-l", "--list", "--search", "--show [state|term|number]") do |v|
44
- @action = :list
45
- case v
46
- when nil, /^o$/
47
- @state = :open
48
- when /^\d+$/
49
- @action = :show
50
- @number = v.to_i
51
- when /^c$/
52
- @state = :closed
53
- else
54
- @action = :search
55
- @state ||= :open
56
- @search_term = v
57
- end
13
+ def gets_from_editor(issue)
14
+ if gitdir
15
+ File.open message_path, "a+", &file_proc(issue)
16
+ else
17
+ Tempfile.new message_filename, &file_proc(issue)
58
18
  end
19
+ return @message.shift.strip, @message.join.sub(/\b\n\b/, " ").strip
20
+ end
59
21
 
60
- opts.on("-o", "--open", "--reopen [number]") do |v|
61
- @action = :open
62
- case v
63
- when /^\d+$/
64
- @action = :reopen
65
- @number = v.to_i
66
- when /^l$/
67
- @action = :list
68
- @state = :open
69
- else
70
- @title = v
71
- end
72
- end
22
+ def delete_message
23
+ File.delete message_path
24
+ rescue TypeError
25
+ nil
26
+ end
73
27
 
74
- opts.on("-c", "--closed", "--close [number]") do |v|
75
- case v
76
- when /^\d+$/
77
- @action = :close
78
- @number = v.to_i
79
- when /^l/, nil
80
- @action = :list
81
- @state = :closed
82
- else
83
- raise OptionParser::InvalidOption
84
- end
28
+ def message_path
29
+ File.join gitdir, message_filename
30
+ end
31
+
32
+ private
33
+
34
+ def editor
35
+ ENV["VISUAL"] || ENV["EDITOR"] || "vi"
36
+ end
37
+
38
+ def gitdir
39
+ @gitdir ||= begin
40
+ dirs = []
41
+ Dir.pwd.count("/").times { |n| dirs << ([".."] * n << ".git") * "/" }
42
+ Dir[*dirs].first
85
43
  end
44
+ end
45
+
46
+ def message_filename
47
+ @message_filename ||= "GHI_#{action.to_s.upcase}#{number}_MESSAGE"
48
+ end
49
+
50
+ def file_proc(issue)
51
+ lambda do |file|
52
+ file << edit_format(issue).join("\n") if File.zero? message_path
53
+ file.rewind
54
+ launch_editor file
55
+ @message = File.readlines(file.path).find_all { |l| !l.match(/^#/) }
86
56
 
87
- opts.on("-e", "--edit [number]") do |v|
88
- case v
89
- when /^\d+$/
90
- @action = :edit
91
- @state = :closed
92
- @number = v.to_i
93
- else
94
- raise OptionParser::MissingArgument
57
+ if message.to_s =~ /\A\s*\Z/
58
+ raise GHI::API::InvalidRequest, "can't file empty issue"
95
59
  end
60
+ raise GHI::API::InvalidRequest, "no change" if issue == message
96
61
  end
62
+ end
63
+ end
97
64
 
98
- opts.on("-r", "--repo", "--repository [name]") do |v|
99
- repo = v.split "/"
100
- repo.unshift GHI.login if repo.length == 1
101
- @user, @repo = repo
65
+ module FormattingHelper
66
+ def list_format(issues, term = nil)
67
+ l = if term
68
+ ["# #{state.to_s.capitalize} #{term.inspect} issues on #{user}/#{repo}"]
69
+ else
70
+ ["# #{state.to_s.capitalize} issues on #{user}/#{repo}"]
102
71
  end
103
72
 
104
- opts.on("-V", "--version") do
105
- puts "#{File.basename($0)}: v#{GHI::VERSION}"
106
- exit
73
+ l += unless issues.empty?
74
+ issues.map { |i| " #{i.number.to_s.rjust 3}: #{truncate i.title, 72}" }
75
+ else
76
+ ["none"]
107
77
  end
78
+ end
108
79
 
109
- opts.on("-h", "--help") do
110
- puts opts
111
- exit
112
- end
113
- }
114
- end
80
+ def edit_format(issue)
81
+ l = []
82
+ l << issue.title if issue.title
83
+ l << ""
84
+ l << issue.body if issue.body
85
+ l << "# Please explain the issue. The first line will become the title."
86
+ l << "# Lines beginning '#' will be ignored. Empty issues won't be filed."
87
+ l << "# All line breaks will be honored in accordance with GFM:"
88
+ l << "#"
89
+ l << "# http://github.github.com/github-flavored-markdown"
90
+ l << "#"
91
+ l << "# On #{user}/#{repo}:"
92
+ l << "#"
93
+ l += show_format(issue, false).map { |line| "# #{line}" }
94
+ end
115
95
 
116
- def search(term, state)
117
- issues = api.search term, state
118
- puts "# #{state.to_s.capitalize} #{term.inspect} issues on #{user}/#{repo}"
119
- if issues.empty?
120
- puts "none"
121
- else
122
- puts issues.map { |i| " #{i.number.to_s.rjust(3)}: #{i.title[0,72]}" }
96
+ def show_format(issue, verbose = true)
97
+ l = []
98
+ l << " number: #{issue.number}" if issue.number
99
+ l << " state: #{issue.state}" if issue.state
100
+ l << " title: #{issue.title}" if issue.title
101
+ l << " user: #{issue.user || GHI.login}"
102
+ l << " votes: #{issue.votes}" if issue.votes
103
+ l << " created at: #{issue.created_at}" if issue.created_at
104
+ l << " updated at: #{issue.updated_at}" if issue.updated_at
105
+ return l unless verbose
106
+ l << ""
107
+ l += issue.body.scan(/.{0,75}(?:\s|$)/).map { |line| " #{line}" }
123
108
  end
124
- end
125
109
 
126
- def list(state)
127
- issues = api.list state
128
- puts "# #{state.to_s.capitalize} issues on #{user}/#{repo}"
129
- if issues.empty?
130
- puts "none"
131
- else
132
- puts issues.map { |i| " #{i.number.to_s.rjust(3)}: #{i.title[0,72]}" }
110
+ def action_format(issue)
111
+ key = "#{action.to_s.capitalize.sub(/e?$/, "ed")} issue #{issue.number}"
112
+ "#{key}: #{truncate issue.title, 78 - key.length}"
113
+ end
114
+
115
+ def truncate(string, length)
116
+ result = string.scan(/.{0,#{length}}(?:\s|$)/).first.strip
117
+ result << "..." if result != string
118
+ result
133
119
  end
134
120
  end
135
121
 
136
- def show(number)
137
- issue = api.show number
138
- puts <<-BODY
139
- #{issue.number}: #{issue.title} [#{issue.state}]
122
+ class Executable
123
+ include FileHelper, FormattingHelper
140
124
 
141
- votes: #{issue.votes}
142
- created_at: #{issue.created_at}
143
- updated_at: #{issue.updated_at}
125
+ attr_reader :message, :user, :repo, :api, :action, :state, :number, :title,
126
+ :search_term
144
127
 
145
- #{issue.body}
128
+ def initialize
129
+ option_parser.parse!(ARGV)
146
130
 
147
- -- #{issue.user}
148
- BODY
149
- end
131
+ `git config --get remote.origin.url`.match %r{([^:/]+)/([^/]+).git$}
132
+ @user ||= $1
133
+ @repo ||= $2
134
+ @api = GHI::API.new user, repo
150
135
 
151
- def open(title)
152
- edit = ENV["VISUAL"] || ENV["EDITOR"] || "vi"
153
- temp = Tempfile.open("open-issue-")
154
- temp.write <<-BODY
155
- #{"#{title}\n" unless title.nil?}
156
- # Please explain the issue. The first line will be used as the title.
157
- # Lines with "#" will be ignored, and empty issues will not be filed.
158
- # All line breaks will be honored in accordance with GFM:
159
- #
160
- # http://github.github.com/github-flavored-markdown
161
- #
162
- # On #{user}/#{repo}:
163
- #
164
- # user: #{GHI.login}
165
- BODY
166
- temp.rewind
167
- system "#{edit} #{temp.path}"
168
- lines = File.readlines(temp.path).find_all { |l| !l.match(/^#/) }
169
- temp.close!
170
- if lines.to_s =~ /\A\s*\Z/
171
- warn "can't file empty issue"
136
+ case action
137
+ when :search then search search_term, state
138
+ when :list then list state
139
+ when :show then show number
140
+ when :open then open title
141
+ when :edit then edit number
142
+ when :close then close number
143
+ when :reopen then reopen number
144
+ else puts option_parser
145
+ end
146
+ rescue GHI::API::InvalidConnection
147
+ warn "#{File.basename $0}: not a GitHub repo"
148
+ exit 1
149
+ rescue GHI::API::InvalidRequest, GHI::API::ResponseError => e
150
+ warn "#{File.basename $0}: #{e.message} (#{user}/#{repo})"
151
+ exit 1
152
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
153
+ warn "#{File.basename $0}: #{e.message}"
172
154
  exit 1
173
- else
174
- title = lines.shift.strip
175
- body = lines.join.sub(/\b\n\b/, " ").strip
176
- issue = api.open title, body
177
- puts " Opened issue #{issue.number}: #{issue.title[0,58]}"
178
155
  end
179
- end
180
156
 
181
- def edit(number)
182
- edit = ENV["VISUAL"] || ENV["EDITOR"] || "vi"
183
- begin
184
- temp = Tempfile.open("open-issue-")
185
- issue = api.show number
186
- temp.write <<-BODY
187
- #{issue.title}#{"\n\n" + issue.body unless issue.body.to_s.strip == ""}
188
- # Please explain the issue. The first line will be used as the title.
189
- # Lines with "#" will be ignored, and empty issues will not be filed.
190
- # All line breaks will be honored in accordance with GFM:
191
- #
192
- # http://github.github.com/github-flavored-markdown
193
- #
194
- # On #@user/#@repo:
195
- #
196
- # number: #{issue.number}
197
- # user: #{issue.user}
198
- # votes: #{issue.votes}
199
- # state: #{issue.state}
200
- # created at: #{issue.created_at}
201
- BODY
202
- if issue.updated_at > issue.created_at
203
- temp.write "# updated at: #{issue.updated_at}"
204
- end
205
- temp.rewind
206
- system "#{edit} #{temp.path}"
207
- lines = File.readlines(temp.path)
208
- if temp.readlines == lines
209
- warn "no change"
210
- exit 1
211
- else
212
- lines.reject! { |l| l.match(/^#/) }
213
- if lines.to_s =~ /\A\s*\Z/
214
- warn "can't file empty issue"
215
- exit 1
216
- else
217
- title = lines.shift.strip
218
- body = lines.join.sub(/\b\n\b/, " ").strip
219
- issue = api.edit number, title, body
220
- puts " Updated issue #{issue.number}: #{issue.title[0,58]}"
157
+ private
158
+
159
+ def option_parser
160
+ @option_parser ||= OptionParser.new { |opts|
161
+ opts.banner = "Usage: #{File.basename $0} [options]"
162
+
163
+ opts.on("-l", "--list", "--search", "--show [state|term|number]") do |v|
164
+ @action = :list
165
+ case v
166
+ when nil, /^o$/
167
+ @state = :open
168
+ when /^\d+$/
169
+ @action = :show
170
+ @number = v.to_i
171
+ when /^c$/
172
+ @state = :closed
173
+ else
174
+ @action = :search
175
+ @state ||= :open
176
+ @search_term = v
177
+ end
221
178
  end
222
- end
223
- ensure
224
- temp.close!
179
+
180
+ opts.on("-o", "--open", "--reopen [number]") do |v|
181
+ @action = :open
182
+ case v
183
+ when /^\d+$/
184
+ @action = :reopen
185
+ @number = v.to_i
186
+ when /^l$/
187
+ @action = :list
188
+ @state = :open
189
+ else
190
+ @title = v
191
+ end
192
+ end
193
+
194
+ opts.on("-c", "--closed", "--close [number]") do |v|
195
+ case v
196
+ when /^\d+$/
197
+ @action = :close
198
+ @number = v.to_i
199
+ when /^l/, nil
200
+ @action = :list
201
+ @state = :closed
202
+ else
203
+ raise OptionParser::InvalidOption
204
+ end
205
+ end
206
+
207
+ opts.on("-e", "--edit [number]") do |v|
208
+ case v
209
+ when /^\d+$/
210
+ @action = :edit
211
+ @state = :closed
212
+ @number = v.to_i
213
+ else
214
+ raise OptionParser::MissingArgument
215
+ end
216
+ end
217
+
218
+ opts.on("-r", "--repo", "--repository [name]") do |v|
219
+ repo = v.split "/"
220
+ repo.unshift GHI.login if repo.length == 1
221
+ @user, @repo = repo
222
+ end
223
+
224
+ opts.on("-V", "--version") do
225
+ puts "#{File.basename($0)}: v#{GHI::VERSION}"
226
+ exit
227
+ end
228
+
229
+ opts.on("-h", "--help") do
230
+ puts opts
231
+ exit
232
+ end
233
+ }
225
234
  end
226
- end
227
235
 
228
- def close(number)
229
- issue = api.close number
230
- puts " Closed issue #{issue.number}: #{issue.title[0,58]}"
231
- end
236
+ def search(term, state)
237
+ issues = api.search term, state
238
+ puts list_format(issues, term)
239
+ end
240
+
241
+ def list(state)
242
+ issues = api.list state
243
+ puts list_format(issues)
244
+ end
245
+
246
+ def show(number)
247
+ issue = api.show number
248
+ puts show_format(issue)
249
+ end
232
250
 
233
- def reopen(number)
234
- issue = api.reopen number
235
- puts " Reopened issue #{issue.number}: #{issue.title[0,56]}"
251
+ def open(title)
252
+ title, body = gets_from_editor GHI::Issue.new(:title => title)
253
+ issue = api.open title, body
254
+ delete_message
255
+ puts action_format(issue)
256
+ end
257
+
258
+ def edit(number)
259
+ title, body = gets_from_editor api.show(number)
260
+ issue = api.edit number, title, body
261
+ delete_message
262
+ puts action_format(issue)
263
+ end
264
+
265
+ def close(number)
266
+ issue = api.close number
267
+ puts action_format(issue)
268
+ end
269
+
270
+ def reopen(number)
271
+ issue = api.reopen number
272
+ puts action_format(issue)
273
+ end
236
274
  end
237
275
  end
data/lib/ghi/issue.rb CHANGED
@@ -12,4 +12,18 @@ class GHI::Issue
12
12
  @created_at = options["created_at"]
13
13
  @updated_at = options["updated_at"]
14
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
15
29
  end
data/lib/ghi.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module GHI
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
 
4
4
  def self.login
5
5
  `git config --get github.user`.chomp
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stephencelis-ghi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Celis