storable 0.8.6 → 0.9.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 +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+
|