marimo-jupyter-extension 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.
Files changed (30) hide show
  1. marimo_jupyter_extension/__init__.py +58 -0
  2. marimo_jupyter_extension/config.py +82 -0
  3. marimo_jupyter_extension/convert.py +31 -0
  4. marimo_jupyter_extension/etc/jupyter_server_config.d/marimo_jupyter_extension.json +7 -0
  5. marimo_jupyter_extension/executable.py +63 -0
  6. marimo_jupyter_extension/exporter.py +70 -0
  7. marimo_jupyter_extension/handlers.py +179 -0
  8. marimo_jupyter_extension/icon.svg +84 -0
  9. marimo_jupyter_extension/labextension/package.json +86 -0
  10. marimo_jupyter_extension/labextension/schemas/@marimo-team/jupyter-extension/package.json.orig +77 -0
  11. marimo_jupyter_extension/labextension/schemas/@marimo-team/jupyter-extension/plugin.json +7 -0
  12. marimo_jupyter_extension/labextension/static/510.5a6289689ce7a0453fc4.js +1 -0
  13. marimo_jupyter_extension/labextension/static/remoteEntry.705371d091b7d314fd9e.js +1 -0
  14. marimo_jupyter_extension/labextension/static/style.js +4 -0
  15. marimo_jupyter_extension/labextension/static/third-party-licenses.json +16 -0
  16. marimo_jupyter_extension-0.1.0.data/data/etc/jupyter/jupyter_server_config.d/marimo_jupyter_extension.json +7 -0
  17. marimo_jupyter_extension-0.1.0.data/data/share/jupyter/labextensions/@marimo-team/jupyter-extension/install.json +5 -0
  18. marimo_jupyter_extension-0.1.0.data/data/share/jupyter/labextensions/@marimo-team/jupyter-extension/package.json +86 -0
  19. marimo_jupyter_extension-0.1.0.data/data/share/jupyter/labextensions/@marimo-team/jupyter-extension/schemas/@marimo-team/jupyter-extension/package.json.orig +77 -0
  20. marimo_jupyter_extension-0.1.0.data/data/share/jupyter/labextensions/@marimo-team/jupyter-extension/schemas/@marimo-team/jupyter-extension/plugin.json +7 -0
  21. marimo_jupyter_extension-0.1.0.data/data/share/jupyter/labextensions/@marimo-team/jupyter-extension/static/510.5a6289689ce7a0453fc4.js +1 -0
  22. marimo_jupyter_extension-0.1.0.data/data/share/jupyter/labextensions/@marimo-team/jupyter-extension/static/remoteEntry.705371d091b7d314fd9e.js +1 -0
  23. marimo_jupyter_extension-0.1.0.data/data/share/jupyter/labextensions/@marimo-team/jupyter-extension/static/style.js +4 -0
  24. marimo_jupyter_extension-0.1.0.data/data/share/jupyter/labextensions/@marimo-team/jupyter-extension/static/third-party-licenses.json +16 -0
  25. marimo_jupyter_extension-0.1.0.dist-info/METADATA +174 -0
  26. marimo_jupyter_extension-0.1.0.dist-info/RECORD +30 -0
  27. marimo_jupyter_extension-0.1.0.dist-info/WHEEL +4 -0
  28. marimo_jupyter_extension-0.1.0.dist-info/entry_points.txt +5 -0
  29. marimo_jupyter_extension-0.1.0.dist-info/licenses/LICENSE +201 -0
  30. marimo_jupyter_extension-0.1.0.dist-info/licenses/NOTICE +21 -0
@@ -0,0 +1,58 @@
1
+ """Jupyter extension to proxy Marimo.
2
+
3
+ This module provides the setup function for jupyter-server-proxy to launch
4
+ marimo.
5
+ """
6
+
7
+ import base64
8
+ import os
9
+ import secrets
10
+
11
+ from .config import get_config
12
+ from .executable import get_marimo_command
13
+
14
+ __version__ = "0.1.0"
15
+ __all__ = ["setup_marimoserver"]
16
+
17
+
18
+ def setup_marimoserver():
19
+ """Setup function for jupyter-server-proxy.
20
+
21
+ Returns a configuration dictionary that jupyter-server-proxy uses to
22
+ launch and proxy marimo.
23
+ """
24
+ token = secrets.token_urlsafe(16)
25
+ config = get_config()
26
+
27
+ # Get marimo command based on config
28
+ marimo_cmd = get_marimo_command(config)
29
+
30
+ return {
31
+ "command": [
32
+ *marimo_cmd,
33
+ "edit",
34
+ "--sandbox",
35
+ "--port",
36
+ "{port}",
37
+ "--base-url",
38
+ config.base_url,
39
+ "--token",
40
+ "--token-password",
41
+ token,
42
+ "--headless",
43
+ "--no-skew-protection",
44
+ ],
45
+ "timeout": config.timeout,
46
+ "absolute_url": True,
47
+ "request_headers_override": {
48
+ "Authorization": "Basic "
49
+ + base64.b64encode(b" :" + token.encode()).decode()
50
+ },
51
+ "launcher_entry": {
52
+ "icon_path": os.path.join(
53
+ os.path.dirname(os.path.abspath(__file__)), "icon.svg"
54
+ ),
55
+ # Disabled - our labextension provides the launcher
56
+ "enabled": False,
57
+ },
58
+ }
@@ -0,0 +1,82 @@
1
+ """Configuration for marimo-jupyter-extension."""
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+ from traitlets import Int, Unicode, default
8
+ from traitlets.config import Configurable
9
+
10
+ DEFAULT_TIMEOUT = 60
11
+
12
+
13
+ class MarimoProxyConfig(Configurable):
14
+ """Configuration for marimo-jupyter-extension.
15
+
16
+ Can be configured in jupyterhub_config.py:
17
+ c.MarimoProxyConfig.marimo_path = "/opt/bin/marimo"
18
+ c.MarimoProxyConfig.uvx_path = "/usr/local/bin/uvx" # enables uvx mode
19
+ c.MarimoProxyConfig.timeout = 120
20
+ """
21
+
22
+ marimo_path = Unicode(
23
+ allow_none=True,
24
+ help="Explicit path to marimo executable. If not set, searches PATH.",
25
+ ).tag(config=True)
26
+
27
+ uvx_path = Unicode(
28
+ allow_none=True,
29
+ help=(
30
+ "Path to uvx executable. If set, uses 'uvx marimo' instead "
31
+ "of marimo directly."
32
+ ),
33
+ ).tag(config=True)
34
+
35
+ timeout = Int(
36
+ DEFAULT_TIMEOUT,
37
+ help="Timeout in seconds for marimo to start.",
38
+ ).tag(config=True)
39
+
40
+ @default("marimo_path")
41
+ def _default_marimo_path(self):
42
+ return None
43
+
44
+ @default("uvx_path")
45
+ def _default_uvx_path(self):
46
+ # Derive uvx from $UV if set (standard uv environment variable)
47
+ if uv_path := os.environ.get("UV"):
48
+ return str(Path(uv_path).parent / "uvx")
49
+ return None
50
+
51
+ @default("timeout")
52
+ def _default_timeout(self):
53
+ return DEFAULT_TIMEOUT
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class Config:
58
+ """Resolved configuration (immutable snapshot)."""
59
+
60
+ marimo_path: str | None # Explicit marimo path
61
+ uvx_path: str | None # If set, use uvx mode
62
+ timeout: int
63
+ base_url: str
64
+
65
+
66
+ def get_config(traitlets_config: MarimoProxyConfig | None = None) -> Config:
67
+ """Load configuration from Traitlets or defaults."""
68
+ # Use traitlets config if provided, otherwise create default
69
+ cfg = traitlets_config or MarimoProxyConfig()
70
+
71
+ return Config(
72
+ marimo_path=cfg.marimo_path,
73
+ uvx_path=cfg.uvx_path,
74
+ timeout=cfg.timeout,
75
+ base_url=_get_base_url(),
76
+ )
77
+
78
+
79
+ def _get_base_url() -> str:
80
+ """Get base URL, gracefully handling non-JupyterHub environments."""
81
+ prefix = os.environ.get("JUPYTERHUB_SERVICE_PREFIX", "/")
82
+ return f"{prefix}marimo"
@@ -0,0 +1,31 @@
1
+ """Shared notebook conversion logic."""
2
+
3
+ import subprocess
4
+
5
+ from .config import get_config
6
+ from .executable import get_marimo_command
7
+
8
+
9
+ def convert_notebook_to_marimo(input_path: str, output_path: str) -> None:
10
+ """Convert a Jupyter notebook to marimo format.
11
+
12
+ Args:
13
+ input_path: Path to input .ipynb file
14
+ output_path: Path to output .py file
15
+
16
+ Raises:
17
+ RuntimeError: If conversion fails
18
+ """
19
+ config = get_config()
20
+ marimo_cmd = get_marimo_command(config)
21
+
22
+ result = subprocess.run(
23
+ [*marimo_cmd, "convert", input_path, "-o", output_path],
24
+ capture_output=True,
25
+ text=True,
26
+ )
27
+
28
+ if result.returncode != 0:
29
+ raise RuntimeError(
30
+ f"marimo convert failed: {result.stderr or result.stdout}"
31
+ )
@@ -0,0 +1,7 @@
1
+ {
2
+ "ServerApp": {
3
+ "jpserver_extensions": {
4
+ "marimo_jupyter_extension.handlers": true
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,63 @@
1
+ """Executable discovery for marimo."""
2
+
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ from .config import Config
7
+
8
+ COMMON_LOCATIONS = [
9
+ "~/.local/bin/marimo",
10
+ "/opt/bin/marimo",
11
+ "/usr/local/bin/marimo",
12
+ ]
13
+
14
+
15
+ def get_marimo_command(config: Config) -> list[str]:
16
+ """Get marimo command based on configuration.
17
+
18
+ Args:
19
+ config: Config dataclass with marimo_path and uvx_path
20
+
21
+ Logic:
22
+ - If uvx_path is set → use uvx mode: [uvx_path, 'marimo']
23
+ - If marimo_path is set → use it directly: [marimo_path]
24
+ - Otherwise → search PATH and common locations
25
+ - If not found → raise FileNotFoundError
26
+
27
+ Returns:
28
+ Command as list, e.g. ['/usr/bin/marimo'] or ['/usr/bin/uvx', 'marimo']
29
+ """
30
+ # uvx mode (opt-in via explicit uvx_path)
31
+ if config.uvx_path:
32
+ return [config.uvx_path, "marimo[sandbox]==0.19.4"]
33
+
34
+ # Explicit marimo path
35
+ if config.marimo_path:
36
+ return [config.marimo_path]
37
+
38
+ # Search for marimo
39
+ if found := _find_marimo():
40
+ return [found]
41
+
42
+ raise FileNotFoundError(
43
+ "marimo executable not found.\n"
44
+ "Solutions:\n"
45
+ " - Install marimo: pip install marimo\n"
46
+ " - Configure MarimoProxyConfig.marimo_path in jupyterhub_config.py\n"
47
+ " - Configure MarimoProxyConfig.uvx_path to use uvx marimo"
48
+ )
49
+
50
+
51
+ def _find_marimo() -> str | None:
52
+ """Search for marimo in PATH and common locations."""
53
+ # Check system PATH
54
+ if which := shutil.which("marimo"):
55
+ return which
56
+
57
+ # Check common locations
58
+ for location in COMMON_LOCATIONS:
59
+ candidate = Path(location).expanduser()
60
+ if candidate.exists() and candidate.is_file():
61
+ return str(candidate)
62
+
63
+ return None
@@ -0,0 +1,70 @@
1
+ """nbconvert exporter for marimo format."""
2
+
3
+ import json
4
+ import tempfile
5
+ from pathlib import Path
6
+
7
+ from nbconvert.exporters import Exporter
8
+ from traitlets import default
9
+
10
+ from .convert import convert_notebook_to_marimo
11
+
12
+
13
+ class MarimoExporter(Exporter):
14
+ """Export Jupyter notebooks to marimo .py format.
15
+
16
+ This exporter integrates with JupyterLab's "File > Save and Export
17
+ Notebook As" menu.
18
+ """
19
+
20
+ export_from_notebook = "marimo"
21
+ output_mimetype = "text/x-python"
22
+
23
+ @default("file_extension")
24
+ def _file_extension_default(self):
25
+ return ".py"
26
+
27
+ def from_notebook_node(self, nb, resources=None, **kwargs):
28
+ """Convert notebook to marimo format.
29
+
30
+ Args:
31
+ nb: NotebookNode object
32
+ resources: Additional resources (paths, metadata, etc.)
33
+ **kwargs: Additional keyword arguments
34
+
35
+ Returns:
36
+ Tuple of (output, resources) where output is the converted string
37
+ """
38
+ if resources is None:
39
+ resources = {}
40
+
41
+ # Write notebook to temporary file
42
+ with tempfile.NamedTemporaryFile(
43
+ mode="w", suffix=".ipynb", delete=False
44
+ ) as tmp_in:
45
+ json.dump(nb, tmp_in)
46
+ tmp_in_path = tmp_in.name
47
+
48
+ # Create temporary output file
49
+ with tempfile.NamedTemporaryFile(
50
+ mode="w", suffix=".py", delete=False
51
+ ) as tmp_out:
52
+ tmp_out_path = tmp_out.name
53
+
54
+ try:
55
+ # Convert notebook using shared conversion logic
56
+ convert_notebook_to_marimo(tmp_in_path, tmp_out_path)
57
+
58
+ # Read converted output
59
+ with open(tmp_out_path) as f:
60
+ output = f.read()
61
+
62
+ # Set output extension for nbconvert handler
63
+ resources["output_extension"] = ".py"
64
+
65
+ return (output, resources)
66
+
67
+ finally:
68
+ # Clean up temporary files
69
+ Path(tmp_in_path).unlink(missing_ok=True)
70
+ Path(tmp_out_path).unlink(missing_ok=True)
@@ -0,0 +1,179 @@
1
+ """Jupyter server extension handlers for marimo tools."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ from jupyter_server.base.handlers import JupyterHandler
7
+ from jupyter_server.utils import url_path_join
8
+ from tornado import web
9
+
10
+ from .convert import convert_notebook_to_marimo
11
+
12
+
13
+ def _find_marimo_proxy_state(web_app):
14
+ """Find the marimo proxy handler's state dict.
15
+
16
+ Searches through the web_app's registered handlers to find the
17
+ jupyter-server-proxy handler for marimo and returns its state dict.
18
+ """
19
+ for host_pattern, handlers in web_app.handlers:
20
+ for spec in handlers:
21
+ if hasattr(spec, "kwargs") and "state" in spec.kwargs:
22
+ if "marimo" in str(spec.regex.pattern):
23
+ return spec.kwargs["state"]
24
+ return None
25
+
26
+
27
+ class ConvertHandler(JupyterHandler):
28
+ """Handler for converting Jupyter notebooks to marimo format."""
29
+
30
+ @web.authenticated
31
+ async def post(self):
32
+ """Convert a Jupyter notebook to marimo format.
33
+
34
+ POST /marimo-tools/convert
35
+ Body: {"input": "notebook.ipynb", "output": "notebook.py"}
36
+ """
37
+ data = json.loads(self.request.body)
38
+ input_path = data.get("input")
39
+ output_path = data.get("output")
40
+
41
+ if not input_path or not output_path:
42
+ self.set_status(400)
43
+ self.finish(
44
+ {"success": False, "error": "Missing input or output path"}
45
+ )
46
+ return
47
+
48
+ try:
49
+ convert_notebook_to_marimo(input_path, output_path)
50
+ self.finish({"success": True, "output": output_path})
51
+ except RuntimeError as e:
52
+ self.set_status(500)
53
+ self.finish({"success": False, "error": str(e)})
54
+
55
+
56
+ class RestartHandler(JupyterHandler):
57
+ """Handler for restarting the marimo server."""
58
+
59
+ @web.authenticated
60
+ async def post(self):
61
+ """Restart the marimo server.
62
+
63
+ POST /marimo-tools/restart
64
+
65
+ Finds the jupyter-server-proxy handler's state, kills the current
66
+ process, and clears the state so the next request spawns a new process.
67
+ """
68
+ proxy_state = _find_marimo_proxy_state(self.application)
69
+
70
+ if not proxy_state:
71
+ self.set_status(503)
72
+ self.finish(
73
+ {"success": False, "error": "Proxy not initialized yet"}
74
+ )
75
+ return
76
+
77
+ try:
78
+ async with proxy_state["proc_lock"]:
79
+ proc = proxy_state.get("proc")
80
+ if proc and proc != "process not managed":
81
+ try:
82
+ await proc.kill()
83
+ except Exception:
84
+ pass # Already dead
85
+ # Clear the process reference so next request spawns new one
86
+ if "proc" in proxy_state:
87
+ del proxy_state["proc"]
88
+
89
+ self.finish({"success": True, "message": "Server restarting"})
90
+ except Exception as e:
91
+ self.set_status(500)
92
+ self.finish({"success": False, "error": str(e)})
93
+
94
+
95
+ class CreateStubHandler(JupyterHandler):
96
+ """Handler for creating marimo notebook stub files."""
97
+
98
+ @web.authenticated
99
+ async def post(self):
100
+ """Create a marimo notebook stub with PEP 723 metadata.
101
+
102
+ POST /marimo-tools/create-stub
103
+ Body: {"path": "notebook.py", "venv": "/path/to/python"}
104
+ """
105
+ data = json.loads(self.request.body)
106
+ path = data.get("path")
107
+ venv = data.get("venv")
108
+
109
+ if not path:
110
+ self.set_status(400)
111
+ self.finish({"success": False, "error": "Missing path"})
112
+ return
113
+
114
+ # Build stub content
115
+ lines = []
116
+
117
+ # Add PEP 723 header if venv is specified
118
+ if venv:
119
+ # Extract venv directory from python executable path
120
+ # e.g., /path/to/venv/bin/python3.12 -> /path/to/venv
121
+ venv_path = Path(venv)
122
+ if venv_path.parent.name == "bin":
123
+ venv_path = venv_path.parent.parent
124
+ lines.extend(
125
+ [
126
+ "# /// script",
127
+ "# [tool.marimo.venv]",
128
+ f'# path = "{venv_path}"',
129
+ "# ///",
130
+ "",
131
+ ]
132
+ )
133
+
134
+ # Add marimo app boilerplate
135
+ lines.extend(
136
+ [
137
+ "import marimo",
138
+ "",
139
+ '__generated_with = "0.19.2"',
140
+ 'app = marimo.App(width="medium")',
141
+ "",
142
+ "",
143
+ 'if __name__ == "__main__":',
144
+ " app.run()",
145
+ "",
146
+ ]
147
+ )
148
+
149
+ content = "\n".join(lines)
150
+
151
+ try:
152
+ file_path = Path(path)
153
+ file_path.write_text(content)
154
+ self.finish({"success": True, "path": path})
155
+ except Exception as e:
156
+ self.set_status(500)
157
+ self.finish({"success": False, "error": str(e)})
158
+
159
+
160
+ def _jupyter_server_extension_points():
161
+ """Return the server extension points for this package."""
162
+ return [{"module": "marimo_jupyter_extension.handlers"}]
163
+
164
+
165
+ def _load_jupyter_server_extension(server_app):
166
+ """Load the jupyter server extension."""
167
+ base_url = server_app.web_app.settings["base_url"]
168
+ server_app.web_app.add_handlers(
169
+ ".*",
170
+ [
171
+ (url_path_join(base_url, "marimo-tools/convert"), ConvertHandler),
172
+ (url_path_join(base_url, "marimo-tools/restart"), RestartHandler),
173
+ (
174
+ url_path_join(base_url, "marimo-tools/create-stub"),
175
+ CreateStubHandler,
176
+ ),
177
+ ],
178
+ )
179
+ server_app.log.info("marimo-jupyter-extension tools extension loaded")
@@ -0,0 +1,84 @@
1
+ <?xml version="1.0" encoding="utf-8" standalone="no"?>
2
+ <svg
3
+ width="97.645645"
4
+ height="100.87186"
5
+ viewbox="0 0 73.234231 75.653891"
6
+ version="1.1"
7
+ id="svg1568"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ xmlns:svg="http://www.w3.org/2000/svg">
10
+ <defs
11
+ id="defs1572" />
12
+ <g
13
+ id="surface1"
14
+ transform="matrix(0.32633463,0,0,0.32633463,-159.18374,-120.45361)">
15
+ <path
16
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
17
+ d="m 517.37109,413.55859 c 1.51172,-1.78125 3.1875,-4.15625 2.10547,-6.26172 6.04688,-3.5625 11.39063,-8.26171 15.76172,-13.76562 -2.375,1.89062 -4.96484,3.40234 -7.71875,4.58984 20.67578,-20.40625 51.76953,-29.6914 80.32422,-23.96875 -22.99609,-0.86328 -46.42188,5.23438 -65.42578,18.19141 -19.05469,12.90234 -33.51953,32.71094 -39.07813,55.05859 8.41797,-24.45312 26.17969,-45.55859 48.85157,-57.97265 -1.78125,1.83593 -3.56641,3.72265 -5.39844,5.55859 8.63672,-3.5625 16.46094,-8.90625 25.04687,-12.63281 25.20703,-11.01172 55.54297,-6.85547 79.02344,7.5039 23.48047,14.35938 40.37891,37.89454 50.20313,63.58985 4.3164,11.22656 7.33984,23.69531 3.50781,35.08594 -0.86328,-14.84375 -5.72266,-29.14844 -12.03906,-42.58985 -10.47266,-22.40234 -25.47657,-43.39844 -46.58204,-56.24609 -21.10546,-12.79297 -49.01562,-16.19531 -70.60546,-4.15625 11.0664,-1.1875 22.23828,-1.83594 33.19531,-0.26953 20.62109,2.91406 39.67578,13.71093 54.14062,28.66015 14.41407,15.00782 24.40235,33.95313 30.28516,53.8711 5.72266,19.48828 7.60937,40.97265 0.26953,59.97265 21.42969,-34.65625 17.86719,-82.91015 -8.3125,-114.05859 -10.95703,-13.00781 -25.15625,-22.99609 -40.48437,-30.22656 -17.70704,-8.3125 -37.14063,-13.11719 -56.67969,-12.57813 -19.53906,0.53906 -39.08203,6.64063 -54.67969,18.40625 12.95313,-11.44531 29.84766,-17.92187 47.01563,-19.70312 17.21875,-1.72657 34.65234,1.08203 51.01171,6.58593 34.00391,11.55079 64.98829,37.03125 74.97657,71.52344 2.53515,8.85156 3.66797,18.02735 4.69531,27.20703 0.86328,7.9336 1.56641,15.86719 1.40234,23.85547 -0.7539,39.46094 -25.96484,78.00391 -62.72265,92.41406 -10.41797,4.05079 -21.42969,6.26172 -32.4961,7.82813 -17.70703,2.48047 -36.05859,3.23828 -53.11718,-2.05078 -8.79688,-2.75391 -17.00391,-7.01953 -24.61329,-12.25391 -31.52343,-21.59375 -51.4414,-59.21484 -51.4414,-97.43359 0,-6.80078 0.59375,-13.54688 1.51172,-20.24219 0.80859,-6.15234 1.88672,-12.25391 3.66797,-18.19141 2.69921,-8.74609 6.91015,-17.0039 12.47265,-24.28906 0.96875,-0.59375 1.94141,-1.24219 2.85938,-1.89062 -12.79297,27.04297 -20.40625,59 -8.69141,86.58203 -4.21094,-17.97266 -4.48047,-36.8125 -0.75391,-54.84375 1.1875,-0.42969 2.375,-0.91406 3.50782,-1.40235 0.0547,-1.1875 0.10547,-2.32031 0.16015,-3.50781 -0.86328,0.97266 -1.67187,1.94141 -2.53515,2.85938 1.99609,-14.30469 8.14843,-27.85157 17.3789,-38.8086 m 8.69141,-0.70312 c 3.34766,-2.26563 6.69531,-4.53516 10.04297,-6.80078 -2.10547,4.26562 -5.99219,7.60937 -10.52735,9.12109 -21.05078,21.75391 -27.53125,54.19531 -25.04687,84.31641 0.97266,11.39062 3.29297,23.21093 10.63281,31.95703 -5.125,-21.05469 -5.39844,-43.29297 -0.69922,-64.45313 0.91797,-0.0547 1.23828,1.13282 1.23828,2.05078 0.37891,14.19922 -2.05078,28.39454 -1.02343,42.58985 1.02343,14.14453 6.3164,29.09765 18.1914,36.8164 9.28516,6.04297 18.51563,12.08985 27.7461,18.13672 -21.59375,-6.91015 -40.4336,-21.91797 -52.08985,-41.34765 3.12891,8.47265 7.5,16.51562 12.89844,23.75 -0.69922,-1.94532 -1.34766,-3.94141 -2.05078,-5.9375 5.45312,5.1289 10.95703,10.14843 16.41016,15.22265 3.99609,3.72266 10.25781,7.5 14.35937,3.9375 0.64844,1.35157 -0.53906,3.02344 -1.99609,3.40235 -1.46094,0.43359 -3.02344,0 -4.48047,-0.4336 6.53125,4.69922 13.33203,9.01563 20.34765,12.95703 -1.02343,0.16016 -1.34765,1.67188 -0.69921,2.53516 0.64453,0.86719 1.72656,1.08203 2.75,1.35156 25.75,5.5586 53.17187,3.50782 77.78515,-5.99218 -21.16015,4.96484 -43.72265,6.10156 -64.23437,-1.07813 -17.32813,-6.04687 -32.4961,-17.97656 -42.42969,-33.35937 -3.18359,-4.96875 -5.82813,-10.20313 -8.25781,-15.54688 -7.82813,-17.59766 -12.79297,-36.8125 -11.39063,-56.03125 1.40625,-19.21484 9.82422,-38.37891 24.94141,-50.25391 1.5625,-1.24218 3.50781,-2.42968 5.44922,-1.94531 -14.89844,10.79688 -23.75,28.66406 -26.07032,46.91016 -2.32031,18.29687 1.45704,36.86719 8.09766,54.03125 4.75,12.14844 11.0625,23.91406 19.75391,33.6289 3.72656,4.15625 7.9375,7.9375 12.73828,10.79688 8.80078,5.28906 19.21875,7.28906 29.42187,8.58203 13.8711,1.78125 27.85157,2.53906 41.83203,2.21484 -17.65234,4.15625 -36.11328,1.1875 -53.98046,-1.78125 30.33984,11.39063 65.91015,4.42579 91.98437,-14.73828 5.17969,-3.83203 10.09375,-8.09375 14.03125,-13.22265 6.04688,-7.88282 9.66406,-17.48828 11.5,-27.26172 4.75,-25.47656 -2.05078,-52.08985 -14.41406,-74.92188 -8.58203,-15.87109 -19.97266,-30.55078 -34.97656,-40.53906 -14.03516,-9.28516 -30.71485,-14.19531 -47.50391,-15.11328 -25.80078,-1.51172 -53.11328,7.01562 -70.28125,26.44922 m 576.75,588.17969 c 11.60547,2.91406 23.32031,5.01953 35.19531,6.42578 -14.57422,3.34375 -29.58203,-1.78125 -43.66797,-6.80078 -8.3164,-2.91797 -16.73437,-5.94141 -24.45312,-10.25782 -18.67969,-10.36328 -32.71485,-28.125 -40.86328,-47.8789 -1.40235,-3.50781 -2.69922,-7.01953 -3.77735,-10.58203 -0.10937,-0.42969 -0.43359,0.59375 0,0.3789 0.42969,-0.16015 0.375,-0.80859 0.16016,-1.24218 -4.85938,-11.71094 -7.82813,-24.28907 -8.74609,-36.92188 -0.91797,19.10938 3.83203,38.32422 12.46875,55.4375 9.39453,18.62109 23.48437,34.98047 41.1875,46.04297 17.65234,11.06641 38.91796,16.67969 59.64843,14.68359 29.14844,-2.85937 54.73438,-19.86328 78.21485,-37.30078 -10.36328,13.71094 -26.07032,22.1875 -41.34766,30.17578 26.39453,-7.39453 47.55469,-28.28515 60.29688,-52.57422 -6.31641,10.79297 -16.19532,19.26954 -26.9375,25.63672 -10.79688,6.3711 -22.61719,10.79688 -34.4375,15.0625 -5.1836,1.89063 -10.41797,3.72266 -15.76172,5.23438 -15.27735,4.375 -31.36328,5.88672 -47.17969,4.48047 m 107.95703,-36.48828 c 2.69922,-2.53907 5.34766,-5.12891 7.50391,-8.04297 3.40234,-4.69532 5.66797,-10.14844 7.82812,-15.54688 2.91406,-7.33984 5.88281,-14.73828 6.64063,-22.61719 -1.40625,1.24219 -1.89063,3.07813 -2.42969,4.85938 -7.17969,24.88281 -24.9375,46.58203 -47.98828,58.45703 10.85156,-2.42969 20.1875,-9.5 28.44531,-17.10937 m -131.92187,26.44921 c 1.13281,0.26954 2.53515,0.75391 2.58984,1.89063 0.42969,-0.75781 1.72656,-0.75781 2.21094,0 0.21875,-0.0547 0.0547,-0.43359 -0.21485,-0.54297 -1.61718,-0.86328 -3.23828,-1.72656 -4.80468,-2.58984 -6.53125,-3.50782 -12.52344,-7.87891 -17.86719,-12.95313 -0.64844,1.40235 -0.16016,3.1836 1.08203,4.04688 2.375,1.72656 4.75,3.45312 7.125,5.18359 2.85937,2.375 6.26172,4.10156 9.87891,4.96484 m 155.89062,-81.61718 c -0.21484,3.99609 -0.43359,8.04297 -0.70312,12.03906 -0.0508,0.53906 -0.0508,1.07812 -0.0508,1.61719 -0.0547,0.97265 -0.10937,1.94531 -0.21875,2.86328 -0.0547,1.1875 -0.10546,2.26562 -0.10546,3.45312 2.26562,-9.17578 3.07421,-18.78515 2.375,-28.17578 -0.26954,-0.10937 -0.54297,-0.26953 -0.75782,-0.43359 -0.16015,2.85937 -0.3789,5.72265 -0.53906,8.63672 m -209.8164,-59.16016 c -0.3789,0.69922 -0.70312,1.40234 -1.02734,2.10547 0.37891,0.48437 1.07813,-0.10938 1.24219,-0.70313 0.16015,-0.53906 0.32422,-1.29687 0.91797,-1.34765 -0.26953,-0.27344 -0.53907,-0.54297 -0.8086,-0.8125 -0.10937,0.21875 -0.21875,0.53906 -0.10937,0.75781 m 39.02734,-44.80469 c 0.53906,-0.42968 1.07813,-0.86328 1.61719,-1.34765 -0.91797,-0.37891 -2.10547,0.10547 -2.48047,1.07812 0.26953,0.10938 0.53906,0.21485 0.86328,0.26953 m 1.67188,-2.53515 c 1.1875,0.26953 2.53906,-0.0547 3.50781,-0.86328 -1.23828,-0.0547 -2.48047,0.21484 -3.50781,0.86328 m -42.96485,54.78906 c -0.0547,-0.26953 -0.10937,-0.53906 -0.10937,-0.75781 -1.02735,0.3789 -1.51172,1.78125 -0.91797,2.69922 0.21484,-0.69922 0.53906,-1.34766 1.02734,-1.94141 m -6.10156,27.52734 c -0.26953,0.37891 -0.0547,1.02735 0.43359,1.08203 0.26954,-0.86328 0.21485,-1.78125 -0.0547,-2.69921 -0.53907,0.26953 -0.70313,1.02343 -0.37891,1.51171"
18
+ id="path1533" />
19
+ <path
20
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
21
+ d="m 527.08984,398.39062 c 0.10547,0.16016 0.21485,0.375 0.32422,0.53907 -0.48828,0.69922 -1.40625,1.02343 -2.21484,0.80859 0.43359,-0.59375 1.08203,-1.02344 1.78125,-1.1875"
22
+ id="path1535" />
23
+ <path
24
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
25
+ d="m 522.82422,401.84375 c 0.75781,-0.26953 1.5664,-0.43359 2.375,-0.37891 -0.16016,1.1875 -2.16016,1.51172 -2.64453,0.37891"
26
+ id="path1537" />
27
+ <path
28
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
29
+ d="m 520.125,399.35937 c 0.48828,-0.7539 1.40234,-1.29296 2.375,-1.1875 0.21484,0.54297 0.0547,1.1875 -0.37891,1.56641 -0.7539,0.10938 -1.67187,0.21484 -2.21093,-0.37891"
30
+ id="path1539" />
31
+ <path
32
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
33
+ d="m 671.42969,402.92187 c 0.32422,-0.0508 0.64843,0.26954 0.59375,0.59375 -0.0547,0.32422 -0.42969,0.54297 -0.75391,0.4336 -0.48828,-0.53906 -0.97266,-1.13281 -1.45703,-1.72656 0.53906,-0.4336 1.50781,-0.0547 1.61719,0.59375"
34
+ id="path1541" />
35
+ <path
36
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
37
+ d="m 524.98437,394.93359 c 0.37891,-0.42968 1.13282,-0.42968 1.56641,-0.0547 -0.70312,0.70312 -1.62109,1.1875 -2.59375,1.51171 -0.10547,-0.59375 0.43359,-1.29296 1.02734,-1.24218"
38
+ id="path1543" />
39
+ <path
40
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
41
+ d="m 516.13281,445.13672 c 0.48438,-0.70313 1.78125,-0.4336 1.94141,0.42969 -0.64844,-0.10547 -1.34766,0.10937 -1.78125,0.59375 -0.26953,0.10937 -0.70313,-0.10547 -0.70313,-0.42969 -0.0508,-0.26953 0.27344,-0.59375 0.59375,-0.54297"
42
+ id="path1545" />
43
+ <path
44
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
45
+ d="m 547.00781,410.58984 c 0.26953,-0.10937 0.64844,-0.21875 0.91797,-0.10937 0.32422,0.10937 0.48438,0.59375 0.26953,0.80859 -1.02734,0.64844 -2.21484,1.02735 -3.40234,1.08203 0.54297,-0.75781 1.35156,-1.35156 2.21484,-1.78125"
46
+ id="path1547" />
47
+ <path
48
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
49
+ d="m 562.92969,579.27344 c 0.54297,0.3789 1.13672,0.75781 1.67578,1.13281 -1.1875,0.48828 -2.58985,0.21875 -3.5625,-0.64453 -0.21485,-0.37891 0.21484,-0.91797 0.59375,-0.91797 0.48437,-0.0547 0.91797,0.21484 1.29297,0.42969"
50
+ id="path1549" />
51
+ <path
52
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
53
+ d="m 538.26172,405.62109 c -0.26953,-0.80859 -0.0547,-1.78125 0.59375,-2.375 0.43359,-0.10547 0.86328,-0.42968 1.02734,-0.91797 0.69922,0.26954 1.34766,0.54297 2.05078,0.8125 -1.1875,0.8086 -2.42968,1.61719 -3.67187,2.48047"
54
+ id="path1551" />
55
+ <path
56
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
57
+ d="m 511.92187,463.59766 c 0,-0.32422 0,-0.64844 0,-0.97266 0.53907,-0.26953 1.13282,-0.26953 1.61719,-0.0547 -0.96875,1.02735 -1.51172,2.42969 -1.40234,3.83203 -0.59375,-0.42968 -1.24219,-0.91797 -1.89063,-1.34765 0.54297,-0.4336 1.13672,-0.91797 1.73047,-1.40625"
58
+ id="path1553" />
59
+ <path
60
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
61
+ d="m 508.35937,415.93359 c 0.21485,-1.67578 1.1875,-3.1875 2.53516,-4.21093 0.37891,-0.26954 0.97266,-0.4336 1.1875,-0.0547 -1.1875,1.1875 -2.16016,2.53515 -2.85937,4.04687 -0.16407,0.27344 -0.59375,0.4336 -0.86329,0.21875"
62
+ id="path1555" />
63
+ <path
64
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
65
+ d="m 513.91797,449.39844 c 1.07812,-0.8086 2.10547,-1.61719 3.18359,-2.42969 -0.26953,1.02734 -0.53906,1.94531 -0.80859,2.96875 -0.37891,0.21875 -0.64844,0.59766 -1.02344,0.8125 -0.37891,0.21484 -0.91797,0.26953 -1.1875,-0.10938 -0.21875,-0.3789 0.375,-0.91796 0.59375,-0.53906 -0.21875,-0.21484 -0.48828,-0.43359 -0.75781,-0.70312"
66
+ id="path1557" />
67
+ <path
68
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
69
+ d="m 555.05078,407.56641 c -0.10937,0.26953 -0.21484,0.53906 -0.10937,0.86328 -0.86329,0.16015 -1.67188,0.32422 -2.42969,0.48437 -0.10547,0.32422 -0.21485,0.64844 -0.32031,0.97266 -0.64844,-0.0547 -1.29688,-0.0547 -1.89063,-0.0547 -0.53906,-0.26953 0,-1.02344 0.53906,-1.29297 1.07813,-0.54297 2.21485,-0.86719 3.40235,-1.02734 -0.16407,-0.26953 -0.32422,-0.48438 -0.48828,-0.75391 0.86328,-0.70312 1.89062,-1.1875 3.02343,-1.51172 0.21485,0.375 0.26953,0.8086 0.16407,1.23828 -0.37891,0.16407 -0.75782,0.21875 -1.13672,0.10938 0.0547,0.26953 0.10937,0.53906 0.16406,0.86328 -0.37891,0.10938 -0.64844,0.16406 -0.91797,0.10938"
70
+ id="path1559" />
71
+ <path
72
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
73
+ d="m 512.83984,412.64062 c 0.42969,-0.26953 1.02344,-0.3789 1.51172,-0.26953 -0.21875,-0.26953 -0.3789,-0.54297 -0.54297,-0.8125 0.54297,-0.10547 1.08203,-0.21484 1.67578,-0.26953 0.21485,-0.53906 0.21485,-1.1875 0,-1.72656 0.59375,-0.0547 1.24219,0.0547 1.78125,0.26953 0.16016,0.21484 0.21485,0.53906 0.16016,0.80859 -0.26953,0.10938 -0.59375,0.21875 -0.91797,0.32422 -0.10547,0.54297 -0.26953,1.08203 -0.375,1.6211 -0.26953,0.16015 -0.70312,0.21484 -1.02734,0.10937 -0.48438,2.91406 -3.67188,4.58594 -4.80469,7.28516 -0.37891,0.91797 -0.75391,2.10547 -1.72656,2.10547 -0.26953,-0.32422 -0.21485,-0.91797 0.16015,-1.1875 -0.375,-0.10938 -0.69921,-0.21485 -1.02343,-0.32422 -0.0547,-0.59375 0.91797,-0.42969 1.40234,-0.0547 0.43359,-0.375 0.0547,-1.24219 -0.48437,-1.24219 0.59375,-0.48437 0.97265,-1.23828 1.07812,-1.99609 0.59375,0.0547 1.1875,-0.16016 1.56641,-0.59375 -0.4336,0.10937 -0.91797,-0.21484 -1.02735,-0.69922 0.86328,0.16016 1.73047,-0.64844 1.67578,-1.51172 0.64844,-0.3789 1.29297,-0.75781 1.94141,-1.13672 -0.26953,-0.16015 -0.64844,-0.42968 -1.02344,-0.69922"
74
+ id="path1561" />
75
+ <path
76
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
77
+ d="m 682.38672,415.5 c -1.1875,-1.61719 -2.42969,-3.18359 -3.9375,-4.53516 -2.10547,-1.88672 -4.80469,-3.5625 -5.23828,-6.36718 12.6875,10.74218 22.45703,24.9375 27.90625,40.64453 0.48828,0.43359 -0.0508,1.40625 -0.64453,1.24218 -4.75,-10.95703 -10.85157,-21.42968 -18.08594,-30.98437"
78
+ id="path1563" />
79
+ <path
80
+ style="fill:#1c7361;fill-opacity:1;fill-rule:nonzero;stroke:none"
81
+ d="m 539.88281,406.97266 c 2.21094,-1.78125 4.75,-3.45704 5.66797,-6.15625 0.26953,-0.75391 1.02344,-1.34766 1.72656,-1.78125 10.90235,-6.85547 23.64453,-10.63282 36.48828,-10.84766 -6.42187,1.72656 -12.79296,3.39844 -19.21484,5.12891 -9.98437,2.64453 -17.32812,10.90234 -24.07422,18.78515 -5.83203,6.85547 -11.71484,13.71094 -17.54297,20.61719 -0.70312,1.89062 -1.35156,3.83203 -2.05078,5.72266 -0.26953,0.86328 -0.59375,1.72656 -1.35156,2.26562 -0.69922,0.54297 -1.88672,0.54297 -2.32031,-0.26953 2.58984,-4.53125 5.18359,-9.12109 7.82812,-13.71094 4.21094,-7.125 8.41797,-14.46484 14.84375,-19.7539"
82
+ id="path1565" />
83
+ </g>
84
+ </svg>