Nonblocking Console.ipynb
226 lines
| 7.3 KiB
| text/plain
|
TextLexer
Jonathan Frederic
|
r14394 | { | |
"metadata": { | |||
"name": "" | |||
}, | |||
"nbformat": 3, | |||
"nbformat_minor": 0, | |||
"worksheets": [ | |||
{ | |||
"cells": [ | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
Jonathan Frederic
|
r14742 | "# Console related imports.\n", | |
Jonathan Frederic
|
r14394 | "from subprocess import Popen, PIPE\n", | |
"import fcntl\n", | |||
"import os\n", | |||
Jonathan Frederic
|
r14742 | "from IPython.utils.py3compat import bytes_to_str, string_types\n", | |
Jonathan Frederic
|
r14394 | "\n", | |
Jonathan Frederic
|
r14742 | "# Widget related imports.\n", | |
Jonathan Frederic
|
r14394 | "from IPython.html import widgets\n", | |
Jonathan Frederic
|
r14742 | "from IPython.display import display" | |
Jonathan Frederic
|
r14394 | ], | |
"language": "python", | |||
"metadata": {}, | |||
"outputs": [], | |||
Jonathan Frederic
|
r14743 | "prompt_number": 1 | |
Jonathan Frederic
|
r14394 | }, | |
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"Define function to run a process without blocking the input." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"def read_process(process, append_output):\n", | |||
" \"\"\" Try to read the stdout and stderr of a process and render it using \n", | |||
" the append_output method provided\n", | |||
" \n", | |||
" Parameters\n", | |||
" ----------\n", | |||
" process: Popen handle\n", | |||
" append_output: method handle\n", | |||
" Callback to render output. Signature of\n", | |||
" append_output(output, [prefix=])\"\"\"\n", | |||
" \n", | |||
" try:\n", | |||
" stdout = process.stdout.read()\n", | |||
" if stdout is not None and len(stdout) > 0:\n", | |||
Jonathan Frederic
|
r14413 | " append_output(stdout, prefix=' ')\n", | |
Jonathan Frederic
|
r14394 | " except:\n", | |
" pass\n", | |||
" \n", | |||
" try:\n", | |||
" stderr = process.stderr.read()\n", | |||
" if stderr is not None and len(stderr) > 0:\n", | |||
" append_output(stderr, prefix='ERR ')\n", | |||
" except:\n", | |||
" pass\n", | |||
"\n", | |||
"\n", | |||
"def set_pipe_nonblocking(pipe):\n", | |||
" \"\"\"Set a pipe as non-blocking\"\"\"\n", | |||
" fl = fcntl.fcntl(pipe, fcntl.F_GETFL)\n", | |||
" fcntl.fcntl(pipe, fcntl.F_SETFL, fl | os.O_NONBLOCK)\n", | |||
"\n", | |||
"\n", | |||
"kernel = get_ipython().kernel\n", | |||
"def run_command(command, append_output, has_user_exited=None):\n", | |||
" \"\"\"Run a command asyncronously\n", | |||
" \n", | |||
" Parameters\n", | |||
" ----------\n", | |||
" command: str\n", | |||
" Shell command to launch a process with.\n", | |||
" append_output: method handle\n", | |||
" Callback to render output. Signature of\n", | |||
" append_output(output, [prefix=])\n", | |||
" has_user_exited: method handle\n", | |||
" Check to see if the user wants to stop the command.\n", | |||
" Must return a boolean.\"\"\"\n", | |||
" \n", | |||
" # Echo input.\n", | |||
" append_output(command, prefix='>>> ')\n", | |||
" \n", | |||
" # Create the process. Make sure the pipes are set as non-blocking.\n", | |||
" process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)\n", | |||
" set_pipe_nonblocking(process.stdout)\n", | |||
" set_pipe_nonblocking(process.stderr)\n", | |||
" \n", | |||
" # Only continue to read from the command \n", | |||
" while (has_user_exited is None or not has_user_exited()) and process.poll() is None:\n", | |||
" read_process(process, append_output)\n", | |||
" kernel.do_one_iteration() # Run IPython iteration. This is the code that\n", | |||
" # makes this operation non-blocking. This will\n", | |||
" # allow widget messages and callbacks to be \n", | |||
" # processed.\n", | |||
" \n", | |||
" # If the process is still running, the user must have exited.\n", | |||
" if process.poll() is None:\n", | |||
" process.kill()\n", | |||
" else:\n", | |||
" read_process(process, append_output) # Read remainer\n", | |||
" \n", | |||
" \n", | |||
" \n", | |||
" " | |||
], | |||
"language": "python", | |||
"metadata": {}, | |||
"outputs": [], | |||
Jonathan Frederic
|
r14743 | "prompt_number": 2 | |
Jonathan Frederic
|
r14742 | }, | |
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"Create the console widgets without displaying them." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"console_container = widgets.ContainerWidget(visible=False)\n", | |||
"console_container.set_css('padding', '10px')\n", | |||
"\n", | |||
"console_style = {\n", | |||
" 'font-family': 'monospace',\n", | |||
" 'color': '#AAAAAA',\n", | |||
" 'background': 'black',\n", | |||
" 'width': '800px',\n", | |||
"}\n", | |||
"\n", | |||
Jonathan Frederic
|
r14834 | "output_box = widgets.TextareaWidget()\n", | |
Jonathan Frederic
|
r14742 | "output_box.set_css(console_style)\n", | |
"output_box.set_css('height', '400px')\n", | |||
"\n", | |||
Jonathan Frederic
|
r14834 | "input_box = widgets.TextWidget()\n", | |
Jonathan Frederic
|
r14742 | "input_box.set_css(console_style)\n", | |
"\n", | |||
"console_container.children = [output_box, input_box]" | |||
], | |||
"language": "python", | |||
"metadata": {}, | |||
"outputs": [], | |||
Jonathan Frederic
|
r14743 | "prompt_number": 3 | |
Jonathan Frederic
|
r14394 | }, | |
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"Hook the process execution methods up to our console widgets." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
Jonathan Frederic
|
r14413 | "\n", | |
"def append_output(output, prefix):\n", | |||
" if isinstance(output, string_types):\n", | |||
" output_str = output\n", | |||
" else:\n", | |||
" output_str = bytes_to_str(output)\n", | |||
" output_lines = output_str.split('\\n')\n", | |||
Jonathan Frederic
|
r14394 | " formatted_output = '\\n'.join([prefix + line for line in output_lines if len(line) > 0]) + '\\n'\n", | |
" output_box.value += formatted_output\n", | |||
" output_box.scroll_to_bottom()\n", | |||
" \n", | |||
"def has_user_exited():\n", | |||
" return not console_container.visible\n", | |||
"\n", | |||
"def handle_input(sender):\n", | |||
" sender.disabled = True\n", | |||
" try:\n", | |||
" command = sender.value\n", | |||
" sender.value = ''\n", | |||
" run_command(command, append_output=append_output, has_user_exited=has_user_exited)\n", | |||
" finally:\n", | |||
" sender.disabled = False\n", | |||
" \n", | |||
"input_box.on_submit(handle_input)" | |||
], | |||
"language": "python", | |||
"metadata": {}, | |||
"outputs": [], | |||
Jonathan Frederic
|
r14743 | "prompt_number": 4 | |
Jonathan Frederic
|
r14394 | }, | |
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
Jonathan Frederic
|
r14742 | "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." | |
Jonathan Frederic
|
r14394 | ] | |
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
Jonathan Frederic
|
r14742 | "toggle_button = widgets.ButtonWidget(description=\"Start Console\")\n", | |
Jonathan Frederic
|
r14834 | "def toggle_console(sender):\n", | |
Jonathan Frederic
|
r14742 | " console_container.visible = not console_container.visible\n", | |
" if console_container.visible:\n", | |||
" toggle_button.description=\"Stop Console\"\n", | |||
" input_box.disabled = False\n", | |||
" else:\n", | |||
" toggle_button.description=\"Start Console\"\n", | |||
"toggle_button.on_click(toggle_console)\n", | |||
"\n", | |||
Jonathan Frederic
|
r14394 | "display(toggle_button)\n", | |
"display(console_container)" | |||
], | |||
"language": "python", | |||
"metadata": {}, | |||
"outputs": [], | |||
Jonathan Frederic
|
r14743 | "prompt_number": 5 | |
Jonathan Frederic
|
r14394 | } | |
], | |||
"metadata": {} | |||
} | |||
] | |||
} |