tpope-pickler 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +4 -0
- data/lib/pickler/feature.rb +13 -4
- data/lib/pickler/runner.rb +119 -20
- data/lib/pickler/tracker/iteration.rb +4 -0
- data/lib/pickler/tracker/note.rb +4 -0
- data/lib/pickler/tracker/story.rb +48 -10
- data/lib/pickler.rb +45 -35
- data/pickler.gemspec +3 -1
- metadata +20 -2
data/README.rdoc
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
Synchronize user stories in Pivotal Tracker with Cucumber features.
|
4
4
|
|
5
|
+
If you aren't using Cucumber, you can still use pickler as a Pivotal Tracker
|
6
|
+
command line client, provided you humor it with a features/ directory
|
7
|
+
containing a tracker.yml file.
|
8
|
+
|
5
9
|
== Getting started
|
6
10
|
|
7
11
|
gem install tpope-pickler --source=http://gems.github.com
|
data/lib/pickler/feature.rb
CHANGED
@@ -13,6 +13,9 @@ class Pickler
|
|
13
13
|
@story = identifier
|
14
14
|
@id = @story.id
|
15
15
|
|
16
|
+
when Integer
|
17
|
+
@id = identifier
|
18
|
+
|
16
19
|
when /^#{URL_REGEX}$/, /^(\d+)$/
|
17
20
|
@id = $1.to_i
|
18
21
|
|
@@ -54,7 +57,9 @@ class Pickler
|
|
54
57
|
|
55
58
|
def start(default = nil)
|
56
59
|
story.transition!("started") if story.startable?
|
57
|
-
|
60
|
+
if filename || default
|
61
|
+
pull(default)
|
62
|
+
end
|
58
63
|
end
|
59
64
|
|
60
65
|
def push
|
@@ -64,9 +69,13 @@ class Pickler
|
|
64
69
|
end
|
65
70
|
|
66
71
|
def finish
|
67
|
-
|
68
|
-
|
69
|
-
|
72
|
+
if filename
|
73
|
+
story.finish
|
74
|
+
story.to_s = local_body
|
75
|
+
story.save
|
76
|
+
else
|
77
|
+
story.finish!
|
78
|
+
end
|
70
79
|
end
|
71
80
|
|
72
81
|
def id
|
data/lib/pickler/runner.rb
CHANGED
@@ -118,20 +118,48 @@ class Pickler
|
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
121
|
+
def colorize(code, string)
|
122
|
+
if color?
|
123
|
+
"\e[#{code}m#{string}\e[00m"
|
124
|
+
else
|
125
|
+
string.to_s
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
121
129
|
def puts_summary(story)
|
122
130
|
summary = "%6d " % story.id
|
123
131
|
type = story.estimate || TYPE_SYMBOLS[story.story_type]
|
124
132
|
state = STATE_SYMBOLS[story.current_state]
|
125
|
-
|
126
|
-
|
127
|
-
summary << "\e[01;3#{TYPE_COLORS[story.story_type]}m#{type}\e[00m "
|
128
|
-
else
|
129
|
-
summary << "#{state} #{type} "
|
130
|
-
end
|
133
|
+
summary << colorize("3#{STATE_COLORS[story.current_state]}", state) << ' '
|
134
|
+
summary << colorize("01;3#{TYPE_COLORS[story.story_type]}", type) << ' '
|
131
135
|
summary << story.name
|
132
136
|
puts summary
|
133
137
|
end
|
134
138
|
|
139
|
+
def puts_full(story)
|
140
|
+
puts colorize("01;3#{TYPE_COLORS[story.story_type]}", story.name)
|
141
|
+
puts "Type: #{story.story_type}".rstrip
|
142
|
+
if story.story_type == "release"
|
143
|
+
puts "Deadline: #{story.deadline}".rstrip
|
144
|
+
else
|
145
|
+
puts "Estimate: #{story.estimate}".rstrip
|
146
|
+
end
|
147
|
+
puts "State: #{story.current_state}".rstrip
|
148
|
+
puts "Labels: #{story.labels.join(', ')}".rstrip
|
149
|
+
puts "Requester: #{story.requested_by}".rstrip
|
150
|
+
puts "Owner: #{story.owned_by}".rstrip
|
151
|
+
puts "URL: #{story.url}".rstrip
|
152
|
+
puts unless story.description.blank?
|
153
|
+
story.description_lines.each do |line|
|
154
|
+
puts " #{line}".rstrip
|
155
|
+
end
|
156
|
+
story.notes.each do |note|
|
157
|
+
puts
|
158
|
+
puts " #{colorize('01', note.author)} (#{note.date})"
|
159
|
+
puts *note.lines(72).map {|l| " #{l}".rstrip}
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
135
163
|
def paginated_output
|
136
164
|
stdout = $stdout
|
137
165
|
if @tty && pager = pickler.config["pager"]
|
@@ -144,6 +172,7 @@ class Pickler
|
|
144
172
|
else
|
145
173
|
yield
|
146
174
|
end
|
175
|
+
rescue Errno::EPIPE
|
147
176
|
ensure
|
148
177
|
$stdout = stdout
|
149
178
|
end
|
@@ -177,9 +206,9 @@ class Pickler
|
|
177
206
|
when 0
|
178
207
|
puts "#{pickler.project_id} #{pickler.project.name}"
|
179
208
|
when 1
|
180
|
-
story = pickler.
|
209
|
+
story = pickler.story(args.first)
|
181
210
|
paginated_output do
|
182
|
-
|
211
|
+
puts_full story
|
183
212
|
end
|
184
213
|
else
|
185
214
|
too_many
|
@@ -200,17 +229,28 @@ class Pickler
|
|
200
229
|
end
|
201
230
|
end
|
202
231
|
[:requester, :owner, :mywork].each do |o|
|
203
|
-
on "--#{o}
|
204
|
-
modifications[o] = value
|
232
|
+
on "--#{o}[=USERNAME]" do |value|
|
233
|
+
modifications[o] = value || pickler.real_name
|
205
234
|
end
|
206
235
|
end
|
207
236
|
on "--[no-]includedone", "include accepted stories" do |value|
|
208
237
|
modifications[:includedone] = value
|
238
|
+
@iterations ||= []
|
239
|
+
@iterations << :done?
|
240
|
+
end
|
241
|
+
|
242
|
+
on "-b", "--backlog", "filter results to future iterations" do |c|
|
243
|
+
@iterations ||= []
|
244
|
+
@iterations << :backlog?
|
209
245
|
end
|
210
246
|
|
211
|
-
attr_writer :current
|
212
247
|
on "-c", "--current", "filter results to current iteration" do |b|
|
213
|
-
|
248
|
+
@iterations ||= []
|
249
|
+
@iterations << :current?
|
250
|
+
end
|
251
|
+
|
252
|
+
on "--[no-]full", "show full story, not a summary line" do |b|
|
253
|
+
@full = b
|
214
254
|
end
|
215
255
|
|
216
256
|
process do |*argv|
|
@@ -221,10 +261,19 @@ class Pickler
|
|
221
261
|
else
|
222
262
|
stories = pickler.project.stories(*argv)
|
223
263
|
end
|
224
|
-
|
264
|
+
if @iterations && @iterations != [:done?]
|
265
|
+
stories.reject! {|s| !@iterations.any? {|i| s.send(i)}}
|
266
|
+
end
|
225
267
|
paginated_output do
|
268
|
+
first = true
|
226
269
|
stories.each do |story|
|
227
|
-
|
270
|
+
if @full
|
271
|
+
puts unless first
|
272
|
+
puts_full story
|
273
|
+
else
|
274
|
+
puts_summary story
|
275
|
+
end
|
276
|
+
first = false
|
228
277
|
end
|
229
278
|
end
|
230
279
|
end
|
@@ -237,6 +286,13 @@ class Pickler
|
|
237
286
|
Upload the given story or all features with a tracker url in a comment on the
|
238
287
|
first line.
|
239
288
|
EOF
|
289
|
+
|
290
|
+
process do |*args|
|
291
|
+
args.replace(pickler.local_features) if args.empty?
|
292
|
+
args.each do |arg|
|
293
|
+
pickler.feature(arg).push
|
294
|
+
end
|
295
|
+
end
|
240
296
|
end
|
241
297
|
|
242
298
|
command :pull do
|
@@ -247,6 +303,13 @@ Download the given story or all well formed stories to the features/ directory.
|
|
247
303
|
Previously unseen stories will be given a numeric filename that you are
|
248
304
|
encouraged to change.
|
249
305
|
EOF
|
306
|
+
|
307
|
+
process do |*args|
|
308
|
+
args.replace(pickler.scenario_features) if args.empty?
|
309
|
+
args.each do |arg|
|
310
|
+
pickler.feature(arg).pull
|
311
|
+
end
|
312
|
+
end
|
250
313
|
end
|
251
314
|
|
252
315
|
command :start do
|
@@ -254,12 +317,11 @@ encouraged to change.
|
|
254
317
|
summary "Pull a story and mark it started"
|
255
318
|
description <<-EOF
|
256
319
|
Pull a given story and change its state to started. If basename is given
|
257
|
-
and no local file exists, features/basename.feature will be created
|
258
|
-
of features/id.feature.
|
320
|
+
and no local file exists, features/basename.feature will be created.
|
259
321
|
EOF
|
260
322
|
|
261
|
-
process do |
|
262
|
-
pickler.start(
|
323
|
+
process do |story, *args|
|
324
|
+
pickler.feature(story).start(args.first)
|
263
325
|
end
|
264
326
|
end
|
265
327
|
|
@@ -267,8 +329,8 @@ of features/id.feature.
|
|
267
329
|
banner_arguments "<story>"
|
268
330
|
summary "Push a story and mark it finished"
|
269
331
|
|
270
|
-
process do |
|
271
|
-
|
332
|
+
process do |story|
|
333
|
+
pickler.feature(story).finish
|
272
334
|
end
|
273
335
|
end
|
274
336
|
|
@@ -288,6 +350,34 @@ of features/id.feature.
|
|
288
350
|
end
|
289
351
|
end
|
290
352
|
|
353
|
+
command :unstart do
|
354
|
+
banner_arguments "[story] ..."
|
355
|
+
summary "Mark stories unstarted"
|
356
|
+
on "--all-started", "unstart all started stories" do
|
357
|
+
@all = true
|
358
|
+
end
|
359
|
+
process do |*args|
|
360
|
+
if @all
|
361
|
+
pickler.project.stories(:state => "started").each do |story|
|
362
|
+
story.transition!('unstarted')
|
363
|
+
end
|
364
|
+
end
|
365
|
+
args.each do |arg|
|
366
|
+
pickler.story(arg).transition!('unstarted')
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
command :unschedule do
|
372
|
+
banner_arguments "[story] ..."
|
373
|
+
summary "Move stories to icebox"
|
374
|
+
process do |*args|
|
375
|
+
args.each do |arg|
|
376
|
+
pickler.story(arg).transition!('unscheduled')
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
291
381
|
command :browse do
|
292
382
|
banner_arguments "[story]"
|
293
383
|
summary "Open a story in the web browser"
|
@@ -324,6 +414,15 @@ Requires launchy (gem install launchy).
|
|
324
414
|
end
|
325
415
|
end
|
326
416
|
|
417
|
+
command :comment do
|
418
|
+
banner_arguments "<story> <paragraph> ..."
|
419
|
+
summary "Post a comment to a story"
|
420
|
+
|
421
|
+
process do |story, *paragraphs|
|
422
|
+
pickler.story(story).comment!(paragraphs.join("\n\n"))
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
327
426
|
def initialize(argv)
|
328
427
|
@argv = argv
|
329
428
|
end
|
data/lib/pickler/tracker/note.rb
CHANGED
@@ -18,6 +18,10 @@ class Pickler
|
|
18
18
|
"#<#{self.class.inspect}:#{id.inspect}, story_id: #{story.id.inspect}, date: #{date.inspect}, author: #{author.inspect}, text: #{text.inspect}>"
|
19
19
|
end
|
20
20
|
|
21
|
+
def lines(width = 79)
|
22
|
+
text.scan(/(?:.{0,#{width}}|\S+?)(?:\s|$)/).map! {|line| line.strip}[0..-2]
|
23
|
+
end
|
24
|
+
|
21
25
|
end
|
22
26
|
end
|
23
27
|
end
|
@@ -5,22 +5,27 @@ class Pickler
|
|
5
5
|
TYPES = %w(bug feature chore release)
|
6
6
|
STATES = %w(unscheduled unstarted started finished delivered rejected accepted)
|
7
7
|
|
8
|
-
attr_reader :project, :iteration
|
9
|
-
reader :url
|
8
|
+
attr_reader :project, :iteration, :labels
|
9
|
+
reader :url
|
10
10
|
date_reader :created_at, :accepted_at, :deadline
|
11
11
|
accessor :current_state, :name, :description, :owned_by, :requested_by, :story_type
|
12
12
|
|
13
13
|
def initialize(project, attributes = {})
|
14
14
|
@project = project
|
15
|
-
@iteration = Iteration.new(project, attributes["iteration"]) if attributes["iteration"]
|
16
15
|
super(attributes)
|
16
|
+
@iteration = Iteration.new(project, @attributes["iteration"]) if @attributes["iteration"]
|
17
|
+
@labels = normalize_labels(@attributes["labels"])
|
18
|
+
end
|
19
|
+
|
20
|
+
def labels=(value)
|
21
|
+
@labels = normalize_labels(value)
|
17
22
|
end
|
18
23
|
|
19
24
|
def transition!(state)
|
20
25
|
raise Pickler::Tracker::Error, "Invalid state #{state}", caller unless STATES.include?(state)
|
21
26
|
self.current_state = state
|
22
27
|
if id
|
23
|
-
xml = "<story><
|
28
|
+
xml = "<story><current_state>#{state}</current_state></story>"
|
24
29
|
error = tracker.request_xml(:put, resource_url, xml).fetch("errors",{})["error"] || true
|
25
30
|
else
|
26
31
|
error = save
|
@@ -28,10 +33,33 @@ class Pickler
|
|
28
33
|
raise Pickler::Tracker::Error, Array(error).join("\n"), caller unless error == true
|
29
34
|
end
|
30
35
|
|
36
|
+
def finish
|
37
|
+
case story_type
|
38
|
+
when "bug", "feature"
|
39
|
+
self.current_state = "finished" unless complete?
|
40
|
+
when "chore", "release"
|
41
|
+
self.current_state = "accepted"
|
42
|
+
end
|
43
|
+
current_state
|
44
|
+
end
|
45
|
+
|
46
|
+
def finish!
|
47
|
+
transition!(finish)
|
48
|
+
end
|
49
|
+
|
50
|
+
def backlog?(as_of = Date.today)
|
51
|
+
iteration && iteration.start >= as_of
|
52
|
+
end
|
53
|
+
|
31
54
|
def current?(as_of = Date.today)
|
32
55
|
iteration && iteration.include?(as_of)
|
33
56
|
end
|
34
57
|
|
58
|
+
# In a previous iteration
|
59
|
+
def done?(as_of = Date.today)
|
60
|
+
iteration && iteration.finish <= as_of
|
61
|
+
end
|
62
|
+
|
35
63
|
def complete?
|
36
64
|
%w(finished delivered accepted).include?(current_state)
|
37
65
|
end
|
@@ -87,22 +115,27 @@ class Pickler
|
|
87
115
|
end
|
88
116
|
|
89
117
|
def comment!(body)
|
90
|
-
raise ArgumentError if body.strip.empty? || body.size > 5000
|
91
118
|
response = tracker.request_xml(:post, "#{resource_url}/notes",{:text => body}.to_xml(:dasherize => false, :root => 'note'))
|
92
|
-
|
119
|
+
if response["note"]
|
120
|
+
Note.new(self, response["note"])
|
121
|
+
else
|
122
|
+
raise Pickler::Tracker::Error, Array(response["errors"]["error"]).join("\n"), caller
|
123
|
+
end
|
93
124
|
end
|
94
125
|
|
95
|
-
def to_xml
|
126
|
+
def to_xml(force_labels = true)
|
96
127
|
hash = @attributes.reject do |k,v|
|
97
128
|
!%w(current_state deadline description estimate name owned_by requested_by story_type).include?(k)
|
98
129
|
end
|
99
|
-
|
130
|
+
if force_labels || !id || normalize_labels(@attributes["labels"]) != labels
|
131
|
+
hash["labels"] = labels.join(", ")
|
132
|
+
end
|
100
133
|
hash.to_xml(:dasherize => false, :root => "story")
|
101
134
|
end
|
102
135
|
|
103
136
|
def destroy
|
104
137
|
if id
|
105
|
-
response = tracker.request_xml(:delete, "/projects/#{project.id}/stories/#{id}",
|
138
|
+
response = tracker.request_xml(:delete, "/projects/#{project.id}/stories/#{id}", "")
|
106
139
|
raise Error, response["message"], caller if response["success"] != "true"
|
107
140
|
@attributes["id"] = nil
|
108
141
|
self
|
@@ -114,7 +147,7 @@ class Pickler
|
|
114
147
|
end
|
115
148
|
|
116
149
|
def save
|
117
|
-
response = tracker.request_xml(id ? :put : :post, resource_url, to_xml)
|
150
|
+
response = tracker.request_xml(id ? :put : :post, resource_url, to_xml(false))
|
118
151
|
if response["success"] == "true"
|
119
152
|
initialize(project, response["story"])
|
120
153
|
true
|
@@ -123,6 +156,11 @@ class Pickler
|
|
123
156
|
end
|
124
157
|
end
|
125
158
|
|
159
|
+
private
|
160
|
+
def normalize_labels(value)
|
161
|
+
Array(value).join(", ").strip.split(/\s*,\s*/)
|
162
|
+
end
|
163
|
+
|
126
164
|
end
|
127
165
|
end
|
128
166
|
end
|
data/lib/pickler.rb
CHANGED
@@ -47,6 +47,41 @@ class Pickler
|
|
47
47
|
self.class.config.merge(@config)
|
48
48
|
end
|
49
49
|
|
50
|
+
def real_name
|
51
|
+
config["real_name"] || (require 'etc'; Etc.getpwuid.gecos.split(',').first)
|
52
|
+
end
|
53
|
+
|
54
|
+
def new_story(attributes = {}, &block)
|
55
|
+
attributes = attributes.inject('requested_by' => real_name) do |h,(k,v)|
|
56
|
+
h.update(k.to_s => v)
|
57
|
+
end
|
58
|
+
project.new_story(attributes, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
def stories(*args)
|
62
|
+
project.stories(*args)
|
63
|
+
end
|
64
|
+
|
65
|
+
def name
|
66
|
+
project.name
|
67
|
+
end
|
68
|
+
|
69
|
+
def iteration_length
|
70
|
+
project.iteration_length
|
71
|
+
end
|
72
|
+
|
73
|
+
def point_scale
|
74
|
+
project.point_scale
|
75
|
+
end
|
76
|
+
|
77
|
+
def week_start_day
|
78
|
+
project.week_start_day
|
79
|
+
end
|
80
|
+
|
81
|
+
def deliver_all_finished_stories
|
82
|
+
project.deliver_all_finished_stories
|
83
|
+
end
|
84
|
+
|
50
85
|
def parser
|
51
86
|
require 'cucumber'
|
52
87
|
require "cucumber/treetop_parser/feature_#@lang"
|
@@ -75,51 +110,26 @@ class Pickler
|
|
75
110
|
Cucumber.language['scenario']
|
76
111
|
end
|
77
112
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
113
|
+
def local_features
|
114
|
+
Dir[features_path('**','*.feature')].map {|f|feature(f)}.select {|f|f.id}
|
115
|
+
end
|
116
|
+
|
117
|
+
def scenario_features
|
118
|
+
project.stories(scenario_word, :includedone => true).reject do |s|
|
119
|
+
%(unscheduled unstarted).include?(s.current_state)
|
120
|
+
end.select do |s|
|
121
|
+
s.to_s =~ /^\s*#{Regexp.escape(scenario_word)}:/ && parser.parse(s.to_s)
|
83
122
|
end
|
84
123
|
end
|
85
124
|
|
86
125
|
def feature(string)
|
87
|
-
Feature.new(self,string)
|
126
|
+
string.kind_of?(Feature) ? string : Feature.new(self,string)
|
88
127
|
end
|
89
128
|
|
90
129
|
def story(string)
|
91
130
|
feature(string).story
|
92
131
|
end
|
93
132
|
|
94
|
-
def pull(*args)
|
95
|
-
if args.empty?
|
96
|
-
args = project.stories(scenario_word, :includedone => true).reject do |s|
|
97
|
-
%(unscheduled unstarted).include?(s.current_state)
|
98
|
-
end.select do |s|
|
99
|
-
s.to_s =~ /^\s*#{Regexp.escape(scenario_word)}:/ && parser.parse(s.to_s)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
args.each do |arg|
|
103
|
-
feature(arg).pull
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def start(arg, default = nil)
|
108
|
-
feature(arg).start(default)
|
109
|
-
end
|
110
|
-
|
111
|
-
def push(*args)
|
112
|
-
features(*args).each do |feature|
|
113
|
-
feature.push
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def finish(*args)
|
118
|
-
features(*args).each do |feature|
|
119
|
-
feature.finish
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
133
|
protected
|
124
134
|
|
125
135
|
end
|
data/pickler.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "pickler"
|
3
|
-
s.version = "0.0.
|
3
|
+
s.version = "0.0.6"
|
4
4
|
|
5
5
|
s.summary = "PIvotal traCKer Liaison to cucumbER"
|
6
6
|
s.description = "Synchronize between Cucumber and Pivotal Tracker"
|
@@ -25,4 +25,6 @@ Gem::Specification.new do |s|
|
|
25
25
|
]
|
26
26
|
s.add_dependency("activesupport", [">= 2.0.0"])
|
27
27
|
s.add_dependency("cucumber", [">= 0.1.9"])
|
28
|
+
s.add_dependency("builder")
|
29
|
+
s.add_dependency("xml-simple")
|
28
30
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tpope-pickler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Pope
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-12-15 00:00:00 -08:00
|
13
13
|
default_executable: pickler
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -30,6 +30,24 @@ dependencies:
|
|
30
30
|
- !ruby/object:Gem::Version
|
31
31
|
version: 0.1.9
|
32
32
|
version:
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: builder
|
35
|
+
version_requirement:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: "0"
|
41
|
+
version:
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: xml-simple
|
44
|
+
version_requirement:
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
33
51
|
description: Synchronize between Cucumber and Pivotal Tracker
|
34
52
|
email: ruby@tpope.info
|
35
53
|
executables:
|