pyDiffTools 0.1.56__tar.gz → 0.1.58__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.56/pyDiffTools.egg-info → pydifftools-0.1.58}/PKG-INFO +12 -1
- {pydifftools-0.1.56 → pydifftools-0.1.58}/README.rst +11 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58/pyDiffTools.egg-info}/PKG-INFO +12 -1
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/browser_lifecycle.py +92 -12
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/command_line.py +11 -21
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/continuous.py +63 -47
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/wrap_sentences.py +11 -2
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pyproject.toml +1 -1
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_browser_lifecycle.py +82 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_continuous_shutdown.py +89 -72
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_forward_search.py +1 -1
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_wrap_check.py +40 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/LICENSE.md +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/MANIFEST.in +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/_quarto.yml +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/1a724af72b16f5a9e607e12b1c721645/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/1b28fc9daac9081847e5161b2c546f8a/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/231f64eee282fa225d1104935cf80a24/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/26e56f6b0ff54851a45145157f2f0dc4/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/311fabd7029ffd050d056e2f316eb50f/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/57da2021e5b156ac3adf01398201c723/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/62b24ea7da75011d92b0f8924faa208d/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/7f1b20d69d889514ab5d1cc92e3cb14f/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/86f74c8c54a87ff892d9b15dd714e8f0/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/88893b4234eac2945d9d6cb2e277f186/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/9a40046ada6f582ee34af00fbdbfb417/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/a1bf4d270d0641ff41faf1d7cce3439a/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/a3789f7d9585a781f2a1c60ce95ff10d/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/be46ecba858d39ad5f0c46902ddf1c02/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/d0cbc57a12f2ccce710a5afd04cc05e7/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/d3e12d320b14228f701231ae32ddd7dd/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/eb434f61555438d020a6970a5dbf9ee8/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/f6b0e73aa7fa029134665d4dde57e096/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/f719a6e4ff09873cb0ffb06ec9d232f9/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/executed/feeee244a7ce3d60e1a227eb604df823/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/example.qmd +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/example.tex +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/index.qmd +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/subproject1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/subproject1/.jupyter_cache/executed/ca90e4df5f4f0583df6554156a68dc7f/base.ipynb +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/subproject1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/subproject1/independent.qmd +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/subproject1/index.qmd +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/subproject1/tasks.qmd +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/subproject1/test_include.qmd +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/subproject1/tryforerror.qmd +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/example_notebook/project1/test_include.qmd +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pyDiffTools.egg-info/SOURCES.txt +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pyDiffTools.egg-info/dependency_links.txt +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pyDiffTools.egg-info/entry_points.txt +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pyDiffTools.egg-info/requires.txt +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pyDiffTools.egg-info/top_level.txt +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/__init__.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/bibliography.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/check_numbers.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/citation_dialog.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/command_registry.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/comment_filter_history.json +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/comment_functions.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/comment_migration.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/comment_tags.lua +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/comment_tags_margin.lua +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/comment_tags_no_comments.lua +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/comment_toggle.js +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/comments.css +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/comments_author_colors.css +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/completions/git-mergein +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/copy_files.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/diff-doc.js +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/doc_contents.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/flowchart/__init__.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/flowchart/comparison.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/flowchart/dot_to_yaml.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/flowchart/graph.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/flowchart/source_jump.js +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/flowchart/svg-pan-zoom.min.js +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/flowchart/watch_graph.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/flowchart/wgrph_view.js +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/forward_search.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/git_aliases.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/git_gd.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/git_gd_history.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/git_gd_image.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/git_gd_qt.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/html_comments.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/html_uncomments.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/line_alignment.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/log_example.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/match_spaces.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/notebook/__init__.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/notebook/display.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/notebook/fast_build.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/notebook/tex_to_qmd.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/onewordify.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/onewordify_undo.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/outline.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/pdf_diff.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/rearrange_tex.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/searchacro.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/separate_comments.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/source_jump.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/split_conflict.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/unseparate_comments.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/update_check.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/wrapping_options.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/xml2xlsx.vbs +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/pydifftools/zotero.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/setup.cfg +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_bibliography.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_check_single_use.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_command_line_help.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_comment_filters.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_comment_migration.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_git_gd_history.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_git_gd_image.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_git_gd_qt.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_git_mergein.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_git_pdf_diff.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_line_alignment.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_pdf_diff.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_qt_lifecycle.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_rrng.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_source_jump.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_tex_to_qmd.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_update_check.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_wrapping.py +0 -0
- {pydifftools-0.1.56 → pydifftools-0.1.58}/tests/test_zotero.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.58
|
|
4
4
|
Summary: Diff tools
|
|
5
5
|
Author: J M Franck
|
|
6
6
|
License: Copyright (c) 2015, jmfranck
|
|
@@ -163,6 +163,17 @@ included are (listed in order of fun/utility):
|
|
|
163
163
|
``python .github/scripts/update_comment_filter_history.py``; the release
|
|
164
164
|
workflow also refreshes it before building distributions.
|
|
165
165
|
|
|
166
|
+
To run Lua filters before pandoc-crossref, declare an ordered list in
|
|
167
|
+
the Markdown YAML header::
|
|
168
|
+
|
|
169
|
+
filters-before-crossref:
|
|
170
|
+
- filters/panel-references.lua
|
|
171
|
+
|
|
172
|
+
Paths are resolved relative to the Markdown file. Each declared filter
|
|
173
|
+
runs once, in listed order, and is excluded from the adjacent Lua filters
|
|
174
|
+
that normally run afterward. Missing files produce a build error.
|
|
175
|
+
Documents without this setting retain the existing filter order.
|
|
176
|
+
|
|
166
177
|
Declare a single local ``.bib`` file with ``bibliography: references.bib``
|
|
167
178
|
in the Markdown YAML header; relative paths are resolved beside the
|
|
168
179
|
Markdown file. Without that declaration, ``cpb`` uses a single adjacent
|
|
@@ -108,6 +108,17 @@ included are (listed in order of fun/utility):
|
|
|
108
108
|
``python .github/scripts/update_comment_filter_history.py``; the release
|
|
109
109
|
workflow also refreshes it before building distributions.
|
|
110
110
|
|
|
111
|
+
To run Lua filters before pandoc-crossref, declare an ordered list in
|
|
112
|
+
the Markdown YAML header::
|
|
113
|
+
|
|
114
|
+
filters-before-crossref:
|
|
115
|
+
- filters/panel-references.lua
|
|
116
|
+
|
|
117
|
+
Paths are resolved relative to the Markdown file. Each declared filter
|
|
118
|
+
runs once, in listed order, and is excluded from the adjacent Lua filters
|
|
119
|
+
that normally run afterward. Missing files produce a build error.
|
|
120
|
+
Documents without this setting retain the existing filter order.
|
|
121
|
+
|
|
111
122
|
Declare a single local ``.bib`` file with ``bibliography: references.bib``
|
|
112
123
|
in the Markdown YAML header; relative paths are resolved beside the
|
|
113
124
|
Markdown file. Without that declaration, ``cpb`` uses a single adjacent
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyDiffTools
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.58
|
|
4
4
|
Summary: Diff tools
|
|
5
5
|
Author: J M Franck
|
|
6
6
|
License: Copyright (c) 2015, jmfranck
|
|
@@ -163,6 +163,17 @@ included are (listed in order of fun/utility):
|
|
|
163
163
|
``python .github/scripts/update_comment_filter_history.py``; the release
|
|
164
164
|
workflow also refreshes it before building distributions.
|
|
165
165
|
|
|
166
|
+
To run Lua filters before pandoc-crossref, declare an ordered list in
|
|
167
|
+
the Markdown YAML header::
|
|
168
|
+
|
|
169
|
+
filters-before-crossref:
|
|
170
|
+
- filters/panel-references.lua
|
|
171
|
+
|
|
172
|
+
Paths are resolved relative to the Markdown file. Each declared filter
|
|
173
|
+
runs once, in listed order, and is excluded from the adjacent Lua filters
|
|
174
|
+
that normally run afterward. Missing files produce a build error.
|
|
175
|
+
Documents without this setting retain the existing filter order.
|
|
176
|
+
|
|
166
177
|
Declare a single local ``.bib`` file with ``bibliography: references.bib``
|
|
167
178
|
in the Markdown YAML header; relative paths are resolved beside the
|
|
168
179
|
Markdown file. Without that declaration, ``cpb`` uses a single adjacent
|
|
@@ -4,8 +4,7 @@ import shutil
|
|
|
4
4
|
import subprocess
|
|
5
5
|
import sys
|
|
6
6
|
|
|
7
|
-
# callbacks
|
|
8
|
-
# that closes its live preview, so issues are dealt with before it reopens
|
|
7
|
+
# Optional callbacks for callers that need to prepare for a dialog.
|
|
9
8
|
dialog_callbacks = []
|
|
10
9
|
|
|
11
10
|
|
|
@@ -97,7 +96,7 @@ def start_browser():
|
|
|
97
96
|
|
|
98
97
|
|
|
99
98
|
def prepare_for_dialog():
|
|
100
|
-
"""
|
|
99
|
+
"""Notify registered callers before a dialog appears."""
|
|
101
100
|
for callback in list(dialog_callbacks):
|
|
102
101
|
callback()
|
|
103
102
|
|
|
@@ -108,6 +107,10 @@ def browser_window_is_alive(browser):
|
|
|
108
107
|
# the same shutdown behavior when a user closes the browser window.
|
|
109
108
|
# Do not probe with execute_script here: page navigations can briefly
|
|
110
109
|
# interrupt script execution even while the window is still open.
|
|
110
|
+
from selenium.common.exceptions import (
|
|
111
|
+
InvalidSessionIdException, NoSuchWindowException, WebDriverException,
|
|
112
|
+
)
|
|
113
|
+
|
|
111
114
|
if browser is None:
|
|
112
115
|
return False
|
|
113
116
|
try:
|
|
@@ -115,10 +118,14 @@ def browser_window_is_alive(browser):
|
|
|
115
118
|
if not handles:
|
|
116
119
|
return False
|
|
117
120
|
return True
|
|
118
|
-
except
|
|
121
|
+
except (InvalidSessionIdException, NoSuchWindowException):
|
|
119
122
|
return False
|
|
123
|
+
except WebDriverException:
|
|
124
|
+
# A transient command failure is not evidence of a closed window.
|
|
125
|
+
return True
|
|
120
126
|
|
|
121
127
|
|
|
128
|
+
# also used by: continuous.py, notebook/fast_build.py, flowchart/watch_graph.py
|
|
122
129
|
def close_browser_window(browser):
|
|
123
130
|
# Close a Selenium browser session and ignore errors from already-closed
|
|
124
131
|
# windows so cleanup paths stay simple.
|
|
@@ -137,20 +144,93 @@ def forward_search_in_browser(browser, search_text):
|
|
|
137
144
|
if browser is None or not search_text:
|
|
138
145
|
return False
|
|
139
146
|
found = browser.execute_script(
|
|
140
|
-
"""
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
r"""
|
|
148
|
+
const searchText = arguments[0];
|
|
149
|
+
let didFind = window.find && window.find(searchText);
|
|
150
|
+
// Match across inline elements, entities, and rendered references.
|
|
151
|
+
if (!didFind) {
|
|
152
|
+
const escape = text => text.replace(
|
|
153
|
+
/[.*+?^${}()|[\]\\]/g, '\\$&'
|
|
154
|
+
).replace(/\s+/g, '\\s+');
|
|
155
|
+
const reference =
|
|
156
|
+
/\[?@([a-z]{2,4}):(\w(?:[\w:./-]*[\w/-])?)\]?/gi;
|
|
157
|
+
let pattern = '', cursor = 0, match;
|
|
158
|
+
let valid = true;
|
|
159
|
+
while ((match = reference.exec(searchText))) {
|
|
160
|
+
pattern += escape(searchText.slice(cursor, match.index));
|
|
161
|
+
const id = match[1].toLowerCase() + ':' + match[2];
|
|
162
|
+
const link = Array.from(
|
|
163
|
+
document.querySelectorAll('a[href]')
|
|
164
|
+
).find(a => a.getAttribute('href') === '#' + id);
|
|
165
|
+
const target = document.getElementById(id);
|
|
166
|
+
if (!link && !target) {
|
|
167
|
+
valid = false;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
// Unlinked crossrefs still render a numeric label. For a
|
|
171
|
+
// reference alone, select its target rather than guessing
|
|
172
|
+
// which occurrence of that number the user intended.
|
|
173
|
+
if (!link && searchText.trim() === match[0]) {
|
|
174
|
+
const range = document.createRange();
|
|
175
|
+
range.selectNodeContents(target);
|
|
176
|
+
const selection = window.getSelection();
|
|
177
|
+
selection.removeAllRanges();
|
|
178
|
+
selection.addRange(range);
|
|
179
|
+
didFind = true;
|
|
180
|
+
valid = false;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
pattern += '(?:(?:eq(?:uation)?|fig(?:ure)?|tab(?:le)?|' +
|
|
184
|
+
'sec(?:tion)?|ch(?:apter)?|lst|listing)s?\\.?\\s*)?' +
|
|
185
|
+
(link ? escape(link.textContent.trim()) :
|
|
186
|
+
'\\(?\\d+(?:[.–-]\\d+)*[a-z]?\\)?');
|
|
187
|
+
cursor = reference.lastIndex;
|
|
188
|
+
}
|
|
189
|
+
pattern += escape(searchText.slice(cursor));
|
|
190
|
+
if (valid) {
|
|
191
|
+
const walker = document.createTreeWalker(
|
|
192
|
+
document.body, NodeFilter.SHOW_TEXT
|
|
193
|
+
);
|
|
194
|
+
const nodes = [], starts = [];
|
|
195
|
+
let text = '', node;
|
|
196
|
+
while ((node = walker.nextNode())) {
|
|
197
|
+
if (node.parentElement.closest(
|
|
198
|
+
'script, style, noscript, [hidden]'
|
|
199
|
+
) || !node.parentElement.getClientRects().length) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
starts.push(text.length);
|
|
203
|
+
nodes.push(node);
|
|
204
|
+
text += node.textContent;
|
|
205
|
+
}
|
|
206
|
+
const result = new RegExp(pattern, 'i').exec(text);
|
|
207
|
+
if (result && result[0].length) {
|
|
208
|
+
const start = result.index;
|
|
209
|
+
const end = start + result[0].length;
|
|
210
|
+
const first = starts.findIndex((offset, i) =>
|
|
211
|
+
offset + nodes[i].length > start
|
|
212
|
+
);
|
|
213
|
+
const last = starts.findIndex((offset, i) =>
|
|
214
|
+
offset + nodes[i].length >= end
|
|
215
|
+
);
|
|
216
|
+
const range = document.createRange();
|
|
217
|
+
range.setStart(nodes[first], start - starts[first]);
|
|
218
|
+
range.setEnd(nodes[last], end - starts[last]);
|
|
219
|
+
const selection = window.getSelection();
|
|
220
|
+
selection.removeAllRanges();
|
|
221
|
+
selection.addRange(range);
|
|
222
|
+
didFind = true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
144
225
|
}
|
|
145
|
-
var didFind = window.find(searchText);
|
|
146
226
|
if (didFind && window.getSelection) {
|
|
147
|
-
|
|
227
|
+
const selection = window.getSelection();
|
|
148
228
|
if (selection.rangeCount > 0) {
|
|
149
|
-
|
|
229
|
+
const rect = selection.getRangeAt(0).getBoundingClientRect();
|
|
150
230
|
window.scrollBy(0, rect.top - window.innerHeight / 3);
|
|
151
231
|
}
|
|
152
232
|
}
|
|
153
|
-
return didFind;
|
|
233
|
+
return Boolean(didFind);
|
|
154
234
|
""",
|
|
155
235
|
search_text,
|
|
156
236
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# PYTHON_ARGCOMPLETE_OK
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
+
import html
|
|
4
5
|
import sys
|
|
5
6
|
import os
|
|
6
7
|
import gzip
|
|
@@ -554,26 +555,14 @@ def fs(arguments):
|
|
|
554
555
|
)
|
|
555
556
|
def mfs(text):
|
|
556
557
|
# Normalize markdown-specific markup so search phrases match rendered text.
|
|
557
|
-
search_text = text
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
# dropped rather than replaced with a space. Plain str.replace is used
|
|
566
|
-
# (not regex) so this is linear time regardless of how many delimiter
|
|
567
|
-
# characters appear.
|
|
568
|
-
search_text = (
|
|
569
|
-
search_text.replace("**", "")
|
|
570
|
-
.replace("__", "")
|
|
571
|
-
.replace("~~", "")
|
|
572
|
-
.replace("*", "")
|
|
573
|
-
.replace("_", "")
|
|
574
|
-
.replace("~", "")
|
|
575
|
-
.replace("^", "")
|
|
576
|
-
)
|
|
558
|
+
search_text = html.unescape(text)
|
|
559
|
+
search_text = re.sub(r"\[@(?![a-z]{2,4}:)[^\]]+\]", " ", search_text,
|
|
560
|
+
flags=re.IGNORECASE)
|
|
561
|
+
# Keep reference IDs intact while removing surrounding Markdown markup.
|
|
562
|
+
parts = re.split(r"(?i)(@[a-z]{2,4}:\w(?:[\w:./-]*[\w/-])?)", search_text)
|
|
563
|
+
for index in range(0, len(parts), 2):
|
|
564
|
+
parts[index] = re.sub(r"[*_~^]", "", parts[index])
|
|
565
|
+
search_text = "".join(parts)
|
|
577
566
|
search_text = re.sub(r"\s+", " ", search_text).strip()
|
|
578
567
|
if not search_text:
|
|
579
568
|
search_text = text.strip()
|
|
@@ -620,7 +609,8 @@ def mfs(text):
|
|
|
620
609
|
if not filename.endswith(".md"):
|
|
621
610
|
continue
|
|
622
611
|
with open(filename, encoding="utf-8") as fp:
|
|
623
|
-
|
|
612
|
+
source_text = fp.read()
|
|
613
|
+
if text.strip() in source_text or search_text in source_text:
|
|
624
614
|
matching_files.append(filename)
|
|
625
615
|
if len(matching_files) == 0:
|
|
626
616
|
raise RuntimeError(
|
|
@@ -11,6 +11,7 @@ import re
|
|
|
11
11
|
import shutil
|
|
12
12
|
import threading
|
|
13
13
|
import queue
|
|
14
|
+
import signal
|
|
14
15
|
import traceback
|
|
15
16
|
import tempfile
|
|
16
17
|
from pathlib import Path
|
|
@@ -27,7 +28,6 @@ from .comment_migration import prepare_comment_source
|
|
|
27
28
|
from .browser_lifecycle import (
|
|
28
29
|
browser_window_is_alive,
|
|
29
30
|
close_browser_window,
|
|
30
|
-
dialog_callbacks,
|
|
31
31
|
forward_search_in_browser,
|
|
32
32
|
prepare_for_dialog,
|
|
33
33
|
start_browser,
|
|
@@ -826,6 +826,7 @@ def run_pandoc(
|
|
|
826
826
|
# }}}
|
|
827
827
|
# {{{ select the declared bibliography before adjacent-file discovery
|
|
828
828
|
bibliography_declared = False
|
|
829
|
+
metadata = None
|
|
829
830
|
bibliography_paths = []
|
|
830
831
|
front_matter = re.match(
|
|
831
832
|
r"\A\ufeff?---[^\S\n]*\n(.*?)\n(?:---|\.\.\.)[^\S\n]*(?:\n|$)",
|
|
@@ -851,6 +852,27 @@ def run_pandoc(
|
|
|
851
852
|
for value in declared
|
|
852
853
|
]
|
|
853
854
|
# }}}
|
|
855
|
+
# {{{ resolve explicitly ordered filters before cross-reference processing
|
|
856
|
+
filters_before_crossref = []
|
|
857
|
+
if isinstance(metadata, dict) and "filters-before-crossref" in metadata:
|
|
858
|
+
declared_filters = metadata["filters-before-crossref"]
|
|
859
|
+
if not isinstance(declared_filters, list) or not all(
|
|
860
|
+
isinstance(value, str) and value for value in declared_filters
|
|
861
|
+
):
|
|
862
|
+
raise ValueError(
|
|
863
|
+
"cpb: filters-before-crossref must be a list of file paths "
|
|
864
|
+
f"in {filename}"
|
|
865
|
+
)
|
|
866
|
+
for value in declared_filters:
|
|
867
|
+
filter_path = Path(source_dir, value).resolve()
|
|
868
|
+
if not filter_path.is_file():
|
|
869
|
+
raise FileNotFoundError(
|
|
870
|
+
"cpb: filters-before-crossref file not found: "
|
|
871
|
+
f"{value!r} (resolved to {filter_path})"
|
|
872
|
+
)
|
|
873
|
+
if str(filter_path) not in filters_before_crossref:
|
|
874
|
+
filters_before_crossref.append(str(filter_path))
|
|
875
|
+
# }}}
|
|
854
876
|
localfiles = {}
|
|
855
877
|
for k in ["csl", "bib"]:
|
|
856
878
|
if k == "bib" and bibliography_declared:
|
|
@@ -886,7 +908,12 @@ def run_pandoc(
|
|
|
886
908
|
"author-info-blocks.lua": 1,
|
|
887
909
|
}
|
|
888
910
|
localfiles["lua"] = sorted(
|
|
889
|
-
[
|
|
911
|
+
[
|
|
912
|
+
f for f in os.listdir(source_dir)
|
|
913
|
+
if f.endswith(".lua")
|
|
914
|
+
and str(Path(source_dir, f).resolve())
|
|
915
|
+
not in filters_before_crossref
|
|
916
|
+
],
|
|
890
917
|
key=lambda name: (lua_priority.get(name, 2), name),
|
|
891
918
|
)
|
|
892
919
|
# Include any javascript files next to the markdown source by injecting
|
|
@@ -908,6 +935,10 @@ def run_pandoc(
|
|
|
908
935
|
html_file,
|
|
909
936
|
filename,
|
|
910
937
|
]
|
|
938
|
+
before_crossref_args = []
|
|
939
|
+
for filter_path in filters_before_crossref:
|
|
940
|
+
before_crossref_args.extend(["--lua-filter", filter_path])
|
|
941
|
+
command[1:1] = before_crossref_args
|
|
911
942
|
if not bibliography_declared and localfiles["bib"]:
|
|
912
943
|
bibliography_paths = [localfiles["bib"]]
|
|
913
944
|
for bibliography_path in bibliography_paths:
|
|
@@ -1200,11 +1231,16 @@ def cpb(
|
|
|
1200
1231
|
daemon=True,
|
|
1201
1232
|
)
|
|
1202
1233
|
chrome = None
|
|
1203
|
-
scroll_position = 0
|
|
1204
1234
|
observer = None
|
|
1205
1235
|
observer_started = False
|
|
1206
1236
|
socket_thread_started = False
|
|
1207
1237
|
source_jump_server = None
|
|
1238
|
+
previous_sigterm = None
|
|
1239
|
+
if threading.current_thread() is threading.main_thread():
|
|
1240
|
+
def stop_cpb(_signum, _frame):
|
|
1241
|
+
raise KeyboardInterrupt
|
|
1242
|
+
|
|
1243
|
+
previous_sigterm = signal.signal(signal.SIGTERM, stop_cpb)
|
|
1208
1244
|
|
|
1209
1245
|
try:
|
|
1210
1246
|
# Bind and serve before any build or browser work. This makes the fixed
|
|
@@ -1290,29 +1326,18 @@ def cpb(
|
|
|
1290
1326
|
# and never be reported as a change.
|
|
1291
1327
|
chrome.get("file://" + os.path.abspath(html_file))
|
|
1292
1328
|
|
|
1293
|
-
def close_for_dialog():
|
|
1294
|
-
# a rebuild needs the user, so close the preview while the
|
|
1295
|
-
# issues are dealt with; it reopens once the rebuild is done
|
|
1296
|
-
nonlocal chrome, scroll_position
|
|
1297
|
-
if chrome is not None:
|
|
1298
|
-
# sessionStorage does not survive quitting the browser.
|
|
1299
|
-
scroll_position = chrome.execute_script(
|
|
1300
|
-
"return window.scrollY;"
|
|
1301
|
-
)
|
|
1302
|
-
close_browser_window(chrome)
|
|
1303
|
-
chrome = None
|
|
1304
|
-
|
|
1305
|
-
dialog_callbacks.append(close_for_dialog)
|
|
1306
1329
|
rebuild_pending = False
|
|
1307
1330
|
stable_signature = None
|
|
1308
1331
|
stable_since = None
|
|
1332
|
+
listener_warning_shown = False
|
|
1309
1333
|
while True:
|
|
1310
|
-
if not socket_thread.is_alive():
|
|
1311
|
-
|
|
1312
|
-
"
|
|
1313
|
-
"
|
|
1314
|
-
|
|
1334
|
+
if not socket_thread.is_alive() and not listener_warning_shown:
|
|
1335
|
+
print(
|
|
1336
|
+
"pydifft cpb: forward-search listener stopped; "
|
|
1337
|
+
"forward search is unavailable. The preview remains open.",
|
|
1338
|
+
file=sys.stderr,
|
|
1315
1339
|
)
|
|
1340
|
+
listener_warning_shown = True
|
|
1316
1341
|
# Browser closure is terminal. Source-file and build failures do
|
|
1317
1342
|
# not affect browser ownership or observer lifetime.
|
|
1318
1343
|
if not browser_window_is_alive(chrome):
|
|
@@ -1321,7 +1346,11 @@ def cpb(
|
|
|
1321
1346
|
for queued_search in drain_forward_search_queue(search_queue):
|
|
1322
1347
|
search_text = queued_search.strip()
|
|
1323
1348
|
if search_text:
|
|
1324
|
-
|
|
1349
|
+
try:
|
|
1350
|
+
forward_search_in_browser(chrome, search_text)
|
|
1351
|
+
except WebDriverException as exc:
|
|
1352
|
+
print(f"pydifft cpb: forward search failed: {exc}",
|
|
1353
|
+
file=sys.stderr)
|
|
1325
1354
|
|
|
1326
1355
|
saw_source_event = False
|
|
1327
1356
|
while not change_queue.empty():
|
|
@@ -1419,30 +1448,16 @@ def cpb(
|
|
|
1419
1448
|
traceback.print_exc()
|
|
1420
1449
|
else:
|
|
1421
1450
|
handled_signature = attempted_signature
|
|
1422
|
-
if
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
file=sys.stderr,
|
|
1433
|
-
)
|
|
1434
|
-
break
|
|
1435
|
-
if chrome is None:
|
|
1436
|
-
# {{{ reopen the preview a dialog closed
|
|
1437
|
-
chrome = start_browser()
|
|
1438
|
-
chrome.get(
|
|
1439
|
-
"file://" + os.path.abspath(html_file)
|
|
1440
|
-
)
|
|
1441
|
-
chrome.execute_script(
|
|
1442
|
-
"window.scrollTo(0, arguments[0]);",
|
|
1443
|
-
scroll_position,
|
|
1444
|
-
)
|
|
1445
|
-
# }}}
|
|
1451
|
+
if not browser_window_is_alive(chrome):
|
|
1452
|
+
break
|
|
1453
|
+
try:
|
|
1454
|
+
chrome.refresh()
|
|
1455
|
+
except WebDriverException as exc:
|
|
1456
|
+
print(
|
|
1457
|
+
"pydifft cpb: preview refresh failed: "
|
|
1458
|
+
f"{exc}",
|
|
1459
|
+
file=sys.stderr,
|
|
1460
|
+
)
|
|
1446
1461
|
# }}}
|
|
1447
1462
|
time.sleep(MAIN_LOOP_INTERVAL_SECONDS)
|
|
1448
1463
|
except KeyboardInterrupt:
|
|
@@ -1458,7 +1473,8 @@ def cpb(
|
|
|
1458
1473
|
if socket_thread_started:
|
|
1459
1474
|
socket_thread.join()
|
|
1460
1475
|
close_browser_window(chrome)
|
|
1461
|
-
|
|
1476
|
+
if previous_sigterm is not None:
|
|
1477
|
+
signal.signal(signal.SIGTERM, previous_sigterm)
|
|
1462
1478
|
|
|
1463
1479
|
|
|
1464
1480
|
if __name__ == "__main__":
|
|
@@ -703,7 +703,14 @@ def wrap_prose(
|
|
|
703
703
|
"""Wrap prose with sentence and punctuation rules for both commands."""
|
|
704
704
|
output = []
|
|
705
705
|
# Keep paragraph separators and the boundary newlines next to exclusions.
|
|
706
|
-
|
|
706
|
+
paragraphs = PARAGRAPH_SPLIT.split(text)
|
|
707
|
+
if filetype == "markdown":
|
|
708
|
+
# Hard line breaks are source boundaries, not wrappable whitespace.
|
|
709
|
+
paragraphs = [
|
|
710
|
+
part for paragraph in paragraphs
|
|
711
|
+
for part in re.split(r"( {2,}\r?\n)", paragraph)
|
|
712
|
+
]
|
|
713
|
+
for paragraph in paragraphs:
|
|
707
714
|
if not paragraph.strip():
|
|
708
715
|
output.append(paragraph)
|
|
709
716
|
continue
|
|
@@ -1001,7 +1008,9 @@ def check_prose(
|
|
|
1001
1008
|
if k < len(separators):
|
|
1002
1009
|
sep = separators[k]
|
|
1003
1010
|
offset += len(sep)
|
|
1004
|
-
if sep == " " and (
|
|
1011
|
+
if sep == " " and not re.match(
|
|
1012
|
+
r"[ \t]*\r?\n", stripped[offset:]
|
|
1013
|
+
) and (
|
|
1005
1014
|
k + 1 >= len(sentences) or sentences[k + 1].strip()
|
|
1006
1015
|
):
|
|
1007
1016
|
issue_line = para_line + stripped.count(
|
|
@@ -271,3 +271,85 @@ def test_start_browser_does_not_hide_programming_errors(monkeypatch):
|
|
|
271
271
|
monkeypatch.setattr(webdriver, "Firefox", firefox)
|
|
272
272
|
with pytest.raises(TypeError, match="unexpected argument"):
|
|
273
273
|
browser_lifecycle.start_browser()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_forward_search_matches_entities_and_rendered_crossrefs(
|
|
277
|
+
monkeypatch, tmp_path
|
|
278
|
+
):
|
|
279
|
+
from selenium.webdriver.chrome.service import Service
|
|
280
|
+
from pydifftools import command_line
|
|
281
|
+
|
|
282
|
+
options = webdriver.ChromeOptions()
|
|
283
|
+
options.add_argument("--headless=new")
|
|
284
|
+
browser = webdriver.Chrome(
|
|
285
|
+
service=Service("/usr/bin/chromedriver"), options=options
|
|
286
|
+
)
|
|
287
|
+
monkeypatch.setattr(browser_lifecycle.shutil, "which", lambda _name: None)
|
|
288
|
+
searches = []
|
|
289
|
+
monkeypatch.setattr(
|
|
290
|
+
command_line, "send_forward_search",
|
|
291
|
+
lambda _address, text: searches.append(text),
|
|
292
|
+
)
|
|
293
|
+
try:
|
|
294
|
+
preview = tmp_path / "preview.html"
|
|
295
|
+
preview.write_text(
|
|
296
|
+
"<p>It melts near 238 K & stays solid.</p>"
|
|
297
|
+
'<p>See eq. <a href="#eq:my_equation">(1)</a> for '
|
|
298
|
+
"<strong>the result</strong>.</p>"
|
|
299
|
+
'<p><a href="#fig:overview">Figure 2</a> shows the sample.</p>'
|
|
300
|
+
'<p>See <a href="#sec:methods">section 3</a>.</p>'
|
|
301
|
+
'<h2 id="sec:unlinked">4 Unlinked section</h2>'
|
|
302
|
+
"<p>As described in sec. 4, it works.</p>"
|
|
303
|
+
)
|
|
304
|
+
browser.get(preview.as_uri())
|
|
305
|
+
examples = [
|
|
306
|
+
("It melts near 238 K & stays solid.",
|
|
307
|
+
"It melts near 238\xa0K & stays solid."),
|
|
308
|
+
("238 K", "238\xa0K"),
|
|
309
|
+
("238 K", "238\xa0K"),
|
|
310
|
+
("See @eq:my_equation for **the result**.",
|
|
311
|
+
"See eq.\xa0(1) for the result."),
|
|
312
|
+
("@eq:my_equation for the result.",
|
|
313
|
+
"eq.\xa0(1) for the result."),
|
|
314
|
+
("See [@eq:my_equation]", "See eq.\xa0(1)"),
|
|
315
|
+
("@fig:overview shows the sample.",
|
|
316
|
+
"Figure 2 shows the sample."),
|
|
317
|
+
("See @sec:methods.", "See section 3."),
|
|
318
|
+
("As described in @sec:unlinked, it works.",
|
|
319
|
+
"As described in sec.\xa04, it works."),
|
|
320
|
+
("@sec:unlinked", "4 Unlinked section"),
|
|
321
|
+
]
|
|
322
|
+
for source, rendered in examples:
|
|
323
|
+
browser.execute_script("window.getSelection().removeAllRanges()")
|
|
324
|
+
command_line.mfs(source)
|
|
325
|
+
assert browser_lifecycle.forward_search_in_browser(
|
|
326
|
+
browser, searches[-1]
|
|
327
|
+
), source
|
|
328
|
+
assert browser.execute_script(
|
|
329
|
+
"return window.getSelection().toString()"
|
|
330
|
+
) == rendered
|
|
331
|
+
assert not browser_lifecycle.forward_search_in_browser(
|
|
332
|
+
browser, "See @eq:missing for the result."
|
|
333
|
+
)
|
|
334
|
+
finally:
|
|
335
|
+
browser.quit()
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@pytest.mark.parametrize("closed", [False, True])
|
|
339
|
+
def test_browser_probe_distinguishes_transient_error_from_closed_session(
|
|
340
|
+
closed,
|
|
341
|
+
):
|
|
342
|
+
class InterruptedBrowser:
|
|
343
|
+
@property
|
|
344
|
+
def window_handles(self):
|
|
345
|
+
if closed:
|
|
346
|
+
from selenium.common.exceptions import (
|
|
347
|
+
InvalidSessionIdException,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
raise InvalidSessionIdException("window closed")
|
|
351
|
+
raise WebDriverException("page navigation interrupted command")
|
|
352
|
+
|
|
353
|
+
assert browser_lifecycle.browser_window_is_alive(
|
|
354
|
+
InterruptedBrowser()
|
|
355
|
+
) is not closed
|