fogbugz 1.0.3 → 1.0.4

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.
@@ -1,22 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
-
3
- require 'rubygems'
4
- require 'net/https'
5
- require 'uri'
6
- require 'rexml/document'
7
- require 'optparse'
8
-
9
- api_url = ENV['FOGBUGZ_API_URL']
10
- unless api_url
11
- puts "Environment variable FOGBUGZ_API_URL must be set."
12
- exit 1
13
- end
14
-
15
- api_token = ENV['FOGBUGZ_API_TOKEN']
16
- unless api_token
17
- puts "Environment variable FOGBUGZ_API_TOKEN must be set."
18
- exit 1
19
- end
2
+ require 'fogbugz/common'
20
3
 
21
4
  options = {}
22
5
  optparse = OptionParser.new do |opts|
@@ -28,33 +11,17 @@ optparse = OptionParser.new do |opts|
28
11
  end
29
12
 
30
13
  options[:resolved] = false
31
- opts.on('-r', '--resolved', 'Only show resolved statuses.') do
14
+ opts.on('--resolved', 'Only show resolved statuses.') do
32
15
  options[:resolved] = true
33
16
  end
34
17
  end
35
18
  optparse.parse!
36
-
37
- uri = URI format("#{api_url}?cmd=listStatuses&token=%s%s",
38
- URI.escape(api_token),
39
- options[:resolved] ? '&fResolved=1' : '')
40
- http = Net::HTTP.new(uri.host, uri.port)
41
- if uri.scheme == 'https'
42
- http.use_ssl = true
43
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
44
- end
45
- response = http.start { |h| h.request Net::HTTP::Get.new(uri.request_uri) }
46
- if response.code != '200'
47
- puts "HTTP request to #{api_url} failed with code #{response.code}."
48
- exit 1
49
- end
50
-
51
- result = REXML::Document.new(response.body)
52
- error = result.elements['/response/error']
53
- if error
54
- puts "Failed with error: #{error.text}."
19
+ unless ARGV.length == 0
20
+ puts optparse.help
55
21
  exit 1
56
22
  end
57
23
 
24
+ result = do_api 'listStatuses', options[:resolved] ? { :fResolved => '1' } : {}
58
25
  result.elements.to_a('/response/statuses/status').map { |s|
59
26
  s.elements['sStatus'].text
60
27
  }.uniq!.sort!.each { |s| puts s }
@@ -1,49 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ require 'fogbugz/common'
2
3
 
3
- require 'rubygems'
4
- require 'net/https'
5
- require 'uri'
6
- require 'rexml/document'
7
- require 'optparse'
8
-
9
- api_url = ENV['FOGBUGZ_API_URL']
10
- unless api_url
11
- puts "Environment variable FOGBUGZ_API_URL must be set."
12
- exit 1
13
- end
14
-
15
- api_token = ENV['FOGBUGZ_API_TOKEN']
16
- unless api_token
17
- puts "Environment variable FOGBUGZ_API_TOKEN must be set."
18
- exit 1
19
- end
20
-
21
- options = {}
22
- optparse = OptionParser.new do |opts|
23
- opts.banner = "usage: #{File::basename(__FILE__)} [options]"
24
-
25
- opts.on_tail('-h', '--help') do
26
- puts optparse.help
27
- exit 1
28
- end
29
- end
30
- optparse.parse!
31
-
32
- uri = URI format("#{api_url}?cmd=stopWork&token=%s", URI.escape(api_token))
33
- http = Net::HTTP.new(uri.host, uri.port)
34
- if uri.scheme == 'https'
35
- http.use_ssl = true
36
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
37
- end
38
- response = http.start { |h| h.request Net::HTTP::Get.new(uri.request_uri) }
39
- if response.code != '200'
40
- puts "HTTP request to #{api_url} failed with code #{response.code}."
41
- exit 1
42
- end
43
-
44
- result = REXML::Document.new(response.body)
45
- error = result.elements['/response/error']
46
- if error
47
- puts "Failed with error: #{error.text}."
48
- exit 1
49
- end
4
+ parse_opts "usage: #{File::basename(__FILE__)} [options]", 0
5
+ do_api 'stopWork'
@@ -0,0 +1,370 @@
1
+ require 'net/https'
2
+ require 'uri'
3
+ require 'rexml/document'
4
+ require 'optparse'
5
+ require 'highline'
6
+ require 'tempfile'
7
+ require 'yaml'
8
+ require 'English'
9
+ require 'time'
10
+ require 'chronic'
11
+
12
+ HighLine.use_color = STDOUT.isatty
13
+ begin
14
+ require 'win32console' if RUBY_PLATFORM =~ /mingw/
15
+ rescue LoadError
16
+ HighLine.use_color = false
17
+ end
18
+
19
+ def r; HighLine.use_color? ? HighLine::RED : ''; end
20
+ def b; HighLine.use_color? ? HighLine::BLUE : ''; end
21
+ def g; HighLine.use_color? ? HighLine::GREEN : ''; end
22
+ def y; HighLine.use_color? ? HighLine::YELLOW : ''; end
23
+ def m; HighLine.use_color? ? HighLine::MAGENTA : ''; end
24
+ def c; HighLine.use_color? ? HighLine::CLEAR : ''; end
25
+
26
+ unless ENV['FOGBUGZ_API_URL']
27
+ puts "Environment variable FOGBUGZ_API_URL must be set."
28
+ exit 1
29
+ end
30
+
31
+ unless ENV['FOGBUGZ_API_TOKEN']
32
+ puts "Environment variable FOGBUGZ_API_TOKEN must be set."
33
+ exit 1
34
+ end
35
+
36
+ def parse_opts(usage, num_args)
37
+ options = {}
38
+ optparse = OptionParser.new do |opts|
39
+ opts.banner = usage
40
+
41
+ opts.on_tail('-h', '--help') do
42
+ puts optparse.help
43
+ exit 1
44
+ end
45
+ end
46
+ optparse.parse!
47
+
48
+ unless ARGV.length == num_args
49
+ puts optparse.help
50
+ exit 1
51
+ end
52
+ options
53
+ end
54
+
55
+ def parse_stdin_opts(usage, num_args)
56
+ options = {}
57
+ optparse = OptionParser.new do |opts|
58
+ opts.banner = usage
59
+
60
+ opts.on_tail('-h', '--help') do
61
+ puts optparse.help
62
+ exit 1
63
+ end
64
+
65
+ options[:file] = nil
66
+ opts.on('--file=<file>',
67
+ 'Take the case content from the given file. Use - to read from STDIN.') do |file|
68
+ options[:file] = file
69
+ end
70
+
71
+ options[:template] = nil
72
+ opts.on('--template=<template>',
73
+ 'Use the file content or - for STDIN as the initial case content.') do |template|
74
+ options[:template] = template
75
+ end
76
+ end
77
+ optparse.parse!
78
+
79
+ unless ARGV.length == num_args or
80
+ (ARGV.length == num_args + 1 and ARGV.last == '-')
81
+ puts optparse.help
82
+ exit 1
83
+ end
84
+
85
+ if ARGV.length == num_args + 1
86
+ ARGV.pop
87
+ options[:file] = '-'
88
+ end
89
+ options
90
+ end
91
+
92
+ def get_editor_content(options, template)
93
+ editor = ENV['EDITOR'] || 'vim'
94
+ invoke_editor = true
95
+ old_argv = ARGV.clone
96
+ if options[:file]
97
+ if options[:file] == '-'
98
+ ARGV.replace []
99
+ else
100
+ ARGV.replace [options[:file]]
101
+ end
102
+ template = ARGF.read
103
+ invoke_editor = false
104
+ elsif options[:template]
105
+ if options[:template] == '-'
106
+ ARGV.replace []
107
+ else
108
+ ARGV.replace [options[:template]]
109
+ end
110
+ template = ARGF.read
111
+ end
112
+
113
+ content = template
114
+ if invoke_editor
115
+ tempfile = Tempfile.new ['case', '.md']
116
+ tempfile.write template
117
+ tempfile.close
118
+ rc = system "#{editor} #{tempfile.path}"
119
+ unless rc
120
+ puts "Editor exited with non-zero status. Aborting."
121
+ exit 1
122
+ end
123
+ tempfile.open
124
+ content = tempfile.read
125
+ tempfile.close
126
+ tempfile.delete
127
+ end
128
+
129
+ data = {}
130
+ if content =~ /(.*?\n?)^(---\s*$\n?)/m
131
+ # Combined YAML front matter with text content.
132
+ begin
133
+ data = YAML.load($1) || {}
134
+ data['body'] = $POSTMATCH if $POSTMATCH and not $POSTMATCH.empty?
135
+ rescue => e
136
+ puts "Exception reading YAML front matter. #{e.inspect}"
137
+ exit 1
138
+ end
139
+ else
140
+ begin
141
+ # YAML only content.
142
+ data = YAML.load(content)
143
+ if data.instance_of? String
144
+ # Text only content.
145
+ data = { 'body' => content }
146
+ end
147
+ rescue => e
148
+ data = {}
149
+ end
150
+ end
151
+
152
+ if not data or data.empty?
153
+ puts "No new content for case. Aborting."
154
+ exit 1
155
+ end
156
+ ARGV.replace old_argv
157
+ data
158
+ end
159
+
160
+ def maybe_show(key, xml)
161
+ if xml and xml.text and not xml.text.empty?
162
+ format "#{b}%-11.11s#{c}#{xml.text}\n", "#{key}:"
163
+ else
164
+ ''
165
+ end
166
+ end
167
+
168
+ def maybe_show_array(key, xml)
169
+ if xml and not xml.empty?
170
+ format "#{b}%-11.11s#{c}#{xml.join(', ')}\n", "#{key}:"
171
+ else
172
+ ''
173
+ end
174
+ end
175
+
176
+ def maybe_show_literal(color, xml)
177
+ if xml and xml.text and not xml.text.empty?
178
+ "#{color}#{xml.text}#{c}\n"
179
+ else
180
+ ''
181
+ end
182
+ end
183
+
184
+ def maybe_show_time(key, xml)
185
+ if xml and xml.text and not xml.text.empty?
186
+ format "#{b}%-11.11s#{c}#{time_string(xml.text)}\n", "#{key}:"
187
+ else
188
+ ''
189
+ end
190
+ end
191
+
192
+ def maybe_show_event(color, xml, time_xml)
193
+ if xml and xml.text and not xml.text.empty?
194
+ if time_xml and time_xml.text and not time_xml.text.empty?
195
+ format("#{color}%s at %s.#{c}\n", xml.text, time_string(time_xml.text))
196
+ else
197
+ "#{color}#{xml.text}#{c}\n"
198
+ end
199
+ else
200
+ ''
201
+ end
202
+ end
203
+
204
+ def maybe_append(key, xml)
205
+ if xml and xml.text and not xml.text.empty?
206
+ "# #{key}: #{xml.text}\n"
207
+ else
208
+ "# #{key}: <#{key}>\n"
209
+ end
210
+ end
211
+
212
+ def maybe_array(key, xml)
213
+ if xml and not xml.empty?
214
+ "# #{key}: [#{xml.join(', ')}]\n"
215
+ else
216
+ "# #{key}: [<#{key}>]\n"
217
+ end
218
+ end
219
+
220
+ def time_short_string(text)
221
+ if text
222
+ Time.parse(text).localtime.strftime('%D')
223
+ else
224
+ ''
225
+ end
226
+ end
227
+
228
+ def time_string(text)
229
+ if text
230
+ time = Time.parse(text).localtime
231
+ format("%s on %s", time.strftime('%-l:%M %p'),
232
+ time.strftime('%A, %B %e %Y'))
233
+ else
234
+ ''
235
+ end
236
+ end
237
+
238
+ def parse_time(text)
239
+ return nil unless text
240
+ begin
241
+ time = Time.parse(text)
242
+ # Ruby 1.8.7 returns the current time on bad parses.
243
+ raise ArgumentError if Time.now.to_s == time.to_s
244
+ rescue ArgumentError
245
+ begin
246
+ time = Chronic.parse(text).utc.iso8601
247
+ rescue
248
+ puts "Unable to parse date #{text}."
249
+ exit 1
250
+ end
251
+ end
252
+ unless time
253
+ puts "Unable to parse date #{text}."
254
+ exit 1
255
+ end
256
+ time
257
+ end
258
+
259
+ def maybe_time(key, xml)
260
+ if xml and xml.text and not xml.text.empty?
261
+ "# #{key}: #{time_string(xml.text)}\n"
262
+ else
263
+ "# #{key}: <#{key}>\n"
264
+ end
265
+ end
266
+
267
+ def maybe_event(xml, time_xml)
268
+ if xml and xml.text and not xml.text.empty?
269
+ if time_xml and time_xml.text and not time_xml.text.empty?
270
+ format("# %s at %s.\n", xml.text, time_string(time_xml.text))
271
+ else
272
+ commented = xml.text.gsub(/\n/, "\n# ")
273
+ "# #{commented}"
274
+ end
275
+ else
276
+ ''
277
+ end
278
+ end
279
+
280
+ def maybe_literal(xml)
281
+ if xml and xml.text and not xml.text.empty?
282
+ commented = xml.text.gsub(/\n/, "\n# ")
283
+ "# #{commented}\n"
284
+ else
285
+ ''
286
+ end
287
+ end
288
+
289
+ def do_api(cmd, params={})
290
+ api_url = ENV['FOGBUGZ_API_URL']
291
+ api_token = ENV['FOGBUGZ_API_TOKEN']
292
+
293
+ query = ''
294
+ params.each_pair do |k,v|
295
+ query << format("&%s=%s", URI.escape(k.to_s), URI.escape(v.to_s)) if v
296
+ end
297
+
298
+ uri = URI "#{api_url}?cmd=#{cmd}&token=#{api_token}#{query}"
299
+ http = Net::HTTP.new(uri.host, uri.port)
300
+ if uri.scheme == 'https'
301
+ http.use_ssl = true
302
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
303
+ end
304
+ response = http.start do |h|
305
+ h.request Net::HTTP::Get.new(uri.request_uri)
306
+ end
307
+ if response.code != '200'
308
+ puts "HTTP request to #{api_url} failed with code #{response.code}."
309
+ exit 1
310
+ end
311
+
312
+ result = REXML::Document.new(response.body)
313
+ error = result.elements['/response/error']
314
+ if error
315
+ puts "Failed with error: #{error.text}."
316
+ exit 1
317
+ end
318
+ result
319
+ end
320
+
321
+ def which(cmd)
322
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
323
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
324
+ exts.each { |ext|
325
+ exe = "#{path}/#{cmd}#{ext}"
326
+ return exe if File.executable? exe
327
+ }
328
+ end
329
+ return nil
330
+ end
331
+
332
+ def dispatch_subcommand(file, commands = {})
333
+ basename = File.basename(file)
334
+ absolute = File.expand_path(file)
335
+
336
+ usage = <<HERE
337
+ usage: #{basename} <subcommands> [options]
338
+
339
+ The subcommands are:
340
+ HERE
341
+ commands.keys.sort { |a,b| a.to_s <=> b.to_s }.each do |k|
342
+ usage << format(" %-16.16s%s\n", k, commands[k])
343
+ end
344
+ usage << <<HERE
345
+
346
+ See '#{basename} help <commands>' for more information on a specific command.
347
+ HERE
348
+
349
+ if ARGV[0] == 'help'
350
+ if ARGV[1] and commands[ARGV[1].to_sym]
351
+ subcommand = ARGV[1]
352
+ ARGV.replace ['--help']
353
+ load "#{absolute}-#{subcommand}"
354
+ elsif ARGV[1] and which("#{basename}-#{ARGV[1]}")
355
+ command = which("#{basename}-#{ARGV[1]}")
356
+ system "#{command} --help"
357
+ else
358
+ puts usage
359
+ exit 1
360
+ end
361
+ elsif ARGV[0] and commands[ARGV[0].to_sym]
362
+ load "#{absolute}-#{ARGV.shift}"
363
+ elsif ARGV[0] and which("#{basename}-#{ARGV[0]}")
364
+ command = which("#{basename}-#{ARGV.shift}")
365
+ Process.wait(Process.fork { Process.exec(command, *ARGV) })
366
+ else
367
+ puts usage
368
+ exit 1
369
+ end
370
+ end