Skip to content

Commit 4c9fed6

Browse files
committed
mostly complete implementation of --only-code-output
1 parent f6a9f03 commit 4c9fed6

9 files changed

Lines changed: 349 additions & 45 deletions

File tree

codebraid/code_chunks.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ def finalize_after_copy(self):
298298
code_chunk = self.code_chunk
299299
custom_options = self.custom_options
300300
if self['lang'] is None:
301+
self['inherited_lang'] = True
301302
self['lang'] = code_chunk.copy_chunks[0].options['lang']
302303
if code_chunk.inline:
303304
if code_chunk.command == 'paste' and 'show' not in custom_options:
@@ -335,6 +336,7 @@ def finalize_after_copy(self):
335336
_default_inline_options = {'complete': True,
336337
'example': False,
337338
'lang': None,
339+
'inherited_lang': False,
338340
'outside_main': False}
339341
_default_block_options = _default_inline_options.copy()
340342
_default_block_options.update({'code_first_number': 'next',
@@ -814,9 +816,9 @@ def __init__(self,
814816
if command == 'paste':
815817
if 'copy' not in custom_options:
816818
self.errors.append(message.SourceError('Command "paste" cannot be used without specifying a target via "copy"'))
817-
self.has_output = False
819+
self.needs_to_copy = True
818820
else:
819-
self.has_output = True # Whether need output from copying
821+
self.needs_to_copy = False
820822
if 'copy' in self.options:
821823
self.copy_chunks = []
822824

@@ -878,6 +880,18 @@ def code_str(self):
878880
return code
879881

880882

883+
@property
884+
def attr_hash(self):
885+
raise NotImplementedError
886+
887+
@property
888+
def code_hash(self):
889+
raise NotImplementedError
890+
891+
def only_code_output(self, format):
892+
raise NotImplementedError
893+
894+
881895
def finalize_after_copy(self):
882896
'''
883897
Finalize options. This can be redefined by subclasses so that they
@@ -926,7 +940,7 @@ def copy_code(self):
926940
if self.command == 'paste':
927941
if all(cc.command == 'code' for cc in copy_chunks):
928942
# When possible, simplify the copying resolution process
929-
self.has_output = True
943+
self.needs_to_copy = False
930944
self.code_start_line_number = copy_chunks[0].code_start_line_number
931945

932946

@@ -983,8 +997,16 @@ def copy_output(self):
983997
self.expr_lines = copy_chunks[0].expr_lines
984998
self.stdout_start_line_number = copy_chunks[0].stdout_start_line_number
985999
self.stderr_start_line_number = copy_chunks[0].stderr_start_line_number
986-
self.has_output = True
1000+
self.needs_to_copy = False
1001+
9871002

1003+
@property
1004+
def as_markup_lines(self):
1005+
raise NotImplementedError
1006+
1007+
@property
1008+
def as_example_markup_lines(self):
1009+
raise NotImplementedError
9881010

9891011
def layout_output(self, output_type, output_format, lines=None):
9901012
'''

codebraid/code_collections.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def finalize(self):
296296
else:
297297
msg = 'Final code chunk cannot have "complete" value "false"'
298298
last_cc.errors.append(message.SourceError(msg))
299-
if self.status.has_errors:
299+
if self.status.prevent_exec:
300300
# Hashes and line numbers are only needed if code will indeed be
301301
# executed. It is impossible to determine these in the case of
302302
# errors like copy errors which leave code for one or more chunks

codebraid/codeprocessors/base.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def __init__(self,
4848
origin_paths_for_cache: Optional[List[pathlib.Path]],
4949
code_defaults: Optional[Dict],
5050
session_defaults: Optional[Dict],
51+
only_code_output: bool,
5152
progress: Progress):
5253
self.code_chunks = code_chunks
5354
self.cross_origin_sessions = cross_origin_sessions
@@ -62,6 +63,7 @@ def __init__(self,
6263
self._origin_paths_for_cache_as_strings = [p.as_posix() for p in origin_paths_for_cache]
6364
self.code_defaults = code_defaults
6465
self.session_defaults = session_defaults
66+
self._only_code_output = only_code_output
6567
self._progress = progress
6668

6769
self._old_cache_index: Optional[Dict] = None
@@ -90,7 +92,7 @@ def exit_code(self) -> int:
9092
if any(s.status.prevent_exec for s in self._sessions.values()):
9193
code ^= 0b00000100
9294
if (any(s.status.has_errors and not s.status.prevent_exec for s in self._sessions.values()) or
93-
any(s.status.has_errors and not s.status.prevent_exec for s in self._sources.values())):
95+
any(s.status.has_errors for s in self._sources.values())):
9496
code ^= 0b00001000
9597
if any(s.status.has_warnings for s in self._sessions.values()):
9698
code ^= 0b00010000
@@ -110,6 +112,9 @@ def process(self):
110112
self._resolve_code_copying()
111113
self._create_sessions_and_sources()
112114
self._prep_cache()
115+
if self._only_code_output and self._cached_sessions:
116+
# Resolve output copying as early as possible
117+
self._resolve_output_copying(from_cache=True)
113118

114119

115120
def exec(self):
@@ -181,6 +186,7 @@ def _set_chunk_keys(self):
181186
'''
182187
Assign code chunk session/source keys.
183188
'''
189+
placeholder_lang_num = 0
184190
for cc in self.code_chunks:
185191
if cc.execute:
186192
if self.cross_origin_sessions:
@@ -193,6 +199,9 @@ def _set_chunk_keys(self):
193199
else:
194200
key = CodeKey(cc.options['lang'], cc.options['source'], 'source', cc.origin_name)
195201
cc.key = key
202+
if cc.options['inherited_lang']:
203+
cc.options['placeholder_lang'] = f'{placeholder_lang_num}_placeholder'
204+
placeholder_lang_num += 1
196205

197206

198207
def _index_named_code_chunks(self):
@@ -292,7 +301,7 @@ def _resolve_code_copying(self):
292301
unresolved_chunks = still_unresolved_chunks
293302
still_unresolved_chunks = []
294303

295-
def _resolve_output_copying(self):
304+
def _resolve_output_copying(self, *, from_cache: bool=False):
296305
'''
297306
For code chunks with copying, handle the output copying. The output
298307
copying for commands like "paste" must be handled after code is
@@ -303,11 +312,11 @@ def _resolve_output_copying(self):
303312
# dependencies; that's already been handled in
304313
# `_resolve_code_copying()`. Still need to check for errors that
305314
# prevent copying, since there can be runtime source errors or other
306-
# errors related to output. Code chunks with `.has_output == True`
307-
# are copying code-only code chunks and thus have no output for
308-
# copying.
315+
# errors related to output. Code chunks with
316+
# `.needs_to_copy == False` are copying code-only code chunks and thus
317+
# have no output for copying.
309318
unresolved_chunks = [cc for cc in self.code_chunks
310-
if cc.command == 'paste' and not cc.errors.prevent_exec and not cc.has_output]
319+
if cc.command == 'paste' and not cc.errors.prevent_exec and cc.needs_to_copy]
311320
still_unresolved_chunks = []
312321
while True:
313322
for cc in unresolved_chunks:
@@ -322,11 +331,13 @@ def _resolve_output_copying(self):
322331
traceback = '\n'.join(traceback_list)
323332
msg = f'Code chunk(s) have error(s) that prevent copying:\n{traceback}'
324333
cc.errors.append(message.SourceError(msg))
325-
elif any(not copied_cc.has_output for copied_cc in cc.copy_chunks):
334+
self._progress.source_chunk_complete(cc.source, chunk=cc)
335+
elif any(copied_cc.needs_to_copy for copied_cc in cc.copy_chunks):
326336
still_unresolved_chunks.append(cc)
327337
else:
328338
cc.copy_output()
329-
if not still_unresolved_chunks:
339+
self._progress.source_chunk_complete(cc.source, chunk=cc)
340+
if not still_unresolved_chunks or (from_cache and len(unresolved_chunks) == len(still_unresolved_chunks)):
330341
break
331342
unresolved_chunks = still_unresolved_chunks
332343
still_unresolved_chunks = []
@@ -347,6 +358,9 @@ def _create_sessions_and_sources(self):
347358
self._session_hash_root_sets[session.hash_root].add(session)
348359
for source in self._sources.values():
349360
source.finalize()
361+
for cc in source.code_chunks:
362+
if not cc.needs_to_copy or cc.errors.prevent_exec:
363+
self._progress.source_chunk_complete(cc.source, chunk=cc)
350364
self._progress.register_sessions(self._sessions.values())
351365
self._progress.register_sources(self._sources.values())
352366

codebraid/codeprocessors/exec_builtin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ def _process_code_chunk_output(self, output: bytes, *, code_chunk: CodeChunk, ou
256256
output_str = self._decode(output, output_type=output_type, code_chunk=code_chunk)
257257
if output_type == 'stderr':
258258
output_str = self._sync_stderr_or_compile_output(output_str, code_chunk=code_chunk)
259-
getattr(self.progress, f'chunk_{output_type}')(self.session, chunk=code_chunk, output=output_str)
259+
getattr(self.progress, f'session_chunk_{output_type}')(self.session, chunk=code_chunk, output=output_str)
260260
getattr(code_chunk, f'{output_type}_lines').extend(util.splitlines_lf(output_str))
261261

262262
def _run_line_number_to_origin(self, run_line_number: int) -> CodeLineOrigin | tuple[None, None]:
@@ -395,7 +395,7 @@ def process_match(match: re.Match):
395395
async def _sync_chunk_start_delims(self, code_chunk: CodeChunk):
396396
self._sync_chunk_start_delims_state[code_chunk.index] += 1
397397
if self._sync_chunk_start_delims_state[code_chunk.index] == 2:
398-
self.progress.chunk_start(self.session, chunk=code_chunk)
398+
self.progress.session_chunk_start(self.session, chunk=code_chunk)
399399
return
400400
self._sync_chunk_start_delims_waiting += 1
401401
while self._sync_chunk_start_delims_state[code_chunk.index] < 2:
@@ -413,7 +413,7 @@ async def _sync_chunk_start_delims(self, code_chunk: CodeChunk):
413413
async def _sync_chunk_end_delims(self, code_chunk: CodeChunk):
414414
self._sync_chunk_end_delims_state[code_chunk.index] += 1
415415
if self._sync_chunk_end_delims_state[code_chunk.index] == 2:
416-
self.progress.chunk_end(self.session, chunk=code_chunk)
416+
self.progress.session_chunk_end(self.session, chunk=code_chunk)
417417
return
418418
self._sync_chunk_end_delims_waiting += 1
419419
while self._sync_chunk_end_delims_state[code_chunk.index] < 2:

codebraid/codeprocessors/exec_jupyter.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,11 @@ async def exec(session: Session, *, cache_key_path: pathlib.Path, progress: Prog
135135
incomplete_cc_stack.append(cc)
136136
continue
137137
if not incomplete_cc_stack:
138-
progress.chunk_start(session, chunk=cc)
138+
progress.session_chunk_start(session, chunk=cc)
139139
cc_jupyter_id = kernel_client.execute(cc.code_str)
140140
else:
141141
incomplete_cc_stack.append(cc)
142-
progress.chunk_start(session, chunk=incomplete_cc_stack[0])
142+
progress.session_chunk_start(session, chunk=incomplete_cc_stack[0])
143143
cc_jupyter_id = kernel_client.execute('\n'.join(icc.code_str for icc in incomplete_cc_stack))
144144
deadline = time.monotonic() + session.jupyter_timeout
145145
while True:
@@ -178,16 +178,16 @@ async def exec(session: Session, *, cache_key_path: pathlib.Path, progress: Prog
178178
cc.rich_output.append(rich_output)
179179
rich_output_text = kernel_msg_content['data'].get('text/plain')
180180
if rich_output_text:
181-
progress.chunk_rich_output_text(session, chunk=cc, output=rich_output_text)
181+
progress.session_chunk_rich_output_text(session, chunk=cc, output=rich_output_text)
182182
if rich_output_files:
183-
progress.chunk_rich_output_files(session, chunk=cc, files=rich_output_files.values())
183+
progress.session_chunk_rich_output_files(session, chunk=cc, files=rich_output_files.values())
184184
elif kernel_msg_type == 'stream':
185185
if kernel_msg_content['name'] == 'stdout':
186186
cc.stdout_lines.extend(util.splitlines_lf(kernel_msg_content['text']))
187-
progress.chunk_stdout(session, chunk=cc, output=kernel_msg_content['text'])
187+
progress.session_chunk_stdout(session, chunk=cc, output=kernel_msg_content['text'])
188188
elif kernel_msg_content['name'] == 'stderr':
189189
cc.stderr_lines.extend(util.splitlines_lf(_home_path_re.sub('~', kernel_msg_content['text'])))
190-
progress.chunk_stderr(session, chunk=cc, output=kernel_msg_content['text'])
190+
progress.session_chunk_stderr(session, chunk=cc, output=kernel_msg_content['text'])
191191
elif kernel_msg_type == 'error':
192192
kernel_msg_text = _ansi_color_escape_code_re.sub('', '\n'.join(kernel_msg_content['traceback']))
193193
kernel_msg_text = _home_path_re.sub('~', kernel_msg_text)
@@ -198,13 +198,11 @@ async def exec(session: Session, *, cache_key_path: pathlib.Path, progress: Prog
198198
cc.stderr_lines.extend(util.splitlines_lf(kernel_msg_text))
199199
cc.errors.append(message.StderrRunError(cc.stderr_lines))
200200
kernel_has_errors = True
201-
progress.chunk_stderr(session, chunk=cc, output=kernel_msg_text)
201+
progress.session_chunk_stderr(session, chunk=cc, output=kernel_msg_text)
202202
if not incomplete_cc_stack:
203-
progress.chunk_end(session, chunk=cc)
203+
progress.session_chunk_end(session, chunk=cc)
204204
else:
205-
# `progress` only takes first chunk but accounts for all
206-
# chunks that are grouped together
207-
progress.chunk_end(session, chunk=incomplete_cc_stack[0])
205+
progress.session_chunk_end(session, chunk=incomplete_cc_stack[-1])
208206
incomplete_cc_stack = []
209207
finally:
210208
kernel_client.stop_channels()

codebraid/converters/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,8 @@ def _process_code_chunks(self):
263263
origin_paths_for_cache=self.origin_paths_for_cache,
264264
code_defaults=self.code_defaults,
265265
session_defaults=self.session_defaults,
266-
progress=self._progress
266+
progress=self._progress,
267+
only_code_output=self.only_code_output,
267268
)
268269
self.code_processor.process()
269270

0 commit comments

Comments
 (0)