with ssh() pt1: Capturing the body of a with statement

I've been advancing a lot with ayrton. A lot of things already work and I'm still toying with the final syntax, but this is not what I came to talk about.

Since its inception, I had one particular idea for a scripting language: the ability to connect through ssh to another machine and execute some code there, without a requirement that the code must be already a script in the remote machine, and that has sane ways to reference variables both local to the instance of the code running in the remote machine and variables local to the 'parent' script. This is more or less what I'm talking about:

ssh $user_machine '\
    cd /foo; \
    cd $(find . -type d -name "Fo-Obarbaz-*" | sort -u | tail -1); \
    file=$(ls -1 *_LOG); \
    echo "File: $file"; \
    cat $file' > local_file

This example is simple in the sense that it only references variable local to the remote execution; just try to imagine how much escaping you would need for referencing both types of variables.

Python3 has a very handy construct for this: context managers. Imagine that you could write:

with ssh (user_machine, _out=open (local_file, 'w+')):
    cd ('/foo')
    cd (tail (sort (find ('. -type d -name Fo-Obarbaz-*'), '-u') '-1'))
    file= ls ('-l', bash ('*_LOG'))
    print ("File: %s" % file)
    cat (file)

That's my current goal. So I set off to find how to achieve this. The first part of the journey is to find the body of the with statement. Suppose this simpler code:

# file with_ssh.ay
with ssh ('localhost'):
    print ('yes!')

Enter the ast module:

import ast

t= ast.parse (open ('with_ssh.ay').read ())
for node in ast.walk (t):
    if type (node)==ast.With and node.items[0].context_expr.func.id=='ssh':
        code= node.body

f= ast.Module(body=code)
f_c= compile (f, 'foo', 'exec')
exec (f_c)

This small piece of code prints yes! without a hitch. My work is done here...

... except that I still have to find how to send the code via ssh1, most probably with all its dependencies, probably check that the python version matches, compile it and execute it there. And chose a way to reference variables in the calling environment and/or transmit them. But that's for another post :)

This kind of AST manipulation could also allow me to properly implement |.


  1. Guess what: ast.AST's are pickle'able :)