pyDiffTools 0.1.28__tar.gz → 0.1.29__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 (94) hide show
  1. {pydifftools-0.1.28/pyDiffTools.egg-info → pydifftools-0.1.29}/PKG-INFO +3 -1
  2. {pydifftools-0.1.28 → pydifftools-0.1.29/pyDiffTools.egg-info}/PKG-INFO +3 -1
  3. {pydifftools-0.1.28 → pydifftools-0.1.29}/pyDiffTools.egg-info/SOURCES.txt +1 -0
  4. {pydifftools-0.1.28 → pydifftools-0.1.29}/pyDiffTools.egg-info/requires.txt +2 -0
  5. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/command_line.py +72 -1
  6. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/continuous.py +158 -67
  7. pydifftools-0.1.29/pydifftools/notebook/display.py +67 -0
  8. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/notebook/fast_build.py +340 -22
  9. {pydifftools-0.1.28 → pydifftools-0.1.29}/pyproject.toml +3 -1
  10. {pydifftools-0.1.28 → pydifftools-0.1.29}/tests/test_command_line_help.py +64 -0
  11. {pydifftools-0.1.28 → pydifftools-0.1.29}/LICENSE.md +0 -0
  12. {pydifftools-0.1.28 → pydifftools-0.1.29}/MANIFEST.in +0 -0
  13. {pydifftools-0.1.28 → pydifftools-0.1.29}/README.rst +0 -0
  14. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/_quarto.yml +0 -0
  15. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/__version__.txt +0 -0
  16. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/1a724af72b16f5a9e607e12b1c721645/base.ipynb +0 -0
  17. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/1b28fc9daac9081847e5161b2c546f8a/base.ipynb +0 -0
  18. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/231f64eee282fa225d1104935cf80a24/base.ipynb +0 -0
  19. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/26e56f6b0ff54851a45145157f2f0dc4/base.ipynb +0 -0
  20. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/311fabd7029ffd050d056e2f316eb50f/base.ipynb +0 -0
  21. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/57da2021e5b156ac3adf01398201c723/base.ipynb +0 -0
  22. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/62b24ea7da75011d92b0f8924faa208d/base.ipynb +0 -0
  23. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/7f1b20d69d889514ab5d1cc92e3cb14f/base.ipynb +0 -0
  24. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/86f74c8c54a87ff892d9b15dd714e8f0/base.ipynb +0 -0
  25. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/88893b4234eac2945d9d6cb2e277f186/base.ipynb +0 -0
  26. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/9a40046ada6f582ee34af00fbdbfb417/base.ipynb +0 -0
  27. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/a1bf4d270d0641ff41faf1d7cce3439a/base.ipynb +0 -0
  28. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/a3789f7d9585a781f2a1c60ce95ff10d/base.ipynb +0 -0
  29. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/be46ecba858d39ad5f0c46902ddf1c02/base.ipynb +0 -0
  30. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/d0cbc57a12f2ccce710a5afd04cc05e7/base.ipynb +0 -0
  31. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/d3e12d320b14228f701231ae32ddd7dd/base.ipynb +0 -0
  32. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/eb434f61555438d020a6970a5dbf9ee8/base.ipynb +0 -0
  33. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/f6b0e73aa7fa029134665d4dde57e096/base.ipynb +0 -0
  34. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/f719a6e4ff09873cb0ffb06ec9d232f9/base.ipynb +0 -0
  35. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/feeee244a7ce3d60e1a227eb604df823/base.ipynb +0 -0
  36. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/global.db +0 -0
  37. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/example.qmd +0 -0
  38. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/example.tex +0 -0
  39. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/index.qmd +0 -0
  40. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/subproject1/.jupyter_cache/__version__.txt +0 -0
  41. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/subproject1/.jupyter_cache/executed/ca90e4df5f4f0583df6554156a68dc7f/base.ipynb +0 -0
  42. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/subproject1/.jupyter_cache/global.db +0 -0
  43. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg +0 -0
  44. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/subproject1/independent.qmd +0 -0
  45. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/subproject1/index.qmd +0 -0
  46. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/subproject1/tasks.qmd +0 -0
  47. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/subproject1/test_include.qmd +0 -0
  48. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/subproject1/tryforerror.qmd +0 -0
  49. {pydifftools-0.1.28 → pydifftools-0.1.29}/example_notebook/project1/test_include.qmd +0 -0
  50. {pydifftools-0.1.28 → pydifftools-0.1.29}/pyDiffTools.egg-info/dependency_links.txt +0 -0
  51. {pydifftools-0.1.28 → pydifftools-0.1.29}/pyDiffTools.egg-info/entry_points.txt +0 -0
  52. {pydifftools-0.1.28 → pydifftools-0.1.29}/pyDiffTools.egg-info/top_level.txt +0 -0
  53. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/__init__.py +0 -0
  54. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/browser_lifecycle.py +0 -0
  55. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/check_numbers.py +0 -0
  56. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/command_registry.py +0 -0
  57. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/comment_functions.py +0 -0
  58. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/comment_tags.lua +0 -0
  59. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/comment_tags_margin.lua +0 -0
  60. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/comment_tags_no_comments.lua +0 -0
  61. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/comment_toggle.js +0 -0
  62. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/comments.css +0 -0
  63. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/copy_files.py +0 -0
  64. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/diff-doc.js +0 -0
  65. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/doc_contents.py +0 -0
  66. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/flowchart/__init__.py +0 -0
  67. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/flowchart/dot_to_yaml.py +0 -0
  68. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/flowchart/graph.py +0 -0
  69. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/flowchart/watch_graph.py +0 -0
  70. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/git_gd.py +0 -0
  71. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/git_gd_qt.py +0 -0
  72. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/html_comments.py +0 -0
  73. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/html_uncomments.py +0 -0
  74. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/log_example.py +0 -0
  75. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/match_spaces.py +0 -0
  76. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/notebook/__init__.py +0 -0
  77. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/notebook/tex_to_qmd.py +0 -0
  78. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/onewordify.py +0 -0
  79. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/onewordify_undo.py +0 -0
  80. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/outline.py +0 -0
  81. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/rearrange_tex.py +0 -0
  82. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/searchacro.py +0 -0
  83. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/separate_comments.py +0 -0
  84. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/split_conflict.py +0 -0
  85. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/unseparate_comments.py +0 -0
  86. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/update_check.py +0 -0
  87. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/wrap_sentences.py +0 -0
  88. {pydifftools-0.1.28 → pydifftools-0.1.29}/pydifftools/xml2xlsx.vbs +0 -0
  89. {pydifftools-0.1.28 → pydifftools-0.1.29}/setup.cfg +0 -0
  90. {pydifftools-0.1.28 → pydifftools-0.1.29}/tests/test_browser_lifecycle.py +0 -0
  91. {pydifftools-0.1.28 → pydifftools-0.1.29}/tests/test_continuous_shutdown.py +0 -0
  92. {pydifftools-0.1.28 → pydifftools-0.1.29}/tests/test_rrng.py +0 -0
  93. {pydifftools-0.1.28 → pydifftools-0.1.29}/tests/test_tex_to_qmd.py +0 -0
  94. {pydifftools-0.1.28 → pydifftools-0.1.29}/tests/test_update_check.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyDiffTools
3
- Version: 0.1.28
3
+ Version: 0.1.29
4
4
  Summary: Diff tools
5
5
  Author: J M Franck
6
6
  License: Copyright (c) 2015, jmfranck
@@ -41,6 +41,8 @@ Requires-Dist: watchdog
41
41
  Requires-Dist: pydot
42
42
  Requires-Dist: python-dateutil
43
43
  Requires-Dist: jinja2
44
+ Requires-Dist: ipykernel
45
+ Requires-Dist: matplotlib
44
46
  Requires-Dist: nbformat
45
47
  Requires-Dist: nbconvert
46
48
  Requires-Dist: pygments
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyDiffTools
3
- Version: 0.1.28
3
+ Version: 0.1.29
4
4
  Summary: Diff tools
5
5
  Author: J M Franck
6
6
  License: Copyright (c) 2015, jmfranck
@@ -41,6 +41,8 @@ Requires-Dist: watchdog
41
41
  Requires-Dist: pydot
42
42
  Requires-Dist: python-dateutil
43
43
  Requires-Dist: jinja2
44
+ Requires-Dist: ipykernel
45
+ Requires-Dist: matplotlib
44
46
  Requires-Dist: nbformat
45
47
  Requires-Dist: nbconvert
46
48
  Requires-Dist: pygments
@@ -81,6 +81,7 @@ pydifftools/flowchart/dot_to_yaml.py
81
81
  pydifftools/flowchart/graph.py
82
82
  pydifftools/flowchart/watch_graph.py
83
83
  pydifftools/notebook/__init__.py
84
+ pydifftools/notebook/display.py
84
85
  pydifftools/notebook/fast_build.py
85
86
  pydifftools/notebook/tex_to_qmd.py
86
87
  tests/test_browser_lifecycle.py
@@ -5,6 +5,8 @@ watchdog
5
5
  pydot
6
6
  python-dateutil
7
7
  jinja2
8
+ ipykernel
9
+ matplotlib
8
10
  nbformat
9
11
  nbconvert
10
12
  pygments
@@ -943,11 +943,82 @@ def main(argv=None):
943
943
  "argument --no-comments: not allowed with argument "
944
944
  "--comments-to-margin"
945
945
  )
946
+ if (
947
+ namespace.command == "qmdb"
948
+ and namespace.always_code
949
+ and namespace.no_code
950
+ ):
951
+ parser._pydifft_subparsers["qmdb"].error(
952
+ "argument --no-code: not allowed with argument --always-code"
953
+ )
946
954
  handler = namespace._handler
947
955
  handler_kwargs = dict(vars(namespace))
948
956
  handler_kwargs.pop("_handler", None)
949
957
  handler_kwargs.pop("command", None)
950
- handler(**handler_kwargs)
958
+ # {{{ explain Chrome and ChromeDriver version mismatches
959
+ from selenium.common.exceptions import SessionNotCreatedException
960
+
961
+ try:
962
+ handler(**handler_kwargs)
963
+ except SessionNotCreatedException as exc:
964
+ details = str(exc)
965
+ driver_match = re.search(
966
+ r"ChromeDriver only supports Chrome version (\d+)",
967
+ details,
968
+ )
969
+ browser_match = re.search(
970
+ r"Current browser version is (\d+)",
971
+ details,
972
+ )
973
+ if driver_match is None or browser_match is None:
974
+ raise
975
+
976
+ driver_major = int(driver_match.group(1))
977
+ browser_major = int(browser_match.group(1))
978
+ print(
979
+ "pydifft: Chrome could not start because ChromeDriver "
980
+ f"{driver_major} and Chrome {browser_major} do not match.",
981
+ file=sys.stderr,
982
+ )
983
+ if shutil.which("apt-get") is not None:
984
+ if browser_major < driver_major:
985
+ binary_match = re.search(
986
+ r"binary path ([^;\n]+)",
987
+ details,
988
+ )
989
+ browser_binary = (
990
+ binary_match.group(1) if binary_match is not None else ""
991
+ )
992
+ browser_package = (
993
+ "chromium"
994
+ if "chromium" in browser_binary
995
+ else "google-chrome-stable"
996
+ )
997
+ packages = browser_package
998
+ elif driver_major < browser_major:
999
+ packages = "chromium-driver"
1000
+ else:
1001
+ packages = "google-chrome-stable chromium-driver"
1002
+ print(
1003
+ "Update the older system package:\n"
1004
+ " sudo apt update\n"
1005
+ f" sudo apt install --only-upgrade {packages}",
1006
+ file=sys.stderr,
1007
+ )
1008
+ else:
1009
+ print(
1010
+ "Update the older Chrome or ChromeDriver installation so "
1011
+ "their major versions match.",
1012
+ file=sys.stderr,
1013
+ )
1014
+ print(
1015
+ "Then retry. You can verify the versions with:\n"
1016
+ " google-chrome --version\n"
1017
+ " chromedriver --version",
1018
+ file=sys.stderr,
1019
+ )
1020
+ raise SystemExit(1) from None
1021
+ # }}}
951
1022
 
952
1023
 
953
1024
  if __name__ == "__main__":
@@ -52,72 +52,88 @@ def forward_search_listener(stop_event, search_queue):
52
52
  server.close()
53
53
 
54
54
 
55
- def _file_contains_text(path, text):
55
+ def _comment_filter_mode(path, packaged_filters=None):
56
56
  if not os.path.exists(path):
57
- return False
57
+ return "missing"
58
+ if packaged_filters is None:
59
+ package_dir = os.path.dirname(os.path.abspath(__file__))
60
+ packaged_filters = {
61
+ "default": os.path.join(package_dir, "comment_tags.lua"),
62
+ "margin": os.path.join(package_dir, "comment_tags_margin.lua"),
63
+ "none": os.path.join(package_dir, "comment_tags_no_comments.lua"),
64
+ }
58
65
  with open(path, encoding="utf-8") as fp:
59
- return text in fp.read()
60
-
61
-
62
- def _comment_filter_mode(path):
63
- if not os.path.exists(path):
64
- return None
65
- if _file_contains_text(path, MARGIN_COMMENTS_FILTER_MARKER):
66
- return "margin"
67
- if _file_contains_text(path, NO_COMMENTS_FILTER_MARKER):
68
- return "none"
69
- return "default"
70
-
66
+ filter_text = fp.read()
67
+ for mode in ["default", "margin", "none"]:
68
+ with open(packaged_filters[mode], encoding="utf-8") as fp:
69
+ if filter_text == fp.read():
70
+ return mode
71
+ return "custom"
71
72
 
72
- def _set_comment_filter_mode(source_dir, mode):
73
- if mode not in {"default", "margin", "none"}:
74
- raise ValueError(f"Unknown comment filter mode: {mode}")
75
- package_dir = os.path.dirname(os.path.abspath(__file__))
76
- active_filter = os.path.join(source_dir, "comment_tags.lua")
77
- inactive_filter = os.path.join(source_dir, "comment_tags.lua.inactive")
78
- packaged_filters = {
79
- "default": os.path.join(package_dir, "comment_tags.lua"),
80
- "margin": os.path.join(package_dir, "comment_tags_margin.lua"),
81
- "none": os.path.join(package_dir, "comment_tags_no_comments.lua"),
82
- }
83
73
 
84
- active_mode = _comment_filter_mode(active_filter)
85
- inactive_mode = _comment_filter_mode(inactive_filter)
86
-
87
- if mode == "default":
88
- if active_mode == "default":
89
- return
90
- if active_mode in {"margin", "none"}:
91
- if inactive_mode == "default":
92
- temp_filter = active_filter + ".swap_tmp"
93
- os.replace(active_filter, temp_filter)
94
- os.replace(inactive_filter, active_filter)
95
- os.replace(temp_filter, inactive_filter)
96
- else:
97
- os.replace(active_filter, inactive_filter)
98
- shutil.copy2(packaged_filters["default"], active_filter)
99
- return
100
- if active_mode is None:
101
- if inactive_mode == "default":
102
- os.replace(inactive_filter, active_filter)
103
- else:
104
- shutil.copy2(packaged_filters["default"], active_filter)
105
- return
106
- shutil.copy2(packaged_filters["default"], active_filter)
107
- return
74
+ def _confirm_restore_comment_filter(active_mode):
75
+ if active_mode == "none":
76
+ message = (
77
+ "The current lua filter is the one that does not show comments, "
78
+ "but you ran without the --no-comments flag.\n\n"
79
+ "Note that you have not locally edited the filter relative to "
80
+ "the library default.\n\n"
81
+ "Do you want to keep the current filter, or restore the library "
82
+ "default that shows comments?"
83
+ )
84
+ default_choice = "restore"
85
+ elif active_mode == "custom":
86
+ message = (
87
+ "The current lua filter does not match the pyDiffTools library "
88
+ "default or the no-comments filter, but you ran without the "
89
+ "--no-comments flag.\n\n"
90
+ "Note that this looks like a locally edited filter.\n\n"
91
+ "Do you want to keep the current filter, or restore the library "
92
+ "default that shows comments?"
93
+ )
94
+ default_choice = "keep"
95
+ else:
96
+ raise ValueError(
97
+ "Comment filter restore prompt is only valid for custom or "
98
+ f"no-comments filters, not {active_mode!r}"
99
+ )
100
+ prompt_script = """
101
+ import sys
102
+ from PySide6.QtWidgets import QApplication, QMessageBox
108
103
 
109
- if active_mode == mode:
110
- return
111
- if active_mode == "default":
112
- os.replace(active_filter, inactive_filter)
113
- shutil.copy2(packaged_filters[mode], active_filter)
114
- return
115
- if active_mode in {"margin", "none"}:
116
- shutil.copy2(packaged_filters[mode], active_filter)
117
- return
118
- if active_mode is None and inactive_mode != "default":
119
- shutil.copy2(packaged_filters["default"], inactive_filter)
120
- shutil.copy2(packaged_filters[mode], active_filter)
104
+ app = QApplication(sys.argv[:1])
105
+ box = QMessageBox()
106
+ box.setWindowTitle("pydifft cpb")
107
+ box.setIcon(QMessageBox.Icon.Question)
108
+ box.setText(sys.argv[1])
109
+ keep_button = box.addButton(
110
+ "Keep current filter", QMessageBox.ButtonRole.RejectRole
111
+ )
112
+ restore_button = box.addButton(
113
+ "Restore library default", QMessageBox.ButtonRole.AcceptRole
114
+ )
115
+ if sys.argv[2] == "restore":
116
+ box.setDefaultButton(restore_button)
117
+ else:
118
+ box.setDefaultButton(keep_button)
119
+ box.exec()
120
+ if box.clickedButton() is restore_button:
121
+ sys.exit(0)
122
+ sys.exit(1)
123
+ """
124
+ result = subprocess.run(
125
+ [sys.executable, "-c", prompt_script, message, default_choice],
126
+ capture_output=True,
127
+ text=True,
128
+ )
129
+ if result.returncode == 0:
130
+ return True
131
+ if result.returncode == 1:
132
+ return False
133
+ stderr = result.stderr.strip()
134
+ if stderr:
135
+ raise RuntimeError(f"pydifft cpb filter dialog failed: {stderr}")
136
+ raise RuntimeError("pydifft cpb filter dialog failed.")
121
137
 
122
138
 
123
139
  def run_pandoc(
@@ -168,10 +184,87 @@ def run_pandoc(
168
184
  ):
169
185
  # Keep the active comment filter in the markdown directory so pandoc
170
186
  # picks it up alongside other user-supplied project filters.
171
- _set_comment_filter_mode(source_dir, comment_filter_mode)
187
+ # {{{ select the active comment_tags.lua filter
188
+ package_dir = os.path.dirname(os.path.abspath(__file__))
189
+ active_filter = os.path.join(source_dir, "comment_tags.lua")
190
+ inactive_filter = os.path.join(
191
+ source_dir, "comment_tags.lua.inactive"
192
+ )
193
+ packaged_filters = {
194
+ "default": os.path.join(package_dir, "comment_tags.lua"),
195
+ "margin": os.path.join(package_dir, "comment_tags_margin.lua"),
196
+ "none": os.path.join(package_dir, "comment_tags_no_comments.lua"),
197
+ }
198
+ active_mode = _comment_filter_mode(active_filter, packaged_filters)
199
+ inactive_mode = _comment_filter_mode(inactive_filter, packaged_filters)
200
+
201
+ if comment_filter_mode == "default":
202
+ if active_mode == "default":
203
+ effective_filter_mode = "default"
204
+ elif active_mode == "none":
205
+ if _confirm_restore_comment_filter("none"):
206
+ shutil.copy2(
207
+ packaged_filters["default"], active_filter
208
+ )
209
+ effective_filter_mode = "default"
210
+ else:
211
+ effective_filter_mode = "none"
212
+ elif active_mode == "custom":
213
+ if _confirm_restore_comment_filter("custom"):
214
+ shutil.copy2(
215
+ packaged_filters["default"], active_filter
216
+ )
217
+ effective_filter_mode = "default"
218
+ else:
219
+ effective_filter_mode = "custom"
220
+ elif active_mode == "margin":
221
+ if inactive_mode in {"default", "custom"}:
222
+ temp_filter = active_filter + ".swap_tmp"
223
+ os.replace(active_filter, temp_filter)
224
+ os.replace(inactive_filter, active_filter)
225
+ os.replace(temp_filter, inactive_filter)
226
+ effective_filter_mode = inactive_mode
227
+ else:
228
+ os.replace(active_filter, inactive_filter)
229
+ shutil.copy2(
230
+ packaged_filters["default"], active_filter
231
+ )
232
+ effective_filter_mode = "default"
233
+ elif active_mode == "missing":
234
+ if inactive_mode in {"default", "custom"}:
235
+ os.replace(inactive_filter, active_filter)
236
+ effective_filter_mode = inactive_mode
237
+ else:
238
+ shutil.copy2(
239
+ packaged_filters["default"], active_filter
240
+ )
241
+ effective_filter_mode = "default"
242
+ else:
243
+ shutil.copy2(packaged_filters["default"], active_filter)
244
+ effective_filter_mode = "default"
245
+ else:
246
+ effective_filter_mode = comment_filter_mode
247
+ if active_mode == comment_filter_mode:
248
+ pass
249
+ elif active_mode in {"default", "custom"}:
250
+ os.replace(active_filter, inactive_filter)
251
+ shutil.copy2(
252
+ packaged_filters[comment_filter_mode], active_filter
253
+ )
254
+ else:
255
+ if active_mode == "missing" and inactive_mode not in {
256
+ "default",
257
+ "custom",
258
+ }:
259
+ shutil.copy2(
260
+ packaged_filters["default"], inactive_filter
261
+ )
262
+ shutil.copy2(
263
+ packaged_filters[comment_filter_mode], active_filter
264
+ )
265
+ # }}}
172
266
  # Only copy the comment UI assets when comments should be rendered.
173
- if comment_filter_mode != "none":
174
- package_dir = os.path.dirname(os.path.abspath(__file__))
267
+ if effective_filter_mode != "none":
175
268
  for asset_name in ["comments.css", "comment_toggle.js"]:
176
269
  target_path = os.path.join(source_dir, asset_name)
177
270
  if not os.path.exists(target_path):
@@ -483,6 +576,4 @@ def cpb(filename, comments_to_margin=False, no_comments=False):
483
576
 
484
577
 
485
578
  if __name__ == "__main__":
486
- filename = sys.argv[1]
487
- cpb(filename)
488
- # Open the HTML file in the default web browser
579
+ raise SystemExit("Use `pydifft cpb <filename.md>` instead.")
@@ -0,0 +1,67 @@
1
+ """Small display helpers for fast notebook builds."""
2
+
3
+
4
+ class _NotebookCapture:
5
+ """Emit notebook content in an explicit order."""
6
+
7
+ def __init__(self):
8
+ self._figures = []
9
+
10
+ def __enter__(self):
11
+ return self
12
+
13
+ def __exit__(self, exc_type, exc, tb):
14
+ for fig in self._figures:
15
+ self._close_figure(fig)
16
+ return False
17
+
18
+ def md(self, text):
19
+ """Display markdown text as a Jupyter Markdown MIME bundle."""
20
+ from IPython.display import Markdown, display
21
+
22
+ display(Markdown(text))
23
+
24
+ def fig(self, obj=None):
25
+ """Display a Matplotlib figure-like object and close it afterward."""
26
+ from io import BytesIO
27
+
28
+ from IPython.display import Image, display
29
+
30
+ fig = self._figure_from_object(obj)
31
+ if hasattr(fig, "savefig"):
32
+ data = BytesIO()
33
+ fig.savefig(data, format="png", bbox_inches="tight")
34
+ display(Image(data=data.getvalue()))
35
+ else:
36
+ display(fig)
37
+ self._figures.append(fig)
38
+ self._close_figure(fig)
39
+
40
+ def _figure_from_object(self, obj):
41
+ import matplotlib.pyplot as plt
42
+
43
+ if obj is None:
44
+ return plt.gcf()
45
+ if isinstance(obj, (list, tuple)):
46
+ if not obj:
47
+ raise ValueError("cannot display an empty figure object list")
48
+ return self._figure_from_object(obj[0])
49
+ if hasattr(obj, "get_figure"):
50
+ fig = obj.get_figure()
51
+ if fig is not None:
52
+ return fig
53
+ if hasattr(obj, "figure"):
54
+ fig = obj.figure
55
+ if fig is not None and fig is not obj:
56
+ return fig
57
+ return obj
58
+
59
+ def _close_figure(self, fig):
60
+ import matplotlib.pyplot as plt
61
+
62
+ plt.close(fig)
63
+
64
+
65
+ def nb_capture():
66
+ """Return an ordered notebook output capture context."""
67
+ return _NotebookCapture()