├── .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…Objects 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 | --------------------------------------------------------------------------------