tpope-pickler 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +43 -0
- data/bin/pickler +7 -0
- data/lib/pickler/tracker/project.rb +46 -0
- data/lib/pickler/tracker/story.rb +90 -0
- data/lib/pickler/tracker.rb +77 -0
- data/lib/pickler.rb +194 -0
- data/pickler.gemspec +24 -0
- metadata +77 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Tim Pope
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
= Pickler
|
2
|
+
|
3
|
+
Synchronize user stories in Pivotal Tracker with Cucumber features.
|
4
|
+
|
5
|
+
== Getting started
|
6
|
+
|
7
|
+
echo "api_token: ..." > ~/.tracker.yml
|
8
|
+
echo "project_id: ..." > ~/my/app/features/tracker.yml
|
9
|
+
|
10
|
+
For details about the Pivotal Tracker API, including where to find your API
|
11
|
+
token and project id, see http://www.pivotaltracker.com/help/api .
|
12
|
+
|
13
|
+
The pull and push commands map the story's name into the "Feature: ..." line
|
14
|
+
and the story's description with an additional two space indent into the
|
15
|
+
feature's body. Keep this in mind when entering stories into Pivotal Tracker.
|
16
|
+
|
17
|
+
== Usage
|
18
|
+
|
19
|
+
pickler pull
|
20
|
+
|
21
|
+
Download all well formed stories to the features/ directory. Previously
|
22
|
+
unseen stories will be given a numeric filename that you are encouraged to
|
23
|
+
change.
|
24
|
+
|
25
|
+
pickler push
|
26
|
+
|
27
|
+
Upload all features with a tracker url in a comment on the first line.
|
28
|
+
|
29
|
+
pickler search <query>
|
30
|
+
|
31
|
+
List all stories matching the given query.
|
32
|
+
|
33
|
+
pickler show <id>
|
34
|
+
|
35
|
+
Show details for the story referenced by the id.
|
36
|
+
|
37
|
+
== Disclaimer
|
38
|
+
|
39
|
+
No warranties, expressed or implied.
|
40
|
+
|
41
|
+
Notably, the push and pull commands are quite happy to blindly clobber
|
42
|
+
features if so instructed. Pivotal Tracker has a history to recover things
|
43
|
+
server side.
|
data/bin/pickler
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
class Pickler
|
2
|
+
class Tracker
|
3
|
+
class Project < Abstract
|
4
|
+
|
5
|
+
attr_reader :tracker
|
6
|
+
reader :point_scale, :week_start_day, :name, :iteration_length
|
7
|
+
|
8
|
+
def initialize(tracker, attributes = {})
|
9
|
+
@tracker = tracker
|
10
|
+
super(attributes)
|
11
|
+
end
|
12
|
+
|
13
|
+
def story(story_id)
|
14
|
+
Story.new(self,tracker.get_xml("/projects/#{id}/stories/#{story_id}")["story"])
|
15
|
+
end
|
16
|
+
|
17
|
+
def stories(*args)
|
18
|
+
filter = encode_term(args) if args.any?
|
19
|
+
path = "/projects/#{id}/stories"
|
20
|
+
path << "?filter=#{CGI.escape(filter)}" if filter
|
21
|
+
response = tracker.get_xml(path)
|
22
|
+
[response["stories"]["story"]].flatten.compact.map {|s| Story.new(self,s)}
|
23
|
+
end
|
24
|
+
|
25
|
+
def new_story(attributes = {}, &block)
|
26
|
+
Story.new(self, attributes, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def deliver_all_finished_stories
|
30
|
+
request_xml(:put,"/projects/#{id}/stories_deliver_all_finished")
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def encode_term(term)
|
35
|
+
case term
|
36
|
+
when Array then term.map {|v| encode_term(v)}.join(" ")
|
37
|
+
when Hash then term.map {|k,v| encode_term("#{k}:#{v}")}.join(" ")
|
38
|
+
when /^\S+$/, Symbol then term
|
39
|
+
when /^(\S+?):(.*)$/ then %{#$1:"#$2"}
|
40
|
+
else %{"#{term}"}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
class Pickler
|
2
|
+
class Tracker
|
3
|
+
class Story < Abstract
|
4
|
+
|
5
|
+
TYPES = %w(bug feature chore release)
|
6
|
+
STATES = %w(unstarted started finished delivered rejected accepted)
|
7
|
+
|
8
|
+
attr_reader :project
|
9
|
+
reader :created_at, :iteration, :url, :labels
|
10
|
+
accessor :current_state, :name, :description, :estimate, :owned_by, :requested_by, :story_type
|
11
|
+
|
12
|
+
def initialize(project, attributes = {})
|
13
|
+
@project = project
|
14
|
+
super(attributes)
|
15
|
+
end
|
16
|
+
|
17
|
+
def tracker
|
18
|
+
project.tracker
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
to_s = "# #{url}\n#{story_type.capitalize}: #{name}\n"
|
23
|
+
description_lines.each do |line|
|
24
|
+
to_s << " #{line}".rstrip << "\n"
|
25
|
+
end
|
26
|
+
to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s=(body)
|
30
|
+
body = body.sub(/\A# .*\n/,'')
|
31
|
+
if body =~ /\A(\w+): (.*)/
|
32
|
+
self.story_type = $1.downcase
|
33
|
+
self.name = $2
|
34
|
+
description = $'
|
35
|
+
else
|
36
|
+
self.story_type = "feature"
|
37
|
+
self.name = body[/.*/]
|
38
|
+
description = $'
|
39
|
+
end
|
40
|
+
self.description = description.gsub(/\A\n+|\n+\Z/,'') + "\n"
|
41
|
+
if description_lines.all? {|l| l.empty? || l =~ /^ /}
|
42
|
+
self.description.gsub!(/^ /,'')
|
43
|
+
end
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def description_lines
|
48
|
+
array = []
|
49
|
+
description.to_s.each_line do |line|
|
50
|
+
array << line.chomp
|
51
|
+
end
|
52
|
+
array
|
53
|
+
end
|
54
|
+
|
55
|
+
def notes
|
56
|
+
[@attributes["notes"]].flatten.compact
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_xml
|
60
|
+
hash = @attributes.except("id","url","iteration","notes","labels")
|
61
|
+
hash["labels"] = Array(@attributes["labels"]).join(", ")
|
62
|
+
hash.to_xml(:root => "story")
|
63
|
+
end
|
64
|
+
|
65
|
+
def destroy
|
66
|
+
if id
|
67
|
+
request = tracker.request_xml(:delete, "/projects/#{project.id}/stories/#{id}", to_xml)
|
68
|
+
raise Error, request["message"], caller if request["success"] != "true"
|
69
|
+
@attributes["id"] = nil
|
70
|
+
self
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def save
|
75
|
+
if id
|
76
|
+
request = tracker.request_xml(:put, "/projects/#{project.id}/stories/#{id}", to_xml)
|
77
|
+
else
|
78
|
+
request = tracker.request_xml(:post, "/projects/#{project.id}/stories", to_xml)
|
79
|
+
end
|
80
|
+
if request["success"] == "true"
|
81
|
+
initialize(project, request["story"])
|
82
|
+
true
|
83
|
+
else
|
84
|
+
Array(request["errors"]["error"])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
class Pickler
|
2
|
+
class Tracker
|
3
|
+
|
4
|
+
ADDRESS = 'www.pivotaltracker.com'
|
5
|
+
BASE_PATH = '/services/v1'
|
6
|
+
SEARCH_KEYS = %w(label type state requester owner mywork id includedone)
|
7
|
+
|
8
|
+
class Error < Pickler::Error; end
|
9
|
+
|
10
|
+
attr_reader :token
|
11
|
+
|
12
|
+
def initialize(token)
|
13
|
+
require 'active_support'
|
14
|
+
@token = token
|
15
|
+
end
|
16
|
+
|
17
|
+
def request(method, path, *args)
|
18
|
+
require 'net/http'
|
19
|
+
Net::HTTP.start(ADDRESS) do |http|
|
20
|
+
headers = {
|
21
|
+
"Token" => @token,
|
22
|
+
"Accept" => "application/xml",
|
23
|
+
"Content-type" => "application/xml"
|
24
|
+
}
|
25
|
+
klass = Net::HTTP.const_get(method.to_s.capitalize)
|
26
|
+
http.request(klass.new("#{BASE_PATH}#{path}", headers), *args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def request_xml(method, path, *args)
|
31
|
+
response = request(method,path,*args)
|
32
|
+
raise response.inspect if response["Content-type"].split(/; */).first != "application/xml"
|
33
|
+
Hash.from_xml(response.body)["response"]
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_xml(path)
|
37
|
+
response = request_xml(:get, path)
|
38
|
+
unless response["success"] == "true"
|
39
|
+
if response["message"]
|
40
|
+
raise Error, response["message"], caller
|
41
|
+
else
|
42
|
+
raise "#{path}: #{response.inspect}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
response
|
46
|
+
end
|
47
|
+
|
48
|
+
def project(id)
|
49
|
+
Project.new(self,get_xml("/projects/#{id}")["project"].merge("id" => id.to_i))
|
50
|
+
end
|
51
|
+
|
52
|
+
class Abstract
|
53
|
+
def initialize(attributes)
|
54
|
+
@attributes = (attributes || {}).stringify_keys
|
55
|
+
yield self if block_given?
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.reader(*methods)
|
59
|
+
methods.each do |method|
|
60
|
+
define_method(method) { @attributes[method.to_s] }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.accessor(*methods)
|
65
|
+
reader(*methods)
|
66
|
+
methods.each do |method|
|
67
|
+
define_method("#{method}=") { |v| @attributes[method.to_s] = v }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
reader :id
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
require 'pickler/tracker/project'
|
77
|
+
require 'pickler/tracker/story'
|
data/lib/pickler.rb
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class Pickler
|
4
|
+
|
5
|
+
class Error < RuntimeError
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.config
|
9
|
+
@config ||= {'api_token' => ENV["TRACKER_API_TOKEN"]}.merge(
|
10
|
+
if File.exist?(path = File.expand_path('~/.tracker.yml'))
|
11
|
+
YAML.load_file(path)
|
12
|
+
end || {}
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.run(argv)
|
17
|
+
pickler = new(Dir.getwd)
|
18
|
+
|
19
|
+
case first = argv.shift
|
20
|
+
when 'show', /^\d+$/
|
21
|
+
story = pickler.project.story(first == 'show' ? argv.shift : first)
|
22
|
+
puts story
|
23
|
+
when 'search'
|
24
|
+
stories = pickler.project.stories(*argv).group_by {|s| s.current_state}
|
25
|
+
first = true
|
26
|
+
states = Tracker::Story::STATES
|
27
|
+
states -= %w(unstarted accepted) if argv.empty?
|
28
|
+
states.each do |state|
|
29
|
+
next unless stories[state]
|
30
|
+
puts unless first
|
31
|
+
first = false
|
32
|
+
puts state.upcase
|
33
|
+
puts '-' * state.length
|
34
|
+
stories[state].each do |story|
|
35
|
+
puts "[#{story.id}] #{story.story_type.capitalize}: #{story.name}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
when 'push'
|
39
|
+
pickler.push(*argv)
|
40
|
+
when 'pull'
|
41
|
+
pickler.pull(*argv)
|
42
|
+
when 'help', '--help', '-h', '', nil
|
43
|
+
puts 'pickler commands: show <id>, search <query>, push, pull'
|
44
|
+
else
|
45
|
+
$stderr.puts "pickler: unknown command #{first}"
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
rescue Pickler::Error
|
49
|
+
$stderr.puts "#$!"
|
50
|
+
exit 1
|
51
|
+
rescue Interrupt
|
52
|
+
$stderr.puts "Interrupted!"
|
53
|
+
exit 130
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_reader :directory
|
57
|
+
|
58
|
+
def initialize(path = '.')
|
59
|
+
@lang = 'en'
|
60
|
+
@directory = File.expand_path(path)
|
61
|
+
until File.directory?(File.join(@directory,'features'))
|
62
|
+
if @directory == File.dirname(@directory)
|
63
|
+
raise Error, 'Project not found. Make sure you have a features/ directory.', caller
|
64
|
+
end
|
65
|
+
@directory = File.dirname(@directory)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def features_path(*subdirs)
|
70
|
+
File.join(@directory,'features',*subdirs)
|
71
|
+
end
|
72
|
+
|
73
|
+
def config_file
|
74
|
+
features_path('tracker.yml')
|
75
|
+
end
|
76
|
+
|
77
|
+
def config
|
78
|
+
@config ||= File.exist?(config_file) && YAML.load_file(config_file) || {}
|
79
|
+
self.class.config.merge(@config)
|
80
|
+
end
|
81
|
+
|
82
|
+
def parser
|
83
|
+
require 'cucumber'
|
84
|
+
require "cucumber/treetop_parser/feature_#@lang"
|
85
|
+
Cucumber.load_language(@lang)
|
86
|
+
@parser ||= Cucumber::TreetopParser::FeatureParser.new
|
87
|
+
end
|
88
|
+
|
89
|
+
def project_id
|
90
|
+
config["project_id"] || (self.class.config["projects"]||{})[File.basename(@directory)]
|
91
|
+
end
|
92
|
+
|
93
|
+
def project
|
94
|
+
@project ||= Dir.chdir(@directory) do
|
95
|
+
unless token = config['api_token']
|
96
|
+
raise Error, 'echo api_token: ... > ~/.tracker.yml'
|
97
|
+
end
|
98
|
+
unless id = project_id
|
99
|
+
raise Error, 'echo project_id: ... > features/tracker.yml'
|
100
|
+
end
|
101
|
+
Tracker.new(token).project(id)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def scenario_word
|
106
|
+
parser
|
107
|
+
Cucumber.language['scenario']
|
108
|
+
end
|
109
|
+
|
110
|
+
def remote_features
|
111
|
+
project.stories(scenario_word, :includedone => true).select do |s|
|
112
|
+
s.to_s =~ /^\s*#{Regexp.escape(scenario_word)}:/ && parser.parse(s.to_s)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def local_features
|
117
|
+
Dir[features_path('**','*.feature')].map {|f| LocalFeature.new(self,f)}
|
118
|
+
end
|
119
|
+
|
120
|
+
def pull
|
121
|
+
l = local_features
|
122
|
+
remote_features.each do |remote|
|
123
|
+
body = "# http://www.pivotaltracker.com/story/show/#{remote.id}\n" <<
|
124
|
+
normalize_feature(remote.to_s)
|
125
|
+
if local = l.detect {|f| f.id == remote.id}
|
126
|
+
filename = local.filename
|
127
|
+
else
|
128
|
+
next if remote.current_state == 'unstarted'
|
129
|
+
filename = features_path("#{remote.id}.feature")
|
130
|
+
end
|
131
|
+
File.open(filename,'w') {|f| f.puts body}
|
132
|
+
end
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
|
136
|
+
def push
|
137
|
+
local_features.select do |local|
|
138
|
+
next unless local.id
|
139
|
+
remote = local.story
|
140
|
+
next if remote.to_s == local.to_s
|
141
|
+
remote.to_s = local.to_s
|
142
|
+
remote.save
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
protected
|
147
|
+
|
148
|
+
def normalize_feature(body)
|
149
|
+
return body unless ast = parser.parse(body)
|
150
|
+
feature = ast.compile
|
151
|
+
new = ''
|
152
|
+
(feature.header.chomp << "\n").each_line do |l|
|
153
|
+
new << ' ' unless new.empty?
|
154
|
+
new << l.strip << "\n"
|
155
|
+
end
|
156
|
+
feature.scenarios.each do |scenario|
|
157
|
+
new << "\n Scenario: #{scenario.name}\n"
|
158
|
+
scenario.steps.each do |step|
|
159
|
+
new << " #{step.keyword} #{step.name}\n"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
new
|
163
|
+
end
|
164
|
+
|
165
|
+
class LocalFeature
|
166
|
+
attr_reader :pickler, :filename
|
167
|
+
|
168
|
+
def initialize(pickler, filename)
|
169
|
+
@pickler = pickler
|
170
|
+
@filename = filename
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_s
|
174
|
+
File.read(@filename)
|
175
|
+
end
|
176
|
+
|
177
|
+
def id
|
178
|
+
unless defined?(@id)
|
179
|
+
@id = if id = to_s[%r{#\s*http://www\.pivotaltracker\.com/\S*?/(\d+)},1]
|
180
|
+
id.to_i
|
181
|
+
end
|
182
|
+
end
|
183
|
+
@id
|
184
|
+
end
|
185
|
+
|
186
|
+
def story
|
187
|
+
@pickler.project.story(id) if id
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
require 'pickler/tracker'
|
data/pickler.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "pickler"
|
3
|
+
s.version = "0.0.0"
|
4
|
+
|
5
|
+
s.summary = "PIvotal traCKer Liaison to cucumbER"
|
6
|
+
s.description = "Synchronize between Cucumber and Pivotal Tracker"
|
7
|
+
s.authors = ["Tim Pope"]
|
8
|
+
s.email = "ruby@tpope.i"+'nfo'
|
9
|
+
s.homepage = "http://github.com/tpope/pickler"
|
10
|
+
s.default_executable = "pickler"
|
11
|
+
s.executables = ["pickler"]
|
12
|
+
s.files = [
|
13
|
+
"README.rdoc",
|
14
|
+
"MIT-LICENSE",
|
15
|
+
"pickler.gemspec",
|
16
|
+
"bin/pickler",
|
17
|
+
"lib/pickler.rb",
|
18
|
+
"lib/pickler/tracker.rb",
|
19
|
+
"lib/pickler/tracker/project.rb",
|
20
|
+
"lib/pickler/tracker/story.rb"
|
21
|
+
]
|
22
|
+
s.add_dependency("activesupport", [">= 2.0.0"])
|
23
|
+
s.add_dependency("cucumber", [">= 0.1.9"])
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tpope-pickler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tim Pope
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-10-27 00:00:00 -07:00
|
13
|
+
default_executable: pickler
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.0.0
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: cucumber
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 0.1.9
|
32
|
+
version:
|
33
|
+
description: Synchronize between Cucumber and Pivotal Tracker
|
34
|
+
email: ruby@tpope.info
|
35
|
+
executables:
|
36
|
+
- pickler
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files: []
|
40
|
+
|
41
|
+
files:
|
42
|
+
- README.rdoc
|
43
|
+
- MIT-LICENSE
|
44
|
+
- pickler.gemspec
|
45
|
+
- bin/pickler
|
46
|
+
- lib/pickler.rb
|
47
|
+
- lib/pickler/tracker.rb
|
48
|
+
- lib/pickler/tracker/project.rb
|
49
|
+
- lib/pickler/tracker/story.rb
|
50
|
+
has_rdoc: false
|
51
|
+
homepage: http://github.com/tpope/pickler
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
version:
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.2.0
|
73
|
+
signing_key:
|
74
|
+
specification_version: 2
|
75
|
+
summary: PIvotal traCKer Liaison to cucumbER
|
76
|
+
test_files: []
|
77
|
+
|