storable 0.9.pre.RC2 → 0.10.pre.RC2
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.
- checksums.yaml +4 -4
- data/README.md +7 -7
- data/lib/core_ext.rb +1 -8
- data/lib/storable.rb +101 -102
- data/storable.gemspec +2 -7
- metadata +6 -8
- data/Rakefile +0 -62
- data/lib/proc_source.rb +0 -321
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d468ae4b4c603f57f1181c2621c009468bd6a9675d6fd3d3fa93d31eaf6aa3c
|
4
|
+
data.tar.gz: 99a96fb2cb80836c7be1ccfd2d7ac7dae4adbefb90db6fd3a40376eb1ce43db6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 625916584f044d82d6bab287ae2cad8218f02f358e622a142140349cea32c6b0152457f946bd41ac75c723a851633176bfce378468e65f8414ebe3d73c500cd7
|
7
|
+
data.tar.gz: 139253c3cd97dbf50d2c3a561ba26f6bb8fc2d892874d9cd70ac6c0eb5bf93a9569c2769ec4396ebe2837ada3fcd106b131d904afabbe92e57e9a0e74baae1e5
|
data/README.md
CHANGED
@@ -78,8 +78,8 @@ Via Rubygems, one of:
|
|
78
78
|
$ sudo gem install delano-storable --source http://gems.github.com/
|
79
79
|
|
80
80
|
or via download:
|
81
|
-
* storable-latest.tar.gz
|
82
|
-
* storable-latest.zip
|
81
|
+
* [storable-latest.tar.gz](http://github.com/delano/storable/tarball/latest)
|
82
|
+
* [storable-latest.zip](http://github.com/delano/storable/zipball/latest)
|
83
83
|
|
84
84
|
|
85
85
|
## Prerequisites
|
@@ -90,20 +90,20 @@ or via download:
|
|
90
90
|
## Credits
|
91
91
|
|
92
92
|
* Delano Mandelbaum (delano@solutious.com)
|
93
|
-
* lib/proc_source.rb is based on http://github.com/imedo/background
|
93
|
+
* lib/proc_source.rb is based on [imedo/background](http://github.com/imedo/background)
|
94
94
|
* OrderedHash implementation by Jan Molic
|
95
95
|
|
96
96
|
|
97
97
|
## Thanks
|
98
98
|
|
99
|
-
* Pierre Riteau (priteau
|
100
|
-
* notro
|
99
|
+
* Pierre Riteau ([priteau](https://github.com/priteau)) for bug fixes.
|
100
|
+
* [notro](https://github.com/notro) for proc_source improvements.
|
101
101
|
|
102
102
|
|
103
103
|
## More Info
|
104
104
|
|
105
|
-
* GitHub
|
105
|
+
* [GitHub](http://github.com/delano/storable)
|
106
106
|
|
107
107
|
## License
|
108
108
|
|
109
|
-
See:
|
109
|
+
See: [storable.gemspec](https://github.com/delano/storable/blob/main/storable.gemspec)
|
data/lib/core_ext.rb
CHANGED
@@ -1,21 +1,14 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
|
2
3
|
#
|
3
4
|
# RubyToken was removed in >= 2.7
|
4
5
|
# Direct copy from Ruby 2.6.6 source
|
5
6
|
#
|
6
|
-
|
7
|
-
# frozen_string_literal: false
|
8
|
-
#
|
9
7
|
# irb/ruby-token.rb - ruby tokens
|
10
8
|
# $Release Version: 0.9.6$
|
11
9
|
# $Revision$
|
12
10
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
13
11
|
#
|
14
|
-
# --
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# :stopdoc:
|
19
12
|
module RubyToken
|
20
13
|
EXPR_BEG = :EXPR_BEG
|
21
14
|
EXPR_MID = :EXPR_MID
|
data/lib/storable.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
|
2
2
|
|
3
|
-
YAJL_LOADED = begin
|
3
|
+
YAJL_LOADED = begin
|
4
4
|
require 'yajl'
|
5
5
|
true
|
6
6
|
rescue LoadError
|
@@ -19,7 +19,7 @@ require 'fileutils'
|
|
19
19
|
require 'time'
|
20
20
|
|
21
21
|
unless defined?(Boolean)
|
22
|
-
# Used in field definitions.
|
22
|
+
# Used in field definitions.
|
23
23
|
#
|
24
24
|
# field :name => Boolean
|
25
25
|
#
|
@@ -27,47 +27,46 @@ unless defined?(Boolean)
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# Storable makes data available in multiple formats and can
|
30
|
-
# re-create objects from files. Fields are defined using the
|
31
|
-
# Storable.field method which tells Storable the order and
|
30
|
+
# re-create objects from files. Fields are defined using the
|
31
|
+
# Storable.field method which tells Storable the order and
|
32
32
|
# name.
|
33
33
|
class Storable
|
34
34
|
USE_ORDERED_HASH = (RUBY_VERSION =~ /^1.9/).nil?
|
35
|
-
|
35
|
+
|
36
36
|
require 'storable/orderedhash' if USE_ORDERED_HASH
|
37
37
|
unless defined?(SUPPORTED_FORMATS) # We can assume all are defined
|
38
38
|
VERSION = "0.8.9"
|
39
|
-
NICE_TIME_FORMAT = "%Y-%m-%d@%H:%M:%S".freeze
|
40
|
-
SUPPORTED_FORMATS = [:tsv, :csv, :yaml, :json, :s, :string].freeze
|
39
|
+
NICE_TIME_FORMAT = "%Y-%m-%d@%H:%M:%S".freeze
|
40
|
+
SUPPORTED_FORMATS = [:tsv, :csv, :yaml, :json, :s, :string].freeze
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
@debug = false
|
44
44
|
class << self
|
45
45
|
attr_accessor :sensitive_fields, :field_names, :field_types, :field_opts, :debug
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
# Passes along fields to inherited classes
|
49
|
-
def self.inherited(obj)
|
50
|
-
unless Storable == self
|
49
|
+
def self.inherited(obj)
|
50
|
+
unless Storable == self
|
51
51
|
obj.sensitive_fields = self.sensitive_fields.clone if !self.sensitive_fields.nil?
|
52
52
|
obj.field_names = self.field_names.clone if !self.field_names.nil?
|
53
53
|
obj.field_types = self.field_types.clone if !self.field_types.nil?
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
57
|
# Accepts field definitions in the one of the follow formats:
|
58
58
|
#
|
59
59
|
# field :product
|
60
60
|
# field :product => Integer
|
61
61
|
# field :product do |val|
|
62
|
-
# # modify val before it's stored.
|
62
|
+
# # modify val before it's stored.
|
63
63
|
# end
|
64
64
|
#
|
65
65
|
# The order they're defined determines the order the will be output. The fields
|
66
66
|
# data is available by the standard accessors, class.product and class.product= etc...
|
67
|
-
# The value of the field will be cast to the type (if provided) when read from a file.
|
68
|
-
# The value is not touched when the type is not provided.
|
67
|
+
# The value of the field will be cast to the type (if provided) when read from a file.
|
68
|
+
# The value is not touched when the type is not provided.
|
69
69
|
def self.field(*args, &processor)
|
70
|
-
# TODO: Examine casting from: http://codeforpeople.com/lib/ruby/fattr/fattr-1.0.3/
|
71
70
|
field_definitions = {}
|
72
71
|
if args.first.kind_of?(Hash)
|
73
72
|
args.first.each_pair do |fname,klass|
|
@@ -80,10 +79,10 @@ class Storable
|
|
80
79
|
elsif Hash === opts
|
81
80
|
field_definitions[fname] = opts
|
82
81
|
else
|
83
|
-
raise ArgumentError, "Second argument must be a hash"
|
82
|
+
raise ArgumentError, "Second argument must be a hash"
|
84
83
|
end
|
85
84
|
end
|
86
|
-
|
85
|
+
|
87
86
|
self.field_names ||= []
|
88
87
|
self.field_types ||= {}
|
89
88
|
self.field_opts ||= {}
|
@@ -91,21 +90,21 @@ class Storable
|
|
91
90
|
self.field_names << fname
|
92
91
|
self.field_opts[fname] = opts
|
93
92
|
self.field_types[fname] = opts[:class] unless opts[:class].nil?
|
94
|
-
|
93
|
+
|
95
94
|
# This processor automatically converts a Proc object
|
96
|
-
# to a String of its source.
|
97
|
-
processor = proc_processor if opts[:class] == Proc && processor.nil?
|
98
|
-
|
95
|
+
# to a String of its source.
|
96
|
+
# processor = proc_processor if opts[:class] == Proc && processor.nil?
|
97
|
+
|
99
98
|
unless processor.nil?
|
100
99
|
define_method("_storable_processor_#{fname}", &processor)
|
101
100
|
end
|
102
|
-
|
101
|
+
|
103
102
|
if method_defined?(fname) # don't redefine the getter method
|
104
103
|
STDERR.puts "method exists: #{self}##{fname}" if Storable.debug
|
105
104
|
else
|
106
|
-
define_method(fname) do
|
105
|
+
define_method(fname) do
|
107
106
|
ret = instance_variable_get("@#{fname}")
|
108
|
-
if ret.nil?
|
107
|
+
if ret.nil?
|
109
108
|
if opts[:default]
|
110
109
|
ret = opts[:default]
|
111
110
|
elsif opts[:meth]
|
@@ -115,63 +114,63 @@ class Storable
|
|
115
114
|
ret
|
116
115
|
end
|
117
116
|
end
|
118
|
-
|
117
|
+
|
119
118
|
if method_defined?("#{fname}=") # don't redefine the setter methods
|
120
119
|
STDERR.puts "method exists: #{self}##{fname}=" if Storable.debug
|
121
120
|
else
|
122
|
-
define_method("#{fname}=") do |val|
|
121
|
+
define_method("#{fname}=") do |val|
|
123
122
|
instance_variable_set("@#{fname}",val)
|
124
123
|
end
|
125
124
|
end
|
126
125
|
end
|
127
126
|
end
|
128
|
-
|
127
|
+
|
129
128
|
def self.sensitive_fields(*args)
|
130
129
|
@sensitive_fields ||= []
|
131
130
|
@sensitive_fields.push *args unless args.empty?
|
132
131
|
@sensitive_fields
|
133
132
|
end
|
134
|
-
|
133
|
+
|
135
134
|
def self.sensitive_field?(name)
|
136
135
|
@sensitive_fields ||= []
|
137
136
|
@sensitive_fields.member?(name)
|
138
137
|
end
|
139
|
-
|
138
|
+
|
140
139
|
def self.has_field?(n)
|
141
140
|
field_names.member? n.to_sym
|
142
141
|
end
|
143
142
|
def has_field?(n)
|
144
143
|
self.class.field_names.member? n.to_sym
|
145
144
|
end
|
146
|
-
|
147
|
-
|
145
|
+
|
146
|
+
|
148
147
|
# This value will be used as a default unless provided on-the-fly.
|
149
148
|
# See SUPPORTED_FORMATS for available values.
|
150
149
|
attr_reader :format
|
151
|
-
|
150
|
+
|
152
151
|
# See SUPPORTED_FORMATS for available values
|
153
152
|
def format=(v)
|
154
153
|
v &&= v.to_sym
|
155
154
|
raise "Unsupported format: #{v}" unless SUPPORTED_FORMATS.member?(v)
|
156
155
|
@format = v
|
157
156
|
end
|
158
|
-
|
157
|
+
|
159
158
|
def postprocess
|
160
159
|
end
|
161
|
-
|
160
|
+
|
162
161
|
def sensitive?
|
163
162
|
@storable_sensitive == true
|
164
163
|
end
|
165
|
-
|
164
|
+
|
166
165
|
def sensitive!
|
167
166
|
@storable_sensitive = true
|
168
167
|
end
|
169
|
-
|
168
|
+
|
170
169
|
# Returns an array of field names defined by self.field
|
171
170
|
def field_names
|
172
171
|
self.class.field_names #|| self.class.ancestors.first.field_names
|
173
172
|
end
|
174
|
-
# Returns an array of field types defined by self.field. Fields that did
|
173
|
+
# Returns an array of field types defined by self.field. Fields that did
|
175
174
|
# not receive a type are set to nil.
|
176
175
|
def field_types
|
177
176
|
self.class.field_types #|| self.class.ancestors.first.field_types
|
@@ -179,21 +178,21 @@ class Storable
|
|
179
178
|
def sensitive_fields
|
180
179
|
self.class.sensitive_fields #|| self.class.ancestors.first.sensitive_fields
|
181
180
|
end
|
182
|
-
|
183
|
-
# Dump the object data to the given format.
|
181
|
+
|
182
|
+
# Dump the object data to the given format.
|
184
183
|
def dump(format=nil, with_titles=false)
|
185
184
|
format &&= format.to_sym
|
186
185
|
format ||= :s # as in, to_s
|
187
186
|
raise "Format not defined (#{format})" unless SUPPORTED_FORMATS.member?(format)
|
188
|
-
send("to_#{format}")
|
187
|
+
send("to_#{format}")
|
189
188
|
end
|
190
|
-
|
189
|
+
|
191
190
|
def to_string(*args)
|
192
191
|
# TODO: sensitive?
|
193
192
|
to_s(*args)
|
194
193
|
end
|
195
|
-
|
196
|
-
# Create a new instance of the object using data from file.
|
194
|
+
|
195
|
+
# Create a new instance of the object using data from file.
|
197
196
|
def self.from_file(file_path, format='yaml')
|
198
197
|
raise "Cannot read file (#{file_path})" unless File.exists?(file_path)
|
199
198
|
raise "#{self} doesn't support from_#{format}" unless self.respond_to?("from_#{format}")
|
@@ -202,7 +201,7 @@ class Storable
|
|
202
201
|
me.format = format
|
203
202
|
me
|
204
203
|
end
|
205
|
-
# Write the object data to the given file.
|
204
|
+
# Write the object data to the given file.
|
206
205
|
def to_file(file_path=nil, with_titles=true)
|
207
206
|
raise "Cannot store to nil path" if file_path.nil?
|
208
207
|
format = File.extname(file_path).tr('.', '')
|
@@ -210,7 +209,7 @@ class Storable
|
|
210
209
|
format ||= @format
|
211
210
|
Storable.write_file(file_path, dump(format, with_titles))
|
212
211
|
end
|
213
|
-
|
212
|
+
|
214
213
|
# Create a new instance of the object from a hash.
|
215
214
|
def self.from_hash(from={})
|
216
215
|
return nil if !from || from.empty?
|
@@ -220,22 +219,22 @@ class Storable
|
|
220
219
|
new.from_hash(from)
|
221
220
|
end
|
222
221
|
end
|
223
|
-
|
222
|
+
|
224
223
|
def init *args
|
225
224
|
from_array *args
|
226
225
|
end
|
227
|
-
|
226
|
+
|
228
227
|
def initialize *args
|
229
228
|
init *args
|
230
229
|
end
|
231
|
-
|
230
|
+
|
232
231
|
def from_array *from
|
233
232
|
(self.field_names || []).each_with_index do |n,index|
|
234
233
|
break if index >= from.size
|
235
234
|
send("#{n}=", from[index])
|
236
235
|
end
|
237
236
|
end
|
238
|
-
|
237
|
+
|
239
238
|
def self.from_array *from
|
240
239
|
from = from.flatten.compact
|
241
240
|
return nil if !from || from.empty?
|
@@ -244,7 +243,7 @@ class Storable
|
|
244
243
|
me.postprocess
|
245
244
|
me
|
246
245
|
end
|
247
|
-
|
246
|
+
|
248
247
|
def call(fname)
|
249
248
|
unless field_types[fname.to_sym] == Proc &&
|
250
249
|
Proc === self.send(fname)
|
@@ -252,16 +251,16 @@ class Storable
|
|
252
251
|
end
|
253
252
|
self.instance_eval &self.send(fname)
|
254
253
|
end
|
255
|
-
|
254
|
+
|
256
255
|
def from_hash(from={})
|
257
256
|
fnames = field_names
|
258
|
-
|
257
|
+
|
259
258
|
return from if fnames.nil? || fnames.empty?
|
260
259
|
fnames.each_with_index do |fname,index|
|
261
260
|
ftype = field_types[fname]
|
262
261
|
value_orig = from[fname.to_s] || from[fname.to_s.to_sym]
|
263
262
|
next if value_orig.nil?
|
264
|
-
|
263
|
+
|
265
264
|
if ( ftype == String or ftype == Symbol ) && value_orig.to_s.empty?
|
266
265
|
value = ''
|
267
266
|
elsif ftype == Array
|
@@ -270,7 +269,7 @@ class Storable
|
|
270
269
|
value = value_orig
|
271
270
|
elsif !ftype.nil?
|
272
271
|
value_orig = value_orig.first if Array === value_orig && value_orig.size == 1
|
273
|
-
|
272
|
+
|
274
273
|
if [Time, DateTime].member?(ftype)
|
275
274
|
value = ftype.parse(value_orig)
|
276
275
|
elsif [TrueClass, FalseClass, Boolean].member?(ftype)
|
@@ -299,26 +298,26 @@ class Storable
|
|
299
298
|
end
|
300
299
|
end
|
301
300
|
elsif ftype == Proc && String === value_orig
|
302
|
-
value = Proc.
|
301
|
+
value = Proc.new { "Procs can not be rehydrated as of v0.10" }
|
303
302
|
end
|
304
303
|
end
|
305
|
-
|
304
|
+
|
306
305
|
value = value_orig if value.nil?
|
307
|
-
|
306
|
+
|
308
307
|
if self.respond_to?("#{fname}=")
|
309
|
-
self.send("#{fname}=", value)
|
308
|
+
self.send("#{fname}=", value)
|
310
309
|
else
|
311
|
-
self.instance_variable_set("@#{fname}", value)
|
310
|
+
self.instance_variable_set("@#{fname}", value)
|
312
311
|
end
|
313
|
-
|
312
|
+
|
314
313
|
end
|
315
314
|
|
316
315
|
self.postprocess
|
317
316
|
self
|
318
317
|
end
|
319
|
-
|
318
|
+
|
320
319
|
# Return the object data as a hash
|
321
|
-
# +with_titles+ is ignored.
|
320
|
+
# +with_titles+ is ignored.
|
322
321
|
def to_hash
|
323
322
|
preprocess if respond_to? :preprocess
|
324
323
|
tmp = USE_ORDERED_HASH ? Storable::OrderedHash.new : {}
|
@@ -328,7 +327,7 @@ class Storable
|
|
328
327
|
v = self.send(fname)
|
329
328
|
v = process(fname, v) if has_processor?(fname)
|
330
329
|
if Array === v
|
331
|
-
v = v.collect { |v2| v2.kind_of?(Storable) ? v2.to_hash : v2 }
|
330
|
+
v = v.collect { |v2| v2.kind_of?(Storable) ? v2.to_hash : v2 }
|
332
331
|
end
|
333
332
|
tmp[fname] = v.kind_of?(Storable) ? v.to_hash : v
|
334
333
|
end
|
@@ -344,12 +343,12 @@ class Storable
|
|
344
343
|
v = self.send(fname)
|
345
344
|
v = process(fname, v) if has_processor?(fname)
|
346
345
|
if Array === v
|
347
|
-
v = v.collect { |v2| v2.kind_of?(Storable) ? v2.to_a : v2 }
|
346
|
+
v = v.collect { |v2| v2.kind_of?(Storable) ? v2.to_a : v2 }
|
348
347
|
end
|
349
348
|
v
|
350
349
|
end
|
351
350
|
end
|
352
|
-
|
351
|
+
|
353
352
|
def to_json(*from, &blk)
|
354
353
|
preprocess if respond_to? :preprocess
|
355
354
|
hash = to_hash
|
@@ -358,11 +357,11 @@ class Storable
|
|
358
357
|
ret
|
359
358
|
elsif JSON_LOADED
|
360
359
|
JSON.generate(hash, *from, &blk)
|
361
|
-
else
|
360
|
+
else
|
362
361
|
raise "no JSON parser loaded"
|
363
362
|
end
|
364
363
|
end
|
365
|
-
|
364
|
+
|
366
365
|
def to_yaml(*from, &blk)
|
367
366
|
preprocess if respond_to? :preprocess
|
368
367
|
to_hash.to_yaml(*from, &blk)
|
@@ -371,22 +370,22 @@ class Storable
|
|
371
370
|
def process(fname, val)
|
372
371
|
self.send :"_storable_processor_#{fname}", val
|
373
372
|
end
|
374
|
-
|
373
|
+
|
375
374
|
def has_processor?(fname)
|
376
375
|
self.respond_to? :"_storable_processor_#{fname}"
|
377
376
|
end
|
378
|
-
|
379
|
-
# Create a new instance of the object from YAML.
|
380
|
-
# +from+ a YAML String or Array (split into by line).
|
377
|
+
|
378
|
+
# Create a new instance of the object from YAML.
|
379
|
+
# +from+ a YAML String or Array (split into by line).
|
381
380
|
def self.from_yaml(*from)
|
382
381
|
from_str = [from].flatten.compact.join('')
|
383
382
|
hash = YAML::load(from_str)
|
384
383
|
hash = from_hash(hash) if Hash === hash
|
385
384
|
hash
|
386
385
|
end
|
387
|
-
|
388
|
-
# Create a new instance of the object from a JSON string.
|
389
|
-
# +from+ a YAML String or Array (split into by line).
|
386
|
+
|
387
|
+
# Create a new instance of the object from a JSON string.
|
388
|
+
# +from+ a YAML String or Array (split into by line).
|
390
389
|
def self.from_json(*from)
|
391
390
|
from_str = [from].flatten.compact.join('')
|
392
391
|
#from_str.force_encoding("ISO-8859-1")
|
@@ -402,11 +401,11 @@ class Storable
|
|
402
401
|
hash[key.to_sym] = tmp[key]
|
403
402
|
hash
|
404
403
|
end
|
405
|
-
hash_sym = from_hash(hash_sym) if hash_sym.kind_of?(Hash)
|
404
|
+
hash_sym = from_hash(hash_sym) if hash_sym.kind_of?(Hash)
|
406
405
|
hash_sym
|
407
406
|
end
|
408
|
-
|
409
|
-
# Return the object data as a delimited string.
|
407
|
+
|
408
|
+
# Return the object data as a delimited string.
|
410
409
|
# +with_titles+ specifiy whether to include field names (default: false)
|
411
410
|
# +delim+ is the field delimiter.
|
412
411
|
def to_delimited(with_titles=false, delim=',')
|
@@ -420,17 +419,17 @@ class Storable
|
|
420
419
|
output = field_names.join(delim) << $/ << output if with_titles
|
421
420
|
output
|
422
421
|
end
|
423
|
-
# Return the object data as a tab delimited string.
|
422
|
+
# Return the object data as a tab delimited string.
|
424
423
|
# +with_titles+ specifiy whether to include field names (default: false)
|
425
424
|
def to_tsv(with_titles=false)
|
426
425
|
to_delimited(with_titles, "\t")
|
427
426
|
end
|
428
|
-
# Return the object data as a comma delimited string.
|
427
|
+
# Return the object data as a comma delimited string.
|
429
428
|
# +with_titles+ specifiy whether to include field names (default: false)
|
430
429
|
def to_csv(with_titles=false)
|
431
430
|
to_delimited(with_titles, ',')
|
432
431
|
end
|
433
|
-
# Create a new instance from tab-delimited data.
|
432
|
+
# Create a new instance from tab-delimited data.
|
434
433
|
# +from+ a JSON string split into an array by line.
|
435
434
|
def self.from_tsv(from=[], sensitive=false)
|
436
435
|
self.from_delimited(from, "\t", sensitive)
|
@@ -440,7 +439,7 @@ class Storable
|
|
440
439
|
def self.from_csv(from=[], sensitive=false)
|
441
440
|
self.from_delimited(from, ',', sensitive)
|
442
441
|
end
|
443
|
-
|
442
|
+
|
444
443
|
# Create a new instance of the object from a delimited string.
|
445
444
|
# +from+ a JSON string split into an array by line.
|
446
445
|
# +delim+ is the field delimiter.
|
@@ -448,48 +447,48 @@ class Storable
|
|
448
447
|
return if from.empty?
|
449
448
|
from = from.split($/) if String === from
|
450
449
|
hash = {}
|
451
|
-
|
450
|
+
|
452
451
|
fnames = sensitive ? (field_names-sensitive_fields) : field_names
|
453
452
|
values = from[0].chomp.split(delim)
|
454
|
-
|
453
|
+
|
455
454
|
fnames.each_with_index do |key,index|
|
456
455
|
next unless values[index]
|
457
456
|
hash[key.to_sym] = values[index]
|
458
457
|
end
|
459
|
-
hash = from_hash(hash) if hash.kind_of?(Hash)
|
458
|
+
hash = from_hash(hash) if hash.kind_of?(Hash)
|
460
459
|
hash
|
461
460
|
end
|
462
461
|
|
463
462
|
def self.read_file_to_array(path)
|
464
463
|
contents = []
|
465
464
|
return contents unless File.exists?(path)
|
466
|
-
|
465
|
+
|
467
466
|
open(path, 'r') do |l|
|
468
467
|
contents = l.readlines
|
469
468
|
end
|
470
469
|
|
471
470
|
contents
|
472
471
|
end
|
473
|
-
|
472
|
+
|
474
473
|
def self.write_file(path, content, flush=true)
|
475
474
|
write_or_append_file('w', path, content, flush)
|
476
475
|
end
|
477
|
-
|
476
|
+
|
478
477
|
def self.append_file(path, content, flush=true)
|
479
478
|
write_or_append_file('a', path, content, flush)
|
480
479
|
end
|
481
|
-
|
480
|
+
|
482
481
|
def self.write_or_append_file(write_or_append, path, content = '', flush = true)
|
483
|
-
#STDERR.puts "Writing to #{ path }..."
|
482
|
+
#STDERR.puts "Writing to #{ path }..."
|
484
483
|
create_dir(File.dirname(path))
|
485
|
-
|
486
|
-
open(path, write_or_append) do |f|
|
484
|
+
|
485
|
+
open(path, write_or_append) do |f|
|
487
486
|
f.puts content
|
488
487
|
f.flush if flush;
|
489
488
|
end
|
490
489
|
File.chmod(0600, path)
|
491
490
|
end
|
492
|
-
|
491
|
+
|
493
492
|
class Anonymous
|
494
493
|
def initialize from
|
495
494
|
@hash = from
|
@@ -501,7 +500,7 @@ class Storable
|
|
501
500
|
@hash[meth.to_sym]
|
502
501
|
end
|
503
502
|
end
|
504
|
-
|
503
|
+
|
505
504
|
end
|
506
505
|
|
507
506
|
|
@@ -517,13 +516,13 @@ class Storable
|
|
517
516
|
#
|
518
517
|
module DefaultProcessors
|
519
518
|
# Replace a hash of Proc objects with a hash
|
520
|
-
# of
|
521
|
-
def hash_proc_processor
|
519
|
+
# of
|
520
|
+
def hash_proc_processor
|
522
521
|
Proc.new do |procs|
|
523
522
|
a = {}
|
524
523
|
unless procs.nil?
|
525
|
-
procs.each_pair { |n,v|
|
526
|
-
a[n] = (Proc === v) ? v.source : v
|
524
|
+
procs.each_pair { |n,v|
|
525
|
+
a[n] = (Proc === v) ? v.source : v
|
527
526
|
}
|
528
527
|
end
|
529
528
|
a
|
@@ -531,14 +530,14 @@ class Storable
|
|
531
530
|
end
|
532
531
|
def proc_processor
|
533
532
|
Proc.new do |val|
|
534
|
-
ret = (Proc === val) ? val.source : val
|
533
|
+
ret = (Proc === val) ? val.source : val
|
535
534
|
ret
|
536
535
|
end
|
537
536
|
end
|
538
537
|
# If the object already has a value for +@id+
|
539
538
|
# use it, otherwise return the current digest.
|
540
539
|
#
|
541
|
-
# This allows an object to have a preset ID.
|
540
|
+
# This allows an object to have a preset ID.
|
542
541
|
#
|
543
542
|
def gibbler_id_processor
|
544
543
|
Proc.new do |val|
|
data/storable.gemspec
CHANGED
@@ -1,25 +1,20 @@
|
|
1
1
|
@spec = Gem::Specification.new do |s|
|
2
2
|
s.name = "storable"
|
3
|
-
s.version = "0.
|
3
|
+
s.version = "0.10-RC2"
|
4
4
|
s.summary = "Ruby classes as strings"
|
5
5
|
s.description = "Storable: Marshal Ruby classes into and out of multiple formats (yaml, json, csv, tsv)"
|
6
6
|
s.authors = ["Delano Mandelbaum"]
|
7
7
|
s.email = "gems@solutious.com"
|
8
8
|
s.homepage = "https://github.com/delano/storable/"
|
9
9
|
s.licenses = ["MIT"] # https://spdx.org/licenses/MIT-Modern-Variant.html
|
10
|
+
s.executables = %w()
|
10
11
|
s.files = %w(
|
11
12
|
README.md
|
12
|
-
Rakefile
|
13
|
-
bin/example
|
14
|
-
bin/tryouts
|
15
13
|
lib/core_ext.rb
|
16
|
-
lib/proc_source.rb
|
17
14
|
lib/storable.rb
|
18
15
|
lib/storable/orderedhash.rb
|
19
16
|
storable.gemspec
|
20
17
|
)
|
21
|
-
s.executables = %w(
|
22
|
-
)
|
23
18
|
s.extra_rdoc_files = %w[README.md]
|
24
19
|
s.rdoc_options = ["--line-numbers", "--title", s.summary, "--main", "README.md"]
|
25
20
|
s.require_paths = %w[lib]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: storable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.pre.RC2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Delano Mandelbaum
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-04-03 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: 'Storable: Marshal Ruby classes into and out of multiple formats (yaml,
|
14
14
|
json, csv, tsv)'
|
@@ -19,9 +19,7 @@ extra_rdoc_files:
|
|
19
19
|
- README.md
|
20
20
|
files:
|
21
21
|
- README.md
|
22
|
-
- Rakefile
|
23
22
|
- lib/core_ext.rb
|
24
|
-
- lib/proc_source.rb
|
25
23
|
- lib/storable.rb
|
26
24
|
- lib/storable/orderedhash.rb
|
27
25
|
- storable.gemspec
|
@@ -29,7 +27,7 @@ homepage: https://github.com/delano/storable/
|
|
29
27
|
licenses:
|
30
28
|
- MIT
|
31
29
|
metadata: {}
|
32
|
-
post_install_message:
|
30
|
+
post_install_message:
|
33
31
|
rdoc_options:
|
34
32
|
- "--line-numbers"
|
35
33
|
- "--title"
|
@@ -49,8 +47,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
47
|
- !ruby/object:Gem::Version
|
50
48
|
version: 1.3.1
|
51
49
|
requirements: []
|
52
|
-
rubygems_version: 3.
|
53
|
-
signing_key:
|
50
|
+
rubygems_version: 3.3.26
|
51
|
+
signing_key:
|
54
52
|
specification_version: 4
|
55
53
|
summary: Ruby classes as strings
|
56
54
|
test_files: []
|
data/Rakefile
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake/clean'
|
3
|
-
require 'rubygems/package_task'
|
4
|
-
require 'fileutils'
|
5
|
-
require 'rdoc/task'
|
6
|
-
include FileUtils
|
7
|
-
|
8
|
-
task :default => :package
|
9
|
-
|
10
|
-
|
11
|
-
# CONFIG =============================================================
|
12
|
-
|
13
|
-
# Change the following according to your needs
|
14
|
-
README = "README.md"
|
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
|
-
task :test do
|
32
|
-
sh "try"
|
33
|
-
end
|
34
|
-
|
35
|
-
# INSTALL =============================================================
|
36
|
-
|
37
|
-
Gem::PackageTask.new(@spec) do |p|
|
38
|
-
p.need_tar = true if RUBY_PLATFORM !~ /mswin/
|
39
|
-
end
|
40
|
-
|
41
|
-
task :build => [ :test, :package ]
|
42
|
-
task :release => [ :rdoc, :package ]
|
43
|
-
task :install => [ :rdoc, :package ] do
|
44
|
-
sh %{sudo gem install pkg/#{name}-#{version}.gem}
|
45
|
-
end
|
46
|
-
task :uninstall => [ :clean ] do
|
47
|
-
sh %{sudo gem uninstall #{name}}
|
48
|
-
end
|
49
|
-
|
50
|
-
|
51
|
-
# RUBY DOCS TASK ==================================
|
52
|
-
|
53
|
-
RDoc::Task.new do |t|
|
54
|
-
t.rdoc_dir = 'doc'
|
55
|
-
t.title = @spec.summary
|
56
|
-
t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
57
|
-
t.options << '--charset' << 'utf-8'
|
58
|
-
t.rdoc_files.include(LICENSE)
|
59
|
-
t.rdoc_files.include(README)
|
60
|
-
#t.rdoc_files.include('bin/*')
|
61
|
-
t.rdoc_files.include('lib/**/*.rb')
|
62
|
-
end
|
data/lib/proc_source.rb
DELETED
@@ -1,321 +0,0 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
# Based on:
|
4
|
-
# https://github.com/imedo/background
|
5
|
-
# https://github.com/imedo/background_lite
|
6
|
-
# With improvements by:
|
7
|
-
# https://github.com/notro/storable
|
8
|
-
#
|
9
|
-
|
10
|
-
# RubyToken was removed in Ruby 2.7
|
11
|
-
if RUBY_VERSION < "2.7"
|
12
|
-
require 'irb/ruby-token'
|
13
|
-
else
|
14
|
-
require './lib/core_ext.rb'
|
15
|
-
end
|
16
|
-
|
17
|
-
require 'irb/ruby-lex'
|
18
|
-
require 'pry'
|
19
|
-
require 'stringio'
|
20
|
-
|
21
|
-
SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
|
22
|
-
|
23
|
-
|
24
|
-
class ProcString < String
|
25
|
-
# Filename where the proc is defined
|
26
|
-
attr_accessor :file
|
27
|
-
|
28
|
-
# Range of lines where the proc is defined. e.g. (12..16)
|
29
|
-
attr_accessor :lines
|
30
|
-
attr_accessor :arity, :kind
|
31
|
-
|
32
|
-
# Return a Proc object
|
33
|
-
# If #lines and #file is specified, these are tied to the proc.
|
34
|
-
def to_proc(kind="proc")
|
35
|
-
if @file && @lines
|
36
|
-
raise "#lines must be a range" unless @lines.kind_of? Range
|
37
|
-
result = eval("#{kind} #{self}", binding, @file, @lines.min)
|
38
|
-
else
|
39
|
-
result = eval("#{kind} #{self}")
|
40
|
-
end
|
41
|
-
result.source = self
|
42
|
-
result
|
43
|
-
end
|
44
|
-
|
45
|
-
# Return a lambda
|
46
|
-
def to_lambda
|
47
|
-
to_proc "lambda"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
class RubyToken::Token
|
52
|
-
# These EXPR_BEG tokens don't have associated end tags
|
53
|
-
FAKIES = [
|
54
|
-
RubyToken::TkWHEN,
|
55
|
-
RubyToken::TkELSIF,
|
56
|
-
RubyToken::TkELSE,
|
57
|
-
RubyToken::TkTHEN,
|
58
|
-
]
|
59
|
-
|
60
|
-
def name
|
61
|
-
@name ||= nil
|
62
|
-
end
|
63
|
-
|
64
|
-
def open_tag?
|
65
|
-
return false if name.nil? || get_props.nil?
|
66
|
-
is_open = (
|
67
|
-
(get_props[1] == RubyToken::EXPR_BEG) &&
|
68
|
-
(self.class.to_s !~ /_MOD/) && # ignore onliner if, unless, etc...
|
69
|
-
(!FAKIES.member?(self.class))
|
70
|
-
)
|
71
|
-
is_open
|
72
|
-
end
|
73
|
-
|
74
|
-
def get_props
|
75
|
-
RubyToken::TkReading2Token[name]
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
# Based heavily on code from http://github.com/imedo/background
|
81
|
-
# Big thanks to the imedo dev team!
|
82
|
-
#
|
83
|
-
module ProcSource
|
84
|
-
def self.find(filename, start_line=1, block_only=true)
|
85
|
-
lines, lexer = nil, nil
|
86
|
-
retried = 0
|
87
|
-
loop do
|
88
|
-
lines = get_lines(filename, start_line)
|
89
|
-
return nil if lines.nil?
|
90
|
-
if !line_has_open?(lines.join) && start_line >= 0
|
91
|
-
start_line -= 1 and retried +=1 and redo
|
92
|
-
end
|
93
|
-
lexer = RubyLex.new
|
94
|
-
lexer.set_input(StringIO.new(lines.join))
|
95
|
-
break
|
96
|
-
end
|
97
|
-
|
98
|
-
stoken, etoken, nesting = nil, nil, 0
|
99
|
-
|
100
|
-
binding.pry
|
101
|
-
if RUBY_VERSION < "2.7"
|
102
|
-
tokens = lexer.instance_variable_get '@OP'
|
103
|
-
else
|
104
|
-
lexer.lex
|
105
|
-
tokens = lexer.instance_variable_get '@tokens'
|
106
|
-
end
|
107
|
-
|
108
|
-
# tokens.each
|
109
|
-
|
110
|
-
while (token = lexer.token) do
|
111
|
-
if RubyToken::TkIDENTIFIER === token
|
112
|
-
# nothing
|
113
|
-
elsif token.open_tag? || RubyToken::TkfLBRACE === token
|
114
|
-
nesting += 1
|
115
|
-
stoken = token if nesting == 1
|
116
|
-
elsif RubyToken::TkEND === token || RubyToken::TkRBRACE === token
|
117
|
-
if nesting == 1
|
118
|
-
etoken = token
|
119
|
-
break
|
120
|
-
end
|
121
|
-
nesting -= 1
|
122
|
-
elsif RubyToken::TkLBRACE === token
|
123
|
-
nesting += 1
|
124
|
-
elsif RubyToken::TkBITOR === token && stoken
|
125
|
-
# nothing
|
126
|
-
elsif RubyToken::TkNL === token && stoken && etoken
|
127
|
-
break if nesting <= 0
|
128
|
-
else
|
129
|
-
# nothing
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
lines = lines[stoken.line_no-1 .. etoken.line_no-1]
|
134
|
-
|
135
|
-
# Remove the crud before the block definition.
|
136
|
-
if block_only
|
137
|
-
spaces = lines.last.match(/^\s+/)[0] rescue ''
|
138
|
-
lines[0] = spaces << lines[0][stoken.char_no .. -1]
|
139
|
-
end
|
140
|
-
ps = ProcString.new lines.join
|
141
|
-
ps.file = filename
|
142
|
-
ps.lines = start_line .. start_line+etoken.line_no-1
|
143
|
-
ps
|
144
|
-
end
|
145
|
-
|
146
|
-
# A hack for Ruby 1.9, otherwise returns true.
|
147
|
-
#
|
148
|
-
# Ruby 1.9 returns an incorrect line number
|
149
|
-
# when a block is specified with do/end. It
|
150
|
-
# happens b/c the line number returned by
|
151
|
-
# Ruby 1.9 is based on the first line in the
|
152
|
-
# block which contains a token (i.e. not a
|
153
|
-
# new line or comment etc...).
|
154
|
-
#
|
155
|
-
# NOTE: This won't work in cases where the
|
156
|
-
# incorrect line also contains a "do".
|
157
|
-
#
|
158
|
-
def self.line_has_open?(str)
|
159
|
-
return true unless RUBY_VERSION >= '1.9' && RUBY_VERSION < '2.0'
|
160
|
-
lexer = RubyLex.new
|
161
|
-
lexer.set_input(StringIO.new(str))
|
162
|
-
success = false
|
163
|
-
while token = lexer.token
|
164
|
-
case token
|
165
|
-
when RubyToken::TkNL
|
166
|
-
break
|
167
|
-
when RubyToken::TkDO
|
168
|
-
success = true
|
169
|
-
when RubyToken::TkfLBRACE
|
170
|
-
success = true
|
171
|
-
when RubyToken::TkCONSTANT
|
172
|
-
if token.name == "Proc" &&
|
173
|
-
lexer.token.is_a?(RubyToken::TkDOT)
|
174
|
-
method = lexer.token
|
175
|
-
if method.is_a?(RubyToken::TkIDENTIFIER) &&
|
176
|
-
method.name == "new"
|
177
|
-
success = true
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
success
|
183
|
-
end
|
184
|
-
|
185
|
-
def self.get_lines(filename, start_line = 1)
|
186
|
-
case filename
|
187
|
-
when nil
|
188
|
-
nil
|
189
|
-
|
190
|
-
# We're in irb
|
191
|
-
when "(irb)"
|
192
|
-
IRB.conf[:MAIN_CONTEXT].io.line(start_line .. -2)
|
193
|
-
|
194
|
-
# Or an eval
|
195
|
-
when /^\(eval.+\)$/
|
196
|
-
EVAL_LINES__[filename][start_line .. -2]
|
197
|
-
|
198
|
-
# Or most likely a .rb file
|
199
|
-
else
|
200
|
-
# Ruby already parsed this file? (see disclaimer above)
|
201
|
-
if defined?(SCRIPT_LINES__) && SCRIPT_LINES__[filename]
|
202
|
-
SCRIPT_LINES__[filename][(start_line - 1) .. -1]
|
203
|
-
|
204
|
-
# If the file exists we're going to try reading it in
|
205
|
-
elsif File.exist?(filename)
|
206
|
-
begin
|
207
|
-
File.readlines(filename)[(start_line - 1) .. -1]
|
208
|
-
rescue
|
209
|
-
nil
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
class Proc # :nodoc:
|
217
|
-
attr_writer :source
|
218
|
-
@@regexp = Regexp.new('^#<Proc:0x[0-9A-Fa-f]+@?\s*(.+):(\d+)(.+?)?>$')
|
219
|
-
|
220
|
-
def source_descriptor
|
221
|
-
return [@file, @line] if @file && @line
|
222
|
-
|
223
|
-
source_location = nil
|
224
|
-
if RUBY_VERSION >= '2.7'
|
225
|
-
source_location = *self.source_location
|
226
|
-
else
|
227
|
-
inspection = inspect
|
228
|
-
md = @@regexp.match(inspection)
|
229
|
-
exmsg = 'Unable to parse proc inspect (%s)' % inspection
|
230
|
-
raise Exception, exmsg if md.nil?
|
231
|
-
source_location = md.captures
|
232
|
-
end
|
233
|
-
|
234
|
-
file, line = *source_location
|
235
|
-
@file, @line = [file, line.to_i]
|
236
|
-
end
|
237
|
-
|
238
|
-
def source
|
239
|
-
@source ||= ProcSource.find(*self.source_descriptor)
|
240
|
-
end
|
241
|
-
|
242
|
-
def line
|
243
|
-
source_descriptor
|
244
|
-
@line
|
245
|
-
end
|
246
|
-
|
247
|
-
def file
|
248
|
-
source_descriptor
|
249
|
-
@file
|
250
|
-
end
|
251
|
-
|
252
|
-
# Dump to Marshal format.
|
253
|
-
# p = Proc.new { false }
|
254
|
-
# Marshal.dump p
|
255
|
-
def _dump(limit)
|
256
|
-
raise "can't dump proc, #source is nil" if source.nil?
|
257
|
-
str = Marshal.dump(source)
|
258
|
-
str
|
259
|
-
end
|
260
|
-
|
261
|
-
# Load from Marshal format.
|
262
|
-
# p = Proc.new { false }
|
263
|
-
# Marshal.load Marshal.dump p
|
264
|
-
def self._load(str)
|
265
|
-
@source = Marshal.load(str)
|
266
|
-
@source.to_proc
|
267
|
-
end
|
268
|
-
|
269
|
-
# Dump to JSON string
|
270
|
-
def to_json(*args)
|
271
|
-
raise "can't serialize proc, #source is nil" if source.nil?
|
272
|
-
{
|
273
|
-
'json_class' => self.class.name,
|
274
|
-
'data' => [source.to_s, source.file, source.lines.min, source.lines.max]
|
275
|
-
}.to_json#(*args)
|
276
|
-
end
|
277
|
-
|
278
|
-
def self.json_create(o)
|
279
|
-
s, file, min, max = o['data']
|
280
|
-
ps = ProcString.new s
|
281
|
-
ps.file = file
|
282
|
-
ps.lines = (min..max)
|
283
|
-
ps.to_proc
|
284
|
-
end
|
285
|
-
|
286
|
-
# Create a Proc object from a string of Ruby code.
|
287
|
-
# It's assumed the string contains do; end or { }.
|
288
|
-
#
|
289
|
-
# Proc.from_string("do; 2+2; end")
|
290
|
-
#
|
291
|
-
def self.from_string(str)
|
292
|
-
eval "Proc.new #{str}"
|
293
|
-
end
|
294
|
-
|
295
|
-
end
|
296
|
-
|
297
|
-
if $0 == __FILE__
|
298
|
-
def store(&blk)
|
299
|
-
@blk = blk
|
300
|
-
end
|
301
|
-
|
302
|
-
store do |blk|
|
303
|
-
puts "Hello Rudy1"
|
304
|
-
end
|
305
|
-
|
306
|
-
a = Proc.new() { |a|
|
307
|
-
puts "Hello Rudy2"
|
308
|
-
}
|
309
|
-
|
310
|
-
b = Proc.new() do |b|
|
311
|
-
puts { "Hello Rudy3" } if true
|
312
|
-
end
|
313
|
-
|
314
|
-
puts @blk.inspect, @blk.source
|
315
|
-
puts [a.inspect, a.source]
|
316
|
-
puts b.inspect, b.source
|
317
|
-
|
318
|
-
proc = @blk.source.to_proc
|
319
|
-
proc.call(1)
|
320
|
-
end
|
321
|
-
|