Putting part 1 and part 2 together is not much more effort. We already know how
to find the body of a `with` statement, and we already know how to compile it,
transfer it, and execute it remotely. Putting them together looks like[^1]:

```python
    def __enter__ (self):
        file, line= traceback.extract_stack (limit=2)[0][:2]
        code= ast.parse (open (file).read ())

        found= None
        for node in ast.walk (code):
            if type (node)==ast.With:
                if node.items[0].context_expr.func.id=='ssh':
                   if node.lineno==line:
                       found= ast.Module(body=node.body)
                       break

        if found is not None:
            data= pickle.dumps (found)
            print (ast.dump (found))

            self.client= paramiko.SSHClient ()
            self.client.load_host_keys (bash ('~/.ssh/known_hosts')[0])
            self.client.connect (*self.args, **self.kwargs)

            command= '''python3 -c "import pickle
 from ast import Module, Assign, Name, Store, Call, Load, Expr
 import sys
 c= pickle.loads (sys.stdin.buffer.read (%d))
 code= compile (c, 'remote', 'exec')
 exec (code)"''' % len (data)

           (i, o, e)= self.client.exec_command (command)
           i.write (data)
           return (i, o, e)
       else:
           raise BodyNotFoundError (file, line, code)
```

There are two complications that arise. One is already fixed in that code: to
detect from the current entering into a context (the execution of the above method
`__enter__()`) what file and line are we being executed. This is solved in the first
line with `extract_stack()` from the `traceback` module. The only difference with
the original body extraction mechanism is that we also check that we're in the right
line number. Just in case, there is an exception when we don't manage to
find the original code.

The second complication is... well, more complicated. We successfully execute the
body in the remote and we're amused that it even works. But here's the hitch:
the body is also executed locally. This is annoying.

This means that we have to not only manage to find the body of the `with` statement
to execute it remotely, we have to make sure that it is not executed locally. In
other words, we have to locally replace it with innocuous code, like `pass`.

Luckily, `ayrton` is already loading and compiling the script's code before executing it.
Adding a step that somehow saves the body of all `with ssh()` statements but also
replaces them with `pass` should be easy. In fact, it's disappointingly easy:

```python
class CrazyASTTransformer (ast.NodeTransformer):

    def visit_With (self, node):
        call= node.items[0].context_expr
        if call.func.id=='ssh':
            m= Module (body=node.body)
            data= pickle.dumps (m)
            s= Bytes (s=data)
            s.lineno= node.lineno
            s.col_offset= node.col_offset
            call.args.insert (0, s)

            p= Pass ()
            p.lineno= node.lineno+1
            p.col_offset= node.col_offset+4
            node.body= [p]
```

This time I'm using a `NodeTransformer` for the task. I'm simply taking the body,
wrapping it around a `Module`, pickling that, creating a new `Bytes` object with
that pickle and prepending it to the arguments of `ssh()`. On the other hand, I'm
replacing the whole body with a `pass` statement. So:

```python
with ssh (...):
    <body>
```

Becomes:

```python
with ssh (pickle.dumps ( Module (body=Bytes (s=<body>)) ), ...):
    pass
```

Easy, right? Back to the context manager, its constructor now takes the pickle of the code to
execute remotely as the fist argument, and the `__enter__()` method now does not
have to look for the code anymore.

There is one more complication that I want to address in this post, so I can more
or less finish with all this. `paramiko`'s `SSHClient.exec_command()` method
returns a sequence of 3 objects, that represent the stdin, out and err for the
remote. It would be nice if we could locally refer to them so we can interact with
the remote; particularly, get it's output. This means that somehow we have to
manage to capture that sequence and bind it to a local name before it's too late.
There is no obvious answer for this, specially because it means that I have to
create a local name, or take it from somewhere, in such a way that, either it doesn't
clash with the local environment, or the user expects it in a particular name.

So I more or less chose the latter. I'm extending the construct in such a way
that if we write `with ssh() as foo: ...`, that sequence ends in `foo` and you
can use it after the `with`'s body. So instead of the `pass` statement for local
execution, I want that sequence assigned to `foo`. For that, we we'll need a
random variable that will replace `foo` in the `as foo` part, and replace `pass`
with `foo= <random_var>`. It complicates things a little, but nothing really otherworldly :)

```python
            # take the `as foo`, make it `with ssh() as <random>: foo= <random>`
            # so we can use foo locally
            local_var= node.items[0].optional_vars.id
            remote_var= random_var ()

            # ` ... as <random>`
            node.items[0].optional_vars.id= remote_var

            # add `foo= <random>` to the body
            last_lineno= node.body[-1].lineno
            col_offset= node.body[0].col_offset

            target= Name(id=local_var, ctx=Store())
            target.lineno= last_lineno+1
            target.col_offset= col_offset

            value= Name(id=remote_var, ctx=Load())
            value.lineno= last_lineno+1
            # this is a little AR :)
            value.col_offset= col_offset+len (local_var)+ len ('= ')

            ass= Assign (targets=[target], value=value)
            ass.lineno= last_lineno+1
            ass.col_offset= col_offset

            node.body= [ ass ]
```

Just to be clear, the final code looks like:

```python
with ssh (pickle.dumps ( Module (body=Bytes (s=<body>)) ), ...) as <random_var>:
    foo= <random_var>
```

The next step is to be able to pass the `locals()` to the remote so it can access
the local values.

[^1]: One note here: I finally managed to make `paramiko` behave in Python3, so
      this code is slightly different from the one in the previous post.

