nbsync 0.0.1__py3-none-any.whl → 0.1.0__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/cell.py ADDED
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ import textwrap
4
+ import uuid
5
+ from dataclasses import dataclass
6
+ from typing import TYPE_CHECKING
7
+
8
+ from nbsync.markdown import is_truelike
9
+
10
+ from .logger import logger
11
+
12
+ if TYPE_CHECKING:
13
+ from nbstore.markdown import Image
14
+
15
+
16
+ @dataclass
17
+ class Cell:
18
+ image: Image
19
+ """The image instance from the Markdown file."""
20
+
21
+ language: str
22
+ """The language of the source to be used to generate the image."""
23
+
24
+ mime: str
25
+ """The MIME type of the image."""
26
+
27
+ content: bytes | str
28
+ """The content of the image."""
29
+
30
+ def convert(self) -> str:
31
+ kind = self.image.attributes.pop("source", "")
32
+ tabs = self.image.attributes.pop("tabs", "")
33
+
34
+ if "/" not in self.mime or not self.content or kind == "source-only":
35
+ if self.image.source:
36
+ source = get_source(self, include_attrs=True)
37
+ kind = "only"
38
+ else:
39
+ source = ""
40
+ result, self.image.url = "", ""
41
+
42
+ elif self.mime.startswith("text/") and isinstance(self.content, str):
43
+ source = get_source(self, include_attrs=True)
44
+ result, self.image.url = self.content, ""
45
+ result = result.rstrip()
46
+
47
+ else:
48
+ source = get_source(self, include_attrs=False)
49
+ result = get_result(self)
50
+
51
+ if markdown := get_markdown(kind, source, result, tabs):
52
+ return textwrap.indent(markdown, self.image.indent)
53
+
54
+ return "" # no cov
55
+
56
+
57
+ def get_source(cell: Cell, *, include_attrs: bool = False) -> str:
58
+ attrs = [cell.language]
59
+ if include_attrs:
60
+ attrs.extend(cell.image.iter_parts())
61
+ attr = " ".join(attrs)
62
+ return f"```{attr}\n{cell.image.source}\n```"
63
+
64
+
65
+ def get_result(cell: Cell) -> str:
66
+ msg = f"{cell.image.url}#{cell.image.identifier} [{cell.mime}]"
67
+ logger.debug(f"Converting image: {msg}")
68
+
69
+ ext = cell.mime.split("/")[1].split("+")[0]
70
+ cell.image.url = f"{uuid.uuid4()}.{ext}"
71
+
72
+ attr = " ".join(cell.image.iter_parts(include_identifier=True))
73
+ return f"![{cell.image.alt}]({cell.image.url}){{{attr}}}"
74
+
75
+
76
+ def get_markdown(kind: str, source: str, result: str, tabs: str) -> str:
77
+ if all(not x for x in (kind, source, result)):
78
+ return ""
79
+
80
+ if not kind or not source:
81
+ return result
82
+
83
+ if kind == "only":
84
+ return source
85
+
86
+ if is_truelike(kind) or kind == "above":
87
+ return f"{source}\n\n{result}"
88
+
89
+ if kind == "below":
90
+ return f"{result}\n\n{source}"
91
+
92
+ if kind == "material-block":
93
+ result = f'<div class="result" markdown="1">{result}</div>'
94
+ return f"{source}\n\n{result}"
95
+
96
+ if kind == "tabbed-left":
97
+ tabs = tabs if "|" in tabs else "Source|Result"
98
+ return get_tabbed(source, result, tabs)
99
+
100
+ if kind == "tabbed-right":
101
+ tabs = tabs if "|" in tabs else "Result|Source"
102
+ return get_tabbed(result, source, tabs)
103
+
104
+ return result
105
+
106
+
107
+ def get_tabbed(left: str, right: str, tabs: str) -> str:
108
+ left_title, right_title = tabs.split("|", 1)
109
+ left = textwrap.indent(left, " ")
110
+ left = f'===! "{left_title}"\n\n{left}'
111
+ right = textwrap.indent(right, " ")
112
+ right = f'=== "{right_title}"\n\n{right}'
113
+ return f"{left}\n\n{right}\n"
nbsync/logger.py ADDED
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ from mkdocs.plugins import get_plugin_logger
4
+
5
+ logger = get_plugin_logger("nbsync")
nbsync/markdown.py ADDED
@@ -0,0 +1,112 @@
1
+ from __future__ import annotations
2
+
3
+ import textwrap
4
+ from typing import TYPE_CHECKING, TypeAlias
5
+
6
+ import nbstore.markdown
7
+ from nbstore.markdown import CodeBlock, Image
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Iterable, Iterator
11
+
12
+ Element: TypeAlias = str | CodeBlock | Image
13
+
14
+
15
+ def convert_image(image: Image, index: int | None = None) -> Iterator[Element]:
16
+ if image.source:
17
+ if not image.identifier and index is None:
18
+ msg = "index is required when source is present and identifier is not set"
19
+ raise ValueError(msg)
20
+
21
+ image.identifier = image.identifier or f"image-nbsync-{index}"
22
+ yield CodeBlock("", image.identifier, [], {}, image.source, image.url)
23
+ yield image
24
+
25
+ elif image.identifier:
26
+ yield image
27
+
28
+ else:
29
+ yield image.text
30
+
31
+
32
+ def convert_code_block(code_block: CodeBlock) -> Iterator[Element]:
33
+ source = code_block.attributes.get("source", None)
34
+ if source == "tabbed-nbsync":
35
+ yield from _convert_code_block_tabbed(code_block)
36
+ else:
37
+ yield code_block
38
+
39
+
40
+ def _convert_code_block_tabbed(code_block: CodeBlock) -> Iterator[Element]:
41
+ markdown = code_block.text.replace('source="tabbed-nbsync"', "")
42
+ markdown = textwrap.indent(markdown, " ")
43
+ yield f'===! "Markdown"\n\n{markdown}\n\n'
44
+
45
+ text = textwrap.indent(code_block.source, " ")
46
+ text = f'=== "Rendered"\n\n{text}'
47
+ yield from nbstore.markdown.parse(text)
48
+
49
+
50
+ SUPPORTED_EXTENSIONS = (".ipynb", ".md", ".py")
51
+
52
+
53
+ def set_url(elem: Image | CodeBlock, url: str) -> tuple[Element, str]:
54
+ """Set the URL of the image or code block.
55
+
56
+ If the URL is empty or ".", set the URL to the current URL.
57
+ """
58
+ if elem.url in ["", "."] and url:
59
+ elem.url = url
60
+ return elem, url
61
+
62
+ if elem.url.endswith(SUPPORTED_EXTENSIONS):
63
+ return elem, elem.url
64
+
65
+ return elem.text, url
66
+
67
+
68
+ def resolve_urls(elems: Iterable[Element]) -> Iterator[Element]:
69
+ """Parse the URL of the image or code block.
70
+
71
+ If a code block has no URL, yield the text of the code block,
72
+ which means that the code block is not processed further.
73
+ """
74
+ url = ""
75
+
76
+ for elem in elems:
77
+ if isinstance(elem, CodeBlock) and not elem.url:
78
+ yield elem.text
79
+
80
+ elif isinstance(elem, Image | CodeBlock):
81
+ elem_, url = set_url(elem, url)
82
+ yield elem_
83
+
84
+ else:
85
+ yield elem
86
+
87
+
88
+ def convert_code_blocks(elems: Iterable[Element]) -> Iterator[Element]:
89
+ for elem in elems:
90
+ if isinstance(elem, CodeBlock):
91
+ yield from convert_code_block(elem)
92
+ else:
93
+ yield elem
94
+
95
+
96
+ def convert_images(elems: Iterable[Element]) -> Iterator[Element]:
97
+ for index, elem in enumerate(elems):
98
+ if isinstance(elem, Image):
99
+ yield from convert_image(elem, index)
100
+ else:
101
+ yield elem
102
+
103
+
104
+ def parse(text: str) -> Iterator[Element]:
105
+ elems = nbstore.markdown.parse(text)
106
+ elems = convert_code_blocks(elems)
107
+ elems = resolve_urls(elems)
108
+ yield from convert_images(elems)
109
+
110
+
111
+ def is_truelike(value: str | None) -> bool:
112
+ return value is not None and value.lower() in ("yes", "true", "1", "on")
nbsync/notebook.py ADDED
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ from typing import TYPE_CHECKING
5
+
6
+ import nbstore.notebook
7
+
8
+ if TYPE_CHECKING:
9
+ from nbformat import NotebookNode
10
+
11
+
12
+ class Notebook:
13
+ nb: NotebookNode
14
+ is_modified: bool
15
+ execution_needed: bool
16
+
17
+ def __init__(self, nb: NotebookNode) -> None:
18
+ self.nb = nb
19
+ self.is_modified = False
20
+ self.execution_needed = False
21
+
22
+ def set_execution_needed(self) -> None:
23
+ self.execution_needed = True
24
+
25
+ def add_cell(self, identifier: str, source: str) -> None:
26
+ if not self.is_modified:
27
+ self.nb = copy.deepcopy(self.nb)
28
+ self.is_modified = True
29
+
30
+ cell = nbstore.notebook.new_code_cell(identifier, source)
31
+ self.nb.cells.append(cell)
32
+ self.set_execution_needed()
33
+
34
+ def equals(self, other: Notebook) -> bool:
35
+ return nbstore.notebook.equals(self.nb, other.nb)
36
+
37
+ def execute(self) -> None:
38
+ nbstore.notebook.execute(self.nb)
39
+ self.execution_needed = False
nbsync/plugin.py ADDED
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING, ClassVar
5
+
6
+ from mkdocs.config import Config as BaseConfig
7
+ from mkdocs.config import config_options
8
+ from mkdocs.plugins import BasePlugin
9
+ from mkdocs.structure.files import File
10
+ from nbstore.store import Store
11
+
12
+ from .logger import logger
13
+ from .sync import Synchronizer
14
+
15
+ if TYPE_CHECKING:
16
+ from typing import Any
17
+
18
+ from mkdocs.config.defaults import MkDocsConfig
19
+ from mkdocs.structure.files import Files
20
+ from mkdocs.structure.pages import Page
21
+
22
+ from .cell import Cell
23
+
24
+
25
+ class Config(BaseConfig):
26
+ """Configuration for Nbstore plugin."""
27
+
28
+ src_dir = config_options.Type((str, list), default=".")
29
+
30
+
31
+ class Plugin(BasePlugin[Config]):
32
+ store: ClassVar[Store | None] = None
33
+ syncs: ClassVar[dict[str, Synchronizer]] = {}
34
+ files: Files
35
+
36
+ def on_config(self, config: MkDocsConfig, **kwargs: Any) -> MkDocsConfig:
37
+ if isinstance(self.config.src_dir, str):
38
+ src_dirs = [self.config.src_dir]
39
+ else:
40
+ src_dirs = self.config.src_dir
41
+
42
+ src_dirs = [(Path(config.docs_dir) / s).resolve() for s in src_dirs]
43
+
44
+ store = self.__class__.store
45
+
46
+ if store is None or store.src_dirs != src_dirs:
47
+ self.__class__.store = Store(src_dirs)
48
+ config.watch.extend(x.as_posix() for x in src_dirs)
49
+
50
+ for name in ["attr_list", "md_in_html"]:
51
+ if name not in config.markdown_extensions:
52
+ config.markdown_extensions.append(name)
53
+
54
+ return config
55
+
56
+ def on_files(self, files: Files, config: MkDocsConfig, **kwargs: Any) -> Files:
57
+ self.files = files
58
+ return files
59
+
60
+ def on_page_markdown(
61
+ self,
62
+ markdown: str,
63
+ page: Page,
64
+ config: MkDocsConfig,
65
+ **kwargs: Any,
66
+ ) -> str:
67
+ if self.__class__.store is None:
68
+ msg = "Store must be initialized before processing markdown"
69
+ logger.error(msg)
70
+ return markdown
71
+
72
+ src_uri = page.file.src_uri
73
+ syncs = self.__class__.syncs
74
+
75
+ if src_uri not in syncs:
76
+ syncs[src_uri] = Synchronizer(self.__class__.store)
77
+
78
+ markdowns = []
79
+
80
+ for elem in syncs[src_uri].convert(markdown):
81
+ if isinstance(elem, str):
82
+ markdowns.append(elem)
83
+
84
+ elif markdown := elem.convert():
85
+ markdowns.append(markdown)
86
+
87
+ if elem.image.url and elem.content:
88
+ file = generate_file(elem, src_uri, config)
89
+ self.files.append(file)
90
+
91
+ return "".join(markdowns)
92
+
93
+
94
+ def generate_file(cell: Cell, page_uri: str, config: MkDocsConfig) -> File:
95
+ src_uri = (Path(page_uri).parent / cell.image.url).as_posix()
96
+ return File.generated(config, src_uri, content=cell.content)
nbsync/sync.py ADDED
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import textwrap
5
+ from dataclasses import dataclass, field
6
+ from typing import TYPE_CHECKING
7
+
8
+ import nbformat
9
+ from nbstore.markdown import CodeBlock, Image
10
+ from nbstore.notebook import get_language, get_mime_content, get_source
11
+
12
+ import nbsync.markdown
13
+ from nbsync.cell import Cell
14
+ from nbsync.markdown import is_truelike
15
+ from nbsync.notebook import Notebook
16
+
17
+ from .logger import logger
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Iterator
21
+
22
+ from nbformat import NotebookNode
23
+ from nbstore import Store
24
+
25
+
26
+ @dataclass
27
+ class Synchronizer:
28
+ store: Store
29
+ notebooks: dict[str, Notebook] = field(default_factory=dict, init=False)
30
+
31
+ def parse(self, text: str) -> Iterator[str | Image | CodeBlock]:
32
+ notebooks: dict[str, Notebook] = {}
33
+
34
+ for elem in nbsync.markdown.parse(text):
35
+ yield elem
36
+
37
+ if isinstance(elem, Image | CodeBlock):
38
+ update_notebooks(notebooks, elem, self.store)
39
+
40
+ for url, notebook in notebooks.items():
41
+ if url not in self.notebooks or not self.notebooks[url].equals(notebook):
42
+ self.notebooks[url] = notebook
43
+
44
+ def execute(self) -> None:
45
+ for url, notebook in self.notebooks.items():
46
+ if not notebook.execution_needed:
47
+ continue
48
+
49
+ path = ".md" if url == ".md" else self.store.find_path(url)
50
+ logger.info(f"Executing notebook: {path}")
51
+ notebook.execute()
52
+
53
+ def convert(self, text: str) -> Iterator[str | Cell]:
54
+ elems = list(self.parse(text))
55
+ self.execute()
56
+
57
+ for elem in elems:
58
+ if isinstance(elem, str):
59
+ yield elem
60
+
61
+ elif elem.identifier not in [".", "_"]:
62
+ if isinstance(elem, Image):
63
+ nb = self.notebooks[elem.url].nb
64
+ yield convert_image(elem, nb)
65
+ else:
66
+ yield convert_code_block(elem)
67
+
68
+
69
+ def update_notebooks(
70
+ notebooks: dict[str, Notebook],
71
+ elem: Image | CodeBlock,
72
+ store: Store,
73
+ ) -> None:
74
+ url = elem.url
75
+
76
+ if url not in notebooks:
77
+ if url == ".md":
78
+ notebooks[url] = Notebook(nbformat.v4.new_notebook())
79
+ else:
80
+ try:
81
+ notebooks[url] = Notebook(store.read(url))
82
+ except Exception: # noqa: BLE001
83
+ logger.warning(f"Error reading notebook: {url}")
84
+ return
85
+
86
+ notebook = notebooks[url]
87
+
88
+ if is_truelike(elem.attributes.pop("exec", None)):
89
+ notebook.set_execution_needed()
90
+
91
+ if isinstance(elem, CodeBlock):
92
+ source = textwrap.dedent(elem.source)
93
+ notebook.add_cell(elem.identifier, source)
94
+
95
+
96
+ def convert_image(image: Image, nb: NotebookNode) -> Cell:
97
+ try:
98
+ image.source = get_source(nb, image.identifier)
99
+ mime_content = get_mime_content(nb, image.identifier)
100
+ except ValueError:
101
+ cell = f"{image.url}#{image.identifier}"
102
+ logger.warning(f"Error reading cell: {cell!r}")
103
+ image.source = ""
104
+ mime_content = ("", "")
105
+
106
+ return Cell(image, get_language(nb), *mime_content)
107
+
108
+
109
+ def convert_code_block(code_block: CodeBlock) -> str:
110
+ source = code_block.attributes.pop("source", None)
111
+ if not is_truelike(source):
112
+ return ""
113
+
114
+ lines = code_block.text.splitlines()
115
+ if lines:
116
+ pattern = f"\\S+#{code_block.identifier}"
117
+ lines[0] = re.sub(pattern, "", lines[0])
118
+ pattern = r"source=[^\s}]+"
119
+ lines[0] = re.sub(pattern, "", lines[0])
120
+
121
+ return "\n".join(lines)
@@ -0,0 +1,177 @@
1
+ Metadata-Version: 2.4
2
+ Name: nbsync
3
+ Version: 0.1.0
4
+ Summary: MkDocs plugin treating Jupyter notebooks, Python scripts and Markdown files as first-class citizens for documentation with dynamic execution and real-time synchronization
5
+ Project-URL: Documentation, https://daizutabi.github.io/nbsync/
6
+ Project-URL: Source, https://github.com/daizutabi/nbsync
7
+ Project-URL: Issues, https://github.com/daizutabi/nbsync/issues
8
+ Author-email: daizutabi <daizutabi@gmail.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2025 Daizu
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: documentation,dynamic-execution,jupyter,markdown,mkdocs,notebook,python,real-time-sync,visualization
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Programming Language :: Python
34
+ Classifier: Programming Language :: Python :: 3.10
35
+ Classifier: Programming Language :: Python :: 3.11
36
+ Classifier: Programming Language :: Python :: 3.12
37
+ Classifier: Programming Language :: Python :: 3.13
38
+ Classifier: Topic :: Documentation
39
+ Classifier: Topic :: Software Development :: Documentation
40
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
41
+ Requires-Python: >=3.10
42
+ Requires-Dist: mkdocs>=1.6
43
+ Requires-Dist: nbstore>=0.4.7
44
+ Description-Content-Type: text/markdown
45
+
46
+ # nbsync
47
+
48
+ [![PyPI Version][pypi-v-image]][pypi-v-link]
49
+ [![Python Version][python-v-image]][python-v-link]
50
+ [![Build Status][GHAction-image]][GHAction-link]
51
+ [![Coverage Status][codecov-image]][codecov-link]
52
+
53
+ <strong>Connect Jupyter notebooks to your MkDocs documentation</strong>
54
+
55
+ nbsync is a MkDocs plugin that seamlessly embeds Jupyter notebook
56
+ visualizations in your documentation, solving the disconnect between
57
+ code development and documentation.
58
+
59
+ ## Why Use nbsync?
60
+
61
+ ### The Documentation Challenge
62
+
63
+ Data scientists, researchers, and technical writers face a common dilemma:
64
+
65
+ - **Development happens in notebooks** - ideal for experimentation and visualization
66
+ - **Documentation lives in markdown** - perfect for narrative and explanation
67
+ - **Connecting the two is painful** - screenshots break, exports get outdated
68
+
69
+ ### Our Solution
70
+
71
+ This plugin creates a live bridge between your notebooks and documentation by:
72
+
73
+ - **Keeping environments separate** - work in the tool best suited for each task
74
+ - **Maintaining connections** - reference specific figures from notebooks
75
+ - **Automating updates** - changes to notebooks reflect in documentation
76
+
77
+ ## Key Benefits
78
+
79
+ - **True Separation of Concerns**:
80
+ Develop visualizations in Jupyter notebooks and write documentation
81
+ in markdown files, with each tool optimized for its purpose.
82
+
83
+ - **Intuitive Markdown Syntax**:
84
+ Use standard image syntax with a simple extension to reference
85
+ notebook figures: `![alt text](notebook.ipynb){#figure-id}`
86
+
87
+ - **Automatic Updates**:
88
+ When you modify your notebooks, your documentation updates
89
+ automatically in MkDocs serve mode.
90
+
91
+ - **Clean Source Documents**:
92
+ Your markdown remains readable and focused on content, without
93
+ code distractions or complex embedding techniques.
94
+
95
+ - **Enhanced Development Experience**:
96
+ Take advantage of IDE features like code completion and syntax
97
+ highlighting in the appropriate environment.
98
+
99
+ ## Quick Start
100
+
101
+ ### 1. Installation
102
+
103
+ ```bash
104
+ pip install nbsync
105
+ ```
106
+
107
+ ### 2. Configuration
108
+
109
+ Add to your `mkdocs.yml`:
110
+
111
+ ```yaml
112
+ plugins:
113
+ - nbsync:
114
+ src_dir: ../notebooks
115
+ ```
116
+
117
+ ### 3. Mark Figures in Your Notebook
118
+
119
+ In your Jupyter notebook, identify figures with a comment:
120
+
121
+ ```python
122
+ # #my-figure
123
+ import matplotlib.pyplot as plt
124
+
125
+ fig, ax = plt.subplots(figsize=(8, 4))
126
+ ax.plot([1, 2, 3, 4], [10, 20, 25, 30])
127
+ ```
128
+
129
+ ### 4. Reference in Markdown
130
+
131
+ Use standard Markdown image syntax with the figure identifier:
132
+
133
+ ```markdown
134
+ ![Chart description](my-notebook.ipynb){#my-figure}
135
+ ```
136
+
137
+ ## Advanced Usage
138
+
139
+ For more detailed information on how to use nbsync, see:
140
+
141
+ - [Notebook Configuration](usage/notebook.md) - Setting up notebook directories
142
+ - [Class Options](usage/class.md) - Control how notebook content is displayed
143
+ <!-- - [Workflow Tips](usage/workflow.md) - Best practices for documentation -->
144
+
145
+ ## The Power of Separation
146
+
147
+ Creating documentation and developing visualizations involve different
148
+ workflows and timeframes. When building visualizations in Jupyter notebooks,
149
+ you need rapid cycles of execution, verification, and modification.
150
+
151
+ This plugin is designed specifically to address these separation of
152
+ concerns, allowing you to:
153
+
154
+ - **Focus on code** in notebooks without documentation distractions
155
+ - **Focus on narrative** in markdown without code interruptions
156
+ - **Maintain powerful connections** between both environments
157
+
158
+ Each environment is optimized for its purpose, while the plugin
159
+ handles the integration automatically.
160
+
161
+ ## Contributing
162
+
163
+ Contributions are welcome! Please open an issue or submit a pull request.
164
+
165
+ ## License
166
+
167
+ This project is licensed under the MIT License.
168
+
169
+ <!-- Badges -->
170
+ [pypi-v-image]: https://img.shields.io/pypi/v/nbsync.svg
171
+ [pypi-v-link]: https://pypi.org/project/nbsync/
172
+ [python-v-image]: https://img.shields.io/pypi/pyversions/nbsync.svg
173
+ [python-v-link]: https://pypi.org/project/nbsync
174
+ [GHAction-image]: https://github.com/daizutabi/nbsync/actions/workflows/ci.yaml/badge.svg?branch=main&event=push
175
+ [GHAction-link]: https://github.com/daizutabi/nbsync/actions?query=event%3Apush+branch%3Amain
176
+ [codecov-image]: https://codecov.io/github/daizutabi/nbsync/coverage.svg?branch=main
177
+ [codecov-link]: https://codecov.io/github/daizutabi/nbsync?branch=main
@@ -0,0 +1,12 @@
1
+ nbsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ nbsync/cell.py,sha256=HjGV7WHYwoW-X8AK9EePcjHz2wHl0rVaEAS_hdI8QNw,3248
3
+ nbsync/logger.py,sha256=tQK8Z8HdWQNhVohQYLwZtJhdaj2w0UTyhHQak3mPqNc,119
4
+ nbsync/markdown.py,sha256=sGNPuyLTfQFv-I1m6jK6uDKFRHAPYY6hpy4P1bJjmkE,3185
5
+ nbsync/notebook.py,sha256=Voh-e8RQsoYmWKnaHzAn4bm6Ud5v-aHwmng2Uc0rUts,1009
6
+ nbsync/plugin.py,sha256=YywBAD7fcrPg3-_9T_kSuvD8RBzsc_ovlIviGlKR9po,2835
7
+ nbsync/sync.py,sha256=RTF6h0izF1emc1CpG6atEgAQHC7mZCit9NkXK26sOA8,3575
8
+ nbsync-0.1.0.dist-info/METADATA,sha256=t97_Xq3uoqCGveX9XblM-zTwyHA7npBBK32PdhN_e9Y,6604
9
+ nbsync-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ nbsync-0.1.0.dist-info/entry_points.txt,sha256=bSqmJTDmrUoSm4yDjk8YDWxI71SShx16judadGdiKbA,47
11
+ nbsync-0.1.0.dist-info/licenses/LICENSE,sha256=wy1pqn52upuo_qYwY-epWmspwE-3UWJso0xodciGXYc,1062
12
+ nbsync-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [mkdocs.plugins]
2
+ nbsync = nbsync.plugin:Plugin
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Daizu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,29 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: nbsync
3
- Version: 0.0.1
4
- Summary: Tools for notebook synchronization and management
5
- Author-email: daizutabi <daizutabi@gmail.com>
6
- License: MIT License
7
-
8
- Copyright (c) 2020-present daizutabi <daizutabi@gmail.com>
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
- License-File: LICENSE
16
- Keywords: content-management,documentation,extraction,jupyter,markdown,notebook,visualization
17
- Classifier: Development Status :: 4 - Beta
18
- Classifier: Programming Language :: Python
19
- Classifier: Programming Language :: Python :: 3.10
20
- Classifier: Programming Language :: Python :: 3.11
21
- Classifier: Programming Language :: Python :: 3.12
22
- Classifier: Programming Language :: Python :: 3.13
23
- Classifier: Topic :: Documentation
24
- Classifier: Topic :: Software Development :: Documentation
25
- Classifier: Topic :: Text Processing :: Markup :: Markdown
26
- Requires-Python: >=3.10
27
- Description-Content-Type: text/markdown
28
-
29
- # nbsync-monorepo
@@ -1,5 +0,0 @@
1
- nbsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- nbsync-0.0.1.dist-info/METADATA,sha256=uSwdGxT3JAkRIc7dbbithBN1254yd6fu8wyQuRQszig,1965
3
- nbsync-0.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4
- nbsync-0.0.1.dist-info/licenses/LICENSE,sha256=dbO9NY-nQXzIS5St3j3rPUd7gm5r6pczFYaNy5GVNdI,1105
5
- nbsync-0.0.1.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2020-present daizutabi <daizutabi@gmail.com>
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
-
7
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
File without changes