rubyless 0.3.0 → 0.3.1

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/History.txt CHANGED
@@ -1,4 +1,14 @@
1
- == 0.3.0 2009-10-03
1
+ == 0.3.1 2009-10-07
2
+
3
+ * 3 major enhancements
4
+ * method name in signatures should always be a string
5
+ * type[:method] is always set and is always a string
6
+ * fixed how class type is guessed from ActiveRecord column
7
+
8
+ * 1 minor enhancement
9
+ * added 'safe_read' method to objects
10
+
11
+ == 0.3.1 2009-10-03
2
12
 
3
13
  * 1 major enhancement
4
14
  * Moved from ParseTree to RubyParser
data/lib/processor.rb CHANGED
@@ -12,8 +12,8 @@ module RubyLess
12
12
  class RubyLessProcessor < SexpProcessor
13
13
  attr_reader :ruby
14
14
 
15
- INFIX_OPERATOR = [:"<=>", :==, :<, :>, :<=, :>=, :-, :+, :*, :/, :%]
16
- PREFIX_OPERATOR = [:"-@"]
15
+ INFIX_OPERATOR = ['<=>', '==', '<', '>', '<=', '>=', '-', '+', '*', '/', '%']
16
+ PREFIX_OPERATOR = ['-@']
17
17
 
18
18
  def self.translate(string, helper)
19
19
  sexp = RubyParser.new.parse(string)
@@ -94,7 +94,7 @@ module RubyLess
94
94
  unless opts = get_method([var_name], @helper, false)
95
95
  raise "Unknown variable or method '#{var_name}'."
96
96
  end
97
- method = opts[:method] || var_name.to_s
97
+ method = opts[:method]
98
98
  t method, opts
99
99
  end
100
100
 
@@ -147,7 +147,7 @@ module RubyLess
147
147
  end
148
148
 
149
149
  def method_call(receiver, exp)
150
- method = exp.shift
150
+ method = exp.shift.to_s
151
151
  arg_sexp = args = exp.shift # rescue nil
152
152
  if arg_sexp
153
153
  args = process(arg_sexp)
@@ -169,14 +169,14 @@ module RubyLess
169
169
  cond += receiver.cond
170
170
  end
171
171
  raise "'#{receiver}' does not respond to '#{method}(#{signature[1..-1].join(', ')})'." unless opts = get_method(signature, receiver.klass)
172
- method = opts[:method] if opts[:method]
173
- if method == :/
172
+ method = opts[:method]
173
+ if method == '/'
174
174
  t_if cond, "(#{receiver.raw}#{method}#{args.raw} rescue nil)", opts.merge(:nil => true)
175
175
  elsif INFIX_OPERATOR.include?(method)
176
176
  t_if cond, "(#{receiver.raw}#{method}#{args.raw})", opts
177
177
  elsif PREFIX_OPERATOR.include?(method)
178
178
  t_if cond, "#{method.to_s[0..0]}#{receiver.raw}", opts
179
- elsif method == :[]
179
+ elsif method == '[]'
180
180
  t_if cond, "#{receiver.raw}[#{args.raw}]", opts
181
181
  else
182
182
  args = "(#{args.raw})" if args != ''
@@ -184,7 +184,7 @@ module RubyLess
184
184
  end
185
185
  else
186
186
  raise "Unknown method '#{method}(#{args.raw})'." unless opts = get_method(signature, @helper, false)
187
- method = opts[:method] if opts[:method]
187
+ method = opts[:method]
188
188
  args = "(#{args.raw})" if args != ''
189
189
  t_if cond, "#{method}#{args}", opts
190
190
  end
data/lib/rubyless.rb CHANGED
@@ -6,7 +6,7 @@ require 'processor'
6
6
  =begin rdoc
7
7
  =end
8
8
  module RubyLess
9
- VERSION = '0.3.0'
9
+ VERSION = '0.3.1'
10
10
 
11
11
  def self.translate(string, helper)
12
12
  RubyLessProcessor.translate(string, helper)
data/lib/safe_class.rb CHANGED
@@ -2,12 +2,12 @@ module RubyLess
2
2
  module SafeClass
3
3
  @@_safe_methods ||= {} # defined for each class
4
4
  @@_safe_methods_all ||= {} # full list with inherited attributes
5
-
5
+
6
6
  # List of safe methods for a specific class.
7
7
  def self.safe_methods_for(klass)
8
8
  @@_safe_methods_all[klass] ||= build_safe_methods_list(klass)
9
9
  end
10
-
10
+
11
11
  # Return method type (options) if the given signature is a safe method for the class.
12
12
  def self.safe_method_type_for(klass, signature)
13
13
  if res = safe_methods_for(klass)[signature]
@@ -16,20 +16,22 @@ module RubyLess
16
16
  nil
17
17
  end
18
18
  end
19
-
19
+
20
20
  # Declare a safe method for a given class
21
21
  def self.safe_method_for(klass, hash)
22
22
  list = (@@_safe_methods[klass] ||= {})
23
23
  hash.each do |k,v|
24
24
  k = [k] unless k.kind_of?(Array)
25
+ k[0] = k[0].to_s
25
26
  v = {:class => v} unless v.kind_of?(Hash) || v.kind_of?(Proc)
27
+ v[:method] = v[:method] ? v[:method].to_s : k.first.to_s
26
28
  list[k] = v
27
29
  end
28
30
  end
29
-
31
+
30
32
  def self.included(base)
31
33
  base.class_eval do
32
-
34
+
33
35
  # Declare safe methods. By providing
34
36
  #
35
37
  # The methods hash has the following format:
@@ -55,21 +57,23 @@ module RubyLess
55
57
  def self.safe_method(hash)
56
58
  methods_hash = hash
57
59
  defaults = methods_hash.delete(:defaults) || {}
58
-
60
+
59
61
  list = (@@_safe_methods[self] ||= {})
60
62
  methods_hash.each do |k,v|
61
63
  k = [k] unless k.kind_of?(Array)
64
+ k[0] = k[0].to_s
62
65
  if v.kind_of?(Hash)
63
66
  v = defaults.merge(v)
67
+ v[:method] = v[:method] ? v[:method].to_s : k.first.to_s
64
68
  elsif v.kind_of?(Proc)
65
69
  # cannot merge defaults
66
70
  else
67
- v = defaults.merge(:class => v)
71
+ v = defaults.merge(:class => v, :method => k.first.to_s)
68
72
  end
69
73
  list[k] = v
70
74
  end
71
75
  end
72
-
76
+
73
77
  # Declare a safe method to access a list of attributes.
74
78
  # This method should only be used when the class is linked with a database table and provides
75
79
  # proper introspection to detect types and the possibility of NULL values.
@@ -82,10 +86,8 @@ module RubyLess
82
86
  opts[:class] = Number
83
87
  elsif col.text?
84
88
  opts[:class] = String
85
- elsif att.to_s =~ /_at$/
86
- opts[:class] = Time
87
89
  else
88
- raise "Could not declare safe_method for '#{att}': could not guess return type"
90
+ opts[:class] = col.klass
89
91
  end
90
92
  safe_method att.to_sym => opts
91
93
  else
@@ -93,34 +95,41 @@ module RubyLess
93
95
  end
94
96
  end
95
97
  end
96
-
98
+
97
99
  # Declare a safe method for a given class
98
100
  def self.safe_method_for(klass, signature)
99
101
  SafeClass.safe_method_for(klass, signature)
100
102
  end
101
-
103
+
102
104
  # Hash of all safe methods defined for the class.
103
105
  def self.safe_methods
104
106
  SafeClass.safe_methods_for(self)
105
107
  end
106
-
108
+
107
109
  # Return true if the given signature corresponds to a safe method for the class.
108
110
  def self.safe_method_type(signature)
109
111
  if res = SafeClass.safe_method_type_for(self, signature)
110
- res.dup
112
+ res.dup # TODO: replace by freeze
111
113
  else
112
114
  nil
113
115
  end
114
116
  end
115
-
117
+
116
118
  # Return the method type (options) if the given signature is a safe method for the class.
117
119
  def safe_method_type(signature)
118
120
  self.class.safe_method_type(signature)
119
121
  end
120
122
  end # base.class_eval
121
123
  end # included
122
-
123
- private
124
+
125
+ # Safe attribute reader used when 'safe_readable?' could not be called because the class
126
+ # is not known during compile time.
127
+ def safe_read(key)
128
+ return "'#{key}' not readable" unless type = self.class.safe_method_type([key])
129
+ self.send(type[:method])
130
+ end
131
+
132
+ private
124
133
  def self.build_safe_methods_list(klass)
125
134
  list = klass.superclass.respond_to?(:safe_methods) ? klass.superclass.safe_methods : {}
126
135
  (@@_safe_methods[klass] || {}).map do |signature, return_value|
@@ -129,12 +138,13 @@ module RubyLess
129
138
  elsif !return_value.kind_of?(Proc)
130
139
  return_value = {:class => return_value}
131
140
  end
132
- signature.map! {|e| parse_class(e)}
141
+ method = signature.shift
142
+ signature = [method] + signature.map {|e| parse_class(e)}
133
143
  list[signature] = return_value
134
144
  end
135
145
  list
136
146
  end
137
-
147
+
138
148
  def self.parse_class(klass)
139
149
  if klass.kind_of?(Array)
140
150
  if klass[0].kind_of?(String)
data/rubyless.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{rubyless}
5
- s.version = "0.3.0"
5
+ s.version = "0.3.1"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Gaspard Bucher"]
@@ -3,7 +3,7 @@ require File.dirname(__FILE__) + '/test_helper.rb'
3
3
 
4
4
  class StringDictionary
5
5
  include RubyLess::SafeClass
6
- safe_method [:[], Symbol] => {:class => String, :nil => true}
6
+ safe_method ['[]', Symbol] => {:class => String, :nil => true}
7
7
  end
8
8
 
9
9
  class SimpleHelper < Test::Unit::TestCase
@@ -15,7 +15,7 @@ class SimpleHelper < Test::Unit::TestCase
15
15
  safe_method :node => lambda {|h| {:class => h.context[:node_class], :method => h.context[:node]}}
16
16
  safe_method :now => {:class => Time, :method => "Time.now"}
17
17
  safe_method :birth => {:class => Time, :method => "Date.parse('2009-06-02 18:44')"}
18
- safe_method :dictionary => {:class => StringDictionary, :method => 'get_dict'}
18
+ safe_method 'dictionary' => {:class => StringDictionary, :method => 'get_dict'}
19
19
  safe_method [:vowel_count, String] => Number
20
20
  safe_method [:log_info, Dummy, String] => String
21
21
  safe_method_for String, [:==, String] => Boolean
@@ -46,6 +46,11 @@ class SimpleHelper < Test::Unit::TestCase
46
46
  "[#{obj.name}] #{msg}"
47
47
  end
48
48
 
49
+ def test_safe_read
50
+ assert_equal 10, Dummy.new.safe_read('id')
51
+ assert_equal "'rm' not readable", Dummy.new.safe_read('rm')
52
+ end
53
+
49
54
  def yt_do_test(file, test, context = yt_get('context',file,test))
50
55
  @@test_strings[file][test].keys.each do |key|
51
56
  next if ['src', 'context'].include?(key)
@@ -60,6 +65,7 @@ class SimpleHelper < Test::Unit::TestCase
60
65
  when 'tem'
61
66
  source ? RubyLess.translate(source, self) : yt_get('tem', file, test)
62
67
  when 'res'
68
+ res = RubyLess.translate(source, self)
63
69
  eval(source ? RubyLess.translate(source, self) : yt_get('tem', file, test)).to_s
64
70
  when 'sxp'
65
71
  RubyParser.new.parse(source).inspect
@@ -8,26 +8,49 @@ module RubyLess
8
8
  @opts[:default]
9
9
  end
10
10
 
11
+ def type
12
+ @opts[:type]
13
+ end
14
+
15
+ # Returns +true+ if the column is either of type string or text.
11
16
  def text?
12
- @opts[:text]
17
+ type == :string || type == :text
13
18
  end
14
19
 
20
+ # Returns +true+ if the column is either of type integer, float or decimal.
15
21
  def number?
16
- @opts[:number]
22
+ type == :integer || type == :float || type == :decimal
17
23
  end
24
+
25
+ # Returns the Ruby class that corresponds to the abstract data type.
26
+ def klass
27
+ case type
28
+ when :integer then Fixnum
29
+ when :float then Float
30
+ when :decimal then BigDecimal
31
+ when :datetime then Time
32
+ when :date then Date
33
+ when :timestamp then Time
34
+ when :time then Time
35
+ when :text, :string then String
36
+ when :binary then String
37
+ when :boolean then Object
38
+ end
39
+ end
40
+
18
41
  end
19
42
 
20
43
  class ActiveRecordMock
21
44
  COLUMNS = {
22
- 'format' => ColumnMock.new(:default => '%d.%m.%Y', :text => true),
23
- 'age' => ColumnMock.new(:default => 5, :number => true),
24
- 'friend_id' => ColumnMock.new(:number => true),
25
- 'log_at' => ColumnMock.new,
45
+ 'format' => ColumnMock.new(:default => '%d.%m.%Y', :type => :text),
46
+ 'age' => ColumnMock.new(:default => 5, :type => :float),
47
+ 'friend_id' => ColumnMock.new(:type => :integer),
48
+ 'log_at' => ColumnMock.new(:type => :datetime),
26
49
  }
27
50
  def self.columns_hash
28
51
  COLUMNS
29
52
  end
30
-
53
+
31
54
  COLUMNS.each do |k, v|
32
55
  define_method(k) do
33
56
  v.default
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyless
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gaspard Bucher
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-03 00:00:00 +02:00
12
+ date: 2009-10-15 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency