pyDiffTools 0.1.53__tar.gz → 0.1.55__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.53/pyDiffTools.egg-info → pydifftools-0.1.55}/PKG-INFO +4 -2
- {pydifftools-0.1.53 → pydifftools-0.1.55}/README.rst +3 -1
- {pydifftools-0.1.53 → pydifftools-0.1.55/pyDiffTools.egg-info}/PKG-INFO +4 -2
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/line_alignment.py +65 -11
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/match_spaces.py +7 -2
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/wrap_sentences.py +24 -5
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pyproject.toml +1 -1
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_line_alignment.py +212 -10
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_wrap_check.py +50 -16
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_wrapping.py +8 -2
- {pydifftools-0.1.53 → pydifftools-0.1.55}/LICENSE.md +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/MANIFEST.in +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/_quarto.yml +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/1a724af72b16f5a9e607e12b1c721645/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/1b28fc9daac9081847e5161b2c546f8a/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/231f64eee282fa225d1104935cf80a24/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/26e56f6b0ff54851a45145157f2f0dc4/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/311fabd7029ffd050d056e2f316eb50f/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/57da2021e5b156ac3adf01398201c723/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/62b24ea7da75011d92b0f8924faa208d/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/7f1b20d69d889514ab5d1cc92e3cb14f/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/86f74c8c54a87ff892d9b15dd714e8f0/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/88893b4234eac2945d9d6cb2e277f186/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/9a40046ada6f582ee34af00fbdbfb417/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/a1bf4d270d0641ff41faf1d7cce3439a/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/a3789f7d9585a781f2a1c60ce95ff10d/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/be46ecba858d39ad5f0c46902ddf1c02/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/d0cbc57a12f2ccce710a5afd04cc05e7/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/d3e12d320b14228f701231ae32ddd7dd/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/eb434f61555438d020a6970a5dbf9ee8/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/f6b0e73aa7fa029134665d4dde57e096/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/f719a6e4ff09873cb0ffb06ec9d232f9/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/feeee244a7ce3d60e1a227eb604df823/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/example.qmd +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/example.tex +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/index.qmd +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/subproject1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/subproject1/.jupyter_cache/executed/ca90e4df5f4f0583df6554156a68dc7f/base.ipynb +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/subproject1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/subproject1/independent.qmd +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/subproject1/index.qmd +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/subproject1/tasks.qmd +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/subproject1/test_include.qmd +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/subproject1/tryforerror.qmd +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/test_include.qmd +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pyDiffTools.egg-info/SOURCES.txt +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pyDiffTools.egg-info/dependency_links.txt +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pyDiffTools.egg-info/entry_points.txt +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pyDiffTools.egg-info/requires.txt +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pyDiffTools.egg-info/top_level.txt +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/__init__.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/bibliography.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/browser_lifecycle.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/check_numbers.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/citation_dialog.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/command_line.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/command_registry.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/comment_filter_history.json +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/comment_functions.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/comment_migration.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/comment_tags.lua +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/comment_tags_margin.lua +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/comment_tags_no_comments.lua +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/comment_toggle.js +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/comments.css +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/comments_author_colors.css +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/completions/git-mergein +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/continuous.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/copy_files.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/diff-doc.js +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/doc_contents.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/flowchart/__init__.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/flowchart/comparison.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/flowchart/dot_to_yaml.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/flowchart/graph.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/flowchart/source_jump.js +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/flowchart/svg-pan-zoom.min.js +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/flowchart/watch_graph.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/flowchart/wgrph_view.js +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/forward_search.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/git_aliases.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/git_gd.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/git_gd_history.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/git_gd_image.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/git_gd_qt.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/html_comments.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/html_uncomments.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/log_example.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/notebook/__init__.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/notebook/display.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/notebook/fast_build.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/notebook/tex_to_qmd.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/onewordify.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/onewordify_undo.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/outline.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/pdf_diff.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/rearrange_tex.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/searchacro.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/separate_comments.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/source_jump.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/split_conflict.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/unseparate_comments.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/update_check.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/wrapping_options.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/xml2xlsx.vbs +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/pydifftools/zotero.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/setup.cfg +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_bibliography.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_browser_lifecycle.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_check_single_use.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_command_line_help.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_comment_filters.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_comment_migration.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_continuous_shutdown.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_forward_search.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_git_gd_history.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_git_gd_image.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_git_gd_qt.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_git_mergein.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_git_pdf_diff.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_pdf_diff.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_rrng.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_source_jump.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_tex_to_qmd.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/tests/test_update_check.py +0 -0
- {pydifftools-0.1.53 → pydifftools-0.1.55}/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.55
|
|
4
4
|
Summary: Diff tools
|
|
5
5
|
Author: J M Franck
|
|
6
6
|
License: Copyright (c) 2015, jmfranck
|
|
@@ -306,7 +306,9 @@ included are (listed in order of fun/utility):
|
|
|
306
306
|
* ``cpb`` and ``wmatch`` share a matcher that finds changed hunks, aligns
|
|
307
307
|
their words using Git's minimal diff, and restores reference whitespace.
|
|
308
308
|
It then considers extra breaks that preserve an unchanged reference line
|
|
309
|
-
without stranding
|
|
309
|
+
without stranding an edited fragment of three words or fewer, and checks
|
|
310
|
+
wrapping rules. When matching a baseline, edited lines may exceed the
|
|
311
|
+
character width by at most four words before wrapping is required.
|
|
310
312
|
|
|
311
313
|
* ``--wrapnumber`` sets the maximum width (79 for ``cpb`` and ``wmatch``).
|
|
312
314
|
Wmatch also uses nearby reference widths for large edits, retaining
|
|
@@ -251,7 +251,9 @@ included are (listed in order of fun/utility):
|
|
|
251
251
|
* ``cpb`` and ``wmatch`` share a matcher that finds changed hunks, aligns
|
|
252
252
|
their words using Git's minimal diff, and restores reference whitespace.
|
|
253
253
|
It then considers extra breaks that preserve an unchanged reference line
|
|
254
|
-
without stranding
|
|
254
|
+
without stranding an edited fragment of three words or fewer, and checks
|
|
255
|
+
wrapping rules. When matching a baseline, edited lines may exceed the
|
|
256
|
+
character width by at most four words before wrapping is required.
|
|
255
257
|
|
|
256
258
|
* ``--wrapnumber`` sets the maximum width (79 for ``cpb`` and ``wmatch``).
|
|
257
259
|
Wmatch also uses nearby reference widths for large edits, retaining
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyDiffTools
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.55
|
|
4
4
|
Summary: Diff tools
|
|
5
5
|
Author: J M Franck
|
|
6
6
|
License: Copyright (c) 2015, jmfranck
|
|
@@ -306,7 +306,9 @@ included are (listed in order of fun/utility):
|
|
|
306
306
|
* ``cpb`` and ``wmatch`` share a matcher that finds changed hunks, aligns
|
|
307
307
|
their words using Git's minimal diff, and restores reference whitespace.
|
|
308
308
|
It then considers extra breaks that preserve an unchanged reference line
|
|
309
|
-
without stranding
|
|
309
|
+
without stranding an edited fragment of three words or fewer, and checks
|
|
310
|
+
wrapping rules. When matching a baseline, edited lines may exceed the
|
|
311
|
+
character width by at most four words before wrapping is required.
|
|
310
312
|
|
|
311
313
|
* ``--wrapnumber`` sets the maximum width (79 for ``cpb`` and ``wmatch``).
|
|
312
314
|
Wmatch also uses nearby reference widths for large edits, retaining
|
|
@@ -88,7 +88,7 @@ def align_line_breaks(
|
|
|
88
88
|
):
|
|
89
89
|
"""Return changed hunks with original, linted and proposed layouts.
|
|
90
90
|
|
|
91
|
-
Align words with minimal diff, reuse
|
|
91
|
+
Align words with minimal diff, reuse equivalent source whitespace,
|
|
92
92
|
then preserve complete reference lines and enforce line linting. CPB
|
|
93
93
|
and wmatch apply the aligned layout automatically.
|
|
94
94
|
Width can be a number or a function choosing a limit after restoration.
|
|
@@ -148,6 +148,9 @@ def align_line_breaks(
|
|
|
148
148
|
margins[row] = allowed[row] and (
|
|
149
149
|
inside or (
|
|
150
150
|
margins[row] and "\t" not in indent and len(indent) < 4
|
|
151
|
+
and not (
|
|
152
|
+
row and not rows[row - 1].strip() and len(indent) >= 2
|
|
153
|
+
)
|
|
151
154
|
)
|
|
152
155
|
)
|
|
153
156
|
position += len(line)
|
|
@@ -174,7 +177,7 @@ def align_line_breaks(
|
|
|
174
177
|
if p["marker"] else "")
|
|
175
178
|
for p in prefixes
|
|
176
179
|
]
|
|
177
|
-
|
|
180
|
+
span_word = re.compile(
|
|
178
181
|
r"\$\$|\$|\\[A-Za-z]+|\\[^\s]|[A-Za-z]+|[0-9]+(?:\.[0-9]+)?|[^\s]"
|
|
179
182
|
)
|
|
180
183
|
tokens = []
|
|
@@ -186,7 +189,11 @@ def align_line_breaks(
|
|
|
186
189
|
end]
|
|
187
190
|
for left, right in zip(boundaries, boundaries[1:]):
|
|
188
191
|
in_math = region_at(equations, base + left) is not None
|
|
189
|
-
|
|
192
|
+
hidden = region_at(comments, base + left) is not None
|
|
193
|
+
# Hidden source has no rendered word boundaries. Separate
|
|
194
|
+
# punctuation there too, so layout changes do not turn
|
|
195
|
+
# "$$R_c" into unmatched "$$" and "R_c" tokens.
|
|
196
|
+
pattern = span_word if in_math or hidden else WORD
|
|
190
197
|
tokens.extend(
|
|
191
198
|
(row, match.start(), match.end())
|
|
192
199
|
for match in pattern.finditer(text, left, right)
|
|
@@ -249,6 +256,7 @@ def align_line_breaks(
|
|
|
249
256
|
if allowed:
|
|
250
257
|
for line, message in check_prose(
|
|
251
258
|
span, width, punctuation_slop, offset,
|
|
259
|
+
overflow_words=4,
|
|
252
260
|
):
|
|
253
261
|
issues.setdefault(line, message)
|
|
254
262
|
offset += span.count("\n")
|
|
@@ -348,6 +356,39 @@ def align_line_breaks(
|
|
|
348
356
|
whitespace = dict(gaps)
|
|
349
357
|
ending = "\r\n" if "\r\n" in before else "\n"
|
|
350
358
|
|
|
359
|
+
def equivalent_gap(number, replacement):
|
|
360
|
+
# {{{ keep rendered separators and explicit Markdown hard breaks
|
|
361
|
+
raw = gaps[number]
|
|
362
|
+
left = start + tokens[number][1]
|
|
363
|
+
right = start + tokens[number + 1][1]
|
|
364
|
+
region = region_at(current_comments, left)
|
|
365
|
+
if region is not None and right < region[1]:
|
|
366
|
+
return True
|
|
367
|
+
region = region_at(current_equations, left)
|
|
368
|
+
if region is not None and right < region[1]:
|
|
369
|
+
# Display delimiters can sit on their own source lines.
|
|
370
|
+
# All other math gaps obey the same rule as prose; no
|
|
371
|
+
# knowledge of TeX commands or text macros is needed.
|
|
372
|
+
if current[region[0]:region[0] + 2] == "$$" and (
|
|
373
|
+
tokens[number][1] + start == region[0]
|
|
374
|
+
or tokens[number + 1][2] + start == region[1]
|
|
375
|
+
):
|
|
376
|
+
return True
|
|
377
|
+
return bool(raw) == bool(replacement)
|
|
378
|
+
# Collapsing a whitespace run retains one separator. It cannot
|
|
379
|
+
# create a separator from an empty gap or remove the last one.
|
|
380
|
+
# Line margins are handled separately, with structural margins
|
|
381
|
+
# excluded. Two spaces before a newline are a Markdown hard
|
|
382
|
+
# break, and a newline after a backslash is explicit as well.
|
|
383
|
+
if any(re.search(r" {2,}\r?\n", gap)
|
|
384
|
+
for gap in (raw, replacement)) or (
|
|
385
|
+
before[tokens[number][2] - 1:tokens[number][2]] == "\\"
|
|
386
|
+
and ("\n" in raw or "\n" in replacement)
|
|
387
|
+
):
|
|
388
|
+
return raw == replacement
|
|
389
|
+
return bool(raw) == bool(replacement)
|
|
390
|
+
# }}}
|
|
391
|
+
|
|
351
392
|
# {{{ restore presentation indentation at matching line starts
|
|
352
393
|
old_first = {}
|
|
353
394
|
for number, (row, _, _) in enumerate(old_tokens):
|
|
@@ -430,19 +471,27 @@ def align_line_breaks(
|
|
|
430
471
|
old_number = candidate
|
|
431
472
|
if old_number in old_gaps:
|
|
432
473
|
raw = old_gaps[old_number]
|
|
474
|
+
if not equivalent_gap(number, raw):
|
|
475
|
+
continue
|
|
433
476
|
region = region_at(regions, start + tokens[number][1])
|
|
434
477
|
inside_region = (
|
|
435
478
|
region is not None
|
|
436
479
|
and start + tokens[number + 1][1] < region[1]
|
|
437
480
|
)
|
|
438
|
-
if (
|
|
439
|
-
|
|
440
|
-
and "\n" not in gaps[number] and not inside_region
|
|
441
|
-
and len("".join(
|
|
442
|
-
before[:tokens[number][2]].split("\n")[-1].split()
|
|
443
|
-
)) < 10
|
|
481
|
+
if "\n" in raw and "\n" not in gaps[number] and (
|
|
482
|
+
not inside_region
|
|
444
483
|
):
|
|
445
|
-
|
|
484
|
+
original_rows = set(render(whitespace).splitlines())
|
|
485
|
+
proposed = dict(whitespace)
|
|
486
|
+
proposed[number] = raw
|
|
487
|
+
inherited = {line.rstrip() for line in reference_lines}
|
|
488
|
+
if any(
|
|
489
|
+
line not in original_rows
|
|
490
|
+
and line.rstrip() not in inherited
|
|
491
|
+
and 0 < len(line.split()) <= 3
|
|
492
|
+
for line in render(proposed).splitlines()
|
|
493
|
+
):
|
|
494
|
+
continue
|
|
446
495
|
if inside_region or (
|
|
447
496
|
current_layout[3][j1 + tokens[number][0]]
|
|
448
497
|
and current_layout[3][j1 + tokens[number + 1][0]]
|
|
@@ -494,12 +543,17 @@ def align_line_breaks(
|
|
|
494
543
|
elif "\n" in proposed[number]:
|
|
495
544
|
proposed[number] = " "
|
|
496
545
|
proposed_rows = render(proposed).splitlines()
|
|
546
|
+
if any(
|
|
547
|
+
not equivalent_gap(number, proposed[number])
|
|
548
|
+
for number in needed
|
|
549
|
+
):
|
|
550
|
+
continue
|
|
497
551
|
if old_line not in {line.rstrip() for line in proposed_rows}:
|
|
498
552
|
continue
|
|
499
553
|
if any(
|
|
500
554
|
line not in original_rows
|
|
501
555
|
and line.rstrip() not in inherited
|
|
502
|
-
and 0 < len(
|
|
556
|
+
and 0 < len(line.split()) <= 3
|
|
503
557
|
for line in proposed_rows
|
|
504
558
|
):
|
|
505
559
|
continue
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from difflib import SequenceMatcher
|
|
2
2
|
from bisect import bisect_left
|
|
3
3
|
import math
|
|
4
|
+
import re
|
|
4
5
|
from .line_alignment import align_line_breaks
|
|
5
6
|
from .wrapping_options import (
|
|
6
7
|
DEFAULT_WIDTH,
|
|
@@ -92,7 +93,6 @@ def match_text(
|
|
|
92
93
|
trailing_dependent_phrase=DEFAULT_TRAILING_DEPENDENT_PHRASE,
|
|
93
94
|
):
|
|
94
95
|
"""Align reference lines and wrap only overflowing edited prose."""
|
|
95
|
-
text2 = text2.replace("\u00a0", " ").replace("\u2004", " ")
|
|
96
96
|
# {{{ choose the nearby reference width before any line linting
|
|
97
97
|
reference_lines = classify_lines(text1, filetype)
|
|
98
98
|
prose_rows = [
|
|
@@ -118,7 +118,12 @@ def match_text(
|
|
|
118
118
|
return (
|
|
119
119
|
local
|
|
120
120
|
if any(
|
|
121
|
-
len(line) >
|
|
121
|
+
len(line) > local and (
|
|
122
|
+
joined or sum(
|
|
123
|
+
match.end() > local
|
|
124
|
+
for match in re.finditer(r"\S+", line)
|
|
125
|
+
) > 4
|
|
126
|
+
)
|
|
122
127
|
for line in restored.splitlines()
|
|
123
128
|
)
|
|
124
129
|
else fallback_width
|
|
@@ -880,9 +880,14 @@ def wr(
|
|
|
880
880
|
fp.write(result)
|
|
881
881
|
|
|
882
882
|
|
|
883
|
-
def check_prose(
|
|
883
|
+
def check_prose(
|
|
884
|
+
content, wrapnumber, punctuation_slop, line_start=1, overflow_words=0,
|
|
885
|
+
):
|
|
884
886
|
"""Report source lines in `content` that break wr's rules.
|
|
885
887
|
|
|
888
|
+
Diff alignment can set overflow_words to tolerate that many words ending
|
|
889
|
+
beyond the character width. Ordinary wrapping checks use a strict limit.
|
|
890
|
+
|
|
886
891
|
Checks mirror wrap_prose's own paragraph/sentence/macro
|
|
887
892
|
splitting so the two commands never disagree about what counts as a
|
|
888
893
|
sentence: lines must not hold more words than wr's own greedy choice
|
|
@@ -953,7 +958,18 @@ def check_prose(content, wrapnumber, punctuation_slop, line_start=1):
|
|
|
953
958
|
+ len(words[actual_end])
|
|
954
959
|
- positions[fragment_offset]
|
|
955
960
|
)
|
|
956
|
-
|
|
961
|
+
# Diff alignment can keep a few extra words to
|
|
962
|
+
# avoid moving the sentence tail onto a new line.
|
|
963
|
+
# Count against the character limit, independently
|
|
964
|
+
# of an earlier preferred punctuation break.
|
|
965
|
+
excess = sum(
|
|
966
|
+
positions[j] + len(words[j])
|
|
967
|
+
- positions[fragment_offset] > wrapnumber
|
|
968
|
+
for j in range(fragment_offset, actual_end + 1)
|
|
969
|
+
)
|
|
970
|
+
if actual_end > wr_end and not (
|
|
971
|
+
overflow_words and 0 < excess <= overflow_words
|
|
972
|
+
):
|
|
957
973
|
overflow = words[wr_end + 1]
|
|
958
974
|
rule = (
|
|
959
975
|
"line too long"
|
|
@@ -1134,7 +1150,7 @@ def _mask_html_comments(content, initially_open=False):
|
|
|
1134
1150
|
|
|
1135
1151
|
|
|
1136
1152
|
def markdown_lint_issues_from_text(
|
|
1137
|
-
content, wrapnumber=45, punctuation_slop=20
|
|
1153
|
+
content, wrapnumber=45, punctuation_slop=20, overflow_words=0,
|
|
1138
1154
|
):
|
|
1139
1155
|
"""Return Markdown writing issues, including unfinished source spans."""
|
|
1140
1156
|
masked_content = _mask_html_comments(content)
|
|
@@ -1149,7 +1165,9 @@ def markdown_lint_issues_from_text(
|
|
|
1149
1165
|
for line, width, span in _iter_block_spans(
|
|
1150
1166
|
markdown_blocks(prose_content), wrapnumber
|
|
1151
1167
|
)
|
|
1152
|
-
for issue in check_prose(
|
|
1168
|
+
for issue in check_prose(
|
|
1169
|
+
span, width, punctuation_slop, line, overflow_words,
|
|
1170
|
+
)
|
|
1153
1171
|
]
|
|
1154
1172
|
issues.extend(
|
|
1155
1173
|
(issue["line"], issue["message"])
|
|
@@ -1523,7 +1541,8 @@ def autofix_markdown_file(
|
|
|
1523
1541
|
# issue at a time re-lints the whole file for every single fix.
|
|
1524
1542
|
for _ in range(100):
|
|
1525
1543
|
issues = markdown_lint_issues_from_text(
|
|
1526
|
-
content, wrapnumber=wrapnumber, punctuation_slop=punctuation_slop
|
|
1544
|
+
content, wrapnumber=wrapnumber, punctuation_slop=punctuation_slop,
|
|
1545
|
+
overflow_words=4 if baseline is not None else 0,
|
|
1527
1546
|
)
|
|
1528
1547
|
fixable = {}
|
|
1529
1548
|
for line_number, message in issues:
|
|
@@ -47,6 +47,129 @@ def test_partial_matching_lines_restore_whitespace_first(tmp_path):
|
|
|
47
47
|
assert new.read_text() == expected
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
@pytest.mark.parametrize("reference,current", [
|
|
51
|
+
(
|
|
52
|
+
r"the TEMPO-SO~4~ probe ($32.9;\text{kJ}/\text{mol}$"
|
|
53
|
+
"(@fig:DodecaTSO4TempCycle, blue),\nwhich\n",
|
|
54
|
+
r"the TEMPO-SO~4~ probe ($32.9;\text{kJ}/\text{mol}$"
|
|
55
|
+
" (@fig:DodecaTSO4TempCycle, blue),\nwhich\n",
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
r"the TEMPO-SO~4~ probe inside small RMs dispersed in"
|
|
59
|
+
"\ndodecane ($32.9;\\text{kJ}/\\text{mol}$ : "
|
|
60
|
+
"@fig:DodecaTSO4TempCycle, blue),\nwhich\n",
|
|
61
|
+
r"the TEMPO-SO~4~ probe inside small RMs dispersed in"
|
|
62
|
+
"\ndodecane ($32.9;\\text{kJ}/\\text{mol}$: "
|
|
63
|
+
"@fig:DodecaTSO4TempCycle, blue),\nwhich\n",
|
|
64
|
+
),
|
|
65
|
+
("First $x$ (reference).\n", "First $x$(reference).\n"),
|
|
66
|
+
("First $x$: reference.\n", "First $x$ : reference.\n"),
|
|
67
|
+
("First words continue here.\n", "First words \ncontinue here.\n"),
|
|
68
|
+
("A long unchanged introductory line stays here.\n"
|
|
69
|
+
"First words\\\ncontinue here.\n",
|
|
70
|
+
"A long unchanged introductory line stays here.\n"
|
|
71
|
+
"First words\\ continue here.\n"),
|
|
72
|
+
("Keep ordinary spaces.\n", "Keep\u00a0ordinary\u2004spaces.\n"),
|
|
73
|
+
(r"Use $\text{a,b}$ here." + "\n",
|
|
74
|
+
r"Use $\text{a, b}$ here." + "\n"),
|
|
75
|
+
(r"Use $\text{a, b}$ here." + "\n",
|
|
76
|
+
r"Use $\text{a,b}$ here." + "\n"),
|
|
77
|
+
(r"Use $\text{a,{b,c}}$ here." + "\n",
|
|
78
|
+
r"Use $\text{a, {b, c}}$ here." + "\n"),
|
|
79
|
+
(r"Use $\text{a \text{b} c,d}$ here." + "\n",
|
|
80
|
+
r"Use $\text{a \text{b} c, d}$ here." + "\n"),
|
|
81
|
+
(r"Use $\alpha{}b$ here." + "\n",
|
|
82
|
+
r"Use $\alpha b$ here." + "\n"),
|
|
83
|
+
(r"Use $\custom{a,b}$ here." + "\n",
|
|
84
|
+
r"Use $\custom{a, b}$ here." + "\n"),
|
|
85
|
+
("Use $<!-- hidden words -->$ here.\n",
|
|
86
|
+
"Use $<!--hidden words-->$ here.\n"),
|
|
87
|
+
])
|
|
88
|
+
def test_rendered_spacing_survives_shared_alignment_and_wmatch(
|
|
89
|
+
tmp_path, reference, current,
|
|
90
|
+
):
|
|
91
|
+
for result in align_line_breaks(reference, current, width=1000):
|
|
92
|
+
assert result["aligned"] == result["before"]
|
|
93
|
+
old, new = tmp_path / "old.md", tmp_path / "new.md"
|
|
94
|
+
old.write_text(reference)
|
|
95
|
+
new.write_text(current)
|
|
96
|
+
run([str(old), str(new)], wrapnumber=1000)
|
|
97
|
+
assert new.read_text() == current
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_removed_hard_break_is_not_restored_by_alignment(tmp_path):
|
|
101
|
+
reference = "First words \ncontinue here.\n"
|
|
102
|
+
current = "First words continue here.\n"
|
|
103
|
+
(result,) = align_line_breaks(reference, current)
|
|
104
|
+
assert " \n" not in result["aligned"]
|
|
105
|
+
old, new = tmp_path / "old.md", tmp_path / "new.md"
|
|
106
|
+
old.write_text(reference)
|
|
107
|
+
new.write_text(current)
|
|
108
|
+
run([str(old), str(new)])
|
|
109
|
+
assert " \n" not in new.read_text()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@pytest.mark.parametrize("context", ["{}", "Use ${}$ here."])
|
|
113
|
+
@pytest.mark.parametrize("before,after", [
|
|
114
|
+
("alpha,beta", "alpha, beta"),
|
|
115
|
+
("alpha, beta", "alpha,beta"),
|
|
116
|
+
("alpha(beta)", "alpha (beta)"),
|
|
117
|
+
("alpha (beta)", "alpha(beta)"),
|
|
118
|
+
("alpha:beta", "alpha :beta"),
|
|
119
|
+
("alpha :beta", "alpha:beta"),
|
|
120
|
+
])
|
|
121
|
+
def test_alignment_preserves_last_space_everywhere(
|
|
122
|
+
tmp_path, context, before, after,
|
|
123
|
+
):
|
|
124
|
+
reference = context.format(before) + "\n"
|
|
125
|
+
current = context.format(after) + "\n"
|
|
126
|
+
for result in align_line_breaks(reference, current):
|
|
127
|
+
assert result["aligned"] == result["before"]
|
|
128
|
+
old, new = tmp_path / "old.md", tmp_path / "new.md"
|
|
129
|
+
old.write_text(reference)
|
|
130
|
+
new.write_text(current)
|
|
131
|
+
run([str(old), str(new)])
|
|
132
|
+
assert new.read_text() == current
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_extra_spaces_and_presentation_margins_can_be_cleaned(tmp_path):
|
|
136
|
+
reference = "Keep one separator here.\n"
|
|
137
|
+
current = " Keep one \t separator here.\n"
|
|
138
|
+
(result,) = align_line_breaks(reference, current, width=79)
|
|
139
|
+
assert result["aligned"] == reference
|
|
140
|
+
old, new = tmp_path / "old.md", tmp_path / "new.md"
|
|
141
|
+
old.write_text(reference)
|
|
142
|
+
new.write_text(current)
|
|
143
|
+
run([str(old), str(new)])
|
|
144
|
+
assert new.read_text() == reference
|
|
145
|
+
new.write_text(reference.rstrip("\n") + " \n")
|
|
146
|
+
autofix_markdown_file(new, wrapnumber=79)
|
|
147
|
+
assert new.read_text() == reference
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_cpb_preserves_rendered_spacing_while_restoring_source_breaks(
|
|
151
|
+
tmp_path,
|
|
152
|
+
):
|
|
153
|
+
import subprocess
|
|
154
|
+
|
|
155
|
+
reference = (
|
|
156
|
+
"A probe inside small RMs\ndispersed in dodecane "
|
|
157
|
+
"($32.9;\\text{kJ}/\\text{mol}$ : @fig:cycle, blue),\nwhich\n"
|
|
158
|
+
)
|
|
159
|
+
current = reference.replace("RMs\ndispersed", "RMs dispersed").replace(
|
|
160
|
+
"$ :", "$:"
|
|
161
|
+
)
|
|
162
|
+
path = tmp_path / "source.md"
|
|
163
|
+
subprocess.run(["git", "init", str(tmp_path)], check=True,
|
|
164
|
+
capture_output=True)
|
|
165
|
+
path.write_text(reference)
|
|
166
|
+
subprocess.run(["git", "-C", str(tmp_path), "add", "source.md"],
|
|
167
|
+
check=True, capture_output=True)
|
|
168
|
+
path.write_text(current)
|
|
169
|
+
autofix_markdown_file(path, wrapnumber=1000, git_index=True)
|
|
170
|
+
assert path.read_text() == reference.replace("$ :", "$:")
|
|
171
|
+
|
|
172
|
+
|
|
50
173
|
@pytest.mark.parametrize("old_prefix,new_prefix", [
|
|
51
174
|
("", " "), (" ", ""), (" ", " "),
|
|
52
175
|
])
|
|
@@ -80,6 +203,10 @@ def test_leading_indentation_edits_do_not_overlap_whitespace_restoration():
|
|
|
80
203
|
("> Quote.\n", " > Quote.\n"),
|
|
81
204
|
("> First.\n continuation.\n", "> First.\n continuation.\n"),
|
|
82
205
|
("```\n Code.\n```\n", "```\n Code.\n```\n"),
|
|
206
|
+
("Introduction.\n\n Indented paragraph.\n",
|
|
207
|
+
"Introduction.\n\n Indented paragraph.\n"),
|
|
208
|
+
("Introduction.\n\n Indented paragraph.\n",
|
|
209
|
+
"Introduction.\n\n Indented paragraph.\n"),
|
|
83
210
|
])
|
|
84
211
|
def test_structural_indentation_is_preserved(reference, current):
|
|
85
212
|
assert all(
|
|
@@ -177,6 +304,63 @@ def test_comment_alignment_preserves_reference_whitespace():
|
|
|
177
304
|
assert restored == reference
|
|
178
305
|
|
|
179
306
|
|
|
307
|
+
@pytest.mark.parametrize("ending", ["\n", "\r\n"])
|
|
308
|
+
@pytest.mark.parametrize("body", [
|
|
309
|
+
r"$$R_c \approx (100\,\text{nm·K})/\Delta T$$",
|
|
310
|
+
r"$$R_c \approx 100/28 \approx 3.6\,\text{nm}$$",
|
|
311
|
+
"Attached(punctuation),without spaces.",
|
|
312
|
+
])
|
|
313
|
+
def test_hidden_source_layout_matches_across_attached_punctuation(
|
|
314
|
+
tmp_path, body, ending,
|
|
315
|
+
):
|
|
316
|
+
import subprocess
|
|
317
|
+
|
|
318
|
+
reference = (
|
|
319
|
+
"Visible prose.\n<!--\nRS: Calculation:\n" + body + "\n-->\n"
|
|
320
|
+
"Visible $x$ (reference): preserve this space.\n"
|
|
321
|
+
).replace("\n", ending)
|
|
322
|
+
changed_body = body.replace("$$", "$$\n", 1)
|
|
323
|
+
if body.endswith("$$"):
|
|
324
|
+
changed_body = changed_body[:-2] + "\n$$"
|
|
325
|
+
else:
|
|
326
|
+
changed_body = body.replace("(", " ( ").replace(
|
|
327
|
+
"),", " ) ,\n"
|
|
328
|
+
)
|
|
329
|
+
current = reference.replace(body, changed_body.replace("\n", ending))
|
|
330
|
+
restored = current
|
|
331
|
+
for result in reversed(align_line_breaks(reference, current, width=79)):
|
|
332
|
+
restored = (
|
|
333
|
+
restored[:result["start"]] + result["aligned"]
|
|
334
|
+
+ restored[result["stop"]:]
|
|
335
|
+
)
|
|
336
|
+
assert restored == reference
|
|
337
|
+
old, new = tmp_path / "old.md", tmp_path / "new.md"
|
|
338
|
+
old.write_bytes(reference.encode())
|
|
339
|
+
new.write_bytes(current.encode())
|
|
340
|
+
run([str(old), str(new)])
|
|
341
|
+
# wmatch reads text with universal newline handling.
|
|
342
|
+
assert new.read_text() == reference.replace(ending, "\n")
|
|
343
|
+
subprocess.run(["git", "init", str(tmp_path)], check=True,
|
|
344
|
+
capture_output=True)
|
|
345
|
+
new.write_bytes(reference.encode())
|
|
346
|
+
subprocess.run(["git", "-C", str(tmp_path), "add", "new.md"],
|
|
347
|
+
check=True, capture_output=True)
|
|
348
|
+
subprocess.run(["git", "-C", str(tmp_path), "config", "user.name",
|
|
349
|
+
"Test"], check=True)
|
|
350
|
+
subprocess.run(["git", "-C", str(tmp_path), "config", "user.email",
|
|
351
|
+
"test@example.com"], check=True)
|
|
352
|
+
subprocess.run(["git", "-C", str(tmp_path), "commit", "-m", "Base"],
|
|
353
|
+
check=True, capture_output=True)
|
|
354
|
+
subprocess.run(["git", "-C", str(tmp_path), "tag", "jf_last"],
|
|
355
|
+
check=True)
|
|
356
|
+
new.write_bytes(current.encode())
|
|
357
|
+
autofix_markdown_file(new, wrapnumber=79, git_ref="jf_last")
|
|
358
|
+
assert new.read_bytes() == reference.encode()
|
|
359
|
+
assert not autofix_markdown_file(
|
|
360
|
+
new, wrapnumber=79, git_ref="jf_last",
|
|
361
|
+
)["fixes"]
|
|
362
|
+
|
|
363
|
+
|
|
180
364
|
def test_literal_comment_delimiters_remain_opaque():
|
|
181
365
|
delimiter = "`"
|
|
182
366
|
reference = f"Use {delimiter}<!-- hidden words -->{delimiter} here.\n"
|
|
@@ -203,8 +387,6 @@ def test_literal_comment_delimiters_remain_opaque():
|
|
|
203
387
|
"$$\n" + r"r(w_0) = \begin{cases}" + "\n"
|
|
204
388
|
+ r"m w_0 + \delta & w_0 < 20 \\[8pt]" + "\n"
|
|
205
389
|
+ r"c & w_0 \geq 20\end{cases}" + "\n$$\n"),
|
|
206
|
-
("Use $<!-- hidden words -->$ here.\n",
|
|
207
|
-
"Use $<!--hidden words-->$ here.\n"),
|
|
208
390
|
])
|
|
209
391
|
def test_math_layout_restores_baseline_without_prose_lint(reference, current):
|
|
210
392
|
for result in reversed(align_line_breaks(reference, current, width=79)):
|
|
@@ -307,13 +489,17 @@ def test_batched_word_diff_keeps_hunks_isolated(monkeypatch):
|
|
|
307
489
|
"Other introduction\nsame reference words\nother closing.\n"
|
|
308
490
|
)
|
|
309
491
|
current = (
|
|
310
|
-
"Changed introduction same reference words
|
|
492
|
+
"Changed words in introduction same reference words "
|
|
493
|
+
"changed words in closing.\n"
|
|
311
494
|
"\nUnchanged anchor.\n\n"
|
|
312
|
-
"Updated introduction same reference words
|
|
495
|
+
"Updated words in introduction same reference words "
|
|
496
|
+
"updated words in closing.\n"
|
|
313
497
|
)
|
|
314
498
|
expected = [
|
|
315
|
-
"Changed introduction\nsame reference words\
|
|
316
|
-
"
|
|
499
|
+
"Changed words in introduction\nsame reference words\n"
|
|
500
|
+
"changed words in closing.\n",
|
|
501
|
+
"Updated words in introduction\nsame reference words\n"
|
|
502
|
+
"updated words in closing.\n",
|
|
317
503
|
]
|
|
318
504
|
calls = []
|
|
319
505
|
original_run = line_alignment.subprocess.run
|
|
@@ -334,10 +520,26 @@ def test_optional_preservation_break_does_not_strand_a_tiny_fragment():
|
|
|
334
520
|
)
|
|
335
521
|
current = "X stable reference words rewritten closing.\n"
|
|
336
522
|
(result,) = align_line_breaks(reference, current)
|
|
337
|
-
#
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
523
|
+
# Restoring either boundary would strand a short changed fragment.
|
|
524
|
+
assert result["aligned"] == current
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
@pytest.mark.parametrize("count", [1, 2, 3, 4])
|
|
528
|
+
@pytest.mark.parametrize("side", ["before", "after"])
|
|
529
|
+
def test_optional_break_uses_word_count_for_long_fragments(count, side):
|
|
530
|
+
reference = "Former opening\nstable reference words\nformer closing.\n"
|
|
531
|
+
fragment = " ".join(["substantial"] * count)
|
|
532
|
+
current = (
|
|
533
|
+
fragment + " stable reference words with a rewritten closing.\n"
|
|
534
|
+
if side == "before" else
|
|
535
|
+
"Here is a rewritten opening stable reference words " + fragment
|
|
536
|
+
+ ".\n"
|
|
537
|
+
)
|
|
538
|
+
(result,) = align_line_breaks(reference, current)
|
|
539
|
+
# Four words justify an optional break to preserve a complete old line.
|
|
540
|
+
# One to three long words must stay with the adjacent reference words.
|
|
541
|
+
assert ("\nstable reference words\n" in result["aligned"]) == (
|
|
542
|
+
count > 3
|
|
341
543
|
)
|
|
342
544
|
|
|
343
545
|
|
|
@@ -754,6 +754,42 @@ def test_stray_line_break_in_an_unchanged_line_is_rejoined(tmp_path):
|
|
|
754
754
|
assert "put those breaks back" in report["fixes"][0]["reason"]
|
|
755
755
|
|
|
756
756
|
|
|
757
|
+
@pytest.mark.parametrize("count", [3, 4, 5])
|
|
758
|
+
@pytest.mark.parametrize("word", ["a", "substantial"])
|
|
759
|
+
@pytest.mark.parametrize("command", ["cpb", "wmatch"])
|
|
760
|
+
def test_diff_wrap_allows_only_four_words_beyond_width(
|
|
761
|
+
tmp_path, count, word, command,
|
|
762
|
+
):
|
|
763
|
+
from pydifftools.match_spaces import run
|
|
764
|
+
|
|
765
|
+
first = "Stable reference words fill the first line"
|
|
766
|
+
width = len(first)
|
|
767
|
+
reference = first + "\nand the reference sentence finishes here.\n"
|
|
768
|
+
path = committed_source(tmp_path, reference)
|
|
769
|
+
current = reference.replace(
|
|
770
|
+
first, first.replace("Stable", "Edited") + " " + " ".join(
|
|
771
|
+
[word] * count
|
|
772
|
+
),
|
|
773
|
+
)
|
|
774
|
+
path.write_text(current)
|
|
775
|
+
old = tmp_path / "reference.md"
|
|
776
|
+
old.write_text(reference)
|
|
777
|
+
for _ in range(2):
|
|
778
|
+
if command == "cpb":
|
|
779
|
+
autofix_markdown_file(
|
|
780
|
+
path, wrapnumber=width, punctuation_slop=0, git_head=True,
|
|
781
|
+
)
|
|
782
|
+
else:
|
|
783
|
+
run([str(old), str(path)], width, 0)
|
|
784
|
+
result = path.read_text()
|
|
785
|
+
assert result.split() == current.split()
|
|
786
|
+
if count <= 4:
|
|
787
|
+
assert result == current
|
|
788
|
+
else:
|
|
789
|
+
assert result != current
|
|
790
|
+
assert result.splitlines()[0] == first.replace("Stable", "Edited")
|
|
791
|
+
|
|
792
|
+
|
|
757
793
|
def test_one_moved_line_break_is_moved_back(tmp_path):
|
|
758
794
|
path = committed_source(tmp_path, HEAD_PARAGRAPH)
|
|
759
795
|
path.write_text(
|
|
@@ -843,7 +879,7 @@ def test_rm_esr_restores_full_head_lines_in_rewritten_sentence(
|
|
|
843
879
|
|
|
844
880
|
expected = edited.replace(
|
|
845
881
|
"the ESR measurements presented",
|
|
846
|
-
"the
|
|
882
|
+
"the ESR measurements\npresented",
|
|
847
883
|
).replace("tube (1.50", "tube\n(1.50")
|
|
848
884
|
assert path.read_text() == expected
|
|
849
885
|
assert report["fixes"] and report["layout"] == []
|
|
@@ -855,25 +891,21 @@ def test_rm_esr_restores_full_head_lines_in_rewritten_sentence(
|
|
|
855
891
|
matched.write_text(edited)
|
|
856
892
|
run([str(reference), str(matched)])
|
|
857
893
|
assert matched.read_text() == expected
|
|
858
|
-
#
|
|
859
|
-
assert numstat(path) == (8,
|
|
894
|
+
# Avoid a new two-word row just to preserve the ESR reference line.
|
|
895
|
+
assert numstat(path) == (8, 9)
|
|
860
896
|
|
|
861
897
|
|
|
862
|
-
def
|
|
898
|
+
def test_embedded_head_line_does_not_strand_short_changed_lines(tmp_path):
|
|
863
899
|
path = committed_source(tmp_path, "Before\nESR measurements\nAfter.\n")
|
|
864
900
|
edited = "Changed before ESR measurements changed after.\n"
|
|
865
901
|
path.write_text(edited)
|
|
866
902
|
|
|
867
903
|
report = autofix_markdown_file(path, wrapnumber=79, git_head=True)
|
|
868
904
|
|
|
869
|
-
expected = (
|
|
870
|
-
"Changed before\nESR measurements\nchanged after."
|
|
871
|
-
)
|
|
905
|
+
expected = edited.rstrip()
|
|
872
906
|
assert path.read_text() == expected + "\n"
|
|
873
907
|
assert report["layout"] == []
|
|
874
|
-
|
|
875
|
-
assert fix["before"] == edited.rstrip()
|
|
876
|
-
assert fix["after"] == expected
|
|
908
|
+
assert report["fixes"] == []
|
|
877
909
|
|
|
878
910
|
|
|
879
911
|
@pytest.mark.parametrize("count", [1, 5, 30])
|
|
@@ -886,13 +918,13 @@ def test_cpb_and_wmatch_restore_scattered_reference_lines(tmp_path, count):
|
|
|
886
918
|
for number in range(count)
|
|
887
919
|
) + "\n"
|
|
888
920
|
current = "\n\n".join(
|
|
889
|
-
f"Changed intro {number} Stable component {number} "
|
|
890
|
-
f"changed outro {number}."
|
|
921
|
+
f"Changed opening intro {number} Stable component {number} "
|
|
922
|
+
f"changed closing outro {number}."
|
|
891
923
|
for number in range(count)
|
|
892
924
|
) + "\n"
|
|
893
925
|
expected = "\n\n".join(
|
|
894
|
-
f"Changed intro {number}\nStable component {number}\n"
|
|
895
|
-
f"changed outro {number}."
|
|
926
|
+
f"Changed opening intro {number}\nStable component {number}\n"
|
|
927
|
+
f"changed closing outro {number}."
|
|
896
928
|
for number in range(count)
|
|
897
929
|
) + "\n"
|
|
898
930
|
path = committed_source(tmp_path, head)
|
|
@@ -1288,14 +1320,16 @@ def test_qt_diff_fixes_fill_pages_with_double_line_breaks(tmp_path):
|
|
|
1288
1320
|
for j in range(count)
|
|
1289
1321
|
) + "\n"
|
|
1290
1322
|
current = "\n\n".join(
|
|
1291
|
-
f"Changed intro {j} Stable component {j}
|
|
1323
|
+
f"Changed opening intro {j} Stable component {j} "
|
|
1324
|
+
f"changed closing outro {j}."
|
|
1292
1325
|
for j in range(count)
|
|
1293
1326
|
) + "\n"
|
|
1294
1327
|
path = committed_source(tmp_path, head)
|
|
1295
1328
|
path.write_text(current)
|
|
1296
1329
|
report = autofix_markdown_file(path, wrapnumber=79, git_index=True)
|
|
1297
1330
|
expected = "\n\n".join(
|
|
1298
|
-
f"Changed intro {j}\nStable component {j}\
|
|
1331
|
+
f"Changed opening intro {j}\nStable component {j}\n"
|
|
1332
|
+
f"changed closing outro {j}."
|
|
1299
1333
|
for j in range(count)
|
|
1300
1334
|
) + "\n"
|
|
1301
1335
|
# Every hunk must already be applied before the notice opens.
|
|
@@ -247,7 +247,9 @@ def test_wmatch_reuses_wr_rules_and_preserves_other_lines(tmp_path, operation):
|
|
|
247
247
|
)
|
|
248
248
|
new.write_text(changed + "\n" + rest)
|
|
249
249
|
run([str(old), str(new)])
|
|
250
|
-
expected = wrap_prose(changed + "\n", 40)
|
|
250
|
+
expected = wrap_prose(changed + "\n", 40).replace(
|
|
251
|
+
"has enough\nwords\n", "has enough words\n",
|
|
252
|
+
) + rest
|
|
251
253
|
assert new.read_text() == expected
|
|
252
254
|
run([str(old), str(new)])
|
|
253
255
|
assert new.read_text() == expected
|
|
@@ -304,7 +306,11 @@ def test_wmatch_fallback_width_and_long_word(tmp_path):
|
|
|
304
306
|
text = "Start " + "extra words " * 20 + long_word + " end.\n"
|
|
305
307
|
new.write_text(text)
|
|
306
308
|
run([str(old), str(new)])
|
|
307
|
-
|
|
309
|
+
expected = wrap_prose(text, 79).replace(
|
|
310
|
+
"extra words\nextra words\n" + long_word + "\nend.\n",
|
|
311
|
+
"extra words extra words " + long_word + " end.\n",
|
|
312
|
+
)
|
|
313
|
+
assert new.read_text() == expected
|
|
308
314
|
assert long_word in new.read_text()
|
|
309
315
|
|
|
310
316
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/__version__.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/global.db
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg
RENAMED
|
File without changes
|
{pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/subproject1/independent.qmd
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/subproject1/test_include.qmd
RENAMED
|
File without changes
|
{pydifftools-0.1.53 → pydifftools-0.1.55}/example_notebook/project1/subproject1/tryforerror.qmd
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|