#!/usr/bin/env python import json import optparse import subprocess import time import random import sys def send_to_screen(screen_name, arg0=None, command='stuff'): args = ['screen', '-S', screen_name, '-p', '0', '-X', command] if arg0 is not None: args.append(arg0) p = subprocess.Popen(args) p.communicate() def load_as_transcript(f): def add_block(clearScreen=False,noReturn=False): # If so, add the current block after trimming leading and trailing blank # lines. while current_block and current_block[0].isspace(): current_block.pop(0) while current_block and current_block[-1].isspace(): current_block.pop() # Add the block if it is non-empty. if current_block or comment_block: # Form the joined script.. script = ''.join(current_block) comment = ''.join(comment_block) # Strip off any trailing newline. if script.endswith('\n'): script = script[:-1] demo_script.append({ 'command' : script, 'comment' : comment, 'clearScreen' : clearScreen, 'noReturn' : noReturn }) # Clear the block and comment block. del current_block[:] del comment_block[:] f.seek(0) demo_script = [] current_block = [] comment_block = [] for ln in f: # Check if this is a block delimiter. if ln.strip() == '>>>': add_block() continue # Check if this is a block delimiter # but marked to clear the screen. if ln.strip() == '>>>CLEAR': add_block(clearScreen=True) continue # Check if this is a block delimiter # but marked to not insert a newline. if ln.strip() == '>>>NORETURN': add_block(noReturn=True) continue # Check if the line starts with a '#' # to indicate a prompter comment. if ln.startswith('#'): comment_block.append(ln) continue # Check for backspace. if ln.strip() == '>>>BACKSPACE': current_block.append('\b') continue # Otherwise, add the line to the current block. current_block.append(ln) return demo_script def main(): parser = optparse.OptionParser("""\ usage: %%prog [options] Run a command line demo script using 'screen'. The script file should be either a JSON document listing the commands to run, as in:: [ { "command" : "ls" }, { "command" : "echo Hello" } ] or, alternately, it should be a text filed delimited by '>>>', as in:: >>> ls >>> echo Hello where leading and trailing blank lines around each block will be discarded. The script requires the 'screen' session to have been started in another window, for example:: $ screen -S demo """) # Determine up front if the terminal supports color. hasColor = sys.stdout.isatty() def prompterPrint(string): if hasColor: attr = [ '32', '1' ] print '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string) else: print string def send(*args, **kwargs): return send_to_screen(opts.screen_name, *args, **kwargs) parser.add_option("-S", "--screen-name", dest="screen_name", metavar="NAME", help="name of the screen sesison to use [%default]", action="store", default="demo") opts, args = parser.parse_args() if len(args) == 1: path, = args else: parser.error("invalid number of arguments") # Read in the demo script. with open(path) as f: try: demo_script = json.load(f) except ValueError: demo_script = load_as_transcript(f) # Validate the entries. for item in demo_script: command = str(item.get('command')) if not isinstance(command, str): raise SystemError("error: invalid item in script: %r" % ( item,)) # Notify screen that we are starting the demo. raw_input('press enter to begin demo...') # Iterate over each command, sending it and waiting for user direction to # continue. for item in demo_script: shouldClear = bool(item['clearScreen']) noReturn = bool(item['noReturn']) command = str(item['command']) comment = str(item['comment']) if comment: prompterPrint(comment) if command: # Send the command slowly, as if it was typed. print "sending command: %r" % (command.replace('\n', ''),) for c in command: send(c) if c == "\n": time.sleep(0.1) else: time.sleep(random.random() * 0.03) if not noReturn: # Wait for user input, then send a return. raw_input('press enter to send return...') send('\n') if item is not demo_script[-1]: raw_input('press enter to continue to next command...') # Send the command slowly, as if it was typed. if shouldClear: print "clearing screen" send(command="clear") send('\n') # Notify screen that the demo is over, after a small wait period. # time.sleep(0.1) # send('Demo session is over.', command='wall') if __name__ == '__main__': main()