storable 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/CHANGES.txt +14 -0
  2. data/LICENSE.txt +19 -0
  3. data/README.rdoc +52 -0
  4. data/Rakefile +104 -0
  5. data/lib/storable.rb +294 -0
  6. data/storable.gemspec +55 -0
  7. metadata +84 -0
@@ -0,0 +1,14 @@
1
+ STORABLE, CHANGES
2
+
3
+
4
+ #### 0.5.1 (2009-05-07) #############################
5
+
6
+ * FIXED: Bug in from_hash which was incorrectly parsing some data types (incl. Integer)
7
+ * ADDED: bin/example
8
+ * ADDED: OrderedHash for Ruby 1.8.x (still unordered in JRuby. Need to investigate.)
9
+
10
+ #### 0.5 (2009-05-07) ###############################
11
+
12
+ * First public release. See commit history for solutious-stella, solutious-rudy,
13
+ and delano-delanotes for complete history of SysInfo (was SystemInfo).
14
+
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Delano Mandelbaum, Solutious Inc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,52 @@
1
+ = Storable - v0.5
2
+
3
+ Marshal Ruby classes into and out of multiple formats (yaml, json, csv, tsv)
4
+
5
+ == Example
6
+
7
+ require 'storable'
8
+
9
+ class Machine < Storable
10
+ field :environment # Define field names for Machine. The
11
+ field :role # default type is String, but you can
12
+ field :position => Integer # specify a type using a hash syntax.
13
+ end
14
+
15
+ mac1 = Machine.new # Instances of Machine have accessors
16
+ mac1.environment = "stage" # just like regular attributes.
17
+ mac1.role = "app"
18
+ mac1.position = 1
19
+
20
+ puts "# YAML", mac1.to_yaml # Note: the field order is maintained
21
+ puts "# CSV", mac1.to_csv # => stage,app,1
22
+ puts "# JSON", mac1.to_json # Note: field order not maintained.
23
+
24
+ mac2 = Machine.from_yaml(mac1.to_yaml)
25
+ puts mac2.environment # => "stage"
26
+ puts mac2.position.class # => Fixnum
27
+
28
+
29
+ == Installation
30
+
31
+ Via Rubygems, one of:
32
+
33
+ $ sudo gem install storable
34
+ $ sudo gem install delano-storable --source http://gems.github.com/
35
+
36
+ or via download:
37
+ * storable-latest.tar.gz[http://github.com/delano/storable/tarball/latest]
38
+ * storable-latest.zip[http://github.com/delano/storable/zipball/latest]
39
+
40
+
41
+ == Prerequisites
42
+
43
+ * Ruby 1.8, Ruby 1.9, or JRuby 1.2
44
+
45
+
46
+ == Credits
47
+
48
+ * Delano Mandelbaum (delano@solutious.com)
49
+
50
+ == License
51
+
52
+ See: LICENSE.txt
@@ -0,0 +1,104 @@
1
+ require 'rubygems'
2
+ require 'rake/clean'
3
+ require 'rake/gempackagetask'
4
+ require 'hanna/rdoctask'
5
+ require 'fileutils'
6
+ include FileUtils
7
+
8
+ task :default => :package
9
+
10
+ # CONFIG =============================================================
11
+
12
+ # Change the following according to your needs
13
+ README = "README.rdoc"
14
+ CHANGES = "CHANGES.txt"
15
+ LICENSE = "LICENSE.txt"
16
+
17
+ # Files and directories to be deleted when you run "rake clean"
18
+ CLEAN.include [ 'pkg', '*.gem', '.config']
19
+
20
+ # Virginia assumes your project and gemspec have the same name
21
+ name = (Dir.glob('*.gemspec') || ['virginia']).first.split('.').first
22
+ load "#{name}.gemspec"
23
+ version = @spec.version
24
+
25
+ # That's it! The following defaults should allow you to get started
26
+ # on other things.
27
+
28
+
29
+ # TESTS/SPECS =========================================================
30
+
31
+
32
+
33
+ # INSTALL =============================================================
34
+
35
+ Rake::GemPackageTask.new(@spec) do |p|
36
+ p.need_tar = true if RUBY_PLATFORM !~ /mswin/
37
+ end
38
+
39
+ task :release => [ :rdoc, :package ]
40
+ task :install => [ :rdoc, :package ] do
41
+ sh %{sudo gem install pkg/#{name}-#{version}.gem}
42
+ end
43
+ task :uninstall => [ :clean ] do
44
+ sh %{sudo gem uninstall #{name}}
45
+ end
46
+
47
+
48
+ # RUBYFORGE RELEASE / PUBLISH TASKS ==================================
49
+
50
+ if @spec.rubyforge_project
51
+ desc 'Publish website to rubyforge'
52
+ task 'publish:rdoc' => 'doc/index.html' do
53
+ sh "scp -rp doc/* rubyforge.org:/var/www/gforge-projects/#{name}/"
54
+ end
55
+
56
+ desc 'Public release to rubyforge'
57
+ task 'publish:gem' => [:package] do |t|
58
+ sh <<-end
59
+ rubyforge add_release -o Any -a #{CHANGES} -f -n #{README} #{name} #{name} #{@spec.version} pkg/#{name}-#{@spec.version}.gem &&
60
+ rubyforge add_file -o Any -a #{CHANGES} -f -n #{README} #{name} #{name} #{@spec.version} pkg/#{name}-#{@spec.version}.tgz
61
+ end
62
+ end
63
+ end
64
+
65
+
66
+
67
+ # RUBY DOCS TASK ==================================
68
+
69
+ Rake::RDocTask.new do |t|
70
+ t.rdoc_dir = 'doc'
71
+ t.title = @spec.summary
72
+ t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
73
+ t.options << '--charset' << 'utf-8'
74
+ t.rdoc_files.include(LICENSE)
75
+ t.rdoc_files.include(README)
76
+ t.rdoc_files.include(CHANGES)
77
+ #t.rdoc_files.include('bin/*')
78
+ t.rdoc_files.include('lib/**/*.rb')
79
+ end
80
+
81
+
82
+
83
+
84
+ #Hoe.new('rspec', Spec::VERSION::STRING) do |p|
85
+ # p.summary = Spec::VERSION::SUMMARY
86
+ # p.description = "Behaviour Driven Development for Ruby."
87
+ # p.rubyforge_name = 'rspec'
88
+ # p.developer('RSpec Development Team', 'rspec-devel@rubyforge.org')
89
+ # p.extra_dev_deps = [["cucumber",">= 0.1.13"]]
90
+ # p.remote_rdoc_dir = "rspec/#{Spec::VERSION::STRING}"
91
+ # p.rspec_options = ['--options', 'spec/spec.opts']
92
+ # p.history_file = 'History.rdoc'
93
+ # p.readme_file = 'README.rdoc'
94
+ # p.post_install_message = <<-POST_INSTALL_MESSAGE
95
+ ##{'*'*50}
96
+ #
97
+ # Thank you for installing rspec-#{Spec::VERSION::STRING}
98
+ #
99
+ # Please be sure to read History.rdoc and Upgrade.rdoc
100
+ # for useful information about this release.
101
+ #
102
+ #{'*'*50}
103
+ #POST_INSTALL_MESSAGE
104
+ #end
@@ -0,0 +1,294 @@
1
+ #--
2
+ # TODO: Handle nested hashes and arrays.
3
+ # TODO: to_xml, see: http://codeforpeople.com/lib/ruby/xx/xx-2.0.0/README
4
+ #++
5
+
6
+
7
+ USE_ORDERED_HASH = (RUBY_VERSION =~ /1.9/).nil?
8
+ require 'orderedhash' if USE_ORDERED_HASH
9
+ require 'json' rescue nil
10
+
11
+ require 'yaml'
12
+ require 'fileutils'
13
+
14
+ # Storable makes data available in multiple formats and can
15
+ # re-create objects from files. Fields are defined using the
16
+ # Storable.field method which tells Storable the order and
17
+ # name.
18
+ class Storable
19
+ unless defined?(SUPPORTED_FORMATS) # We can assume all are defined
20
+ VERSION = 0.5
21
+ NICE_TIME_FORMAT = "%Y-%m-%d@%H:%M:%S".freeze
22
+ SUPPORTED_FORMATS = [:tsv, :csv, :yaml, :json, :s, :string].freeze
23
+ end
24
+
25
+ # This value will be used as a default unless provided on-the-fly.
26
+ # See SUPPORTED_FORMATS for available values.
27
+ attr_reader :format
28
+
29
+ # See SUPPORTED_FORMATS for available values
30
+ def format=(v)
31
+ v &&= v.to_sym
32
+ raise "Unsupported format: #{v}" unless SUPPORTED_FORMATS.member?(v)
33
+ @format = v
34
+ end
35
+
36
+ def postprocess
37
+ end
38
+
39
+ # TODO: from_args([HASH or ordered params])
40
+
41
+ # Accepts field definitions in the one of the follow formats:
42
+ #
43
+ # field :product
44
+ # field :product => Integer
45
+ #
46
+ # The order they're defined determines the order the will be output. The fields
47
+ # data is available by the standard accessors, class.product and class.product= etc...
48
+ # The value of the field will be cast to the type (if provided) when read from a file.
49
+ # The value is not touched when the type is not provided.
50
+ def self.field(args={})
51
+ # TODO: Examine casting from: http://codeforpeople.com/lib/ruby/fattr/fattr-1.0.3/
52
+ args = {args => nil} unless args.kind_of?(Hash)
53
+
54
+ args.each_pair do |m,t|
55
+
56
+ [[:@@field_names, m], [:@@field_types, t]].each do |tuple|
57
+ class_variable_set(tuple[0], []) unless class_variable_defined?(tuple[0])
58
+ class_variable_set(tuple[0], class_variable_get(tuple[0]) << tuple[1])
59
+ end
60
+
61
+ next if method_defined?(m)
62
+
63
+ define_method(m) do instance_variable_get("@#{m}") end
64
+ define_method("#{m}=") do |val|
65
+ instance_variable_set("@#{m}",val)
66
+ end
67
+ end
68
+ end
69
+
70
+ # Returns an array of field names defined by self.field
71
+ def self.field_names
72
+ class_variable_get(:@@field_names)
73
+ end
74
+ # Returns an array of field names defined by self.field
75
+ def field_names
76
+ self.class.send(:class_variable_get, :@@field_names)
77
+ end
78
+ # Returns an array of field types defined by self.field. Fields that did
79
+ # not receive a type are set to nil.
80
+ def self.field_types
81
+ class_variable_get(:@@field_types)
82
+ end
83
+ # Returns an array of field types defined by self.field. Fields that did
84
+ # not receive a type are set to nil.
85
+ def field_types
86
+ self.class.send(:class_variable_get, :@@field_types)
87
+ end
88
+
89
+ # Dump the object data to the given format.
90
+ def dump(format=nil, with_titles=false)
91
+ format &&= format.to_sym
92
+ format ||= 's' # as in, to_s
93
+ raise "Format not defined (#{format})" unless SUPPORTED_FORMATS.member?(format)
94
+ send("to_#{format}", with_titles)
95
+ end
96
+
97
+ def to_string(*args)
98
+ to_s(*args)
99
+ end
100
+
101
+ # Create a new instance of the object using data from file.
102
+ def self.from_file(file_path, format='yaml')
103
+ raise "Cannot read file (#{file_path})" unless File.exists?(file_path)
104
+ raise "#{self} doesn't support from_#{format}" unless self.respond_to?("from_#{format}")
105
+ format = format || File.extname(file_path).tr('.', '')
106
+ me = send("from_#{format}", read_file_to_array(file_path))
107
+ me.format = format
108
+ me
109
+ end
110
+ # Write the object data to the given file.
111
+ def to_file(file_path=nil, with_titles=true)
112
+ raise "Cannot store to nil path" if file_path.nil?
113
+ format = File.extname(file_path).tr('.', '')
114
+ format &&= format.to_sym
115
+ format ||= @format
116
+ Storable.write_file(file_path, dump(format, with_titles))
117
+ end
118
+
119
+ # Create a new instance of the object from a hash.
120
+ def self.from_hash(from={})
121
+ return nil if !from || from.empty?
122
+ me = self.new
123
+
124
+ fnames = field_names
125
+ fnames.each_with_index do |key,index|
126
+
127
+ stored_value = from[key] || from[key.to_s] # support for symbol keys and string keys
128
+
129
+ # TODO: Correct this horrible implementation (sorry, me. It's just one of those days.)
130
+
131
+ if field_types[index] == Array
132
+ ((value ||= []) << stored_value).flatten
133
+ elsif field_types[index].kind_of?(Hash)
134
+
135
+ value = stored_value
136
+ else
137
+
138
+ # SimpleDB stores attribute shit as lists of values
139
+ ##value = stored_value.first if stored_value.is_a?(Array) && stored_value.size == 1
140
+ value = (stored_value.is_a?(Array) && stored_value.size == 1) ? stored_value.first : stored_value
141
+
142
+ if field_types[index] == Time
143
+ value = Time.parse(value)
144
+ elsif field_types[index] == DateTime
145
+ value = DateTime.parse(value)
146
+ elsif field_types[index] == TrueClass
147
+ value = (value.to_s == "true")
148
+ elsif field_types[index] == Float
149
+ value = value.to_f
150
+ elsif field_types[index] == Integer
151
+ value = value.to_i
152
+ elsif field_types[index].kind_of?(Storable) && stored_value.kind_of?(Hash)
153
+ # I don't know why this is here so I'm going to raise an exception
154
+ # and wait a while for an error in one of my other projects.
155
+ #value = field_types[index].from_hash(stored_value)
156
+ raise "Delano, delano, delano. Clean up Storable!"
157
+ end
158
+ end
159
+
160
+ me.send("#{key}=", value) if self.method_defined?("#{key}=")
161
+ end
162
+
163
+ me.postprocess
164
+
165
+ me
166
+ end
167
+ # Return the object data as a hash
168
+ # +with_titles+ is ignored.
169
+ def to_hash(with_titles=true)
170
+ tmp = USE_ORDERED_HASH ? OrderedHash.new : {}
171
+ field_names.each do |fname|
172
+ tmp[fname] = self.send(fname)
173
+ end
174
+ tmp
175
+ end
176
+
177
+ # Create a new instance of the object from YAML.
178
+ # +from+ a YAML String or Array (split into by line).
179
+ def self.from_yaml(*from)
180
+ from_str = [from].flatten.compact.join('')
181
+ hash = YAML::load(from_str)
182
+ hash = from_hash(hash) if hash.kind_of?(Hash)
183
+ hash
184
+ end
185
+ def to_yaml(with_titles=true)
186
+ to_hash.to_yaml
187
+ end
188
+
189
+ # Create a new instance of the object from a JSON string.
190
+ # +from+ a JSON string split into an array by line.
191
+ def self.from_json(from=[])
192
+ # from is an array of strings
193
+ from_str = from.join('')
194
+ tmp = JSON::load(from_str)
195
+ hash_sym = tmp.keys.inject({}) do |hash, key|
196
+ hash[key.to_sym] = tmp[key]
197
+ hash
198
+ end
199
+ hash_sym = from_hash(hash_sym) if hash_sym.kind_of?(Hash)
200
+ hash_sym
201
+ end
202
+ def to_json(with_titles=true)
203
+ to_hash.to_json
204
+ end
205
+
206
+ # Return the object data as a delimited string.
207
+ # +with_titles+ specifiy whether to include field names (default: false)
208
+ # +delim+ is the field delimiter.
209
+ def to_delimited(with_titles=false, delim=',')
210
+ values = []
211
+ field_names.each do |fname|
212
+ values << self.send(fname.to_s) # TODO: escape values
213
+ end
214
+ output = values.join(delim)
215
+ output = field_names.join(delim) << $/ << output if with_titles
216
+ output
217
+ end
218
+ # Return the object data as a tab delimited string.
219
+ # +with_titles+ specifiy whether to include field names (default: false)
220
+ def to_tsv(with_titles=false)
221
+ to_delimited(with_titles, "\t")
222
+ end
223
+ # Return the object data as a comma delimited string.
224
+ # +with_titles+ specifiy whether to include field names (default: false)
225
+ def to_csv(with_titles=false)
226
+ to_delimited(with_titles, ',')
227
+ end
228
+ # Create a new instance from tab-delimited data.
229
+ # +from+ a JSON string split into an array by line.
230
+ def self.from_tsv(from=[])
231
+ self.from_delimited(from, "\t")
232
+ end
233
+ # Create a new instance of the object from comma-delimited data.
234
+ # +from+ a JSON string split into an array by line.
235
+ def self.from_csv(from=[])
236
+ self.from_delimited(from, ',')
237
+ end
238
+
239
+ # Create a new instance of the object from a delimited string.
240
+ # +from+ a JSON string split into an array by line.
241
+ # +delim+ is the field delimiter.
242
+ def self.from_delimited(from=[],delim=',')
243
+ return if from.empty?
244
+ # We grab an instance of the class so we can
245
+ hash = {}
246
+
247
+ fnames = values = []
248
+ if (from.size > 1 && !from[1].empty?)
249
+ fnames = from[0].chomp.split(delim)
250
+ values = from[1].chomp.split(delim)
251
+ else
252
+ fnames = self.field_names
253
+ values = from[0].chomp.split(delim)
254
+ end
255
+
256
+ fnames.each_with_index do |key,index|
257
+ next unless values[index]
258
+ hash[key.to_sym] = values[index]
259
+ end
260
+ hash = from_hash(hash) if hash.kind_of?(Hash)
261
+ hash
262
+ end
263
+
264
+ def self.read_file_to_array(path)
265
+ contents = []
266
+ return contents unless File.exists?(path)
267
+
268
+ open(path, 'r') do |l|
269
+ contents = l.readlines
270
+ end
271
+
272
+ contents
273
+ end
274
+
275
+ def self.write_file(path, content, flush=true)
276
+ write_or_append_file('w', path, content, flush)
277
+ end
278
+
279
+ def self.append_file(path, content, flush=true)
280
+ write_or_append_file('a', path, content, flush)
281
+ end
282
+
283
+ def self.write_or_append_file(write_or_append, path, content = '', flush = true)
284
+ #STDERR.puts "Writing to #{ path }..."
285
+ create_dir(File.dirname(path))
286
+
287
+ open(path, write_or_append) do |f|
288
+ f.puts content
289
+ f.flush if flush;
290
+ end
291
+ File.chmod(0600, path)
292
+ end
293
+ end
294
+
@@ -0,0 +1,55 @@
1
+ @spec = Gem::Specification.new do |s|
2
+ s.name = "storable"
3
+ s.rubyforge_project = "storable"
4
+ s.version = "0.5.1"
5
+ s.summary = "Storable: Marshal Ruby classes into and out of multiple formats (yaml, json, csv, tsv)"
6
+ s.description = s.summary
7
+ s.author = "Delano Mandelbaum"
8
+ s.email = "delano@solutious.com"
9
+ s.homepage = "http://solutious.com/"
10
+
11
+
12
+ # = EXECUTABLES =
13
+ # The list of executables in your project (if any). Don't include the path,
14
+ # just the base filename.
15
+ s.executables = %w[]
16
+
17
+ # = DEPENDENCIES =
18
+ # Add all gem dependencies
19
+ s.add_dependency 'sysinfo', '>= 0.5.0'
20
+
21
+ # = MANIFEST =
22
+ # The complete list of files to be included in the release. When GitHub packages your gem,
23
+ # it doesn't allow you to run any command that accesses the filesystem. You will get an
24
+ # error. You can ask your VCS for the list of versioned files:
25
+ # git ls-files
26
+ # svn list -R
27
+ s.files = %w(
28
+ CHANGES.txt
29
+ LICENSE.txt
30
+ README.rdoc
31
+ Rakefile
32
+ lib/storable.rb
33
+ storable.gemspec
34
+ )
35
+
36
+ s.extra_rdoc_files = %w[README.rdoc LICENSE.txt]
37
+ s.has_rdoc = true
38
+ s.rdoc_options = ["--line-numbers", "--title", s.summary, "--main", "README.rdoc"]
39
+ s.require_paths = %w[lib]
40
+ s.rubygems_version = '1.3.0'
41
+
42
+ if s.respond_to? :specification_version then
43
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
44
+ s.specification_version = 2
45
+
46
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
47
+ s.add_runtime_dependency(%q<RedCloth>, [">= 4.0.4"])
48
+ else
49
+ s.add_dependency(%q<RedCloth>, [">= 4.0.4"])
50
+ end
51
+ else
52
+ s.add_dependency(%q<RedCloth>, [">= 4.0.4"])
53
+ end
54
+
55
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: storable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.1
5
+ platform: ruby
6
+ authors:
7
+ - Delano Mandelbaum
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-07 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: sysinfo
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.5.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: RedCloth
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 4.0.4
34
+ version:
35
+ description: "Storable: Marshal Ruby classes into and out of multiple formats (yaml, json, csv, tsv)"
36
+ email: delano@solutious.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ - LICENSE.txt
44
+ files:
45
+ - CHANGES.txt
46
+ - LICENSE.txt
47
+ - README.rdoc
48
+ - Rakefile
49
+ - lib/storable.rb
50
+ - storable.gemspec
51
+ has_rdoc: true
52
+ homepage: http://solutious.com/
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options:
57
+ - --line-numbers
58
+ - --title
59
+ - "Storable: Marshal Ruby classes into and out of multiple formats (yaml, json, csv, tsv)"
60
+ - --main
61
+ - README.rdoc
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project: storable
79
+ rubygems_version: 1.3.2
80
+ signing_key:
81
+ specification_version: 2
82
+ summary: "Storable: Marshal Ruby classes into and out of multiple formats (yaml, json, csv, tsv)"
83
+ test_files: []
84
+