##// END OF EJS Templates
Add Markdown to the list of downloadable nbconverted formats....
Add Markdown to the list of downloadable nbconverted formats. Seem it was just forgotten. The list was not auto generated on purpose IIRC, as some format, like TeX were deemed not useful in menu.

File last commit:

r20278:8f4dcac7
r20542:bfd58b1f
Show More
Nonblocking Console.ipynb
239 lines | 7.2 KiB | text/plain | TextLexer
In [ ]:
# Console related imports.
from subprocess import Popen, PIPE
import os
from IPython.utils.py3compat import bytes_to_str, string_types

# Widget related imports.
from IPython.html import  widgets
from IPython.display import display

Define function to run a process without blocking the input.

In [ ]:
def read_process(process, append_output):
    """ Try to read the stdout and stderr of a process and render it using 
    the append_output method provided
    
    Parameters
    ----------
    process: Popen handle
    append_output: method handle
        Callback to render output.  Signature of
        append_output(output, [prefix=])"""
    
    try:
        stdout = process.stdout.read()
        if stdout is not None and len(stdout) > 0:
            append_output(stdout, prefix='    ')
    except:
        pass
        
    try:
        stderr = process.stderr.read()
        if stderr is not None and len(stderr) > 0:
            append_output(stderr, prefix='ERR ')
    except:
        pass


def set_pipe_nonblocking(pipe):
    """Set a pipe as non-blocking"""
    try:
        import fcntl
        fl = fcntl.fcntl(pipe, fcntl.F_GETFL)
        fcntl.fcntl(pipe, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    except:
        pass

kernel = get_ipython().kernel
def run_command(command, append_output, has_user_exited=None):
    """Run a command asyncronously
    
    Parameters
    ----------
    command: str
        Shell command to launch a process with.
    append_output: method handle
        Callback to render output.  Signature of
        append_output(output, [prefix=])
    has_user_exited: method handle
        Check to see if the user wants to stop the command.
        Must return a boolean."""
    
    # Echo input.
    append_output(command, prefix='>>> ')
    
    # Create the process.  Make sure the pipes are set as non-blocking.
    process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
    set_pipe_nonblocking(process.stdout)
    set_pipe_nonblocking(process.stderr)
    
    # Only continue to read from the command 
    while (has_user_exited is None or not has_user_exited()) and process.poll() is None:
        read_process(process, append_output)
        kernel.do_one_iteration() # Run IPython iteration.  This is the code that
                                  # makes this operation non-blocking.  This will
                                  # allow widget messages and callbacks to be 
                                  # processed.
        
    # If the process is still running, the user must have exited.
    if process.poll() is None:
        process.kill()
    else:
        read_process(process, append_output) # Read remainer
        
    
        
        

Create the console widgets without displaying them.

In [ ]:
console_container = widgets.VBox(visible=False)
console_container.padding = '10px'

output_box = widgets.Textarea()
output_box.height = '400px'
output_box.font_family = 'monospace'
output_box.color = '#AAAAAA'
output_box.background_color = 'black'
output_box.width = '800px'

input_box = widgets.Text()
input_box.font_family = 'monospace'
input_box.color = '#AAAAAA'
input_box.background_color = 'black'
input_box.width = '800px'

console_container.children = [output_box, input_box]

Hook the process execution methods up to our console widgets.

In [ ]:
def append_output(output, prefix):
    if isinstance(output, string_types):
        output_str = output
    else:
        output_str = bytes_to_str(output)
    output_lines = output_str.split('\n')
    formatted_output = '\n'.join([prefix + line for line in output_lines if len(line) > 0]) + '\n'
    output_box.value += formatted_output
    output_box.scroll_to_bottom()
    
def has_user_exited():
    return not console_container.visible

def handle_input(sender):
    sender.disabled = True
    try:
        command = sender.value
        sender.value = ''
        run_command(command, append_output=append_output, has_user_exited=has_user_exited)
    finally:
        sender.disabled = False
        
input_box.on_submit(handle_input)

Create the button that will be used to display and hide the console. Display both the console container and the new button used to toggle it.

In [ ]:
toggle_button = widgets.Button(description="Start Console")
def toggle_console(sender):
    console_container.visible = not console_container.visible
    if console_container.visible:
        toggle_button.description="Stop Console"
        input_box.disabled = False
    else:
        toggle_button.description="Start Console"
toggle_button.on_click(toggle_console)

display(toggle_button)
display(console_container)