nbsync 0.3.10__tar.gz → 0.4.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nbsync
3
- Version: 0.3.10
3
+ Version: 0.4.0
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
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "nbsync"
7
- version = "0.3.10"
7
+ version = "0.4.0"
8
8
  description = "A core library to synchronize Jupyter notebooks and Markdown documents, enabling seamless integration and dynamic content execution"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -35,19 +35,26 @@ requires-python = ">=3.10"
35
35
  dependencies = ["nbstore>=0.5.2"]
36
36
 
37
37
  [project.urls]
38
+ Changelog = "https://github.com/daizutabi/nbsync/releases"
38
39
  Documentation = "https://daizutabi.github.io/nbsync/"
39
- Source = "https://github.com/daizutabi/nbsync"
40
+ Homepage = "https://daizutabi.github.io/nbsync/"
40
41
  Issues = "https://github.com/daizutabi/nbsync/issues"
42
+ Source = "https://github.com/daizutabi/nbsync"
41
43
 
42
44
  [dependency-groups]
43
45
  dev = [
46
+ "basedpyright>=1.31.7",
44
47
  "ipykernel>=6",
45
48
  "matplotlib>=3",
46
49
  "nbconvert>=7",
50
+ "prek>=0.2.12",
47
51
  "pytest-cov>=6",
52
+ "pytest-mock>=3.15.1",
48
53
  "pytest-randomly>=3",
54
+ "ruff>=0.14.0",
55
+ "ty>=0.0.1a22",
49
56
  ]
50
- docs = ["mkapi", "mkdocs-material"]
57
+ docs = ["mkapi", "mkdocs-material", "mkdocs-nbsync>=0.1.5"]
51
58
 
52
59
  [tool.pytest.ini_options]
53
60
  testpaths = ["src", "tests"]
@@ -82,3 +89,10 @@ ignore = [
82
89
 
83
90
  [tool.ruff.lint.per-file-ignores]
84
91
  "**/tests/*" = ["ANN", "ARG", "D", "FBT", "PGH003", "PLR", "RUF", "S", "SLF"]
92
+
93
+ [tool.basedpyright]
94
+ include = ["src", "tests"]
95
+ reportAny = false
96
+ reportExplicitAny = false
97
+ reportImportCycles = false
98
+ reportUnusedCallResult = false
@@ -105,7 +105,7 @@ def get_source(
105
105
 
106
106
 
107
107
  def _add_prompt(source: str) -> str:
108
- lines = []
108
+ lines: list[str] = []
109
109
  for line in source.splitlines():
110
110
  if not line:
111
111
  lines.append("")
@@ -7,7 +7,9 @@ from typing import Any
7
7
  _logger = logging.getLogger("nbsync")
8
8
 
9
9
 
10
- def set_logger(logger: Logger | LoggerAdapter | None = None) -> Logger | LoggerAdapter:
10
+ def set_logger(
11
+ logger: Logger | LoggerAdapter[Logger] | None = None,
12
+ ) -> Logger | LoggerAdapter[Logger]:
11
13
  global _logger # noqa: PLW0603
12
14
 
13
15
  if logger:
@@ -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
 
@@ -72,7 +72,7 @@ def update_notebooks(
72
72
 
73
73
  if url not in notebooks:
74
74
  if url == ".md":
75
- notebooks[url] = Notebook(nbformat.v4.new_notebook())
75
+ notebooks[url] = Notebook(nbformat.v4.new_notebook()) # pyright: ignore[reportUnknownMemberType]
76
76
  else:
77
77
  try:
78
78
  notebooks[url] = Notebook(store.read(url))
@@ -101,6 +101,10 @@ def convert(
101
101
  return ""
102
102
 
103
103
  nb = notebooks[elem.url].nb
104
+
105
+ if "console" in elem.classes:
106
+ return convert_console(elem, nb)
107
+
104
108
  return convert_image(elem, nb)
105
109
 
106
110
  return convert_code_block(elem)
@@ -121,6 +125,24 @@ def convert_image(image: Image, nb: NotebookNode) -> Cell:
121
125
  return Cell(image, get_language(nb), *mime_content)
122
126
 
123
127
 
128
+ def remove_ansi(text: str) -> str:
129
+ return re.sub(r"\x1B\[[0-?]*[ -/]*[@-~]", "", text)
130
+
131
+
132
+ def convert_console(image: Image, nb: NotebookNode) -> str:
133
+ result = image.attributes.get("result", "bash")
134
+ if result == "1":
135
+ result = "bash"
136
+
137
+ source = image.attributes.get("source", None)
138
+ source = f"{image.source}\n" if is_truelike(source) else ""
139
+
140
+ _, content = get_mime_content(nb, image.identifier)
141
+ if isinstance(content, str):
142
+ content = remove_ansi(content)
143
+ return f"```{result}\n{source}{content}```"
144
+
145
+
124
146
  def convert_code_block(code_block: CodeBlock) -> str:
125
147
  source = code_block.attributes.pop("source", None)
126
148
  if not is_truelike(source):
File without changes
File without changes
File without changes
File without changes
File without changes