python-color-math 0.2.12__tar.gz → 0.2.14__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.
- {python_color_math-0.2.12/python_color_math.egg-info → python_color_math-0.2.14}/PKG-INFO +4 -1
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/__init__.py +7 -1
- python_color_math-0.2.14/color_math/ipython.py +183 -0
- python_color_math-0.2.14/color_math/jupyter.py +99 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/pyproject.toml +3 -2
- {python_color_math-0.2.12 → python_color_math-0.2.14/python_color_math.egg-info}/PKG-INFO +4 -1
- {python_color_math-0.2.12 → python_color_math-0.2.14}/python_color_math.egg-info/SOURCES.txt +3 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/python_color_math.egg-info/requires.txt +4 -0
- python_color_math-0.2.14/tests/test_ipython.py +89 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/.pre-commit-hooks.yaml +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/LICENSE +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/MANIFEST.in +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/README.md +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/__main__.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/adapters.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/colormath.default.json +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/completions.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/config.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/converters/__init__.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/converters/align.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/converters/block.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/converters/derivative.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/converters/equation.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/converters/generic.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/converters/integral.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/converters/limit.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/converters/matrix.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/converters/semantic.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/converters/unicode_converter.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/custom_definitions.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/gui.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/io.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/main.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/mcp.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/mcp_server.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/__init__.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/alignment.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/braket.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/constants.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/delimiters.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/differentials.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/dimensionless.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/frontmatter.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/latex_spans.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/markdown_scanner.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/math_parser.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/physics.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/scanner.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/taxonomy.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/units.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/variable_hash.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/py.typed +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/self_test.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/tutorial.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/undo.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/unicode.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/utils/__init__.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/utils/coloring.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/utils/latex_helpers.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/utils/spans.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/python_color_math/__init__.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/python_color_math/__main__.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/python_color_math.egg-info/dependency_links.txt +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/python_color_math.egg-info/entry_points.txt +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/python_color_math.egg-info/top_level.txt +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/setup.cfg +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_adversarial.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_cli.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_config_tolerance.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_custom_definitions.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_extreme_adversarial.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_features.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_frontmatter.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_fuzz.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_gui.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_mcp_hardcore.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_mcp_server.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_physics.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_unicode.py +0 -0
- {python_color_math-0.2.12 → python_color_math-0.2.14}/tests/test_unicode_converter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-color-math
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.14
|
|
4
4
|
Summary: A fast, local semantic LaTeX colorizer for Markdown, Jupyter, and LaTeX math equations.
|
|
5
5
|
Author: Aditya
|
|
6
6
|
License: MIT License
|
|
@@ -56,10 +56,13 @@ Requires-Dist: mcp>=1.0.0; extra == "mcp"
|
|
|
56
56
|
Provides-Extra: tutorial
|
|
57
57
|
Requires-Dist: rich>=13.0.0; extra == "tutorial"
|
|
58
58
|
Requires-Dist: questionary>=2.0.0; extra == "tutorial"
|
|
59
|
+
Provides-Extra: jupyter
|
|
60
|
+
Requires-Dist: ipython>=8.0.0; extra == "jupyter"
|
|
59
61
|
Provides-Extra: all
|
|
60
62
|
Requires-Dist: mcp>=1.0.0; extra == "all"
|
|
61
63
|
Requires-Dist: rich>=13.0.0; extra == "all"
|
|
62
64
|
Requires-Dist: questionary>=2.0.0; extra == "all"
|
|
65
|
+
Requires-Dist: ipython>=8.0.0; extra == "all"
|
|
63
66
|
Dynamic: license-file
|
|
64
67
|
|
|
65
68
|
# 🎨 Python Color Math
|
|
@@ -5,13 +5,19 @@ from .config import ColorMathOptions
|
|
|
5
5
|
from .converters.block import convert_text
|
|
6
6
|
from .undo import uncolor_text
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
from .ipython import ColorMath, colormath, load_ipython_extension, unload_ipython_extension
|
|
9
|
+
|
|
10
|
+
__version__ = "0.2.14"
|
|
9
11
|
|
|
10
12
|
__all__ = [
|
|
11
13
|
"__version__",
|
|
14
|
+
"ColorMath",
|
|
12
15
|
"ColorMathOptions",
|
|
16
|
+
"colormath",
|
|
13
17
|
"convert_text",
|
|
14
18
|
"detect_format",
|
|
19
|
+
"load_ipython_extension",
|
|
15
20
|
"transform_document",
|
|
16
21
|
"uncolor_text",
|
|
22
|
+
"unload_ipython_extension",
|
|
17
23
|
]
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""IPython and Jupyter integration for color-math.
|
|
2
|
+
|
|
3
|
+
Enables seamless colorized math in Jupyter Notebook 7, JupyterLab, Classic
|
|
4
|
+
Notebook, and Google Colab via `%load_ext color_math`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import shlex
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from .adapters import transform_document
|
|
13
|
+
from .config import ColorMathOptions, THEMES
|
|
14
|
+
from .converters.block import convert_math_block, convert_text
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from IPython.core.magic import Magics, cell_magic, line_magic, magics_class
|
|
18
|
+
from IPython.display import Markdown, Math, display
|
|
19
|
+
HAS_IPYTHON = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
HAS_IPYTHON = False
|
|
22
|
+
Magics = object # type: ignore[misc,assignment]
|
|
23
|
+
def magics_class(cls: Any) -> Any: return cls
|
|
24
|
+
def line_magic(func: Any) -> Any: return func
|
|
25
|
+
def cell_magic(func: Any) -> Any: return func
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def colormath(
|
|
29
|
+
text: str,
|
|
30
|
+
palette: dict[str, str] | None = None,
|
|
31
|
+
options: ColorMathOptions | None = None,
|
|
32
|
+
theme: str | None = None,
|
|
33
|
+
is_fragment: bool = False,
|
|
34
|
+
) -> str:
|
|
35
|
+
"""Convenience helper to colorize LaTeX math or Markdown math.
|
|
36
|
+
|
|
37
|
+
If is_fragment is True, text is treated as raw math without $ delimiters.
|
|
38
|
+
"""
|
|
39
|
+
active_palette = THEMES.get(theme) if theme else palette
|
|
40
|
+
if is_fragment:
|
|
41
|
+
converted = convert_math_block(f"$${text}$$", palette=active_palette, options=options)
|
|
42
|
+
return converted[2:-2]
|
|
43
|
+
return convert_text(text, palette=active_palette, options=options)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if HAS_IPYTHON:
|
|
47
|
+
class ColorMath(Math):
|
|
48
|
+
"""A drop-in replacement for IPython.display.Math that renders with semantic colors."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
data: Any = None,
|
|
53
|
+
palette: dict[str, str] | None = None,
|
|
54
|
+
options: ColorMathOptions | None = None,
|
|
55
|
+
theme: str | None = None,
|
|
56
|
+
**kwargs: Any,
|
|
57
|
+
) -> None:
|
|
58
|
+
raw = "" if data is None else str(data)
|
|
59
|
+
colored = colormath(raw, palette=palette, options=options, theme=theme, is_fragment=True)
|
|
60
|
+
super().__init__(data=colored, **kwargs)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@magics_class
|
|
64
|
+
class ColorMathMagics(Magics):
|
|
65
|
+
"""IPython magics for semantic LaTeX colorization."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, shell: Any) -> None:
|
|
68
|
+
super().__init__(shell)
|
|
69
|
+
self._auto_hook_active = False
|
|
70
|
+
self._orig_math_repr = None
|
|
71
|
+
|
|
72
|
+
@line_magic("color_math")
|
|
73
|
+
@line_magic("colormath")
|
|
74
|
+
def color_math_line(self, line: str) -> Any:
|
|
75
|
+
r"""Colorize and display a single LaTeX formula.
|
|
76
|
+
|
|
77
|
+
Usage:
|
|
78
|
+
%color_math \int_0^1 x^2 dx = \frac{1}{3}
|
|
79
|
+
%color_math --theme catppuccin \hat{H}\psi = E\psi
|
|
80
|
+
"""
|
|
81
|
+
theme = None
|
|
82
|
+
raw_line = line.strip()
|
|
83
|
+
if raw_line.startswith("--theme "):
|
|
84
|
+
parts = shlex.split(raw_line)
|
|
85
|
+
if len(parts) >= 3 and parts[0] == "--theme":
|
|
86
|
+
theme = parts[1]
|
|
87
|
+
raw_line = " ".join(parts[2:])
|
|
88
|
+
|
|
89
|
+
if not raw_line:
|
|
90
|
+
display(Markdown("*Color Math: please provide a LaTeX expression.*"))
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
# Render as display math
|
|
94
|
+
colored = colormath(raw_line, theme=theme, is_fragment=True)
|
|
95
|
+
return Math(colored)
|
|
96
|
+
|
|
97
|
+
@cell_magic("color_math")
|
|
98
|
+
@cell_magic("colormath")
|
|
99
|
+
def color_math_cell(self, line: str, cell: str) -> Any:
|
|
100
|
+
r"""Colorize and display a cell containing LaTeX or Markdown math.
|
|
101
|
+
|
|
102
|
+
Usage:
|
|
103
|
+
%%color_math
|
|
104
|
+
Here is the equation:
|
|
105
|
+
$$ \frac{df}{dx} = f'(x) $$
|
|
106
|
+
"""
|
|
107
|
+
theme = None
|
|
108
|
+
args = shlex.split(line.strip()) if line.strip() else []
|
|
109
|
+
if len(args) >= 2 and args[0] == "--theme":
|
|
110
|
+
theme = args[1]
|
|
111
|
+
|
|
112
|
+
converted = colormath(cell, theme=theme, is_fragment=False)
|
|
113
|
+
display(Markdown(converted))
|
|
114
|
+
|
|
115
|
+
@line_magic("color_math_auto")
|
|
116
|
+
def color_math_auto(self, line: str) -> None:
|
|
117
|
+
"""Toggle automatic semantic colorization for all IPython.display.Math outputs.
|
|
118
|
+
|
|
119
|
+
Usage:
|
|
120
|
+
%color_math_auto on
|
|
121
|
+
%color_math_auto off
|
|
122
|
+
%color_math_auto status
|
|
123
|
+
"""
|
|
124
|
+
cmd = line.strip().lower()
|
|
125
|
+
if cmd in ("on", "enable", "1", "true"):
|
|
126
|
+
self.enable_auto_display()
|
|
127
|
+
print("Color Math: Automatic display coloring enabled for IPython.display.Math.")
|
|
128
|
+
elif cmd in ("off", "disable", "0", "false"):
|
|
129
|
+
self.disable_auto_display()
|
|
130
|
+
print("Color Math: Automatic display coloring disabled.")
|
|
131
|
+
else:
|
|
132
|
+
status = "ENABLED" if self._auto_hook_active else "DISABLED"
|
|
133
|
+
print(f"Color Math: Automatic display coloring is currently {status}.")
|
|
134
|
+
|
|
135
|
+
def enable_auto_display(self) -> None:
|
|
136
|
+
"""Patch IPython.display.Math to automatically colorize its LaTeX payload."""
|
|
137
|
+
if self._auto_hook_active:
|
|
138
|
+
return
|
|
139
|
+
self._orig_math_repr = Math._repr_latex_
|
|
140
|
+
|
|
141
|
+
def _colorized_repr_latex(math_self: Math) -> str:
|
|
142
|
+
raw = self._orig_math_repr(math_self) # type: ignore[misc]
|
|
143
|
+
if not raw:
|
|
144
|
+
return raw
|
|
145
|
+
# Raw may be surrounded by $$ or not
|
|
146
|
+
if raw.startswith("$$") and raw.endswith("$$"):
|
|
147
|
+
body = raw[2:-2]
|
|
148
|
+
return f"$${colormath(body, is_fragment=True)}$$"
|
|
149
|
+
if raw.startswith("$") and raw.endswith("$"):
|
|
150
|
+
body = raw[1:-1]
|
|
151
|
+
return f"${colormath(body, is_fragment=True)}$"
|
|
152
|
+
return colormath(raw, is_fragment=True)
|
|
153
|
+
|
|
154
|
+
Math._repr_latex_ = _colorized_repr_latex # type: ignore[assignment]
|
|
155
|
+
self._auto_hook_active = True
|
|
156
|
+
|
|
157
|
+
def disable_auto_display(self) -> None:
|
|
158
|
+
"""Restore original IPython.display.Math behavior."""
|
|
159
|
+
if not self._auto_hook_active:
|
|
160
|
+
return
|
|
161
|
+
if self._orig_math_repr is not None:
|
|
162
|
+
Math._repr_latex_ = self._orig_math_repr # type: ignore[assignment]
|
|
163
|
+
self._auto_hook_active = False
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def load_ipython_extension(ipython: Any) -> None:
|
|
167
|
+
"""Standard IPython extension loader."""
|
|
168
|
+
if not HAS_IPYTHON:
|
|
169
|
+
return
|
|
170
|
+
magics = ColorMathMagics(ipython)
|
|
171
|
+
ipython.register_magics(magics)
|
|
172
|
+
# Store reference so it can be cleanly unloaded
|
|
173
|
+
ipython.custom_exceptions = getattr(ipython, "custom_exceptions", {})
|
|
174
|
+
setattr(ipython, "_color_math_magics", magics)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def unload_ipython_extension(ipython: Any) -> None:
|
|
178
|
+
"""Standard IPython extension unloader."""
|
|
179
|
+
if not HAS_IPYTHON:
|
|
180
|
+
return
|
|
181
|
+
magics = getattr(ipython, "_color_math_magics", None)
|
|
182
|
+
if magics is not None and hasattr(magics, "disable_auto_display"):
|
|
183
|
+
magics.disable_auto_display()
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Jupyter Server & Notebook 7 integration for color-math.
|
|
2
|
+
|
|
3
|
+
Provides automated pre-save formatting so that whenever a notebook is saved
|
|
4
|
+
in Jupyter Notebook 7 or JupyterLab, all LaTeX formulas in Markdown cells
|
|
5
|
+
are automatically colorized.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from .config import ColorMathOptions
|
|
15
|
+
from .converters.block import convert_text
|
|
16
|
+
from .undo import uncolor_text
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
HOOK_SNIPPET = """
|
|
20
|
+
# >>> color-math Jupyter pre-save hook >>>
|
|
21
|
+
try:
|
|
22
|
+
from color_math.jupyter import notebook_pre_save_hook
|
|
23
|
+
c.FileContentsManager.pre_save_hook = notebook_pre_save_hook
|
|
24
|
+
except ImportError:
|
|
25
|
+
pass
|
|
26
|
+
# <<< color-math Jupyter pre-save hook <<<
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def notebook_pre_save_hook(
|
|
31
|
+
model: dict[str, Any],
|
|
32
|
+
os_path: str = "",
|
|
33
|
+
contents_manager: Any = None,
|
|
34
|
+
options: ColorMathOptions | None = None,
|
|
35
|
+
**kwargs: Any,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Jupyter FileContentsManager pre_save_hook.
|
|
38
|
+
|
|
39
|
+
Modifies the notebook model in-place before it is written to disk.
|
|
40
|
+
"""
|
|
41
|
+
if model.get("type") != "notebook":
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
content = model.get("content")
|
|
45
|
+
if not isinstance(content, dict):
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
cells = content.get("cells")
|
|
49
|
+
if not isinstance(cells, list):
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
for cell in cells:
|
|
53
|
+
if not isinstance(cell, dict) or cell.get("cell_type") != "markdown":
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
source = cell.get("source", "")
|
|
57
|
+
if isinstance(source, str):
|
|
58
|
+
joined = source
|
|
59
|
+
elif isinstance(source, list) and all(isinstance(part, str) for part in source):
|
|
60
|
+
joined = "".join(source)
|
|
61
|
+
else:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
converted = convert_text(joined, options=options)
|
|
65
|
+
if converted != joined:
|
|
66
|
+
if isinstance(source, str):
|
|
67
|
+
cell["source"] = converted
|
|
68
|
+
else:
|
|
69
|
+
cell["source"] = converted.splitlines(keepends=True) or ([""] if source else [])
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_jupyter_config_dir() -> Path:
|
|
73
|
+
"""Locate the active Jupyter user configuration directory."""
|
|
74
|
+
env_dir = os.environ.get("JUPYTER_CONFIG_DIR")
|
|
75
|
+
if env_dir:
|
|
76
|
+
return Path(env_dir)
|
|
77
|
+
return Path.home() / ".jupyter"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def install_jupyter_hook(config_dir: Path | str | None = None) -> Path:
|
|
81
|
+
"""Install the pre-save hook into the user's Jupyter configuration."""
|
|
82
|
+
target_dir = Path(config_dir) if config_dir else get_jupyter_config_dir()
|
|
83
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
|
|
85
|
+
# In modern Jupyter (Notebook 7 / JupyterLab 4), jupyter_server_config.py is preferred
|
|
86
|
+
config_file = target_dir / "jupyter_server_config.py"
|
|
87
|
+
if not config_file.exists():
|
|
88
|
+
legacy_file = target_dir / "jupyter_notebook_config.py"
|
|
89
|
+
if legacy_file.exists():
|
|
90
|
+
config_file = legacy_file
|
|
91
|
+
|
|
92
|
+
existing_content = config_file.read_text(encoding="utf-8") if config_file.exists() else ""
|
|
93
|
+
if "color_math.jupyter" in existing_content:
|
|
94
|
+
return config_file
|
|
95
|
+
|
|
96
|
+
with open(config_file, "a", encoding="utf-8") as f:
|
|
97
|
+
f.write(HOOK_SNIPPET + "\n")
|
|
98
|
+
|
|
99
|
+
return config_file
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-color-math"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.14"
|
|
8
8
|
description = "A fast, local semantic LaTeX colorizer for Markdown, Jupyter, and LaTeX math equations."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -35,7 +35,8 @@ dependencies = []
|
|
|
35
35
|
[project.optional-dependencies]
|
|
36
36
|
mcp = ["mcp>=1.0.0"]
|
|
37
37
|
tutorial = ["rich>=13.0.0", "questionary>=2.0.0"]
|
|
38
|
-
|
|
38
|
+
jupyter = ["ipython>=8.0.0"]
|
|
39
|
+
all = ["mcp>=1.0.0", "rich>=13.0.0", "questionary>=2.0.0", "ipython>=8.0.0"]
|
|
39
40
|
|
|
40
41
|
[project.urls]
|
|
41
42
|
Homepage = "https://github.com/36ty-blip/python-color-math"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-color-math
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.14
|
|
4
4
|
Summary: A fast, local semantic LaTeX colorizer for Markdown, Jupyter, and LaTeX math equations.
|
|
5
5
|
Author: Aditya
|
|
6
6
|
License: MIT License
|
|
@@ -56,10 +56,13 @@ Requires-Dist: mcp>=1.0.0; extra == "mcp"
|
|
|
56
56
|
Provides-Extra: tutorial
|
|
57
57
|
Requires-Dist: rich>=13.0.0; extra == "tutorial"
|
|
58
58
|
Requires-Dist: questionary>=2.0.0; extra == "tutorial"
|
|
59
|
+
Provides-Extra: jupyter
|
|
60
|
+
Requires-Dist: ipython>=8.0.0; extra == "jupyter"
|
|
59
61
|
Provides-Extra: all
|
|
60
62
|
Requires-Dist: mcp>=1.0.0; extra == "all"
|
|
61
63
|
Requires-Dist: rich>=13.0.0; extra == "all"
|
|
62
64
|
Requires-Dist: questionary>=2.0.0; extra == "all"
|
|
65
|
+
Requires-Dist: ipython>=8.0.0; extra == "all"
|
|
63
66
|
Dynamic: license-file
|
|
64
67
|
|
|
65
68
|
# 🎨 Python Color Math
|
{python_color_math-0.2.12 → python_color_math-0.2.14}/python_color_math.egg-info/SOURCES.txt
RENAMED
|
@@ -12,6 +12,8 @@ color_math/config.py
|
|
|
12
12
|
color_math/custom_definitions.py
|
|
13
13
|
color_math/gui.py
|
|
14
14
|
color_math/io.py
|
|
15
|
+
color_math/ipython.py
|
|
16
|
+
color_math/jupyter.py
|
|
15
17
|
color_math/main.py
|
|
16
18
|
color_math/mcp.py
|
|
17
19
|
color_math/mcp_server.py
|
|
@@ -68,6 +70,7 @@ tests/test_features.py
|
|
|
68
70
|
tests/test_frontmatter.py
|
|
69
71
|
tests/test_fuzz.py
|
|
70
72
|
tests/test_gui.py
|
|
73
|
+
tests/test_ipython.py
|
|
71
74
|
tests/test_mcp_hardcore.py
|
|
72
75
|
tests/test_mcp_server.py
|
|
73
76
|
tests/test_physics.py
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Tests for IPython and Jupyter integrations."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from IPython.core.interactiveshell import InteractiveShell
|
|
5
|
+
from IPython.display import Math
|
|
6
|
+
|
|
7
|
+
from color_math.ipython import (
|
|
8
|
+
ColorMath,
|
|
9
|
+
colormath,
|
|
10
|
+
load_ipython_extension,
|
|
11
|
+
unload_ipython_extension,
|
|
12
|
+
)
|
|
13
|
+
from color_math.jupyter import notebook_pre_save_hook
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestIPythonIntegration(unittest.TestCase):
|
|
17
|
+
def setUp(self):
|
|
18
|
+
self.shell = InteractiveShell.instance()
|
|
19
|
+
|
|
20
|
+
def test_colormath_helper(self):
|
|
21
|
+
# Fragment test (no dollar signs)
|
|
22
|
+
frag = colormath(r"\int_0^1 x^2 dx = \frac{1}{3}", is_fragment=True)
|
|
23
|
+
self.assertIn(r"\textcolor{#e0af68}{\int", frag)
|
|
24
|
+
self.assertIn(r"\textcolor{white}{=}", frag)
|
|
25
|
+
|
|
26
|
+
# Full markdown test
|
|
27
|
+
md = colormath("Let $f(x) = x^2$.")
|
|
28
|
+
self.assertIn(r"$\textcolor{#7aa2f7}{f}(x)", md)
|
|
29
|
+
|
|
30
|
+
def test_colormath_display_object(self):
|
|
31
|
+
obj = ColorMath(r"\frac{df}{dx} = f'(x)")
|
|
32
|
+
self.assertIsInstance(obj, Math)
|
|
33
|
+
self.assertIn(r"\textcolor{#7aa2f7}{f'}(x)", obj.data)
|
|
34
|
+
|
|
35
|
+
def test_load_and_unload_extension(self):
|
|
36
|
+
load_ipython_extension(self.shell)
|
|
37
|
+
self.assertTrue(self.shell.find_magic("color_math") is not None)
|
|
38
|
+
self.assertTrue(self.shell.find_magic("colormath") is not None)
|
|
39
|
+
self.assertTrue(self.shell.find_magic("color_math_auto") is not None)
|
|
40
|
+
|
|
41
|
+
# Execute line magic
|
|
42
|
+
result = self.shell.run_line_magic("color_math", r"\int x^2 dx")
|
|
43
|
+
self.assertIsInstance(result, Math)
|
|
44
|
+
self.assertIn(r"\textcolor{#e0af68}{\int", result.data)
|
|
45
|
+
|
|
46
|
+
# Execute cell magic
|
|
47
|
+
cell_body = "The equation is:\n$$ \\psi + \\omega $$"
|
|
48
|
+
self.shell.run_cell_magic("color_math", "", cell_body)
|
|
49
|
+
|
|
50
|
+
# Test auto hook
|
|
51
|
+
self.shell.run_line_magic("color_math_auto", "on")
|
|
52
|
+
# Creating a standard IPython Math object should now produce colored latex in _repr_latex_
|
|
53
|
+
standard_math = Math(r"\int x^2 dx")
|
|
54
|
+
repr_latex = standard_math._repr_latex_()
|
|
55
|
+
self.assertIn(r"\textcolor{#e0af68}{\int", repr_latex)
|
|
56
|
+
|
|
57
|
+
self.shell.run_line_magic("color_math_auto", "off")
|
|
58
|
+
unload_ipython_extension(self.shell)
|
|
59
|
+
|
|
60
|
+
def test_jupyter_pre_save_hook(self):
|
|
61
|
+
model = {
|
|
62
|
+
"type": "notebook",
|
|
63
|
+
"content": {
|
|
64
|
+
"cells": [
|
|
65
|
+
{
|
|
66
|
+
"cell_type": "markdown",
|
|
67
|
+
"source": "Calculate $$\\int_0^1 x dx = \\frac{1}{2}$$.",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"cell_type": "code",
|
|
71
|
+
"source": "x = 1\nprint(x)",
|
|
72
|
+
},
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
notebook_pre_save_hook(model)
|
|
78
|
+
|
|
79
|
+
# Markdown cell was transformed
|
|
80
|
+
md_cell = model["content"]["cells"][0]
|
|
81
|
+
self.assertIn(r"\textcolor{#e0af68}{\int", md_cell["source"])
|
|
82
|
+
|
|
83
|
+
# Code cell was untouched
|
|
84
|
+
code_cell = model["content"]["cells"][1]
|
|
85
|
+
self.assertEqual(code_cell["source"], "x = 1\nprint(x)")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/converters/unicode_converter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_color_math-0.2.12 → python_color_math-0.2.14}/color_math/parsers/markdown_scanner.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_color_math-0.2.12 → python_color_math-0.2.14}/python_color_math.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{python_color_math-0.2.12 → python_color_math-0.2.14}/python_color_math.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|