storable 0.8.5 → 0.9.pre.RC1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/proc_source.rb CHANGED
@@ -1,113 +1,156 @@
1
- #--
1
+
2
+ #
2
3
  # Based on:
3
- # http://github.com/imedo/background
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
+ begin
11
+ require 'irb/ruby-token' # ruby <2.7
12
+ rescue
13
+ require 'lib/core_ext' # ruby >=2.7
14
+ end
5
15
 
6
- require 'stringio'
7
16
  require 'irb/ruby-lex'
8
- #SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
17
+ require 'stringio'
18
+
19
+ SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
20
+
9
21
 
10
22
  class ProcString < String
11
- attr_accessor :file, :lines, :arity, :kind
23
+ # Filename where the proc is defined
24
+ attr_accessor :file
25
+
26
+ # Range of lines where the proc is defined
27
+ # ex. (12..16)
28
+ attr_accessor :lines
29
+
30
+ attr_accessor :arity, :kind # :nodoc: FIXME: Should be removed?
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
- result = eval("#{kind} #{self}")
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 "lamda"
47
+ to_proc "lambda"
19
48
  end
20
49
  end
21
50
 
22
51
  class RubyToken::Token
23
-
24
- # These EXPR_BEG tokens don't have associated end tags
25
- FAKIES = [RubyToken::TkWHEN, RubyToken::TkELSIF, RubyToken::TkTHEN]
26
-
52
+
53
+ # These EXPR_BEG tokens don't have associated end tags
54
+ FAKIES = [
55
+ RubyToken::TkWHEN,
56
+ RubyToken::TkELSIF,
57
+ RubyToken::TkELSE,
58
+ RubyToken::TkTHEN,
59
+ ]
60
+
61
+ def name
62
+ @name ||= nil
63
+ end
64
+
27
65
  def open_tag?
28
- return false if @name.nil? || get_props.nil?
29
- a = (get_props[1] == RubyToken::EXPR_BEG) &&
30
- self.class.to_s !~ /_MOD/ && # ignore onliner if, unless, etc...
31
- !FAKIES.member?(self.class)
32
- a
66
+ return false if name.nil? || get_props.nil?
67
+ is_open = (
68
+ (get_props[1] == RubyToken::EXPR_BEG) &&
69
+ (self.class.to_s !~ /_MOD/) && # ignore onliner if, unless, etc...
70
+ (!FAKIES.member?(self.class))
71
+ )
72
+ is_open
33
73
  end
34
-
74
+
35
75
  def get_props
36
- RubyToken::TkReading2Token[@name]
76
+ RubyToken::TkReading2Token[name]
37
77
  end
38
-
78
+
39
79
  end
40
80
 
41
81
  # Based heavily on code from http://github.com/imedo/background
42
82
  # Big thanks to the imedo dev team!
43
83
  #
44
84
  module ProcSource
45
-
46
- def self.find(filename, start_line=0, block_only=true)
85
+
86
+ def self.find(filename, start_line=1, block_only=true)
47
87
  lines, lexer = nil, nil
48
88
  retried = 0
49
89
  loop do
50
90
  lines = get_lines(filename, start_line)
91
+ # binding.pry
51
92
  return nil if lines.nil?
52
- #p [start_line, lines[0]]
53
93
  if !line_has_open?(lines.join) && start_line >= 0
54
- start_line -= 1 and retried +=1 and redo
94
+ start_line -= 1 and retried +=1 and redo
55
95
  end
56
96
  lexer = RubyLex.new
57
97
  lexer.set_input(StringIO.new(lines.join))
58
98
  break
59
99
  end
100
+
60
101
  stoken, etoken, nesting = nil, nil, 0
61
102
  while token = lexer.token
62
- n = token.instance_variable_get(:@name)
63
-
64
103
  if RubyToken::TkIDENTIFIER === token
65
- #nothing
104
+ # nothing
66
105
  elsif token.open_tag? || RubyToken::TkfLBRACE === token
67
106
  nesting += 1
68
107
  stoken = token if nesting == 1
69
108
  elsif RubyToken::TkEND === token || RubyToken::TkRBRACE === token
70
109
  if nesting == 1
71
- etoken = token
110
+ etoken = token
72
111
  break
73
112
  end
74
113
  nesting -= 1
114
+ elsif RubyToken::TkLBRACE === token
115
+ nesting += 1
75
116
  elsif RubyToken::TkBITOR === token && stoken
76
- #nothing
117
+ # nothing
77
118
  elsif RubyToken::TkNL === token && stoken && etoken
78
119
  break if nesting <= 0
79
120
  else
80
- #p token
121
+ # nothing
81
122
  end
82
123
  end
83
- # puts lines if etoken.nil?
124
+
125
+ # binding.pry
126
+
84
127
  lines = lines[stoken.line_no-1 .. etoken.line_no-1]
85
-
86
- # Remove the crud before the block definition.
128
+
129
+ # Remove the crud before the block definition.
87
130
  if block_only
88
131
  spaces = lines.last.match(/^\s+/)[0] rescue ''
89
132
  lines[0] = spaces << lines[0][stoken.char_no .. -1]
90
133
  end
91
134
  ps = ProcString.new lines.join
92
- ps.file, ps.lines = filename, start_line .. start_line+etoken.line_no-1
93
-
135
+ ps.file = filename
136
+ ps.lines = start_line .. start_line+etoken.line_no-1
94
137
  ps
95
138
  end
96
-
139
+
97
140
  # A hack for Ruby 1.9, otherwise returns true.
98
141
  #
99
142
  # Ruby 1.9 returns an incorrect line number
100
143
  # when a block is specified with do/end. It
101
- # happens b/c the line number returned by
144
+ # happens b/c the line number returned by
102
145
  # Ruby 1.9 is based on the first line in the
103
146
  # block which contains a token (i.e. not a
104
- # new line or comment etc...).
147
+ # new line or comment etc...).
105
148
  #
106
- # NOTE: This won't work in cases where the
107
- # incorrect line also contains a "do".
149
+ # NOTE: This won't work in cases where the
150
+ # incorrect line also contains a "do".
108
151
  #
109
152
  def self.line_has_open?(str)
110
- return true unless RUBY_VERSION >= '1.9'
153
+ return true unless RUBY_VERSION >= '1.9' && RUBY_VERSION < '2.0'
111
154
  lexer = RubyLex.new
112
155
  lexer.set_input(StringIO.new(str))
113
156
  success = false
@@ -117,12 +160,14 @@ module ProcSource
117
160
  break
118
161
  when RubyToken::TkDO
119
162
  success = true
163
+ when RubyToken::TkfLBRACE
164
+ success = true
120
165
  when RubyToken::TkCONSTANT
121
- if token.instance_variable_get(:@name) == "Proc" &&
166
+ if token.name == "Proc" &&
122
167
  lexer.token.is_a?(RubyToken::TkDOT)
123
168
  method = lexer.token
124
169
  if method.is_a?(RubyToken::TkIDENTIFIER) &&
125
- method.instance_variable_get(:@name) == "new"
170
+ method.name == "new"
126
171
  success = true
127
172
  end
128
173
  end
@@ -130,20 +175,26 @@ module ProcSource
130
175
  end
131
176
  success
132
177
  end
133
-
134
-
135
- def self.get_lines(filename, start_line = 0)
178
+
179
+ def self.get_lines(filename, start_line = 1)
136
180
  case filename
137
181
  when nil
138
182
  nil
139
- when "(irb)" # special "(irb)" descriptor?
183
+
184
+ # We're in irb
185
+ when "(irb)"
140
186
  IRB.conf[:MAIN_CONTEXT].io.line(start_line .. -2)
141
- when /^\(eval.+\)$/ # special "(eval...)" descriptor?
187
+
188
+ # Or an eval
189
+ when /^\(eval.+\)$/
142
190
  EVAL_LINES__[filename][start_line .. -2]
143
- else # regular file
191
+
192
+ # Or most likely a .rb file
193
+ else
144
194
  # Ruby already parsed this file? (see disclaimer above)
145
195
  if defined?(SCRIPT_LINES__) && SCRIPT_LINES__[filename]
146
196
  SCRIPT_LINES__[filename][(start_line - 1) .. -1]
197
+
147
198
  # If the file exists we're going to try reading it in
148
199
  elsif File.exist?(filename)
149
200
  begin
@@ -156,24 +207,77 @@ module ProcSource
156
207
  end
157
208
  end
158
209
 
159
- class Proc #:nodoc:
160
- attr_reader :file, :line
210
+ class Proc # :nodoc:
161
211
  attr_writer :source
162
-
212
+ @@regexp = Regexp.new('^#<Proc:0x[0-9A-Fa-f]+@?\s*(.+):(\d+)(.+?)?>$')
213
+
163
214
  def source_descriptor
164
- unless @file && @line
165
- if md = /^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+)(.+?)?>$/.match(inspect)
166
- @file, @line = md.captures
167
- end
215
+ return [@file, @line] if @file && @line
216
+
217
+ source_location = nil
218
+ if RUBY_VERSION >= '2.7'
219
+ source_location = *self.source_location
220
+ else
221
+ inspection = inspect
222
+ md = @@regexp.match(inspection)
223
+ exmsg = 'Unable to parse proc inspect (%s)' % inspection
224
+ raise Exception, exmsg if md.nil?
225
+ source_location = md.captures
168
226
  end
169
- [@file, @line.to_i]
227
+
228
+ file, line = *source_location
229
+ @file, @line = [file, line.to_i]
170
230
  end
171
-
231
+
172
232
  def source
173
233
  @source ||= ProcSource.find(*self.source_descriptor)
174
234
  end
175
-
176
- # Create a Proc object from a string of Ruby code.
235
+
236
+ def line
237
+ source_descriptor
238
+ @line
239
+ end
240
+
241
+ def file
242
+ source_descriptor
243
+ @file
244
+ end
245
+
246
+ # Dump to Marshal format.
247
+ # p = Proc.new { false }
248
+ # Marshal.dump p
249
+ def _dump(limit)
250
+ raise "can't dump proc, #source is nil" if source.nil?
251
+ str = Marshal.dump(source)
252
+ str
253
+ end
254
+
255
+ # Load from Marshal format.
256
+ # p = Proc.new { false }
257
+ # Marshal.load Marshal.dump p
258
+ def self._load(str)
259
+ @source = Marshal.load(str)
260
+ @source.to_proc
261
+ end
262
+
263
+ # Dump to JSON string
264
+ def to_json(*args)
265
+ raise "can't serialize proc, #source is nil" if source.nil?
266
+ {
267
+ 'json_class' => self.class.name,
268
+ 'data' => [source.to_s, source.file, source.lines.min, source.lines.max]
269
+ }.to_json#(*args)
270
+ end
271
+
272
+ def self.json_create(o)
273
+ s, file, min, max = o['data']
274
+ ps = ProcString.new s
275
+ ps.file = file
276
+ ps.lines = (min..max)
277
+ ps.to_proc
278
+ end
279
+
280
+ # Create a Proc object from a string of Ruby code.
177
281
  # It's assumed the string contains do; end or { }.
178
282
  #
179
283
  # Proc.from_string("do; 2+2; end")
@@ -181,7 +285,7 @@ class Proc #:nodoc:
181
285
  def self.from_string(str)
182
286
  eval "Proc.new #{str}"
183
287
  end
184
-
288
+
185
289
  end
186
290
 
187
291
  if $0 == __FILE__
@@ -194,17 +298,17 @@ if $0 == __FILE__
194
298
  end
195
299
 
196
300
  a = Proc.new() { |a|
197
- puts "Hello Rudy2"
301
+ puts "Hello Rudy2"
198
302
  }
199
-
303
+
200
304
  b = Proc.new() do |b|
201
305
  puts { "Hello Rudy3" } if true
202
306
  end
203
-
307
+
204
308
  puts @blk.inspect, @blk.source
205
309
  puts [a.inspect, a.source]
206
310
  puts b.inspect, b.source
207
-
311
+
208
312
  proc = @blk.source.to_proc
209
313
  proc.call(1)
210
314
  end
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.5"
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={}, &processor)
69
+ def self.field(*args, &processor)
70
70
  # TODO: Examine casting from: http://codeforpeople.com/lib/ruby/fattr/fattr-1.0.3/
71
- args = {args => nil} unless args.kind_of?(Hash)
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
- args.each_pair do |m,t|
76
- self.field_names << m
77
- self.field_types[m] = t unless t.nil?
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 t == Proc && processor.nil?
97
+ processor = proc_processor if opts[:class] == Proc && processor.nil?
82
98
 
83
99
  unless processor.nil?
84
- define_method("_storable_processor_#{m}", &processor)
100
+ define_method("_storable_processor_#{fname}", &processor)
85
101
  end
86
102
 
87
- if method_defined?(m) # don't redefine the getter method
88
- STDERR.puts "method exists: #{self}##{m}" if Storable.debug
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(m) do
91
- instance_variable_get("@#{m}")
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?("#{m}=") # don't redefine the setter methods
96
- STDERR.puts "method exists: #{self}##{m}=" if Storable.debug
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("#{m}=") do |val|
99
- instance_variable_set("@#{m}",val)
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.
@@ -201,6 +225,10 @@ class Storable
201
225
  from_array *args
202
226
  end
203
227
 
228
+ def initialize *args
229
+ init *args
230
+ end
231
+
204
232
  def from_array *from
205
233
  (self.field_names || []).each_with_index do |n,index|
206
234
  break if index >= from.size
@@ -294,14 +322,16 @@ class Storable
294
322
  def to_hash
295
323
  preprocess if respond_to? :preprocess
296
324
  tmp = USE_ORDERED_HASH ? Storable::OrderedHash.new : {}
297
- field_names.each do |fname|
298
- next if sensitive? && self.class.sensitive_field?(fname)
299
- v = self.send(fname)
300
- v = process(fname, v) if has_processor?(fname)
301
- if Array === v
302
- v = v.collect { |v2| v2.kind_of?(Storable) ? v2.to_hash : v2 }
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
303
334
  end
304
- tmp[fname] = v.kind_of?(Storable) ? v.to_hash : v
305
335
  end
306
336
  tmp
307
337
  end
@@ -325,9 +355,6 @@ class Storable
325
355
  hash = to_hash
326
356
  if YAJL_LOADED # set by Storable
327
357
  ret = Yajl::Encoder.encode(hash)
328
- #raise "DELANO"
329
- #ret.force_encoding("ISO-8859-1")
330
- #p [:to, ret.encoding.name] if ret.respond_to?(:encoding)
331
358
  ret
332
359
  elsif JSON_LOADED
333
360
  JSON.generate(hash, *from, &blk)
@@ -504,7 +531,8 @@ class Storable
504
531
  end
505
532
  def proc_processor
506
533
  Proc.new do |val|
507
- (Proc === val) ? val.source : val
534
+ ret = (Proc === val) ? val.source : val
535
+ ret
508
536
  end
509
537
  end
510
538
  # If the object already has a value for +@id+