pyDiffTools 0.1.27__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.
- {pydifftools-0.1.27/pyDiffTools.egg-info → pydifftools-0.1.29}/PKG-INFO +3 -1
- {pydifftools-0.1.27 → pydifftools-0.1.29/pyDiffTools.egg-info}/PKG-INFO +3 -1
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pyDiffTools.egg-info/SOURCES.txt +1 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pyDiffTools.egg-info/requires.txt +2 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/command_line.py +72 -1
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/continuous.py +158 -67
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/flowchart/graph.py +133 -36
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/flowchart/watch_graph.py +59 -48
- pydifftools-0.1.29/pydifftools/notebook/display.py +67 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/notebook/fast_build.py +340 -22
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pyproject.toml +3 -1
- {pydifftools-0.1.27 → pydifftools-0.1.29}/tests/test_command_line_help.py +64 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/LICENSE.md +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/MANIFEST.in +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/README.rst +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/_quarto.yml +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/1a724af72b16f5a9e607e12b1c721645/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/1b28fc9daac9081847e5161b2c546f8a/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/231f64eee282fa225d1104935cf80a24/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/26e56f6b0ff54851a45145157f2f0dc4/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/311fabd7029ffd050d056e2f316eb50f/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/57da2021e5b156ac3adf01398201c723/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/62b24ea7da75011d92b0f8924faa208d/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/7f1b20d69d889514ab5d1cc92e3cb14f/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/86f74c8c54a87ff892d9b15dd714e8f0/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/88893b4234eac2945d9d6cb2e277f186/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/9a40046ada6f582ee34af00fbdbfb417/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/a1bf4d270d0641ff41faf1d7cce3439a/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/a3789f7d9585a781f2a1c60ce95ff10d/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/be46ecba858d39ad5f0c46902ddf1c02/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/d0cbc57a12f2ccce710a5afd04cc05e7/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/d3e12d320b14228f701231ae32ddd7dd/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/eb434f61555438d020a6970a5dbf9ee8/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/f6b0e73aa7fa029134665d4dde57e096/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/f719a6e4ff09873cb0ffb06ec9d232f9/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/executed/feeee244a7ce3d60e1a227eb604df823/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/example.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/example.tex +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/index.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/subproject1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/subproject1/.jupyter_cache/executed/ca90e4df5f4f0583df6554156a68dc7f/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/subproject1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/subproject1/independent.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/subproject1/index.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/subproject1/tasks.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/subproject1/test_include.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/subproject1/tryforerror.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/example_notebook/project1/test_include.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pyDiffTools.egg-info/dependency_links.txt +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pyDiffTools.egg-info/entry_points.txt +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pyDiffTools.egg-info/top_level.txt +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/__init__.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/browser_lifecycle.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/check_numbers.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/command_registry.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/comment_functions.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/comment_tags.lua +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/comment_tags_margin.lua +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/comment_tags_no_comments.lua +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/comment_toggle.js +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/comments.css +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/copy_files.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/diff-doc.js +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/doc_contents.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/flowchart/__init__.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/flowchart/dot_to_yaml.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/git_gd.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/git_gd_qt.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/html_comments.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/html_uncomments.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/log_example.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/match_spaces.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/notebook/__init__.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/notebook/tex_to_qmd.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/onewordify.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/onewordify_undo.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/outline.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/rearrange_tex.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/searchacro.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/separate_comments.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/split_conflict.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/unseparate_comments.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/update_check.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/wrap_sentences.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/pydifftools/xml2xlsx.vbs +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/setup.cfg +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/tests/test_browser_lifecycle.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/tests/test_continuous_shutdown.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/tests/test_rrng.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.29}/tests/test_tex_to_qmd.py +0 -0
- {pydifftools-0.1.27 → 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.
|
|
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.
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
55
|
+
def _comment_filter_mode(path, packaged_filters=None):
|
|
56
56
|
if not os.path.exists(path):
|
|
57
|
-
return
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
487
|
-
cpb(filename)
|
|
488
|
-
# Open the HTML file in the default web browser
|
|
579
|
+
raise SystemExit("Use `pydifft cpb <filename.md>` instead.")
|
|
@@ -321,8 +321,7 @@ def _node_text_with_due(node, depends_on_undated_parent=False):
|
|
|
321
321
|
|
|
322
322
|
# Completed tasks should always show their calendar date so the original
|
|
323
323
|
# deadline remains visible even if it was today or overdue when finished.
|
|
324
|
-
|
|
325
|
-
is_completed = "complete" in style_name
|
|
324
|
+
is_completed = node_is_completed(node)
|
|
326
325
|
# Replace the actual date with high-visibility notices when the deadline
|
|
327
326
|
# is today or overdue. These are rendered in a bold 12 pt font so they are
|
|
328
327
|
# immediately noticeable in the diagram. Completed tasks skip these
|
|
@@ -367,6 +366,68 @@ def _node_text_with_due(node, depends_on_undated_parent=False):
|
|
|
367
366
|
return formatted
|
|
368
367
|
|
|
369
368
|
|
|
369
|
+
def node_is_completed(node):
|
|
370
|
+
return "complete" in str(node.get("style", ""))
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def node_is_endpoint(node):
|
|
374
|
+
return "endpoint" in str(node.get("style", ""))
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def trace_ancestors(
|
|
378
|
+
data,
|
|
379
|
+
start_name,
|
|
380
|
+
stop_at=None,
|
|
381
|
+
include_stopped=False,
|
|
382
|
+
skip_over=None,
|
|
383
|
+
):
|
|
384
|
+
ancestors = []
|
|
385
|
+
nodes = data.get("nodes", {})
|
|
386
|
+
if start_name not in nodes:
|
|
387
|
+
return ancestors
|
|
388
|
+
ancestors_to_visit = list(nodes[start_name].get("parents", []))
|
|
389
|
+
already_seen = set([start_name])
|
|
390
|
+
while ancestors_to_visit:
|
|
391
|
+
ancestor = ancestors_to_visit.pop()
|
|
392
|
+
if ancestor in already_seen:
|
|
393
|
+
continue
|
|
394
|
+
already_seen.add(ancestor)
|
|
395
|
+
if ancestor not in nodes:
|
|
396
|
+
continue
|
|
397
|
+
ancestor_node = nodes[ancestor]
|
|
398
|
+
if stop_at is not None and stop_at(ancestor, ancestor_node):
|
|
399
|
+
if include_stopped:
|
|
400
|
+
ancestors.append(ancestor)
|
|
401
|
+
continue
|
|
402
|
+
if skip_over is not None and skip_over(ancestor, ancestor_node):
|
|
403
|
+
for parent in ancestor_node.get("parents", []):
|
|
404
|
+
ancestors_to_visit.append(parent)
|
|
405
|
+
continue
|
|
406
|
+
ancestors.append(ancestor)
|
|
407
|
+
for parent in ancestor_node.get("parents", []):
|
|
408
|
+
ancestors_to_visit.append(parent)
|
|
409
|
+
return ancestors
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def endpoint_projects(data):
|
|
413
|
+
endpoints = set()
|
|
414
|
+
for name, node_data in data.get("nodes", {}).items():
|
|
415
|
+
if node_is_endpoint(node_data):
|
|
416
|
+
endpoints.add(name)
|
|
417
|
+
|
|
418
|
+
projects = {}
|
|
419
|
+
for endpoint in sorted(endpoints):
|
|
420
|
+
projects[endpoint] = [endpoint]
|
|
421
|
+
projects[endpoint].extend(
|
|
422
|
+
trace_ancestors(
|
|
423
|
+
data,
|
|
424
|
+
endpoint,
|
|
425
|
+
stop_at=lambda name, _node: name in endpoints,
|
|
426
|
+
)
|
|
427
|
+
)
|
|
428
|
+
return projects
|
|
429
|
+
|
|
430
|
+
|
|
370
431
|
def _node_label(text, wrap_width=55):
|
|
371
432
|
if text is None:
|
|
372
433
|
return ""
|
|
@@ -415,6 +476,8 @@ def _append_node(
|
|
|
415
476
|
if node_has_due:
|
|
416
477
|
for parent in node.get("parents", []):
|
|
417
478
|
parent_node = data["nodes"].get(parent, {})
|
|
479
|
+
if node_is_completed(parent_node):
|
|
480
|
+
continue
|
|
418
481
|
parent_due = parent_node.get("due")
|
|
419
482
|
if parent_due is None or not str(parent_due).strip():
|
|
420
483
|
depends_on_undated_parent = True
|
|
@@ -586,6 +649,65 @@ def yaml_to_dot(data, wrap_width=55, order_by_date=False):
|
|
|
586
649
|
return "\n".join(lines)
|
|
587
650
|
|
|
588
651
|
|
|
652
|
+
def _filter_nodes_for_dot(
|
|
653
|
+
data, node_names, include_completed_endpoint_ancestors=False
|
|
654
|
+
):
|
|
655
|
+
nodes = data.get("nodes", {})
|
|
656
|
+
included = []
|
|
657
|
+
included_set = set()
|
|
658
|
+
|
|
659
|
+
def include_node(name):
|
|
660
|
+
if name not in included_set:
|
|
661
|
+
included.append(name)
|
|
662
|
+
included_set.add(name)
|
|
663
|
+
|
|
664
|
+
for name in node_names:
|
|
665
|
+
if name not in nodes:
|
|
666
|
+
continue
|
|
667
|
+
if node_is_completed(nodes[name]):
|
|
668
|
+
continue
|
|
669
|
+
include_node(name)
|
|
670
|
+
|
|
671
|
+
if include_completed_endpoint_ancestors:
|
|
672
|
+
for name in list(included):
|
|
673
|
+
completed_endpoint_ancestors = trace_ancestors(
|
|
674
|
+
data,
|
|
675
|
+
name,
|
|
676
|
+
stop_at=lambda _name, node: node_is_endpoint(node),
|
|
677
|
+
include_stopped=True,
|
|
678
|
+
skip_over=lambda _name, node: (
|
|
679
|
+
node_is_completed(node) and not node_is_endpoint(node)
|
|
680
|
+
),
|
|
681
|
+
)
|
|
682
|
+
for ancestor in completed_endpoint_ancestors:
|
|
683
|
+
if ancestor not in nodes:
|
|
684
|
+
continue
|
|
685
|
+
if node_is_completed(nodes[ancestor]) and node_is_endpoint(
|
|
686
|
+
nodes[ancestor]
|
|
687
|
+
):
|
|
688
|
+
include_node(ancestor)
|
|
689
|
+
|
|
690
|
+
data_for_dot = {"nodes": {}, "styles": {}}
|
|
691
|
+
if "styles" in data:
|
|
692
|
+
data_for_dot["styles"] = data["styles"]
|
|
693
|
+
for name in included:
|
|
694
|
+
data_for_dot["nodes"][name] = dict(nodes[name])
|
|
695
|
+
for name in data_for_dot["nodes"]:
|
|
696
|
+
if "children" in data_for_dot["nodes"][name]:
|
|
697
|
+
data_for_dot["nodes"][name]["children"] = [
|
|
698
|
+
child
|
|
699
|
+
for child in data_for_dot["nodes"][name]["children"]
|
|
700
|
+
if child in included_set
|
|
701
|
+
]
|
|
702
|
+
if "parents" in data_for_dot["nodes"][name]:
|
|
703
|
+
data_for_dot["nodes"][name]["parents"] = [
|
|
704
|
+
parent
|
|
705
|
+
for parent in data_for_dot["nodes"][name]["parents"]
|
|
706
|
+
if parent in included_set
|
|
707
|
+
]
|
|
708
|
+
return data_for_dot
|
|
709
|
+
|
|
710
|
+
|
|
589
711
|
def save_graph_yaml(path, data):
|
|
590
712
|
# Ensure stored dates are normalized before writing.
|
|
591
713
|
_normalize_graph_dates(data)
|
|
@@ -610,6 +732,7 @@ def write_dot_from_yaml(
|
|
|
610
732
|
old_data=None,
|
|
611
733
|
validate_due_dates=False,
|
|
612
734
|
filter_task=None,
|
|
735
|
+
filter_completed=False,
|
|
613
736
|
resolve_due_date_conflict=None,
|
|
614
737
|
):
|
|
615
738
|
data = load_graph_yaml(str(yaml_path), old_data=old_data)
|
|
@@ -755,40 +878,14 @@ def write_dot_from_yaml(
|
|
|
755
878
|
)
|
|
756
879
|
# Include the target task alongside its ancestors in the filtered view.
|
|
757
880
|
ancestors = set([filter_task])
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
parents_to_check.append(grandparent)
|
|
767
|
-
incomplete_ancestors = set()
|
|
768
|
-
for name in ancestors:
|
|
769
|
-
if name not in data["nodes"]:
|
|
770
|
-
continue
|
|
771
|
-
if "complete" in str(data["nodes"][name].get("style", "")):
|
|
772
|
-
continue
|
|
773
|
-
incomplete_ancestors.add(name)
|
|
774
|
-
data_for_dot = {"nodes": {}, "styles": {}}
|
|
775
|
-
if "styles" in data:
|
|
776
|
-
data_for_dot["styles"] = data["styles"]
|
|
777
|
-
for name in incomplete_ancestors:
|
|
778
|
-
data_for_dot["nodes"][name] = dict(data["nodes"][name])
|
|
779
|
-
for name in data_for_dot["nodes"]:
|
|
780
|
-
if "children" in data_for_dot["nodes"][name]:
|
|
781
|
-
data_for_dot["nodes"][name]["children"] = [
|
|
782
|
-
child
|
|
783
|
-
for child in data_for_dot["nodes"][name]["children"]
|
|
784
|
-
if child in incomplete_ancestors
|
|
785
|
-
]
|
|
786
|
-
if "parents" in data_for_dot["nodes"][name]:
|
|
787
|
-
data_for_dot["nodes"][name]["parents"] = [
|
|
788
|
-
parent
|
|
789
|
-
for parent in data_for_dot["nodes"][name]["parents"]
|
|
790
|
-
if parent in incomplete_ancestors
|
|
791
|
-
]
|
|
881
|
+
ancestors.update(trace_ancestors(data, filter_task))
|
|
882
|
+
data_for_dot = _filter_nodes_for_dot(data, ancestors)
|
|
883
|
+
elif filter_completed:
|
|
884
|
+
data_for_dot = _filter_nodes_for_dot(
|
|
885
|
+
data,
|
|
886
|
+
data.get("nodes", {}).keys(),
|
|
887
|
+
include_completed_endpoint_ancestors=True,
|
|
888
|
+
)
|
|
792
889
|
dot_str = yaml_to_dot(
|
|
793
890
|
data_for_dot, wrap_width=wrap_width, order_by_date=order_by_date
|
|
794
891
|
)
|