ghi 0.3.1 → 0.9.0.dev

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.
metadata CHANGED
@@ -1,70 +1,92 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: ghi
3
- version: !ruby/object:Gem::Version
4
- version: 0.3.1
5
- prerelease:
3
+ version: !ruby/object:Gem::Version
4
+ hash: -1101288818
5
+ prerelease: 6
6
+ segments:
7
+ - 0
8
+ - 9
9
+ - 0
10
+ - dev
11
+ version: 0.9.0.dev
6
12
  platform: ruby
7
- authors:
13
+ authors:
8
14
  - Stephen Celis
9
15
  autorequire:
10
16
  bindir: bin
11
17
  cert_chain: []
12
- date: 2012-01-13 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: rspec
16
- requirement: &70100746900540 !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - <
20
- - !ruby/object:Gem::Version
21
- version: '2.0'
22
- type: :development
23
- prerelease: false
24
- version_requirements: *70100746900540
25
- description: GitHub Issues on the command line. Use your `$EDITOR`, not your browser.
18
+
19
+ date: 2012-04-04 00:00:00 -07:00
20
+ default_executable:
21
+ dependencies: []
22
+
23
+ description: |
24
+ GitHub Issues on the command line. Use your `$EDITOR`, not your browser.
25
+
26
26
  email: stephen@stephencelis.com
27
- executables:
27
+ executables:
28
28
  - ghi
29
29
  extensions: []
30
- extra_rdoc_files:
31
- - README.rdoc
32
- files:
33
- - bin/ghi
34
- - lib/ghi/api.rb
35
- - lib/ghi/cli.rb
36
- - lib/ghi/issue.rb
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/ghi/authorization.rb
35
+ - lib/ghi/client.rb
36
+ - lib/ghi/commands/assign.rb
37
+ - lib/ghi/commands/close.rb
38
+ - lib/ghi/commands/command.rb
39
+ - lib/ghi/commands/comment.rb
40
+ - lib/ghi/commands/config.rb
41
+ - lib/ghi/commands/edit.rb
42
+ - lib/ghi/commands/help.rb
43
+ - lib/ghi/commands/label.rb
44
+ - lib/ghi/commands/list.rb
45
+ - lib/ghi/commands/milestone.rb
46
+ - lib/ghi/commands/open.rb
47
+ - lib/ghi/commands/show.rb
48
+ - lib/ghi/commands/version.rb
49
+ - lib/ghi/commands.rb
50
+ - lib/ghi/formatting/colors.rb
51
+ - lib/ghi/formatting.rb
52
+ - lib/ghi/json.rb
37
53
  - lib/ghi.rb
38
- - spec/ghi/api_spec.rb
39
- - spec/ghi/cli_spec.rb
40
- - spec/ghi/issue_spec.rb
41
- - spec/ghi_spec.rb
42
- - README.rdoc
43
- homepage: http://github.com/stephencelis/ghi
54
+ - bin/ghi
55
+ has_rdoc: true
56
+ homepage: https://github.com/stephencelis/ghi
44
57
  licenses: []
58
+
45
59
  post_install_message:
46
- rdoc_options:
47
- - --main
48
- - README.rdoc
49
- require_paths:
60
+ rdoc_options: []
61
+
62
+ require_paths:
50
63
  - lib
51
- required_ruby_version: !ruby/object:Gem::Requirement
64
+ required_ruby_version: !ruby/object:Gem::Requirement
52
65
  none: false
53
- requirements:
54
- - - ! '>='
55
- - !ruby/object:Gem::Version
56
- version: '0'
57
- required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ hash: 3
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
74
  none: false
59
- requirements:
60
- - - ! '>='
61
- - !ruby/object:Gem::Version
62
- version: '0'
75
+ requirements:
76
+ - - ">"
77
+ - !ruby/object:Gem::Version
78
+ hash: 25
79
+ segments:
80
+ - 1
81
+ - 3
82
+ - 1
83
+ version: 1.3.1
63
84
  requirements: []
85
+
64
86
  rubyforge_project:
65
- rubygems_version: 1.8.11
87
+ rubygems_version: 1.6.2
66
88
  signing_key:
67
89
  specification_version: 3
68
90
  summary: GitHub Issues command line interface
69
91
  test_files: []
70
- has_rdoc: true
92
+
data/README.rdoc DELETED
@@ -1,126 +0,0 @@
1
- = ghi
2
-
3
- http://github.com/stephencelis/ghi
4
-
5
-
6
- GitHub Issues on the command line. Use your <tt>$EDITOR</tt>, not your
7
- browser.
8
-
9
- == HOW?
10
-
11
- Get:
12
-
13
- % gem install ghi
14
-
15
-
16
- Go:
17
-
18
- Usage: ghi [options]
19
- -l, --list [state|term|number]
20
- --search, --show
21
- -v, --verbose
22
- --ssl
23
- -o, --open [title|number]
24
- --reopen
25
- -c, --closed, --close [number]
26
- -e, --edit [number]
27
- -r, --repo, --repository [name]
28
- -m, --comment [number|comment]
29
- -t, --label [number] [label]
30
- --claim [number]
31
- -d, --unlabel [number] [label]
32
- -u, --url [state|number]
33
- --[no-]color
34
- --[no-]pager
35
- -V, --version
36
- -h, --help
37
-
38
-
39
- == EXAMPLE?
40
-
41
- ghi works simply from within a repository. Some short examples:
42
-
43
- ghi -l # Lists all open issues
44
- ghi # Shorter shorthand for "ghi -l"
45
- ghi -v # Lists all open issues, verbosely (includes body)
46
- ghi -lc # Lists all closed issues
47
- ghi -l "doesn't work" # Searches for open issues matching "doesn't work"
48
- ghi -l invalid -c # Searches for closed issues matching "invalid"
49
- ghi -l1 # Shows issue 1
50
- ghi -1 # Shorter shorthand for "ghi -l1"
51
- ghi 1 # Shorter shorthand still
52
- ghi -o # Opens a new issue (in your $EDITOR)
53
- ghi -o "New issue" # Opens a new issue with the title "New issue"
54
- ghi -o "Title" -m "Body" # Opens a new issue with specified title and body
55
- ghi -e1 # Edits issue number 1 (in your $EDITOR)
56
- ghi -e1 -m "New body" # Edits issue number 1 with the specified body
57
- ghi -c1 # Closes issue 1
58
- ghi -c1 -m # Closes issue with comment (from your $EDITOR)
59
- ghi -c1 -m "Comment" # Closes issue with specified comment
60
- ghi -o1 # Reopens 1 (accepts comments, too)
61
- ghi -m1 # Comments on issue 1 (in your $EDITOR)
62
- ghi -t1 "tag" # Labels issue 1 with "tag"
63
- ghi -d1 "tag" # Removes the label, "tag"
64
- ghi --claim 1 # Tags issue 1 with your GitHub username
65
- ghi -u # Loads issues in your browser.
66
- ghi -u1 # Loads an issue in your browser.
67
-
68
-
69
- ghi also works anywhere:
70
-
71
- ghi -rghi # Your fork of "ghi"
72
- ghi -rstephencelis/ghi # Mine: "stephencelis/ghi"
73
- ghi stephencelis/ghi # Shorthand to merely list open.
74
-
75
-
76
- ghi uses ANSI colors if you use them in git.
77
-
78
- ghi looks for a <tt>$GHI_PAGER</tt> variable for paging.
79
-
80
- Always favor SSL by setting it:
81
-
82
- git config --global github.ssl true
83
-
84
-
85
- == CONTRIBUTORS
86
-
87
- * Jamie Macey (http://blog.tracefunc.com)
88
- * Hiroshi Nakamura (http://github.com/nahi)
89
- * David J. Hamilton
90
-
91
-
92
- === CONTRIBUTE?
93
-
94
- Running the tests should be as simple as a `bundle` and
95
- `bundle exec spec spec`.
96
-
97
- ghi is not under currently under the control of any gem packaging system. To
98
- build, use RubyGems:
99
-
100
- % gem build ghi.gemspec
101
- % sudo gem install ghi*.gem
102
-
103
-
104
- == LICENSE
105
-
106
- (The MIT License)
107
-
108
- (c) 2009-* Stephen Celis, stephen@stephencelis.com.
109
-
110
- Permission is hereby granted, free of charge, to any person obtaining a copy
111
- of this software and associated documentation files (the "Software"), to deal
112
- in the Software without restriction, including without limitation the rights
113
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
114
- copies of the Software, and to permit persons to whom the Software is
115
- furnished to do so, subject to the following conditions:
116
-
117
- The above copyright notice and this permission notice shall be included in all
118
- copies or substantial portions of the Software.
119
-
120
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
121
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
122
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
123
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
124
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
125
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
126
- SOFTWARE.
data/lib/ghi/api.rb DELETED
@@ -1,145 +0,0 @@
1
- require "net/https"
2
- require "yaml"
3
- require "cgi"
4
-
5
- class GHI::API
6
- class GHIError < StandardError
7
- end
8
-
9
- class InvalidRequest < GHIError
10
- end
11
-
12
- class InvalidConnection < GHIError
13
- end
14
-
15
- class ResponseError < GHIError
16
- end
17
-
18
- API_HOST = "github.com"
19
- API_PATH = "/api/v2/yaml/issues/:action/:user/:repo"
20
-
21
- attr_reader :user, :repo
22
-
23
- def initialize(user, repo, use_ssl = false)
24
- raise InvalidConnection if user.nil? || repo.nil?
25
- @user, @repo, @use_ssl = user, repo, use_ssl
26
- end
27
-
28
- def search(term, state = :open)
29
- get(:search, state, term)["issues"].map { |attrs| GHI::Issue.new(attrs) }
30
- end
31
-
32
- def list(state = :open)
33
- get(:list, state)["issues"].map { |attrs| GHI::Issue.new(attrs) }
34
- end
35
-
36
- def show(number)
37
- GHI::Issue.new get(:show, number)["issue"]
38
- end
39
-
40
- def comments(number)
41
- get(:comments, number)["comments"]
42
- end
43
-
44
- def open(title, body)
45
- if title.empty? && body.empty?
46
- raise GHIError, "Aborting request due to empty issue."
47
- end
48
- GHI::Issue.new post(:open, "title" => title, "body" => body)["issue"]
49
- end
50
-
51
- def edit(number, title, body)
52
- if title.empty? && body.empty?
53
- raise GHIError, "Aborting request due to empty issue."
54
- end
55
- res = post :edit, number, "title" => title, "body" => body
56
- GHI::Issue.new res["issue"]
57
- end
58
-
59
- def close(number)
60
- GHI::Issue.new post(:close, number)["issue"]
61
- end
62
-
63
- def reopen(number)
64
- GHI::Issue.new post(:reopen, number)["issue"]
65
- end
66
-
67
- def add_label(label, number)
68
- post("label/add", label, number)["labels"]
69
- end
70
-
71
- def remove_label(label, number)
72
- post("label/remove", label, number)["labels"]
73
- end
74
-
75
- def comment(number, comment)
76
- if comment.empty?
77
- raise GHIError, "Aborting request due to empty comment."
78
- end
79
- post(:comment, number, "comment" => comment)["comment"]
80
- end
81
-
82
- private
83
-
84
- def get(*args)
85
- res = nil
86
- http = Net::HTTP.new(API_HOST, @use_ssl ? 443 : 80)
87
- http.use_ssl = true if @use_ssl
88
- http.start do
89
- if @use_ssl
90
- req = Net::HTTP::Post.new path(*args)
91
- req.set_form_data auth
92
- else
93
- req = Net::HTTP::Get.new(path(*args) + auth(true))
94
- end
95
- res = YAML.load http.request(req).body
96
- end
97
-
98
- raise ResponseError, errors(res) if res["error"] || res[:error]
99
- res
100
- rescue ArgumentError, URI::InvalidURIError
101
- raise ResponseError, "GitHub hiccuped on your request"
102
- rescue SocketError
103
- raise ResponseError, "couldn't find the internet"
104
- end
105
-
106
- def post(*args)
107
- params = args.last.is_a?(Hash) ? args.pop : {}
108
-
109
- res = nil
110
- http = Net::HTTP.new(API_HOST, @use_ssl ? 443 : 80)
111
- http.use_ssl = true if @use_ssl
112
- http.start do
113
- req = Net::HTTP::Post.new path(*args)
114
- req.set_form_data params.merge(auth)
115
- res = YAML.load http.request(req).body
116
- end
117
-
118
- raise ResponseError, errors(res) if res["error"] || res[:error]
119
- res
120
- rescue ArgumentError, URI::InvalidURIError
121
- raise ResponseError, "GitHub hiccuped on your request"
122
- rescue SocketError
123
- raise ResponseError, "couldn't find the internet"
124
- end
125
-
126
- def errors(response)
127
- return response[:error] if response.key? :error
128
- [*response["error"]].map { |e| e["error"] } * ", "
129
- end
130
-
131
- def auth(query = false)
132
- if query
133
- "?login=#{GHI.login}&token=#{GHI.token}"
134
- else
135
- { "login" => GHI.login, "token" => GHI.token }
136
- end
137
- end
138
-
139
- def path(action, *args)
140
- @path ||= API_PATH.sub(":user", user).sub(":repo", repo)
141
- path = @path.sub ":action", action.to_s
142
- path << "/#{args.join("/")}" unless args.empty?
143
- path
144
- end
145
- end
data/lib/ghi/cli.rb DELETED
@@ -1,657 +0,0 @@
1
- require "optparse"
2
- require "tempfile"
3
- require "ghi"
4
- require "ghi/api"
5
- require "ghi/issue"
6
-
7
- begin
8
- require "launchy"
9
- rescue LoadError
10
- # No launchy!
11
- end
12
-
13
- module GHI::CLI #:nodoc:
14
- module FileHelper
15
- def launch_editor(file)
16
- system "#{editor} #{file.path}"
17
- end
18
-
19
- def gets_from_editor(issue)
20
- if windows?
21
- warn "Please supply the message with the -m option"
22
- exit 1
23
- end
24
-
25
- if in_repo?
26
- File.open message_path, "a+", &file_proc(issue)
27
- else
28
- Tempfile.open message_filename, &file_proc(issue)
29
- end
30
-
31
- return @message if comment?
32
- return @message.shift.strip, @message.join.sub(/\b\n\b/, " ").strip
33
- end
34
-
35
- def delete_message
36
- File.delete message_path
37
- rescue Errno::ENOENT, TypeError
38
- nil
39
- end
40
-
41
- def message_path
42
- File.join gitdir, message_filename
43
- end
44
-
45
- private
46
-
47
- def editor
48
- ENV["GHI_EDITOR"] || ENV["GIT_EDITOR"] ||
49
- `git config --get-all core.editor`.split.first || ENV["EDITOR"] || "vi"
50
- end
51
-
52
- def gitdir
53
- @gitdir ||= `git rev-parse --git-dir 2>/dev/null`.chomp
54
- end
55
-
56
- def message_filename
57
- @message_filename ||= "GHI_#{action.to_s.upcase}#{number}_MESSAGE"
58
- end
59
-
60
- def file_proc(issue)
61
- lambda do |file|
62
- file << edit_format(issue).join("\n") if File.zero? file.path
63
- file.rewind
64
- launch_editor file
65
- @message = File.readlines(file.path).find_all { |l| !l.match(/^#/) }
66
-
67
- if message.to_s =~ /\A\s*\Z/
68
- raise GHI::API::InvalidRequest, "can't file empty message"
69
- end
70
- raise GHI::API::InvalidRequest, "no change" if issue == message
71
- end
72
- end
73
-
74
- def in_repo?
75
- !gitdir.empty? && user == local_user && repo == local_repo
76
- end
77
- end
78
-
79
- module FormattingHelper
80
- def list_header(term = nil)
81
- if term
82
- "# #{state.to_s.capitalize} #{term.inspect} issues on #{user}/#{repo}"
83
- else
84
- "# #{state.to_s.capitalize} issues on #{user}/#{repo}"
85
- end
86
- end
87
-
88
- def list_format(issues, verbosity = nil)
89
- unless issues.empty?
90
- if verbosity
91
- issues.map { |i| ["=" * 79] + show_format(i) }
92
- else
93
- issues.map { |i| " #{i.number.to_s.rjust 3}: #{truncate(i.title, 72)} #{label_format(i.labels)}" }
94
- end
95
- else
96
- "none"
97
- end
98
- end
99
-
100
- def label_format(labels)
101
- labels and labels.map {|l| "[#{l}]"}.join(' ')
102
- end
103
-
104
- def edit_format(issue)
105
- l = []
106
- l << issue.title if issue.title && !comment?
107
- l << ""
108
- l << issue.body if issue.body && !comment?
109
- if comment?
110
- l << "# Please enter your comment."
111
- else
112
- l << "# Please explain the issue. The first line will become the title."
113
- end
114
- l << "# Lines beginning '#' will be ignored; ghi aborts empty messages."
115
- l << "# All line breaks will be honored in accordance with GFM:"
116
- l << "#"
117
- l << "# http://github.github.com/github-flavored-markdown"
118
- l << "#"
119
- l << "# On #{user}/#{repo}:"
120
- l << "#"
121
- l += show_format(issue, false).map { |line| "# #{line}" }
122
- end
123
-
124
- def show_format(issue, verbose = true)
125
- l = []
126
- l << " number: #{issue.number}" if issue.number
127
- l << " state: #{issue.state}" if issue.state
128
- l << " title: #{indent(issue.title, 15, 0).join}" if issue.title
129
- l << " labels: #{label_format(issue.labels)}" unless issue.labels.nil? || issue.labels.empty?
130
- l << " user: #{issue.user || GHI.login}"
131
- l << " votes: #{issue.votes}" if issue.votes
132
- l << " created at: #{issue.created_at}" if issue.created_at
133
- l << " updated at: #{issue.updated_at}" if issue.updated_at
134
- return l unless verbose
135
- l << ""
136
- l += indent(issue.body)[0..-2]
137
-
138
- comments = api.comments(issue.number)
139
- unless comments.empty?
140
- l << "=" * 79
141
- comments.each do |c|
142
- l << "#{c["user"]} commented:"
143
- l << ""
144
- l += indent(c["body"])
145
- l << "-" * 79
146
- end
147
- end
148
-
149
- l
150
- end
151
-
152
- def action_format(value = nil)
153
- key = "#{action.to_s.capitalize.sub(/e?$/, "ed")} issue #{number}"
154
- "#{key}: #{truncate(value.to_s, 78 - key.length)}"
155
- end
156
-
157
- def truncate(string, length)
158
- result = string.scan(/.{0,#{length - 3}}(?:\s|\Z)/).first.strip
159
- result << "..." if result != string
160
- result
161
- end
162
-
163
- def indent(string, level = 4, first = level)
164
- string = string.gsub(/\n{3,}/, "\n\n")
165
- lines = string.scan(/.{0,#{79 - level}}(?:\s|\Z)/).map { |line|
166
- " " * level + line
167
- }
168
- lines.first.sub!(/^\s+/) {} if first != level
169
- lines
170
- end
171
-
172
- private
173
-
174
- def comment?
175
- ![:open, :edit].include?(action)
176
- end
177
-
178
- def puts(*args)
179
- args = args.flatten.each { |arg|
180
- arg.gsub!(/\b\*(.+)\*\b/) { "\e[1m#$1\e[0m" } # Bold
181
- arg.gsub!(/\b_(.+)_\b/) { "\e[4m#$1\e[0m" } # Underline
182
- arg.gsub!(/(state:)?(# Open.*| open)$/) { "#$1\e[32m#$2\e[0m" }
183
- arg.gsub!(/(state:)?(# Closed.*| closed)$/) { "#$1\e[31m#$2\e[0m" }
184
- marked = [GHI.login, search_term, tag, "(?:#|gh)-\d+"].compact * "|"
185
- unless arg.include? "\e"
186
- arg.gsub!(/(#{marked})/i) { "\e[1;4;33m#{$&}\e[0m" }
187
- end
188
- } if colorize?
189
- rescue NoMethodError
190
- # Do nothing.
191
- ensure
192
- $stdout.puts(*args)
193
- end
194
-
195
- def colorize?
196
- return @colorize if defined? @colorize
197
- @colorize = if $stdout.isatty && !windows?
198
- !`git config --get-regexp color`.chomp.empty?
199
- else
200
- false
201
- end
202
- end
203
-
204
- def prepare_stdout
205
- return if @prepared || @no_pager || !$stdout.isatty || pager.nil?
206
- colorize? # Check for colorization.
207
- $stdout = pager
208
- @prepared = true
209
- end
210
-
211
- def pager
212
- return @pager if defined? @pager
213
- pager = ENV["GHI_PAGER"] || ENV["GIT_PAGER"] ||
214
- `git config --get-all core.pager`.split.first || ENV["PAGER"] ||
215
- "less -EMRX"
216
-
217
- @pager = IO.popen(pager, "w")
218
- end
219
-
220
- def windows?
221
- RUBY_PLATFORM.include? "mswin"
222
- end
223
- end
224
-
225
- class Executable
226
- include FileHelper, FormattingHelper
227
-
228
- attr_reader :message, :local_user, :local_repo, :user, :repo, :api,
229
- :action, :search_term, :number, :title, :body, :tag, :args, :verbosity,
230
- :use_ssl
231
-
232
- def parse!(*argv)
233
- @args, @argv = argv, argv.dup
234
-
235
- remotes = `git config --get-regexp remote\..+\.url`.split /\n/
236
- repo_expression = %r{([^:/]+)/([^/\s]+?)(?:\.git)?$}
237
- if remote = remotes.find { |r| r.include? "github.com" }
238
- remote.match repo_expression
239
- @user, @repo = $1, $2
240
- end
241
-
242
- option_parser.parse!(*args)
243
-
244
- if action.nil? && fallback_parsing(*args).nil?
245
- puts option_parser
246
- exit
247
- end
248
- rescue OptionParser::InvalidOption, OptionParser::InvalidArgument => e
249
- if fallback_parsing(*e.args).nil?
250
- warn "#{File.basename $0}: #{e.message}"
251
- puts option_parser
252
- exit 1
253
- end
254
- rescue OptionParser::MissingArgument, OptionParser::AmbiguousOption => e
255
- warn "#{File.basename $0}: #{e.message}"
256
- puts option_parser
257
- exit 1
258
- ensure
259
- run!
260
- $stdout.close_write
261
- end
262
-
263
- def run!
264
- @api = GHI::API.new user, repo, use_ssl
265
-
266
- case action
267
- when :search then search
268
- when :list then list
269
- when :show then show
270
- when :open then open
271
- when :edit then edit
272
- when :close then close
273
- when :reopen then reopen
274
- when :comment then prepare_comment && comment
275
- when :label, :claim then prepare_label && label
276
- when :unlabel then prepare_label && unlabel
277
- when :url then url
278
- end
279
- rescue GHI::API::InvalidConnection
280
- if action
281
- code = 1
282
- warn "#{File.basename $0}: not a GitHub repo"
283
- puts option_parser if args.flatten.empty?
284
- exit 1
285
- end
286
- rescue GHI::API::InvalidRequest => e
287
- warn "#{File.basename $0}: #{e.message} (#{user}/#{repo})"
288
- delete_message
289
- exit 1
290
- rescue GHI::API::ResponseError => e
291
- warn "#{File.basename $0}: #{e.message} (#{user}/#{repo})"
292
- exit 1
293
- rescue GHI::API::GHIError => e
294
- warn e.message
295
- exit 1
296
- end
297
-
298
- def commenting?
299
- @commenting
300
- end
301
-
302
- def state
303
- @state || :open
304
- end
305
-
306
- private
307
-
308
- def option_parser
309
- @option_parser ||= OptionParser.new { |opts|
310
- opts.banner = "Usage: #{File.basename $0} [options]"
311
-
312
- opts.on("-l", "--list", "--search", "--show [state|term|number]") do |v|
313
- @action = :list
314
- case v
315
- when nil, /^o(?:pen)?$/
316
- # Defaults.
317
- when /^\d+$/
318
- @action = :show
319
- @number = v.to_i
320
- when /^c(?:losed)?$/
321
- @state = :closed
322
- when /^[uw]$/
323
- @action = :url
324
- when /^v$/
325
- @verbosity = true
326
- else
327
- @action = :search
328
- @search_term = v
329
- end
330
- end
331
-
332
- opts.on("-v", "--verbose") do |v|
333
- if v
334
- @action ||= :list
335
- @verbosity = true
336
- end
337
- end
338
-
339
- opts.on("--ssl") do |ssl|
340
- @use_ssl = true
341
- end
342
-
343
- opts.on("-o", "--open", "--reopen [title|number]") do |v|
344
- @action = :open
345
- case v
346
- when /^\d+$/
347
- @action = :reopen
348
- @number = v.to_i
349
- when /^l$/
350
- @action = :list
351
- when /^m$/
352
- @title = args * " "
353
- when /^[uw]$/
354
- @action = :url
355
- else
356
- @title = v
357
- end
358
- end
359
-
360
- opts.on("-c", "--closed", "--close [number]") do |v|
361
- case v
362
- when /^\d+$/
363
- @action = :close
364
- @number = v.to_i unless v.nil?
365
- when /^l$/
366
- @action = :list
367
- @state = :closed
368
- when /^[uw]$/
369
- @action = :url
370
- @state = :closed
371
- when nil
372
- if @action.nil? || @number
373
- @action = :close
374
- else
375
- @state = :closed
376
- end
377
- else
378
- raise OptionParser::InvalidArgument
379
- end
380
- end
381
-
382
- opts.on("-e", "--edit [number]") do |v|
383
- case v
384
- when /^\d+$/
385
- @action = :edit
386
- @number = v.to_i
387
- when nil
388
- raise OptionParser::MissingArgument
389
- else
390
- raise OptionParser::InvalidArgument
391
- end
392
- end
393
-
394
- opts.on("-r", "--repo", "--repository [name]") do |v|
395
- case v
396
- when nil
397
- raise OptionParser::MissingArgument
398
- else
399
- repo = v.split "/"
400
- if repo.length == 1
401
- if @repo && `git remote 2>/dev/null`[/^#{repo}$/]
402
- repo << @repo
403
- else
404
- repo.unshift(GHI.login)
405
- end
406
- end
407
- @user, @repo = repo
408
- end
409
- end
410
-
411
- opts.on("-m", "--comment [number|comment]") do |v|
412
- case v
413
- when /^\d+$/, nil
414
- @action ||= :comment
415
- @number ||= v.to_i unless v.nil?
416
- @commenting = true
417
- else
418
- @body = v
419
- end
420
- end
421
-
422
- opts.on("-t", "--label [number] [label]") do |v|
423
- raise OptionParser::MissingArgument if v.nil?
424
- @action ||= :label
425
- @number = v.to_i
426
- end
427
-
428
- opts.on("--claim [number]") do |v|
429
- raise OptionParser::MissingArgument if v.nil?
430
- @action = :claim
431
- @number = v.to_i
432
- @tag = GHI.login
433
- end
434
-
435
- opts.on("-d", "--unlabel [number] [label]") do |v|
436
- @action = :unlabel
437
- case v
438
- when /^\d+$/
439
- @number = v.to_i
440
- when /^\w+$/
441
- @tag = v
442
- end
443
- end
444
-
445
- opts.on("-u", "-w", "--url", "--web [state|number]") do |v|
446
- @action = :url
447
- case v
448
- when /^\d+$/
449
- @number = v.to_i
450
- when /^c(?:losed)?$/
451
- @state = :closed
452
- when /^u(?:nread)?$/
453
- @state = :unread
454
- end
455
- end
456
-
457
- opts.on("--[no-]color") do |v|
458
- @colorize = v
459
- end
460
-
461
- opts.on("--[no-]pager") do |v|
462
- @no_pager = (v == false)
463
- end
464
-
465
- opts.on_tail("-V", "--version") do
466
- puts "#{File.basename($0)}: v#{GHI::VERSION}"
467
- exit
468
- end
469
-
470
- opts.on_tail("-h", "--help") do
471
- puts opts
472
- exit
473
- end
474
- }
475
- end
476
-
477
- def search
478
- prepare_stdout
479
- puts list_header(search_term)
480
- issues = api.search search_term, state
481
- puts list_format(issues, verbosity)
482
- end
483
-
484
- def list
485
- prepare_stdout
486
- puts list_header
487
- issues = api.list(state)
488
- puts list_format(issues, verbosity)
489
- end
490
-
491
- def show
492
- prepare_stdout
493
- issue = api.show number
494
- puts show_format(issue)
495
- end
496
-
497
- def open
498
- if title.nil?
499
- new_title, new_body = gets_from_editor GHI::Issue.new("title" => body)
500
- elsif @commenting && body.nil?
501
- new_title, new_body = gets_from_editor GHI::Issue.new("title" => title)
502
- end
503
- new_title ||= title
504
- new_body ||= body
505
- issue = api.open new_title, new_body
506
- delete_message
507
- @number = issue.number
508
- puts action_format(issue.title)
509
- end
510
-
511
- def edit
512
- shown = api.show number
513
- new_title, new_body = gets_from_editor(shown) if body.nil?
514
- new_title ||= shown.title
515
- new_body ||= body
516
- issue = api.edit number, new_title, new_body
517
- delete_message
518
- puts action_format(issue.title)
519
- end
520
-
521
- def close
522
- raise GHI::API::InvalidRequest, "need a number" if number.nil?
523
- issue = api.close number
524
- if @commenting || new_body = body
525
- new_body ||= gets_from_editor issue
526
- comment = api.comment number, new_body
527
- end
528
- puts action_format(issue.title)
529
- puts "(commented)" if comment
530
- end
531
-
532
- def reopen
533
- issue = api.reopen number
534
- if @commenting || new_body = body
535
- new_body ||= gets_from_editor issue
536
- comment = api.comment number, new_body
537
- end
538
- puts action_format(issue.title)
539
- puts "(commented)" if comment
540
- end
541
-
542
- def prepare_label
543
- @tag ||= (body || args * " ")
544
- raise GHI::API::InvalidRequest, "need a label" if @tag.empty?
545
- true
546
- end
547
-
548
- def label
549
- labels = api.add_label tag, number
550
- puts action_format
551
- puts indent(labels.join(", "))
552
- end
553
-
554
- def unlabel
555
- labels = api.remove_label tag, number
556
- puts action_format
557
- puts indent(labels.empty? ? "no labels" : labels.join(", "))
558
- end
559
-
560
- def prepare_comment
561
- @body = args.flatten.first
562
- @commenting = false unless body.nil?
563
- true
564
- end
565
-
566
- def comment
567
- @body ||= gets_from_editor api.show(number)
568
- comment = api.comment(number, body)
569
- delete_message
570
- puts "(comment})"
571
- end
572
-
573
- def url
574
- url = "https://github.com/#{user}/#{repo}/issues"
575
- if number.nil?
576
- url << "/#{state}" unless state == :open
577
- else
578
- url << "/#{number}"
579
- end
580
- defined?(Launchy) ? Launchy.open(url) : puts(url)
581
- end
582
-
583
- #-
584
- # Because these are mere fallbacks, any options used earlier will muddle
585
- # things: `ghi list` will work, `ghi list -c` will not.
586
- #
587
- # Argument parsing will have to better integrate with option parsing to
588
- # overcome this.
589
- #+
590
- def fallback_parsing(*arguments)
591
- arguments = arguments.flatten
592
- case command = arguments.shift
593
- when nil, "list"
594
- @action = :list
595
- if arg = arguments.shift
596
- @state ||= arg.to_sym if %w(open closed).include? arg
597
- @user, @repo = arg.split "/" if arg.count("/") == 1
598
- end
599
- when "search"
600
- @action = :search
601
- @search_term ||= arguments.shift
602
- when "show", /^-?(\d+)$/
603
- @action = :show
604
- @number ||= ($1 || arguments.shift[/\d+/]).to_i
605
- when "open"
606
- if arguments.first =~ /^\d+$/
607
- @action = :reopen
608
- @number ||= arguments.shift[/\d+/].to_i
609
- else
610
- @action = :open
611
- @title = arguments.join(' ')
612
- end
613
- when "edit"
614
- @action = :edit
615
- @number ||= arguments.shift[/\d+/].to_i
616
- when "close"
617
- @action = :close
618
- @number ||= arguments.shift[/\d+/].to_i
619
- when "reopen"
620
- @action = :reopen
621
- @number ||= arguments.shift[/\d+/].to_i
622
- when "label"
623
- @action = :label
624
- @number ||= arguments.shift[/\d+/].to_i
625
- @label ||= arguments.shift
626
- when "unlabel"
627
- @action = :unlabel
628
- @number ||= arguments.shift[/\d+/].to_i
629
- @label ||= arguments.shift
630
- when "comment"
631
- @action = :comment
632
- @number ||= arguments.shift[/\d+/].to_i
633
- when "claim"
634
- @action = :claim
635
- @number ||= arguments.shift[/\d+/].to_i
636
- when %r{^([^/]+)/([^/]+)$}
637
- @action = :list
638
- @user, @repo = $1, $2
639
- when "url", "web"
640
- @action = :url
641
- @number ||= arguments.shift[/\d+/].to_i
642
- end
643
-
644
- @use_ssl ||= `git config github.ssl`.chomp == 'true'
645
-
646
- if @action
647
- @args = @argv.dup
648
- args.delete_if { |arg| arg == command }
649
- option_parser.parse!(*args)
650
- return true
651
- end
652
- unless command.start_with? "-"
653
- warn "#{File.basename $0}: what do you mean, '#{command}'?"
654
- end
655
- end
656
- end
657
- end