figpack 0.2.8__tar.gz → 0.2.10__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.
Potentially problematic release.
This version of figpack might be problematic. Click here for more details.
- {figpack-0.2.8/figpack.egg-info → figpack-0.2.10}/PKG-INFO +1 -1
- figpack-0.2.10/figpack/__init__.py +16 -0
- figpack-0.2.10/figpack/core/__init__.py +5 -0
- figpack-0.2.10/figpack/core/_bundle_utils.py +180 -0
- figpack-0.2.10/figpack/core/extension_view.py +53 -0
- figpack-0.2.10/figpack/core/figpack_extension.py +137 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/core/figpack_view.py +1 -1
- figpack-0.2.8/figpack/figpack-figure-dist/assets/index-CjiTpC6i.js → figpack-0.2.10/figpack/figpack-figure-dist/assets/index-D2FnM_hN.js +82 -84
- figpack-0.2.8/figpack/figpack-figure-dist/assets/index-Cmae55E4.css → figpack-0.2.10/figpack/figpack-figure-dist/assets/index-D9a3K6eW.css +1 -1
- {figpack-0.2.8 → figpack-0.2.10}/figpack/figpack-figure-dist/index.html +2 -2
- figpack-0.2.10/figpack/spike_sorting/views/UnitMetricsGraph.py +129 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/__init__.py +8 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/Spectrogram.py +50 -21
- {figpack-0.2.8 → figpack-0.2.10/figpack.egg-info}/PKG-INFO +1 -1
- {figpack-0.2.8 → figpack-0.2.10}/figpack.egg-info/SOURCES.txt +6 -2
- {figpack-0.2.8 → figpack-0.2.10}/pyproject.toml +1 -1
- figpack-0.2.10/tests/test_extension_system.py +233 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_spectrogram.py +0 -24
- figpack-0.2.8/figpack/__init__.py +0 -9
- figpack-0.2.8/figpack/core/__init__.py +0 -0
- figpack-0.2.8/figpack/core/_bundle_utils.py +0 -57
- {figpack-0.2.8 → figpack-0.2.10}/LICENSE +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/MANIFEST.in +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/README.md +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/cli.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/core/_save_figure.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/core/_server_manager.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/core/_show_view.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/core/_upload_bundle.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/core/_view_figure.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/core/config.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/figpack-figure-dist/assets/neurosift-logo-CLsuwLMO.png +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/franklab/__init__.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/franklab/views/TrackAnimation.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/franklab/views/__init__.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/__init__.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/AutocorrelogramItem.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/Autocorrelograms.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/AverageWaveforms.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/CrossCorrelogramItem.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/CrossCorrelograms.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/RasterPlot.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/RasterPlotItem.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/SpikeAmplitudes.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/SpikeAmplitudesItem.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/UnitSimilarityScore.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/UnitsTable.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/UnitsTableColumn.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/spike_sorting/views/UnitsTableRow.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/Box.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/DataFrame.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/Gallery.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/GalleryItem.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/Image.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/LayoutItem.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/Markdown.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/MatplotlibFigure.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/MultiChannelTimeseries.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/PlotlyFigure.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/Splitter.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/TabLayout.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/TabLayoutItem.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/TimeseriesGraph.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack/views/__init__.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack.egg-info/dependency_links.txt +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack.egg-info/entry_points.txt +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack.egg-info/requires.txt +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/figpack.egg-info/top_level.txt +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/setup.cfg +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_average_waveforms.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_box.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_cli.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_core.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_dataframe.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_figpack_view.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_gallery.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_image.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_markdown.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_matplotlib_figure.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_multichannel_timeseries.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_plotly_figure.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_raster_plot.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_server_manager.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_show_view.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_spike_amplitudes.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_spike_sorting_correlograms.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_splitter.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_tablayout.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_timeseries_graph.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_track_animation.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_units_table.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_upload_bundle.py +0 -0
- {figpack-0.2.8 → figpack-0.2.10}/tests/test_view_figure.py +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
figpack - A Python package for creating shareable, interactive visualizations in the browser
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.2.10"
|
|
6
|
+
|
|
7
|
+
from .cli import view_figure
|
|
8
|
+
from .core import FigpackView, FigpackExtension, ExtensionRegistry, ExtensionView
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"view_figure",
|
|
12
|
+
"FigpackView",
|
|
13
|
+
"FigpackExtension",
|
|
14
|
+
"ExtensionRegistry",
|
|
15
|
+
"ExtensionView",
|
|
16
|
+
]
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pathlib
|
|
3
|
+
from typing import Set
|
|
4
|
+
|
|
5
|
+
import zarr
|
|
6
|
+
|
|
7
|
+
from .figpack_view import FigpackView
|
|
8
|
+
from .figpack_extension import ExtensionRegistry
|
|
9
|
+
from .extension_view import ExtensionView
|
|
10
|
+
|
|
11
|
+
thisdir = pathlib.Path(__file__).parent.resolve()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def prepare_figure_bundle(
|
|
15
|
+
view: FigpackView, tmpdir: str, *, title: str, description: str = None
|
|
16
|
+
) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Prepare a figure bundle in the specified temporary directory.
|
|
19
|
+
|
|
20
|
+
This function:
|
|
21
|
+
1. Copies all files from the figpack-figure-dist directory to tmpdir
|
|
22
|
+
2. Writes the view data to a zarr group
|
|
23
|
+
3. Discovers and writes extension JavaScript files
|
|
24
|
+
4. Consolidates zarr metadata
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
view: The figpack view to prepare
|
|
28
|
+
tmpdir: The temporary directory to prepare the bundle in
|
|
29
|
+
title: Title for the figure (required)
|
|
30
|
+
description: Optional description for the figure (markdown supported)
|
|
31
|
+
"""
|
|
32
|
+
html_dir = thisdir / ".." / "figpack-figure-dist"
|
|
33
|
+
if not os.path.exists(html_dir):
|
|
34
|
+
raise SystemExit(f"Error: directory not found: {html_dir}")
|
|
35
|
+
|
|
36
|
+
# Copy all files in html_dir recursively to tmpdir
|
|
37
|
+
for item in html_dir.iterdir():
|
|
38
|
+
if item.is_file():
|
|
39
|
+
target = pathlib.Path(tmpdir) / item.name
|
|
40
|
+
target.write_bytes(item.read_bytes())
|
|
41
|
+
elif item.is_dir():
|
|
42
|
+
target = pathlib.Path(tmpdir) / item.name
|
|
43
|
+
target.mkdir(exist_ok=True)
|
|
44
|
+
for subitem in item.iterdir():
|
|
45
|
+
target_sub = target / subitem.name
|
|
46
|
+
target_sub.write_bytes(subitem.read_bytes())
|
|
47
|
+
|
|
48
|
+
# Write the view data to the Zarr group
|
|
49
|
+
zarr_group = zarr.open_group(
|
|
50
|
+
pathlib.Path(tmpdir) / "data.zarr",
|
|
51
|
+
mode="w",
|
|
52
|
+
synchronizer=zarr.ThreadSynchronizer(),
|
|
53
|
+
)
|
|
54
|
+
view._write_to_zarr_group(zarr_group)
|
|
55
|
+
|
|
56
|
+
# Add title and description as attributes on the top-level zarr group
|
|
57
|
+
zarr_group.attrs["title"] = title
|
|
58
|
+
if description is not None:
|
|
59
|
+
zarr_group.attrs["description"] = description
|
|
60
|
+
|
|
61
|
+
# Discover and write extension JavaScript files
|
|
62
|
+
required_extensions = _discover_required_extensions(view)
|
|
63
|
+
_write_extension_files(required_extensions, tmpdir)
|
|
64
|
+
|
|
65
|
+
zarr.consolidate_metadata(zarr_group.store)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _discover_required_extensions(view: FigpackView) -> Set[str]:
|
|
69
|
+
"""
|
|
70
|
+
Recursively discover all extensions required by a view and its children
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
view: The root view to analyze
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Set of extension names required by this view hierarchy
|
|
77
|
+
"""
|
|
78
|
+
extensions = set()
|
|
79
|
+
visited = set() # Prevent infinite recursion
|
|
80
|
+
|
|
81
|
+
def _collect_extensions(v: FigpackView):
|
|
82
|
+
# Prevent infinite recursion
|
|
83
|
+
if id(v) in visited:
|
|
84
|
+
return
|
|
85
|
+
visited.add(id(v))
|
|
86
|
+
|
|
87
|
+
# Check if this view is an extension view
|
|
88
|
+
if isinstance(v, ExtensionView):
|
|
89
|
+
extensions.add(v.extension_name)
|
|
90
|
+
|
|
91
|
+
# Recursively check all attributes that might contain child views
|
|
92
|
+
for attr_name in dir(v):
|
|
93
|
+
if attr_name.startswith("_"):
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
attr_value = getattr(v, attr_name)
|
|
98
|
+
|
|
99
|
+
# Handle single child view
|
|
100
|
+
if isinstance(attr_value, FigpackView):
|
|
101
|
+
_collect_extensions(attr_value)
|
|
102
|
+
|
|
103
|
+
# Handle lists/tuples of items that might contain views
|
|
104
|
+
elif isinstance(attr_value, (list, tuple)):
|
|
105
|
+
for item in attr_value:
|
|
106
|
+
# Check if item has a 'view' attribute (like LayoutItem)
|
|
107
|
+
if hasattr(item, "view") and isinstance(item.view, FigpackView):
|
|
108
|
+
_collect_extensions(item.view)
|
|
109
|
+
# Or if the item itself is a view
|
|
110
|
+
elif isinstance(item, FigpackView):
|
|
111
|
+
_collect_extensions(item)
|
|
112
|
+
|
|
113
|
+
# Handle objects that might have a 'view' attribute
|
|
114
|
+
elif hasattr(attr_value, "view") and isinstance(
|
|
115
|
+
attr_value.view, FigpackView
|
|
116
|
+
):
|
|
117
|
+
_collect_extensions(attr_value.view)
|
|
118
|
+
|
|
119
|
+
except (AttributeError, TypeError):
|
|
120
|
+
# Skip attributes that can't be accessed or aren't relevant
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
_collect_extensions(view)
|
|
124
|
+
return extensions
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _write_extension_files(extension_names: Set[str], tmpdir: str) -> None:
|
|
128
|
+
"""
|
|
129
|
+
Write JavaScript files for the required extensions
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
extension_names: Set of extension names to write
|
|
133
|
+
tmpdir: Directory to write extension files to
|
|
134
|
+
"""
|
|
135
|
+
if not extension_names:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
registry = ExtensionRegistry.get_instance()
|
|
139
|
+
tmpdir_path = pathlib.Path(tmpdir)
|
|
140
|
+
|
|
141
|
+
for extension_name in extension_names:
|
|
142
|
+
extension = registry.get_extension(extension_name)
|
|
143
|
+
if extension is None:
|
|
144
|
+
raise RuntimeError(
|
|
145
|
+
f"Extension '{extension_name}' is required but not registered"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Write the main JavaScript file
|
|
149
|
+
js_filename = extension.get_javascript_filename()
|
|
150
|
+
js_path = tmpdir_path / js_filename
|
|
151
|
+
|
|
152
|
+
# Add some metadata as comments at the top
|
|
153
|
+
js_content = f"""/*
|
|
154
|
+
* Figpack Extension: {extension.name}
|
|
155
|
+
* Version: {extension.version}
|
|
156
|
+
* Generated automatically - do not edit
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
{extension.javascript_code}
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
js_path.write_text(js_content, encoding="utf-8")
|
|
163
|
+
|
|
164
|
+
# Write additional JavaScript files
|
|
165
|
+
additional_filenames = extension.get_additional_filenames()
|
|
166
|
+
for original_name, safe_filename in additional_filenames.items():
|
|
167
|
+
additional_content = extension.additional_files[original_name]
|
|
168
|
+
additional_path = tmpdir_path / safe_filename
|
|
169
|
+
|
|
170
|
+
# Add metadata header to additional files too
|
|
171
|
+
additional_js_content = f"""/*
|
|
172
|
+
* Figpack Extension Additional File: {extension.name}/{original_name}
|
|
173
|
+
* Version: {extension.version}
|
|
174
|
+
* Generated automatically - do not edit
|
|
175
|
+
*/
|
|
176
|
+
|
|
177
|
+
{additional_content}
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
additional_path.write_text(additional_js_content, encoding="utf-8")
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for views that use figpack extensions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import zarr
|
|
6
|
+
from .figpack_view import FigpackView
|
|
7
|
+
from .figpack_extension import ExtensionRegistry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ExtensionView(FigpackView):
|
|
11
|
+
"""
|
|
12
|
+
Base class for views that are rendered by figpack extensions
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, *, extension_name: str):
|
|
16
|
+
"""
|
|
17
|
+
Initialize an extension-based view
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
extension_name: Name of the extension that will render this view
|
|
21
|
+
"""
|
|
22
|
+
super().__init__()
|
|
23
|
+
self.extension_name = extension_name
|
|
24
|
+
|
|
25
|
+
# Validate that the extension is registered
|
|
26
|
+
registry = ExtensionRegistry.get_instance()
|
|
27
|
+
extension = registry.get_extension(extension_name)
|
|
28
|
+
if extension is None:
|
|
29
|
+
raise ValueError(
|
|
30
|
+
f"Extension '{extension_name}' is not registered. "
|
|
31
|
+
f"Make sure to register the extension before creating views that use it."
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def _write_to_zarr_group(self, group: zarr.Group) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Write the extension view metadata to a Zarr group.
|
|
37
|
+
Subclasses should call super()._write_to_zarr_group(group) first,
|
|
38
|
+
then add their own data.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
group: Zarr group to write data into
|
|
42
|
+
"""
|
|
43
|
+
# Set the view type to indicate this is an extension view
|
|
44
|
+
group.attrs["view_type"] = "ExtensionView"
|
|
45
|
+
|
|
46
|
+
# Store the extension name so the frontend knows which extension to use
|
|
47
|
+
group.attrs["extension_name"] = self.extension_name
|
|
48
|
+
|
|
49
|
+
# Store extension metadata for debugging/compatibility
|
|
50
|
+
registry = ExtensionRegistry.get_instance()
|
|
51
|
+
extension = registry.get_extension(self.extension_name)
|
|
52
|
+
if extension:
|
|
53
|
+
group.attrs["extension_version"] = extension.version
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Extension system for figpack - allows runtime loading of custom view components
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FigpackExtension:
|
|
9
|
+
"""
|
|
10
|
+
Base class for figpack extensions that provide custom view components
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
*,
|
|
16
|
+
name: str,
|
|
17
|
+
javascript_code: str,
|
|
18
|
+
additional_files: Optional[Dict[str, str]] = None,
|
|
19
|
+
version: str = "1.0.0",
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Initialize a figpack extension
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
name: Unique name for the extension (used as identifier)
|
|
26
|
+
javascript_code: JavaScript code that implements the extension
|
|
27
|
+
additional_files: Optional dictionary of additional JavaScript files
|
|
28
|
+
{filename: content} that the extension can load
|
|
29
|
+
version: Version string for compatibility tracking
|
|
30
|
+
"""
|
|
31
|
+
self.name = name
|
|
32
|
+
self.javascript_code = javascript_code
|
|
33
|
+
self.additional_files = additional_files or {}
|
|
34
|
+
self.version = version
|
|
35
|
+
|
|
36
|
+
# Validate extension name
|
|
37
|
+
if not name or not isinstance(name, str):
|
|
38
|
+
raise ValueError("Extension name must be a non-empty string")
|
|
39
|
+
|
|
40
|
+
# Basic validation of JavaScript code
|
|
41
|
+
if not javascript_code or not isinstance(javascript_code, str):
|
|
42
|
+
raise ValueError("Extension javascript_code must be a non-empty string")
|
|
43
|
+
|
|
44
|
+
def get_javascript_filename(self) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Get the filename that should be used for this extension's JavaScript file
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Filename for the extension JavaScript file
|
|
50
|
+
"""
|
|
51
|
+
# Sanitize the name for use as a filename
|
|
52
|
+
safe_name = "".join(c for c in self.name if c.isalnum() or c in "-_")
|
|
53
|
+
return f"extension-{safe_name}.js"
|
|
54
|
+
|
|
55
|
+
def get_additional_filenames(self) -> Dict[str, str]:
|
|
56
|
+
"""
|
|
57
|
+
Get the filenames for additional JavaScript files
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Dictionary mapping original filenames to safe filenames
|
|
61
|
+
"""
|
|
62
|
+
safe_name = "".join(c for c in self.name if c.isalnum() or c in "-_")
|
|
63
|
+
return {
|
|
64
|
+
original_name: f"extension-{safe_name}-{original_name}"
|
|
65
|
+
for original_name in self.additional_files.keys()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ExtensionRegistry:
|
|
70
|
+
"""
|
|
71
|
+
Singleton registry for managing figpack extensions
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
_instance: Optional["ExtensionRegistry"] = None
|
|
75
|
+
|
|
76
|
+
def __init__(self):
|
|
77
|
+
self._extensions: Dict[str, FigpackExtension] = {}
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def get_instance(cls) -> "ExtensionRegistry":
|
|
81
|
+
"""Get the singleton instance of the extension registry"""
|
|
82
|
+
if cls._instance is None:
|
|
83
|
+
cls._instance = cls()
|
|
84
|
+
return cls._instance
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def register(cls, extension: FigpackExtension) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Register an extension with the global registry
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
extension: The extension to register
|
|
93
|
+
"""
|
|
94
|
+
registry = cls.get_instance()
|
|
95
|
+
registry._register_extension(extension)
|
|
96
|
+
|
|
97
|
+
def _register_extension(self, extension: FigpackExtension) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Internal method to register an extension
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
extension: The extension to register
|
|
103
|
+
"""
|
|
104
|
+
if extension.name in self._extensions:
|
|
105
|
+
existing = self._extensions[extension.name]
|
|
106
|
+
if existing.version != extension.version:
|
|
107
|
+
print(
|
|
108
|
+
f"Warning: Replacing extension '{extension.name}' "
|
|
109
|
+
f"version {existing.version} with version {extension.version}"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
self._extensions[extension.name] = extension
|
|
113
|
+
|
|
114
|
+
def get_extension(self, name: str) -> Optional[FigpackExtension]:
|
|
115
|
+
"""
|
|
116
|
+
Get an extension by name
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
name: Name of the extension to retrieve
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
The extension if found, None otherwise
|
|
123
|
+
"""
|
|
124
|
+
return self._extensions.get(name)
|
|
125
|
+
|
|
126
|
+
def get_all_extensions(self) -> Dict[str, FigpackExtension]:
|
|
127
|
+
"""
|
|
128
|
+
Get all registered extensions
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Dictionary mapping extension names to extension objects
|
|
132
|
+
"""
|
|
133
|
+
return self._extensions.copy()
|
|
134
|
+
|
|
135
|
+
def clear(self) -> None:
|
|
136
|
+
"""Clear all registered extensions (mainly for testing)"""
|
|
137
|
+
self._extensions.clear()
|
|
@@ -121,7 +121,7 @@ class FigpackView:
|
|
|
121
121
|
)
|
|
122
122
|
print("** Development mode **")
|
|
123
123
|
print(
|
|
124
|
-
f"For development, run figpack-figure in dev mode and use http://localhost:5173?
|
|
124
|
+
f"For development, run figpack-figure in dev mode and use http://localhost:5173?figure=http://localhost:{port}/{_local_figure_name}/"
|
|
125
125
|
)
|
|
126
126
|
print("")
|
|
127
127
|
|