pyDiffTools 0.1.38__tar.gz → 0.1.39__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 (114) hide show
  1. {pydifftools-0.1.38/pyDiffTools.egg-info → pydifftools-0.1.39}/PKG-INFO +1 -1
  2. {pydifftools-0.1.38 → pydifftools-0.1.39/pyDiffTools.egg-info}/PKG-INFO +1 -1
  3. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/wrap_sentences.py +63 -38
  4. {pydifftools-0.1.38 → pydifftools-0.1.39}/pyproject.toml +1 -1
  5. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_wrap_check.py +55 -0
  6. {pydifftools-0.1.38 → pydifftools-0.1.39}/LICENSE.md +0 -0
  7. {pydifftools-0.1.38 → pydifftools-0.1.39}/MANIFEST.in +0 -0
  8. {pydifftools-0.1.38 → pydifftools-0.1.39}/README.rst +0 -0
  9. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/_quarto.yml +0 -0
  10. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/__version__.txt +0 -0
  11. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/1a724af72b16f5a9e607e12b1c721645/base.ipynb +0 -0
  12. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/1b28fc9daac9081847e5161b2c546f8a/base.ipynb +0 -0
  13. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/231f64eee282fa225d1104935cf80a24/base.ipynb +0 -0
  14. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/26e56f6b0ff54851a45145157f2f0dc4/base.ipynb +0 -0
  15. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/311fabd7029ffd050d056e2f316eb50f/base.ipynb +0 -0
  16. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/57da2021e5b156ac3adf01398201c723/base.ipynb +0 -0
  17. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/62b24ea7da75011d92b0f8924faa208d/base.ipynb +0 -0
  18. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/7f1b20d69d889514ab5d1cc92e3cb14f/base.ipynb +0 -0
  19. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/86f74c8c54a87ff892d9b15dd714e8f0/base.ipynb +0 -0
  20. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/88893b4234eac2945d9d6cb2e277f186/base.ipynb +0 -0
  21. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/9a40046ada6f582ee34af00fbdbfb417/base.ipynb +0 -0
  22. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/a1bf4d270d0641ff41faf1d7cce3439a/base.ipynb +0 -0
  23. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/a3789f7d9585a781f2a1c60ce95ff10d/base.ipynb +0 -0
  24. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/be46ecba858d39ad5f0c46902ddf1c02/base.ipynb +0 -0
  25. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/d0cbc57a12f2ccce710a5afd04cc05e7/base.ipynb +0 -0
  26. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/d3e12d320b14228f701231ae32ddd7dd/base.ipynb +0 -0
  27. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/eb434f61555438d020a6970a5dbf9ee8/base.ipynb +0 -0
  28. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/f6b0e73aa7fa029134665d4dde57e096/base.ipynb +0 -0
  29. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/f719a6e4ff09873cb0ffb06ec9d232f9/base.ipynb +0 -0
  30. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/executed/feeee244a7ce3d60e1a227eb604df823/base.ipynb +0 -0
  31. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/.jupyter_cache/global.db +0 -0
  32. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/example.qmd +0 -0
  33. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/example.tex +0 -0
  34. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/index.qmd +0 -0
  35. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/subproject1/.jupyter_cache/__version__.txt +0 -0
  36. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/subproject1/.jupyter_cache/executed/ca90e4df5f4f0583df6554156a68dc7f/base.ipynb +0 -0
  37. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/subproject1/.jupyter_cache/global.db +0 -0
  38. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg +0 -0
  39. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/subproject1/independent.qmd +0 -0
  40. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/subproject1/index.qmd +0 -0
  41. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/subproject1/tasks.qmd +0 -0
  42. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/subproject1/test_include.qmd +0 -0
  43. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/subproject1/tryforerror.qmd +0 -0
  44. {pydifftools-0.1.38 → pydifftools-0.1.39}/example_notebook/project1/test_include.qmd +0 -0
  45. {pydifftools-0.1.38 → pydifftools-0.1.39}/pyDiffTools.egg-info/SOURCES.txt +0 -0
  46. {pydifftools-0.1.38 → pydifftools-0.1.39}/pyDiffTools.egg-info/dependency_links.txt +0 -0
  47. {pydifftools-0.1.38 → pydifftools-0.1.39}/pyDiffTools.egg-info/entry_points.txt +0 -0
  48. {pydifftools-0.1.38 → pydifftools-0.1.39}/pyDiffTools.egg-info/requires.txt +0 -0
  49. {pydifftools-0.1.38 → pydifftools-0.1.39}/pyDiffTools.egg-info/top_level.txt +0 -0
  50. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/__init__.py +0 -0
  51. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/bibliography.py +0 -0
  52. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/browser_lifecycle.py +0 -0
  53. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/check_numbers.py +0 -0
  54. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/citation_dialog.py +0 -0
  55. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/command_line.py +0 -0
  56. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/command_registry.py +0 -0
  57. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/comment_functions.py +0 -0
  58. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/comment_migration.py +0 -0
  59. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/comment_tags.lua +0 -0
  60. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/comment_tags_margin.lua +0 -0
  61. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/comment_tags_no_comments.lua +0 -0
  62. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/comment_toggle.js +0 -0
  63. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/comments.css +0 -0
  64. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/continuous.py +0 -0
  65. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/copy_files.py +0 -0
  66. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/diff-doc.js +0 -0
  67. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/doc_contents.py +0 -0
  68. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/flowchart/__init__.py +0 -0
  69. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/flowchart/comparison.py +0 -0
  70. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/flowchart/dot_to_yaml.py +0 -0
  71. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/flowchart/graph.py +0 -0
  72. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/flowchart/svg-pan-zoom.min.js +0 -0
  73. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/flowchart/watch_graph.py +0 -0
  74. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/flowchart/wgrph_view.js +0 -0
  75. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/forward_search.py +0 -0
  76. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/git_gd.py +0 -0
  77. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/git_gd_history.py +0 -0
  78. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/git_gd_image.py +0 -0
  79. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/git_gd_qt.py +0 -0
  80. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/html_comments.py +0 -0
  81. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/html_uncomments.py +0 -0
  82. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/log_example.py +0 -0
  83. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/match_spaces.py +0 -0
  84. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/notebook/__init__.py +0 -0
  85. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/notebook/display.py +0 -0
  86. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/notebook/fast_build.py +0 -0
  87. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/notebook/tex_to_qmd.py +0 -0
  88. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/onewordify.py +0 -0
  89. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/onewordify_undo.py +0 -0
  90. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/outline.py +0 -0
  91. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/rearrange_tex.py +0 -0
  92. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/searchacro.py +0 -0
  93. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/separate_comments.py +0 -0
  94. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/split_conflict.py +0 -0
  95. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/unseparate_comments.py +0 -0
  96. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/update_check.py +0 -0
  97. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/xml2xlsx.vbs +0 -0
  98. {pydifftools-0.1.38 → pydifftools-0.1.39}/pydifftools/zotero.py +0 -0
  99. {pydifftools-0.1.38 → pydifftools-0.1.39}/setup.cfg +0 -0
  100. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_bibliography.py +0 -0
  101. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_browser_lifecycle.py +0 -0
  102. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_check_single_use.py +0 -0
  103. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_command_line_help.py +0 -0
  104. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_comment_migration.py +0 -0
  105. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_continuous_shutdown.py +0 -0
  106. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_forward_search.py +0 -0
  107. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_git_gd_history.py +0 -0
  108. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_git_gd_image.py +0 -0
  109. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_git_gd_qt.py +0 -0
  110. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_rrng.py +0 -0
  111. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_tex_to_qmd.py +0 -0
  112. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_update_check.py +0 -0
  113. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_wrapping.py +0 -0
  114. {pydifftools-0.1.38 → pydifftools-0.1.39}/tests/test_zotero.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyDiffTools
3
- Version: 0.1.38
3
+ Version: 0.1.39
4
4
  Summary: Diff tools
5
5
  Author: J M Franck
6
6
  License: Copyright (c) 2015, jmfranck
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyDiffTools
3
- Version: 0.1.38
3
+ Version: 0.1.39
4
4
  Summary: Diff tools
5
5
  Author: J M Franck
6
6
  License: Copyright (c) 2015, jmfranck
@@ -942,7 +942,9 @@ def _iter_classified_spans(text, filetype, line_start=1):
942
942
  leaf prose blocks -- both just need the same protected-region
943
943
  exclusions wr itself uses.
944
944
  """
945
- classified = classify_lines(text, filetype, normalize_math=True)
945
+ # normalizing math can add lines (e.g. splitting "$${#eq:x}"), which
946
+ # would shift every reported line number after it
947
+ classified = classify_lines(text, filetype)
946
948
  line = line_start
947
949
  for wrappable, group in itertools.groupby(classified, key=lambda x: x[0]):
948
950
  content = "".join(l for _, l in group)
@@ -1122,19 +1124,24 @@ def apply_markdown_issue_fix(content, line_number, message):
1122
1124
  prefix = re.match(r"^[ \t]*", old_line).group()
1123
1125
  # }}}
1124
1126
  if message.startswith("line too long:"):
1125
- match = re.search(r"before '([^']+)'", message)
1127
+ match = re.search(r"after '(.+?)' \(before '(.+?)'\); move", message)
1126
1128
  if match is None:
1127
1129
  raise ValueError(
1128
1130
  f"Cannot find the suggested word in {message!r}"
1129
1131
  )
1130
- word = re.escape(match.group(1))
1131
- position = re.search(r"(?<!\S)" + word + r"(?!\S)", old_line)
1132
+ # split at the suggested word that follows wr's break word, since the
1133
+ # same word can appear earlier on the line (e.g. "for ... ($3x$ for")
1134
+ position = re.search(
1135
+ r"(?<!\S)" + re.escape(match.group(1)) + r"[ \t]+("
1136
+ + re.escape(match.group(2)) + r")(?!\S)",
1137
+ old_line,
1138
+ )
1132
1139
  if position is None:
1133
1140
  raise ValueError(
1134
- f"Cannot find {match.group(1)!r} on Markdown line "
1135
- f"{line_number}"
1141
+ f"Cannot find {match.group(2)!r} after {match.group(1)!r} on "
1142
+ f"Markdown line {line_number}"
1136
1143
  )
1137
- split_at = position.start()
1144
+ split_at = position.start(1)
1138
1145
  first = old_line[:split_at].rstrip()
1139
1146
  second = old_line[split_at:].lstrip(" \t")
1140
1147
  reason = (
@@ -1157,9 +1164,22 @@ def apply_markdown_issue_fix(content, line_number, message):
1157
1164
  else:
1158
1165
  raise ValueError(f"No automatic fix is available for {message!r}")
1159
1166
 
1160
- fixed_line = first + ending + prefix + second + ending
1161
- lines[index] = fixed_line
1162
- after = first + "\n" + prefix + second
1167
+ if not first.strip():
1168
+ # a break here would insert a blank line, i.e. a paragraph break
1169
+ raise ValueError(
1170
+ f"Automatic fix would leave an empty line at {line_number}"
1171
+ )
1172
+ if second.strip():
1173
+ lines[index] = first + ending + prefix + second + ending
1174
+ after = first + "\n" + prefix + second
1175
+ else:
1176
+ # only trailing whitespace followed the break, so drop it rather
1177
+ # than adding a blank line
1178
+ lines[index] = first + ending
1179
+ after = first
1180
+ reason = (
1181
+ "A sentence ended with a trailing space. I removed the space."
1182
+ )
1163
1183
  return "".join(lines), old_line, after, reason
1164
1184
 
1165
1185
 
@@ -1168,39 +1188,44 @@ def autofix_markdown_file(filename, wrapnumber=55, punctuation_slop=20):
1168
1188
  with open(filename, encoding="utf-8", newline="") as fp:
1169
1189
  content = fp.read()
1170
1190
  fixes = []
1171
- for _ in range(1000):
1191
+ # Lint once, fix every flagged line, then lint again, since fixing one
1192
+ # issue at a time re-lints the whole file for every single fix.
1193
+ for _ in range(100):
1172
1194
  issues = markdown_lint_issues_from_text(
1173
1195
  content, wrapnumber=wrapnumber, punctuation_slop=punctuation_slop
1174
1196
  )
1175
- fixable = next(
1176
- (
1177
- issue
1178
- for issue in issues
1179
- if not issue[1].startswith(
1180
- ("unclosed math:", "unclosed HTML comment:")
1181
- )
1182
- ),
1183
- None,
1184
- )
1185
- if fixable is None:
1197
+ fixable = {}
1198
+ for line_number, message in issues:
1199
+ if not message.startswith(
1200
+ ("unclosed math:", "unclosed HTML comment:")
1201
+ ):
1202
+ # a fix only rewrites its own line, so fix one issue per line
1203
+ # per pass and let the next lint catch whatever remains
1204
+ fixable.setdefault(line_number, message)
1205
+ if not fixable:
1186
1206
  break
1187
- line_number, message = fixable
1188
- updated, before, after, reason = apply_markdown_issue_fix(
1189
- content, line_number, message
1190
- )
1191
- if updated == content:
1192
- raise RuntimeError(
1193
- f"Automatic Markdown fix made no change at line {line_number}"
1207
+ # work from the bottom up so each inserted line break leaves the
1208
+ # line numbers of the remaining issues unchanged
1209
+ pass_fixes = []
1210
+ for line_number in sorted(fixable, reverse=True):
1211
+ updated, before, after, reason = apply_markdown_issue_fix(
1212
+ content, line_number, fixable[line_number]
1194
1213
  )
1195
- fixes.append(
1196
- {
1197
- "line": line_number,
1198
- "reason": reason,
1199
- "before": before,
1200
- "after": after,
1201
- }
1202
- )
1203
- content = updated
1214
+ if updated == content:
1215
+ raise RuntimeError(
1216
+ "Automatic Markdown fix made no change at line "
1217
+ f"{line_number}"
1218
+ )
1219
+ pass_fixes.append(
1220
+ {
1221
+ "line": line_number,
1222
+ "reason": reason,
1223
+ "before": before,
1224
+ "after": after,
1225
+ }
1226
+ )
1227
+ content = updated
1228
+ fixes.extend(reversed(pass_fixes))
1204
1229
  else:
1205
1230
  raise RuntimeError("Too many automatic Markdown fixes were needed.")
1206
1231
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyDiffTools"
7
- version = "0.1.38"
7
+ version = "0.1.39"
8
8
  authors = [{ name = "J M Franck" }]
9
9
  description = "Diff tools"
10
10
  readme = "README.rst"
@@ -120,6 +120,61 @@ def test_one_reported_long_line_is_fixed_at_the_suggested_word():
120
120
  assert "hard to read" in reason
121
121
 
122
122
 
123
+ def test_lint_line_numbers_survive_labeled_display_math():
124
+ # normalizing "$${#eq:x}" for wrapping splits it onto two lines, which
125
+ # must not shift the line numbers reported for later prose
126
+ source = (
127
+ "Intro.\n$$\nx = y\n$${#eq:x}\nwhere this sentence ends. "
128
+ "Another starts.\n"
129
+ )
130
+
131
+ ((line, message),) = markdown_lint_issues_from_text(source, 55)
132
+
133
+ assert line == 5
134
+ assert message.startswith("sentence ends mid-line")
135
+
136
+
137
+ def test_long_line_fix_splits_at_the_word_after_the_break():
138
+ # "for" also starts the line, but wr breaks before the second one
139
+ source = (
140
+ "for $30\\;\\text{s}$ ($3\\times$ for AOT) with some more words "
141
+ "added.\n"
142
+ )
143
+ message = (
144
+ "line too long: wr would break after '($3\\times$' (before 'for'); "
145
+ "move 'for' onward to the next line, or shorten this sentence"
146
+ )
147
+
148
+ fixed, _, _, _ = apply_markdown_issue_fix(source, 1, message)
149
+
150
+ assert fixed.startswith("for $30\\;\\text{s}$ ($3\\times$\nfor AOT)")
151
+
152
+
153
+ def test_trailing_space_after_sentence_is_removed_not_a_blank_line(tmp_path):
154
+ path = tmp_path / "source.md"
155
+ path.write_text("It melts near 238&nbsp;K. \nIn contrast, it stays.\n")
156
+
157
+ autofix_markdown_file(path)
158
+
159
+ fixed = path.read_text()
160
+ assert fixed.startswith("It melts near 238&nbsp;K.\nIn contrast,")
161
+ assert "\n\n" not in fixed
162
+
163
+
164
+ def test_autofix_fixes_every_line_in_one_pass_from_the_end(tmp_path):
165
+ path = tmp_path / "source.md"
166
+ paragraph = "First sentence ends here. A second sentence begins.\n"
167
+ path.write_text("\n".join([paragraph] * 30))
168
+
169
+ report = autofix_markdown_file(path)
170
+
171
+ fixed = path.read_text()
172
+ assert len(report["fixes"]) == 30
173
+ assert [fix["line"] for fix in report["fixes"]] == list(range(1, 60, 2))
174
+ assert fixed.count("\n\n") == 29
175
+ assert markdown_lint_issues_from_text(fixed, wrapnumber=55) == []
176
+
177
+
123
178
  def test_unclosed_math_is_reported_after_fifty_lines():
124
179
  short_math = "$$\n" + ("x = y\n" * 48)
125
180
  long_math = "$$\n" + ("x = y\n" * 49)
File without changes
File without changes
File without changes
File without changes