rubyless 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ == 0.1.0 2009-06-02
2
+
3
+ * 1 major enhancement:
4
+ * Initial alpha release
@@ -0,0 +1,92 @@
1
+ = RubyLess
2
+
3
+ * http://zenadmin.org/546
4
+
5
+ == DESCRIPTION:
6
+
7
+ RubyLess is an interpreter for "safe ruby". The idea is to transform some "unsafe" ruby code into safe, type checked
8
+ ruby, eventually rewriting some variables or methods. The goals:
9
+
10
+ 1. give ruby scripting access to users without any security risk
11
+ 2. rewrite variable names depending on compilation context
12
+ 3. never raise runtime errors through compile time type checking and powerful nil handling
13
+
14
+ This library is based on Ruby2Ruby by Ryan Davis, thanks to him for sharing his work.
15
+
16
+ == SYNOPSIS:
17
+
18
+ For every class that will be involved in your RubyLess scripts, you need to declare safe methods with the 'safe_method' macro if
19
+ you want to enable methods from this class. You have to specify the return type of the method. If you have some methods that
20
+ return 'nil' instead of the declared output, you need to wrap your final ruby 'eval' with a rescue clause.
21
+
22
+ # signature is made of [method, arg_class, arg_class, ...]
23
+ class Node
24
+ include RubyLess::SafeClass
25
+ safe_method [:ancestor?, Node] => RubyLess::Boolean
26
+ end
27
+
28
+ # methods defined in helper
29
+
30
+ # global methods
31
+ include RubyLess::SafeClass
32
+ safe_method :prev => {:class => Dummy, :method => 'previous', :nil => true}
33
+ safe_method :node => lambda {|h| {:class => h.context[:node_class], :method => h.context[:node]}}
34
+ safe_method [:strftime, Time, String] => String
35
+ safe_method_for String, [:==, String] => RubyLess::Boolean
36
+ safe_method_for String, [:to_s] => String
37
+
38
+ You can also redefine 'safe_method?' for any class or for the main helper in order to do some more complicated renaming. Note
39
+ also that you should add ':nil => true' declaration to any method that could return a nil value so that RubyLess can render
40
+ code that will not break during runtime (adding nil checking in the form of "foo ? foo.name : nil").
41
+
42
+ You can now parse some ruby code:
43
+
44
+ RubyLess.translate("!prev.ancestor?(main) && !node.ancestor?(main)", self)
45
+ => "(not previous.ancestor?(@node) and not var1.ancestor?(@node))"
46
+
47
+ RubyLess.translate("id > 45 and (3 > -id or 3+3)", self)
48
+ => "(var1.zip>45 and ((3>-var1.zip) or (3+3)))"
49
+
50
+ RubyLess.translate("strftime(now, '%Y')", self)
51
+ => "strftime(Time.now, \"%Y\")"
52
+
53
+ RubyLess.translate("log_info(spouse, spouse.name)", self)
54
+ => "(var1.spouse ? log_info(var1.spouse, var1.spouse.name) : nil)"
55
+
56
+ Since most of the code in SafeClass is string evaluated (to scope class variables), there is not much to parse for rdoc. You
57
+ can look at the tests for an idea of how to declare things. If you have more questions, ask on zena's mailing list:
58
+
59
+ http://zenadmin.org/community
60
+
61
+ == REQUIREMENTS:
62
+
63
+ * parse_tree
64
+
65
+ == INSTALL:
66
+
67
+ sudo gem install rubyless
68
+
69
+ == LICENSE:
70
+
71
+ (The MIT License)
72
+
73
+ Copyright (c) 2009 Gaspard Bucher
74
+
75
+ Permission is hereby granted, free of charge, to any person obtaining
76
+ a copy of this software and associated documentation files (the
77
+ 'Software'), to deal in the Software without restriction, including
78
+ without limitation the rights to use, copy, modify, merge, publish,
79
+ distribute, sublicense, and/or sell copies of the Software, and to
80
+ permit persons to whom the Software is furnished to do so, subject to
81
+ the following conditions:
82
+
83
+ The above copyright notice and this permission notice shall be
84
+ included in all copies or substantial portions of the Software.
85
+
86
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
87
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
88
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
89
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
90
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
91
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
92
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,124 @@
1
+ require "rubygems"
2
+ require "rake/gempackagetask"
3
+ require "rake/rdoctask"
4
+ require "lib/RubyLess"
5
+
6
+ task :default => :test
7
+
8
+ require "rake/testtask"
9
+ Rake::TestTask.new do |t|
10
+ t.libs << "test"
11
+ t.test_files = FileList["test/**/*_test.rb"]
12
+ t.verbose = true
13
+ end
14
+
15
+ # This builds the actual gem. For details of what all these options
16
+ # mean, and other ones you can add, check the documentation here:
17
+ #
18
+ # http://rubygems.org/read/chapter/20
19
+ #
20
+ spec = Gem::Specification.new do |s|
21
+
22
+ # Change these as appropriate
23
+ s.name = "rubyless"
24
+ s.version = RubyLess::VERSION
25
+ s.summary = %q{RubyLess is an interpreter for "safe ruby". The idea is to transform some "unsafe" ruby code into safe, type checked
26
+ ruby, eventually rewriting some variables or methods}
27
+ s.author = "Gaspard Bucher"
28
+ s.email = "gaspard@teti.ch"
29
+ s.homepage = "http://zenadmin.org/546"
30
+
31
+ s.has_rdoc = true
32
+ s.extra_rdoc_files = %w(README.rdoc)
33
+ s.rdoc_options = %w(--main README.rdoc)
34
+
35
+ # Add any extra files to include in the gem
36
+ s.files = %w(History.txt Rakefile README.rdoc rubyless.gemspec) + Dir.glob("{test,lib}/**/*")
37
+
38
+ s.require_paths = ["lib"]
39
+
40
+ # If you want to depend on other gems, add them here, along with any
41
+ # relevant versions
42
+ # s.add_dependency("some_other_gem", "~> 0.1.0")
43
+
44
+ # If your tests use any gems, include them here
45
+ # s.add_development_dependency("mocha")
46
+
47
+ # If you want to publish automatically to rubyforge, you'll may need
48
+ # to tweak this, and the publishing task below too.
49
+ s.rubyforge_project = "rubyless"
50
+ end
51
+
52
+ # This task actually builds the gem. We also regenerate a static
53
+ # .gemspec file, which is useful if something (i.e. GitHub) will
54
+ # be automatically building a gem for this project. If you're not
55
+ # using GitHub, edit as appropriate.
56
+ Rake::GemPackageTask.new(spec) do |pkg|
57
+ pkg.gem_spec = spec
58
+
59
+ # Generate the gemspec file for github.
60
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
61
+ File.open(file, "w") {|f| f << spec.to_ruby }
62
+ end
63
+
64
+ # Generate documentation
65
+ Rake::RDocTask.new do |rd|
66
+ rd.main = "README.rdoc"
67
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
68
+ rd.rdoc_dir = "rdoc"
69
+ end
70
+
71
+ desc 'Clear out RDoc and generated packages'
72
+ task :clean => [:clobber_rdoc, :clobber_package] do
73
+ rm "#{spec.name}.gemspec"
74
+ end
75
+
76
+ # If you want to publish to RubyForge automatically, here's a simple
77
+ # task to help do that. If you don't, just get rid of this.
78
+ # Be sure to set up your Rubyforge account details with the Rubyforge
79
+ # gem; you'll need to run `rubyforge setup` and `rubyforge config` at
80
+ # the very least.
81
+ begin
82
+ require "rake/contrib/sshpublisher"
83
+ namespace :rubyforge do
84
+
85
+ desc "Release gem and RDoc documentation to RubyForge"
86
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
87
+
88
+ namespace :release do
89
+ desc "Release a new version of this gem"
90
+ task :gem => [:package] do
91
+ require 'rubyforge'
92
+ rubyforge = RubyForge.new
93
+ rubyforge.configure
94
+ rubyforge.login
95
+ rubyforge.userconfig['release_notes'] = spec.summary
96
+ path_to_gem = File.join(File.dirname(__FILE__), "pkg", "#{spec.name}-#{spec.version}.gem")
97
+ puts "Publishing #{spec.name}-#{spec.version.to_s} to Rubyforge..."
98
+ rubyforge.add_release(spec.rubyforge_project, spec.name, spec.version.to_s, path_to_gem)
99
+ end
100
+
101
+ desc "Publish RDoc to RubyForge."
102
+ task :docs => [:rdoc] do
103
+ config = YAML.load(
104
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
105
+ )
106
+
107
+ host = "#{config['username']}@rubyforge.org"
108
+ remote_dir = "/var/www/gforge-projects/rubyless/" # Should be the same as the rubyforge project name
109
+ local_dir = 'rdoc'
110
+
111
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
112
+ end
113
+ end
114
+ end
115
+ rescue LoadError
116
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
117
+ end
118
+
119
+ desc 'Generate the gemspec to serve this Gem from Github'
120
+ task :github do
121
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
122
+ File.open(file, 'w') {|f| f << spec.to_ruby }
123
+ puts "Created gemspec: #{file}"
124
+ end
@@ -0,0 +1,304 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'rubygems'
5
+ require 'parse_tree'
6
+ require 'SafeClass'
7
+ =begin rdoc
8
+ =end
9
+ module RubyLess
10
+ VERSION = '0.1.0'
11
+
12
+ def self.translate(string, helper)
13
+ RubyLessProcessor.translate(string, helper)
14
+ end
15
+
16
+ class Boolean
17
+ end
18
+
19
+ class Number
20
+ include SafeClass
21
+ safe_method( [:==, Number] => Boolean, [:< , Number] => Boolean, [:> , Number] => Boolean, [:<=, Number] => Boolean, [:>=, Number] => Boolean,
22
+ [:- , Number] => Number, [:+ , Number] => Number, [:* , Number] => Number, [:/ , Number] => Number,
23
+ [:% , Number] => Number, [:"-@"] => Number )
24
+ end
25
+
26
+
27
+ class Missing
28
+ [:==, :< , :> , :<=, :>=, :"?"].each do |sym|
29
+ define_method(sym) do |arg|
30
+ false
31
+ end
32
+ end
33
+
34
+ def to_s
35
+ ''
36
+ end
37
+
38
+ def nil?
39
+ true
40
+ end
41
+
42
+ def method_missing(*meth)
43
+ self
44
+ end
45
+ end
46
+
47
+ Nil = Missing.new
48
+
49
+ class TypedString < String
50
+ attr_reader :klass, :opts
51
+
52
+ def initialize(content = "", opts = nil)
53
+ opts ||= {:class => String}
54
+ replace(content)
55
+ @opts = opts.dup
56
+ if could_be_nil? && !@opts[:cond]
57
+ @opts[:cond] = [self.to_s]
58
+ end
59
+ end
60
+
61
+ def klass
62
+ @opts[:class]
63
+ end
64
+
65
+ def could_be_nil?
66
+ @opts[:nil]
67
+ end
68
+
69
+ # condition when 'could_be_nil' comes from a different method then the last one:
70
+ # var1.spouse.name == ''
71
+ # "var1.spouse" would be the condition that inserted 'could_be_nil?'.
72
+ def cond
73
+ @opts[:cond]
74
+ end
75
+
76
+ # raw result without nil checking:
77
+ # "var1.spouse.name" instead of "(var1.spouse ? var1.spouse.name : nil)"
78
+ def raw
79
+ @opts[:raw] || self.to_s
80
+ end
81
+
82
+ def <<(typed_string)
83
+ append_opts(typed_string)
84
+ if self.empty?
85
+ replace(typed_string.raw)
86
+ else
87
+ replace("#{self.raw}, #{typed_string.raw}")
88
+ end
89
+ end
90
+
91
+ def append_opts(typed_string)
92
+ if self.empty?
93
+ @opts = typed_string.opts.dup
94
+ else
95
+ if klass.kind_of?(Array)
96
+ klass << typed_string.klass
97
+ else
98
+ @opts[:class] = [klass, typed_string.klass]
99
+ end
100
+ append_cond(typed_string.cond) if typed_string.could_be_nil?
101
+ end
102
+ end
103
+
104
+ def append_cond(condition)
105
+ @opts[:cond] ||= []
106
+ @opts[:cond] += [condition].flatten
107
+ @opts[:cond].uniq!
108
+ end
109
+ end
110
+
111
+ class RubyLessProcessor < SexpProcessor
112
+ attr_reader :ruby
113
+
114
+ INFIX_OPERATOR = [:"<=>", :==, :<, :>, :<=, :>=, :-, :+, :*, :/, :%]
115
+ PREFIX_OPERATOR = [:"-@"]
116
+
117
+ def self.translate(string, helper)
118
+ sexp = ParseTree.translate(string)
119
+ self.new(helper).process(sexp)
120
+ end
121
+
122
+ def initialize(helper)
123
+ super()
124
+ @helper = helper
125
+ @indent = " "
126
+ self.auto_shift_type = true
127
+ self.strict = true
128
+ self.expected = TypedString
129
+ end
130
+
131
+ def process_and(exp)
132
+ t "(#{process(exp.shift)} and #{process(exp.shift)})", Boolean
133
+ end
134
+
135
+ def process_or(exp)
136
+ t "(#{process(exp.shift)} or #{process(exp.shift)})", Boolean
137
+ end
138
+
139
+ def process_not(exp)
140
+ t "not #{process(exp.shift)}", Boolean
141
+ end
142
+
143
+ def process_if(exp)
144
+ cond = process(exp.shift)
145
+ true_res = process(exp.shift)
146
+ false_res = process(exp.shift)
147
+
148
+ if true_res && false_res && true_res.klass != false_res.klass
149
+ raise "Error in conditional expression: '#{true_res}' and '#{false_res}' do not return results of same type (#{true_res.klass} != #{false_res.klass})."
150
+ end
151
+ raise "Error in conditional expression." unless true_res || false_res
152
+ opts = {}
153
+ opts[:nil] = true_res.nil? || true_res.could_be_nil? || false_res.nil? || false_res.could_be_nil?
154
+ opts[:class] = true_res ? true_res.klass : false_res.klass
155
+ t "#{cond} ? #{true_res || 'nil'} : #{false_res || 'nil'}", opts
156
+ end
157
+
158
+ def process_call(exp)
159
+ receiver_node_type = exp.first.nil? ? nil : exp.first.first
160
+ receiver = process exp.shift
161
+
162
+ # receiver = t("(#{receiver})", receiver.klass) if
163
+ # Ruby2Ruby::ASSIGN_NODES.include? receiver_node_type
164
+
165
+ method_call(receiver, exp)
166
+ end
167
+
168
+ def process_fcall(exp)
169
+ method_call(nil, exp)
170
+ end
171
+
172
+ def process_arglist(exp)
173
+ code = t("")
174
+ until exp.empty? do
175
+ code << process(exp.shift)
176
+ end
177
+ code
178
+ end
179
+
180
+ def process_array(exp)
181
+ res = process_arglist(exp)
182
+ exp.size > 1 ? t("[#{res}]", res.opts) : res
183
+ end
184
+
185
+ def process_vcall(exp)
186
+ var_name = exp.shift
187
+ unless opts = get_method([var_name], @helper, false)
188
+ raise "Unknown variable or method '#{var_name}'."
189
+ end
190
+ method = opts[:method] || var_name.to_s
191
+ t method, opts
192
+ end
193
+
194
+ def process_lit(exp)
195
+ t exp.shift.to_s, Number
196
+ end
197
+
198
+ def process_str(exp)
199
+ t exp.shift.inspect, String
200
+ end
201
+
202
+ def process_dstr(exp)
203
+ t "\"#{parse_dstr(exp)}\"", String
204
+ end
205
+
206
+ def process_evstr(exp)
207
+ exp.empty? ? t('', String) : process(exp.shift)
208
+ end
209
+
210
+ private
211
+ def t(content, opts = nil)
212
+ if opts.nil?
213
+ opts = {:class => String}
214
+ elsif !opts.kind_of?(Hash)
215
+ opts = {:class => opts}
216
+ end
217
+ TypedString.new(content, opts)
218
+ end
219
+
220
+ def t_if(cond, true_res, opts)
221
+ if cond != []
222
+ if cond.size > 1
223
+ condition = "(#{cond.join(' && ')})"
224
+ else
225
+ condition = cond.join('')
226
+ end
227
+
228
+ # we can append to 'raw'
229
+ if opts[:nil]
230
+ # applied method could produce a nil value (so we cannot concat method on top of 'raw' and only check previous condition)
231
+ t "(#{condition} ? #{true_res} : nil)", opts
232
+ else
233
+ # we can keep on checking only 'condition' and appending methods to 'raw'
234
+ t "(#{condition} ? #{true_res} : nil)", opts.merge(:nil => true, :cond => cond, :raw => true_res)
235
+ end
236
+ else
237
+ t true_res, opts
238
+ end
239
+ end
240
+
241
+ def method_call(receiver, exp)
242
+ method = exp.shift
243
+ if args = exp.shift rescue nil
244
+ args = process args || []
245
+ signature = [method] + [args.klass].flatten ## FIXME: error prone !
246
+ # execution conditional
247
+ cond = args.cond || []
248
+ else
249
+ args = []
250
+ signature = [method]
251
+ cond = []
252
+ end
253
+
254
+ if receiver
255
+ if receiver.could_be_nil?
256
+ cond += receiver.cond
257
+ end
258
+ raise "'#{receiver}' does not respond to '#{method}(#{args.raw})'." unless opts = get_method(signature, receiver.klass)
259
+ method = opts[:method] if opts[:method]
260
+ if method == :/
261
+ t_if cond, "(#{receiver.raw}#{method}#{args.raw} rescue nil)", opts.merge(:nil => true)
262
+ elsif INFIX_OPERATOR.include?(method)
263
+ t_if cond, "(#{receiver.raw}#{method}#{args.raw})", opts
264
+ elsif PREFIX_OPERATOR.include?(method)
265
+ t_if cond, "#{method.to_s[0..0]}#{receiver.raw}", opts
266
+ else
267
+ args = "(#{args.raw})" if args != []
268
+ t_if cond, "#{receiver.raw}.#{method}#{args}", opts
269
+ end
270
+ else
271
+ raise "Unknown method '#{method}(#{args.raw})'." unless opts = get_method(signature, @helper, false)
272
+ method = opts[:method] if opts[:method]
273
+ args = "(#{args.raw})" if args != []
274
+ t_if cond, "#{method}#{args}", opts
275
+ end
276
+ end
277
+
278
+ def parse_dstr(exp, in_regex = false)
279
+ res = escape_str(exp.shift, in_regex)
280
+
281
+ while part = exp.shift
282
+ case part.first
283
+ when :str then
284
+ res << escape_str(part.last, in_regex)
285
+ else
286
+ res << '#{' << process(part) << '}'
287
+ end
288
+ end
289
+ res
290
+ end
291
+
292
+ def escape_str(str, in_regex = false)
293
+ res = str.gsub(/"/, '\"').gsub(/\n/, '\n')
294
+ res.gsub!(/\//, '\/') if in_regex
295
+ res
296
+ end
297
+
298
+ def get_method(signature, receiver, is_method = true)
299
+ res = receiver.respond_to?(:safe_method?) ? receiver.safe_method?(signature) : @helper.class.safe_method_for?(receiver, signature)
300
+ res = res.call(@helper) if res.kind_of?(Proc)
301
+ res
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,98 @@
1
+ module RubyLess
2
+ module SafeClass
3
+ def self.included(base)
4
+ # add all methods from the module "AddActsAsMethod" to the 'base' module
5
+ base.class_eval <<-END
6
+ @@_safe_methods ||= {} # defined for each class
7
+ @@_safe_methods_all ||= {} # full list with inherited attributes
8
+
9
+ def self.safe_method(hash)
10
+ list = (@@_safe_methods[self] ||= {})
11
+ hash.each do |k,v|
12
+ k = [k] unless k.kind_of?(Array)
13
+ v = {:class => v} unless v.kind_of?(Hash) || v.kind_of?(Proc)
14
+ list[k] = v
15
+ end
16
+ end
17
+
18
+ def self.safe_methods
19
+ safe_methods_for(self)
20
+ end
21
+
22
+ def self.safe_methods_for(klass)
23
+ @@_safe_methods_all[klass] ||= build_safe_methods_list(klass)
24
+ end
25
+
26
+ def self.build_safe_methods_list(klass)
27
+ list = klass.superclass.respond_to?(:safe_methods) ? klass.superclass.safe_methods : {}
28
+ (@@_safe_methods[klass] || {}).map do |signature, return_value|
29
+ if return_value.kind_of?(Hash)
30
+ return_value[:class] = parse_class(return_value[:class])
31
+ elsif !return_value.kind_of?(Proc)
32
+ return_value = {:class => return_value}
33
+ end
34
+ signature.map! {|e| parse_class(e)}
35
+ list[signature] = return_value
36
+ end
37
+ list
38
+ end
39
+
40
+ def self.safe_method?(signature)
41
+ if res = safe_methods[signature]
42
+ res.dup
43
+ else
44
+ nil
45
+ end
46
+ end
47
+
48
+ def self.safe_method_for?(klass, signature)
49
+ if res = safe_methods_for(klass)[signature]
50
+ res.dup
51
+ else
52
+ nil
53
+ end
54
+ end
55
+
56
+ def safe_method?(signature)
57
+ self.class.safe_methods[signature]
58
+ end
59
+
60
+ def self.safe_method_for(klass, hash)
61
+ list = (@@_safe_methods[klass] ||= {})
62
+ hash.each do |k,v|
63
+ k = [k] unless k.kind_of?(Array)
64
+ v = {:class => v} unless v.kind_of?(Hash) || v.kind_of?(Proc)
65
+ list[k] = v
66
+ end
67
+ end
68
+
69
+ def self.parse_class(klass)
70
+ if klass.kind_of?(Array)
71
+ if klass[0].kind_of?(String)
72
+ [Module::const_get(klass[0])]
73
+ else
74
+ klass
75
+ end
76
+ else
77
+ if klass.kind_of?(String)
78
+ Module::const_get(klass)
79
+ else
80
+ klass
81
+ end
82
+ end
83
+ end
84
+
85
+ def self.safe_attribute?(sym)
86
+ column_names.include?(sym) || zafu_readable?(sym) || safe_attribute_list.include?(sym.to_s)
87
+ end
88
+
89
+ def self.zafu_readable?(sym)
90
+ if sym.to_s =~ /(.*)_zips?$/
91
+ return true if self.ancestors.include?(Node) && RelationProxy.find_by_role($1.singularize)
92
+ end
93
+ self.zafu_readable_attributes.include?(sym.to_s)
94
+ end
95
+ END
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{rubyless}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Gaspard Bucher"]
9
+ s.date = %q{2009-06-02}
10
+ s.email = %q{gaspard@teti.ch}
11
+ s.extra_rdoc_files = ["README.rdoc"]
12
+ s.files = ["History.txt", "Rakefile", "README.rdoc", "rubyless.gemspec", "test/mock", "test/mock/dummy_class.rb", "test/RubyLess", "test/RubyLess/basic.yml", "test/RubyLess/errors.yml", "test/RubyLess_test.rb", "test/test_helper.rb", "lib/RubyLess.rb", "lib/SafeClass.rb"]
13
+ s.has_rdoc = true
14
+ s.homepage = %q{http://zenadmin.org/546}
15
+ s.rdoc_options = ["--main", "README.rdoc"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{rubyless}
18
+ s.rubygems_version = %q{1.3.1}
19
+ s.summary = %q{RubyLess is an interpreter for "safe ruby". The idea is to transform some "unsafe" ruby code into safe, type checked ruby, eventually rewriting some variables or methods}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 2
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ else
27
+ end
28
+ else
29
+ end
30
+ end
@@ -0,0 +1,81 @@
1
+ empty:
2
+ src: ""
3
+ tem: null
4
+
5
+ numbers:
6
+ src: "id > 45 and (3 > -id or 3+3)"
7
+ tem: "((var1.zip>45) and ((3>-var1.zip) or (3+3)))"
8
+
9
+ global_method:
10
+ src: "strftime(now, '%Y')"
11
+ tem: "strftime(Time.now, \"%Y\")"
12
+
13
+ dynamic_string:
14
+ src: "strftime(now, \"#{name}\")"
15
+ tem: "strftime(Time.now, \"#{var1.name}\")"
16
+
17
+ dynamic_string_again:
18
+ src: "strftime(now, \"#{name}\")"
19
+ tem: "strftime(Time.now, \"#{var1.name}\")"
20
+
21
+ rewrite_variables:
22
+ src: "!prev.ancestor?(main) && !node.ancestor?(main)"
23
+ tem: "(not previous.ancestor?(@node) and not var1.ancestor?(@node))"
24
+
25
+ method_can_return_nil:
26
+ src: "spouse.name"
27
+ tem: "(var1.spouse ? var1.spouse.name : nil)"
28
+
29
+ method_on_method_can_return_nil:
30
+ src: "spouse.name == 'yo'"
31
+ tem: "(var1.spouse ? (var1.spouse.name==\"yo\") : nil)"
32
+ res: ""
33
+
34
+ nil_greater_then:
35
+ src: "spouse.id > 1"
36
+ tem: "(var1.spouse ? (var1.spouse.zip>1) : nil)"
37
+
38
+ nil_ternary_op:
39
+ src: "spouse ? 'foo' : 'bar'"
40
+ tem: "var1.spouse ? \"foo\" : \"bar\""
41
+ res: 'bar'
42
+
43
+ nested_ternary_op:
44
+ src: "spouse.name == 'Adam' ? 'man' : 'not a man'"
45
+ tem: "(var1.spouse ? (var1.spouse.name==\"Adam\") : nil) ? \"man\" : \"not a man\""
46
+ res: "not a man"
47
+
48
+ method_on_method:
49
+ src: "project.name.to_s"
50
+ tem: "var1.project.name.to_s"
51
+ res: 'project'
52
+
53
+ comp_ternary_op:
54
+ src: "1 > 2 ? 'foo' : 'bar'"
55
+ tem: "(1>2) ? \"foo\" : \"bar\""
56
+ res: "bar"
57
+
58
+ method_ternary_op:
59
+ src: "id > 2 ? 'foo' : 'bar'"
60
+ tem: "(var1.zip>2) ? \"foo\" : \"bar\""
61
+ res: "foo"
62
+
63
+ method_argument_can_be_nil:
64
+ src: "vowel_count(spouse.name)"
65
+ tem: "(var1.spouse ? vowel_count(var1.spouse.name) : nil)"
66
+
67
+ multi_arg_method_argument_can_be_nil:
68
+ src: "log_info(spouse, 'foobar')"
69
+ tem: "(var1.spouse ? log_info(var1.spouse, \"foobar\") : nil)"
70
+
71
+ multi_arg_method_arguments_can_be_nil:
72
+ src: "log_info(husband, spouse.name)"
73
+ tem: "((var1.husband && var1.spouse) ? log_info(var1.husband, var1.spouse.name) : nil)"
74
+
75
+ multi_arg_method_arguments_can_be_nil_same_condition:
76
+ src: "log_info(spouse, spouse.name)"
77
+ tem: "(var1.spouse ? log_info(var1.spouse, var1.spouse.name) : nil)"
78
+
79
+ literal_argument_for_method:
80
+ src: "vowel_count('ruby')"
81
+ res: "2"
@@ -0,0 +1,16 @@
1
+ unknown_global_method:
2
+ src: "system('echo date')"
3
+ res: "Unknown method 'system(\"echo date\")'."
4
+
5
+ bad_argument_types:
6
+ src: "strftime(34,'ffoo')"
7
+ res: "Unknown method 'strftime(34, \"ffoo\")'."
8
+
9
+ zero_div:
10
+ src: "1/(id-10)"
11
+ tem: "(1/(var1.zip-10) rescue nil)"
12
+ res: ""
13
+
14
+ no_looping:
15
+ src: "while(true) do puts 'flood' end"
16
+ res: 'Bug! Unknown node-type :while to RubyLess::RubyLessProcessor'
@@ -0,0 +1,65 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class SimpleHelper < Test::Unit::TestCase
4
+ attr_reader :context
5
+ yamltest :src_from_title => false
6
+ include RubyLess::SafeClass
7
+ safe_method :prev => {:class => Dummy, :method => 'previous'}
8
+ safe_method :main => {:class => Dummy, :method => '@node'}
9
+ safe_method :node => lambda {|h| {:class => h.context[:node_class], :method => h.context[:node]}}
10
+ safe_method :now => {:class => Time, :method => 'Time.now'}
11
+ safe_method [:strftime, Time, String] => String
12
+ safe_method [:vowel_count, String] => RubyLess::Number
13
+ safe_method [:log_info, Dummy, String] => String
14
+ safe_method_for String, [:==, String] => RubyLess::Boolean
15
+ safe_method_for String, [:to_s] => String
16
+
17
+ def safe_method?(signature)
18
+ unless res = self.class.safe_method?(signature)
19
+ # try to execute method in the current var "var.method"
20
+ if res = context[:node_class].safe_method?(signature)
21
+ res = res.call(self) if res.kind_of?(Proc)
22
+ res[:method] = "#{context[:node]}.#{res[:method] || signature[0]}"
23
+ end
24
+ end
25
+ res
26
+ end
27
+
28
+ def var1
29
+ Dummy.new
30
+ end
31
+
32
+ def vowel_count(str)
33
+ str.tr('^aeiouy', '').size
34
+ end
35
+
36
+ def log_info(obj, msg)
37
+ "[#{obj.name}] #{msg}"
38
+ end
39
+
40
+ def yt_do_test(file, test, context = yt_get('context',file,test))
41
+ @@test_strings[file][test].keys.each do |key|
42
+ next if ['src', 'context'].include?(key)
43
+ yt_assert yt_get(key,file,test), parse(key, file, test, context)
44
+ end
45
+ end
46
+
47
+ def parse(key, file, test, opts)
48
+ @context = {:node => 'var1', :node_class => Dummy}
49
+ source = yt_get('src', file, test)
50
+ case key
51
+ when 'tem'
52
+ source ? RubyLess.translate(source, self) : yt_get('tem', file, test)
53
+ when 'res'
54
+ eval(source ? RubyLess.translate(source, self) : yt_get('tem', file, test)).to_s
55
+ else
56
+ "Unknown key '#{key}'. Should be 'tem' or 'res'."
57
+ end
58
+ rescue => err
59
+ # puts "\n\n#{err.message}"
60
+ # puts err.backtrace
61
+ err.message
62
+ end
63
+
64
+ yt_make
65
+ end
@@ -0,0 +1,35 @@
1
+ class Dummy
2
+ attr_reader :name
3
+ include RubyLess::SafeClass
4
+
5
+ safe_method [:ancestor?, Dummy] => RubyLess::Boolean
6
+ safe_method :parent => {:class => 'Dummy', :special_option => 'foobar'}
7
+ safe_method :children => ['Dummy']
8
+ safe_method :project => 'Dummy'
9
+ safe_method :spouse => {:class => 'Dummy', :nil => true}
10
+ safe_method :husband => {:class => 'Dummy', :nil => true}
11
+ safe_method :id => {:class => RubyLess::Number, :method => :zip}
12
+ safe_method :name => String
13
+
14
+ def initialize(name = 'dummy')
15
+ @name = name
16
+ end
17
+
18
+ # This method returns pseudo-nil and does not need to be declared with :nil => true
19
+ def project
20
+ Dummy.new('project')
21
+ end
22
+
23
+ # This method can return nil and must be declared with :nil => true
24
+ def spouse
25
+ nil
26
+ end
27
+
28
+ def husband
29
+ nil
30
+ end
31
+
32
+ def zip
33
+ 10
34
+ end
35
+ end
@@ -0,0 +1,6 @@
1
+ require 'stringio'
2
+ require 'test/unit'
3
+ require File.dirname(__FILE__) + '/../lib/RubyLess'
4
+ require File.dirname(__FILE__) + '/mock/dummy_class'
5
+ require 'rubygems'
6
+ require 'yamltest'
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubyless
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gaspard Bucher
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-02 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: gaspard@teti.ch
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - History.txt
26
+ - Rakefile
27
+ - README.rdoc
28
+ - rubyless.gemspec
29
+ - test/mock
30
+ - test/mock/dummy_class.rb
31
+ - test/RubyLess
32
+ - test/RubyLess/basic.yml
33
+ - test/RubyLess/errors.yml
34
+ - test/RubyLess_test.rb
35
+ - test/test_helper.rb
36
+ - lib/RubyLess.rb
37
+ - lib/SafeClass.rb
38
+ has_rdoc: true
39
+ homepage: http://zenadmin.org/546
40
+ post_install_message:
41
+ rdoc_options:
42
+ - --main
43
+ - README.rdoc
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project: rubyless
61
+ rubygems_version: 1.3.1
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: RubyLess is an interpreter for "safe ruby". The idea is to transform some "unsafe" ruby code into safe, type checked ruby, eventually rewriting some variables or methods
65
+ test_files: []
66
+