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.
data/lib/proc_source.rb CHANGED
@@ -1,113 +1,162 @@
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
+ # 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
- #SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
18
+ require 'pry'
19
+ require 'stringio'
20
+
21
+ SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
22
+
9
23
 
10
24
  class ProcString < String
11
- attr_accessor :file, :lines, :arity, :kind
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
- 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
+ # 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 @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
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[@name]
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
- while token = lexer.token
62
- n = token.instance_variable_get(:@name)
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
- #p token
129
+ # nothing
81
130
  end
82
131
  end
83
- # puts lines if etoken.nil?
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, ps.lines = filename, start_line .. start_line+etoken.line_no-1
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.instance_variable_get(:@name) == "Proc" &&
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.instance_variable_get(:@name) == "new"
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
- when "(irb)" # special "(irb)" descriptor?
189
+
190
+ # We're in irb
191
+ when "(irb)"
140
192
  IRB.conf[:MAIN_CONTEXT].io.line(start_line .. -2)
141
- when /^\(eval.+\)$/ # special "(eval...)" descriptor?
193
+
194
+ # Or an eval
195
+ when /^\(eval.+\)$/
142
196
  EVAL_LINES__[filename][start_line .. -2]
143
- else # regular file
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 #:nodoc:
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
- unless @file && @line
165
- if md = /^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+)(.+?)?>$/.match(inspect)
166
- @file, @line = md.captures
167
- end
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
- [@file, @line.to_i]
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
- # Create a Proc object from a string of Ruby code.
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.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.
@@ -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.each do |fname|
302
- next if sensitive? && self.class.sensitive_field?(fname)
303
- v = self.send(fname)
304
- v = process(fname, v) if has_processor?(fname)
305
- if Array === v
306
- 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
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+