python-color-math 0.2.18__tar.gz → 0.2.20__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.
Files changed (80) hide show
  1. {python_color_math-0.2.18/python_color_math.egg-info → python_color_math-0.2.20}/PKG-INFO +1 -1
  2. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/__init__.py +11 -3
  3. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/config.py +105 -51
  4. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/ipython.py +48 -30
  5. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/main.py +22 -2
  6. {python_color_math-0.2.18 → python_color_math-0.2.20}/pyproject.toml +1 -1
  7. {python_color_math-0.2.18 → python_color_math-0.2.20/python_color_math.egg-info}/PKG-INFO +1 -1
  8. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_ipython.py +5 -3
  9. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_mcp_hardcore.py +4 -4
  10. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_mcp_server.py +4 -4
  11. {python_color_math-0.2.18 → python_color_math-0.2.20}/.pre-commit-hooks.yaml +0 -0
  12. {python_color_math-0.2.18 → python_color_math-0.2.20}/LICENSE +0 -0
  13. {python_color_math-0.2.18 → python_color_math-0.2.20}/MANIFEST.in +0 -0
  14. {python_color_math-0.2.18 → python_color_math-0.2.20}/README.md +0 -0
  15. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/__main__.py +0 -0
  16. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/adapters.py +0 -0
  17. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/colormath.default.json +0 -0
  18. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/completions.py +0 -0
  19. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/converters/__init__.py +0 -0
  20. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/converters/align.py +0 -0
  21. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/converters/block.py +0 -0
  22. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/converters/derivative.py +0 -0
  23. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/converters/equation.py +0 -0
  24. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/converters/generic.py +0 -0
  25. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/converters/integral.py +0 -0
  26. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/converters/limit.py +0 -0
  27. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/converters/matrix.py +0 -0
  28. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/converters/semantic.py +0 -0
  29. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/converters/unicode_converter.py +0 -0
  30. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/custom_definitions.py +0 -0
  31. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/gui.py +0 -0
  32. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/io.py +0 -0
  33. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/jupyter.py +0 -0
  34. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/mcp.py +0 -0
  35. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/mcp_server.py +0 -0
  36. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/__init__.py +0 -0
  37. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/alignment.py +0 -0
  38. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/braket.py +0 -0
  39. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/constants.py +0 -0
  40. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/delimiters.py +0 -0
  41. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/differentials.py +0 -0
  42. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/dimensionless.py +0 -0
  43. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/frontmatter.py +0 -0
  44. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/latex_spans.py +0 -0
  45. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/markdown_scanner.py +0 -0
  46. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/math_parser.py +0 -0
  47. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/physics.py +0 -0
  48. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/scanner.py +0 -0
  49. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/taxonomy.py +0 -0
  50. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/units.py +0 -0
  51. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/parsers/variable_hash.py +0 -0
  52. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/py.typed +0 -0
  53. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/self_test.py +0 -0
  54. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/tutorial.py +0 -0
  55. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/undo.py +0 -0
  56. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/unicode.py +0 -0
  57. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/utils/__init__.py +0 -0
  58. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/utils/coloring.py +0 -0
  59. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/utils/latex_helpers.py +0 -0
  60. {python_color_math-0.2.18 → python_color_math-0.2.20}/color_math/utils/spans.py +0 -0
  61. {python_color_math-0.2.18 → python_color_math-0.2.20}/python_color_math/__init__.py +0 -0
  62. {python_color_math-0.2.18 → python_color_math-0.2.20}/python_color_math/__main__.py +0 -0
  63. {python_color_math-0.2.18 → python_color_math-0.2.20}/python_color_math.egg-info/SOURCES.txt +0 -0
  64. {python_color_math-0.2.18 → python_color_math-0.2.20}/python_color_math.egg-info/dependency_links.txt +0 -0
  65. {python_color_math-0.2.18 → python_color_math-0.2.20}/python_color_math.egg-info/entry_points.txt +0 -0
  66. {python_color_math-0.2.18 → python_color_math-0.2.20}/python_color_math.egg-info/requires.txt +0 -0
  67. {python_color_math-0.2.18 → python_color_math-0.2.20}/python_color_math.egg-info/top_level.txt +0 -0
  68. {python_color_math-0.2.18 → python_color_math-0.2.20}/setup.cfg +0 -0
  69. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_adversarial.py +0 -0
  70. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_cli.py +0 -0
  71. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_config_tolerance.py +0 -0
  72. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_custom_definitions.py +0 -0
  73. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_extreme_adversarial.py +0 -0
  74. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_features.py +0 -0
  75. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_frontmatter.py +0 -0
  76. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_fuzz.py +0 -0
  77. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_gui.py +0 -0
  78. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_physics.py +0 -0
  79. {python_color_math-0.2.18 → python_color_math-0.2.20}/tests/test_unicode.py +0 -0
  80. {python_color_math-0.2.18 → python_color_math-0.2.20}/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.18
3
+ Version: 0.2.20
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
@@ -1,13 +1,19 @@
1
1
  """Public helpers for converting Obsidian LaTeX color markup."""
2
2
 
3
3
  from .adapters import detect_format, transform_document
4
- from .config import ColorMathOptions, ensure_global_config_exists, open_config_folder
4
+ from .config import (
5
+ ColorMathOptions,
6
+ find_config_path,
7
+ get_venv_config_path,
8
+ init_config,
9
+ open_config_folder,
10
+ )
5
11
  from .converters.block import convert_text
6
12
  from .undo import uncolor_text
7
13
 
8
14
  from .ipython import ColorMath, colormath, load_ipython_extension, unload_ipython_extension
9
15
 
10
- __version__ = "0.2.18"
16
+ __version__ = "0.2.20"
11
17
 
12
18
  __all__ = [
13
19
  "__version__",
@@ -16,7 +22,9 @@ __all__ = [
16
22
  "colormath",
17
23
  "convert_text",
18
24
  "detect_format",
19
- "ensure_global_config_exists",
25
+ "find_config_path",
26
+ "get_venv_config_path",
27
+ "init_config",
20
28
  "load_ipython_extension",
21
29
  "open_config_folder",
22
30
  "transform_document",
@@ -714,31 +714,89 @@ def default_config_dict() -> dict[str, object]:
714
714
  }
715
715
 
716
716
 
717
- def get_global_config_path() -> Path | None:
718
- """Return platform-appropriate XDG / AppData user configuration path."""
719
- if sys.platform == "win32":
720
- app_data = os.environ.get("APPDATA")
721
- if app_data:
722
- return Path(app_data) / "color-math" / "config.json"
723
- else:
724
- xdg_config = os.environ.get("XDG_CONFIG_HOME")
725
- if xdg_config:
726
- return Path(xdg_config) / "color-math" / "config.json"
727
- home = os.environ.get("HOME")
728
- if home:
729
- return Path(home) / ".config" / "color-math" / "config.json"
717
+ def get_user_home_config_path() -> Path:
718
+ """Return platform-standard user-level config path (~/.config/color-math/config.json)."""
719
+ xdg_config = os.environ.get("XDG_CONFIG_HOME")
720
+ if xdg_config:
721
+ return Path(xdg_config) / "color-math" / "config.json"
722
+ return Path.home() / ".config" / "color-math" / "config.json"
723
+
724
+
725
+ def get_global_config_path() -> Path:
726
+ """Backward-compatible alias for user home configuration path."""
727
+ return get_user_home_config_path()
728
+
729
+
730
+ def find_project_config_path(start_dir: Path | None = None) -> Path | None:
731
+ """Search for .colormath.json in start_dir and walk up parent directories."""
732
+ curr = (start_dir or Path.cwd()).resolve()
733
+ for parent in [curr, *curr.parents]:
734
+ candidate = parent / ".colormath.json"
735
+ if candidate.exists() and candidate.is_file():
736
+ return candidate
737
+ if (parent / ".git").exists():
738
+ break
730
739
  return None
731
740
 
732
741
 
733
- def ensure_global_config_exists() -> Path | None:
734
- """Ensure that the global configuration file and folder exist on disk.
742
+ def get_venv_config_path() -> Path | None:
743
+ """Return path to .colormath.json inside active virtual environment, if any."""
744
+ if sys.prefix != sys.base_prefix or "VIRTUAL_ENV" in os.environ:
745
+ venv_root = Path(os.environ.get("VIRTUAL_ENV", sys.prefix))
746
+ return venv_root / ".colormath.json"
747
+ return None
748
+
749
+
750
+ def find_config_path(start_dir: Path | None = None) -> tuple[Path | None, str]:
751
+ """Resolve active configuration path in order of precedence:
752
+ 1. Project hierarchy (.colormath.json in current or parent dirs)
753
+ 2. Active virtual environment (.colormath.json in sys.prefix or $VIRTUAL_ENV)
754
+ 3. User home config (~/.config/color-math/config.json)
755
+
756
+ Returns (path_or_none, scope_name).
757
+ """
758
+ proj = find_project_config_path(start_dir)
759
+ if proj is not None:
760
+ return proj, "project"
761
+
762
+ venv_cfg = get_venv_config_path()
763
+ if venv_cfg is not None and venv_cfg.exists():
764
+ return venv_cfg, "venv"
735
765
 
736
- Creates the directory and saves the documented default configuration if it
737
- does not already exist. Returns the Path to the global config.
766
+ home_cfg = get_user_home_config_path()
767
+ if home_cfg.exists():
768
+ return home_cfg, "home"
769
+
770
+ return None, "default"
771
+
772
+
773
+ def init_config(scope: str = "project", target_dir: Path | None = None) -> Path:
774
+ """Initialize a new .colormath.json configuration file.
775
+
776
+ Scopes:
777
+ - 'project': in target_dir or current working directory (isolated per project)
778
+ - 'venv': inside the active virtual environment (.venv)
779
+ - 'home': in user home (~/.config/color-math/config.json)
738
780
  """
739
- path = get_global_config_path()
740
- if path is None:
741
- return None
781
+ if scope == "venv":
782
+ venv_cfg = get_venv_config_path()
783
+ if venv_cfg is None:
784
+ raise RuntimeError("No active virtual environment detected to initialize config in.")
785
+ path = venv_cfg
786
+ elif scope == "home":
787
+ path = get_user_home_config_path()
788
+ else: # project
789
+ directory = (target_dir or Path.cwd()).resolve()
790
+ path = directory / ".colormath.json"
791
+
792
+ path.parent.mkdir(parents=True, exist_ok=True)
793
+ save_default_config(path)
794
+ return path
795
+
796
+
797
+ def ensure_global_config_exists() -> Path | None:
798
+ """Legacy helper maintained for backward compatibility."""
799
+ path = get_user_home_config_path()
742
800
  if not path.exists():
743
801
  try:
744
802
  path.parent.mkdir(parents=True, exist_ok=True)
@@ -748,41 +806,43 @@ def ensure_global_config_exists() -> Path | None:
748
806
  return path
749
807
 
750
808
 
751
- def open_config_folder(path: Path | None = None) -> bool:
752
- """Open the configuration folder in the operating system's file manager."""
809
+ def open_config_folder(path: Path | None = None, scope: str = "project") -> tuple[bool, Path]:
810
+ """Open the configuration file or folder in the operating system's default application."""
753
811
  target = path
754
812
  if target is None:
755
- local_candidate = Path(".colormath.json")
756
- if local_candidate.exists():
757
- target = local_candidate
813
+ found, _ = find_config_path()
814
+ if found is not None:
815
+ target = found
758
816
  else:
759
- target = ensure_global_config_exists() or get_global_config_path()
760
-
761
- if target is None:
762
- return False
817
+ target = init_config(scope=scope)
763
818
 
764
- folder = target.parent if target.suffix else target
765
- try:
766
- folder.mkdir(parents=True, exist_ok=True)
767
- if not target.exists() and target.suffix == ".json":
819
+ if not target.exists():
820
+ try:
821
+ target.parent.mkdir(parents=True, exist_ok=True)
768
822
  save_default_config(target)
769
- except OSError:
770
- pass
823
+ except OSError:
824
+ pass
771
825
 
772
826
  try:
773
827
  if sys.platform == "win32":
774
- os.startfile(folder)
775
- return True
828
+ os.startfile(target)
829
+ return True, target
776
830
  elif sys.platform == "darwin":
777
831
  import subprocess
778
- subprocess.run(["open", str(folder)], check=False)
779
- return True
832
+ subprocess.run(["open", str(target)], check=False)
833
+ return True, target
780
834
  else:
781
835
  import subprocess
782
- subprocess.run(["xdg-open", str(folder)], check=False)
783
- return True
836
+ subprocess.run(["xdg-open", str(target)], check=False)
837
+ return True, target
784
838
  except Exception:
785
- return False
839
+ try:
840
+ if sys.platform == "win32":
841
+ os.startfile(target.parent)
842
+ return True, target.parent
843
+ except Exception:
844
+ pass
845
+ return False, target
786
846
 
787
847
 
788
848
  DEFAULT_UNICODE_CONFIG: dict[str, object] = {
@@ -821,8 +881,8 @@ def load_config(
821
881
  path: Path | None = None,
822
882
  ) -> tuple[dict[str, str], ColorMathOptions, dict[str, object]]:
823
883
  """
824
- Load configuration from path, local '.colormath.json', or global user config.
825
- Fault-tolerant: If the user file contains JSON syntax errors or invalid types,
884
+ Load configuration from path, project hierarchy, active venv, or user home.
885
+ Fault-tolerant: If the file contains JSON syntax errors or invalid types,
826
886
  a warning is emitted and it safely falls back to factory defaults without crashing.
827
887
  Returns (palette_dict, ColorMathOptions, unicode_config_dict).
828
888
  """
@@ -832,13 +892,7 @@ def load_config(
832
892
 
833
893
  target = path
834
894
  if target is None:
835
- local_candidate = Path(".colormath.json")
836
- if local_candidate.exists():
837
- target = local_candidate
838
- else:
839
- global_candidate = ensure_global_config_exists()
840
- if global_candidate and global_candidate.exists():
841
- target = global_candidate
895
+ target, _ = find_config_path()
842
896
 
843
897
  if target is None or not target.exists():
844
898
  return palette, options, unicode_config
@@ -14,9 +14,10 @@ from .adapters import transform_document
14
14
  from .config import (
15
15
  ColorMathOptions,
16
16
  THEMES,
17
+ find_config_path,
18
+ get_venv_config_path,
19
+ init_config,
17
20
  load_config,
18
- get_global_config_path,
19
- ensure_global_config_exists,
20
21
  open_config_folder,
21
22
  save_default_config,
22
23
  )
@@ -79,11 +80,9 @@ if HAS_IPYTHON:
79
80
  self._auto_hook_active = False
80
81
  self._orig_math_repr = None
81
82
  palette, options, _ = load_config()
82
- local_or_global = Path(".colormath.json").exists() or bool(
83
- get_global_config_path() and get_global_config_path().exists()
84
- )
83
+ cfg_path, _ = find_config_path()
85
84
  self._palette = palette
86
- self._options = options if local_or_global else ColorMathOptions.extended()
85
+ self._options = options if cfg_path is not None else ColorMathOptions.extended()
87
86
 
88
87
  def _parse_magic_args(self, arg_str: str) -> tuple[str | None, ColorMathOptions, str]:
89
88
  """Parse leading flags (--theme, -v, --variables) without corrupting LaTeX backslashes."""
@@ -180,44 +179,63 @@ if HAS_IPYTHON:
180
179
 
181
180
  @line_magic("color_math_config")
182
181
  def color_math_config(self, line: str) -> None:
183
- """Inspect or initialize Color Math configuration.
182
+ """Inspect, create, or open Color Math configuration.
184
183
 
185
184
  Usage:
186
- %color_math_config
187
- %color_math_config --init
188
- %color_math_config --reload
185
+ %color_math_config - Show current active config and scope
186
+ %color_math_config init - Create .colormath.json in current project
187
+ %color_math_config init --venv - Create .colormath.json inside active .venv
188
+ %color_math_config open - Open the active config file in editor
189
+ %color_math_config reload - Reload config from disk
189
190
  """
190
- raw = line.strip().lower()
191
- if "--init" in raw:
192
- target = Path(".colormath.json")
193
- save_default_config(target)
194
- print(f"Color Math: Created config template at '{target.resolve()}'.")
195
- self._palette, self._options, _ = load_config(target)
196
- raw = line.strip().lower()
197
- if raw in ("open", "--open", "explore", "folder"):
198
- opened = open_config_folder()
199
- if opened:
200
- print("Color Math: Opened configuration folder in file manager.")
191
+ parts = line.strip().split()
192
+ subcmd = parts[0].lower() if parts else ""
193
+
194
+ if subcmd in ("init", "--init"):
195
+ scope = "venv" if "--venv" in parts else "project"
196
+ try:
197
+ target = init_config(scope=scope)
198
+ print(f"Color Math: Created {scope} configuration at '{target}'.")
199
+ self._palette, self._options, _ = load_config(target)
200
+ except Exception as err:
201
+ print(f"Color Math error: {err}")
202
+ return
203
+
204
+ if subcmd in ("open", "--open", "explore", "folder", "edit"):
205
+ scope = "venv" if "--venv" in parts else "project"
206
+ success, target = open_config_folder(scope=scope)
207
+ if success:
208
+ print(f"Color Math: Opened '{target}'.")
201
209
  else:
202
- print("Color Math: Could not open configuration folder automatically.")
210
+ print(f"Color Math: Configuration file is at '{target}'.")
203
211
  return
204
212
 
205
- if "--reload" in raw or raw == "reload":
213
+ if subcmd in ("reload", "--reload"):
206
214
  self._palette, self._options, _ = load_config()
207
- print("Color Math: Configuration reloaded.")
215
+ print("Color Math: Configuration reloaded from disk.")
208
216
  return
209
217
 
210
- local_path = Path(".colormath.json")
211
- global_path = ensure_global_config_exists() or get_global_config_path()
218
+ active_path, scope = find_config_path()
212
219
  print("Color Math Configuration:")
213
- print(f" Local config: {local_path.resolve()} ({'exists' if local_path.exists() else 'not found'})")
214
- if global_path:
215
- print(f" Global config: {global_path} ({'exists' if global_path.exists() else 'not found'})")
220
+ if active_path:
221
+ print(f" Active Config: {active_path} (scope: {scope})")
222
+ else:
223
+ print(" Active Config: Built-in defaults (zero disk pollution)")
224
+
225
+ venv_cfg = get_venv_config_path()
226
+ if venv_cfg:
227
+ status = "configured" if venv_cfg.exists() else "no config in venv"
228
+ print(f" Virtual Env: {venv_cfg.parent.name} ({status})")
229
+
216
230
  print(f" Variables: {'ENABLED' if self._options.variable_data_flow else 'DISABLED'}")
217
231
  print(f" Taxonomy: {'ENABLED' if self._options.enable_taxonomy else 'DISABLED'}")
218
232
  print(f" Delimiters: {'ENABLED' if self._options.rainbow_delimiters else 'DISABLED'}")
219
233
  print(f" Auto-display: {'ENABLED' if self._auto_hook_active else 'DISABLED'}")
220
- print("\nTip: Run '%color_math_config open' to open the configuration folder in your file manager.")
234
+ print("\nCommands:")
235
+ print(" %color_math_config init - Create project-level .colormath.json")
236
+ print(" %color_math_config init --venv - Create .colormath.json inside current .venv")
237
+ print(" %color_math_config open - Open active config in your editor")
238
+ print(" %color_math_config reload - Reload settings from disk")
221
239
 
222
240
  @line_magic("color_math_auto")
223
241
  def color_math_auto(self, line: str) -> None:
@@ -227,10 +227,15 @@ def build_parser() -> argparse.ArgumentParser:
227
227
  const=".colormath.json",
228
228
  help="Generate a .colormath.json config template (optional target path).",
229
229
  )
230
+ style_group.add_argument(
231
+ "--init-venv",
232
+ action="store_true",
233
+ help="Generate a .colormath.json inside the active virtual environment (.venv).",
234
+ )
230
235
  style_group.add_argument(
231
236
  "--open-config",
232
237
  action="store_true",
233
- help="Open configuration folder in system file manager.",
238
+ help="Open configuration file in default editor/file manager.",
234
239
  )
235
240
  style_group.add_argument(
236
241
  "--config",
@@ -589,7 +594,18 @@ def _main_impl(argv: list[str] | None = None) -> int:
589
594
  sys.stdout.write("Reset runtime color palette to factory defaults (Tokyo Night).\n")
590
595
  return 0
591
596
 
597
+ if args.init_venv:
598
+ from .config import init_config
599
+ try:
600
+ cfg_path = init_config(scope="venv")
601
+ sys.stdout.write(f"Created virtual environment configuration: {cfg_path}\n")
602
+ except Exception as err:
603
+ sys.stderr.write(f"color-math error: {err}\n")
604
+ return 1
605
+ return 0
606
+
592
607
  if args.init_config:
608
+ from .config import init_config
593
609
  cfg_path = Path(args.init_config)
594
610
  save_default_config(cfg_path)
595
611
  sys.stdout.write(f"Created configuration file: {cfg_path}\n")
@@ -597,7 +613,11 @@ def _main_impl(argv: list[str] | None = None) -> int:
597
613
 
598
614
  if args.open_config:
599
615
  from .config import open_config_folder
600
- open_config_folder(args.config)
616
+ success, path = open_config_folder(args.config)
617
+ if success:
618
+ sys.stdout.write(f"Opened configuration: {path}\n")
619
+ else:
620
+ sys.stdout.write(f"Configuration file located at: {path}\n")
601
621
  return 0
602
622
 
603
623
  # 3. Load base configuration & theme
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-color-math"
7
- version = "0.2.18"
7
+ version = "0.2.20"
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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-color-math
3
- Version: 0.2.18
3
+ Version: 0.2.20
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
@@ -1,7 +1,9 @@
1
1
  import unittest
2
- import pytest
3
-
4
- pytest.importorskip("IPython")
2
+ try:
3
+ import pytest
4
+ pytest.importorskip("IPython")
5
+ except ImportError:
6
+ pass
5
7
 
6
8
  from IPython.core.interactiveshell import InteractiveShell
7
9
  from IPython.display import Math
@@ -7,10 +7,10 @@ import json
7
7
  import tempfile
8
8
  import time
9
9
  import unittest
10
- from pathlib import Path
11
- import pytest
12
-
13
- pytest.importorskip("mcp")
10
+ try:
11
+ import mcp
12
+ except ImportError:
13
+ raise unittest.SkipTest("mcp not installed")
14
14
 
15
15
  from color_math.mcp_server import create_mcp_server
16
16
 
@@ -4,10 +4,10 @@ from __future__ import annotations
4
4
 
5
5
  import tempfile
6
6
  import unittest
7
- from pathlib import Path
8
- import pytest
9
-
10
- pytest.importorskip("mcp")
7
+ try:
8
+ import mcp
9
+ except ImportError:
10
+ raise unittest.SkipTest("mcp not installed")
11
11
 
12
12
  from color_math.mcp_server import create_mcp_server
13
13