tpope-pickler 0.0.0
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/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
|
+
|