pyDiffTools 0.1.54__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.
Files changed (129) hide show
  1. {pydifftools-0.1.54/pyDiffTools.egg-info → pydifftools-0.1.55}/PKG-INFO +4 -2
  2. {pydifftools-0.1.54 → pydifftools-0.1.55}/README.rst +3 -1
  3. {pydifftools-0.1.54 → pydifftools-0.1.55/pyDiffTools.egg-info}/PKG-INFO +4 -2
  4. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/line_alignment.py +15 -8
  5. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/match_spaces.py +7 -1
  6. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/wrap_sentences.py +24 -5
  7. {pydifftools-0.1.54 → pydifftools-0.1.55}/pyproject.toml +1 -1
  8. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_line_alignment.py +28 -8
  9. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_wrap_check.py +50 -16
  10. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_wrapping.py +8 -2
  11. {pydifftools-0.1.54 → pydifftools-0.1.55}/LICENSE.md +0 -0
  12. {pydifftools-0.1.54 → pydifftools-0.1.55}/MANIFEST.in +0 -0
  13. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/_quarto.yml +0 -0
  14. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/__version__.txt +0 -0
  15. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/1a724af72b16f5a9e607e12b1c721645/base.ipynb +0 -0
  16. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/1b28fc9daac9081847e5161b2c546f8a/base.ipynb +0 -0
  17. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/231f64eee282fa225d1104935cf80a24/base.ipynb +0 -0
  18. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/26e56f6b0ff54851a45145157f2f0dc4/base.ipynb +0 -0
  19. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/311fabd7029ffd050d056e2f316eb50f/base.ipynb +0 -0
  20. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/57da2021e5b156ac3adf01398201c723/base.ipynb +0 -0
  21. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/62b24ea7da75011d92b0f8924faa208d/base.ipynb +0 -0
  22. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/7f1b20d69d889514ab5d1cc92e3cb14f/base.ipynb +0 -0
  23. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/86f74c8c54a87ff892d9b15dd714e8f0/base.ipynb +0 -0
  24. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/88893b4234eac2945d9d6cb2e277f186/base.ipynb +0 -0
  25. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/9a40046ada6f582ee34af00fbdbfb417/base.ipynb +0 -0
  26. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/a1bf4d270d0641ff41faf1d7cce3439a/base.ipynb +0 -0
  27. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/a3789f7d9585a781f2a1c60ce95ff10d/base.ipynb +0 -0
  28. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/be46ecba858d39ad5f0c46902ddf1c02/base.ipynb +0 -0
  29. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/d0cbc57a12f2ccce710a5afd04cc05e7/base.ipynb +0 -0
  30. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/d3e12d320b14228f701231ae32ddd7dd/base.ipynb +0 -0
  31. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/eb434f61555438d020a6970a5dbf9ee8/base.ipynb +0 -0
  32. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/f6b0e73aa7fa029134665d4dde57e096/base.ipynb +0 -0
  33. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/f719a6e4ff09873cb0ffb06ec9d232f9/base.ipynb +0 -0
  34. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/executed/feeee244a7ce3d60e1a227eb604df823/base.ipynb +0 -0
  35. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/.jupyter_cache/global.db +0 -0
  36. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/example.qmd +0 -0
  37. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/example.tex +0 -0
  38. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/index.qmd +0 -0
  39. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/subproject1/.jupyter_cache/__version__.txt +0 -0
  40. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/subproject1/.jupyter_cache/executed/ca90e4df5f4f0583df6554156a68dc7f/base.ipynb +0 -0
  41. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/subproject1/.jupyter_cache/global.db +0 -0
  42. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg +0 -0
  43. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/subproject1/independent.qmd +0 -0
  44. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/subproject1/index.qmd +0 -0
  45. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/subproject1/tasks.qmd +0 -0
  46. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/subproject1/test_include.qmd +0 -0
  47. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/subproject1/tryforerror.qmd +0 -0
  48. {pydifftools-0.1.54 → pydifftools-0.1.55}/example_notebook/project1/test_include.qmd +0 -0
  49. {pydifftools-0.1.54 → pydifftools-0.1.55}/pyDiffTools.egg-info/SOURCES.txt +0 -0
  50. {pydifftools-0.1.54 → pydifftools-0.1.55}/pyDiffTools.egg-info/dependency_links.txt +0 -0
  51. {pydifftools-0.1.54 → pydifftools-0.1.55}/pyDiffTools.egg-info/entry_points.txt +0 -0
  52. {pydifftools-0.1.54 → pydifftools-0.1.55}/pyDiffTools.egg-info/requires.txt +0 -0
  53. {pydifftools-0.1.54 → pydifftools-0.1.55}/pyDiffTools.egg-info/top_level.txt +0 -0
  54. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/__init__.py +0 -0
  55. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/bibliography.py +0 -0
  56. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/browser_lifecycle.py +0 -0
  57. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/check_numbers.py +0 -0
  58. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/citation_dialog.py +0 -0
  59. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/command_line.py +0 -0
  60. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/command_registry.py +0 -0
  61. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/comment_filter_history.json +0 -0
  62. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/comment_functions.py +0 -0
  63. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/comment_migration.py +0 -0
  64. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/comment_tags.lua +0 -0
  65. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/comment_tags_margin.lua +0 -0
  66. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/comment_tags_no_comments.lua +0 -0
  67. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/comment_toggle.js +0 -0
  68. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/comments.css +0 -0
  69. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/comments_author_colors.css +0 -0
  70. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/completions/git-mergein +0 -0
  71. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/continuous.py +0 -0
  72. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/copy_files.py +0 -0
  73. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/diff-doc.js +0 -0
  74. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/doc_contents.py +0 -0
  75. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/flowchart/__init__.py +0 -0
  76. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/flowchart/comparison.py +0 -0
  77. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/flowchart/dot_to_yaml.py +0 -0
  78. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/flowchart/graph.py +0 -0
  79. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/flowchart/source_jump.js +0 -0
  80. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/flowchart/svg-pan-zoom.min.js +0 -0
  81. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/flowchart/watch_graph.py +0 -0
  82. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/flowchart/wgrph_view.js +0 -0
  83. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/forward_search.py +0 -0
  84. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/git_aliases.py +0 -0
  85. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/git_gd.py +0 -0
  86. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/git_gd_history.py +0 -0
  87. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/git_gd_image.py +0 -0
  88. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/git_gd_qt.py +0 -0
  89. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/html_comments.py +0 -0
  90. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/html_uncomments.py +0 -0
  91. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/log_example.py +0 -0
  92. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/notebook/__init__.py +0 -0
  93. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/notebook/display.py +0 -0
  94. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/notebook/fast_build.py +0 -0
  95. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/notebook/tex_to_qmd.py +0 -0
  96. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/onewordify.py +0 -0
  97. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/onewordify_undo.py +0 -0
  98. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/outline.py +0 -0
  99. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/pdf_diff.py +0 -0
  100. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/rearrange_tex.py +0 -0
  101. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/searchacro.py +0 -0
  102. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/separate_comments.py +0 -0
  103. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/source_jump.py +0 -0
  104. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/split_conflict.py +0 -0
  105. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/unseparate_comments.py +0 -0
  106. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/update_check.py +0 -0
  107. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/wrapping_options.py +0 -0
  108. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/xml2xlsx.vbs +0 -0
  109. {pydifftools-0.1.54 → pydifftools-0.1.55}/pydifftools/zotero.py +0 -0
  110. {pydifftools-0.1.54 → pydifftools-0.1.55}/setup.cfg +0 -0
  111. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_bibliography.py +0 -0
  112. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_browser_lifecycle.py +0 -0
  113. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_check_single_use.py +0 -0
  114. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_command_line_help.py +0 -0
  115. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_comment_filters.py +0 -0
  116. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_comment_migration.py +0 -0
  117. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_continuous_shutdown.py +0 -0
  118. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_forward_search.py +0 -0
  119. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_git_gd_history.py +0 -0
  120. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_git_gd_image.py +0 -0
  121. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_git_gd_qt.py +0 -0
  122. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_git_mergein.py +0 -0
  123. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_git_pdf_diff.py +0 -0
  124. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_pdf_diff.py +0 -0
  125. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_rrng.py +0 -0
  126. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_source_jump.py +0 -0
  127. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_tex_to_qmd.py +0 -0
  128. {pydifftools-0.1.54 → pydifftools-0.1.55}/tests/test_update_check.py +0 -0
  129. {pydifftools-0.1.54 → 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.54
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 a tiny edited fragment, and checks wrapping rules.
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 a tiny edited fragment, and checks wrapping rules.
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.54
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 a tiny edited fragment, and checks wrapping rules.
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
@@ -256,6 +256,7 @@ def align_line_breaks(
256
256
  if allowed:
257
257
  for line, message in check_prose(
258
258
  span, width, punctuation_slop, offset,
259
+ overflow_words=4,
259
260
  ):
260
261
  issues.setdefault(line, message)
261
262
  offset += span.count("\n")
@@ -477,14 +478,20 @@ def align_line_breaks(
477
478
  region is not None
478
479
  and start + tokens[number + 1][1] < region[1]
479
480
  )
480
- if (
481
- number not in matches[hunk] and "\n" in raw
482
- and "\n" not in gaps[number] and not inside_region
483
- and len("".join(
484
- before[:tokens[number][2]].split("\n")[-1].split()
485
- )) < 10
481
+ if "\n" in raw and "\n" not in gaps[number] and (
482
+ not inside_region
486
483
  ):
487
- continue
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
488
495
  if inside_region or (
489
496
  current_layout[3][j1 + tokens[number][0]]
490
497
  and current_layout[3][j1 + tokens[number + 1][0]]
@@ -546,7 +553,7 @@ def align_line_breaks(
546
553
  if any(
547
554
  line not in original_rows
548
555
  and line.rstrip() not in inherited
549
- and 0 < len("".join(line.split())) < 10
556
+ and 0 < len(line.split()) <= 3
550
557
  for line in proposed_rows
551
558
  ):
552
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,
@@ -117,7 +118,12 @@ def match_text(
117
118
  return (
118
119
  local
119
120
  if any(
120
- len(line) > (local if joined else 1.5 * local)
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
+ )
121
127
  for line in restored.splitlines()
122
128
  )
123
129
  else fallback_width
@@ -880,9 +880,14 @@ def wr(
880
880
  fp.write(result)
881
881
 
882
882
 
883
- def check_prose(content, wrapnumber, punctuation_slop, line_start=1):
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
- if actual_end > wr_end:
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(span, width, punctuation_slop, line)
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:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyDiffTools"
7
- version = "0.1.54"
7
+ version = "0.1.55"
8
8
  authors = [{ name = "J M Franck" }]
9
9
  description = "Diff tools"
10
10
  readme = "README.rst"
@@ -489,13 +489,17 @@ def test_batched_word_diff_keeps_hunks_isolated(monkeypatch):
489
489
  "Other introduction\nsame reference words\nother closing.\n"
490
490
  )
491
491
  current = (
492
- "Changed introduction same reference words changed closing.\n"
492
+ "Changed words in introduction same reference words "
493
+ "changed words in closing.\n"
493
494
  "\nUnchanged anchor.\n\n"
494
- "Updated introduction same reference words updated closing.\n"
495
+ "Updated words in introduction same reference words "
496
+ "updated words in closing.\n"
495
497
  )
496
498
  expected = [
497
- "Changed introduction\nsame reference words\nchanged closing.\n",
498
- "Updated introduction\nsame reference words\nupdated closing.\n",
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",
499
503
  ]
500
504
  calls = []
501
505
  original_run = line_alignment.subprocess.run
@@ -516,10 +520,26 @@ def test_optional_preservation_break_does_not_strand_a_tiny_fragment():
516
520
  )
517
521
  current = "X stable reference words rewritten closing.\n"
518
522
  (result,) = align_line_breaks(reference, current)
519
- # Reference whitespace is restored after "words", but separating "X"
520
- # just to preserve the next reference line would make a tiny new row.
521
- assert result["aligned"] == (
522
- "X stable reference words\nrewritten closing.\n"
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
523
543
  )
524
544
 
525
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\nESR measurements\npresented",
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
- # Automatic alignment preserves both unchanged HEAD lines.
859
- assert numstat(path) == (8, 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 test_embedded_head_line_restores_both_boundaries_together(tmp_path):
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
- (fix,) = report["fixes"]
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} changed outro {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}\nchanged outro {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) + rest
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
- assert new.read_text() == wrap_prose(text, 80)
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