storable 0.5.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.
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
+