rubypython 0.3.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.autotest +3 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +13 -0
  4. data/.hgignore +14 -0
  5. data/.hgtags +7 -0
  6. data/.rspec +1 -1
  7. data/Contributors.rdoc +9 -0
  8. data/History.rdoc +148 -0
  9. data/{License.txt → License.rdoc} +7 -1
  10. data/Manifest.txt +15 -10
  11. data/PostInstall.txt +11 -4
  12. data/README.rdoc +272 -0
  13. data/Rakefile +107 -22
  14. data/autotest/discover.rb +1 -0
  15. data/lib/rubypython.rb +214 -120
  16. data/lib/rubypython/blankobject.rb +16 -14
  17. data/lib/rubypython/conversion.rb +242 -173
  18. data/lib/rubypython/legacy.rb +30 -31
  19. data/lib/rubypython/macros.rb +43 -34
  20. data/lib/rubypython/operators.rb +103 -101
  21. data/lib/rubypython/options.rb +41 -44
  22. data/lib/rubypython/pygenerator.rb +61 -0
  23. data/lib/rubypython/pymainclass.rb +46 -29
  24. data/lib/rubypython/pyobject.rb +193 -177
  25. data/lib/rubypython/python.rb +189 -176
  26. data/lib/rubypython/pythonerror.rb +54 -63
  27. data/lib/rubypython/pythonexec.rb +123 -0
  28. data/lib/rubypython/rubypyproxy.rb +213 -137
  29. data/lib/rubypython/type.rb +20 -0
  30. data/spec/basic_spec.rb +50 -0
  31. data/spec/callback_spec.rb +7 -17
  32. data/spec/conversion_spec.rb +7 -21
  33. data/spec/legacy_spec.rb +1 -16
  34. data/spec/pymainclass_spec.rb +6 -15
  35. data/spec/pyobject_spec.rb +39 -64
  36. data/spec/python_helpers/basics.py +20 -0
  37. data/spec/python_helpers/objects.py +24 -20
  38. data/spec/pythonerror_spec.rb +5 -17
  39. data/spec/refcnt_spec.rb +4 -10
  40. data/spec/rubypyclass_spec.rb +1 -11
  41. data/spec/rubypyproxy_spec.rb +45 -54
  42. data/spec/rubypython_spec.rb +45 -57
  43. data/spec/spec_helper.rb +49 -33
  44. metadata +87 -63
  45. data.tar.gz.sig +0 -0
  46. data/History.markdown +0 -97
  47. data/README.markdown +0 -105
  48. data/lib/rubypython/core_ext/string.rb +0 -7
  49. data/lib/rubypython/version.rb +0 -9
  50. data/spec/python_helpers/objects.pyc +0 -0
  51. metadata.gz.sig +0 -0
data/Rakefile CHANGED
@@ -1,22 +1,107 @@
1
- require 'rspec/core/rake_task'
2
- require 'yard'
3
-
4
- desc "Run all examples"
5
- RSpec::Core::RakeTask.new('spec') do |t|
6
- t.pattern = 'spec/**/*_spec.rb'
7
- t.rspec_opts = '-t ~@slow' unless ENV['filter'] == 'none'
8
- end
9
-
10
- desc "Run all examples with RCov"
11
- RSpec::Core::RakeTask.new('spec:rcov') do |t|
12
- t.pattern = 'spec/**/*.rb'
13
- t.rspec_opts = '--tag ~slow:true' unless ENV['filter'] == 'none'
14
- t.rcov = true
15
- t.rcov_opts = ['--exclude', 'spec']
16
- end
17
-
18
- YARD::Rake::YardocTask.new do |t|
19
- t.options = [ '--markup','markdown', '--title', 'RubyPython Documentation' ]
20
- end
21
-
22
- Dir['tasks/**/*.rake'].each { |rake| load rake }
1
+ # -*- ruby encoding: utf-8 -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.plugin :doofus
7
+ Hoe.plugin :gemspec
8
+ Hoe.plugin :git
9
+ Hoe.plugin :hg
10
+
11
+ Hoe.spec 'rubypython' do |spec|
12
+ spec.rubyforge_name = spec.name
13
+
14
+ developer('Steeve Morin', 'swiuzzz+rubypython@gmail.com')
15
+ developer('Austin Ziegler', 'austin@rubyforge.org')
16
+ developer('Zach Raines', 'raineszm+rubypython@gmail.com')
17
+
18
+ spec.remote_rdoc_dir = 'rdoc'
19
+ spec.rsync_args << ' --exclude=statsvn/'
20
+
21
+ spec.history_file = 'History.rdoc'
22
+ spec.readme_file = 'README.rdoc'
23
+ spec.extra_rdoc_files = FileList["*.rdoc"].to_a
24
+
25
+ spec.extra_deps << ['ffi', '~> 1.0.7']
26
+ spec.extra_deps << ['blankslate', '>= 2.1.2.3']
27
+
28
+ spec.extra_dev_deps << ['rspec', '~> 2.0']
29
+ spec.extra_dev_deps << ['tilt', '~> 1.0']
30
+
31
+ spec.spec_extras[:requirements] = [ "Python, ~> 2.4" ]
32
+ end
33
+
34
+ namespace :website do
35
+ desc "Build the website files."
36
+ task :build => [ "website/index.html" ]
37
+
38
+ deps = FileList["website/**/*"].exclude { |f| File.directory? f }
39
+ deps.include(*%w(Rakefile))
40
+ deps.include(*FileList["*.rdoc"].to_a)
41
+ deps.exclude(*%w(website/index.html website/images/*))
42
+
43
+ file "website/index.html" => deps do |t|
44
+ require 'tilt'
45
+ require 'rubypython'
46
+
47
+ puts "Generating #{t.name}…"
48
+
49
+ # Let's modify the rdoc for presenation purposes.
50
+ body_rdoc = File.read("README.rdoc")
51
+
52
+ contrib = File.read("Contributors.rdoc")
53
+ body_rdoc.gsub!(/^:include: Contributors.rdoc/, contrib)
54
+
55
+ license = File.read("License.rdoc")
56
+ body_rdoc.sub!(/^:include: License.rdoc/, license)
57
+ toc_elements = body_rdoc.scan(/^(=+) (.*)$/)
58
+ toc_elements.map! { |e| [ e[0].count('='), e[1] ] }
59
+ body_rdoc.gsub!(/^(=.*)/) { "#{$1.downcase}" }
60
+ body = Tilt::RDocTemplate.new(nil) { body_rdoc }.render
61
+
62
+ title = nil
63
+ body.gsub!(%r{<h1>(.*)</h1>}) { title = $1; "" }
64
+
65
+ toc_elements = toc_elements.select { |e| e[0].between?(2, 3) }
66
+
67
+ last_level = 0
68
+ toc = ""
69
+
70
+ toc_elements.each do |element|
71
+ level, text = *element
72
+ ltext = text.downcase
73
+ id = text.downcase.gsub(/[^a-z]+/, '-')
74
+
75
+ body.gsub!(%r{<h#{level}>#{ltext}</h#{level}>}) {
76
+ %Q(<h#{level} id="#{id}">#{ltext}</h#{level}>)
77
+ }
78
+
79
+ if last_level != level
80
+ if level > last_level
81
+ toc << "<ol>"
82
+ else
83
+ toc << "</li></ol></li>"
84
+ end
85
+
86
+ last_level = level
87
+ end
88
+
89
+ toc << %Q(<li><a href="##{id}">#{text}</a>)
90
+ end
91
+ toc << "</li></ol>"
92
+
93
+ template = Tilt.new("website/index.rhtml", :trim => "<>%")
94
+ context = {
95
+ :title => title,
96
+ :toc => toc,
97
+ :body => body,
98
+ :download => "http://rubyforge.org/frs/?group_id=6737",
99
+ :version => RubyPython::VERSION,
100
+ :modified => Time.now
101
+ }
102
+ File.open(t.name, "w") { |f| f.write template.render(self, context) } end
103
+ end
104
+
105
+ task "docs" => "website:build"
106
+
107
+ # vim: syntax=ruby
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "rspec2" }
data/lib/rubypython.rb CHANGED
@@ -1,176 +1,270 @@
1
- require 'rubypython/core_ext/string'
1
+ # RubyPython is a bridge between the Ruby and \Python interpreters. It
2
+ # embeds a \Python interpreter in the Ruby application's process using FFI
3
+ # and provides a means for wrapping, converting, and calling \Python objects
4
+ # and methods.
5
+ #
6
+ # == Usage
7
+ # The \Python interpreter must be started before the RubyPython bridge is
8
+ # functional. The user can either manually manage the running of the
9
+ # interpreter as shown below, or use the +RubyPython.run+ or
10
+ # +RubyPython.session+ methods to automatically start and stop the
11
+ # interpreter.
12
+ #
13
+ # RubyPython.start
14
+ # cPickle = RubyPython.import "cPickle"
15
+ # puts cPickle.dumps("RubyPython is awesome!").rubify
16
+ # RubyPython.stop
17
+ module RubyPython
18
+ VERSION = '0.5.0' #:nodoc:
19
+
20
+ # Do not load the FFI interface by default. Wait until the user asks for
21
+ # it.
22
+ @load_ffi = false
23
+
24
+ # Indicates whether the \Python DLL has been loaded. For internal use
25
+ # only.
26
+ def self.load_ffi? #:nodoc:
27
+ @load_ffi
28
+ end
29
+ end
30
+
31
+ require 'rubypython/blankobject'
2
32
  require 'rubypython/options'
3
33
  require 'rubypython/python'
4
34
  require 'rubypython/pythonerror'
5
35
  require 'rubypython/pyobject'
6
36
  require 'rubypython/rubypyproxy'
7
37
  require 'rubypython/pymainclass'
38
+ require 'rubypython/pygenerator'
8
39
 
9
-
10
- #This module provides the direct user interface for the RubyPython extension.
11
- #
12
- #RubyPython interfaces to the Python C API via the {Python} module using the
13
- #Ruby FFI gem. However, the end user should only worry about dealing with the
14
- #methods made avaiable via the RubyPython module.
15
- #
16
- #Usage
17
- #-----
18
- #It is important to remember that the Python Interpreter must be
19
- #started before the bridge is functional. This will start the embedded
20
- #interpreter. If this approach is used, the user should remember to call
21
- #RubyPython.stop when they are finished with Python.
22
- #@example
23
- # RubyPython.start
24
- # cPickle = RubyPython.import "cPickle"
25
- # puts cPickle.dumps("RubyPython is awesome!").rubify
26
- # RubyPython.stop
27
- #
28
- #Legacy Mode vs Normal Mode
29
- #---------------------------
30
- #By default RubyPython always returns a proxy class which refers method calls to
31
- #the wrapped Python object. If you instead would like RubyPython to aggressively
32
- #attempt conversion of return values, as it did in RubyPython 0.2.x, then you
33
- #should set {RubyPython.legacy_mode} to true. In this case RubyPython will
34
- #attempt to convert any return value from Python to a native Ruby type, and only
35
- #return a proxy if conversion is not possible. For further examples see
36
- #{RubyPython.legacy_mode}.
37
40
  module RubyPython
38
-
39
-
40
41
  class << self
41
-
42
- #Determines whether RubyPython is operating in Normal Mode or Legacy Mode.
43
- #If legacy_mode is true, RubyPython switches into a mode compatible with
44
- #versions < 0.3.0. All Python objects returned by method invocations are
45
- #automatically converted to natve Ruby Types if RubyPython knows how to do
46
- #this. Only if no such conversion is known are the objects wrapped in proxy
47
- #objects. Otherwise RubyPython automatically wraps all returned objects as
48
- #an instance of {RubyPyProxy} or one of its subclasses.
49
- #@return [Boolean]
50
- #@example Normal Mode
51
- # RubyPython.start
52
- # string = RubyPython.import 'string'
53
- # ascii_letters = string.ascii_letters # Here ascii_letters is a proxy object
54
- # puts ascii_letters.rubify # we use the rubify method to convert it to a
55
- # # native type
56
- # RubyPython.stop
57
- #
58
- #@example Legacy Mode
59
- # RubyPython.legacy_mode = true
60
- # RubyPython.start
61
- # string = RubyPython.import 'string'
62
- # ascii_letters = string.ascii_letters # Here ascii_letters is a native ruby string
63
- # puts ascii_letters # No explicit conversion is neccessary
64
- # RubyPython.stop
42
+ # Controls whether RubyPython is operating in <em>Normal Mode</em> or
43
+ # <em>Legacy Mode</em>.
44
+ #
45
+ # === Normal Mode
46
+ # By default, +legacy_mode+ is +false+, meaning that any object returned
47
+ # from a \Python function call will be wrapped in an instance of
48
+ # +RubyPyProxy+ or one of its subclasses. This allows \Python method
49
+ # calls to be forwarded to the \Python object, even if it would otherwise
50
+ # be a native Ruby object.
51
+ #
52
+ # RubyPython.session do
53
+ # string = RubyPython.import 'string'
54
+ # ascii_letters = string.ascii_letters
55
+ # puts ascii_letters.isalpha # => True
56
+ # puts ascii_letters.rubify.isalpha # throws NoMethodError
57
+ # end
58
+ #
59
+ # === Legacy Mode
60
+ # If +legacy_mode+ is +true+, RubyPython automatically tries to convert
61
+ # returned objects to native Ruby object types. If there is no such
62
+ # conversion, the object remains wrapped in +RubyPyProxy+. This
63
+ # behaviour is the same as RubyPython 0.2 and earlier. This mode is not
64
+ # recommended and may be phased out for RubyPython 1.0.
65
+ #
66
+ # RubyPython.legacy_mode = true
67
+ # RubyPython.session do
68
+ # string = RubyPython.import 'string'
69
+ # ascii_letters = string.ascii_letters
70
+ # puts ascii_letters.isalpha # throws NoMethodError
71
+ # end
65
72
  attr_accessor :legacy_mode
66
73
 
67
- #Starts ups the Python interpreter. This method **must** be run
68
- #before using any Python code. The only alternatives are use of the
69
- #{session} and {run} methods. If the Python interpreter needs to
70
- #be loaded or reloaded, it will be done here.
71
- #@return [Boolean] returns true if the interpreter was started here
72
- # and false otherwise
73
- def start
74
- unless @loaded
75
- @loaded = true
74
+ # Starts the \Python interpreter. Either +RubyPython.start+,
75
+ # +RubyPython.session+, or +RubyPython.run+ must be run before using any
76
+ # \Python code. Returns +true+ if the interpreter was started; +false+
77
+ # otherwise.
78
+ #
79
+ # [options] Configures the interpreter prior to starting it. Principally
80
+ # used to provide an alternative \Python interpreter to start.
81
+ #
82
+ # With no options provided:
83
+ # RubyPython.start
84
+ # sys = RubyPython.import 'sys'
85
+ # p sys.version # => "2.6.6"
86
+ # RubyPython.stop
87
+ #
88
+ # With an alternative \Python executable:
89
+ # RubyPython.start(:python_exe => 'python2.7')
90
+ # sys = RubyPython.import 'sys'
91
+ # p sys.version # => "2.7.1"
92
+ # RubyPython.stop
93
+ #
94
+ # *NOTE*: In the current version of RubyPython, it _is_ possible to
95
+ # change \Python interpreters in a single Ruby process execution, but it
96
+ # is *strongly* discouraged as this may lead to segmentation faults.
97
+ # This feature is highly experimental and may be disabled in the future.
98
+ def start(options = {})
99
+ RubyPython.configure(options)
100
+
101
+ unless @load_ffi
102
+ @load_ffi = true
103
+ @reload = false
76
104
  reload_library
77
105
  end
78
- if Python.Py_IsInitialized != 0
79
- return false
80
- end
106
+
107
+ return false if RubyPython::Python.Py_IsInitialized != 0
108
+
81
109
  if @reload
82
110
  reload_library
83
111
  @reload = false
84
112
  end
85
- Python.Py_Initialize
113
+
114
+ RubyPython::Python.Py_Initialize
86
115
  notify :start
87
116
  true
88
117
  end
89
118
 
90
- #Stops the Python interpreter if it is running. Returns true if the
91
- #intepreter is stopped by this invocation. All wrapped Python objects
92
- #should be considered invalid after invocation of this method.
93
- #@return [Boolean] returns true if the interpreter was stopped here
94
- # and false otherwise
119
+ # Stops the \Python interpreter if it is running. Returns +true+ if the
120
+ # intepreter is stopped. All wrapped \Python objects are invalid after
121
+ # invocation of this method. If you need the values within the \Python
122
+ # proxy objects, be sure to call +RubyPyProxy#rubify+ on them.
95
123
  def stop
96
- if Python.Py_IsInitialized !=0
124
+ if defined? Python.Py_IsInitialized and Python.Py_IsInitialized != 0
97
125
  Python.Py_Finalize
98
126
  notify :stop
99
- return true
127
+ true
128
+ else
129
+ false
100
130
  end
101
- false
102
131
  end
103
132
 
104
- #Import a Python module into the interpreter and return a proxy object
105
- #for it. This is the preferred way to gain access to Python object.
106
- #@param [String] mod_name the name of the module to import
107
- #@return [RubyPyModule] a proxy object wrapping the requested
108
- #module
133
+ # Import a \Python module into the interpreter and return a proxy object
134
+ # for it.
135
+ #
136
+ # This is the preferred way to gain access to \Python objects.
137
+ #
138
+ # [mod_name] The name of the module to import.
109
139
  def import(mod_name)
110
- pModule = Python.PyImport_ImportModule mod_name
111
- if(PythonError.error?)
112
- raise PythonError.handle_error
140
+ if defined? Python.Py_IsInitialized and Python.Py_IsInitialized != 0
141
+ pModule = Python.PyImport_ImportModule mod_name
142
+ raise PythonError.handle_error if PythonError.error?
143
+ pymod = PyObject.new pModule
144
+ RubyPyModule.new(pymod)
145
+ else
146
+ raise "Python has not been started."
113
147
  end
114
- pymod = PyObject.new pModule
115
- RubyPyModule.new(pymod)
116
148
  end
117
149
 
118
- #Execute the given block, starting the Python interperter before its execution
119
- #and stopping the interpreter after its execution. The last expression of the
120
- #block is returned; be careful that this is not a Python object as it will
121
- #become invalid when the interpreter is stopped.
122
- #@param [Block] block the code to be executed while the interpreter is running
123
- #@return the result of evaluating the given block
124
- def session
125
- start
126
- result = yield
150
+ # Starts the \Python interpreter (optionally with options) and +yields+
151
+ # to the provided block. When the block exits for any reason, the
152
+ # \Python interpreter is stopped automatically.
153
+ #
154
+ # The last executed expression of the block is returned. Be careful that
155
+ # the last expression of the block does not return a RubyPyProxy object,
156
+ # because the proxy object will be invalidated when the interpreter is
157
+ # stopped.
158
+ #
159
+ # [options] Configures the interpreter prior to starting it. Principally
160
+ # used to provide an alternative \Python interpreter to start.
161
+ #
162
+ # *NOTE*: In the current version of RubyPython, it _is_ possible to change
163
+ # \Python interpreters in a single Ruby process execution, but it is
164
+ # *strongly* discouraged as this may lead to segmentation faults. This
165
+ # feature is highly experimental and may be disabled in the future.
166
+ #
167
+ # :call-seq:
168
+ # session(options = {}) { block to execute }
169
+ def session(options = {})
170
+ start(options)
171
+ yield
172
+ ensure
127
173
  stop
128
- result
129
174
  end
130
175
 
131
- #The same as {session} except that the block is executed within the scope
132
- #of the RubyPython module.
133
- #@return the result of evaluating the given block.
134
- def run(&block)
135
- start
136
- result = module_eval(&block)
176
+ # Starts the \Python interpreter (optionally with options) and executes
177
+ # the provided block in the RubyPython module scope. When the block
178
+ # exits for any reason, the \Python interpreter is stopped
179
+ # automatically.
180
+ #
181
+ # The last executed expression of the block is returned. Be careful that
182
+ # the last expression of the block does not return a RubyPyProxy object,
183
+ # because the proxy object will be invalidated when the interpreter is
184
+ # stopped.
185
+ #
186
+ # [options] Configures the interpreter prior to starting it. Principally
187
+ # used to provide an alternative \Python interpreter to start.
188
+ #
189
+ # *NOTE*: In the current version of RubyPython, it _is_ possible to
190
+ # change \Python interpreters in a single Ruby process execution, but it
191
+ # is *strongly* discouraged as this may lead to segmentation faults.
192
+ # This feature is highly experimental and may be disabled in the future.
193
+ #
194
+ # :call-seq:
195
+ # run(options = {}) { block to execute in RubyPython context }
196
+ def run(options = {}, &block)
197
+ start(options)
198
+ module_eval(&block)
199
+ ensure
137
200
  stop
201
+ end
202
+
203
+ # Starts the \Python interpreter for a
204
+ # {virtualenv}[http://pypi.python.org/pypi/virtualenv] virtual
205
+ # environment. Returns +true+ if the interpreter was started.
206
+ #
207
+ # [virtualenv] The root path to the virtualenv-installed \Python
208
+ # interpreter.
209
+ #
210
+ # RubyPython.start_from_virtualenv('/path/to/virtualenv')
211
+ # sys = RubyPython.import 'sys'
212
+ # p sys.version # => "2.7.1"
213
+ # RubyPython.stop
214
+ #
215
+ # *NOTE*: In the current version of RubyPython, it _is_ possible to
216
+ # change \Python interpreters in a single Ruby process execution, but it
217
+ # is *strongly* discouraged as this may lead to segmentation faults.
218
+ # This feature is highly experimental and may be disabled in the future.
219
+ def start_from_virtualenv(virtualenv)
220
+ result = start(:python => File.join(virtualenv, "bin", "python"))
221
+ activate
138
222
  result
139
223
  end
140
224
 
141
- private
225
+ # Returns an object describing the currently active Python interpreter.
226
+ def python
227
+ RubyPython::Python::EXEC
228
+ end
229
+
230
+ # Used to activate the virtualenv.
231
+ def activate
232
+ imp = import("imp")
233
+ imp.load_source("activate_this",
234
+ File.join(File.dirname(RubyPython::Python::EXEC.python),
235
+ "activate_this.py"))
236
+ end
237
+ private :activate
142
238
 
143
239
  def add_observer(object)
144
240
  @observers ||= []
145
241
  @observers << object
146
242
  true
147
243
  end
244
+ private :add_observer
148
245
 
149
246
  def notify(status)
150
- if not @observers.nil?
151
- @observers.each do |o|
152
- o.update status
153
- end
247
+ @observers ||= []
248
+ @observers.each do |o|
249
+ next if nil === o
250
+ o.update status
154
251
  end
155
252
  end
253
+ private :notify
156
254
 
157
255
  def reload_library
256
+ # Invalidate the current Python instance, if defined.
257
+ if defined? RubyPython::Python::EXEC and RubyPython::Python::EXEC
258
+ RubyPython::Python::EXEC.instance_eval { invalidate! }
259
+ end
158
260
  remove_const :Python
159
- load 'rubypython/python.rb'
261
+ load RubyPython::PYTHON_RB
160
262
  true
161
263
  end
162
-
163
- end
164
-
165
- [
166
- PyMain,
167
- Operators,
168
- PyObject::AutoPyPointer
169
- ].each do |observer|
170
- add_observer observer
264
+ private :reload_library
171
265
  end
172
266
 
173
-
267
+ add_observer PyMain
268
+ add_observer Operators
269
+ add_observer PyObject::AutoPyPointer
174
270
  end
175
-
176
-