libtclpy
========

This is a Tcl extension to effortlessly to call bidirectionally between Tcl and
Python, targeting Tcl >= 8.5 and Python 3.6+

The extension is available under the 3-clause BSD license (see "LICENSE").

Tcl users may also want to consider using
[pyman](http://chiselapp.com/user/gwlester/repository/pyman/home), a Tcl package
that provides a higher level of abstraction on top of tclpy.

USAGE
-----

You can import the libtclpy in either a Tcl or Python parent interpreter. Doing
so will initialise an interpreter for the other language and insert all
libtclpy methods. This means you can call backwards and forwards between
interpreters.

*FROM TCL*

General notes:
 - Unless otherwise noted, 'interpreter' refers to the python interpreter.
 - All commands are run in the context of a single interpreter session. Imports,
   function definitions and variables persist.
 - Exceptions in the python interpreter will return a stack trace of the python
   code that was executing. If the exception continues up the stack, the tcl
   stack trace will be appended to it.
   They may be masked (as per tcl stack traces) with catch.

Reference:
 - `py call ?obj.?func ?arg ...?`
   - `takes: name of a python function`
   - `returns: return value of function with the first appropriate conversion
      applied from the list below:`
     - `None is converted to an empty string`
     - `True is converted to 1`
     - `False is converted to 0`
     - `Python 'str' objects are considered to be byte arrays`
     - `Python 'unicode' objects are considered to be unicode strings`
     - `Python 'number' objects are converted to base 10 if necessary`
     - `Python mapping objects (supporting key-val mapping, e.g. python dicts)
        are converted to tcl dicts`
     - `Python sequence objects (supporting indexing, e.g. python lists) are
        converted to tcl lists`
     - `Otherwise, the str function is applied to the python object`
   - `side effects: executes function`
   - `func` may be a dot qualified name (i.e. object or module method)
 - `py eval evalString`
   - `takes: string of valid python code`
   - `returns: nothing`
   - `side effects: executes code in the python interpreter`
   - **Do not use with substituted input**
   - `evalString` may be any valid python code, including semicolons for single
     line statements or (non-indented) multiline blocks
   - errors reaching the python interpreter top level are printed to stderr
 - `py import module`
   - `takes: name of a python module`
   - `returns: nothing`
   - `side effects: imports named module into globals of the python interpreter`
   - the name of the module may be of the form module.submodule

example tclsh session:

```
% load ./libtclpy.so
%
% py eval {def mk(dir): os.mkdir(dir)}
% py eval {def rm(dir): os.rmdir(dir); return 15}
% py import os
% set a [py eval {print "creating 'testdir'"; mk('testdir')}]
creating 'testdir'
% set b [py call rm testdir]
15
%
% py import StringIO
% py eval {sio = StringIO.StringIO()}
% py call sio.write someinput
% set c [py call sio.getvalue]
someinput
%
% py eval {divide = lambda x: 1.0/int(x)}
% set d [py call divide 16]
0.0625
% list [catch {py call divide 0} err] $err
1 {ZeroDivisionError: float division by zero
  File "<string>", line 1, in <lambda>
----- tcl -> python interface -----}
%
% py import json
% py eval {
def jobj(*args):
    d = {}
    for i in range(len(args)/2):
        d[args[2*i]] = args[2*i+1]
    return json.dumps(d)
}
% set e [dict create]
% dict set e {t"est} "11{24"
t\"est 11\{24
% dict set e 6 5
t\"est 11\{24 6 5
% set e [py call jobj {*}$e]
{"t\"est": "11{24", "6": "5"}
%
% py import sqlite3
% py eval {b = sqlite3.connect(":memory:").cursor()}
% py eval {def exe(sql, *args): b.execute(sql, args)}
% py call exe "create table x(y integer, z integer)"
% py call exe "insert into x values (?,?)" 1 5
% py call exe "insert into x values (?,?)" 7 9
% py call exe "select avg(y), min(z) from x"
% py call b.fetchone
4.0 5
% py call exe "select * from x"
% set f [py call b.fetchall]
{1 5} {7 9}
%
% puts "a: $a, b: $b, c: $c, d: $d, e: $e, f: $f"
a: , b: 15, c: someinput, d: 0.0625, e: {"t\"est": "11{24", "6": "5"}, f: {1 5} {7 9}
```

*FROM PYTHON*

Reference:
 - `tclpy.eval(evalstring)`
   - `takes: string of valid Tcl code`
   - `returns: the final return value`
   - `side effects: executes code in the Tcl interpreter`
   - **Do not use with substituted input**
   - `evalString` may be any valid Tcl code, including semicolons for single
     line statements or multiline blocks
   - errors reaching the Tcl interpreter top level are raised as an exception

example python session:

```
>>> import tclpy
>>> a = tclpy.eval('list 1 [list 2 4 5] 3')
>>> print a
1 {2 4 5} 3
```

UNIX BUILD
----------

It is assumed that you
 - have got the repo (either by `git clone` or a tar.gz from the releases page).
 - have updated your package lists.

The build process fairly simple:
 - make sure `make` and `gcc` are installed.
 - make sure you can run `python-config` and have the Python headers available
   (usually installed by the Python development package for your distro).
 - locate the tclConfig.sh file and make sure you have the Tcl headers available
   (usually installed by the Tcl development package for your distro).
 - run `make`
   - specifying the tclConfig.sh path if not `/usr/lib/tclConfig.sh`
     (`TCLCONFIG=/path/to/tclConfig.sh`).
   - disabling tcl stubs if you wish to use Python as the parent interpreter
     (`TCL_STUBS=0`). Note this then requires compilation per Tcl interpreter.

On Ubuntu the default tclConfig.sh path is correct:

	$ sudo apt-get install -y python-dev tcl-dev
	$ cd libtclpy
	$ make

For other distros you may need give the path of tclConfig.sh. E.g. CentOS 6.5:

	$ sudo yum install -y python-devel tcl-devel make gcc
	$ cd libtclpy
	$ make TCLCONFIG=/usr/lib64/tclConfig.sh

Now try it out:

	$ TCLLIBPATH=. tclsh
	% package require tclpy
	0.3
	% py import random
	% py call random.random
	0.507094977417

TESTS
-----

Run the tests with

	$ make test

GOTCHAS
-------

1. Be very careful when putting unicode characters into a inside a `py eval`
call - they are decoded by the tcl parser and passed as literal bytes
to the python interpreter. So if we directly have the character "ಠ", it is
decoded to a utf-8 byte sequence and becomes u"\xe0\xb2\xa0" (where the \xXY are
literal bytes) as seen by the Python interpreter.
2. Escape sequences (e.g. `\x00`) inside py eval may be interpreted by tcl - use
{} quoting to avoid this.

TODO
----

In order of priority:

 - allow python to call back into tcl
 - allow compiling on Windows
 - `py call -types [list t1 ...] func ?arg ...? : ?t1 ...? -> multi`
   (polymorphic args, polymorphic return)
 - unicode handling (exception messages, fn param, returns from calls...AS\_STRING is bad)
 - allow statically compiling python into tclpy
   - http://pkaudio.blogspot.co.uk/2008/11/notes-for-embedding-python-in-your-cc.html
   - https://github.com/albertz/python-embedded
   - https://github.com/zeha/python-superstatic
   - http://www.velocityreviews.com/forums/t741756-embedded-python-static-modules.html
   - http://christian.hofstaedtler.name/blog/2013/01/embedding-python-on-win32.html
   - http://stackoverflow.com/questions/1150373/compile-the-python-interpreter-statically
 - allow statically compiling
 - check threading compatibility
 - let `py eval` work with indented multiline blocks
 - `py import ?-from module? module : -> nil`
 - return the short error line in the catch err variable and put the full stack
   trace in errorInfo
 - py call of non-existing function says raises attribute err, should be a
   NameError
 - make `py call` look in the builtins module - http://stackoverflow.com/a/11181607
 - all TODOs
