nbsync 0.3.11__py3-none-any.whl → 0.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
nbsync/markdown.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import shlex
3
4
  import textwrap
4
5
  from typing import TYPE_CHECKING, TypeAlias
5
6
 
@@ -40,14 +41,19 @@ def _convert_code_block_attrs(code_block: CodeBlock) -> CodeBlock | Image:
40
41
  if exec_ != "1" or not code_block.classes:
41
42
  return code_block
42
43
 
43
- if code_block.classes[0] != "python":
44
+ if code_block.classes[0] == "python":
45
+ classes = code_block.classes[1:]
46
+ elif code_block.classes[0] == "console":
47
+ classes = code_block.classes
48
+ else:
44
49
  return code_block
45
50
 
46
51
  del code_block.attributes["exec"]
52
+
47
53
  return Image(
48
54
  code_block.indent,
49
55
  "",
50
- code_block.classes[1:],
56
+ classes,
51
57
  code_block.attributes,
52
58
  code_block.source,
53
59
  url=".md",
@@ -62,7 +68,7 @@ def convert_image(image: Image, index: int | None = None) -> Iterator[Element]:
62
68
  raise ValueError(msg)
63
69
 
64
70
  image.identifier = image.identifier or f"image-nbsync-{index}"
65
- yield CodeBlock("", image.identifier, [], {}, image.source, image.url)
71
+ yield create_code_block(image)
66
72
  yield image
67
73
 
68
74
  elif image.identifier:
@@ -72,6 +78,29 @@ def convert_image(image: Image, index: int | None = None) -> Iterator[Element]:
72
78
  yield image.text
73
79
 
74
80
 
81
+ def create_code_block(image: Image) -> CodeBlock:
82
+ if "console" in image.classes:
83
+ source = create_subprocess_source(image.source)
84
+ else:
85
+ source = image.source
86
+
87
+ return CodeBlock("", image.identifier, [], {}, source, image.url)
88
+
89
+
90
+ def create_subprocess_source(source: str) -> str:
91
+ """Create a Python source that runs the command in subprocess."""
92
+ args = shlex.split(source)
93
+ if not args:
94
+ return ""
95
+
96
+ if args[0] in ["$", "#", ">"]:
97
+ args = args[1:]
98
+
99
+ return textwrap.dedent(f"""\
100
+ import subprocess
101
+ print(subprocess.check_output({args}, text=True).rstrip())""")
102
+
103
+
75
104
  SUPPORTED_EXTENSIONS = (".ipynb", ".md", ".py")
76
105
 
77
106
 
nbsync/notebook.py CHANGED
@@ -7,8 +7,6 @@ from typing import TYPE_CHECKING
7
7
 
8
8
  import nbstore.notebook
9
9
 
10
- from nbsync import logger
11
-
12
10
  if TYPE_CHECKING:
13
11
  from nbformat import NotebookNode
14
12
 
@@ -42,12 +40,8 @@ class Notebook:
42
40
  return nbstore.notebook.equals(self.nb, other.nb)
43
41
 
44
42
  def execute(self) -> float:
45
- try:
46
- start_time = time.perf_counter()
47
- nbstore.notebook.execute(self.nb)
48
- end_time = time.perf_counter()
49
- self.execution_needed = False
50
- return end_time - start_time
51
- except ModuleNotFoundError as e: # no cov
52
- logger.warning(e.msg)
53
- return 0
43
+ start_time = time.perf_counter()
44
+ nbstore.notebook.execute(self.nb)
45
+ end_time = time.perf_counter()
46
+ self.execution_needed = False
47
+ return end_time - start_time
nbsync/sync.py CHANGED
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  import re
4
4
  import textwrap
5
5
  from dataclasses import dataclass, field
6
- from pathlib import Path
7
6
  from typing import TYPE_CHECKING
8
7
 
9
8
  import nbformat
@@ -41,19 +40,29 @@ class Synchronizer:
41
40
  if url not in self.notebooks or not self.notebooks[url].equals(notebook):
42
41
  self.notebooks[url] = notebook
43
42
 
44
- def execute(self) -> None:
43
+ def execute(self, src_uri: str | None = None) -> None:
45
44
  for url, notebook in self.notebooks.items():
46
45
  if not notebook.execution_needed:
47
46
  continue
48
47
 
49
- path = ".md" if url == ".md" else self.store.find_path(url)
50
- logger.info(f"Executing notebook: {path}")
51
- if elapsed := notebook.execute():
52
- logger.info(f"{Path(path).name!r} executed in {elapsed:.2f} seconds")
48
+ path = src_uri or ".md" if url == ".md" else self.store.find_path(url)
53
49
 
54
- def convert(self, text: str) -> Iterator[str | Cell]:
50
+ try:
51
+ elapsed = notebook.execute()
52
+ except Exception as e: # noqa: BLE001
53
+ if src_uri and src_uri != path:
54
+ msg = f"Error reading page {src_uri!r}: "
55
+ else:
56
+ msg = ""
57
+ msg = f"{msg}Error executing notebook {path!r}: {e}"
58
+ logger.error(msg)
59
+ raise SystemExit(1) from None
60
+ else:
61
+ logger.info(f"{path!r} executed in {elapsed:.2f} seconds")
62
+
63
+ def convert(self, text: str, src_uri: str | None = None) -> Iterator[str | Cell]:
55
64
  elems = list(self.parse(text))
56
- self.execute()
65
+ self.execute(src_uri)
57
66
 
58
67
  for elem in elems:
59
68
  if isinstance(elem, str):
@@ -101,6 +110,10 @@ def convert(
101
110
  return ""
102
111
 
103
112
  nb = notebooks[elem.url].nb
113
+
114
+ if "console" in elem.classes:
115
+ return convert_console(elem, nb)
116
+
104
117
  return convert_image(elem, nb)
105
118
 
106
119
  return convert_code_block(elem)
@@ -121,6 +134,24 @@ def convert_image(image: Image, nb: NotebookNode) -> Cell:
121
134
  return Cell(image, get_language(nb), *mime_content)
122
135
 
123
136
 
137
+ def remove_ansi(text: str) -> str:
138
+ return re.sub(r"\x1B\[[0-?]*[ -/]*[@-~]", "", text)
139
+
140
+
141
+ def convert_console(image: Image, nb: NotebookNode) -> str:
142
+ result = image.attributes.get("result", "bash")
143
+ if result == "1":
144
+ result = "bash"
145
+
146
+ source = image.attributes.get("source", None)
147
+ source = f"{image.source}\n" if is_truelike(source) else ""
148
+
149
+ _, content = get_mime_content(nb, image.identifier)
150
+ if isinstance(content, str):
151
+ content = remove_ansi(content)
152
+ return f"```{result}\n{source}{content}```"
153
+
154
+
124
155
  def convert_code_block(code_block: CodeBlock) -> str:
125
156
  source = code_block.attributes.pop("source", None)
126
157
  if not is_truelike(source):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nbsync
3
- Version: 0.3.11
3
+ Version: 0.4.1
4
4
  Summary: A core library to synchronize Jupyter notebooks and Markdown documents, enabling seamless integration and dynamic content execution
5
5
  Keywords: jupyter,notebook,documentation,markdown,python,visualization,dynamic-execution,real-time-sync
6
6
  Author: daizutabi
@@ -38,7 +38,9 @@ Classifier: Topic :: Software Development :: Documentation
38
38
  Classifier: Topic :: Text Processing :: Markup :: Markdown
39
39
  Requires-Dist: nbstore>=0.5.2
40
40
  Requires-Python: >=3.10
41
+ Project-URL: Changelog, https://github.com/daizutabi/nbsync/releases
41
42
  Project-URL: Documentation, https://daizutabi.github.io/nbsync/
43
+ Project-URL: Homepage, https://daizutabi.github.io/nbsync/
42
44
  Project-URL: Issues, https://github.com/daizutabi/nbsync/issues
43
45
  Project-URL: Source, https://github.com/daizutabi/nbsync
44
46
  Description-Content-Type: text/markdown
@@ -0,0 +1,10 @@
1
+ nbsync/__init__.py,sha256=3NuWx9D0LcBnXjEKrC-uypNDOz8rLWXI7mIjdN9xSeE,126
2
+ nbsync/cell.py,sha256=58RkBXzcYajMJzFbUBfdtxXScfkl642B-dckel5-5ZA,4909
3
+ nbsync/logger.py,sha256=4nXvoy5_5Xd2uXCiT_0u1LqOI6L5ZgV4PFCCmy3leVM,759
4
+ nbsync/markdown.py,sha256=2jIyKL-jSjIw3pPsCDIoyYL3MuHAhV9MjfuXa7zh0XA,4549
5
+ nbsync/notebook.py,sha256=XA7Z7nlIZzSMD5UNfkf5EeKe22t5M9P6R7-EHVJtU2s,1284
6
+ nbsync/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ nbsync/sync.py,sha256=HR2-LHOoA8m5E2ffflXvApTMF-VxAtgiEUgobA3EThs,5051
8
+ nbsync-0.4.1.dist-info/WHEEL,sha256=5w2T7AS2mz1-rW9CNagNYWRCaB0iQqBMYLwKdlgiR4Q,78
9
+ nbsync-0.4.1.dist-info/METADATA,sha256=EVirwM5CtKEODeT7tdVuLwAYtYCvdF98hlC8brwc_ws,7232
10
+ nbsync-0.4.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.8.13
2
+ Generator: uv 0.9.7
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,10 +0,0 @@
1
- nbsync/__init__.py,sha256=3NuWx9D0LcBnXjEKrC-uypNDOz8rLWXI7mIjdN9xSeE,126
2
- nbsync/cell.py,sha256=58RkBXzcYajMJzFbUBfdtxXScfkl642B-dckel5-5ZA,4909
3
- nbsync/logger.py,sha256=4nXvoy5_5Xd2uXCiT_0u1LqOI6L5ZgV4PFCCmy3leVM,759
4
- nbsync/markdown.py,sha256=6jDaDtCEkDLsukInF0EnauJ2pUki6nVswjyu3Zg2AUE,3832
5
- nbsync/notebook.py,sha256=4F3V1KDHfJnIR5ZSb1xWdN9JGiC6mWUVimXXK1BFsbc,1450
6
- nbsync/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- nbsync/sync.py,sha256=x7fsWjfKiQbenXXx9v9BYtfpx7pcmAlHj3Fo9PoHGnI,4034
8
- nbsync-0.3.11.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
9
- nbsync-0.3.11.dist-info/METADATA,sha256=BSEoL2e5Tciq1-GhQIobEdgkGKi7gaMPzQG_VB3Ftak,7105
10
- nbsync-0.3.11.dist-info/RECORD,,