storable 0.8.6 → 0.9.pre.RC2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/{README.rdoc → README.md} +38 -37
- data/Rakefile +12 -65
- data/lib/core_ext.rb +273 -0
- data/lib/proc_source.rb +176 -67
- data/lib/storable.rb +57 -33
- data/storable.gemspec +22 -38
- metadata +36 -59
- data/CHANGES.txt +0 -156
- data/LICENSE.txt +0 -19
data/lib/proc_source.rb
CHANGED
@@ -1,113 +1,162 @@
|
|
1
|
-
|
1
|
+
|
2
|
+
#
|
2
3
|
# Based on:
|
3
|
-
#
|
4
|
-
|
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
|
5
16
|
|
6
|
-
require 'stringio'
|
7
17
|
require 'irb/ruby-lex'
|
8
|
-
|
18
|
+
require 'pry'
|
19
|
+
require 'stringio'
|
20
|
+
|
21
|
+
SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
|
22
|
+
|
9
23
|
|
10
24
|
class ProcString < String
|
11
|
-
|
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.
|
12
34
|
def to_proc(kind="proc")
|
13
|
-
|
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
|
14
41
|
result.source = self
|
15
42
|
result
|
16
43
|
end
|
44
|
+
|
45
|
+
# Return a lambda
|
17
46
|
def to_lambda
|
18
|
-
to_proc "
|
47
|
+
to_proc "lambda"
|
19
48
|
end
|
20
49
|
end
|
21
50
|
|
22
51
|
class RubyToken::Token
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
+
|
27
64
|
def open_tag?
|
28
|
-
return false if
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
33
72
|
end
|
34
|
-
|
73
|
+
|
35
74
|
def get_props
|
36
|
-
RubyToken::TkReading2Token[
|
75
|
+
RubyToken::TkReading2Token[name]
|
37
76
|
end
|
38
|
-
|
77
|
+
|
39
78
|
end
|
40
79
|
|
41
80
|
# Based heavily on code from http://github.com/imedo/background
|
42
81
|
# Big thanks to the imedo dev team!
|
43
82
|
#
|
44
83
|
module ProcSource
|
45
|
-
|
46
|
-
def self.find(filename, start_line=0, block_only=true)
|
84
|
+
def self.find(filename, start_line=1, block_only=true)
|
47
85
|
lines, lexer = nil, nil
|
48
86
|
retried = 0
|
49
87
|
loop do
|
50
88
|
lines = get_lines(filename, start_line)
|
51
89
|
return nil if lines.nil?
|
52
|
-
#p [start_line, lines[0]]
|
53
90
|
if !line_has_open?(lines.join) && start_line >= 0
|
54
|
-
start_line -= 1 and retried +=1 and redo
|
91
|
+
start_line -= 1 and retried +=1 and redo
|
55
92
|
end
|
56
93
|
lexer = RubyLex.new
|
57
94
|
lexer.set_input(StringIO.new(lines.join))
|
58
95
|
break
|
59
96
|
end
|
97
|
+
|
60
98
|
stoken, etoken, nesting = nil, nil, 0
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
64
111
|
if RubyToken::TkIDENTIFIER === token
|
65
|
-
#nothing
|
112
|
+
# nothing
|
66
113
|
elsif token.open_tag? || RubyToken::TkfLBRACE === token
|
67
114
|
nesting += 1
|
68
115
|
stoken = token if nesting == 1
|
69
116
|
elsif RubyToken::TkEND === token || RubyToken::TkRBRACE === token
|
70
117
|
if nesting == 1
|
71
|
-
etoken = token
|
118
|
+
etoken = token
|
72
119
|
break
|
73
120
|
end
|
74
121
|
nesting -= 1
|
122
|
+
elsif RubyToken::TkLBRACE === token
|
123
|
+
nesting += 1
|
75
124
|
elsif RubyToken::TkBITOR === token && stoken
|
76
|
-
#nothing
|
125
|
+
# nothing
|
77
126
|
elsif RubyToken::TkNL === token && stoken && etoken
|
78
127
|
break if nesting <= 0
|
79
128
|
else
|
80
|
-
#
|
129
|
+
# nothing
|
81
130
|
end
|
82
131
|
end
|
83
|
-
|
132
|
+
|
84
133
|
lines = lines[stoken.line_no-1 .. etoken.line_no-1]
|
85
|
-
|
86
|
-
# Remove the crud before the block definition.
|
134
|
+
|
135
|
+
# Remove the crud before the block definition.
|
87
136
|
if block_only
|
88
137
|
spaces = lines.last.match(/^\s+/)[0] rescue ''
|
89
138
|
lines[0] = spaces << lines[0][stoken.char_no .. -1]
|
90
139
|
end
|
91
140
|
ps = ProcString.new lines.join
|
92
|
-
ps.file
|
93
|
-
|
141
|
+
ps.file = filename
|
142
|
+
ps.lines = start_line .. start_line+etoken.line_no-1
|
94
143
|
ps
|
95
144
|
end
|
96
|
-
|
145
|
+
|
97
146
|
# A hack for Ruby 1.9, otherwise returns true.
|
98
147
|
#
|
99
148
|
# Ruby 1.9 returns an incorrect line number
|
100
149
|
# when a block is specified with do/end. It
|
101
|
-
# happens b/c the line number returned by
|
150
|
+
# happens b/c the line number returned by
|
102
151
|
# Ruby 1.9 is based on the first line in the
|
103
152
|
# block which contains a token (i.e. not a
|
104
|
-
# new line or comment etc...).
|
153
|
+
# new line or comment etc...).
|
105
154
|
#
|
106
|
-
# NOTE: This won't work in cases where the
|
107
|
-
# incorrect line also contains a "do".
|
155
|
+
# NOTE: This won't work in cases where the
|
156
|
+
# incorrect line also contains a "do".
|
108
157
|
#
|
109
158
|
def self.line_has_open?(str)
|
110
|
-
return true unless RUBY_VERSION >= '1.9'
|
159
|
+
return true unless RUBY_VERSION >= '1.9' && RUBY_VERSION < '2.0'
|
111
160
|
lexer = RubyLex.new
|
112
161
|
lexer.set_input(StringIO.new(str))
|
113
162
|
success = false
|
@@ -117,12 +166,14 @@ module ProcSource
|
|
117
166
|
break
|
118
167
|
when RubyToken::TkDO
|
119
168
|
success = true
|
169
|
+
when RubyToken::TkfLBRACE
|
170
|
+
success = true
|
120
171
|
when RubyToken::TkCONSTANT
|
121
|
-
if token.
|
172
|
+
if token.name == "Proc" &&
|
122
173
|
lexer.token.is_a?(RubyToken::TkDOT)
|
123
174
|
method = lexer.token
|
124
175
|
if method.is_a?(RubyToken::TkIDENTIFIER) &&
|
125
|
-
method.
|
176
|
+
method.name == "new"
|
126
177
|
success = true
|
127
178
|
end
|
128
179
|
end
|
@@ -130,20 +181,26 @@ module ProcSource
|
|
130
181
|
end
|
131
182
|
success
|
132
183
|
end
|
133
|
-
|
134
|
-
|
135
|
-
def self.get_lines(filename, start_line = 0)
|
184
|
+
|
185
|
+
def self.get_lines(filename, start_line = 1)
|
136
186
|
case filename
|
137
187
|
when nil
|
138
188
|
nil
|
139
|
-
|
189
|
+
|
190
|
+
# We're in irb
|
191
|
+
when "(irb)"
|
140
192
|
IRB.conf[:MAIN_CONTEXT].io.line(start_line .. -2)
|
141
|
-
|
193
|
+
|
194
|
+
# Or an eval
|
195
|
+
when /^\(eval.+\)$/
|
142
196
|
EVAL_LINES__[filename][start_line .. -2]
|
143
|
-
|
197
|
+
|
198
|
+
# Or most likely a .rb file
|
199
|
+
else
|
144
200
|
# Ruby already parsed this file? (see disclaimer above)
|
145
201
|
if defined?(SCRIPT_LINES__) && SCRIPT_LINES__[filename]
|
146
202
|
SCRIPT_LINES__[filename][(start_line - 1) .. -1]
|
203
|
+
|
147
204
|
# If the file exists we're going to try reading it in
|
148
205
|
elsif File.exist?(filename)
|
149
206
|
begin
|
@@ -156,24 +213,77 @@ module ProcSource
|
|
156
213
|
end
|
157
214
|
end
|
158
215
|
|
159
|
-
class Proc
|
160
|
-
attr_reader :file, :line
|
216
|
+
class Proc # :nodoc:
|
161
217
|
attr_writer :source
|
162
|
-
|
218
|
+
@@regexp = Regexp.new('^#<Proc:0x[0-9A-Fa-f]+@?\s*(.+):(\d+)(.+?)?>$')
|
219
|
+
|
163
220
|
def source_descriptor
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
168
232
|
end
|
169
|
-
|
233
|
+
|
234
|
+
file, line = *source_location
|
235
|
+
@file, @line = [file, line.to_i]
|
170
236
|
end
|
171
|
-
|
237
|
+
|
172
238
|
def source
|
173
239
|
@source ||= ProcSource.find(*self.source_descriptor)
|
174
240
|
end
|
175
|
-
|
176
|
-
|
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.
|
177
287
|
# It's assumed the string contains do; end or { }.
|
178
288
|
#
|
179
289
|
# Proc.from_string("do; 2+2; end")
|
@@ -181,7 +291,7 @@ class Proc #:nodoc:
|
|
181
291
|
def self.from_string(str)
|
182
292
|
eval "Proc.new #{str}"
|
183
293
|
end
|
184
|
-
|
294
|
+
|
185
295
|
end
|
186
296
|
|
187
297
|
if $0 == __FILE__
|
@@ -194,19 +304,18 @@ if $0 == __FILE__
|
|
194
304
|
end
|
195
305
|
|
196
306
|
a = Proc.new() { |a|
|
197
|
-
puts "Hello Rudy2"
|
307
|
+
puts "Hello Rudy2"
|
198
308
|
}
|
199
|
-
|
309
|
+
|
200
310
|
b = Proc.new() do |b|
|
201
311
|
puts { "Hello Rudy3" } if true
|
202
312
|
end
|
203
|
-
|
313
|
+
|
204
314
|
puts @blk.inspect, @blk.source
|
205
315
|
puts [a.inspect, a.source]
|
206
316
|
puts b.inspect, b.source
|
207
|
-
|
317
|
+
|
208
318
|
proc = @blk.source.to_proc
|
209
319
|
proc.call(1)
|
210
320
|
end
|
211
321
|
|
212
|
-
|
data/lib/storable.rb
CHANGED
@@ -35,14 +35,14 @@ class Storable
|
|
35
35
|
require 'proc_source'
|
36
36
|
require 'storable/orderedhash' if USE_ORDERED_HASH
|
37
37
|
unless defined?(SUPPORTED_FORMATS) # We can assume all are defined
|
38
|
-
VERSION = "0.8.
|
38
|
+
VERSION = "0.8.9"
|
39
39
|
NICE_TIME_FORMAT = "%Y-%m-%d@%H:%M:%S".freeze
|
40
40
|
SUPPORTED_FORMATS = [:tsv, :csv, :yaml, :json, :s, :string].freeze
|
41
41
|
end
|
42
42
|
|
43
43
|
@debug = false
|
44
44
|
class << self
|
45
|
-
attr_accessor :sensitive_fields, :field_names, :field_types, :debug
|
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
|
@@ -66,37 +66,61 @@ class Storable
|
|
66
66
|
# data is available by the standard accessors, class.product and class.product= etc...
|
67
67
|
# The value of the field will be cast to the type (if provided) when read from a file.
|
68
68
|
# The value is not touched when the type is not provided.
|
69
|
-
def self.field(args
|
69
|
+
def self.field(*args, &processor)
|
70
70
|
# TODO: Examine casting from: http://codeforpeople.com/lib/ruby/fattr/fattr-1.0.3/
|
71
|
-
|
72
|
-
|
71
|
+
field_definitions = {}
|
72
|
+
if args.first.kind_of?(Hash)
|
73
|
+
args.first.each_pair do |fname,klass|
|
74
|
+
field_definitions[fname] = { :class => klass }
|
75
|
+
end
|
76
|
+
else
|
77
|
+
fname, opts = *args
|
78
|
+
if opts.nil?
|
79
|
+
field_definitions[fname] = {}
|
80
|
+
elsif Hash === opts
|
81
|
+
field_definitions[fname] = opts
|
82
|
+
else
|
83
|
+
raise ArgumentError, "Second argument must be a hash"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
73
87
|
self.field_names ||= []
|
74
88
|
self.field_types ||= {}
|
75
|
-
|
76
|
-
|
77
|
-
self.
|
89
|
+
self.field_opts ||= {}
|
90
|
+
field_definitions.each_pair do |fname,opts|
|
91
|
+
self.field_names << fname
|
92
|
+
self.field_opts[fname] = opts
|
93
|
+
self.field_types[fname] = opts[:class] unless opts[:class].nil?
|
78
94
|
|
79
95
|
# This processor automatically converts a Proc object
|
80
96
|
# to a String of its source.
|
81
|
-
processor = proc_processor if
|
97
|
+
processor = proc_processor if opts[:class] == Proc && processor.nil?
|
82
98
|
|
83
99
|
unless processor.nil?
|
84
|
-
define_method("_storable_processor_#{
|
100
|
+
define_method("_storable_processor_#{fname}", &processor)
|
85
101
|
end
|
86
102
|
|
87
|
-
if method_defined?(
|
88
|
-
STDERR.puts "method exists: #{self}##{
|
103
|
+
if method_defined?(fname) # don't redefine the getter method
|
104
|
+
STDERR.puts "method exists: #{self}##{fname}" if Storable.debug
|
89
105
|
else
|
90
|
-
define_method(
|
91
|
-
instance_variable_get("@#{
|
106
|
+
define_method(fname) do
|
107
|
+
ret = instance_variable_get("@#{fname}")
|
108
|
+
if ret.nil?
|
109
|
+
if opts[:default]
|
110
|
+
ret = opts[:default]
|
111
|
+
elsif opts[:meth]
|
112
|
+
ret = self.send(opts[:meth])
|
113
|
+
end
|
114
|
+
end
|
115
|
+
ret
|
92
116
|
end
|
93
117
|
end
|
94
118
|
|
95
|
-
if method_defined?("#{
|
96
|
-
STDERR.puts "method exists: #{self}##{
|
119
|
+
if method_defined?("#{fname}=") # don't redefine the setter methods
|
120
|
+
STDERR.puts "method exists: #{self}##{fname}=" if Storable.debug
|
97
121
|
else
|
98
|
-
define_method("#{
|
99
|
-
instance_variable_set("@#{
|
122
|
+
define_method("#{fname}=") do |val|
|
123
|
+
instance_variable_set("@#{fname}",val)
|
100
124
|
end
|
101
125
|
end
|
102
126
|
end
|
@@ -145,15 +169,15 @@ class Storable
|
|
145
169
|
|
146
170
|
# Returns an array of field names defined by self.field
|
147
171
|
def field_names
|
148
|
-
self.class.field_names
|
172
|
+
self.class.field_names #|| self.class.ancestors.first.field_names
|
149
173
|
end
|
150
174
|
# Returns an array of field types defined by self.field. Fields that did
|
151
175
|
# not receive a type are set to nil.
|
152
176
|
def field_types
|
153
|
-
self.class.field_types
|
177
|
+
self.class.field_types #|| self.class.ancestors.first.field_types
|
154
178
|
end
|
155
179
|
def sensitive_fields
|
156
|
-
self.class.sensitive_fields
|
180
|
+
self.class.sensitive_fields #|| self.class.ancestors.first.sensitive_fields
|
157
181
|
end
|
158
182
|
|
159
183
|
# Dump the object data to the given format.
|
@@ -200,7 +224,7 @@ class Storable
|
|
200
224
|
def init *args
|
201
225
|
from_array *args
|
202
226
|
end
|
203
|
-
|
227
|
+
|
204
228
|
def initialize *args
|
205
229
|
init *args
|
206
230
|
end
|
@@ -298,14 +322,16 @@ class Storable
|
|
298
322
|
def to_hash
|
299
323
|
preprocess if respond_to? :preprocess
|
300
324
|
tmp = USE_ORDERED_HASH ? Storable::OrderedHash.new : {}
|
301
|
-
field_names
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
325
|
+
if field_names
|
326
|
+
field_names.each do |fname|
|
327
|
+
next if sensitive? && self.class.sensitive_field?(fname)
|
328
|
+
v = self.send(fname)
|
329
|
+
v = process(fname, v) if has_processor?(fname)
|
330
|
+
if Array === v
|
331
|
+
v = v.collect { |v2| v2.kind_of?(Storable) ? v2.to_hash : v2 }
|
332
|
+
end
|
333
|
+
tmp[fname] = v.kind_of?(Storable) ? v.to_hash : v
|
307
334
|
end
|
308
|
-
tmp[fname] = v.kind_of?(Storable) ? v.to_hash : v
|
309
335
|
end
|
310
336
|
tmp
|
311
337
|
end
|
@@ -329,9 +355,6 @@ class Storable
|
|
329
355
|
hash = to_hash
|
330
356
|
if YAJL_LOADED # set by Storable
|
331
357
|
ret = Yajl::Encoder.encode(hash)
|
332
|
-
#raise "DELANO"
|
333
|
-
#ret.force_encoding("ISO-8859-1")
|
334
|
-
#p [:to, ret.encoding.name] if ret.respond_to?(:encoding)
|
335
358
|
ret
|
336
359
|
elsif JSON_LOADED
|
337
360
|
JSON.generate(hash, *from, &blk)
|
@@ -508,7 +531,8 @@ class Storable
|
|
508
531
|
end
|
509
532
|
def proc_processor
|
510
533
|
Proc.new do |val|
|
511
|
-
(Proc === val) ? val.source : val
|
534
|
+
ret = (Proc === val) ? val.source : val
|
535
|
+
ret
|
512
536
|
end
|
513
537
|
end
|
514
538
|
# If the object already has a value for +@id+
|