ghi 0.9.0.dev1 → 0.9.0.20120627

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,25 +1,33 @@
1
1
  module GHI
2
2
  module Commands
3
3
  class Show < Command
4
+ attr_accessor :web
5
+
4
6
  def options
5
7
  OptionParser.new do |opts|
6
8
  opts.banner = 'usage: ghi show <issueno>'
7
9
  opts.separator ''
10
+ opts.on('-w', '--web') { self.web = true }
8
11
  end
9
12
  end
10
13
 
11
14
  def execute
12
15
  require_issue
13
16
  require_repo
14
- i = throb { api.get "/repos/#{repo}/issues/#{issue}" }.body
15
- page do
16
- puts format_issue(i)
17
- n = i['comments']
18
- if n > 0
19
- puts "#{n} Comment#{'s' unless n == 1}:\n\n"
20
- Comment.execute %W(-l #{issue} -- #{repo})
17
+ options.parse! args
18
+ if web
19
+ Web.new(repo).open "issues/#{issue}"
20
+ else
21
+ i = throb { api.get "/repos/#{repo}/issues/#{issue}" }.body
22
+ page do
23
+ puts format_issue(i)
24
+ n = i['comments']
25
+ if n > 0
26
+ puts "#{n} comment#{'s' unless n == 1}:\n\n"
27
+ Comment.execute %W(-l #{issue} -- #{repo})
28
+ end
29
+ break
21
30
  end
22
- break
23
31
  end
24
32
  end
25
33
  end
@@ -4,7 +4,7 @@ module GHI
4
4
  MAJOR = 0
5
5
  MINOR = 9
6
6
  PATCH = 0
7
- PRE = 'dev1'
7
+ PRE = 20120627
8
8
 
9
9
  VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join '.'
10
10
 
data/lib/ghi/editor.rb CHANGED
@@ -1,26 +1,48 @@
1
- require 'tempfile'
1
+ require 'tmpdir'
2
2
 
3
3
  module GHI
4
4
  class Editor
5
- class << self
6
- def gets prefill
7
- Tempfile.open 'GHI_ISSUE' do |f|
8
- f << prefill
9
- f.rewind
10
- system "#{editor} #{f.path}"
11
- return File.read(f.path).gsub(/^#.*$\n?/, '').strip
12
- end
5
+ attr_reader :filename
6
+ def initialize filename
7
+ @filename = filename
8
+ end
9
+
10
+ def gets prefill
11
+ File.open path, 'a+' do |f|
12
+ f << prefill if File.zero? path
13
+ f.rewind
14
+ system "#{editor} #{f.path}"
15
+ return File.read(f.path).gsub(/(?:^#.*$\n?)+\s*\z/, '').strip
13
16
  end
17
+ end
14
18
 
15
- private
19
+ def unlink message = nil
20
+ File.delete path
21
+ abort message if message
22
+ end
16
23
 
17
- def editor
18
- editor = ENV['GHI_EDITOR']
19
- editor ||= ENV['GIT_EDITOR']
20
- editor ||= `git config core.editor`.split.first
21
- editor ||= ENV['EDITOR']
22
- editor ||= 'vi'
23
- end
24
+ private
25
+
26
+ def editor
27
+ editor = GHI.config 'ghi.editor'
28
+ editor ||= GHI.config 'core.editor'
29
+ editor ||= ENV['VISUAL']
30
+ editor ||= ENV['EDITOR']
31
+ editor ||= 'vi'
32
+ end
33
+
34
+ def path
35
+ File.join dir, filename
36
+ end
37
+
38
+ def dir
39
+ @dir ||= git_dir || Dir.tmpdir
40
+ end
41
+
42
+ def git_dir
43
+ return unless Commands::Command.detected_repo
44
+ dir = `git rev-parse --git-dir 2>/dev/null`.chomp
45
+ dir unless dir.empty?
24
46
  end
25
47
  end
26
48
  end
@@ -4,6 +4,11 @@ require 'erb'
4
4
 
5
5
  module GHI
6
6
  module Formatting
7
+ class << self
8
+ attr_accessor :paginate
9
+ end
10
+ self.paginate = true # Default.
11
+
7
12
  autoload :Colors, 'ghi/formatting/colors'
8
13
  include Colors
9
14
 
@@ -37,9 +42,20 @@ module GHI
37
42
  super strings
38
43
  end
39
44
 
40
- def page header = nil, throttle = 1
41
- $stdout = IO.popen('less -EKrX -b1', 'w') if $stdout == STDOUT
42
- puts header if header
45
+ def page header = nil, throttle = 0
46
+ if paginate?
47
+ pager = GHI.config('ghi.pager') || GHI.config('core.pager')
48
+ pager ||= ENV['PAGER']
49
+ pager ||= 'less'
50
+ pager += ' -EKRX -b1' if pager =~ /^less( -[EKRX]+)?$/
51
+
52
+ if pager && !pager.empty? && pager != 'cat'
53
+ $stdout = IO.popen pager, 'w'
54
+ end
55
+
56
+ puts header if header
57
+ end
58
+
43
59
  loop do
44
60
  yield
45
61
  sleep throttle
@@ -55,6 +71,10 @@ module GHI
55
71
  end
56
72
  end
57
73
 
74
+ def paginate?
75
+ $stdout.tty? && $stdout == STDOUT && Formatting.paginate
76
+ end
77
+
58
78
  def truncate string, reserved
59
79
  result = string.scan(/.{0,#{columns - reserved}}(?:\s|\Z)/).first.strip
60
80
  result << "..." if result != string
@@ -141,18 +161,19 @@ module GHI
141
161
 
142
162
  issues.map { |i|
143
163
  n, title, labels = i['number'], i['title'], i['labels']
144
- l = 8 + nmax + rmax + no_color { format_labels labels }.to_s.length
164
+ l = 9 + nmax + rmax + no_color { format_labels labels }.to_s.length
145
165
  a = i['assignee'] && i['assignee']['login'] == Authorization.username
146
166
  l += 2 if a
147
167
  p = i['pull_request']['html_url'] and l += 2
148
- c = i['comments'] if i['comments'] > 0 and l += 2
168
+ c = i['comments']
169
+ l += c.to_s.length + 1 unless c == 0
149
170
  [
150
171
  " ",
151
172
  (i['repo'].to_s.rjust(rmax) if i['repo']),
152
173
  "#{bright { n.to_s.rjust nmax }}:",
153
174
  truncate(title, l),
154
175
  format_labels(labels),
155
- (fg('aaaaaa') { c } if c),
176
+ (fg('aaaaaa') { c } unless c == 0),
156
177
  (fg('aaaaaa') { '↑' } if p),
157
178
  (fg(:yellow) { '@' } if a)
158
179
  ].compact.join ' '
@@ -161,18 +182,31 @@ module GHI
161
182
 
162
183
  # TODO: Show milestone, number of comments, pull request attached.
163
184
  def format_issue i, width = columns
185
+ return unless i['created_at']
164
186
  ERB.new(<<EOF).result binding
165
187
  <% p = i['pull_request']['html_url'] %>\
166
188
  <%= bright { no_color { indent '%s%s: %s' % [p ? '↑' : '#', \
167
189
  *i.values_at('number', 'title')], 0, width } } %>
168
190
  @<%= i['user']['login'] %> opened this <%= p ? 'pull request' : 'issue' %> \
169
191
  <%= format_date DateTime.parse(i['created_at']) %>. \
170
- <%= format_state i['state'], format_tag(i['state']), :bg %>\
192
+ <%= format_state i['state'], format_tag(i['state']), :bg %> \
193
+ <% unless i['comments'] == 0 %>\
194
+ <%= fg('aaaaaa'){
195
+ template = "%d comment"
196
+ template << "s" unless i['comments'] == 1
197
+ '(' << template % i['comments'] << ')'
198
+ } %>\
199
+ <% end %>\
171
200
  <% if i['assignee'] || !i['labels'].empty? %>
172
201
  <% if i['assignee'] %>@<%= i['assignee']['login'] %> is assigned. <% end %>\
173
202
  <% unless i['labels'].empty? %><%= format_labels(i['labels']) %><% end %>\
203
+ <% end %>\
204
+ <% if i['milestone'] %>
205
+ Milestone #<%= i['milestone']['number'] %>: <%= i['milestone']['title'] %>\
206
+ <%= " \#{bright{fg(:yellow){'⚠'}}}" if past_due? i['milestone'] %>\
174
207
  <% end %>
175
- <% if i['body'] && !i['body'].empty? %>\n<%= indent i['body'], 4, width %>
208
+ <% if i['body'] && !i['body'].empty? %>
209
+ <%= indent i['body'], 4, width %>
176
210
  <% end %>
177
211
 
178
212
  EOF
@@ -201,33 +235,55 @@ EOF
201
235
  }.last['number'].to_s.size
202
236
 
203
237
  milestones.map { |m|
204
- due_on = m['due_on'] && DateTime.parse(m['due_on'])
205
- [
206
- " #{bright { m['number'].to_s.rjust max }}:",
207
- fg((:red if due_on && due_on <= DateTime.now)) {
208
- truncate(m['title'], max + 4)
209
- }
210
- ].compact.join ' '
238
+ line = [" #{m['number'].to_s.rjust max }:"]
239
+ space = past_due?(m) ? 6 : 4
240
+ line << truncate(m['title'], max + space)
241
+ line << '⚠' if past_due? m
242
+ percent m, line.join(' ')
211
243
  }
212
244
  end
213
245
 
214
- def format_milestone m
246
+ def format_milestone m, width = columns
215
247
  ERB.new(<<EOF).result binding
216
248
  <%= bright { no_color { \
217
- indent '#%s: %s' % m.values_at('number', 'title'), 0 } } %>
218
- @<%= m['creator']['login'] %> created this milestone <%= m['created_at'] %>. \
249
+ indent '#%s: %s' % m.values_at('number', 'title'), 0, width } } %>
250
+ @<%= m['creator']['login'] %> created this milestone \
251
+ <%= format_date DateTime.parse(m['created_at']) %>. \
219
252
  <%= format_state m['state'], format_tag(m['state']), :bg %>
220
253
  <% if m['due_on'] %>\
221
254
  <% due_on = DateTime.parse m['due_on'] %>\
222
- Due <%= fg((:red if due_on <= DateTime.now)) { format_date due_on } %>.
255
+ <% if past_due? m %>\
256
+ <%= bright{fg(:yellow){"⚠"}} %> \
257
+ <%= bright{fg(:red){"Past due by \#{format_date due_on, false}."}} %>
258
+ <% else %>\
259
+ Due in <%= format_date due_on, false %>.
260
+ <% end %>\
223
261
  <% end %>\
262
+ <%= percent m %>
224
263
  <% if m['description'] && !m['description'].empty? %>
225
- <%= indent m['description'] %>
264
+ <%= indent m['description'], 4, width %>
226
265
  <% end %>
227
266
 
228
267
  EOF
229
268
  end
230
269
 
270
+ def past_due? milestone
271
+ return false unless milestone['due_on']
272
+ DateTime.parse(milestone['due_on']) <= DateTime.now
273
+ end
274
+
275
+ def percent milestone, string = nil
276
+ open, closed = milestone.values_at('open_issues', 'closed_issues')
277
+ complete = closed.to_f / (open + closed)
278
+ complete = 0 if complete.nan?
279
+ i = (columns * complete).round
280
+ if string.nil?
281
+ string = ' %d%% (%d closed, %d open)' % [complete * 100, closed, open]
282
+ end
283
+ string = string.ljust columns
284
+ [bg('2cc200'){string[0, i]}, string[i, columns - i]].join
285
+ end
286
+
231
287
  def format_state state, string = state, layer = :fg
232
288
  send(layer, state == 'closed' ? 'ff0000' : '2cc200') { string }
233
289
  end
@@ -245,12 +301,15 @@ EOF
245
301
  # Helpers:
246
302
  #++
247
303
 
304
+ #--
305
+ # TODO: DRY up editor formatters.
306
+ #++
248
307
  def format_editor issue = nil
249
308
  message = ERB.new(<<EOF).result binding
250
309
 
251
- Please explain the issue. The first line will become the title. Lines
252
- starting with '#' will be ignored, and empty messages will not be filed.
253
- Issues are formatted with GitHub Flavored Markdown (GFM):
310
+ Please explain the issue. The first line will become the title. Trailing
311
+ lines starting with '#' (like these) will be ignored, and empty messages will
312
+ not be submitted. Issues are formatted with GitHub Flavored Markdown (GFM):
254
313
 
255
314
  http://github.github.com/github-flavored-markdown
256
315
 
@@ -260,20 +319,45 @@ On <%= repo %>
260
319
  EOF
261
320
  message.rstrip!
262
321
  message.gsub!(/(?!\A)^.*$/) { |line| "# #{line}".rstrip }
263
- message.insert 0, [issue['title'], issue['body']].join("\n\n") if issue
322
+ message.insert 0, [
323
+ issue['title'] || issue[:title], issue['body'] || issue[:body]
324
+ ].compact.join("\n\n") if issue
325
+ message
326
+ end
327
+
328
+ def format_milestone_editor milestone = nil
329
+ message = ERB.new(<<EOF).result binding
330
+
331
+ Describe the milestone. The first line will become the title. Trailing lines
332
+ starting with '#' (like these) will be ignored, and empty messages will not be
333
+ submitted. Milestones are formatted with GitHub Flavored Markdown (GFM):
334
+
335
+ http://github.github.com/github-flavored-markdown
336
+
337
+ On <%= repo %>
338
+
339
+ <%= no_color { format_milestone milestone, columns - 2 } if milestone %>
340
+ EOF
341
+ message.rstrip!
342
+ message.gsub!(/(?!\A)^.*$/) { |line| "# #{line}".rstrip }
343
+ message.insert 0, [
344
+ milestone['title'], milestone['description']
345
+ ].join("\n\n") if milestone
264
346
  message
265
347
  end
266
348
 
267
349
  def format_comment_editor issue, comment = nil
268
350
  message = ERB.new(<<EOF).result binding
269
351
 
270
- Leave a comment. Lines starting with '#' will be ignored, and empty messages
271
- will not be sent. Comments are formatted with GitHub Flavored Markdown (GFM):
352
+ Leave a comment. The first line will become the title. Trailing lines starting
353
+ with '#' (like these) will be ignored, and empty messages will not be
354
+ submitted. Comments are formatted with GitHub Flavored Markdown (GFM):
272
355
 
273
356
  http://github.github.com/github-flavored-markdown
274
357
 
275
- On <%= repo %> issue #<%= issue %>
358
+ On <%= repo %> issue #<%= issue['number'] %>
276
359
 
360
+ <%= no_color { format_issue issue } if verbose %>\
277
361
  <%= no_color { format_comment comment, columns - 2 } if comment %>
278
362
  EOF
279
363
  message.rstrip!
@@ -312,19 +396,19 @@ EOF
312
396
  fg(c){'\1' + underline{'\2'} + '\3'}
313
397
  )
314
398
  # Code.
315
- string.gsub!(
316
- /
317
- (^\ {#{indent}}```.*?$)(.+?^\ {#{indent}}```$)|
318
- (^|[^`])(`[^`]+`)([^`]|$)
319
- /mx
320
- ) {
321
- post = $5
322
- fg(c){"#$1#$2#$3#$4".gsub(/\e\[[\d;]+m/, '')} + "#{post}"
323
- }
399
+ # string.gsub!(
400
+ # /
401
+ # (^\ {#{indent}}```.*?$)(.+?^\ {#{indent}}```$)|
402
+ # (^|[^`])(`[^`]+`)([^`]|$)
403
+ # /mx
404
+ # ) {
405
+ # post = $5
406
+ # fg(c){"#$1#$2#$3#$4".gsub(/\e\[[\d;]+m/, '')} + "#{post}"
407
+ # }
324
408
  string
325
409
  end
326
410
 
327
- def format_date date
411
+ def format_date date, suffix = true
328
412
  days = (interval = DateTime.now - date).to_i.abs
329
413
  string = if days.zero?
330
414
  seconds, _ = interval.divmod Rational(1, 86400)
@@ -340,11 +424,12 @@ EOF
340
424
  else
341
425
  "#{days} day#{'s' unless days == 1}"
342
426
  end
343
- [string, interval < 0 ? 'from now' : 'ago'].join ' '
427
+ ago = interval < 0 ? 'from now' : 'ago' if suffix
428
+ [string, ago].compact.join ' '
344
429
  end
345
430
 
346
431
  def throb position = 0, redraw = CURSOR[:up][1]
347
- return yield unless $stdout.tty?
432
+ return yield unless paginate?
348
433
 
349
434
  throb = THROBBERS[rand(THROBBERS.length)]
350
435
  throb.reverse! if rand > 0.5
@@ -352,7 +437,7 @@ EOF
352
437
 
353
438
  thread = Thread.new do
354
439
  dot = lambda do
355
- print("\r#{CURSOR[:column][position]}#{throb[i]}#{CURSOR[:hide]}")
440
+ print "\r#{CURSOR[:column][position]}#{throb[i]}#{CURSOR[:hide]}"
356
441
  i = (i + 1) % throb.length
357
442
  sleep 0.1 and dot.call
358
443
  end
@@ -240,7 +240,7 @@ module GHI
240
240
 
241
241
  def offset hex
242
242
  h, s, l = rgb_to_hsl(to_rgb(WEB[hex.to_s] || hex))
243
- l < 55 && !(40..80).include?(h) ? l *= 1.75 : l /= 2
243
+ l < 55 && !(40..80).include?(h) ? l *= 1.875 : l /= 3
244
244
  hsl_to_rgb([h, s, l]).map { |c| '%02x' % c }.join
245
245
  end
246
246
 
data/lib/ghi/web.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'uri'
2
+
3
+ module GHI
4
+ class Web
5
+ BASE_URI = 'https://github.com/'
6
+
7
+ attr_reader :base
8
+ def initialize base
9
+ @base = base
10
+ end
11
+
12
+ def open path = '', params = {}
13
+ unless params.empty?
14
+ q = params.map { |k, v| "#{CGI.escape k.to_s}=#{CGI.escape v.to_s}" }
15
+ path += "?#{q.join '&'}"
16
+ end
17
+ system "open '#{uri + path}'"
18
+ end
19
+
20
+ private
21
+
22
+ def uri
23
+ URI(BASE_URI) + "#{base}/"
24
+ end
25
+ end
26
+ end
metadata CHANGED
@@ -1,37 +1,57 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: ghi
3
- version: !ruby/object:Gem::Version
4
- hash: -4071658224
5
- prerelease: 6
6
- segments:
7
- - 0
8
- - 9
9
- - 0
10
- - dev
11
- - 1
12
- version: 0.9.0.dev1
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0.20120627
5
+ prerelease:
13
6
  platform: ruby
14
- authors:
7
+ authors:
15
8
  - Stephen Celis
16
9
  autorequire:
17
10
  bindir: bin
18
11
  cert_chain: []
12
+ date: 2012-07-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: ronn
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: ! 'GitHub Issues on the command line. Use your `$EDITOR`, not your browser.
19
47
 
20
- date: 2012-04-05 00:00:00 -07:00
21
- default_executable:
22
- dependencies: []
23
-
24
- description: |
25
- GitHub Issues on the command line. Use your `$EDITOR`, not your browser.
26
-
48
+ '
27
49
  email: stephen@stephencelis.com
28
- executables:
50
+ executables:
29
51
  - ghi
30
52
  extensions: []
31
-
32
53
  extra_rdoc_files: []
33
-
34
- files:
54
+ files:
35
55
  - lib/ghi/authorization.rb
36
56
  - lib/ghi/client.rb
37
57
  - lib/ghi/commands/assign.rb
@@ -52,43 +72,32 @@ files:
52
72
  - lib/ghi/formatting/colors.rb
53
73
  - lib/ghi/formatting.rb
54
74
  - lib/ghi/json.rb
75
+ - lib/ghi/web.rb
55
76
  - lib/ghi.rb
56
77
  - bin/ghi
57
- has_rdoc: true
58
78
  homepage: https://github.com/stephencelis/ghi
59
79
  licenses: []
60
-
61
80
  post_install_message:
62
81
  rdoc_options: []
63
-
64
- require_paths:
82
+ require_paths:
65
83
  - lib
66
- required_ruby_version: !ruby/object:Gem::Requirement
84
+ required_ruby_version: !ruby/object:Gem::Requirement
67
85
  none: false
68
- requirements:
69
- - - ">="
70
- - !ruby/object:Gem::Version
71
- hash: 3
72
- segments:
73
- - 0
74
- version: "0"
75
- required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
91
  none: false
77
- requirements:
78
- - - ">"
79
- - !ruby/object:Gem::Version
80
- hash: 25
81
- segments:
82
- - 1
83
- - 3
84
- - 1
85
- version: 1.3.1
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
86
96
  requirements: []
87
-
88
97
  rubyforge_project:
89
- rubygems_version: 1.6.2
98
+ rubygems_version: 1.8.23
90
99
  signing_key:
91
100
  specification_version: 3
92
101
  summary: GitHub Issues command line interface
93
102
  test_files: []
94
-
103
+ has_rdoc: false