Skip to content

Commit

Permalink
added command-line option --live-output (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
gpoore committed May 1, 2021
1 parent 1a80fe8 commit c57d451
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 19 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
eliminates sync failure in the form of `StopIteration` errors (#36, #38,
#44). Synchronization will now be faster and will not raise errors, but
may also be less accurate occasionally.

* Option `live_output` is now compatible with Jupyter kernels (#21).

* Added command-line option `--live-output`. This changes the default
`live_output` value for all sessions to `true` (#21).



## v0.5.0 (2021-02-28)
Expand Down
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ output.
### More about key features

*Easy debugging* — By default, stderr is shown automatically in the document
whenever there is an error, right next to the code that caused it.
whenever there is an error, right next to the code that caused it. It is also
possible to monitor code output in real time during execution via
`--live-output`.

*Simple language support* — Codebraid supports Jupyter kernels. It also has a
built-in system for executing code. Adding support for a new language with
Expand Down Expand Up @@ -198,6 +200,16 @@ needed in Markdown documents, this can be accomplished by piping the output of
`codebraid` through `pandoc`.


## Additional non-Pandoc command-line options

* `--live-output` — Show code output (stdout and stderr) live in the
terminal during code execution. For Jupyter kernels, also show errors and
a summary of rich output. Output still appears in the document as normal.

Individual sessions can override this by setting `live_output=false` in the
document.


## Caching

By default, code output is cached, and code is only re-executed when it is
Expand Down Expand Up @@ -330,7 +342,12 @@ session is in use).
* `live_output`={`true`, `false`} — Show code output (stdout and stderr) live
in the terminal during code execution. For Jupyter kernels, also show
errors and a summary of rich output. Output still appears in the document
as normal.
as normal. Showing output can also be enabled via the command-line option
`--live-output`.

When `live_output=false` is set for a session, this setting takes precedence
over the command-line option `--live-output`, and output will not be shown
for that session.

All output is written to stderr, so stdout only contains the document when
`--output` is not specified. Output is interspersed with delimiters marking
Expand Down
17 changes: 14 additions & 3 deletions codebraid/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ def print_help(self):
finally:
sys.stdout = original_stdout
try:
before, after = help_text.split('FILE', 1)
before, after = help_text.split('[FILE', 1)
except ValueError:
print(help_text)
else:
indent = before.rsplit('\n', 1)[1]
help_text = before + '[<PANDOC OPTIONS>]\n' + indent + 'FILE' + after
help_text = before + '[<PANDOC OPTIONS>]\n' + indent + '[FILE' + after
help_text += ' [<PANDOC OPTIONS>]\n'
help_text += indent + 'See output of "pandoc --help"\n'
print(help_text)
Expand Down Expand Up @@ -72,6 +72,11 @@ def print_help(self):
'(a cache directory may still be created for use with temporary files)')
parser_pandoc.add_argument('--cache-dir',
help='Location for caching code output (default is "_codebraid" in document directory)')
parser_pandoc.add_argument('--live-output', action='store_true',
help='Show code output (stdout and stderr) live in the terminal during code execution. '
'For Jupyter kernels, also show errors and a summary of rich output. '
'Output still appears in the document as normal. '
'Individual sessions can override this by setting live_output=false in the document.')
parser_pandoc.add_argument('files', nargs='*', metavar='FILE',
help="Files (multiple files are allowed for formats supported by Pandoc)")
for opts_or_long_opt, narg in PANDOC_OPTIONS.items():
Expand Down Expand Up @@ -143,12 +148,18 @@ def pandoc(args):
else:
paths = args.files
strings = None

session_defaults = {}
if args.live_output:
session_defaults['live_output'] = args.live_output

converter = converters.PandocConverter(paths=paths,
strings=strings,
from_format=args.from_format,
pandoc_file_scope=args.pandoc_file_scope,
no_cache=args.no_cache,
cache_path=args.cache_dir)
cache_path=args.cache_dir,
session_defaults=session_defaults)

converter.code_braid()

Expand Down
33 changes: 22 additions & 11 deletions codebraid/codeprocessors/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import threading
import tempfile
import textwrap
from typing import Dict, Optional, Union
import time
import zipfile
from .. import err
Expand Down Expand Up @@ -130,7 +131,7 @@ class Session(object):
'''
Code chunks comprising a session.
'''
def __init__(self, session_key):
def __init__(self, session_key, *, session_defaults: Optional[Dict[str, Union[bool, str]]]):
self.code_processor = session_key[0]
self.lang = session_key[1]
self.name = session_key[2]
Expand All @@ -146,6 +147,7 @@ def __init__(self, session_key):
self.executable = None
self.jupyter_kernel = None
self.jupyter_timeout = None
self.live_output = False

self.code_options = None
self.code_chunks = []
Expand All @@ -168,6 +170,13 @@ def __init__(self, session_key):
self.post_run_errors = False
self.post_run_error_lines = None

if session_defaults is not None:
for k, v in session_defaults.items():
if hasattr(self, k):
setattr(self, k, v)
else:
raise AttributeError


def append(self, code_chunk):
'''
Expand All @@ -189,6 +198,9 @@ def append(self, code_chunk):
else:
self.executable = code_chunk.options['first_chunk_options'].get('executable')
self.repl = self.lang_def.repl
live_output = code_chunk.options['first_chunk_options'].get('live_output')
if live_output is not None:
self.live_output = live_output
elif code_chunk.options['first_chunk_options']:
invalid_options = ', '.join('"{0}"'.format(k) for k in code_chunk.options['first_chunk_options'])
del code_chunk.options['first_chunk_options']
Expand Down Expand Up @@ -307,7 +319,7 @@ class CodeProcessor(object):
from files for inclusion, or a combination of the two.
'''
def __init__(self, *, code_chunks, code_options, cross_source_sessions,
no_cache, cache_path, cache_key, cache_source_paths):
no_cache, cache_path, cache_key, cache_source_paths, session_defaults):
self.code_chunks = code_chunks
self.code_options = code_options
self.cross_source_sessions = cross_source_sessions
Expand All @@ -319,6 +331,7 @@ def __init__(self, *, code_chunks, code_options, cross_source_sessions,
self.cache_source_paths_as_strings = None
else:
self.cache_source_paths_as_strings = [p.as_posix() for p in cache_source_paths]
self.session_defaults = session_defaults

self.cache_key_path = cache_path / cache_key
self.cache_index_path = cache_path / cache_key / '{0}_index.zip'.format(cache_key)
Expand Down Expand Up @@ -575,7 +588,7 @@ def _resolve_output_copying(self):


def _create_sessions(self):
sessions = util.KeyDefaultDict(Session)
sessions = util.KeyDefaultDict(lambda x: Session(x, session_defaults=self.session_defaults))
for cc in self.code_chunks:
if cc.execute:
sessions[cc.key].append(cc)
Expand Down Expand Up @@ -965,8 +978,7 @@ def _run(self, session):
'hash': session.hash[:64],
}

live_output = session.code_chunks[0].options['first_chunk_options'].get('live_output', False)
if live_output:
if session.live_output:
subproc = self._subproc_live_output
else:
subproc = self._subproc_default
Expand Down Expand Up @@ -1357,7 +1369,6 @@ def shutdown_kernel():
chunk_runtime_source_error_dict[0].append('Jupyter kernel "{0}" timed out during startup:\n"{1}"'.format(kernel_name, e))
return

live_output = session.code_chunks[0].options['first_chunk_options'].get('live_output', False)
delim_border_n_chars = 60
delim_text = 'run: {lang}, session {session}\n'
delim_text = delim_text.format(lang=session.lang,
Expand All @@ -1372,7 +1383,7 @@ def shutdown_kernel():
total_chunks=len(session.code_chunks))
chunk_start_delim = '\n' + '='*delim_border_n_chars + '\n' + chunk_delim_text + '-'*delim_border_n_chars + '\n'
stage_start_delim = '\n' + '#'*delim_border_n_chars + '\n' + delim_text + '#'*delim_border_n_chars + '\n'
if live_output:
if session.live_output:
print(stage_start_delim, end='', file=sys.stderr, flush=True)

try:
Expand Down Expand Up @@ -1427,7 +1438,7 @@ def shutdown_kernel():
ro_path.write_bytes(base64.b64decode(data))
rich_output_files[mime_type] = ro_path.as_posix()
chunk_rich_output_dict[cc.session_output_index].append(rich_output)
if live_output:
if session.live_output:
delim = chunk_start_delim.format(source=cc.source_name,
line=cc.source_start_line_number,
chunk=cc.session_index+1,
Expand All @@ -1442,7 +1453,7 @@ def shutdown_kernel():
if msg_type == 'error':
msg = re.sub('\x1b.*?m', '', '\n'.join(msg_content['traceback']))
chunk_stderr_dict[cc.session_output_index].extend(util.splitlines_lf(msg))
if live_output:
if session.live_output:
delim = chunk_start_delim.format(source=cc.source_name,
line=cc.source_start_line_number,
chunk=cc.session_index+1,
Expand All @@ -1453,7 +1464,7 @@ def shutdown_kernel():
if msg_type == 'stream':
if msg_content['name'] == 'stdout':
chunk_stdout_dict[cc.session_output_index].extend(util.splitlines_lf(msg_content['text']))
if live_output:
if session.live_output:
delim = chunk_start_delim.format(source=cc.source_name,
line=cc.source_start_line_number,
chunk=cc.session_index+1,
Expand All @@ -1462,7 +1473,7 @@ def shutdown_kernel():
print(msg_content['text'], end='', file=sys.stderr, flush=True)
elif msg_content['name'] == 'stderr':
chunk_stderr_dict[cc.session_output_index].extend(util.splitlines_lf(msg_content['text']))
if live_output:
if session.live_output:
delim = chunk_start_delim.format(source=cc.source_name,
line=cc.source_start_line_number,
chunk=cc.session_index+1,
Expand Down
7 changes: 5 additions & 2 deletions codebraid/converters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import re
import sys
import textwrap
import typing; from typing import List, Optional, Sequence, Union
from typing import Dict, List, Optional, Sequence, Union
import zipfile
from .. import codeprocessors
from .. import err
Expand Down Expand Up @@ -1062,12 +1062,14 @@ def __init__(self, *,
expanduser: bool=False,
expandvars: bool=False,
from_format: Optional[str]=None,
session_defaults: Optional[Dict[str, Union[bool, str]]]=None,
synctex: bool=False):
if not all(isinstance(x, bool) for x in (cross_source_sessions, expanduser, expandvars)):
raise TypeError
self.cross_source_sessions = cross_source_sessions
self.expanduser = expanduser
self.expandvars = expandvars
self.session_defaults = session_defaults

if paths is not None and strings is None:
if isinstance(paths, str):
Expand Down Expand Up @@ -1211,7 +1213,8 @@ def _process_code_chunks(self):
no_cache=self.no_cache,
cache_path=self.cache_path,
cache_key=self.cache_key,
cache_source_paths=self.cache_source_paths)
cache_source_paths=self.cache_source_paths,
session_defaults=self.session_defaults)
cp.process()

def _postprocess_code_chunks(self):
Expand Down
2 changes: 1 addition & 1 deletion codebraid/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-

from .fmtversion import get_version_plus_info
__version__, __version_info__ = get_version_plus_info(0, 6, 0, 'dev', 2)
__version__, __version_info__ = get_version_plus_info(0, 6, 0, 'dev', 3)

0 comments on commit c57d451

Please sign in to comment.