├── .autotest
├── .gitignore
├── .hgignore
├── .hgtags
├── .rspec
├── Contributors.rdoc
├── History.rdoc
├── License.rdoc
├── Manifest.txt
├── PostInstall.txt
├── README.rdoc
├── Rakefile
├── autotest
└── discover.rb
├── lib
├── rubypython.rb
└── rubypython
│ ├── blankobject.rb
│ ├── conversion.rb
│ ├── interpreter.rb
│ ├── macros.rb
│ ├── operators.rb
│ ├── pygenerator.rb
│ ├── pymainclass.rb
│ ├── pyobject.rb
│ ├── python.rb
│ ├── pythonerror.rb
│ ├── rubypyproxy.rb
│ ├── tuple.rb
│ └── type.rb
├── rubypython.gemspec
├── spec
├── basic_spec.rb
├── callback_spec.rb
├── conversion_spec.rb
├── pymainclass_spec.rb
├── pyobject_spec.rb
├── python_helpers
│ ├── basics.py
│ ├── errors.py
│ └── objects.py
├── pythonerror_spec.rb
├── refcnt_spec.rb
├── rubypyclass_spec.rb
├── rubypyproxy_spec.rb
├── rubypython_spec.rb
└── spec_helper.rb
└── website
├── index.rhtml
├── robots.txt
└── stylesheets
├── 960.css
├── border-radius.htc
└── screen.css
/.autotest:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 |
3 | # vim: syntax=ruby
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.bundle
2 | *.log
3 | *.o
4 | *.pyc
5 | *.so
6 | *.swp
7 | *~
8 | .DS_Store
9 | .rake_tasks~
10 | .rvmrc
11 | .yardoc/
12 | coverage.info
13 | coverage/
14 | doc/
15 | html/
16 | pkg/
17 | publish/
18 | website/index.html
19 |
--------------------------------------------------------------------------------
/.hgignore:
--------------------------------------------------------------------------------
1 | syntax:glob
2 | *.bundle
3 | *.log
4 | *.o
5 | *.pyc
6 | *.so
7 | *.swp
8 | *~
9 | .DS_Store
10 | .rake_tasks~
11 | .rvmrc
12 | .yardoc/*
13 | coverage.info
14 | coverage/*
15 | doc/*
16 | ext/*/Makefile
17 | html/*
18 | pkg/*
19 | publish/*
20 | website/index.html
21 |
--------------------------------------------------------------------------------
/.hgtags:
--------------------------------------------------------------------------------
1 | 910a6e5ac94959fd6d9b04caea995afd354531f6 v0.2.3
2 | 7361975ce7bbbdd284e1d9f84344d4f8df6e9eab v0.2.8
3 | 09e65f05148ae3e1ba5f54156cd7bcf159cfe14f v0.2.9
4 | 7ebd15a009f37aefab52e8e669080eed165320a0 v0.2.10
5 | fe1fe7136461a92e4012cd7c8f23c8c446d4fbc1 v0.2.11
6 | 746fffb7ee3db61278d592c303939daeb6a17a03 v0.3.1
7 | e61cfeb18e14c8d0d86e68459bf7957535c4452b v0.3.2
8 | 0e18cecdd4b7d6261a708a9b60b80a4794efac89 r0.5.0
9 | cd45222e2319bf006798c50f5c18669ae6160246 r0.5.2
10 | b82963530f976af0cc0f4e62488b5112028fdfc9 r0.5.3
11 | 4e39510b4683d1394361106ddb387e5b85f18789 r0.6.0
12 | 898514bedf99296b48fdb95945745ff523ad3b11 r0.6.1
13 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --colour
2 | --format documentation
3 |
--------------------------------------------------------------------------------
/Contributors.rdoc:
--------------------------------------------------------------------------------
1 | == Contributors
2 |
3 | RubyPython has a growing list of contributors.
4 |
5 | * Zach Raines (raineszm)
6 | * The creator of RubyPython.
7 | * Steeve Morin (steeve)
8 | * Austin Ziegler (halostatue)
9 | * Aman Gupta (tmm1)
10 | * Ben Doerr (bendoerr)
11 | * Akzhan Abdulin (azkhan)
12 |
--------------------------------------------------------------------------------
/History.rdoc:
--------------------------------------------------------------------------------
1 | === 0.6.4 / 2017-06-20
2 | * Fix long conversion issue
3 | * Fix lib path for Debian/Ubuntu
4 |
5 | === 0.6.4 / 2012-09-25
6 | * Major Enhancements
7 | * Legacy Mode removed
8 | * Minor Enhancements
9 | * Depend on newer versions of FFI
10 | * Make RubyPython correctly detect python on archlinux. [madjar]
11 | * Bug Fixes:
12 | * Fixed some memory leaks, but still looking for others.
13 |
14 | === 0.6.2 / 2012-05-24
15 | * Bug Fixes:
16 | * Fixed a possible namespace collision bug with the FFI library. [Akzhan]
17 |
18 | === 0.6.1 / 2012-05-18
19 | * Bug Fixes:
20 | * Widened the library naming schemes that RubyPython will search for the Python library. [Akzhan]
21 | * Better handling of 64-bit systems which also have 32-bit libraries. [Akzhan]
22 |
23 | === 0.6 / 2012-04-17
24 | * Major Enhancements:
25 | * Previous versions of RubyPython theoretically allowed the loading of
26 | different Python interpreters during a single Ruby session. Because of the
27 | likelihood of crashes, this functionality has been removed. Now, if you
28 | attempt to start RubyPython more than once while specifying different
29 | Python interpreters, RubyPython will print a warning and continue working
30 | with the already loaded interpreter.
31 | * The Python interpreter DLL will only be loaded once. It is configured with
32 | a self-removing method, Interpreter#infect! instead of require/load reload
33 | hacks.
34 | * Removed RubyPython.configure; since the interpreter can only be configured
35 | once, independent configuration no longer makes sense.
36 | * Minor Enhancements:
37 | * RubyPython interpreter initialization is done with a Mutex synchronization
38 | to ensure that only one Python interpreter DLL is loaded.
39 | * Added RubyPython::Tuple, a simple subclass of ::Array that will correctly
40 | be converted to a Python tuple object such that isinstance(x, tuple)
41 | returns True.
42 | * Renamed RubyPython::PythonExec to RubyPython::Interpreter. Added some
43 | helper methods to assist with comparison. Construction is with an options
44 | hash.
45 | * The #update methods on Python interpreter observers are now private. This
46 | is an implementation detail, not a public interface. (The methods have also
47 | been renamed to #python_interpreter_update.)
48 | * Deprecation:
49 | * RubyPython's legacy mode (automatic unboxing of Python proxy objects where
50 | possible) has been deprecated and will be removed in the next non-bugfix
51 | release after this version. Introduced a new private method
52 | (RubyPython.legacy_mode?) to check if legacy mode is turned on so that the
53 | deprecation warning is not printed in all uses of RubyPython.
54 |
55 | === 0.5.3 / 2011-10-22
56 | * Bug Fixes:
57 | * Improved 64-bit Python detection on Unixes with Linux-like semantics (e.g.,
58 | .../lib64). Resolves issue #7 on bitbucket.
59 |
60 | === 0.5.2 / 2011-10-21
61 | * Minor Enhancements:
62 | * The traceback for Python exceptions is now returned usefully. [raineszm]
63 | * Bug Fixes:
64 | * Improved the robustness of Windows DLL detection. Based on work by
65 | bendoerr, raineszm, and halostatue.
66 |
67 | === 0.5.1 / 2011-03-17
68 | * Major Enhancements:
69 | * Procs and methods can be passed to Python. [raineszm]
70 | * Python to Ruby inheritance: A Ruby class can inherit from a Python class
71 | (EXPERIMENTAL). [steeve]
72 | * The Python library can now be reloaded (EXPERIMENTAL). There is a good
73 | chance of a segfault if this is used with a native extension. [raineszm]
74 | * Python functions and methods can be called with a terminal exclamation
75 | point to enable keyword argument processing in Python (EXPERIMENTAL). [tmm1]
76 | * Minor Enhancements:
77 | * Improved Python generator support. [steeve]
78 | * PythonError now inherits from RuntimeError. [halostatue]
79 | * Added a Py_REFCNT macro for debugging reference counting. [tmm1]
80 | * Changes:
81 | * Moved to RSpec2. [halostatue]
82 | * Moved Rakefile to hoe; added autotest. [halostatue]
83 | * Bug Fixes:
84 | * Fixed a reference counting bug that could crash the Python VM. [steeve]
85 | * Fixed a memory leak/reference counting bug related to method invocation
86 | (when turning the argument list into a tuple, PyList_SetItem steals
87 | references). Depends on FFI 1.0.7 (or higher) with modifications to
88 | aggressively free temporary Python objects created. [tmm1]
89 | * Restored Ruby 1.8.7 compatibility where possible. [halostatue]
90 | * Fixed FFI loader to be more robust and do more through FFI. [halostatue]
91 | * Fixed documentation errors. [halostatue]
92 | * Project Management:
93 | * Rupy has been merged back into RubyPython.
94 | * Made the project website on RubyForge generatable from the readme and a
95 | template. Adopted the 960 grid system and modern CSS techniques.
96 | * Added a contributors document.
97 |
98 | === 0.4.1 / / 2011-01-10
99 | * Rupy was created as an active fork of Zach Raines's RubyPython and released
100 | indepdently.
101 | * Major Enhancements:
102 | * Python/Ruby callbacks [steeve]
103 | * Virtualenv support [steeve]
104 | * Python-style generators with Ruby (generators only work on Ruby 1.9).
105 | [steeve]
106 |
107 | === 0.3.2 / 2011-02-02
108 | * Major Enhancements
109 | * Allow procs and methods to be passed to Ruby.
110 | * Allow configuration of the loaded Python library.
111 |
112 | === 0.3.1 / 2011-01-19
113 | * Compatability Updates
114 | * Cleanup of code which finds Python library thanks to Austin Ziegler.
115 |
116 | === 0.3.0 / 2010-09-06
117 | * Major Enhancements
118 | * Version 0.3.0 represents an almost complete rewrite of the RubyPython
119 | codebase.
120 | * A native C extension is no longer required. RubyPython uses the 'ffi' gem.
121 | * RubyPython by default now returns proxy instances for all Ruby objects. See
122 | the documentation for more information.
123 | * Compatibility Updates
124 | * Version 0.3.0 was created with the goal of being compatible with as many
125 | Ruby versions as possible. It should at the very least run on MRI 1.8.6 -
126 | 1.9.1.
127 | * A legacy mode option has been added to provide partial compatibility with
128 | version 0.2.x.
129 |
130 | === 0.2.11 / 2010-07-12
131 | * Bug Fixes
132 | * Fixed some issues with building the extension under ruby 1.9. Should now be
133 | truly 1.9 compatible.
134 |
135 | === 0.2.10 / 2010-07-08
136 | * Bug Fixes
137 | * Made some changes to how the native extension is configured and build.
138 |
139 | === 0.2.9 / 2009-10-19
140 | * Minor Enhancements
141 | * Updated the C code to make it cleaner, more readable, and more
142 | maintainable.
143 |
144 | === 0.2.8 / 2009-10-05
145 | * Bug Fixes
146 | * Some test files were improperly formatted (minor bug fix).
147 |
148 | === 0.2.7 / 2009-3-30
149 | * Bug Fixes
150 | * Fixed some bugs which caused rubypython to be unable to determine python
151 | version correctly.
152 |
153 | === 0.2.6 / 2009-3-19
154 | * Bug Fixes
155 | * Further updates to increase compatibility with 1.9.
156 |
157 | === 0.2.5 / 2009-3-18
158 | * Bug Fixes
159 | * Updated to build and run under Ruby 1.9.
160 |
161 | === 0.2.4 / 2008-10-24
162 | * Major Enhancements
163 | * Provided setter methods for object attributes. Python object attributes can
164 | now be set from within ruby.
165 | * Made wrapper classes a subclass of custom made blank object class to try to
166 | avoid name collisions.
167 | * Bug Fix
168 | * Changed part of extconf.rb file that would not work under windows.
169 |
170 | === 0.2.3 / 2008-08-29
171 | * 2 Major Enhancements
172 | * Introduced PyMain object as a singleton wrapper for the Python __main__ and
173 | __builtin__ modules.
174 | * Introduced block functionality for PyMain object.
175 | * Compatibility Updates
176 | * Changed some declarations in the C code to make RubyPython more compatible
177 | with the style conventions of the Ruby C API.
178 | * Update how RubyPython locates the Python headers and library.
179 | * 1 Bug Fix
180 | * Fixed an error in ptor.c that might have prevented RubyPython from building
181 | correctly on certain systems.
182 |
183 | === 0.2.2 / 2008-08-07
184 | * Major Enhancements
185 | * Wrapped classes and instances should now behave as expected.
186 | * Gave RubyPyClasses a "new" method for creating instances.
187 | * Callable class can now be called provided that at least one argument is
188 | given.
189 | * A wrapped object's respond_to? method now has some relation to its actual
190 | methods.
191 | * Bug fixes
192 | * Fixed bug with inspect method of RubyPyObject that caused a bus error when
193 | inspecting certain objects
194 |
195 | === 0.2.1 / 2008-08-02
196 | * 1 Bug Fix
197 | * Incorrect require fixed
198 |
199 | === 0.2.0 / 2008-08-02
200 | * 3 major enhancements
201 | * RubyPython can now effectively convert or wrap most types from Python.
202 | * Errors in the Python interpreter are relayed to Ruby errors.
203 | * Less seg faults by mass.
204 | * Bug Fixes
205 | * RubyPython.run did not work correctly. This is fixed now.
206 | * Cleanup in RubyPython.stop fixes some issues in RubyPythonBridge.stop
207 |
208 | === 0.1.0 / 2008-08-01
209 | * A lot of major enhancements
210 | * Too many to name. Hey I'm still developing
211 |
212 | === 0.0.1 / 2008-07-30
213 | * 1 major enhancement:
214 | * Initial release
215 |
--------------------------------------------------------------------------------
/License.rdoc:
--------------------------------------------------------------------------------
1 | == License
2 |
3 | This software is available under the terms of the MIT license.
4 |
5 | * Copyright 2011 Zach Raines, Steeve Morin, Austin Ziegler, and other
6 | contributors.
7 | * Copyright 2008–2010 Zach Raines.
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining
10 | a copy of this software and associated documentation files (the
11 | "Software"), to deal in the Software without restriction, including
12 | without limitation the rights to use, copy, modify, merge, publish,
13 | distribute, sublicense, and/or sell copies of the Software, and to
14 | permit persons to whom the Software is furnished to do so, subject to
15 | the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be
18 | included in all copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 |
--------------------------------------------------------------------------------
/Manifest.txt:
--------------------------------------------------------------------------------
1 | .autotest
2 | .gitignore
3 | .hgignore
4 | .hgtags
5 | .rspec
6 | Contributors.rdoc
7 | History.rdoc
8 | License.rdoc
9 | Manifest.txt
10 | PostInstall.txt
11 | README.rdoc
12 | Rakefile
13 | autotest/discover.rb
14 | lib/rubypython.rb
15 | lib/rubypython/blankobject.rb
16 | lib/rubypython/conversion.rb
17 | lib/rubypython/interpreter.rb
18 | lib/rubypython/macros.rb
19 | lib/rubypython/operators.rb
20 | lib/rubypython/pygenerator.rb
21 | lib/rubypython/pymainclass.rb
22 | lib/rubypython/pyobject.rb
23 | lib/rubypython/python.rb
24 | lib/rubypython/pythonerror.rb
25 | lib/rubypython/rubypyproxy.rb
26 | lib/rubypython/tuple.rb
27 | lib/rubypython/type.rb
28 | spec/basic_spec.rb
29 | spec/callback_spec.rb
30 | spec/conversion_spec.rb
31 | spec/pymainclass_spec.rb
32 | spec/pyobject_spec.rb
33 | spec/python_helpers/basics.py
34 | spec/python_helpers/errors.py
35 | spec/python_helpers/objects.py
36 | spec/pythonerror_spec.rb
37 | spec/refcnt_spec.rb
38 | spec/rubypyclass_spec.rb
39 | spec/rubypyproxy_spec.rb
40 | spec/rubypython_spec.rb
41 | spec/spec_helper.rb
42 | website/index.rhtml
43 | website/robots.txt
44 | website/stylesheets/960.css
45 | website/stylesheets/border-radius.htc
46 | website/stylesheets/screen.css
47 |
--------------------------------------------------------------------------------
/PostInstall.txt:
--------------------------------------------------------------------------------
1 | RubyPython version 0.3 and higher are different than RubyPython through
2 | version 0.2.x. If you're upgrading from such an earlier version, make sure you
3 | read the documents for instructions on how your code needs to change to work
4 | with modern RubyPython.
5 |
6 | For more information on RubyPython, see: http://rubypython.rubyforge.org/
7 |
8 | If you find a bug, or have any suggestions, please feel free to enter them on
9 | the tracker:
10 |
11 | https://bitbucket.org/raineszm/rubypython/issues?status=new&status=open
12 |
13 | If you would like to help develop RubyPython, please check out the source on
14 | Bitbucket:
15 |
16 | https://bitbucket.org/raineszm/rubypython/
17 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = rubypython
2 |
3 | == Description
4 |
5 | RubyPython is a bridge between the Ruby and Python interpreters. It embeds a
6 | running Python interpreter in the Ruby application's process using FFI and
7 | provides a means for wrapping, converting, and calling Python objects and
8 | methods.
9 |
10 | RubyPython uses FFI to marshal the data between the Ruby and Python VMs and
11 | make Python calls. You can:
12 |
13 | * Inherit from Python classes.
14 | * Configure callbacks from Python.
15 | * Run Python generators (on Ruby 1.9.2 or later).
16 |
17 | == Where
18 |
19 | * {RubyForge}[http://rubypython.rubyforge.org/]
20 | * {RubyGems}[http://rubygems.org/gems/rubypython]
21 | * {Bitbucket}[http://raineszm.bitbucket.org/rubypython/]
22 | * {GitHub}[https://github.com/halostatue/rubypython]
23 |
24 | The RubyPython homepage, project description, and main downloads can be found
25 | on {RubyForge}[http://rubypython.rubyforge.org/].
26 |
27 | Source is kept in sync between
28 | {Bitbucket}[http://raineszm.bitbucket.org/rubypython/] and
29 | {GitHub}[https://github.com/halostatue/rubypython], but the Bitbucket
30 | repository is the canonical repository and where the {issue
31 | tracker}[https://bitbucket.org/raineszm/rubypython/issues?status=new&status=open]
32 | resides. We use {Hg-Git}[http://hg-git.github.com/] to keep the two
33 | repositories in sync.
34 |
35 | == Synopsis
36 |
37 | RubyPython is fairly easy to start using; there are three phases to its use:
38 |
39 | 1. Start the Python interpreter (+RubyPython.start+).
40 | 2. Import and use Python code (+RubyPython.import+).
41 | 3. Stop the Python interpreter (+RubyPython.stop+).
42 |
43 | There are also two methods, +RubyPython.session+ and +RubyPython.run+ that will
44 | start before running the code provided in the block and stop it afterwards.
45 |
46 | === Basic Usage
47 |
48 | require "rubypython"
49 |
50 | RubyPython.start # start the Python interpreter
51 |
52 | cPickle = RubyPython.import("cPickle")
53 | p cPickle.dumps("Testing RubyPython.").rubify
54 |
55 | RubyPython.stop # stop the Python interpreter
56 |
57 | === Specific Python Version
58 |
59 | require "rubypython"
60 |
61 | RubyPython.start(:python_exe => "python2.7") # Can also be a full path
62 |
63 | cPickle = RubyPython.import("cPickle")
64 | p cPickle.dumps("Testing RubyPython.").rubify
65 |
66 | RubyPython.stop # stop the Python interpreter
67 |
68 | === VirtualEnv
69 |
70 | # Easy
71 | RubyPython.start_from_virtualenv("/path/to/virtualenv")
72 |
73 | # Or verbose
74 | RubyPython.start(:python_exe => "/path/to/virtualenv/bin/python")
75 | RubyPython.activate
76 |
77 | === Iterator support
78 |
79 | # Python
80 | def readfile():
81 | for line in open("/some/file"):
82 | yield line
83 |
84 | # Ruby
85 | readfile.to_enum.each do |line|
86 | puts line
87 | end
88 |
89 | # Python
90 | def iterate_list():
91 | for item in [ 1, 2, 3 ]:
92 | yield item
93 |
94 | # Ruby
95 | items = []
96 | iterate_list.to_enum.each { |item| items << item }
97 | puts items == [ 1, 2, 3 ] # => true
98 |
99 | === Python to Ruby callbacks
100 |
101 | # Python
102 | def simple_callback(callback, value):
103 | return callback(value)
104 |
105 | # Ruby
106 | simple_callback(lambda { |v| v * v }, 4) # => 16
107 |
108 | def triple(v)
109 | v * 3
110 | end
111 |
112 | simple_callback(method(:triple), 4) # => 12
113 |
114 | === Python-style Generators
115 |
116 | # Python
117 | def test_generator(callback):
118 | for i in callback():
119 | print "Got %d" % i
120 |
121 | # Ruby 1.9.2 or later
122 | test_generator(RubyPython.generator do
123 | (0..10).each { |i| RubyPython.yield i }
124 | end)
125 |
126 | === Python named arguments (Experimental)
127 |
128 | This format is experimental and may be changed.
129 |
130 | # Python
131 | def foo(arg1, arg2):
132 | pass
133 |
134 | # Ruby
135 | foo!(:arg2 => "bar2", :arg1 => "bar1")
136 |
137 | # with Ruby 1.9
138 | foo!(arg2: "bar2", arg1: "bar1")
139 |
140 | == Features / Problems
141 |
142 | === Features
143 |
144 | * Simple two-way conversion of built-in types between Ruby and Python.
145 | * Python module import and arbitrary method execution.
146 | * Python objects can be treated as Ruby objects.
147 | * Python's standard library available from within Ruby.
148 | * Pass Ruby methods and procs as callbacks and call them from within Python
149 | code.
150 | * Specify the Python executable to be loaded, including using virtualenv.
151 |
152 | ==== Experimental Features
153 |
154 | * Calling Python methods or functions that expect keyword arguments, or call
155 | any Python method or function with named parameters.
156 |
157 | # Python
158 | def func(a, b, c):
159 | pass
160 |
161 | # Ruby
162 | func!(:b => 2, :c => 3, :a => 1) # => [ 1, 2, 3 ]
163 |
164 | While we are committed to keeping this feature in place, we have not yet
165 | determined that the form (+method!+) is the best way to achieve this
166 | functionality.
167 |
168 | This mechanism is experimental because the use of the bang at the end of the
169 | method to indicate the use of keyword arguments may not be the best use of
170 | that feature of Ruby naming.
171 |
172 | * Changing Python interpreters in a single Ruby program. Under some
173 | circumstances, this will partially work. If a native Python extension has
174 | been imported (such as +cPickle+), there is a very high likelihood that there
175 | will be a segmentation fault because the newly loaded DLL will still refer to
176 | the other version's loaded extension. This is not a recommended workflow.
177 |
178 | === Known Problems
179 |
180 | * Built-in Python methods requiring a top-level frame object (such as eval(),
181 | dir(), and the like) do not work properly at present.
182 | * There is no support for passing complicated (non-basic) Ruby types to Python.
183 |
184 | == What's planned
185 | There are features that are not currently supported in RubyPython that may be
186 | considered for future releases, dependent on need, interest, and solutions.
187 |
188 | === Python 3
189 | We do plan on working this, but as none of the projects any of us are working
190 | on require Python 3 as of yet, this is not yet started.
191 |
192 | === Simpler Imports
193 | It might be nice to have some nice import helpers provided by RubyPython to
194 | make the interface more seamless and provide advanced import features:
195 |
196 | ==== Import Aliasing
197 |
198 | # Python
199 | from mod2.mod1 import sym as mysym
200 |
201 | # Ruby
202 | py :from => "mod2.mod1", :import => "sym", :as => "mysym"
203 | py :from => "mod2.mod1", :import => :sym, :as => :mysym
204 | py :from => [ :mod2, :mod1 ], :import => :sym, :as => :mysym
205 |
206 | # Python
207 | import mod1 as mymod
208 |
209 | # Ruby
210 | py :import => "mod1", :as => "mymod"
211 | py :import => :mod1, :as => :mymod
212 |
213 | # Python
214 | from mod2.mod1 import *
215 |
216 | # Ruby
217 | py :from => "mod2.mod1", :import => :*
218 | pyrequire "mod2/mod1" # ruby style imports
219 |
220 | === Catch Exceptions from Ruby
221 |
222 | # Python
223 | class MyFirstException(Exception):
224 | pass
225 |
226 | class MySecondException(MyFirstException):
227 | pass
228 |
229 | def test():
230 | raise MySecondException
231 |
232 | # Ruby
233 | begin
234 | test
235 | rescue MyFirstException => e
236 | # We may need to work out name collisions
237 | puts e.message
238 | end
239 |
240 | == Requirements
241 |
242 | * Python >= 2.4, < 3.0
243 | * Ruby >= 1.8.6, or JRuby >= 1.6.0
244 | * You must either have the ability to build the Ruby
245 | {FFI}[https://github.com/ffi/ffi] gem, version 1.0.7 or better in your
246 | environment or have a pre-built one that you can install.
247 |
248 | === Python Support
249 | RubyPython has been tested with the C-based Python interpreter (cpython),
250 | versions 2.4 through 2.7. Work is planned to enable Python 3 support, but has
251 | not yet been started. If you're interested in helping us enable Python 3
252 | support, please let us know.
253 |
254 | === Ruby Support
255 | * Ruby 1.8.7 and 1.9.2 (MRI)
256 | * JRuby 1.6.0
257 |
258 | It should work with other implementations that support the Ruby FFI gem with no
259 | modification.
260 |
261 | === OS Support
262 | RubyPython has been extensively tested on Mac OS 10.5 and 10.6, and Ubuntu
263 | 10.10 (64-bit Intel). If your platform has a DLL or shared object version of
264 | Python and supports the FFI gem, it should work. Feedback on other platforms is
265 | always welcome.
266 |
267 | == Install
268 | gem install rubypython
269 |
270 | :include: Contributors.rdoc
271 |
272 | :include: License.rdoc
273 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # -*- ruby encoding: utf-8 -*-
2 |
3 | require 'rubygems'
4 | require 'hoe'
5 |
6 | Hoe.plugin :doofus
7 | Hoe.plugin :gemspec2
8 | Hoe.plugin :rubyforge unless ENV['CI'] or ENV['TRAVIS']
9 | Hoe.plugin :git
10 | Hoe.plugin :hg
11 | Hoe.plugin :travis
12 |
13 | Hoe.spec 'rubypython' do
14 | self.rubyforge_name = self.name
15 |
16 | developer('Steeve Morin', 'swiuzzz+rubypython@gmail.com')
17 | developer('Austin Ziegler', 'austin@rubyforge.org')
18 | developer('Zach Raines', 'raineszm+rubypython@gmail.com')
19 |
20 | license 'MIT'
21 |
22 | self.remote_rdoc_dir = 'rdoc'
23 | self.rsync_args << ' --exclude=statsvn/'
24 |
25 | self.history_file = 'History.rdoc'
26 | self.readme_file = 'README.rdoc'
27 | self.extra_rdoc_files = FileList["*.rdoc"].to_a
28 |
29 | self.extra_deps << ['ffi', '~> 1.0.7']
30 | self.extra_deps << ['blankslate', '>= 2.1.2.3']
31 |
32 | self.extra_dev_deps << ['hoe-doofus', '~> 1.0']
33 | self.extra_dev_deps << ['hoe-gemspec2', '~> 1.1']
34 | self.extra_dev_deps << ['hoe-git', '~> 1.5']
35 | self.extra_dev_deps << ['hoe-hg', '~> 1.0']
36 | self.extra_dev_deps << ['hoe-rubygems', '~> 1.0']
37 | self.extra_dev_deps << ['hoe-travis', '~> 1.2']
38 |
39 | self.extra_dev_deps << ['rspec', '~> 2.0']
40 | self.extra_dev_deps << ['tilt', '~> 1.0']
41 |
42 | self.spec_extras[:requirements] = [ "Python, ~> 2.4" ]
43 | end
44 |
45 | namespace :website do
46 | desc "Build the website files."
47 | task :build => [ "website/index.html" ]
48 |
49 | deps = FileList["website/**/*"].exclude { |f| File.directory? f }
50 | deps.include(*%w(Rakefile))
51 | deps.include(*FileList["*.rdoc"].to_a)
52 | deps.exclude(*%w(website/index.html website/images/*))
53 |
54 | file "website/index.html" => deps do |t|
55 | require 'tilt'
56 | require 'rubypython'
57 |
58 | puts "Generating #{t.name}…"
59 |
60 | # Let's modify the rdoc for presenation purposes.
61 | body_rdoc = File.read("README.rdoc")
62 |
63 | contrib = File.read("Contributors.rdoc")
64 | body_rdoc.gsub!(/^:include: Contributors.rdoc/, contrib)
65 |
66 | license = File.read("License.rdoc")
67 | body_rdoc.sub!(/^:include: License.rdoc/, license)
68 | toc_elements = body_rdoc.scan(/^(=+) (.*)$/)
69 | toc_elements.map! { |e| [ e[0].count('='), e[1] ] }
70 | body_rdoc.gsub!(/^(=.*)/) { "#{$1.downcase}" }
71 | body = Tilt::RDocTemplate.new(nil) { body_rdoc }.render
72 |
73 | title = nil
74 | body.gsub!(%r{
(.*) }) { title = $1; "" }
75 |
76 | toc_elements = toc_elements.select { |e| e[0].between?(2, 3) }
77 |
78 | last_level = 0
79 | toc = ""
80 |
81 | toc_elements.each do |element|
82 | level, text = *element
83 | ltext = text.downcase
84 | id = text.downcase.gsub(/[^a-z]+/, '-')
85 |
86 | body.gsub!(%r{#{ltext} }) {
87 | %Q(#{ltext} )
88 | }
89 |
90 | if last_level != level
91 | if level > last_level
92 | toc << ""
93 | else
94 | toc << " "
95 | end
96 |
97 | last_level = level
98 | end
99 |
100 | toc << %Q(#{text} )
101 | end
102 | toc << " "
103 |
104 | template = Tilt.new("website/index.rhtml", :trim => "<>%")
105 | context = {
106 | :title => title,
107 | :toc => toc,
108 | :body => body,
109 | :download => "http://rubyforge.org/frs/?group_id=6737",
110 | :version => RubyPython::VERSION,
111 | :modified => Time.now
112 | }
113 | File.open(t.name, "w") { |f| f.write template.render(self, context) } end
114 | end
115 |
116 | task "docs" => "website:build"
117 |
118 | # vim: syntax=ruby
119 |
--------------------------------------------------------------------------------
/autotest/discover.rb:
--------------------------------------------------------------------------------
1 | Autotest.add_discovery { "rspec2" }
2 |
--------------------------------------------------------------------------------
/lib/rubypython.rb:
--------------------------------------------------------------------------------
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.6.4'
19 | end
20 |
21 | require 'rubypython/blankobject'
22 | require 'rubypython/interpreter'
23 | require 'rubypython/python'
24 | require 'rubypython/pythonerror'
25 | require 'rubypython/pyobject'
26 | require 'rubypython/rubypyproxy'
27 | require 'rubypython/pymainclass'
28 | require 'rubypython/pygenerator'
29 | require 'rubypython/tuple'
30 | require 'thread'
31 |
32 | module RubyPython
33 | class << self
34 | ## Starts the \Python interpreter. One of +RubyPython.start+,
35 | # RubyPython.session+, or +RubyPython.run+ must be run before using any
36 | # \Python code. Returns +true+ if the interpreter was started; +false+
37 | # otherwise.
38 | #
39 | # [options] Configures the interpreter prior to starting it. Principally
40 | # used to provide an alternative \Python interpreter to start.
41 | #
42 | # With no options provided:
43 | # RubyPython.start
44 | # sys = RubyPython.import 'sys'
45 | # p sys.version # => "2.6.6"
46 | # RubyPython.stop
47 | #
48 | # With an alternative \Python executable:
49 | # RubyPython.start(:python_exe => 'python2.7')
50 | # sys = RubyPython.import 'sys'
51 | # p sys.version # => "2.7.1"
52 | # RubyPython.stop
53 | def start(options = {})
54 | RubyPython::Python.synchronize do
55 | # Has the Runtime interpreter been defined?
56 | if self.const_defined?(:Runtime)
57 | # If this constant is defined, then yes it is. Since it is, let's
58 | # see if we should print a warning to the user.
59 | unless Runtime == options
60 | warn "The Python interpreter has already been loaded from #{Runtime.python} and cannot be changed in this process. Continuing with the current runtime."
61 | end
62 | else
63 | interp = RubyPython::Interpreter.new(options)
64 | if interp.valid?
65 | self.const_set(:Runtime, interp)
66 | else
67 | raise RubyPython::InvalidInterpreter, "An invalid interpreter was specified."
68 | end
69 | end
70 |
71 | unless defined? RubyPython::Python.ffi_libraries
72 | Runtime.__send__(:infect!, RubyPython::Python)
73 | end
74 |
75 | return false if RubyPython::Python.Py_IsInitialized != 0
76 | RubyPython::Python.Py_Initialize
77 | notify :start
78 | true
79 | end
80 | end
81 |
82 | # Stops the \Python interpreter if it is running. Returns +true+ if the
83 | # intepreter is stopped. All wrapped \Python objects are invalid after
84 | # invocation of this method. If you need the values within the \Python
85 | # proxy objects, be sure to call +RubyPyProxy#rubify+ on them.
86 | def stop
87 | RubyPython::Python.synchronize do
88 | if defined? Python.Py_IsInitialized and Python.Py_IsInitialized != 0
89 | Python.Py_Finalize
90 | notify :stop
91 | true
92 | else
93 | false
94 | end
95 | end
96 | end
97 |
98 | # Import a \Python module into the interpreter and return a proxy object
99 | # for it.
100 | #
101 | # This is the preferred way to gain access to \Python objects.
102 | #
103 | # [mod_name] The name of the module to import.
104 | def import(mod_name)
105 | if defined? Python.Py_IsInitialized and Python.Py_IsInitialized != 0
106 | pModule = Python.PyImport_ImportModule mod_name
107 | raise PythonError.handle_error if PythonError.error?
108 | pymod = PyObject.new pModule
109 | RubyPyModule.new(pymod)
110 | else
111 | raise "Python has not been started."
112 | end
113 | end
114 |
115 | # Starts the \Python interpreter (optionally with options) and +yields+
116 | # to the provided block. When the block exits for any reason, the
117 | # \Python interpreter is stopped automatically.
118 | #
119 | # The last executed expression of the block is returned. Be careful that
120 | # the last expression of the block does not return a RubyPyProxy object,
121 | # because the proxy object will be invalidated when the interpreter is
122 | # stopped.
123 | #
124 | # [options] Configures the interpreter prior to starting it. Principally
125 | # used to provide an alternative \Python interpreter to start.
126 | #
127 | # *NOTE*: In the current version of RubyPython, it _is_ possible to change
128 | # \Python interpreters in a single Ruby process execution, but it is
129 | # *strongly* discouraged as this may lead to segmentation faults. This
130 | # feature is highly experimental and may be disabled in the future.
131 | #
132 | # :call-seq:
133 | # session(options = {}) { block to execute }
134 | def session(options = {})
135 | start(options)
136 | yield
137 | ensure
138 | stop
139 | end
140 |
141 | # Starts the \Python interpreter (optionally with options) and executes
142 | # the provided block in the RubyPython module scope. When the block
143 | # exits for any reason, the \Python interpreter is stopped
144 | # automatically.
145 | #
146 | # The last executed expression of the block is returned. Be careful that
147 | # the last expression of the block does not return a RubyPyProxy object,
148 | # because the proxy object will be invalidated when the interpreter is
149 | # stopped.
150 | #
151 | # [options] Configures the interpreter prior to starting it. Principally
152 | # used to provide an alternative \Python interpreter to start.
153 | #
154 | # *NOTE*: In the current version of RubyPython, it _is_ possible to
155 | # change \Python interpreters in a single Ruby process execution, but it
156 | # is *strongly* discouraged as this may lead to segmentation faults.
157 | # This feature is highly experimental and may be disabled in the future.
158 | #
159 | # :call-seq:
160 | # run(options = {}) { block to execute in RubyPython context }
161 | def run(options = {}, &block)
162 | start(options)
163 | self.module_eval(&block)
164 | ensure
165 | stop
166 | end
167 |
168 | # Starts the \Python interpreter for a
169 | # {virtualenv}[http://pypi.python.org/pypi/virtualenv] virtual
170 | # environment. Returns +true+ if the interpreter was started.
171 | #
172 | # [virtualenv] The root path to the virtualenv-installed \Python
173 | # interpreter.
174 | #
175 | # RubyPython.start_from_virtualenv('/path/to/virtualenv')
176 | # sys = RubyPython.import 'sys'
177 | # p sys.version # => "2.7.1"
178 | # RubyPython.stop
179 | #
180 | # *NOTE*: In the current version of RubyPython, it _is_ possible to
181 | # change \Python interpreters in a single Ruby process execution, but it
182 | # is *strongly* discouraged as this may lead to segmentation faults.
183 | # This feature is highly experimental and may be disabled in the future.
184 | def start_from_virtualenv(virtualenv)
185 | result = start(:python_exe => File.join(virtualenv, "bin", "python"))
186 | activate_virtualenv
187 | result
188 | end
189 |
190 | # Returns an object describing the active Python interpreter. Returns
191 | # +nil+ if there is no active interpreter.
192 | def python
193 | if self.const_defined? :Runtime
194 | self::Runtime
195 | else
196 | nil
197 | end
198 | end
199 |
200 | # Used to activate the virtualenv.
201 | def activate_virtualenv
202 | imp = import("imp")
203 | imp.load_source("activate_this",
204 | File.join(File.dirname(RubyPython::Runtime.python),
205 | "activate_this.py"))
206 | end
207 | private :activate_virtualenv
208 |
209 | def add_observer(object)
210 | @observers ||= []
211 | @observers << object
212 | true
213 | end
214 | private :add_observer
215 |
216 | def notify(status)
217 | @observers ||= []
218 | @observers.each do |o|
219 | next if nil === o
220 | o.__send__ :python_interpreter_update, status
221 | end
222 | end
223 | private :notify
224 | end
225 |
226 | add_observer PyMain
227 | add_observer Operators
228 | add_observer PyObject::AutoPyPointer
229 | end
230 |
--------------------------------------------------------------------------------
/lib/rubypython/blankobject.rb:
--------------------------------------------------------------------------------
1 | require 'blankslate'
2 |
3 | # This document is the basis of the RubyPyProxy precisely because it hides
4 | # the implementation of so many things that should be forwarded on to the
5 | # Python object. This class is for internal use only.
6 | #
7 | # Note that in Ruby 1.9, BasicObject might be a better choice, but there are
8 | # some decisions made in the rest of the library that make this harder. I
9 | # don't see a clean way to integrate both Ruby 1.8 and 1.9 support for this.
10 | class RubyPython::BlankObject < ::BlankSlate #:nodoc:
11 | class << self
12 | def hide(name)
13 | if instance_methods.include?(name) and
14 | name.to_s !~ /^(__|instance_eval|object_id)/
15 | @hidden_methods ||= {}
16 | @hidden_methods[name.to_sym] = instance_method(name)
17 | undef_method name
18 | end
19 | end
20 | end
21 |
22 | instance_methods.each { |m| hide(m) }
23 | end
24 |
--------------------------------------------------------------------------------
/lib/rubypython/conversion.rb:
--------------------------------------------------------------------------------
1 | require 'rubypython/python'
2 | require 'rubypython/macros'
3 |
4 | # Acts as a namespace for methods to bidirectionally convert between native
5 | # Ruby types and native \Python types. Unsupported conversions raise
6 | # UnsupportedConversion.
7 | #
8 | # The methods in this module should be considered internal implementation to
9 | # RubyPython as they all return FFI pointers to \Python objects.
10 | module RubyPython::Conversion
11 | # Raised when RubyPython does not know how to convert an object from
12 | # \Python to Ruby or vice versa.
13 | class UnsupportedConversion < Exception; end
14 | class ConversionError < RuntimeError; end
15 |
16 | # Convert a Ruby string to a \Python string. Returns an FFI::Pointer to
17 | # a PyStringObject.
18 | def self.rtopString(rString)
19 | size = rString.respond_to?(:bytesize) ? rString.bytesize : rString.size
20 | ptr = RubyPython::Python.PyString_FromStringAndSize(rString, size)
21 | if ptr.null?
22 | raise ConversionError.new "Python failed to create a string with contents #{rString}"
23 | else
24 | ptr
25 | end
26 | end
27 |
28 | # Convert a Ruby Array to \Python List. Returns an FFI::Pointer to
29 | # a PyListObject.
30 | def self.rtopArrayToList(rArray)
31 | size = rArray.length
32 | pList = RubyPython::Python.PyList_New size
33 | if pList.null?
34 | raise ConversionError.new "Python failed to create list of size #{size}"
35 | end
36 | rArray.each_with_index do |el, i|
37 | # PyList_SetItem steals a reference, but rtopObject creates a new reference
38 | # So we wind up with giving a new reference to the Python interpreter for every
39 | # object
40 | ret = RubyPython::Python.PyList_SetItem pList, i, rtopObject(el)
41 | raise ConversionError.new "Failed to set item #{el} in array conversion" if ret == -1
42 | end
43 | pList
44 | end
45 |
46 | # Convert a Ruby Array (including the subclass RubyPython::Tuple) to
47 | # \Python \tuple. Returns an FFI::Pointer to a PyTupleObject.
48 | def self.rtopArrayToTuple(rArray)
49 | pList = rtopArrayToList(rArray)
50 | pTuple = RubyPython::Python.PyList_AsTuple(pList)
51 | RubyPython::Python.Py_DecRef(pList)
52 | if pTuple.null?
53 | raise Conversion.new "Python failed to convert an intermediate list of #{rArray} to a tuple"
54 | end
55 | pTuple
56 | end
57 |
58 | # Convert a Ruby Hash to a \Python Dict. Returns an FFI::Pointer to a
59 | # PyDictObject.
60 | def self.rtopHash(rHash)
61 | pDict = RubyPython::Python.PyDict_New
62 | if pDict.null?
63 | raise ConversionError.new "Python failed to create new dict"
64 | end
65 | rHash.each do |k,v|
66 | key = rtopObject(k, :key => true)
67 | value = rtopObject(v)
68 |
69 | # PyDict_SetItem INCREFS both the key and the value passed to it.
70 | # Since rtopObject already gives us a new reference, this is not necessary.
71 | # Thus, we decref the passed in objects to balancy things out
72 | if RubyPython::Python.PyDict_SetItem(pDict, key, value) == -1
73 | raise ConversionError.new "Python failed to set #{key}, #{value} in dict conversion"
74 | end
75 |
76 | RubyPython::Python.Py_DecRef key
77 | RubyPython::Python.Py_DecRef value
78 | end
79 |
80 | pDict
81 | end
82 |
83 | # Convert a Ruby Fixnum to a \Python Int. Returns an FFI::Pointer to a
84 | # PyIntObject.
85 | def self.rtopFixnum(rNum)
86 | num = RubyPython::Python.PyInt_FromLong(rNum)
87 | raise ConversionError.new "Failed to convert #{rNum}" if num.null?
88 | num
89 | end
90 |
91 | # Convert a Ruby Bignum to a \Python Long. Returns an FFI::Pointer to a
92 | # PyLongObject.
93 | def self.rtopBigNum(rNum)
94 | num = RubyPython::Python.PyLong_FromLongLong(rNum)
95 | raise ConversionError.new "Failed to convert #{rNum}" if num.null?
96 | num
97 | end
98 |
99 | # Convert a Ruby float to a \Python Float. Returns an FFI::Pointer to a
100 | # PyFloatObject.
101 | def self.rtopFloat(rNum)
102 | num = RubyPython::Python.PyFloat_FromDouble(rNum)
103 | raise ConversionError.new "Failed to convert #{rNum}" if num.null?
104 | num
105 | end
106 |
107 | # Returns a \Python False value (equivalent to Ruby's +false+). Returns an
108 | # FFI::Pointer to Py_ZeroStruct.
109 | def self.rtopFalse
110 | RubyPython::Macros.Py_RETURN_FALSE
111 | end
112 |
113 | # Returns a \Python True value (equivalent to Ruby's +true+). Returns an
114 | # FFI::Pointer to Py_TrueStruct.
115 | def self.rtopTrue
116 | RubyPython::Macros.Py_RETURN_TRUE
117 | end
118 |
119 | # Returns a \Python None value (equivalent to Ruby's +nil+). Returns an
120 | # FFI::Pointer to Py_NoneStruct.
121 | def self.rtopNone
122 | RubyPython::Macros.Py_RETURN_NONE
123 | end
124 |
125 | # Convert a Ruby Symbol to a \Python String. Returns an FFI::Pointer to a
126 | # PyStringObject.
127 | def self.rtopSymbol(rSymbol)
128 | rtopString rSymbol.to_s
129 | end
130 |
131 | # Convert a Ruby Proc to a \Python Function. Returns an FFI::Pointer to a
132 | # PyCFunction.
133 | def self.rtopFunction(rObj)
134 | proc = ::FFI::Function.new(:pointer, [:pointer, :pointer]) do |p_self, p_args|
135 | retval = rObj.call(*ptorTuple(p_args))
136 | pObject = retval.is_a?(RubyPython::RubyPyProxy) ? retval.pObject : RubyPython::PyObject.new(retval)
137 |
138 | # make sure the refcount is >1 when pObject is destroyed
139 | pObject.xIncref
140 | pObject.pointer
141 | end
142 |
143 | defn = RubyPython::Python::PyMethodDef.new
144 | defn[:ml_name] = ::FFI::MemoryPointer.from_string("RubyPython::Proc::%s" % rObj.object_id)
145 | defn[:ml_meth] = proc
146 | defn[:ml_flags] = RubyPython::Python::METH_VARARGS
147 | defn[:ml_doc] = nil
148 |
149 | return RubyPython::Python.PyCFunction_New(defn, nil)
150 | end
151 |
152 | # This will attempt to convert a Ruby object to an equivalent \Python
153 | # native type. Returns an FFI::Pointer to a \Python object (the
154 | # appropriate Py_Object C structure). If the conversion is unsuccessful,
155 | # will raise UnsupportedConversion.
156 | #
157 | # [rObj] A native Ruby object.
158 | # [is_key] Set to +true+ if the provided Ruby object will be used as a
159 | # key in a \Python +dict+. (This primarily matters for Array
160 | # conversion.)
161 | def self.rtopObject(rObj, is_key = false)
162 | case rObj
163 | when String
164 | rtopString rObj
165 | when RubyPython::Tuple
166 | rtopArrayToTuple rObj
167 | when Array
168 | # If this object is going to be used as a hash key we should make it a
169 | # tuple instead of a list
170 | if is_key
171 | rtopArrayToTuple rObj
172 | else
173 | rtopArrayToList rObj
174 | end
175 | when Hash
176 | rtopHash rObj
177 | when Fixnum
178 | rtopFixnum rObj
179 | when Bignum
180 | rtopBignum rObj
181 | when Float
182 | rtopFloat rObj
183 | when true
184 | rtopTrue
185 | when false
186 | rtopFalse
187 | when Symbol
188 | rtopSymbol rObj
189 | when Proc, Method
190 | rtopFunction rObj
191 | when nil
192 | rtopNone
193 | when RubyPython::PyObject
194 | rObj.xIncref
195 | rObj.pointer
196 | when RubyPython::RubyPyProxy
197 | rtopObject(rObj.pObject, is_key)
198 | else
199 | raise UnsupportedConversion.new("Unsupported type #{rObj.class} for conversion.")
200 | end
201 | end
202 |
203 | # Convert an FFI::Pointer to a \Python String (PyStringObject) to a Ruby
204 | # String.
205 | def self.ptorString(pString)
206 | #strPtr is a pointer to a pointer to the internal character array.
207 | #FFI will free strPtr when we are done but the internal array MUST
208 | #not be modified
209 | strPtr = ::FFI::MemoryPointer.new(:pointer)
210 | sizePtr = ::FFI::MemoryPointer.new(:ssize_t)
211 |
212 | RubyPython::Python.PyString_AsStringAndSize(pString, strPtr, sizePtr)
213 |
214 | size = case ::FFI.find_type(:ssize_t)
215 | when ::FFI.find_type(:long)
216 | sizePtr.read_long
217 | when ::FFI.find_type(:int)
218 | sizePtr.read_int
219 | when ::FFI.find_type(:long_long)
220 | sizePtr.read_long_long
221 | else
222 | nil
223 | end
224 |
225 | strPtr.read_pointer.read_string(size)
226 | end
227 |
228 | # Convert an FFI::Pointer to a \Python List (PyListObject) to a Ruby
229 | # Array.
230 | def self.ptorList(pList)
231 | rb_array = []
232 | list_size = RubyPython::Python.PyList_Size(pList)
233 |
234 | list_size.times do |i|
235 | element = RubyPython::Python.PyList_GetItem(pList, i)
236 | rObject = ptorObject(element)
237 | rb_array.push rObject
238 | end
239 |
240 | rb_array
241 | end
242 |
243 | # Convert an FFI::Pointer to a \Python Int (PyIntObject) to a Ruby Fixnum.
244 | def self.ptorInt(pNum)
245 | RubyPython::Python.PyInt_AsLong(pNum)
246 | end
247 |
248 | # Convert an FFI::Pointer to a \Python Long (PyLongObject) to a Ruby
249 | # Fixnum. This version does not do overflow checking, but probably should.
250 | def self.ptorLong(pNum)
251 | RubyPython::Python.PyLong_AsLong(pNum)
252 | # TODO Overflow Checking
253 | end
254 |
255 | # Convert an FFI::Pointer to a \Python Float (PyFloatObject) to a Ruby
256 | # Float.
257 | def self.ptorFloat(pNum)
258 | RubyPython::Python.PyFloat_AsDouble(pNum)
259 | end
260 |
261 | # Convert an FFI::Pointer to a \Python Tuple (PyTupleObject) to an
262 | # instance of RubyPython::Tuple, a subclass of the Ruby Array class.
263 | def self.ptorTuple(pTuple)
264 | #PySequence_List returns a new list. Since we are only using it as a temporary
265 | #here, we will have to DecRef it once we are done.
266 | pList = RubyPython::Python.PySequence_List pTuple
267 | rArray = ptorList pList
268 | RubyPython::Python.Py_DecRef pList
269 | RubyPython::Tuple.tuple(rArray)
270 | end
271 |
272 | # Convert an FFI::Pointer to a \Python Dictionary (PyDictObject) to a Ruby
273 | # Hash.
274 | def self.ptorDict(pDict)
275 | rb_hash = {}
276 |
277 | pos = ::FFI::MemoryPointer.new :ssize_t
278 | pos.write_int 0
279 | key = ::FFI::MemoryPointer.new :pointer
280 | val = ::FFI::MemoryPointer.new :pointer
281 |
282 | while RubyPython::Python.PyDict_Next(pDict, pos, key, val) != 0
283 | #PyDict_Next sets key and val to borrowed references. We do not care
284 | #if we are able to convert them to native ruby types, but if we wind up
285 | #wrapping either in a proxy we better IncRef it to make sure it stays
286 | #around.
287 | pKey = key.read_pointer
288 | pVal = val.read_pointer
289 | rKey = ptorObject(pKey)
290 | rVal = ptorObject(pVal)
291 | RubyPython.Py_IncRef pKey if rKey.kind_of? ::FFI::Pointer
292 | RubyPython.Py_IncRef pVal if rVal.kind_of? ::FFI::Pointer
293 | rb_hash[rKey] = rVal
294 | end
295 |
296 | rb_hash
297 | end
298 |
299 | # Converts a pointer to a \Python object into a native Ruby type, if
300 | # possible. If the conversion cannot be done, the Python object will be
301 | # returned unmodified.
302 | #
303 | # [pObj] An FFI::Pointer to a \Python object.
304 | def self.ptorObject(pObj)
305 | if RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyString_Type.to_ptr) != 0
306 | ptorString pObj
307 | elsif RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyList_Type.to_ptr) != 0
308 | ptorList pObj
309 | elsif RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyInt_Type.to_ptr) != 0
310 | ptorInt pObj
311 | elsif RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyLong_Type.to_ptr) != 0
312 | ptorLong pObj
313 | elsif RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyFloat_Type.to_ptr) != 0
314 | ptorFloat pObj
315 | elsif RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyTuple_Type.to_ptr) != 0
316 | ptorTuple pObj
317 | elsif RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyDict_Type.to_ptr) != 0
318 | ptorDict pObj
319 | elsif pObj == RubyPython::Macros.Py_True
320 | true
321 | elsif pObj == RubyPython::Macros.Py_False
322 | false
323 | elsif pObj == RubyPython::Macros.Py_None
324 | nil
325 | else
326 | pObj
327 | end
328 | end
329 | end
330 |
--------------------------------------------------------------------------------
/lib/rubypython/interpreter.rb:
--------------------------------------------------------------------------------
1 | # -*- ruby encoding: utf-8 -*-
2 |
3 | class RubyPython::InvalidInterpreter < Exception
4 | end
5 |
6 | ##
7 | # An instance of this class represents information about a particular
8 | # \Python interpreter.
9 | #
10 | # This class represents the current Python interpreter.
11 | # A class that represents a \Python executable.
12 | #
13 | # End users may get the instance that represents the current running \Python
14 | # interpreter (from +RubyPython.python+), but should not directly
15 | # instantiate this class.
16 | class RubyPython::Interpreter
17 |
18 | ##
19 | # Compare the current Interpreter to the provided Interpreter or
20 | # configuration hash. A configuration hash will be converted to an
21 | # Interpreter object before being compared.
22 | # :python_exe basename is used. If comparing against another Interpreter
23 | # object, the Interpreter basename and version are used.
24 | def ==(other)
25 | other = self.class.new(other) if other.kind_of? Hash
26 | return false unless other.kind_of? self.class
27 | (self.version == other.version) && (self.version_name == other.version_name)
28 | end
29 |
30 | ##
31 | # Create a new instance of an Interpreter instance describing a particular
32 | # \Python executable and shared library.
33 | #
34 | # Expects a hash that matches the configuration options provided to
35 | # RubyPython.start; currently only one value is recognized in that hash:
36 | #
37 | # * :python_exe : Specifies the name of the python executable to
38 | # run.
39 | def initialize(options = {})
40 | @python_exe = options[:python_exe]
41 | # Windows: 'C:\\Python27\python.exe'
42 | # Mac OS X: '/usr/bin/
43 |
44 | # The default interpreter might be python3 on some systems
45 | rc, majorversion = runpy "import sys; print(sys.version_info[0])"
46 | if majorversion == "3"
47 | warn "The python interpreter is python 3, switching to python2"
48 | @python_exe = "python2"
49 | end
50 |
51 | rc, @python = runpy "import sys; print sys.executable"
52 | if rc.exitstatus.nonzero?
53 | raise RubyPython::InvalidInterpreter, "An invalid interpreter was specified."
54 | end
55 | rc, @version = runpy "import sys; print '%d.%d' % sys.version_info[:2]"
56 | rc, @sys_prefix = runpy "import sys; print sys.prefix"
57 |
58 | if ::FFI::Platform.windows?
59 | flat_version = @version.tr('.', '')
60 | basename = File.basename(@python, '.exe')
61 |
62 | if basename =~ /(?:#{@version}|#{flat_version})$/
63 | @version_name = basename
64 | else
65 | @version_name = "#{basename}#{flat_version}"
66 | end
67 | else
68 | basename = File.basename(@python)
69 | if basename =~ /#{@version}/
70 | @version_name = basename
71 | elsif basename.end_with?("2")
72 | @version_name = "#{basename[0..-2]}#{@version}"
73 | else
74 | @version_name = "#{basename}#{@version}"
75 | end
76 | end
77 |
78 | @library = find_python_lib
79 | end
80 |
81 | def find_python_lib
82 | # By default, the library name will be something like
83 | # libpython2.6.so, but that won't always work.
84 | @libbase = "#{::FFI::Platform::LIBPREFIX}#{@version_name}"
85 | @libext = ::FFI::Platform::LIBSUFFIX
86 | @libname = "#{@libbase}.#{@libext}"
87 |
88 | # We may need to look in multiple locations for Python, so let's
89 | # build this as an array.
90 | @locations = [ File.join(@sys_prefix, "lib", @libname) ]
91 |
92 | if ::FFI::Platform.mac?
93 | # On the Mac, let's add a special case that has even a different
94 | # @libname. This may not be fully useful on future versions of OS
95 | # X, but it should work on 10.5 and 10.6. Even if it doesn't, the
96 | # next step will (/usr/lib/libpython.dylib is a symlink
97 | # to the correct location).
98 | @locations << File.join(@sys_prefix, "Python")
99 | # Let's also look in the location that was originally set in this
100 | # library:
101 | File.join(@sys_prefix, "lib", "#{@realname}", "config", @libname)
102 | end
103 |
104 | if ::FFI::Platform.unix?
105 | # On Unixes, let's look in some standard alternative places, too.
106 | # Just in case. Some Unixes don't include a .so symlink when they
107 | # should, so let's look for the base cases of .so.1 and .so.1.0, too.
108 | [ @libname, "#{@libname}.1", "#{@libname}.1.0" ].each do |name|
109 | if ::FFI::Platform::ARCH != 'i386'
110 | @locations << File.join("/opt/local/lib64", name)
111 | @locations << File.join("/opt/lib64", name)
112 | @locations << File.join("/usr/local/lib64", name)
113 | @locations << File.join("/usr/lib64", name)
114 | @locations << File.join("/usr/lib/x86_64-linux-gnu", name)
115 | end
116 | @locations << File.join("/opt/local/lib", name)
117 | @locations << File.join("/opt/lib", name)
118 | @locations << File.join("/usr/local/lib", name)
119 | @locations << File.join("/usr/lib", name)
120 | end
121 | end
122 |
123 | if ::FFI::Platform.windows?
124 | # On Windows, the appropriate DLL is usually be found in
125 | # %SYSTEMROOT%\system or %SYSTEMROOT%\system32; as a fallback we'll
126 | # use C:\Windows\system{,32} as well as the install directory and the
127 | # install directory + libs.
128 | system_root = File.expand_path(ENV['SYSTEMROOT']).gsub(/\\/, '/')
129 | @locations << File.join(system_root, 'system', @libname)
130 | @locations << File.join(system_root, 'system32', @libname)
131 | @locations << File.join("C:/WINDOWS", "System", @libname)
132 | @locations << File.join("C:/WINDOWS", "System32", @libname)
133 | @locations << File.join(sys_prefix, @libname)
134 | @locations << File.join(sys_prefix, 'libs', @libname)
135 | @locations << File.join(system_root, "SysWOW64", @libname)
136 | @locations << File.join("C:/WINDOWS", "SysWOW64", @libname)
137 | end
138 |
139 | # Let's add alternative extensions; again, just in case.
140 | @locations.dup.each do |location|
141 | path = File.dirname(location)
142 | base = File.basename(location, ".#{@libext}")
143 | @locations << File.join(path, "#{base}.so") # Standard Unix
144 | @locations << File.join(path, "#{base}.dylib") # Mac OS X
145 | @locations << File.join(path, "#{base}.dll") # Windows
146 | end
147 |
148 | # Remove redundant locations
149 | @locations.uniq!
150 |
151 | library = nil
152 |
153 | @locations.each do |location|
154 | if File.exists? location
155 | library = location
156 | break
157 | end
158 | end
159 |
160 | library
161 | end
162 | private :find_python_lib
163 |
164 | def valid?
165 | if @python.nil? or @python.empty?
166 | false
167 | elsif @library.nil? or @library.empty?
168 | false
169 | else
170 | true
171 | end
172 | end
173 |
174 | ##
175 | # The name of the \Python executable that is used. This is the value of
176 | # 'sys.executable' for the \Python interpreter provided in
177 | # :python_exe or 'python' if it is not provided.
178 | #
179 | # On Mac OS X Lion (10.7), this value is '/usr/bin/python' for 'python'.
180 | attr_reader :python
181 | ##
182 | # The version of the \Python interpreter. This is a decimalized version of
183 | # 'sys.version_info[:2]' (such that \Python 2.7.1 is reported as '2.7').
184 | attr_reader :version
185 | ##
186 | # The system prefix for the \Python interpreter. This is the value of
187 | # 'sys.prefix'.
188 | attr_reader :sys_prefix
189 | ##
190 | # The basename of the \Python interpreter with a version number. This is
191 | # mostly an intermediate value used to find the shared \Python library,
192 | # but /usr/bin/python is often a link to /usr/bin/python2.7 so it may be
193 | # of value. Note that this does *not* include the full path to the
194 | # interpreter.
195 | attr_reader :version_name
196 |
197 | # The \Python library.
198 | attr_reader :library
199 |
200 | # Run a Python command-line command.
201 | def runpy(command)
202 | i = @python || @python_exe || 'python'
203 | if ::FFI::Platform.windows?
204 | o = %x(#{i} -c "#{command}" 2> NUL:)
205 | else
206 | o = %x(#{i} -c "#{command}" 2> /dev/null)
207 | end
208 |
209 | [ $?, o.chomp ]
210 | end
211 | private :runpy
212 |
213 | def inspect(debug = false)
214 | if debug
215 | debug_s
216 | elsif @python
217 | "#<#{self.class}: #{python} v#{version} #{sys_prefix} #{version_name}>"
218 | else
219 | "#<#{self.class}: invalid interpreter>"
220 | end
221 | end
222 |
223 | def debug_s(format = nil)
224 | system = ""
225 | system << "windows " if ::FFI::Platform.windows?
226 | system << "mac " if ::FFI::Platform.mac?
227 | system << "unix " if ::FFI::Platform.unix?
228 | system << "unknown " if system.empty?
229 |
230 | case format
231 | when :report
232 | s = <<-EOS
233 | python_exe: #{@python_exe}
234 | python: #{@python}
235 | version: #{@version}
236 | sys_prefix: #{@sys_prefix}
237 | version_name: #{@version_name}
238 | platform: #{system.chomp}
239 | library: #{@library.inspect}
240 | libbase: #{@libbase}
241 | libext: #{@libext}
242 | libname: #{@libname}
243 | locations: #{@locations.inspect}
244 | EOS
245 | else
246 | s = "#<#{self.class}: "
247 | s << "python_exe=#{@python_exe.inspect} "
248 | s << "python=#{@python.inspect} "
249 | s << "version=#{@version.inspect} "
250 | s << "sys_prefix=#{@sys_prefix.inspect} "
251 | s << "version_name=#{@version_name.inspect} "
252 | s << system
253 | s << "library=#{@library.inspect} "
254 | s << "libbase=#{@libbase.inspect} "
255 | s << "libext=#{@libext.inspect} "
256 | s << "libname=#{@libname.inspect} "
257 | s << "locations=#{@locations.inspect}"
258 | end
259 |
260 | s
261 | end
262 | end
263 |
--------------------------------------------------------------------------------
/lib/rubypython/macros.rb:
--------------------------------------------------------------------------------
1 | require 'ffi'
2 | require 'rubypython/python'
3 |
4 | # Contains Python C API macros reimplemented in Ruby. For internal use only.
5 | module RubyPython::Macros #:nodoc:
6 | # Returns the reference count for the provided pointer.
7 | def self.Py_REFCNT(pObjPointer)
8 | pStruct = RubyPython::Python::PyObjectStruct.new pObjPointer
9 | pStruct[:ob_refcnt]
10 | end
11 |
12 | # Returns the object type for the provided pointer.
13 | def self.Py_TYPE(pObjPointer)
14 | pStruct = RubyPython::Python::PyObjectStruct.new pObjPointer
15 | pStruct[:ob_type]
16 | end
17 |
18 | # This has been modified from the C API macro to allow for multiple
19 | # pointer objects to be passed. It simplifies a number of checks.
20 | def self.PyObject_TypeCheck(pObject, pTypePointer)
21 | type = self.Py_TYPE(pObject)
22 |
23 | [ pTypePointer ].flatten.each do |pointer|
24 | return 1 if type == pointer
25 | end
26 |
27 | return 0
28 | end
29 |
30 | def self.Py_True
31 | RubyPython::Python.Py_TrueStruct.to_ptr
32 | end
33 |
34 | def self.Py_False
35 | RubyPython::Python.Py_ZeroStruct.to_ptr
36 | end
37 |
38 | def self.Py_None
39 | RubyPython::Python.Py_NoneStruct.to_ptr
40 | end
41 |
42 | def self.Py_RETURN_FALSE
43 | RubyPython::Python.Py_IncRef(self.Py_False)
44 | self.Py_False
45 | end
46 |
47 | def self.Py_RETURN_TRUE
48 | RubyPython::Python.Py_IncRef(self.Py_True)
49 | self.Py_True
50 | end
51 |
52 | def self.Py_RETURN_NONE
53 | RubyPython::Python.Py_IncRef(self.Py_None)
54 | self.Py_None
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/rubypython/operators.rb:
--------------------------------------------------------------------------------
1 | # A mixin module to provide method delegation to a proxy class. This is done
2 | # either by delegating to methods defined on the wrapped object or by using
3 | # the \Python _operator_ module. A large number of the methods are
4 | # dynamically generated and so their documentation is not provided here. In
5 | # general all operators that can be overloaded are delegated.
6 | module RubyPython::Operators
7 | # Provides access to the \Python _operator_ module.
8 | def self.operator_
9 | @@operator ||= RubyPython.import('operator')
10 | end
11 |
12 | # Creates a method to delegate a binary operation.
13 | # [rname] The name of the Ruby method for this operation. Can be either a
14 | # Symbol or a String.
15 | # [pname] The name of the \Python magic method to which this method should
16 | # be delegated.
17 | def self.bin_op(rname, pname)
18 | define_method rname.to_sym do |other|
19 | self.__send__(pname, other)
20 | end
21 | end
22 |
23 | # Creates a method to delegate a relational operator. The result of the
24 | # delegated method will always be converted to a Ruby type so that simple
25 | # boolean testing may occur. These methods are implemented with calls the
26 | # _operator_ module.
27 | #
28 | # [rname] The name of the Ruby method for this operation. Can be a Symbol
29 | # or a String.
30 | # [pname] The name of the \Python magic method to which this method should
31 | # be delegated.
32 | def self.rel_op(rname, pname)
33 | define_method rname.to_sym do |other|
34 | RubyPython::Operators.operator_.__send__(pname, self, other).rubify
35 | end
36 | end
37 |
38 | # Creates a method to delegate a relational operator.
39 | # These methods are implemented with calls the _operator_ module.
40 | # [rname] The name of the Ruby method for this operation. Can be a Symbol
41 | # or a String.
42 | # [pname] The name of the \Python magic method to which this method should
43 | # be delegated.
44 | def self.unary_op(rname, pname)
45 | define_method rname.to_sym do
46 | RubyPython::Operators.operator_.__send__(pname, self)
47 | end
48 | end
49 |
50 | [
51 | [:+, '__add__'],
52 | [:-, '__sub__'],
53 | [:*, '__mul__'],
54 | [:/, '__div__'],
55 | [:&, '__and__'],
56 | [:^, '__xor__'],
57 | [:%, '__mod__'],
58 | [:**, '__pow__'],
59 | [:>>, '__rshift__'],
60 | [:<<, '__lshift__'],
61 | [:|, '__or__']
62 | ].each do |args|
63 | bin_op *args
64 | end
65 |
66 | [
67 | [:~, :__invert__],
68 | [:+@, :__pos__],
69 | [:-@, :__neg__]
70 | ].each do |args|
71 | unary_op *args
72 | end
73 |
74 | [
75 | [:==, 'eq'],
76 | [:<, 'lt'],
77 | [:<=, 'le'],
78 | [:>, 'gt'],
79 | [:>=, 'ge'],
80 | [:equal?, 'is_']
81 | ].each do |args|
82 | rel_op *args
83 | end
84 |
85 | # Delegates object indexed access to the wrapped \Python object.
86 | def [](index)
87 | self.__getitem__ index
88 | end
89 |
90 | # Delegates setting of various indices to the wrapped \Python object.
91 | def []=(index, value)
92 | self.__setitem__ index, value
93 | end
94 |
95 | # Delegates membership testing to \Python.
96 | def include?(item)
97 | self.__contains__(item).rubify
98 | end
99 |
100 | # Delegates Comparison to \Python.
101 | def <=>(other)
102 | RubyPython::PyMain.cmp(self, other)
103 | end
104 |
105 | class << self
106 | # Called by RubyPython when the interpreter is started or stopped so
107 | # that the necessary preparation or cleanup can be done. For internal
108 | # use only.
109 | def python_interpreter_update(status)
110 | case status
111 | when :stop
112 | @@operator = nil
113 | end
114 | end
115 | private :python_interpreter_update
116 | end
117 |
118 | # Aliases eql? to == for Python objects.
119 | alias_method :eql?, :==
120 | end
121 |
--------------------------------------------------------------------------------
/lib/rubypython/pygenerator.rb:
--------------------------------------------------------------------------------
1 | require "rubypython/python"
2 | require "rubypython/conversion"
3 | require 'rubypython/macros'
4 | require 'rubypython/conversion'
5 | require 'rubypython/pyobject'
6 | require "rubypython/pymainclass"
7 | require "rubypython/rubypyproxy"
8 |
9 | if defined? Fiber
10 | module RubyPython
11 | class << self
12 | # Creates a \Python generator object called +rubypython_generator+
13 | # that accepts a callback and yields to it.
14 | #
15 | # *Note*: This method only exists in the RubyPython if the Fiber
16 | # exists.
17 | def generator_type
18 | @generator_type ||= lambda do
19 | code = <<-EOM
20 | def rubypython_generator(callback):
21 | while True:
22 | yield callback()
23 | EOM
24 |
25 | globals = PyObject.new({ "__builtins__" => PyMain.builtin.pObject, })
26 | empty_hash = PyObject.new({})
27 | ptr = Python.PyRun_String(code, Python::PY_FILE_INPUT, globals.pointer, empty_hash.pointer)
28 | ptr = Python.PyRun_String("rubypython_generator", Python::PY_EVAL_INPUT, globals.pointer, empty_hash.pointer)
29 | raise PythonError.handle_error if PythonError.error?
30 | RubyPyProxy.new(PyObject.new(ptr))
31 | end.call
32 | end
33 |
34 | # Creates a Ruby lambda that acts like a \Python generator. Uses
35 | # +RubyPython.generator_type+ and Fiber to work the generator as a
36 | # coroutine.
37 | #
38 | # *Note*: This method only exists in the RubyPython if the Fiber
39 | # exists.
40 | def generator
41 | return lambda do |*args|
42 | fib = Fiber.new do
43 | yield *args
44 | Python.PyErr_SetNone(Python.PyExc_StopIteration)
45 | ::FFI::Pointer::NULL
46 | end
47 | generator_type.__call__(lambda { fib.resume })
48 | end
49 | end
50 |
51 | # Performs a +Fiber.yield+ with the provided arguments, continuing the
52 | # coroutine execution of the generator.
53 | #
54 | # *Note*: This method only exists in the RubyPython if the Fiber
55 | # exists.
56 | def yield(*args)
57 | Fiber.yield(*args)
58 | end
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/lib/rubypython/pymainclass.rb:
--------------------------------------------------------------------------------
1 | require 'rubypython/blankobject'
2 | require 'singleton'
3 |
4 | module RubyPython
5 | # A singleton object providing access to the \Python __main__ and
6 | # __builtin__ modules. This can be conveniently accessed through
7 | # +PyMain+. The __main__ namespace is searched before the
8 | # __builtin__ namespace. As such, naming clashes will be resolved
9 | # in that order.
10 | #
11 | # RubyPython::PyMain.dir("dir") # => ['__add__', '__class__', … ]
12 | #
13 | # === Block Syntax
14 | # PyMainClass provides experimental block support for called methods. A
15 | # block may be passed to a method call and the object returned by the
16 | # function call will be passed as an argument to the block.
17 | #
18 | # RubyPython::PyMain.dir("dir") { |a| a.rubify.map { |e| e.to_sym } }
19 | # # => [:__add__, :__class__, :__contains__, … ]
20 | class PyMainClass < RubyPython::BlankObject
21 | include Singleton
22 |
23 | # Returns a proxy object wrapping the \Python __main__ namespace.
24 | def main
25 | @main ||= RubyPython.import "__main__"
26 | end
27 |
28 | # Returns a proxy object wrapping the \Python __builtin__
29 | # namespace.
30 | def builtin
31 | @builtin ||= RubyPython.import "__builtin__"
32 | end
33 |
34 | # Delegates any method calls on this object to the \Python
35 | # __main__ or __builtin__ namespaces, in that order. If
36 | # a block is provided, the result of calling the \Python method will be
37 | # yielded as an argument to the block.
38 | #
39 | # [name] The name of the \Python method or function to call.
40 | # [args] The arguments to pass to the \Python method.
41 | # [block] A block to execute with the result of calling the \Python
42 | # method. If a block is provided, the result of the block is returned,
43 | # not the result of the \Python method.
44 | def method_missing(name, *args, &block)
45 | proxy = if main.respond_to?(name)
46 | main
47 | elsif builtin.respond_to?(name)
48 | builtin
49 | else
50 | super(name, *args)
51 | end
52 | result = if proxy.is_real_method?(name)
53 | proxy.__send__(name, *args)
54 | else
55 | proxy.__send__(:method_missing, name, *args)
56 | end
57 |
58 | if block
59 | block.call(result)
60 | else
61 | result
62 | end
63 | end
64 |
65 | # Called by RubyPython when the interpreter is started or stopped so
66 | # that the neccesary preparation or cleanup can be done. For internal
67 | # use only.
68 | def python_interpreter_update(status)
69 | case status
70 | when :stop
71 | @main = nil
72 | @builtin = nil
73 | end
74 | end
75 | private :python_interpreter_update
76 | end
77 |
78 | # The accessible instance of PyMainClass.
79 | PyMain = RubyPython::PyMainClass.instance
80 | end
81 |
--------------------------------------------------------------------------------
/lib/rubypython/pyobject.rb:
--------------------------------------------------------------------------------
1 | require 'rubypython/python'
2 | require 'rubypython/macros'
3 | require 'rubypython/conversion'
4 | require 'ffi'
5 |
6 | # This object is an opaque wrapper around the C Py…Object types used by the
7 | # \Python C API.
8 | #
9 | # This class is *only* for RubyPython internal use.
10 | class RubyPython::PyObject # :nodoc: all
11 | # This class wraps C Py…Object s so that the RubyPython::Python
12 | # reference count is automatically decreased when the Ruby object
13 | # referencing them goes out of scope.
14 | class AutoPyPointer < ::FFI::AutoPointer # :nodoc:
15 | class << self
16 | # Keeps track of which objects are associated with the currently
17 | # running RubyPython::Python interpreter, so that RubyPython knows not
18 | # to try to decrease the reference counts of the others when garbage
19 | # collecting.
20 | attr_accessor :current_pointers
21 |
22 | # When used along with the FFI Library method is executed whenever a
23 | # pointer is garbage collected so that cleanup can be done. In our
24 | # case we decrease the reference count of the held pointer as long as
25 | # the object is still good. There is really no reason the end-user
26 | # would need to the use this method directly.
27 | def release(pointer)
28 | obj_id = pointer.object_id
29 | deleted = @current_pointers.delete(obj_id)
30 | if pointer.null?
31 | puts "Warning: Trying to DecRef NULL pointer" if RubyPython::Python.Py_IsInitialized != 0
32 | return
33 | end
34 | if deleted and (RubyPython::Python.Py_IsInitialized != 0)
35 | RubyPython::Python.Py_DecRef pointer
36 | end
37 | end
38 |
39 | # Called by RubyPython when the interpreter is started or stopped so
40 | # that the necessary preparation or cleanup can be done. For internal
41 | # use only.
42 | def python_interpreter_update(status)
43 | case status
44 | when :stop
45 | current_pointers.clear
46 | end
47 | end
48 | private :python_interpreter_update
49 | end
50 |
51 | self.current_pointers = {}
52 | end
53 |
54 | # The AutoPyPointer object which represents the RubyPython::Python
55 | # Py…Object.
56 | attr_reader :pointer
57 |
58 | # [rObject] FFI Pointer objects passed into the constructor are wrapped in
59 | # an AutoPyPointer and assigned to the +#pointer+ attribute. Other objects
60 | # are converted, if possible, from their Ruby types to their \Python types
61 | # and wrapped in an AutoPyPointer. The conversion is done with
62 | # +RubyPython::Conversion.rtopObject+.
63 | def initialize(rObject)
64 | if rObject.kind_of? ::FFI::AutoPointer
65 | new_pointer = ::FFI::Pointer.new rObject
66 | @pointer = AutoPyPointer.new new_pointer
67 | xIncref
68 | elsif rObject.kind_of? ::FFI::Pointer
69 | @pointer = AutoPyPointer.new rObject
70 | else
71 | @pointer = AutoPyPointer.new RubyPython::Conversion.rtopObject(rObject)
72 | end
73 | AutoPyPointer.current_pointers[@pointer.object_id] = true
74 | end
75 |
76 | # Attempts to convert the wrapped object to a native ruby type. Returns
77 | # either the Ruby object or the unmodified \Python object.
78 | def rubify
79 | RubyPython::Conversion.ptorObject @pointer
80 | end
81 |
82 | # Tests whether the wrapped \Python object has a given attribute. Returns
83 | # +true+ if the attribute exists.
84 | # [attrName] The name of the attribute to look up.
85 | def hasAttr(attrName)
86 | RubyPython::Python.PyObject_HasAttrString(@pointer, attrName) == 1
87 | end
88 |
89 | # Retrieves an object from the wrapped \Python object.
90 | # [attrName] The name of the attribute to fetch.
91 | def getAttr(attrName)
92 | pyAttr = RubyPython::Python.PyObject_GetAttrString(@pointer, attrName)
93 | self.class.new pyAttr
94 | end
95 |
96 | # Sets an attribute of the wrapped \Python object. Returns +true+ if the
97 | # attribute was successfully set.
98 | # [attrName] The name of the attribute to set.
99 | # [rbPyAttr] A PyObject wrapper around the value that we wish to set the
100 | # attribute to.
101 | def setAttr(attrName, rbPyAttr)
102 | #SetAttrString should incref whatever gets passed to it.
103 | RubyPython::Python.PyObject_SetAttrString(@pointer, attrName, rbPyAttr.pointer) != -1
104 | end
105 |
106 | # Calls the wrapped \Python object with the supplied arguments and keyword
107 | # arguments. Returns a PyObject wrapper around the returned object, which
108 | # may be +NULL+.
109 | # [rbPyArgs] A PyObject wrapping a Tuple of the supplied arguments.
110 | # [rbPyKeywords] A PyObject wrapping a Dict of keyword arguments.
111 | def callObjectKeywords(rbPyArgs, rbPyKeywords)
112 | pyReturn = RubyPython::Python.PyObject_Call(@pointer, rbPyArgs.pointer, rbPyKeywords.pointer)
113 | self.class.new pyReturn
114 | end
115 |
116 | # Calls the wrapped \Python object with the supplied arguments. Returns a
117 | # PyObject wrapper around the returned object, which may be +NULL+.
118 | # [rbPyArgs] A PyObject wrapping a Tuple of the supplied arguments.
119 | def callObject(rbPyArgs)
120 | pyReturn = RubyPython::Python.PyObject_CallObject(@pointer, rbPyArgs.pointer)
121 | self.class.new pyReturn
122 | end
123 |
124 | # Decrease the reference count of the wrapped object.
125 | def xDecref
126 | AutoPyPointer.release(@pointer)
127 | @pointer = nil
128 | end
129 |
130 | # Increase the reference count of the wrapped object
131 | def xIncref
132 | RubyPython::Python.Py_IncRef @pointer
133 | nil
134 | end
135 |
136 | # Tests whether the wrapped object is +NULL+.
137 | def null?
138 | @pointer.null?
139 | end
140 |
141 | # Performs a compare on two Python objects. Returns a value similar to
142 | # that of the spaceship operator (<=>).
143 | def cmp(other)
144 | RubyPython::Python.PyObject_Compare @pointer, other.pointer
145 | end
146 |
147 | # Tests whether the wrapped object is a function or a method. This is not
148 | # the same as #callable? as many other \Python objects are callable.
149 | def function_or_method?
150 | check = RubyPython::Macros.PyObject_TypeCheck(@pointer, [
151 | RubyPython::Python.PyFunction_Type.to_ptr,
152 | RubyPython::Python.PyCFunction_Type.to_ptr,
153 | RubyPython::Python.PyMethod_Type.to_ptr
154 | ])
155 | check != 0
156 | end
157 |
158 | # Is the wrapped object callable?
159 | def callable?
160 | RubyPython::Python.PyCallable_Check(@pointer) != 0
161 | end
162 |
163 | # Returns the 'directory' of the RubyPython::Python object; similar to #methods in
164 | # Ruby.
165 | def dir
166 | return self.class.new(RubyPython::Python.PyObject_Dir(@pointer)).rubify.map do |x|
167 | x.to_sym
168 | end
169 | end
170 |
171 | # Tests whether the wrapped object is a RubyPython::Python class (both new
172 | # and old style).
173 | def class?
174 | check = RubyPython::Macros.PyObject_TypeCheck(@pointer, [
175 | RubyPython::Python.PyClass_Type.to_ptr,
176 | RubyPython::Python.PyType_Type.to_ptr
177 | ])
178 | check != 0
179 | end
180 |
181 |
182 | # Takes an array of objects, converting them to \Python objects if necessary,
183 | # and wraps them in a Tuple such that they may be passed to #callObject.
184 | # [args] An array; the arguments to be inserted into the
185 | # Tuple.
186 | def self.buildArgTuple(*args)
187 | self.new RubyPython::Conversion.rtopArrayToTuple(args)
188 | end
189 | end
190 |
--------------------------------------------------------------------------------
/lib/rubypython/python.rb:
--------------------------------------------------------------------------------
1 | require 'ffi'
2 | require 'thread'
3 | require 'rubypython/interpreter'
4 |
5 | module RubyPython
6 | # This module will hold the loaded RubyPython interpreter.
7 | module Python #:nodoc: all
8 | @lock = Mutex.new
9 |
10 | def self.synchronize(&block)
11 | @lock.synchronize(&block)
12 | end
13 | end
14 | end
15 |
16 | class RubyPython::Interpreter
17 | # Infects the provided module with the Python FFI. Once a single module
18 | # has been infected, the #infect! method is removed from
19 | # RubyPython::Interpreter.
20 | def infect!(mod)
21 | self.class.class_eval do
22 | undef :infect!
23 | end
24 |
25 | mod.extend ::FFI::Library
26 | # FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL
27 | mod.ffi_lib_flags :lazy, :global
28 | mod.ffi_lib self.library
29 |
30 | # This class is a little bit of a hack to extract the address of
31 | # global structs. If someone knows a better way please let me know.
32 | mod.module_eval do
33 | self.const_set :DummyStruct, Class.new(::FFI::Struct)
34 | self::DummyStruct.layout :dummy_var, :int
35 |
36 | self.const_set(:PY_FILE_INPUT, 257)
37 | self.const_set(:PY_EVAL_INPUT, 258)
38 | self.const_set(:METH_VARARGS, 0x0001)
39 |
40 | # Function methods & constants
41 | attach_function :PyCFunction_New, [:pointer, :pointer], :pointer
42 | callback :PyCFunction, [:pointer, :pointer], :pointer
43 |
44 | attach_function :PyRun_String, [:string, :int, :pointer, :pointer], :pointer
45 | attach_function :PyRun_SimpleString, [:string], :pointer
46 | attach_function :Py_CompileString, [:string, :string, :int], :pointer
47 | attach_function :PyEval_EvalCode, [:pointer, :pointer, :pointer], :pointer
48 | attach_function :PyErr_SetString, [:pointer, :string], :void
49 |
50 | # Python interpreter startup and shutdown
51 | attach_function :Py_IsInitialized, [], :int
52 | attach_function :Py_Initialize, [], :void
53 | attach_function :Py_Finalize, [], :void
54 |
55 | # Module methods
56 | attach_function :PyImport_ImportModule, [:string], :pointer
57 |
58 | # Object Methods
59 | attach_function :PyObject_HasAttrString, [:pointer, :string], :int
60 | attach_function :PyObject_GetAttrString, [:pointer, :string], :pointer
61 | attach_function :PyObject_SetAttrString, [:pointer, :string, :pointer], :int
62 | attach_function :PyObject_Dir, [:pointer], :pointer
63 |
64 | attach_function :PyObject_Compare, [:pointer, :pointer], :int
65 |
66 | attach_function :PyObject_Call, [:pointer, :pointer, :pointer], :pointer
67 | attach_function :PyObject_CallObject, [:pointer, :pointer], :pointer
68 | attach_function :PyCallable_Check, [:pointer], :int
69 |
70 | ### Python To Ruby Conversion
71 | # String Methods
72 | attach_function :PyString_AsString, [:pointer], :string
73 | attach_function :PyString_FromString, [:string], :pointer
74 | attach_function :PyString_AsStringAndSize, [:pointer, :pointer, :pointer], :int
75 | attach_function :PyString_FromStringAndSize, [:buffer_in, :ssize_t], :pointer
76 |
77 | # List Methods
78 | attach_function :PyList_GetItem, [:pointer, :int], :pointer
79 | attach_function :PyList_Size, [:pointer], :int
80 | attach_function :PyList_New, [:int], :pointer
81 | attach_function :PyList_SetItem, [:pointer, :int, :pointer], :void
82 |
83 | # Integer Methods
84 | attach_function :PyInt_AsLong, [:pointer], :long
85 | attach_function :PyInt_FromLong, [:long], :pointer
86 |
87 | attach_function :PyLong_AsLong, [:pointer], :long
88 | attach_function :PyLong_FromLongLong, [:long_long], :pointer
89 |
90 | # Float Methods
91 | attach_function :PyFloat_AsDouble, [:pointer], :double
92 | attach_function :PyFloat_FromDouble, [:double], :pointer
93 |
94 | # Tuple Methods
95 | attach_function :PySequence_List, [:pointer], :pointer
96 | attach_function :PyList_AsTuple, [:pointer], :pointer
97 | attach_function :PyTuple_Pack, [:int, :varargs], :pointer
98 |
99 | # Dict/Hash Methods
100 | attach_function :PyDict_Next, [:pointer, :pointer, :pointer, :pointer], :int
101 | attach_function :PyDict_New, [], :pointer
102 | attach_function :PyDict_SetItem, [:pointer, :pointer, :pointer], :int
103 | attach_function :PyDict_Contains, [:pointer, :pointer], :int
104 | attach_function :PyDict_GetItem, [:pointer, :pointer], :pointer
105 |
106 | # Error Methods
107 | attach_variable :PyExc_Exception, self::DummyStruct.by_ref
108 | attach_variable :PyExc_StopIteration, self::DummyStruct.by_ref
109 | attach_function :PyErr_SetNone, [:pointer], :void
110 | attach_function :PyErr_Fetch, [:pointer, :pointer, :pointer], :void
111 | attach_function :PyErr_Occurred, [], :pointer
112 | attach_function :PyErr_Clear, [], :void
113 |
114 | # Reference Counting
115 | attach_function :Py_IncRef, [:pointer], :void
116 | attach_function :Py_DecRef, [:pointer], :void
117 |
118 | # Type Objects
119 | # attach_variable :PyBaseObject_Type, self::DummyStruct.by_value # built-in 'object'
120 | # attach_variable :PyBaseString_Type, self::DummyStruct.by_value
121 | # attach_variable :PyBool_Type, self::DummyStruct.by_value
122 | # attach_variable :PyBuffer_Type, self::DummyStruct.by_value
123 | # attach_variable :PyByteArrayIter_Type, self::DummyStruct.by_value
124 | # attach_variable :PyByteArray_Type, self::DummyStruct.by_value
125 | attach_variable :PyCFunction_Type, self::DummyStruct.by_value
126 | # attach_variable :PyCObject_Type, self::DummyStruct.by_value
127 | # attach_variable :PyCallIter_Type, self::DummyStruct.by_value
128 | # attach_variable :PyCapsule_Type, self::DummyStruct.by_value
129 | # attach_variable :PyCell_Type, self::DummyStruct.by_value
130 | # attach_variable :PyClassMethod_Type, self::DummyStruct.by_value
131 | attach_variable :PyClass_Type, self::DummyStruct.by_value
132 | # attach_variable :PyCode_Type, self::DummyStruct.by_value
133 | # attach_variable :PyComplex_Type, self::DummyStruct.by_value
134 | # attach_variable :PyDictItems_Type, self::DummyStruct.by_value
135 | # attach_variable :PyDictIterItem_Type, self::DummyStruct.by_value
136 | # attach_variable :PyDictIterKey_Type, self::DummyStruct.by_value
137 | # attach_variable :PyDictIterValue_Type, self::DummyStruct.by_value
138 | # attach_variable :PyDictKeys_Type, self::DummyStruct.by_value
139 | # attach_variable :PyDictProxy_Type, self::DummyStruct.by_value
140 | # attach_variable :PyDictValues_Type, self::DummyStruct.by_value
141 | attach_variable :PyDict_Type, self::DummyStruct.by_value
142 | # attach_variable :PyEllipsis_Type, self::DummyStruct.by_value
143 | # attach_variable :PyEnum_Type, self::DummyStruct.by_value
144 | # attach_variable :PyFile_Type, self::DummyStruct.by_value
145 | attach_variable :PyFloat_Type, self::DummyStruct.by_value
146 | # attach_variable :PyFrame_Type, self::DummyStruct.by_value
147 | # attach_variable :PyFrozenSet_Type, self::DummyStruct.by_value
148 | attach_variable :PyFunction_Type, self::DummyStruct.by_value
149 | # attach_variable :PyGen_Type, self::DummyStruct.by_value
150 | # attach_variable :PyGetSetDescr_Type, self::DummyStruct.by_value
151 | # attach_variable :PyInstance_Type, self::DummyStruct.by_value
152 | attach_variable :PyInt_Type, self::DummyStruct.by_value
153 | attach_variable :PyList_Type, self::DummyStruct.by_value
154 | attach_variable :PyLong_Type, self::DummyStruct.by_value
155 | # attach_variable :PyMemberDescr_Type, self::DummyStruct.by_value
156 | # attach_variable :PyMemoryView_Type, self::DummyStruct.by_value
157 | attach_variable :PyMethod_Type, self::DummyStruct.by_value
158 | # attach_variable :PyModule_Type, self::DummyStruct.by_value
159 | # attach_variable :PyNullImporter_Type, self::DummyStruct.by_value
160 | # attach_variable :PyProperty_Type, self::DummyStruct.by_value
161 | # attach_variable :PyRange_Type, self::DummyStruct.by_value
162 | # attach_variable :PyReversed_Type, self::DummyStruct.by_value
163 | # attach_variable :PySTEntry_Type, self::DummyStruct.by_value
164 | # attach_variable :PySeqIter_Type, self::DummyStruct.by_value
165 | # attach_variable :PySet_Type, self::DummyStruct.by_value
166 | # attach_variable :PySlice_Type, self::DummyStruct.by_value
167 | # attach_variable :PyStaticMethod_Type, self::DummyStruct.by_value
168 | attach_variable :PyString_Type, self::DummyStruct.by_value
169 | # attach_variable :PySuper_Type, self::DummyStruct.by_value # built-in 'super'
170 | # attach_variable :PyTraceBack_Type, self::DummyStruct.by_value
171 | attach_variable :PyTuple_Type, self::DummyStruct.by_value
172 | attach_variable :PyType_Type, self::DummyStruct.by_value
173 | # attach_variable :PyUnicode_Type, self::DummyStruct.by_value
174 | # attach_variable :PyWrapperDescr_Type, self::DummyStruct.by_value
175 |
176 | attach_variable :Py_TrueStruct, :_Py_TrueStruct, self::DummyStruct.by_value
177 | attach_variable :Py_ZeroStruct, :_Py_ZeroStruct, self::DummyStruct.by_value
178 | attach_variable :Py_NoneStruct, :_Py_NoneStruct, self::DummyStruct.by_value
179 |
180 | # This is an implementation of the basic structure of a Python PyObject
181 | # struct. The C struct is actually much larger, but since we only access
182 | # the first two data members via FFI and always deal with struct
183 | # pointers there is no need to mess around with the rest of the object.
184 | self.const_set :PyObjectStruct, Class.new(::FFI::Struct)
185 | self::PyObjectStruct.layout :ob_refcnt, :ssize_t,
186 | :ob_type, :pointer
187 |
188 | # This struct is used when defining Python methods.
189 | self.const_set :PyMethodDef, Class.new(::FFI::Struct)
190 | self::PyMethodDef.layout :ml_name, :pointer,
191 | :ml_meth, :PyCFunction,
192 | :ml_flags, :int,
193 | :ml_doc, :pointer
194 | end
195 |
196 | end
197 |
198 | private :infect!
199 | end
200 |
--------------------------------------------------------------------------------
/lib/rubypython/pythonerror.rb:
--------------------------------------------------------------------------------
1 | require 'rubypython/python'
2 | require 'rubypython/macros'
3 |
4 | # Raised when an error occurs in the \Python interpreter.
5 | class RubyPython::PythonError < RuntimeError
6 | # The \Python traceback object associated with this error. This will be
7 | # a RubyPython::RubyPyProxy object.
8 | attr_reader :traceback
9 |
10 | # Creates the PythonError.
11 | # [typeName] The class name of the \Python error.
12 | # [msg] The message attached to the \Python error.
13 | # [traceback] The traceback, if any, associated with the \Python error.
14 | def initialize(typeName, msg, traceback = nil)
15 | @type = typeName
16 | @traceback = traceback
17 | super([typeName, msg].join(': '))
18 | end
19 |
20 | # This method should be called when an error has occurred in the \Python
21 | # interpreter. This acts as factory function for PythonError objects. The
22 | # function fetches calls +#fetch+ to get the error information from the
23 | # \Python interpreter and uses this to build a PythonError object. It then
24 | # calls +#clear to clear the error flag in the python interpreter. After
25 | # the error flag has been cleared, the PythonError object is returned.
26 | def self.handle_error
27 | rbType, rbValue, rbTraceback = fetch()
28 |
29 | if not rbValue.null?
30 | msg = rbValue.getAttr("__str__").callObject RubyPython::PyObject.buildArgTuple
31 | msg = msg.rubify
32 | else
33 | msg = nil
34 | end
35 |
36 | if not rbTraceback.null?
37 | traceback = RubyPython::RubyPyProxy.new rbTraceback
38 | else
39 | traceback = nil
40 | end
41 |
42 | # Decrease the reference count. This will happen anyway when they go out
43 | # of scope but might as well.
44 | rbValue.xDecref if not rbValue.null?
45 | pyName = rbType.getAttr("__name__")
46 |
47 | rbType.xDecref
48 | rbName = pyName.rubify
49 | pyName.xDecref
50 |
51 | RubyPython::PythonError.clear
52 | RubyPython::PythonError.new(rbName, msg, traceback)
53 | end
54 |
55 | # A wrapper to the \Python C API +PyErr_Fetch+ function. Returns an array
56 | # with three PyObject instances, representing the Type, the Value, and the
57 | # stack trace of the Python error.
58 | def self.fetch
59 | typePointer = ::FFI::MemoryPointer.new :pointer
60 | valuePointer = ::FFI::MemoryPointer.new :pointer
61 | tracebackPointer = ::FFI::MemoryPointer.new :pointer
62 |
63 | RubyPython::Python.PyErr_Fetch typePointer, valuePointer, tracebackPointer
64 |
65 | rbType = RubyPython::PyObject.new typePointer.read_pointer
66 | rbValue = RubyPython::PyObject.new valuePointer.read_pointer
67 | rbTraceback = RubyPython::PyObject.new tracebackPointer.read_pointer
68 | [rbType, rbValue, rbTraceback]
69 | end
70 |
71 | # Determines whether an error has occurred in the \Python interpreter.
72 | def self.error?
73 | !RubyPython::Python.PyErr_Occurred.null?
74 | end
75 |
76 | # Resets the \Python interpreter error flag
77 | def self.clear
78 | RubyPython::Python.PyErr_Clear
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/lib/rubypython/rubypyproxy.rb:
--------------------------------------------------------------------------------
1 | require 'rubypython/pythonerror'
2 | require 'rubypython/pyobject'
3 | require 'rubypython/conversion'
4 | require 'rubypython/operators'
5 | require 'rubypython/blankobject'
6 |
7 | module RubyPython
8 | # In most cases, users will interact with RubyPyProxy objects that hold
9 | # references to active objects in the \Python interpreter. RubyPyProxy
10 | # delegates method calls to \Python objects, wrapping and returning the
11 | # results as RubyPyProxy objects.
12 | #
13 | # The allocation, deallocation, and reference counting on RubyPyProxy
14 | # objects is automatic: RubyPython takes care of it all. When the object
15 | # is garbage collected, the instance will automatically decrement its
16 | # object reference count.
17 | #
18 | # [NOTE:] All RubyPyProxy objects become invalid when the \Python
19 | # interpreter is halted.
20 | #
21 | # == Calling Methods With Blocks
22 | # Any method which is forwarded to a \Python object may be called with a
23 | # block. The result of the method will passed as the argument to that
24 | # block.
25 | #
26 | # RubyPython.run do
27 | # sys = RubyPython.import 'sys'
28 | # sys.version { |v| v.rubify.split(' ') }
29 | # end
30 | # # => [ "2.6.1", … ]
31 | #
32 | # == Passing Procs and Methods to \Python Methods
33 | # RubyPython supports passing Proc and Method objects to \Python methods.
34 | # The Proc or Method object must be passed explicitly. As seen above,
35 | # supplying a block to a method will result in the return value of the
36 | # method call being passed to the block.
37 | #
38 | # When a Proc or Method is supplied as a callback, then arguments that it
39 | # will be called with will be wrapped \Python objects. It will therefore
40 | # typically be necessary to write a wrapper around any Ruby callback that
41 | # requires native Ruby objects.
42 | #
43 | # # Python Code: sample.py
44 | # def apply_callback(callback, argument):
45 | # return callback(argument)
46 | #
47 | # # IRB Session
48 | # >> RubyPython.start
49 | # => true
50 | # >> sys = RubyPython.import 'sys'
51 | # =>
52 | # >> sys.path.append('.')
53 | # => None
54 | # >> sample = RubyPython.import 'sample'
55 | # =>
56 | # >> callback = Proc.new { |arg| arg * 2 }
57 | # => #
58 | # >> sample.apply_callback(callback, 21).rubify
59 | # => 42
60 | # >> RubyPython.stop
61 | # => true
62 | class RubyPyProxy < BlankObject
63 | include Operators
64 |
65 | attr_reader :pObject
66 |
67 | # Creates a \Python proxy for the provided Ruby object.
68 | #
69 | # Only the following Ruby types can be represented in \Python:
70 | # * String
71 | # * Array
72 | # * Hash
73 | # * Fixnum
74 | # * Bignum
75 | # * Float
76 | # * Symbol (as a String)
77 | # * Proc
78 | # * Method
79 | # * +true+ (as True)
80 | # * +false+ (as False)
81 | # * +nil+ (as None)
82 | def initialize(pObject)
83 | if pObject.kind_of? PyObject
84 | @pObject = pObject
85 | else
86 | @pObject = PyObject.new pObject
87 | end
88 | end
89 |
90 | # Handles the job of wrapping up anything returned by a RubyPyProxy
91 | # instance. Every returned # object is wrapped by an instance of +RubyPyProxy+
92 | def _wrap(pyobject)
93 | if pyobject.class?
94 | RubyPyClass.new(pyobject)
95 | else
96 | RubyPyProxy.new(pyobject)
97 | end
98 | rescue Conversion::UnsupportedConversion => exc
99 | RubyPyProxy.new pyobject
100 | end
101 | private :_wrap
102 |
103 | reveal(:respond_to?)
104 |
105 | # The standard Ruby +#respond_to?+ method has been renamed to allow
106 | # RubyPython to query if the proxied \Python object supports the method
107 | # desired. Setter methods (e.g., +foo=+) are always supported.
108 | alias :is_real_method? :respond_to?
109 |
110 | # RubyPython checks the attribute dictionary of the wrapped object to
111 | # check whether it will respond to a method call. This should not return
112 | # false positives but it may return false negatives. The built-in Ruby
113 | # respond_to? method has been aliased to is_real_method?.
114 | def respond_to?(mname)
115 | return true if is_real_method?(mname)
116 | mname = mname.to_s
117 | return true if mname =~ /=$/
118 | @pObject.hasAttr(mname)
119 | end
120 |
121 | # Delegates method calls to proxied \Python objects.
122 | #
123 | # == Delegation Rules
124 | # 1. If the method ends with a question-mark (e.g., +nil?+), it can only
125 | # be a Ruby method on RubyPyProxy. Attempt to reveal it (RubyPyProxy
126 | # is a BlankObject) and call it.
127 | # 2. If the method ends with equals signs (e.g., +value=+) it's a setter
128 | # and we can always set an attribute on a \Python object.
129 | # 3. If the method ends with an exclamation point (e.g., +foo!+) we are
130 | # attempting to call a method with keyword arguments.
131 | # 4. The Python method or value will be called, if it's callable.
132 | # 5. RubyPython will wrap the return value in a RubyPyProxy object
133 | # 6. If a block has been provided, the wrapped return value will be
134 | # passed into the block.
135 | def method_missing(name, *args, &block)
136 | name = name.to_s
137 |
138 | if name =~ /\?$/
139 | begin
140 | RubyPyProxy.reveal(name.to_sym)
141 | return self.__send__(name.to_sym, *args, &block)
142 | rescue RuntimeError => exc
143 | raise NoMethodError.new(name) if exc.message =~ /Don't know how to reveal/
144 | raise
145 | end
146 | end
147 |
148 | kwargs = false
149 |
150 | if name =~ /=$/
151 | return @pObject.setAttr(name.chomp('='),
152 | PyObject.new(args.pop))
153 | elsif name =~ /!$/
154 | kwargs = true
155 | name.chomp! "!"
156 | end
157 |
158 | raise NoMethodError.new(name) if !@pObject.hasAttr(name)
159 |
160 | pFunc = @pObject.getAttr(name)
161 |
162 | if pFunc.callable?
163 | if args.empty? and pFunc.class?
164 | pReturn = pFunc
165 | else
166 | if kwargs and args.last.is_a?(Hash)
167 | pKeywords = PyObject.new args.pop
168 | end
169 | pReturn = _method_call(pFunc, args, pKeywords)
170 | pFunc.xDecref
171 | end
172 | else
173 | pReturn = pFunc
174 | end
175 |
176 | result = _wrap(pReturn)
177 |
178 | if block
179 | block.call(result)
180 | else
181 | result
182 | end
183 | end
184 |
185 | #Handles the of calling a wrapped callable Python object at a higher level
186 | #than +PyObject#callObject+. For internal use only.
187 | def _method_call(pFunc, args, pKeywords)
188 | pTuple = PyObject.buildArgTuple(*args)
189 | pReturn = if pKeywords
190 | pFunc.callObjectKeywords(pTuple, pKeywords)
191 | else
192 | pFunc.callObject(pTuple)
193 | end
194 |
195 | # Clean up unused Python vars instead of waiting on Ruby's GC to
196 | # do it.
197 | pTuple.xDecref
198 | pKeywords.xDecref if pKeywords
199 | raise PythonError.handle_error if PythonError.error?
200 | pReturn
201 | end
202 | private :_method_call
203 |
204 | # RubyPython will attempt to translate the wrapped object into a native
205 | # Ruby object. This will only succeed for simple built-in type.
206 | def rubify
207 | converted = @pObject.rubify
208 | if converted.kind_of? ::FFI::Pointer
209 | converted = self.class.new converted
210 | end
211 | converted
212 | end
213 |
214 | # Returns the String representation of the wrapped object via a call to
215 | # the object's __repr__ method, or the +repr+ method in PyMain.
216 | def inspect
217 | self.__repr__.rubify
218 | rescue PythonError, NoMethodError
219 | RubyPython::PyMain.repr(self).rubify
220 | end
221 |
222 | # Returns the string representation of the wrapped object via a call to
223 | # the object's __str__ method or the +str+ method in PyMain.
224 | def to_s
225 | self.__str__.rubify
226 | rescue PythonError, NoMethodError
227 | RubyPython::PyMain.str(self).rubify
228 | end
229 |
230 | # Converts the wrapped \Python object to a Ruby Array. Note that this
231 | # only converts one level, so a nested array will remain a proxy object.
232 | # Only wrapped objects which have an __iter__ method may be
233 | # converted using +to_a+.
234 | #
235 | # Note that for \Python Dict objects, this method returns what you would
236 | # get in \Python, not in Ruby: +a_dict.to_a+ returns an array of the
237 | # dictionary's keys.
238 | #
239 | # === List #to_a Returns an Array
240 | # >> RubyPython.start
241 | # => true
242 | # >> list = RubyPython::RubyPyProxy.new([1, 'a', 2, 'b'])
243 | # => [1, 'a', 2, 'b']
244 | # >> list.kind_of? RubyPython::RubyPyProxy
245 | # => true
246 | # >> list.to_a
247 | # => [1, 'a', 2, 'b']
248 | # >> RubyPython.stop
249 | # => true
250 | #
251 | # === Dict #to_a Returns An Array of Keys
252 | # >> RubyPython.start
253 | # => true
254 | # >> dict = RubyPython::RubyPyProxy.new({1 => '2', :three => [4,5]})
255 | # => {1: '2', 'three': [4, 5]}
256 | # >> dict.kind_of? RubyPython::RubyPyProxy
257 | # => true
258 | # >> dict.to_a
259 | # => [1, 'three']
260 | # >> RubyPython.stop
261 | # => true
262 | #
263 | # === Non-Array Values Do Not Convert
264 | # >> RubyPython.start
265 | # => true
266 | # >> item = RubyPython::RubyPyProxy.new(42)
267 | # => 42
268 | # >> item.to_a
269 | # NoMethodError: __iter__
270 | def to_a
271 | iter = self.__iter__
272 | ary = []
273 | loop do
274 | ary << iter.next()
275 | end
276 | rescue PythonError => exc
277 | raise if exc.message !~ /StopIteration/
278 | ary
279 | end
280 |
281 | # Returns the methods on the \Python object by calling the +dir+
282 | # built-in.
283 | def methods
284 | pObject.dir.map { |x| x.to_sym }
285 | end
286 |
287 | # Creates a PyEnumerable for this object. The object must have the
288 | # __iter__ method.
289 | def to_enum
290 | PyEnumerable.new(@pObject)
291 | end
292 | end
293 |
294 | # A class to wrap \Python modules. It behaves exactly the same as
295 | # RubyPyProxy. It is just here for Bookkeeping and aesthetics.
296 | class RubyPyModule < RubyPyProxy; end
297 |
298 | # A class to wrap \Python classes.
299 | class RubyPyClass < RubyPyProxy
300 | # Create an instance of the wrapped class. This is a workaround for the
301 | # fact that \Python classes are meant to be callable.
302 | def new(*args)
303 | pReturn = _method_call(@pObject, args, nil)
304 | RubyPyInstance.new pReturn
305 | end
306 | end
307 |
308 | # An object representing an instance of a \Python class. It behaves
309 | # exactly the same as RubyPyProxy. It is just here for Bookkeeping and
310 | # aesthetics.
311 | class RubyPyInstance < RubyPyProxy; end
312 |
313 | # An object representing a Python enumerable object.
314 | class PyEnumerable < RubyPyProxy
315 | include Enumerable
316 |
317 | def each
318 | iter = self.__iter__
319 | loop do
320 | begin
321 | yield iter.next
322 | rescue RubyPython::PythonError => exc
323 | return if exc.message =~ /StopIteration/
324 | end
325 | end
326 | end
327 | end
328 | end
329 |
--------------------------------------------------------------------------------
/lib/rubypython/tuple.rb:
--------------------------------------------------------------------------------
1 | module RubyPython
2 | # A subclass of ::Array that will convert to a Python Tuple automatically.
3 | class Tuple < ::Array
4 | def self.tuple(array)
5 | value = self.new
6 | value.replace(array.dup)
7 | value
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/rubypython/type.rb:
--------------------------------------------------------------------------------
1 | module RubyPython
2 | # Creates a Ruby class that inherits from a proxied Python object.
3 | def self.Type(name)
4 | mod, match, klass = name.rpartition(".")
5 | pymod = RubyPython.import(mod)
6 | pyclass = pymod.pObject.getAttr(klass)
7 | rclass = Class.new(RubyPyProxy) do
8 | define_method(:initialize) do |*args|
9 | args = PyObject.convert(*args)
10 | pTuple = PyObject.buildArgTuple(*args)
11 | pReturn = pyclass.callObject(pTuple)
12 | if PythonError.error?
13 | raise PythonError.handle_error
14 | end
15 | @pObject = pReturn
16 | end
17 | end
18 | return rclass
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/rubypython.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | # stub: rubypython 0.6.4 ruby lib
3 |
4 | Gem::Specification.new do |s|
5 | s.name = "rubypython"
6 | s.version = "0.6.4"
7 |
8 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9 | s.require_paths = ["lib"]
10 | s.authors = ["Steeve Morin", "Austin Ziegler", "Zach Raines"]
11 | s.date = "2014-02-04"
12 | s.description = "RubyPython is a bridge between the Ruby and Python interpreters. It embeds a\nrunning Python interpreter in the Ruby application's process using FFI and\nprovides a means for wrapping, converting, and calling Python objects and\nmethods.\n\nRubyPython uses FFI to marshal the data between the Ruby and Python VMs and\nmake Python calls. You can:\n\n* Inherit from Python classes.\n* Configure callbacks from Python.\n* Run Python generators (on Ruby 1.9.2 or later)."
13 | s.email = ["swiuzzz+rubypython@gmail.com", "austin@rubyforge.org", "raineszm+rubypython@gmail.com"]
14 | s.extra_rdoc_files = ["Contributors.rdoc", "History.rdoc", "License.rdoc", "Manifest.txt", "PostInstall.txt", "README.rdoc", "website/robots.txt", "Contributors.rdoc", "History.rdoc", "License.rdoc", "README.rdoc"]
15 | s.files = [".autotest", ".gitignore", ".hgignore", ".hgtags", ".rspec", "Contributors.rdoc", "History.rdoc", "License.rdoc", "Manifest.txt", "PostInstall.txt", "README.rdoc", "Rakefile", "autotest/discover.rb", "lib/rubypython.rb", "lib/rubypython/blankobject.rb", "lib/rubypython/conversion.rb", "lib/rubypython/interpreter.rb", "lib/rubypython/macros.rb", "lib/rubypython/operators.rb", "lib/rubypython/pygenerator.rb", "lib/rubypython/pymainclass.rb", "lib/rubypython/pyobject.rb", "lib/rubypython/python.rb", "lib/rubypython/pythonerror.rb", "lib/rubypython/rubypyproxy.rb", "lib/rubypython/tuple.rb", "lib/rubypython/type.rb", "spec/basic_spec.rb", "spec/callback_spec.rb", "spec/conversion_spec.rb", "spec/pymainclass_spec.rb", "spec/pyobject_spec.rb", "spec/python_helpers/basics.py", "spec/python_helpers/errors.py", "spec/python_helpers/objects.py", "spec/pythonerror_spec.rb", "spec/refcnt_spec.rb", "spec/rubypyclass_spec.rb", "spec/rubypyproxy_spec.rb", "spec/rubypython_spec.rb", "spec/spec_helper.rb", "website/index.rhtml", "website/robots.txt", "website/stylesheets/960.css", "website/stylesheets/border-radius.htc", "website/stylesheets/screen.css"]
16 | s.licenses = ["MIT"]
17 | s.rdoc_options = ["--main", "README.rdoc"]
18 | s.requirements = ["Python, ~> 2.4"]
19 | s.rubyforge_project = "rubypython"
20 | s.rubygems_version = "2.2.1"
21 | s.summary = "RubyPython is a bridge between the Ruby and Python interpreters"
22 |
23 | if s.respond_to? :specification_version then
24 | s.specification_version = 4
25 |
26 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
27 | s.add_runtime_dependency(%q, ["~> 1.0.7"])
28 | s.add_runtime_dependency(%q, [">= 2.1.2.3"])
29 | s.add_development_dependency(%q, [">= 2.0.4"])
30 | s.add_development_dependency(%q, ["~> 4.0"])
31 | s.add_development_dependency(%q, ["~> 1.0"])
32 | s.add_development_dependency(%q, ["~> 1.1"])
33 | s.add_development_dependency(%q, ["~> 1.5"])
34 | s.add_development_dependency(%q, ["~> 1.0"])
35 | s.add_development_dependency(%q, ["~> 1.0"])
36 | s.add_development_dependency(%q, ["~> 1.2"])
37 | s.add_development_dependency(%q, ["~> 2.0"])
38 | s.add_development_dependency(%q, ["~> 1.0"])
39 | s.add_development_dependency(%q, ["~> 3.8"])
40 | else
41 | s.add_dependency(%q, ["~> 1.0.7"])
42 | s.add_dependency(%q, [">= 2.1.2.3"])
43 | s.add_dependency(%q, [">= 2.0.4"])
44 | s.add_dependency(%q, ["~> 4.0"])
45 | s.add_dependency(%q, ["~> 1.0"])
46 | s.add_dependency(%q, ["~> 1.1"])
47 | s.add_dependency(%q, ["~> 1.5"])
48 | s.add_dependency(%q, ["~> 1.0"])
49 | s.add_dependency(%q, ["~> 1.0"])
50 | s.add_dependency(%q, ["~> 1.2"])
51 | s.add_dependency(%q, ["~> 2.0"])
52 | s.add_dependency(%q, ["~> 1.0"])
53 | s.add_dependency(%q, ["~> 3.8"])
54 | end
55 | else
56 | s.add_dependency(%q, ["~> 1.0.7"])
57 | s.add_dependency(%q, [">= 2.1.2.3"])
58 | s.add_dependency(%q, [">= 2.0.4"])
59 | s.add_dependency(%q, ["~> 4.0"])
60 | s.add_dependency(%q, ["~> 1.0"])
61 | s.add_dependency(%q, ["~> 1.1"])
62 | s.add_dependency(%q, ["~> 1.5"])
63 | s.add_dependency(%q, ["~> 1.0"])
64 | s.add_dependency(%q, ["~> 1.0"])
65 | s.add_dependency(%q, ["~> 1.2"])
66 | s.add_dependency(%q, ["~> 2.0"])
67 | s.add_dependency(%q, ["~> 1.0"])
68 | s.add_dependency(%q, ["~> 3.8"])
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/spec/basic_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/spec_helper.rb'
2 |
3 | include TestConstants
4 |
5 | describe "RubyPython" do
6 | it "can import and use a native extension like cPickle" do
7 | cPickle = RubyPython.import("cPickle")
8 | string = cPickle.dumps("Testing RubyPython.")
9 | string.should_not be_a_kind_of String
10 | string.rubify.should be_a_kind_of String
11 | string.rubify.should ~ /S'Testing RubyPython.'\n/
12 | end
13 |
14 | it "can import and use a pure Python extension like pickle" do
15 | pickle = RubyPython.import("pickle")
16 | string = pickle.dumps("Testing RubyPython.")
17 | string.should_not be_a_kind_of String
18 | string.rubify.should be_a_kind_of String
19 | string.rubify.should ~ /S'Testing RubyPython.'\n/
20 | end
21 |
22 | it "can use iterators from Python" do
23 | items = []
24 | @basics.iterate_list.to_enum.each { |item| items << item }
25 | items.should == [ 1, 2, 3 ]
26 | end
27 |
28 | it "can use Ruby procs as callbacks to Python code" do
29 | @basics.simple_callback(lambda { |v| v * v }, 4).should == 16
30 | end
31 |
32 | it "can use Ruby methods as callbacks to Python code" do
33 | def triple(v)
34 | v * 3
35 | end
36 | @basics.simple_callback(method(:triple), 4).should == 12
37 | end
38 |
39 | it "can feed a Python generator in Ruby 1.9", :ruby_version => '1.9' do
40 | output = @basics.simple_generator(RubyPython.generator do
41 | (1..10).each { |i| RubyPython.yield i }
42 | end)
43 | output.should == [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
44 | end
45 |
46 | it "can use named parameters to functions" do
47 | @basics.named_args(2, 1).should == [ 2, 1 ]
48 | @basics.named_args!(:arg2 => 2, :arg1 => 1).should == [ 1, 2 ]
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/spec/callback_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/spec_helper.rb'
2 |
3 | include TestConstants
4 |
5 | describe 'Callbacks' do
6 | {
7 | 'procs' => AProc,
8 | 'methods' => AMethod,
9 | }.each do |rb_type, rb_object|
10 | it "accepts #{rb_type} as functions" do
11 | [
12 | [2, 2],
13 | ["a", "Word"],
14 | [ [1, 2], [3, 4] ]
15 | ].each do |args|
16 | @objects.apply_callback(rb_object, args).should == rb_object.call(*args)
17 | end
18 | end
19 | end
20 |
21 | [
22 | ["an int", AnInt],
23 | ["a float", AFloat],
24 | ["a string", AString],
25 | ["a string with nulls", AStringWithNULLs],
26 | ["an array", AnArray],
27 | ["an array", AnArray],
28 | ["a hash", AConvertedHash],
29 | ["true", true],
30 | ["false", false],
31 | ["nil", nil]
32 | ].each do |rb_type, rb_value|
33 | it "is able to return #{rb_type}" do
34 | callback = Proc.new do
35 | rb_value
36 | end
37 |
38 | @objects.apply_callback(callback, []).should == rb_value
39 | end
40 | end
41 |
42 | it "is able to be stored by python variables" do
43 | mockObject = @objects.RubyPythonMockObject.new
44 | mockObject.callback = AProc
45 | end
46 |
47 | it "is callable as a python instance variable" do
48 | mockObject = @objects.RubyPythonMockObject.new
49 | mockObject.callback = AProc
50 | mockObject.callback(2, 2).rubify.should == 4
51 | end
52 |
53 | end
54 |
--------------------------------------------------------------------------------
/spec/conversion_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/spec_helper.rb'
2 |
3 | include TestConstants
4 |
5 | describe RubyPython::Conversion do
6 | subject { RubyPython::Conversion }
7 |
8 | context "when converting from Python to Ruby" do
9 | [
10 | ["an int", "an int", AnInt],
11 | ["a float", "a float", AFloat],
12 | ["a string", "a string", AString],
13 | ["a string_with_nulls", "a string_with_nulls", AStringWithNULLs],
14 | ["a list", "an array", AnArray],
15 | ["a tuple", "a tuple", ATuple],
16 | ["a dict", "a hash", AConvertedHash],
17 | ["python True", "true", true],
18 | ["python False", "false", false],
19 | ["python None", "nil", nil]
20 | ].each do |py_type, rb_type, output|
21 | it "should convert #{py_type} to #{rb_type}" do
22 | py_object_ptr = @objects.__send__(py_type.sub(' ', '_')).pObject.pointer
23 | obj = subject.ptorObject(py_object_ptr)
24 | obj.should == output
25 | obj.class.should == output.class
26 | end
27 | end
28 |
29 | it "should return an FFI::Pointer when it cannot convert" do
30 | unconvertable = @objects.RubyPythonMockObject.pObject.pointer
31 | subject.ptorObject(unconvertable).should be_a_kind_of(FFI::Pointer)
32 | end
33 | end
34 |
35 | context "when converting Ruby to Python" do
36 | [
37 | ["an int", "an int", AnInt],
38 | ["a float", "a float", AFloat],
39 | ["a string", "a string", AString],
40 | ["a string_with_nulls", "a string_with_nulls", AStringWithNULLs],
41 | ["a string", "a symbol", ASym],
42 | ["a list", "an array", AnArray],
43 | ["a tuple", "a tuple", ATuple ],
44 | ["a dict", "a hash", AConvertedHash],
45 | ["python True", "true", true],
46 | ["python False", "false", false],
47 | ["python None", "nil", nil],
48 | ["a function", "a proc", AProc, true]
49 | ].each do |py_type, rb_type, input, no_compare|
50 | it "should convert #{rb_type} to #{py_type}" do
51 | py_object_ptr = subject.rtopObject(input)
52 | unless no_compare
53 | output = @objects.__send__(rb_type.sub(' ', '_')).pObject.pointer
54 | RubyPython::Python.PyObject_Compare(py_object_ptr, output).should == 0
55 | end
56 | end
57 | end
58 |
59 | it "should raise an exception when it cannot convert" do
60 | lambda { subject.rtopObject(Class) }.should raise_exception(subject::UnsupportedConversion)
61 | end
62 |
63 | it "should convert a tuple correctly" do
64 | @basics.expects_tuple(AnArray).should == false
65 | @basics.expects_tuple(RubyPython::Tuple.tuple(AnArray)).should == true
66 | end
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/spec/pymainclass_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/spec_helper.rb'
2 |
3 | include TestConstants
4 |
5 | describe RubyPython::PyMainClass do
6 | subject { RubyPython::PyMain }
7 |
8 | it "should delegate to builtins" do
9 | subject.float(AnInt).rubify.should == AnInt.to_f
10 | end
11 |
12 | it "should handle block syntax" do
13 | subject.float(AnInt) {|f| f.rubify*2}.should == (AnInt.to_f * 2)
14 | end
15 |
16 | it "should allow attribute access" do
17 | subject.main.__name__.rubify.should == '__main__'
18 | end
19 |
20 | it "should allow global variable setting" do
21 | subject.x = 2
22 | subject.x.rubify.should == 2
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/pyobject_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/spec_helper.rb'
2 |
3 | include TestConstants
4 |
5 | describe RubyPython::PyObject do
6 | before do
7 | @string = RubyPython.import('string').pObject
8 | @urllib2 = RubyPython.import('urllib2').pObject
9 | @builtin = RubyPython.import("__builtin__")
10 | sys = RubyPython.import 'sys'
11 | sys.path.append './spec/python_helpers/'
12 | @objects = RubyPython.import('objects')
13 | end
14 |
15 | describe ".new" do
16 | [
17 | ["a string", AString],
18 | ["an int", AnInt],
19 | ["a float", AFloat],
20 | ["an array", AnArray],
21 | ["a symbol", ASym],
22 | ["a hash", AHash]
23 | ].each do |title, obj|
24 | it "should wrap #{title}" do
25 | lambda { described_class.new(obj) }.should_not raise_exception
26 | end
27 | end
28 |
29 | [
30 | "a string",
31 | "an int",
32 | "a list",
33 | "a dict",
34 | "a tuple"
35 | ].each do |title|
36 | it "should take #{title} from a Python pointer" do
37 | lambda do
38 | py_obj = @objects.__send__(title.gsub(' ','_')).pObject.pointer
39 | described_class.new(py_obj)
40 | end.should_not raise_exception
41 | end
42 | end
43 | end #new
44 |
45 | describe "#rubify" do
46 | [
47 | ["a string", AString],
48 | ["an int", AnInt],
49 | ["a float", AFloat],
50 | ["an array", AnArray],
51 | ["a symbol", ASym, ASym.to_s],
52 | ["a hash", AHash, AConvertedHash]
53 | ].each do |arr|
54 | type, input, output = arr
55 | output ||= input
56 |
57 | it "should faithfully unwrap #{type}" do
58 | described_class.new(input).rubify.should == output
59 | end
60 | end
61 | end #rubify
62 |
63 | describe "#hasAttr" do
64 | it "should return true when object has the requested attribute" do
65 | @string.hasAttr("ascii_letters").should be_true
66 | end
67 |
68 | it "should return false when object does not have the requested attribute" do
69 | @string.hasAttr("nonExistentThing").should be_false
70 | end
71 | end
72 |
73 | describe "#getAttr" do
74 | it "should fetch requested object attribute" do
75 | ascii_letters = @string.getAttr "ascii_letters"
76 | ascii_letters.rubify.should == "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
77 | end
78 |
79 | it "should return a PyObject instance" do
80 | ascii_letters = @string.getAttr "ascii_letters"
81 | ascii_letters.should be_kind_of(described_class)
82 | end
83 | end
84 |
85 | describe "#setAttr" do
86 | it "should modify the specified attribute of the object" do
87 | pyNewLetters = described_class.new "RbPy"
88 | @string.setAttr "ascii_letters", pyNewLetters
89 | @string.getAttr("ascii_letters").rubify.should == pyNewLetters.rubify
90 | end
91 |
92 | it "should create the requested attribute if it doesn't exist" do
93 | pyNewString = described_class.new "python"
94 | @string.setAttr "ruby", pyNewString
95 | @string.getAttr("ruby").rubify.should == pyNewString.rubify
96 | end
97 | end
98 |
99 | describe "#cmp" do
100 | before do
101 | @less = described_class.new 5
102 | @greater = described_class.new 10
103 | @less_dup = described_class.new 5
104 | end
105 |
106 | it "should return 0 when objects are equal" do
107 | @less.cmp(@less_dup).should == 0
108 | end
109 |
110 | it "should change sign under interchange of arguments" do
111 | @less.cmp(@greater).should == -@greater.cmp(@less)
112 | end
113 |
114 | it "should return -1 when first object is less than the second" do
115 | @less.cmp(@greater).should == -1
116 | end
117 |
118 | it "should return 1 when first object is greater than the second" do
119 | @greater.cmp(@less).should == 1
120 | end
121 | end
122 |
123 |
124 | describe "#callObject" do
125 | #Expand coverage types
126 | it "should execute wrapped object with supplied arguments" do
127 | arg = described_class.new AnInt
128 | argt = described_class.buildArgTuple arg
129 |
130 | builtin = @builtin.pObject
131 | stringClass = builtin.getAttr "str"
132 | stringClass.callObject(argt).rubify.should == AnInt.to_s
133 | end
134 | end
135 |
136 | describe "#function_or_method?" do
137 | it "should be true given a method" do
138 | mockObjClass = @objects.RubyPythonMockObject.pObject
139 | mockObjClass.getAttr('square_elements').should be_a_function_or_method
140 | end
141 |
142 | it "should be true given a function" do
143 | @objects.pObject.getAttr('identity').should be_a_function_or_method
144 | end
145 |
146 | it "should return true given a builtin function" do
147 | any = @builtin.pObject.getAttr('any')
148 | any.should be_a_function_or_method
149 | end
150 |
151 | it "should return false given a class" do
152 | @objects.RubyPythonMockObject.pObject.should_not be_a_function_or_method
153 | end
154 | end
155 |
156 | describe "#class?" do
157 | it "should return true if wrapped object is an old style class" do
158 | @objects.RubyPythonMockObject.pObject.should be_a_class
159 | end
160 |
161 | it "should return true if wrapped object is an new style class" do
162 | @objects.NewStyleClass.pObject.should be_a_class
163 | end
164 |
165 | it "should return true if wrapped object is a builtin class" do
166 | strClass = @builtin.pObject.getAttr('str')
167 | strClass.should be_a_class
168 | end
169 |
170 | it "should return false given an object instance" do
171 | @objects.RubyPythonMockObject.new.pObject.should_not be_a_class
172 | end
173 | end
174 |
175 | describe "#callable?" do
176 | it "should be true given a method" do
177 | mockObjClass = @objects.RubyPythonMockObject.pObject
178 | mockObjClass.getAttr('square_elements').should be_callable
179 | end
180 |
181 | it "should be true given a function" do
182 | @objects.pObject.getAttr('identity').should be_callable
183 | end
184 |
185 | it "should return true given a builtin function" do
186 | any = @builtin.pObject.getAttr('any')
187 | any.should be_callable
188 | end
189 |
190 | it "should return true given a class" do
191 | @objects.RubyPythonMockObject.pObject.should be_callable
192 | end
193 |
194 | it "should return false given a non-callable instance" do
195 | @objects.RubyPythonMockObject.new.pObject.should_not be_callable
196 | end
197 |
198 | specify { described_class.new(6).should_not be_callable }
199 |
200 | end
201 |
202 | end
203 |
--------------------------------------------------------------------------------
/spec/python_helpers/basics.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | def iterate_list():
4 | for item in [ 1, 2, 3 ]:
5 | yield item
6 |
7 | def identity(object):
8 | return object
9 |
10 | def simple_callback(callback, value):
11 | return callback(value)
12 |
13 | def simple_generator(callback):
14 | output = []
15 | for i in callback():
16 | output.append(i)
17 | return output
18 |
19 | def named_args(arg1, arg2):
20 | return [arg1, arg2]
21 |
22 | def expects_tuple(tvalue):
23 | return isinstance(tvalue, tuple)
24 |
--------------------------------------------------------------------------------
/spec/python_helpers/errors.py:
--------------------------------------------------------------------------------
1 | def nested_error():
2 | return 1 / 0
3 |
--------------------------------------------------------------------------------
/spec/python_helpers/objects.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | def identity(object):
4 | return object
5 |
6 | def apply_callback(callback, args):
7 | return callback(*args)
8 |
9 | def named_args(arg1, arg2):
10 | return [arg2*2, arg1*2]
11 |
12 | class RubyPythonMockObject:
13 | STRING = "STRING"
14 | STRING_LIST = ["STRING1", "STRING2"]
15 | INT = 1
16 | INT_LIST = [1,1]
17 | FLOAT = 1.0
18 | FLOAT_LIST = [1.0,1.0]
19 |
20 | def square_elements(self, aList):
21 | return [x**2 for x in aList]
22 |
23 | def sum_elements(self, aList):
24 | return sum(aList)
25 |
26 | def __eq__(self, other):
27 | if type(self) == type(other):
28 | return True
29 | else:
30 | return False
31 |
32 | class NewStyleClass(object):
33 | def a_method(self):
34 | pass
35 |
36 | an_int = 1
37 | a_char = 'a'
38 | a_float = 1.0
39 | a_symbol = 'sym'
40 | a_string = "STRING"
41 | a_string_with_nulls = "STRING\0WITH\0NULLS"
42 | an_array = a_list = [an_int, a_char, a_float, a_string]
43 | a_hash = a_dict = { an_int: an_int, a_char: a_char, a_symbol: a_float,
44 | a_string: a_string }
45 | true = python_True = True
46 | false = python_False = False
47 | nil = python_None = None
48 | a_tuple = tuple(a_list)
49 |
--------------------------------------------------------------------------------
/spec/pythonerror_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/spec_helper.rb'
2 |
3 | describe RubyPython::PythonError do
4 | def cause_error
5 | RubyPython::Python.PyImport_ImportModule("wat")
6 | end
7 |
8 | describe "#error?" do
9 | it "should return false when no error has occured" do
10 | described_class.error?.should be_false
11 | end
12 |
13 | it "should return true when an error has occured" do
14 | cause_error
15 | described_class.error?.should be_true
16 | end
17 | end
18 |
19 | describe "#clear" do
20 | it "should reset the Python error flag" do
21 | cause_error
22 | described_class.clear
23 | described_class.error?.should be_false
24 | end
25 |
26 | it "should not barf when there is no error" do
27 | lambda {described_class.clear}.should_not raise_exception
28 | end
29 | end
30 |
31 | describe "#fetch" do
32 | it "should make availible Python error type" do
33 | cause_error
34 | rbType, rbValue, rbTraceback = described_class.fetch
35 | rbType.getAttr("__name__").rubify.should == "ImportError"
36 | end
37 | end
38 |
39 | describe ".last_traceback" do
40 | it "should make availble the Python traceback of the last error" do
41 | traceback = RubyPython.import 'traceback'
42 | errors = RubyPython.import 'errors'
43 | begin
44 | errors.nested_error
45 | rescue RubyPython::PythonError => exc
46 | tb = exc.traceback
47 | list = traceback.format_tb(tb)
48 | list.rubify[0].should =~ /1 \/ 0/
49 | end
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/spec/refcnt_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/spec_helper.rb'
2 |
3 | def get_refcnt(pobject)
4 | raise 'Cannot work with a nil object' if pobject.nil?
5 |
6 | if pobject.kind_of? RubyPython::RubyPyProxy
7 | pobject = pobject.pObject.pointer
8 | elsif pobject.kind_of? RubyPython::PyObject
9 | pobject = pobject.pointer
10 | end
11 | RubyPython::Macros.Py_REFCNT pobject
12 | end
13 |
14 | include TestConstants
15 |
16 | describe 'Reference Counting' do
17 | before :all do
18 | RubyPython.start
19 | @sys = RubyPython.import 'sys'
20 | @sys.path.append './spec/python_helpers'
21 | @objects = RubyPython.import 'objects'
22 | end
23 |
24 | after :all do
25 | RubyPython.stop
26 | end
27 |
28 | it "should be one given a new object" do
29 | pyObj = @objects.RubyPythonMockObject.new
30 | get_refcnt(pyObj).should == 1
31 | end
32 |
33 | it "should increase when a new reference is passed into Ruby" do
34 | pyObj = @objects.RubyPythonMockObject
35 | refcnt = get_refcnt(pyObj)
36 | pyObj2 = @objects.RubyPythonMockObject
37 | get_refcnt(pyObj).should == (refcnt + 1)
38 | end
39 |
40 | describe RubyPython::PyObject do
41 | describe "#xIncref" do
42 | it "should increase the reference count" do
43 | pyObj = @objects.RubyPythonMockObject.new
44 | refcnt = get_refcnt(pyObj)
45 | pyObj.pObject.xIncref
46 | get_refcnt(pyObj).should == refcnt + 1
47 | end
48 | end
49 |
50 | describe "#xDecref" do
51 | it "should decrease the reference count" do
52 | pyObj = @objects.RubyPythonMockObject.new
53 | pyObj.pObject.xIncref
54 | refcnt = get_refcnt(pyObj)
55 | pointer = pyObj.pObject.pointer
56 | pyObj.pObject.xDecref
57 | get_refcnt(pointer).should == refcnt - 1
58 | end
59 | end
60 | end
61 |
62 | describe RubyPython::Conversion do
63 | describe ".rtopArrayToList" do
64 | it "should incref any wrapped objects in the array" do
65 | int = RubyPython::PyObject.new AnInt
66 | refcnt = get_refcnt(int)
67 | arr = [int]
68 | pyArr = subject.rtopArrayToList(arr)
69 | get_refcnt(int).should == refcnt + 1
70 | end
71 |
72 | end
73 |
74 | describe ".rtopObject" do
75 | [
76 | ["string", AString],
77 | ["float", AFloat],
78 | ["array", AnArray],
79 | #["symbol", ASym],
80 | ["hash", AHash]
81 | ].each do |arr|
82 | type, input = arr
83 |
84 | it "should return a refcnt of 1 for newly created #{type}" do
85 | pyObj = subject.rtopObject(input)
86 | get_refcnt(pyObj).should == 1
87 | end
88 |
89 | it "should increment the refcnt each time the same #{type} is passed in" do
90 | pyObj = RubyPython::PyObject.new subject.rtopObject(input)
91 | refcnt = get_refcnt(pyObj)
92 | pyObj2 = subject.rtopObject(pyObj)
93 | get_refcnt(pyObj2).should == refcnt + 1
94 | end
95 | end
96 | end
97 | end
98 | end
99 |
--------------------------------------------------------------------------------
/spec/rubypyclass_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/spec_helper.rb'
2 |
3 | describe RubyPython::RubyPyClass do
4 | describe "#new" do
5 | it "should return a RubyPyInstance" do
6 | urllib2 = RubyPython.import 'urllib2'
7 | urllib2.Request.new('google.com').should be_a(RubyPython::RubyPyInstance)
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/rubypyproxy_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/spec_helper.rb'
2 |
3 | include TestConstants
4 | describe RubyPython::RubyPyProxy do
5 | before do
6 | @a = RubyPython::PyObject.new "a"
7 | @b = RubyPython::PyObject.new "b"
8 | @builtin = RubyPython.import("__builtin__").pObject
9 | @string = RubyPython.import("string").pObject
10 |
11 | @two = described_class.new 2
12 | @six = described_class.new 6
13 |
14 | @sys = RubyPython.import 'sys'
15 | @sys.path.append './spec/python_helpers'
16 | @objects = RubyPython.import 'objects'
17 | end
18 |
19 | describe "#new" do
20 | it "should accept a PyObject instance" do
21 | rbPyObject = RubyPython::PyObject.new AString
22 | lambda {described_class.new rbPyObject}.should_not raise_exception
23 | end
24 |
25 | [
26 | ["a string", AString],
27 | ["an int", AnInt],
28 | ["a float", AFloat],
29 | ["an array", AnArray],
30 | ["a symbol", ASym, ASym.to_s],
31 | ["a hash", AHash, AConvertedHash]
32 | ].each do |arr|
33 | type, input, output = arr
34 | output ||= input
35 |
36 | it "should convert #{type} to wrapped pObject" do
37 | described_class.new(input).pObject.rubify.should == output
38 | end
39 | end
40 | end
41 |
42 | describe "#rubify" do
43 | [
44 | ["a string", AString],
45 | ["an int", AnInt],
46 | ["a float", AFloat],
47 | ["an array", AnArray],
48 | ["a symbol", ASym],
49 | ["a hash", AHash]
50 | ].each do |title, obj|
51 | it "should faithfully unwrap #{title}" do
52 | pyObject = RubyPython::PyObject.new obj
53 | proxy = described_class.new pyObject
54 | proxy.rubify.should == pyObject.rubify
55 | end
56 | end
57 | end
58 |
59 | describe "#inspect" do
60 | it "should return 'repr' of wrapped object" do
61 | @six.inspect.should == '6'
62 | end
63 |
64 | it "should gracefully handle lack of defined __repr__" do
65 | lambda { @objects.RubyPythonMockObject.inspect }.should_not raise_exception
66 | end
67 |
68 | it "always tries the 'repr' function if __repr__ produces an error" do
69 | RubyPython::PyMain.list.inspect.should == run_python_command('print repr(list)').chomp
70 | end
71 | end
72 |
73 | describe "#to_s" do
74 | it "should return 'str' of wrapped object" do
75 | @six.to_s.should == '6'
76 | end
77 |
78 | it "should gracefully handle lack of defined __str__" do
79 | lambda { @objects.RubyPythonMockObject.to_s }.should_not raise_exception
80 | end
81 |
82 | it "always tries the 'str' function if __repr__ produces an error" do
83 | RubyPython::PyMain.list.to_s.should == run_python_command('print str(list)').chomp
84 | end
85 | end
86 |
87 | describe "#to_a" do
88 | it "should convert a list to an array of its entries" do
89 | list = @objects.a_list
90 | list.to_a.should == AnArray.map { |x| described_class.new(x) }
91 | end
92 |
93 | it "should convert a tuple to an array of its entries" do
94 | tuple = @objects.a_tuple
95 | tuple.to_a.should == AnArray.map { |x| described_class.new(x) }
96 | end
97 |
98 | it "should convert a dict to an array of keys" do
99 | dict = @objects.a_dict
100 | dict.to_a.sort.should == AConvertedHash.keys.map {|x| described_class.new(x)}.sort
101 | end
102 | end
103 |
104 | describe "#respond_to?" do
105 | it "should return true given getters" do
106 | @objects.should respond_to(:RubyPythonMockObject)
107 | end
108 |
109 | it "should return false given undefined methods" do
110 | @objects.should_not respond_to(:undefined_attr)
111 | end
112 |
113 | it "should return true given any setter" do
114 | @objects.should respond_to(:any_variable=)
115 | end
116 |
117 | it "should return true given methods on RubyPyProxy instance" do
118 | @objects.should respond_to(:inspect)
119 | end
120 | end
121 |
122 | describe "method delegation" do
123 | it "should refer method calls to wrapped object" do
124 | aProxy = described_class.new(@a)
125 | bProxy = described_class.new(@b)
126 | aProxy.__add__(bProxy).rubify.should == (@a.rubify + @b.rubify)
127 | end
128 |
129 | it "should raise NoMethodError when method is undefined" do
130 | aProxy = described_class.new @a
131 | lambda {aProxy.wat}.should raise_exception(NoMethodError)
132 | end
133 |
134 | it "raises NoMethodError when boolean method is undefined" do
135 | aProxy = described_class.new @a
136 | lambda { aProxy.wat? }.should raise_exception(NoMethodError)
137 | end
138 |
139 | it "should allow methods to be called with no arguments" do
140 | builtinProxy = described_class.new @builtin
141 | rbStrClass = builtinProxy.str
142 | rbStrClass.new.rubify.should == String.new
143 | end
144 |
145 | it "should fetch attributes when method name is an attribute" do
146 | pyLetters = @string.getAttr "ascii_letters"
147 | stringProxy = described_class.new @string
148 | stringProxy.ascii_letters.rubify.should == pyLetters.rubify
149 | end
150 |
151 | it "should set attribute if method call is a setter" do
152 | stringProxy = described_class.new @string
153 | stringProxy.letters = AString
154 | stringProxy.letters.rubify.should == AString
155 | end
156 |
157 | it "should create nonexistent attirubte if method call is a setter" do
158 | stringProxy = described_class.new @string
159 | stringProxy.nonExistent = 1
160 | stringProxy.nonExistent.rubify.should == 1
161 | end
162 |
163 | it "should return a class as a RubyPyClass" do
164 | urllib2 = RubyPython.import('urllib2')
165 | urllib2.Request.should be_a(RubyPython::RubyPyClass)
166 | end
167 |
168 | it "should pass named args via bang method" do
169 | @objects.named_args!(:arg2 => 2, :arg1 => 1).rubify.should == [4,2]
170 | end
171 |
172 | it "should pass through keyword arguments via bang method" do
173 | builtinProxy = described_class.new @builtin
174 | builtinProxy.dict!({'dict'=>'val'}, :keyword=>true).rubify.should == {
175 | 'dict' => 'val',
176 | 'keyword' => true
177 | }
178 | end
179 | end
180 |
181 | describe "when used with an operator" do
182 | [
183 | '+', '-', '/', '*', '&', '^', '%', '**',
184 | '>>', '<<', '<=>', '|'
185 | ].each do |op|
186 | it "should delegate #{op}" do
187 | @six.__send__(op, @two).rubify.should == 6.__send__(op, 2)
188 | end
189 | end
190 |
191 | [
192 | '~', '-@', '+@'
193 | ].each do |op|
194 | it "should delegate #{op}" do
195 | @six.__send__(op).rubify.should == 6.__send__(op)
196 | end
197 | end
198 |
199 | ['==', '<', '>', '<=', '>='].each do |op|
200 | it "should delegate #{op}" do
201 | @six.__send__(op, @two).should == 6.__send__(op, 2)
202 | end
203 | end
204 |
205 | describe "#equal?" do
206 | it "be true given proxies representing the same object" do
207 | obj1 = @objects.RubyPythonMockObject
208 | obj2 = @objects.RubyPythonMockObject
209 | obj1.should equal(obj2)
210 | end
211 |
212 | it "should be false given objects which are different" do
213 | @two.should_not equal(@six)
214 | end
215 | end
216 |
217 | it "should allow list indexing" do
218 | array = described_class.new(AnArray)
219 | array[1].rubify.should == AnArray[1]
220 | end
221 |
222 | it "should allow dict access" do
223 | dict = described_class.new(AHash)
224 | key = AConvertedHash.keys[0]
225 | dict[key].rubify.should == AConvertedHash[key]
226 | end
227 |
228 | it "should allow list index assignment" do
229 | array = described_class.new(AnArray)
230 | val = AString*2
231 | array[1] = val
232 | array[1].rubify.should == val
233 | end
234 |
235 | it "should allow dict value modification" do
236 | dict = described_class.new(AHash)
237 | key = AConvertedHash.keys[0]
238 | val = AString*2
239 | dict[key] = val
240 | dict[key].rubify.should == val
241 | end
242 |
243 | it "should allow creation of new dict key-val pair" do
244 | dict = described_class.new(AHash)
245 | key = AString*2
246 | dict[key] = AString
247 | dict[key].rubify.should == AString
248 | end
249 |
250 | it "should allow membership tests with include?" do
251 | list = described_class.new(AnArray)
252 | list.include?(AnArray[0]).should be_true
253 | end
254 | end
255 |
256 | it "should delegate object equality" do
257 | urllib_a = RubyPython.import('urllib')
258 | urllib_b = RubyPython.import('urllib')
259 | urllib_a.should == urllib_b
260 | end
261 | end
262 |
--------------------------------------------------------------------------------
/spec/rubypython_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/spec_helper.rb'
2 |
3 | describe RubyPython do
4 | describe "#import" do
5 | it "should handle multiple imports" do
6 | lambda do
7 | RubyPython.import 'cPickle'
8 | RubyPython.import 'urllib'
9 | end.should_not raise_exception
10 | end
11 |
12 | it "should propagate Python errors" do
13 | lambda do
14 | RubyPython.import 'nonExistentModule'
15 | end.should raise_exception(RubyPython::PythonError)
16 | end
17 |
18 | it "should return a RubyPyModule" do
19 | RubyPython.import('urllib2').should be_a(RubyPython::RubyPyModule)
20 | end
21 | end
22 | end
23 |
24 | describe RubyPython, :self_start => true do
25 |
26 | describe "#session" do
27 | it "should start interpreter" do
28 | RubyPython.session do
29 | cPickle = RubyPython.import "cPickle"
30 | cPickle.loads("(dp1\nS'a'\nS'n'\ns(I1\nS'2'\ntp2\nI4\ns.").rubify.should == {"a"=>"n", [1, "2"]=>4}
31 | end
32 | end
33 |
34 | it "should stop the interpreter" do
35 | RubyPython.session do
36 | cPickle = RubyPython.import "cPickle"
37 | end
38 |
39 | RubyPython.stop.should be_false
40 | end
41 | end
42 |
43 | describe "#run" do
44 | it "should start interpreter" do
45 | RubyPython.run do
46 | cPickle = import "cPickle"
47 | cPickle.loads("(dp1\nS'a'\nS'n'\ns(I1\nS'2'\ntp2\nI4\ns.").rubify.should == {"a"=>"n", [1, "2"]=>4}
48 | end
49 | end
50 |
51 | it "should stop the interpreter" do
52 | RubyPython.run do
53 | cPickle = import "cPickle"
54 | end
55 |
56 | RubyPython.stop.should be_false
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | begin
2 | require 'rspec'
3 | rescue LoadError
4 | require 'rubygems' unless ENV['NO_RUBYGEMS']
5 | require 'rspec'
6 | end
7 |
8 | dir = File.dirname(__FILE__)
9 |
10 | $:.unshift(File.join(dir, '..', 'lib'))
11 | require 'rubypython'
12 |
13 | module TestConstants
14 | AString = "STRING"
15 | AStringWithNULLs = "STRING\0WITH\0NULLS"
16 | AnInt = 1
17 | AChar = 'a'
18 | AFloat = 1.0
19 | AnArray = [AnInt, AChar, AFloat, AString]
20 | ATuple = RubyPython::Tuple.tuple(AnArray)
21 | # ATuple << AnInt << AChar << AFloat << AString
22 | ASym = :sym
23 | AHash = {
24 | AnInt => AnInt,
25 | AChar.to_sym => AChar,
26 | ASym => AFloat,
27 | AString => AString
28 | }
29 | AConvertedHash = Hash[*AHash.map do |k, v|
30 | key = k.is_a?(Symbol) ? k.to_s : k
31 | [key, v]
32 | end.flatten]
33 | AProc = Proc.new { |a1, a2| a1 + a2 }
34 | def self.a_method(a1, a2)
35 | a1 + a2
36 | end
37 | AMethod = method(:a_method)
38 | end
39 |
40 | def run_python_command(cmd)
41 | %x(python -c '#{cmd}').chomp
42 | end
43 |
44 | RSpec.configure do |config|
45 | if RUBY_VERSION < '1.9.2'
46 | config.filter_run_excluding :ruby_version => '1.9'
47 | end
48 |
49 | config.before(:all) do
50 | class RubyPython::RubyPyProxy
51 | [:should, :should_not, :class].each { |m| reveal(m) }
52 | end
53 | end
54 |
55 | config.before(:all) do
56 | RubyPython.start
57 |
58 | @sys = RubyPython.import 'sys'
59 | @sys.path.append File.join(dir, 'python_helpers')
60 | @objects = RubyPython.import 'objects'
61 | @basics = RubyPython.import 'basics'
62 | end
63 |
64 | config.before(:all, :self_start => true) do
65 | RubyPython.stop
66 | end
67 |
68 | config.after(:all) do
69 | RubyPython.stop
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/website/index.rhtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= title %>
9 |
10 |
11 |
18 |
19 |
20 |
contents
21 | <%= toc %>
22 |
23 |
24 | <%= body %>
25 |
26 |
27 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/website/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /softwaremap/ # This is an infinite virtual URL space
3 | Disallow: /statcvs/ # This is an infinite virtual URL space
4 | Disallow: /usage/ # This is an infinite virtual URL space
5 | Disallow: /wiki/ # This is an infinite virtual URL space
6 |
--------------------------------------------------------------------------------
/website/stylesheets/960.css:
--------------------------------------------------------------------------------
1 | /* Containers */
2 | .container_24 {
3 | margin-left: auto;
4 | margin-right: auto;
5 | width: 960px;
6 | }
7 |
8 | /* Grid >> Children (Alpha ~ First, Omega ~ Last) */
9 | .alpha {
10 | margin-left: 0 !important;
11 | }
12 |
13 | .omega {
14 | margin-right: 0 !important;
15 | }
16 |
17 | /* Grid >> Global */
18 | .grid_1,
19 | .grid_2,
20 | .grid_3,
21 | .grid_4,
22 | .grid_5,
23 | .grid_6,
24 | .grid_7,
25 | .grid_8,
26 | .grid_9,
27 | .grid_10,
28 | .grid_11,
29 | .grid_12,
30 | .grid_13,
31 | .grid_14,
32 | .grid_15,
33 | .grid_16,
34 | .grid_17,
35 | .grid_18,
36 | .grid_19,
37 | .grid_20,
38 | .grid_21,
39 | .grid_22,
40 | .grid_23,
41 | .grid_24 {
42 | display: inline;
43 | float: left;
44 | position: relative;
45 | margin-left: 10.0px;
46 | margin-right: 10.0px;
47 | }
48 |
49 | /* Grid >> 2 Columns */
50 | .container_24 .grid_1 {
51 | width: 20px;
52 | }
53 |
54 | .container_24 .grid_2 {
55 | width: 60px;
56 | }
57 |
58 | .container_24 .grid_3 {
59 | width: 100px;
60 | }
61 |
62 | .container_24 .grid_4 {
63 | width: 140px;
64 | }
65 |
66 | .container_24 .grid_5 {
67 | width: 180px;
68 | }
69 |
70 | .container_24 .grid_6 {
71 | width: 220px;
72 | }
73 |
74 | .container_24 .grid_7 {
75 | width: 260px;
76 | }
77 |
78 | .container_24 .grid_8 {
79 | width: 300px;
80 | }
81 |
82 | .container_24 .grid_9 {
83 | width: 340px;
84 | }
85 |
86 | .container_24 .grid_10 {
87 | width: 380px;
88 | }
89 |
90 | .container_24 .grid_11 {
91 | width: 420px;
92 | }
93 |
94 | .container_24 .grid_12 {
95 | width: 460px;
96 | }
97 |
98 | .container_24 .grid_13 {
99 | width: 500px;
100 | }
101 |
102 | .container_24 .grid_14 {
103 | width: 540px;
104 | }
105 |
106 | .container_24 .grid_15 {
107 | width: 580px;
108 | }
109 |
110 | .container_24 .grid_16 {
111 | width: 620px;
112 | }
113 |
114 | .container_24 .grid_17 {
115 | width: 660px;
116 | }
117 |
118 | .container_24 .grid_18 {
119 | width: 700px;
120 | }
121 |
122 | .container_24 .grid_19 {
123 | width: 740px;
124 | }
125 |
126 | .container_24 .grid_20 {
127 | width: 780px;
128 | }
129 |
130 | .container_24 .grid_21 {
131 | width: 820px;
132 | }
133 |
134 | .container_24 .grid_22 {
135 | width: 860px;
136 | }
137 |
138 | .container_24 .grid_23 {
139 | width: 900px;
140 | }
141 |
142 | .container_24 .grid_24 {
143 | width: 940px;
144 | }
145 |
146 | /* Prefix Extra Space >> 2 Columns */
147 | .container_24 .prefix_1 {
148 | padding-left: 40px;
149 | }
150 |
151 | .container_24 .prefix_2 {
152 | padding-left: 80px;
153 | }
154 |
155 | .container_24 .prefix_3 {
156 | padding-left: 120px;
157 | }
158 |
159 | .container_24 .prefix_4 {
160 | padding-left: 160px;
161 | }
162 |
163 | .container_24 .prefix_5 {
164 | padding-left: 200px;
165 | }
166 |
167 | .container_24 .prefix_6 {
168 | padding-left: 240px;
169 | }
170 |
171 | .container_24 .prefix_7 {
172 | padding-left: 280px;
173 | }
174 |
175 | .container_24 .prefix_8 {
176 | padding-left: 320px;
177 | }
178 |
179 | .container_24 .prefix_9 {
180 | padding-left: 360px;
181 | }
182 |
183 | .container_24 .prefix_10 {
184 | padding-left: 400px;
185 | }
186 |
187 | .container_24 .prefix_11 {
188 | padding-left: 440px;
189 | }
190 |
191 | .container_24 .prefix_12 {
192 | padding-left: 480px;
193 | }
194 |
195 | .container_24 .prefix_13 {
196 | padding-left: 520px;
197 | }
198 |
199 | .container_24 .prefix_14 {
200 | padding-left: 560px;
201 | }
202 |
203 | .container_24 .prefix_15 {
204 | padding-left: 600px;
205 | }
206 |
207 | .container_24 .prefix_16 {
208 | padding-left: 640px;
209 | }
210 |
211 | .container_24 .prefix_17 {
212 | padding-left: 680px;
213 | }
214 |
215 | .container_24 .prefix_18 {
216 | padding-left: 720px;
217 | }
218 |
219 | .container_24 .prefix_19 {
220 | padding-left: 760px;
221 | }
222 |
223 | .container_24 .prefix_20 {
224 | padding-left: 800px;
225 | }
226 |
227 | .container_24 .prefix_21 {
228 | padding-left: 840px;
229 | }
230 |
231 | .container_24 .prefix_22 {
232 | padding-left: 880px;
233 | }
234 |
235 | .container_24 .prefix_23 {
236 | padding-left: 920px;
237 | }
238 |
239 | /* Suffix Extra Space >> 2 Columns */
240 | .container_24 .suffix_1 {
241 | padding-right: 40px;
242 | }
243 |
244 | .container_24 .suffix_2 {
245 | padding-right: 80px;
246 | }
247 |
248 | .container_24 .suffix_3 {
249 | padding-right: 120px;
250 | }
251 |
252 | .container_24 .suffix_4 {
253 | padding-right: 160px;
254 | }
255 |
256 | .container_24 .suffix_5 {
257 | padding-right: 200px;
258 | }
259 |
260 | .container_24 .suffix_6 {
261 | padding-right: 240px;
262 | }
263 |
264 | .container_24 .suffix_7 {
265 | padding-right: 280px;
266 | }
267 |
268 | .container_24 .suffix_8 {
269 | padding-right: 320px;
270 | }
271 |
272 | .container_24 .suffix_9 {
273 | padding-right: 360px;
274 | }
275 |
276 | .container_24 .suffix_10 {
277 | padding-right: 400px;
278 | }
279 |
280 | .container_24 .suffix_11 {
281 | padding-right: 440px;
282 | }
283 |
284 | .container_24 .suffix_12 {
285 | padding-right: 480px;
286 | }
287 |
288 | .container_24 .suffix_13 {
289 | padding-right: 520px;
290 | }
291 |
292 | .container_24 .suffix_14 {
293 | padding-right: 560px;
294 | }
295 |
296 | .container_24 .suffix_15 {
297 | padding-right: 600px;
298 | }
299 |
300 | .container_24 .suffix_16 {
301 | padding-right: 640px;
302 | }
303 |
304 | .container_24 .suffix_17 {
305 | padding-right: 680px;
306 | }
307 |
308 | .container_24 .suffix_18 {
309 | padding-right: 720px;
310 | }
311 |
312 | .container_24 .suffix_19 {
313 | padding-right: 760px;
314 | }
315 |
316 | .container_24 .suffix_20 {
317 | padding-right: 800px;
318 | }
319 |
320 | .container_24 .suffix_21 {
321 | padding-right: 840px;
322 | }
323 |
324 | .container_24 .suffix_22 {
325 | padding-right: 880px;
326 | }
327 |
328 | .container_24 .suffix_23 {
329 | padding-right: 920px;
330 | }
331 |
332 | /* Push Space >> 2 Columns */
333 | .container_24 .push_1 {
334 | left: 40px;
335 | }
336 |
337 | .container_24 .push_2 {
338 | left: 80px;
339 | }
340 |
341 | .container_24 .push_3 {
342 | left: 120px;
343 | }
344 |
345 | .container_24 .push_4 {
346 | left: 160px;
347 | }
348 |
349 | .container_24 .push_5 {
350 | left: 200px;
351 | }
352 |
353 | .container_24 .push_6 {
354 | left: 240px;
355 | }
356 |
357 | .container_24 .push_7 {
358 | left: 280px;
359 | }
360 |
361 | .container_24 .push_8 {
362 | left: 320px;
363 | }
364 |
365 | .container_24 .push_9 {
366 | left: 360px;
367 | }
368 |
369 | .container_24 .push_10 {
370 | left: 400px;
371 | }
372 |
373 | .container_24 .push_11 {
374 | left: 440px;
375 | }
376 |
377 | .container_24 .push_12 {
378 | left: 480px;
379 | }
380 |
381 | .container_24 .push_13 {
382 | left: 520px;
383 | }
384 |
385 | .container_24 .push_14 {
386 | left: 560px;
387 | }
388 |
389 | .container_24 .push_15 {
390 | left: 600px;
391 | }
392 |
393 | .container_24 .push_16 {
394 | left: 640px;
395 | }
396 |
397 | .container_24 .push_17 {
398 | left: 680px;
399 | }
400 |
401 | .container_24 .push_18 {
402 | left: 720px;
403 | }
404 |
405 | .container_24 .push_19 {
406 | left: 760px;
407 | }
408 |
409 | .container_24 .push_20 {
410 | left: 800px;
411 | }
412 |
413 | .container_24 .push_21 {
414 | left: 840px;
415 | }
416 |
417 | .container_24 .push_22 {
418 | left: 880px;
419 | }
420 |
421 | .container_24 .push_23 {
422 | left: 920px;
423 | }
424 |
425 | /* Pull Space >> 2 Columns */
426 | .container_24 .pull_1 {
427 | right: 40px;
428 | }
429 |
430 | .container_24 .pull_2 {
431 | right: 80px;
432 | }
433 |
434 | .container_24 .pull_3 {
435 | right: 120px;
436 | }
437 |
438 | .container_24 .pull_4 {
439 | right: 160px;
440 | }
441 |
442 | .container_24 .pull_5 {
443 | right: 200px;
444 | }
445 |
446 | .container_24 .pull_6 {
447 | right: 240px;
448 | }
449 |
450 | .container_24 .pull_7 {
451 | right: 280px;
452 | }
453 |
454 | .container_24 .pull_8 {
455 | right: 320px;
456 | }
457 |
458 | .container_24 .pull_9 {
459 | right: 360px;
460 | }
461 |
462 | .container_24 .pull_10 {
463 | right: 400px;
464 | }
465 |
466 | .container_24 .pull_11 {
467 | right: 440px;
468 | }
469 |
470 | .container_24 .pull_12 {
471 | right: 480px;
472 | }
473 |
474 | .container_24 .pull_13 {
475 | right: 520px;
476 | }
477 |
478 | .container_24 .pull_14 {
479 | right: 560px;
480 | }
481 |
482 | .container_24 .pull_15 {
483 | right: 600px;
484 | }
485 |
486 | .container_24 .pull_16 {
487 | right: 640px;
488 | }
489 |
490 | .container_24 .pull_17 {
491 | right: 680px;
492 | }
493 |
494 | .container_24 .pull_18 {
495 | right: 720px;
496 | }
497 |
498 | .container_24 .pull_19 {
499 | right: 760px;
500 | }
501 |
502 | .container_24 .pull_20 {
503 | right: 800px;
504 | }
505 |
506 | .container_24 .pull_21 {
507 | right: 840px;
508 | }
509 |
510 | .container_24 .pull_22 {
511 | right: 880px;
512 | }
513 |
514 | .container_24 .pull_23 {
515 | right: 920px;
516 | }
517 |
518 | /* Clear Floated Elements */
519 | .clear {
520 | clear: both;
521 | display: block;
522 | overflow: hidden;
523 | visibility: hidden;
524 | width: 0;
525 | height: 0;
526 | }
527 |
528 | .clearfix:after {
529 | clear: both;
530 | content: ' ';
531 | display: block;
532 | font-size: 0;
533 | line-height: 0;
534 | visibility: hidden;
535 | width: 0;
536 | height: 0;
537 | }
538 |
539 | .clearfix {
540 | display: inline-block;
541 | }
542 |
543 | * html .clearfix {
544 | height: 1%;
545 | }
546 |
547 | .clearfix {
548 | display: block;
549 | }
550 |
--------------------------------------------------------------------------------
/website/stylesheets/border-radius.htc:
--------------------------------------------------------------------------------
1 | --Do not remove this if you are using--
2 | Original Author: Remiz Rahnas
3 | Original Author URL: http://www.htmlremix.com
4 | Published date: 2008/09/24
5 |
6 | Changes by Nick Fetchak:
7 | - IE8 standards mode compatibility
8 | - VML elements now positioned behind original box rather than inside of it - should be less prone to breakage
9 | Published date : 2009/11/18
10 |
11 |
12 |
13 |
143 |
144 |
--------------------------------------------------------------------------------
/website/stylesheets/screen.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #E1D1F1;
3 | font-family: "Georgia", sans-serif;
4 | font-size: 16px;
5 | line-height: 1.6em;
6 | padding: 1.6em 0 0 0;
7 | color: #333;
8 | }
9 |
10 | h1, h2, h3, h4, h5, h6 {
11 | font-family: 'Cabin', sans-serif;
12 | color: #444;
13 | }
14 |
15 | #header h1 {
16 | font-weight: normal;
17 | font-size: 4em;
18 | line-height: 0.8em;
19 | letter-spacing: -0.05em;
20 | margin: 5px;
21 | }
22 |
23 | #toc {
24 | line-height: 1.2em;
25 | font-size: 0.9em;
26 | }
27 |
28 | #toc li {
29 | line-height: 1.15em;
30 | }
31 |
32 | ul {
33 | list-style-type: square;
34 | }
35 |
36 | li {
37 | padding: 0;
38 | margin: 0;
39 | }
40 |
41 | a {
42 | color: #5E5AFF;
43 | font-weight: normal;
44 | text-decoration: underline;
45 | }
46 |
47 | .coda {
48 | text-align: right;
49 | color: #77f;
50 | font-size: smaller;
51 | }
52 |
53 | pre, code {
54 | font-family: 'Inconsolata', monospace;
55 | font-size: 90%;
56 | line-height: 1.4em;
57 | color: #ff8;
58 | background-color: #111;
59 | padding: 2px 10px 2px 10px;
60 | }
61 |
62 | .teardrop-corners {
63 | behavior: url(stylesheets/border-radius.htc);
64 | /*
65 | * -moz-border-radius: 10px;
66 | * -webkit-border-radius: 10px;
67 | * -khtml-border-radius: 10px;
68 | * border-radius: 10px;
69 | */
70 | -webkit-border-top-left-radius: 50px;
71 | -webkit-border-bottom-right-radius: 50px;
72 | -moz-border-radius-topleft: 50px;
73 | -moz-border-radius-bottomright: 50px;
74 | border-top-left-radius: 50px;
75 | border-bottom-right-radius: 50px;
76 | }
77 |
78 | #toc {
79 | border: 3px solid #141331;
80 | line-height: 1.2em !important;
81 | font-size: 0.8em !important;
82 | padding: 0;
83 | }
84 |
85 | #toc h1 {
86 | margin-left: 1em;
87 | }
88 |
89 | #toc li {
90 | line-height: 1.2em;
91 | }
92 |
93 | #version {
94 | text-align: right;
95 | font-family: 'Cabin', sans-serif;
96 | font-weight: normal;
97 | background-color: #B3ABFF;
98 | color: #141331;
99 | padding: 10px 20px 10px 20px;
100 | margin: 0 auto;
101 | margin-top: 15px;
102 | margin-bottom: 15px;
103 | border: 3px solid #141331;
104 | }
105 |
106 | #version .numbers {
107 | display: block;
108 | font-size: 4em;
109 | line-height: 0.8em;
110 | letter-spacing: -0.2ex;
111 | margin-bottom: 15px;
112 | }
113 |
114 | #version p {
115 | text-decoration: none;
116 | color: #141331;
117 | background-color: #B3ABFF;
118 | margin: 0;
119 | padding: 0;
120 | }
121 |
122 | #version a {
123 | text-decoration: none;
124 | color: #141331;
125 | background-color: #B3ABFF;
126 | }
127 |
128 | .clickable {
129 | cursor: pointer;
130 | cursor: hand;
131 | }
132 |
133 |
--------------------------------------------------------------------------------