rubyless 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,9 +1,23 @@
1
+ == 0.3.0 2009-10-03
2
+
3
+ * 1 major enhancement
4
+ * Moved from ParseTree to RubyParser
5
+
6
+ * 1 minor enhancement:
7
+ * Using Mr Bones to generate gems
8
+
9
+ == 0.2.1 2009-07-01
10
+
11
+ * 2 minor enhancements:
12
+ * :[] method is now treated as in conventional ruby (foo[:bar], not foo.[](:bar))
13
+ * Better error reporting in case of signature mismatch
14
+
1
15
  == 0.2.0 2009-06-02
2
16
 
3
17
  * 1 major enhancement:
4
18
  * Added support for ActiveRecord attributes
5
19
 
6
- * 2 minor enhancement:
20
+ * 2 minor enhancements:
7
21
  * Better documentation
8
22
  * Removed eval (this means safe methods are globally declared)
9
23
 
@@ -5,16 +5,19 @@
5
5
  == DESCRIPTION:
6
6
 
7
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:
8
+ ruby, eventually rewriting some variables or methods.
9
+
10
+
11
+ == GOALS:
9
12
 
10
13
  1. give ruby scripting access to users without any security risk
11
14
  2. rewrite variable names depending on compilation context
12
- 3. never raise runtime errors through compile time type checking and powerful nil handling
15
+ 3. never raise runtime errors through compile time type checking and powerful nil handling
13
16
 
14
17
  This library is based on Ruby2Ruby by Ryan Davis, thanks to him for sharing his work.
15
18
 
16
19
  == SYNOPSIS:
17
-
20
+
18
21
  For every class that will be involved in your RubyLess scripts, you need to declare safe methods with the 'safe_method' macro if
19
22
  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
23
  return 'nil' instead of the declared output, you need to wrap your final ruby 'eval' with a rescue clause.
@@ -22,17 +25,17 @@ return 'nil' instead of the declared output, you need to wrap your final ruby 'e
22
25
  # signature is made of [method, arg_class, arg_class, ...]
23
26
  class Node
24
27
  include RubyLess::SafeClass
25
- safe_method [:ancestor?, Node] => RubyLess::Boolean
28
+ safe_method [:ancestor?, Node] => Boolean
26
29
  end
27
-
30
+
28
31
  # methods defined in helper
29
-
32
+
30
33
  # global methods
31
34
  include RubyLess::SafeClass
32
35
  safe_method :prev => {:class => Dummy, :method => 'previous', :nil => true}
33
36
  safe_method :node => lambda {|h| {:class => h.context[:node_class], :method => h.context[:node]}}
34
37
  safe_method [:strftime, Time, String] => String
35
- safe_method_for String, [:==, String] => RubyLess::Boolean
38
+ safe_method_for String, [:==, String] => Boolean
36
39
  safe_method_for String, [:to_s] => String
37
40
 
38
41
  You can also redefine 'safe_method_type' for any class or for the main helper in order to do some more complicated renaming. Note
@@ -48,20 +51,20 @@ You can now parse some ruby code:
48
51
 
49
52
  RubyLess.translate("!prev.ancestor?(main) && !node.ancestor?(main)", self)
50
53
  => "(not previous.ancestor?(@node) and not var1.ancestor?(@node))"
51
-
54
+
52
55
  RubyLess.translate("id > 45 and (3 > -id or 3+3)", self)
53
56
  => "(var1.zip>45 and ((3>-var1.zip) or (3+3)))"
54
-
57
+
55
58
  RubyLess.translate("strftime(now, '%Y')", self)
56
59
  => "strftime(Time.now, \"%Y\")"
57
-
60
+
58
61
  RubyLess.translate("log_info(spouse, spouse.name)", self)
59
62
  => "(var1.spouse ? log_info(var1.spouse, var1.spouse.name) : nil)"
60
63
 
61
64
  You can look at the tests for an idea of how to declare things. If you have more questions, ask on zena's mailing list:
62
65
 
63
66
  http://zenadmin.org/community
64
-
67
+
65
68
  == REQUIREMENTS:
66
69
 
67
70
  * parse_tree
data/Rakefile CHANGED
@@ -1,7 +1,5 @@
1
1
  require "rubygems"
2
- require "rake/gempackagetask"
3
2
  require "rake/rdoctask"
4
- require "lib/rubyless"
5
3
 
6
4
  task :default => :test
7
5
 
@@ -12,113 +10,39 @@ Rake::TestTask.new do |t|
12
10
  t.verbose = true
13
11
  end
14
12
 
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
13
 
31
- s.has_rdoc = true
32
- s.extra_rdoc_files = %w(README.rdoc)
33
- s.rdoc_options = %w(--main README.rdoc)
14
+ # BONES gem management
34
15
 
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
16
  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
17
+ require 'bones'
18
+ Bones.setup
115
19
  rescue LoadError
116
- puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
20
+ begin
21
+ load 'tasks/setup.rb'
22
+ rescue LoadError
23
+ raise RuntimeError, '### please install the "bones" gem ###'
24
+ end
117
25
  end
118
26
 
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
27
+ ensure_in_path 'lib'
28
+ require 'rubyless'
29
+
30
+ PROJ.name = 'rubyless'
31
+ PROJ.authors = 'Gaspard Bucher'
32
+ PROJ.email = 'gaspard@teti.ch'
33
+ PROJ.url = 'http://zenadmin.org/546'
34
+ PROJ.version = RubyLess::VERSION
35
+ PROJ.rubyforge.name = 'rubyless'
36
+
37
+ PROJ.spec.opts << '--color'
38
+ PROJ.gem.files = (
39
+ ['History.txt', 'README.txt', 'Rakefile', 'rubyless.gemspec'] +
40
+ ['lib', 'test'].map do |d|
41
+ Dir.glob("#{d}/**/*").reject {|path| File.basename(path) =~ /^\./ }
42
+ end
43
+ ).flatten
44
+
45
+ PROJ.gem.dependencies << ['ruby_parser', '>= 2.0.4']
46
+ PROJ.gem.dependencies << ['sexp_processor', '>= 3.0.1']
47
+ PROJ.gem.development_dependencies << ['yamltest', '>= 0.5.3']
48
+
data/lib/basic_types.rb CHANGED
@@ -1,39 +1,15 @@
1
1
  require 'safe_class'
2
2
 
3
- module RubyLess
4
-
5
- class Boolean
6
- end
7
-
8
- class Number
9
- include SafeClass
10
- safe_method( [:==, Number] => Boolean, [:< , Number] => Boolean, [:> , Number] => Boolean, [:<=, Number] => Boolean, [:>=, Number] => Boolean,
11
- [:- , Number] => Number, [:+ , Number] => Number, [:* , Number] => Number, [:/ , Number] => Number,
12
- [:% , Number] => Number, [:"-@"] => Number )
13
- end
14
-
15
-
16
- class Missing
17
- [:==, :< , :> , :<=, :>=, :"?"].each do |sym|
18
- define_method(sym) do |arg|
19
- false
20
- end
21
- end
22
-
23
- def to_s
24
- ''
25
- end
26
-
27
- def nil?
28
- true
29
- end
30
-
31
- def method_missing(*meth)
32
- self
33
- end
34
- end
35
-
36
- Nil = Missing.new
37
3
 
38
-
39
- end
4
+ class Boolean
5
+ end
6
+
7
+ class Number
8
+ end
9
+
10
+
11
+ RubyLess::SafeClass.safe_method_for( Number,
12
+ [:==, Number] => Boolean, [:< , Number] => Boolean, [:> , Number] => Boolean, [:<=, Number] => Boolean, [:>=, Number] => Boolean,
13
+ [:- , Number] => Number, [:+ , Number] => Number, [:* , Number] => Number, [:/ , Number] => Number,
14
+ [:% , Number] => Number, [:"-@"] => Number )
15
+
data/lib/processor.rb CHANGED
@@ -1,11 +1,14 @@
1
1
  require 'rubygems'
2
- require 'parse_tree'
2
+ require 'ruby_parser'
3
+ require 'sexp_processor'
3
4
 
4
5
  require 'basic_types'
5
6
  require 'typed_string'
6
7
  require 'safe_class'
8
+ require 'ruby-debug'
9
+ Debugger.start
7
10
 
8
- module RubyLess
11
+ module RubyLess
9
12
  class RubyLessProcessor < SexpProcessor
10
13
  attr_reader :ruby
11
14
 
@@ -13,19 +16,25 @@ module RubyLess
13
16
  PREFIX_OPERATOR = [:"-@"]
14
17
 
15
18
  def self.translate(string, helper)
16
- sexp = ParseTree.translate(string)
19
+ sexp = RubyParser.new.parse(string)
17
20
  self.new(helper).process(sexp)
18
21
  end
19
22
 
20
23
  def initialize(helper)
21
24
  super()
22
- @helper = helper
23
- @indent = " "
25
+ @helper = helper
26
+ @indent = " "
24
27
  self.auto_shift_type = true
25
28
  self.strict = true
26
29
  self.expected = TypedString
27
30
  end
28
31
 
32
+ #def process(exp)
33
+ # return nil if exp.nil?
34
+ # method = exp.shift
35
+ # send("process_#{method}", exp)
36
+ #end
37
+
29
38
  def process_and(exp)
30
39
  t "(#{process(exp.shift)} and #{process(exp.shift)})", Boolean
31
40
  end
@@ -37,12 +46,12 @@ module RubyLess
37
46
  def process_not(exp)
38
47
  t "not #{process(exp.shift)}", Boolean
39
48
  end
40
-
49
+
41
50
  def process_if(exp)
42
51
  cond = process(exp.shift)
43
52
  true_res = process(exp.shift)
44
53
  false_res = process(exp.shift)
45
-
54
+
46
55
  if true_res && false_res && true_res.klass != false_res.klass
47
56
  raise "Error in conditional expression: '#{true_res}' and '#{false_res}' do not return results of same type (#{true_res.klass} != #{false_res.klass})."
48
57
  end
@@ -90,7 +99,8 @@ module RubyLess
90
99
  end
91
100
 
92
101
  def process_lit(exp)
93
- t exp.shift.to_s, Number
102
+ lit = exp.shift
103
+ t lit.inspect, lit.class == Symbol ? Symbol : Number
94
104
  end
95
105
 
96
106
  def process_str(exp)
@@ -114,7 +124,7 @@ module RubyLess
114
124
  end
115
125
  TypedString.new(content, opts)
116
126
  end
117
-
127
+
118
128
  def t_if(cond, true_res, opts)
119
129
  if cond != []
120
130
  if cond.size > 1
@@ -122,7 +132,7 @@ module RubyLess
122
132
  else
123
133
  condition = cond.join('')
124
134
  end
125
-
135
+
126
136
  # we can append to 'raw'
127
137
  if opts[:nil]
128
138
  # applied method could produce a nil value (so we cannot concat method on top of 'raw' and only check previous condition)
@@ -138,9 +148,14 @@ module RubyLess
138
148
 
139
149
  def method_call(receiver, exp)
140
150
  method = exp.shift
141
- if args = exp.shift rescue nil
142
- args = process args || []
143
- signature = [method] + [args.klass].flatten ## FIXME: error prone !
151
+ arg_sexp = args = exp.shift # rescue nil
152
+ if arg_sexp
153
+ args = process(arg_sexp)
154
+ if args == ''
155
+ signature = [method]
156
+ else
157
+ signature = [method] + [args.klass].flatten
158
+ end
144
159
  # execution conditional
145
160
  cond = args.cond || []
146
161
  else
@@ -148,12 +163,12 @@ module RubyLess
148
163
  signature = [method]
149
164
  cond = []
150
165
  end
151
-
166
+
152
167
  if receiver
153
168
  if receiver.could_be_nil?
154
169
  cond += receiver.cond
155
170
  end
156
- raise "'#{receiver}' does not respond to '#{method}(#{args.raw})'." unless opts = get_method(signature, receiver.klass)
171
+ raise "'#{receiver}' does not respond to '#{method}(#{signature[1..-1].join(', ')})'." unless opts = get_method(signature, receiver.klass)
157
172
  method = opts[:method] if opts[:method]
158
173
  if method == :/
159
174
  t_if cond, "(#{receiver.raw}#{method}#{args.raw} rescue nil)", opts.merge(:nil => true)
@@ -161,14 +176,16 @@ module RubyLess
161
176
  t_if cond, "(#{receiver.raw}#{method}#{args.raw})", opts
162
177
  elsif PREFIX_OPERATOR.include?(method)
163
178
  t_if cond, "#{method.to_s[0..0]}#{receiver.raw}", opts
179
+ elsif method == :[]
180
+ t_if cond, "#{receiver.raw}[#{args.raw}]", opts
164
181
  else
165
- args = "(#{args.raw})" if args != []
182
+ args = "(#{args.raw})" if args != ''
166
183
  t_if cond, "#{receiver.raw}.#{method}#{args}", opts
167
184
  end
168
185
  else
169
186
  raise "Unknown method '#{method}(#{args.raw})'." unless opts = get_method(signature, @helper, false)
170
187
  method = opts[:method] if opts[:method]
171
- args = "(#{args.raw})" if args != []
188
+ args = "(#{args.raw})" if args != ''
172
189
  t_if cond, "#{method}#{args}", opts
173
190
  end
174
191
  end
@@ -192,7 +209,7 @@ module RubyLess
192
209
  res.gsub!(/\//, '\/') if in_regex
193
210
  res
194
211
  end
195
-
212
+
196
213
  def get_method(signature, receiver, is_method = true)
197
214
  res = receiver.respond_to?(:safe_method_type) ? receiver.safe_method_type(signature) : SafeClass.safe_method_type_for(receiver, signature)
198
215
  res = res.call(@helper) if res.kind_of?(Proc)
data/lib/rubyless.rb CHANGED
@@ -6,8 +6,8 @@ require 'processor'
6
6
  =begin rdoc
7
7
  =end
8
8
  module RubyLess
9
- VERSION = '0.2.0'
10
-
9
+ VERSION = '0.3.0'
10
+
11
11
  def self.translate(string, helper)
12
12
  RubyLessProcessor.translate(string, helper)
13
13
  end