.. @+leo-ver=5-thin
.. @+node:ekr.20100805165051.7169: * @file leoBridge.txt
.. @@language rest
.. @@tabwidth -4
.. @+all
.. @+node:ekr.20070317033759.1: ** @rst html\leoBridge.html
#######################################
Embedding Leo with the leoBridge module
#######################################

The leoBridge module allows complete access to all aspects of Leo from other
Python programs running independently of Leo. Let us call such a program a
**host** program. Using the leoBridge module, host programs can get access to:

- all of Leo's source code,
- the contents of any .leo file,
- the commander of any .leo file.

.. contents::
    :depth: 2

.. @+node:ekr.20070317033759.2: *3* @rst-no-head links
.. Links
.. @+node:ekr.20070317033759.3: *3* The basics
Host programs use the leoBridge module as follows::

    import leo.core.leoBridge as leoBridge
    
    controller = leoBridge.controller(gui='nullGui',
        loadPlugins=True,  # True: attempt to load plugins.
        readSettings=True, # True: read standard settings files.
        silent=False,      # True: don't print signon messages.
        verbose=False)     # True: print informational messages.

    g = controller.globals()
    c = controller.openLeoFile(path)

Let us look at these statements in detail. The first two statements
import the leoBridge module and create a **bridge controller**. In
effect, these statements embed an invisible copy of Leo into the host
program. This embedded copy of Leo uses a null gui, which simulates
all aspects of Leo's normal gui code without creating any screen
objects.

The statement::

    g = controller.globals()

provides access to Leo's leoGlobals module, and properly inits globals such as
g.app, g.app.gui, etc. *Host programs should not import leoGlobals directly*,
because doing so would not init the g.app object properly.

The statement::

    c = controller.openLeoFile(path)

invisibly opens the .leo file given by the path argument. This call returns a
completely standard Leo commander, properly inited. This is the big payoff from
the leoBridge module: the host program gets instant access to c.config.getBool,
etc. Do you see how sweet this is?

For example, the following script runs leo/test/leoBridgeTest.py outside of Leo.
leoBridgeTest.py uses the leoBridge module to run all unit tests in leo/test/unitTest.leo::

    import os,sys

    path = g.os_path_abspath(
        g.os_path_join(
            g.app.loadDir,'..','test','leoBridgeTest.py'))

    os.system('%s %s' % (sys.executable,path))

The file leo/test/test.leo contains the source code for leoBridgeTest.py.
Here it is, stripped of its sentinel lines::

    '''A program to run unit tests with the leoBridge module.'''

    import leo.core.leoBridge as leoBridge
    import leo.core.leoTest as leoTest

    def main ():
        tag = 'leoTestBridge'

        # Setting verbose=True prints messages that would be sent to the log pane.
        bridge = leoBridge.controller(gui='nullGui',verbose=False)
        if bridge.isOpen():
            g = bridge.globals()
            path = g.os_path_abspath(g.os_path_join(
                g.app.loadDir,'..','test','unitTest.leo'))
            c = bridge.openLeoFile(path)
            g.es('%s %s' % (tag,c.shortFileName()))
            runUnitTests(c,g)

        print tag,'done'

    def runUnitTests (c,g):
        nodeName = 'All unit tests' # The tests to run.
        try:
            u = leoTest.testUtils(c)
            p = u.findNodeAnywhere(nodeName)
            if p:
                g.es('running unit tests in %s...' % nodeName)
                c.selectPosition(p)
                c.debugCommands.runUnitTests()
                g.es('unit tests complete')
            else:
                g.es('node not found:' % nodeName)
        except Exception:
            g.es('unexpected exception')
            g.es_exception()
            raise

    if __name__ == '__main__':
        main()
.. @+node:ekr.20071210094621: *3* Running leoBridge from within Leo
This following is adapted from Terry Brown's entry in Leo's wiki.

You can not just run leoBridge from Leo, because the leoBridge module is designed
to run a separate copy of Leo. However, it is possible to run leoBridge from a
separate process. That turned out to be more, um, interesting than anticipated,
so I'm recording the results here.

The idea is that script A running in Leo (i.e. in a regular GUI Leo session)
calls script B through subprocess.Popen(), script B uses LeoBridge to do
something (parse unloaded Leo files), and returns the result to script A.
Passing the result back via the clipboard seemed like a possibility, but
XWindows / tcl/tk clipboard madness being what it is, that didn't seem to work.

First trick, calling script B from script A::

    import subprocess
    p = subprocess.Popen(('python',
        path_to_script_B,
        parameter_for_script_B,),
        stdout=subprocess.PIPE,
        env={'PYTHONPATH': g.app.loadDir,'USER': g.app.leoID},
    )
    p.wait()

Setting PYTHONPATH in the environment seemed like the easiest way to let
script B find leoBridge.py (which it needs to import).  But by setting the
env parameter you limit script B's environment to be **only** PYTHONPATH,
which causes leoBridge to fail because, in unix at least, it depends
on USER in the environment.  So you need to pass that through, too.

Now, because passing stuff back on the clipboard seems unreliable, at least
in XWindows, script B passes results back to script A via stdout (print),
but there's some Leo initialization chatter you want to avoid.  So put a
sentinel, 'START_CLIPBOARD', in the output, and collect it like this::

    response = p.stdout.readlines()
    while response and 'START_CLIPBOARD' not in response[0]:
        del response[0]
    del response[0]  # delete the sentinel as well
    response = ''.join(response)

This is the basic mechanism.  What I *actually* wanted to do was have script
B generate a branch of nodes and pass that back to script A for insertion in
the tree script A is running in.  That's relatively easy if you use::

    c.setCurrentPosition(pos_of_branch_to_return)
    c.copyOutline()
    print '<!-- START_CLIPBOARD -->'
    print g.app.gui.getTextFromClipboard()
    print '<!-- END_CLIPBOARD -->'

at the end of script B. Back in script A, after you've rebuilt
`response` as shown above, do::

    g.app.gui.replaceClipboardWith(response)
    c.pasteOutline()
.. @-all
.. @-leo
