fogbugz 1.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/bin/fogbugz +78 -0
- data/bin/fogbugz-areas +64 -0
- data/bin/fogbugz-assign +58 -0
- data/bin/fogbugz-categories +55 -0
- data/bin/fogbugz-close +57 -0
- data/bin/fogbugz-edit +281 -0
- data/bin/fogbugz-filter +78 -0
- data/bin/fogbugz-filters +53 -0
- data/bin/fogbugz-list +69 -0
- data/bin/fogbugz-login +60 -0
- data/bin/fogbugz-logoff +49 -0
- data/bin/fogbugz-milestones +57 -0
- data/bin/fogbugz-new +165 -0
- data/bin/fogbugz-people +57 -0
- data/bin/fogbugz-priorities +55 -0
- data/bin/fogbugz-projects +57 -0
- data/bin/fogbugz-reactivate +56 -0
- data/bin/fogbugz-reopen +56 -0
- data/bin/fogbugz-resolve +82 -0
- data/bin/fogbugz-show +147 -0
- data/bin/fogbugz-start +55 -0
- data/bin/fogbugz-statuses +62 -0
- data/bin/fogbugz-stop +50 -0
- data/lib/dummy.rb +1 -0
- metadata +119 -0
data/bin/fogbugz
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
basename = File.basename(__FILE__)
|
4
|
+
absolute = File.expand_path(__FILE__)
|
5
|
+
|
6
|
+
usage = <<HERE
|
7
|
+
usage: #{basename} <subcommand> [options]
|
8
|
+
#{basename} <case>
|
9
|
+
|
10
|
+
The subcommands are:
|
11
|
+
areas List active areas.
|
12
|
+
assign Assign a case.
|
13
|
+
categories List available categories.
|
14
|
+
close Close a resolved case.
|
15
|
+
edit Edit an existing case.
|
16
|
+
filter Set the current default filter.
|
17
|
+
filters List available filters.
|
18
|
+
list List cases by the current filter or a search query.
|
19
|
+
login Create an API token.
|
20
|
+
logoff Invalidate an API token.
|
21
|
+
milestones List active milestones.
|
22
|
+
new Create a new case.
|
23
|
+
people List active users.
|
24
|
+
priorities List available priorities.
|
25
|
+
projects List active projects.
|
26
|
+
reactivate Reactivate a resolved case.
|
27
|
+
reopen Reopen a closed case.
|
28
|
+
resolve Resolve a case.
|
29
|
+
show Show details on a case.
|
30
|
+
start Start working on a case.
|
31
|
+
statuses List available statuses.
|
32
|
+
stop Stop working on a case.
|
33
|
+
|
34
|
+
See '#{basename} help <commands>' for more information on a \
|
35
|
+
specific command.
|
36
|
+
HERE
|
37
|
+
|
38
|
+
if ARGV.length == 0
|
39
|
+
puts usage
|
40
|
+
exit 1
|
41
|
+
end
|
42
|
+
|
43
|
+
case ARGV[0]
|
44
|
+
when 'help'
|
45
|
+
if ARGV.length == 1
|
46
|
+
puts usage
|
47
|
+
exit 1
|
48
|
+
else
|
49
|
+
case ARGV[1]
|
50
|
+
when 'areas', 'assign', 'categories', 'close', 'edit', 'filter', 'filters',
|
51
|
+
'list', 'login', 'logoff', 'milestones', 'new', 'people', 'priorities',
|
52
|
+
'projects', 'reactivate', 'reopen', 'resolve', 'show', 'start',
|
53
|
+
'statuses', 'stop'
|
54
|
+
subcommand = ARGV[1]
|
55
|
+
ARGV.clear
|
56
|
+
ARGV.push '--help'
|
57
|
+
load "#{absolute}-#{subcommand}"
|
58
|
+
exit 0
|
59
|
+
else
|
60
|
+
puts usage
|
61
|
+
exit 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
when 'areas', 'assign', 'categories', 'close', 'edit', 'filter', 'filters',
|
65
|
+
'list', 'login', 'logoff', 'milestones', 'new', 'people', 'priorities',
|
66
|
+
'projects', 'reactivate', 'reopen', 'resolve', 'show', 'start',
|
67
|
+
'statuses', 'stop'
|
68
|
+
load "#{absolute}-#{ARGV.shift}"
|
69
|
+
exit 0
|
70
|
+
else
|
71
|
+
if ARGV[0].to_i.to_s == ARGV[0]
|
72
|
+
load "#{absolute}-show"
|
73
|
+
exit 0
|
74
|
+
else
|
75
|
+
puts usage
|
76
|
+
exit 1
|
77
|
+
end
|
78
|
+
end
|
data/bin/fogbugz-areas
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'typhoeus'
|
5
|
+
require 'xml'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
api_url = ENV['FOGBUGZ_API_URL']
|
9
|
+
unless api_url
|
10
|
+
puts "Environment variable FOGBUGZ_API_URL must be set."
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
|
14
|
+
api_token = ENV['FOGBUGZ_API_TOKEN']
|
15
|
+
unless api_url
|
16
|
+
puts "Environment variable FOGBUGZ_API_TOKEN must be set."
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
|
20
|
+
options = {}
|
21
|
+
optparse = OptionParser.new do |opts|
|
22
|
+
opts.banner = "usage: #{File::basename(__FILE__)} [options]"
|
23
|
+
|
24
|
+
options[:verbose] = false
|
25
|
+
opts.on('-v', '--verbose', 'Output verbose debugging information') do
|
26
|
+
options[:verbose] = true
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on_tail('-h', '--help') do
|
30
|
+
puts optparse.help
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
|
34
|
+
options[:project] = nil
|
35
|
+
opts.on('--project=<project>', 'Filter by project.') do |project|
|
36
|
+
options[:project] = project
|
37
|
+
end
|
38
|
+
end
|
39
|
+
optparse.parse!
|
40
|
+
|
41
|
+
response = Typhoeus::Request.get(api_url,
|
42
|
+
:verbose => options[:verbose],
|
43
|
+
:params => {
|
44
|
+
:cmd => 'listAreas',
|
45
|
+
:token => api_token,
|
46
|
+
:ixProject => options[:project] })
|
47
|
+
if response.code != 200
|
48
|
+
puts "HTTP request to #{api_url} failed with code #{response.code}."
|
49
|
+
exit 1
|
50
|
+
end
|
51
|
+
|
52
|
+
result = XML::Parser.string(response.body).parse
|
53
|
+
error = result.find_first('/response/error')
|
54
|
+
if error
|
55
|
+
puts "Failed with error: #{error.content}."
|
56
|
+
exit 1
|
57
|
+
end
|
58
|
+
|
59
|
+
result.find('/response/areas/area').each do |status|
|
60
|
+
puts format("%-20.20s %-20.20s %s\n",
|
61
|
+
status.find_first('sPersonOwner').content,
|
62
|
+
status.find_first('sProject').content,
|
63
|
+
status.find_first('sArea').content).strip!
|
64
|
+
end
|
data/bin/fogbugz-assign
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'typhoeus'
|
5
|
+
require 'xml'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
api_url = ENV['FOGBUGZ_API_URL']
|
9
|
+
unless api_url
|
10
|
+
puts "Environment variable FOGBUGZ_API_URL must be set."
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
|
14
|
+
api_token = ENV['FOGBUGZ_API_TOKEN']
|
15
|
+
unless api_url
|
16
|
+
puts "Environment variable FOGBUGZ_API_TOKEN must be set."
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
|
20
|
+
options = {}
|
21
|
+
optparse = OptionParser.new do |opts|
|
22
|
+
opts.banner = "usage: #{File::basename(__FILE__)} [options] <case> <assignee>"
|
23
|
+
|
24
|
+
options[:verbose] = false
|
25
|
+
opts.on('-v', '--verbose', 'Output verbose debugging information.') do
|
26
|
+
options[:verbose] = true
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on_tail('-h', '--help') do
|
30
|
+
puts optparse.help
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
optparse.parse!
|
35
|
+
|
36
|
+
unless ARGV.length == 2
|
37
|
+
puts optparse.help
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
|
41
|
+
response = Typhoeus::Request.get(api_url,
|
42
|
+
:verbose => options[:verbose],
|
43
|
+
:params => {
|
44
|
+
:cmd => 'assign',
|
45
|
+
:token => api_token,
|
46
|
+
:ixBug => ARGV[0],
|
47
|
+
:sPersonAssignedTo => ARGV[1] })
|
48
|
+
if response.code != 200
|
49
|
+
puts "HTTP request to #{api_url} failed with code #{response.code}."
|
50
|
+
exit 1
|
51
|
+
end
|
52
|
+
|
53
|
+
result = XML::Parser.string(response.body).parse
|
54
|
+
error = result.find_first('/response/error')
|
55
|
+
if error
|
56
|
+
puts "Failed with error: #{error.content}."
|
57
|
+
exit 1
|
58
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'typhoeus'
|
5
|
+
require 'xml'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
api_url = ENV['FOGBUGZ_API_URL']
|
9
|
+
unless api_url
|
10
|
+
puts "Environment variable FOGBUGZ_API_URL must be set."
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
|
14
|
+
api_token = ENV['FOGBUGZ_API_TOKEN']
|
15
|
+
unless api_url
|
16
|
+
puts "Environment variable FOGBUGZ_API_TOKEN must be set."
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
|
20
|
+
options = {}
|
21
|
+
optparse = OptionParser.new do |opts|
|
22
|
+
opts.banner = "usage: #{File::basename(__FILE__)} [options]"
|
23
|
+
|
24
|
+
options[:verbose] = false
|
25
|
+
opts.on('-v', '--verbose', 'Output verbose debugging information') do
|
26
|
+
options[:verbose] = true
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on_tail('-h', '--help') do
|
30
|
+
puts optparse.help
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
optparse.parse!
|
35
|
+
|
36
|
+
response = Typhoeus::Request.get(api_url,
|
37
|
+
:verbose => options[:verbose],
|
38
|
+
:params => {
|
39
|
+
:cmd => 'listCategories',
|
40
|
+
:token => api_token })
|
41
|
+
if response.code != 200
|
42
|
+
puts "HTTP request to #{api_url} failed with code #{response.code}."
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
|
46
|
+
result = XML::Parser.string(response.body).parse
|
47
|
+
error = result.find_first('/response/error')
|
48
|
+
if error
|
49
|
+
puts "Failed with error: #{error.content}."
|
50
|
+
exit 1
|
51
|
+
end
|
52
|
+
|
53
|
+
result.find('/response/categories/category').each do |status|
|
54
|
+
puts status.find_first('sCategory').content
|
55
|
+
end
|
data/bin/fogbugz-close
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'typhoeus'
|
5
|
+
require 'xml'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
api_url = ENV['FOGBUGZ_API_URL']
|
9
|
+
unless api_url
|
10
|
+
puts "Environment variable FOGBUGZ_API_URL must be set."
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
|
14
|
+
api_token = ENV['FOGBUGZ_API_TOKEN']
|
15
|
+
unless api_url
|
16
|
+
puts "Environment variable FOGBUGZ_API_TOKEN must be set."
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
|
20
|
+
options = {}
|
21
|
+
optparse = OptionParser.new do |opts|
|
22
|
+
opts.banner = "usage: #{File::basename(__FILE__)} [options] <case>"
|
23
|
+
|
24
|
+
options[:verbose] = false
|
25
|
+
opts.on('-v', '--verbose', 'Output verbose debugging information.') do
|
26
|
+
options[:verbose] = true
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on_tail('-h', '--help') do
|
30
|
+
puts optparse.help
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
optparse.parse!
|
35
|
+
|
36
|
+
unless ARGV[0]
|
37
|
+
puts optparse.help
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
|
41
|
+
response = Typhoeus::Request.get(api_url,
|
42
|
+
:verbose => options[:verbose],
|
43
|
+
:params => {
|
44
|
+
:cmd => 'listAreas',
|
45
|
+
:token => api_token,
|
46
|
+
:ixBug => ARGV[0] })
|
47
|
+
if response.code != 200
|
48
|
+
puts "HTTP request to #{api_url} failed with code #{response.code}."
|
49
|
+
exit 1
|
50
|
+
end
|
51
|
+
|
52
|
+
result = XML::Parser.string(response.body).parse
|
53
|
+
error = result.find_first('/response/error')
|
54
|
+
if error
|
55
|
+
puts "Failed with error: #{error.content}."
|
56
|
+
exit 1
|
57
|
+
end
|
data/bin/fogbugz-edit
ADDED
@@ -0,0 +1,281 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'typhoeus'
|
5
|
+
require 'xml'
|
6
|
+
require 'tempfile'
|
7
|
+
require 'optparse'
|
8
|
+
require 'yaml'
|
9
|
+
require 'English'
|
10
|
+
|
11
|
+
api_url = ENV['FOGBUGZ_API_URL']
|
12
|
+
unless api_url
|
13
|
+
puts "Environment variable FOGBUGZ_API_URL must be set."
|
14
|
+
exit 1
|
15
|
+
end
|
16
|
+
|
17
|
+
api_token = ENV['FOGBUGZ_API_TOKEN']
|
18
|
+
unless api_url
|
19
|
+
puts "Environment variable FOGBUGZ_API_TOKEN must be set."
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
|
23
|
+
editor = ENV['EDITOR'] || 'vim'
|
24
|
+
|
25
|
+
options = {}
|
26
|
+
optparse = OptionParser.new do |opts|
|
27
|
+
opts.banner = "usage: #{File::basename(__FILE__)} [options] <case>"
|
28
|
+
|
29
|
+
options[:verbose] = false
|
30
|
+
opts.on('-v', '--verbose', 'Output verbose debugging information') do
|
31
|
+
options[:verbose] = true
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on_tail('-h', '--help') do
|
35
|
+
puts optparse.help
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
|
39
|
+
options[:file] = nil
|
40
|
+
opts.on('-f', '--file=<file>',
|
41
|
+
'Take the case content from the given file. Use - to read from STDIN.') do |file|
|
42
|
+
options[:file] = file
|
43
|
+
end
|
44
|
+
|
45
|
+
options[:template] = nil
|
46
|
+
opts.on('-t', '--template=<template>',
|
47
|
+
'Use the file content or - for STDIN as the initial case content.') do |template|
|
48
|
+
options[:template] = template
|
49
|
+
end
|
50
|
+
end
|
51
|
+
optparse.parse!
|
52
|
+
|
53
|
+
unless ARGV.length == 1
|
54
|
+
puts optparse.help
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
case_id = ARGV[0]
|
58
|
+
|
59
|
+
response = Typhoeus::Request.get(api_url,
|
60
|
+
:verbose => options[:verbose],
|
61
|
+
:params => {
|
62
|
+
:cmd => 'search',
|
63
|
+
:token => api_token,
|
64
|
+
:cols => 'ixBug,ixBugParent,tags,sTitle,sProject,sArea,sFixFor,sCategory,sPersonAssignedTo,sPriority,hrsCurrEst,events',
|
65
|
+
:q => case_id })
|
66
|
+
if response.code != 200
|
67
|
+
puts "HTTP request to #{api_url} failed with code #{response.code}."
|
68
|
+
exit 1
|
69
|
+
end
|
70
|
+
|
71
|
+
result = XML::Parser.string(response.body).parse
|
72
|
+
error = result.find_first('/response/error')
|
73
|
+
if error
|
74
|
+
puts "Failed with error: #{error.content}."
|
75
|
+
exit 1
|
76
|
+
end
|
77
|
+
|
78
|
+
bug = result.find_first("/response/cases/case[@ixBug='#{case_id}']")
|
79
|
+
unless bug
|
80
|
+
puts "Case #{case_id} does not exist."
|
81
|
+
exit 1
|
82
|
+
end
|
83
|
+
|
84
|
+
invoke_editor = true
|
85
|
+
if options[:file]
|
86
|
+
if options[:file] == '-'
|
87
|
+
ARGV.replace []
|
88
|
+
else
|
89
|
+
ARGV.replace [options[:file]]
|
90
|
+
end
|
91
|
+
template = ARGF.read
|
92
|
+
invoke_editor = false
|
93
|
+
elsif options[:template]
|
94
|
+
if options[:template] == '-'
|
95
|
+
ARGV.replace []
|
96
|
+
else
|
97
|
+
ARGV.replace [options[:template]]
|
98
|
+
end
|
99
|
+
template = ARGF.read
|
100
|
+
else
|
101
|
+
template = ''
|
102
|
+
template << <<HERE
|
103
|
+
# Fill in metadata for the case.
|
104
|
+
HERE
|
105
|
+
|
106
|
+
sTitle = bug.find_first('sTitle')
|
107
|
+
if sTitle and sTitle.content and not sTitle.content.empty?
|
108
|
+
template << "# title: #{sTitle.content}\n"
|
109
|
+
else
|
110
|
+
template << "# title: <title>\n"
|
111
|
+
end
|
112
|
+
|
113
|
+
sPersonAssignedTo = bug.find_first('sPersonAssignedTo')
|
114
|
+
if sPersonAssignedTo and sPersonAssignedTo.content and
|
115
|
+
not sPersonAssignedTo.content.empty?
|
116
|
+
template << "# assignee: #{sPersonAssignedTo.content}\n"
|
117
|
+
else
|
118
|
+
template << "# assignee: <person>\n"
|
119
|
+
end
|
120
|
+
|
121
|
+
ixBugParent = bug.find_first('ixBugParent')
|
122
|
+
if ixBugParent and ixBugParent.content and not ixBugParent.content.empty? and
|
123
|
+
ixBugParent.content != '0'
|
124
|
+
template << "# parent: #{ixBugParent.content}\n"
|
125
|
+
else
|
126
|
+
template << "# parent: <case>\n"
|
127
|
+
end
|
128
|
+
|
129
|
+
tags = bug.find('tags/tag').collect { |tag| tag.content }
|
130
|
+
if tags and not tags.empty?
|
131
|
+
template << "# tags: [#{tags.join(', ')}]\n"
|
132
|
+
else
|
133
|
+
template << "# tags: [bug, enhancement]\n"
|
134
|
+
end
|
135
|
+
|
136
|
+
sProject = bug.find_first('sProject')
|
137
|
+
if sProject and sProject.content and not sProject.content.empty?
|
138
|
+
template << "# project: #{sProject.content}\n"
|
139
|
+
else
|
140
|
+
template << "# project: <project>\n"
|
141
|
+
end
|
142
|
+
|
143
|
+
sArea = bug.find_first('sArea')
|
144
|
+
if sArea and sArea.content and not sArea.content.empty?
|
145
|
+
template << "# area: #{sArea.content}\n"
|
146
|
+
else
|
147
|
+
template << "# area: <area>\n"
|
148
|
+
end
|
149
|
+
|
150
|
+
sFixFor = bug.find_first('sFixFor')
|
151
|
+
if sFixFor and sFixFor.content and not sFixFor.content.empty?
|
152
|
+
template << "# milestone: #{sFixFor.content}\n"
|
153
|
+
else
|
154
|
+
template << "# milestone: <milestone>"
|
155
|
+
end
|
156
|
+
|
157
|
+
sCategory = bug.find_first('sCategory')
|
158
|
+
if sCategory and sCategory.content and not sCategory.content.empty?
|
159
|
+
template << "# category: #{sCategory.content}\n"
|
160
|
+
else
|
161
|
+
template << "# category: <category>"
|
162
|
+
end
|
163
|
+
|
164
|
+
sPriority = bug.find_first('sPriority')
|
165
|
+
if sPriority and sPriority.content and not sPriority.content.empty?
|
166
|
+
template << "# priority: #{sPriority.content}\n"
|
167
|
+
else
|
168
|
+
template << "# priority: <priority>"
|
169
|
+
end
|
170
|
+
|
171
|
+
hrsCurrEst = bug.find_first('hrsCurrEst')
|
172
|
+
if hrsCurrEst and hrsCurrEst.content and not hrsCurrEst.content.empty? and
|
173
|
+
hrsCurrEst.content != '0'
|
174
|
+
template << "# estimate: #{hrsCurrEst.content}\n"
|
175
|
+
else
|
176
|
+
template << "# estimate: <estimate>\n"
|
177
|
+
end
|
178
|
+
|
179
|
+
template << "\n"
|
180
|
+
bug.find('events/event').each do |event|
|
181
|
+
evtDescription = event.find_first('evtDescription')
|
182
|
+
if evtDescription and evtDescription.content and
|
183
|
+
not evtDescription.content.empty?
|
184
|
+
time = Time.parse(event.find_first('dt').content).localtime
|
185
|
+
template << format("# %s at %s on %s.\n", evtDescription.content,
|
186
|
+
time.strftime('%-l:%M %p'),
|
187
|
+
time.strftime('%A, %B %e %Y'))
|
188
|
+
end
|
189
|
+
|
190
|
+
sChanges = event.find_first('sChanges')
|
191
|
+
if sChanges and sChanges.content and not sChanges.content.empty?
|
192
|
+
commented = sChanges.content.gsub(/\n/, "\n# ")
|
193
|
+
template << "# #{commented}\n"
|
194
|
+
end
|
195
|
+
|
196
|
+
summary = event.find_first('s')
|
197
|
+
if summary and summary.content and not summary.content.empty?
|
198
|
+
commented = summary.content.gsub(/\n/, "\n# ")
|
199
|
+
template << "# #{commented}\n"
|
200
|
+
end
|
201
|
+
template << "\n"
|
202
|
+
end
|
203
|
+
template << <<HERE
|
204
|
+
# Enter an additional note for the case after the dashes.
|
205
|
+
---
|
206
|
+
HERE
|
207
|
+
end
|
208
|
+
|
209
|
+
content = template
|
210
|
+
if invoke_editor
|
211
|
+
tempfile = Tempfile.new ['case', '.md']
|
212
|
+
tempfile.write template
|
213
|
+
tempfile.close
|
214
|
+
rc = system "#{editor} #{tempfile.path}"
|
215
|
+
unless rc
|
216
|
+
puts "Editor exited with non-zero status. Aborting."
|
217
|
+
exit 1
|
218
|
+
end
|
219
|
+
tempfile.open
|
220
|
+
content = tempfile.read
|
221
|
+
tempfile.close
|
222
|
+
tempfile.delete
|
223
|
+
end
|
224
|
+
|
225
|
+
data = {}
|
226
|
+
if content =~ /(.*?\n?)^(---\s*$\n?)/m
|
227
|
+
# Combined YAML front matter with text content.
|
228
|
+
begin
|
229
|
+
data = YAML.load($1) || {}
|
230
|
+
data['body'] = $POSTMATCH if $POSTMATCH and not $POSTMATCH.empty?
|
231
|
+
rescue => e
|
232
|
+
puts "Exception reading YAML front matter. #{e.inspect}"
|
233
|
+
exit 1
|
234
|
+
end
|
235
|
+
else
|
236
|
+
begin
|
237
|
+
# YAML only content.
|
238
|
+
data = YAML.load(content)
|
239
|
+
was_yaml = true if data and not data.empty?
|
240
|
+
if data.instance_of? String
|
241
|
+
# Text only content.
|
242
|
+
data = { 'body' => data }
|
243
|
+
end
|
244
|
+
rescue => e
|
245
|
+
data = {}
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
if not data or data.empty?
|
250
|
+
puts "No changes to case. Aborting."
|
251
|
+
exit 1
|
252
|
+
end
|
253
|
+
|
254
|
+
response = Typhoeus::Request.get(api_url,
|
255
|
+
:verbose => options[:verbose],
|
256
|
+
:params => {
|
257
|
+
:cmd => 'edit',
|
258
|
+
:token => api_token,
|
259
|
+
:ixBug => case_id,
|
260
|
+
:sTitle => data['title'],
|
261
|
+
:ixBugParent => data['parent'],
|
262
|
+
:sTags => data['tags'] ? data['tags'].join(',') : nil,
|
263
|
+
:sProject => data['project'],
|
264
|
+
:sArea => data['area'],
|
265
|
+
:sFixFor => data['milestone'],
|
266
|
+
:sCategory => data['category'],
|
267
|
+
:sPersonAssignedTo => data['assignee'],
|
268
|
+
:sPriority => data['priority'],
|
269
|
+
:hrsCurrEst => data['estimate'],
|
270
|
+
:sEvent => data['body'] })
|
271
|
+
if response.code != 200
|
272
|
+
puts "HTTP request to #{api_url} failed with code #{response.code}."
|
273
|
+
exit 1
|
274
|
+
end
|
275
|
+
|
276
|
+
result = XML::Parser.string(response.body).parse
|
277
|
+
error = result.find_first('/response/error')
|
278
|
+
if error
|
279
|
+
puts "Failed with error: #{error.content}."
|
280
|
+
exit 1
|
281
|
+
end
|