pyDiffTools 0.1.39__tar.gz → 0.1.41__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.39/pyDiffTools.egg-info → pydifftools-0.1.41}/PKG-INFO +1 -1
- {pydifftools-0.1.39 → pydifftools-0.1.41/pyDiffTools.egg-info}/PKG-INFO +1 -1
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/continuous.py +1 -1
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/wrap_sentences.py +146 -26
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pyproject.toml +1 -1
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_wrap_check.py +125 -2
- {pydifftools-0.1.39 → pydifftools-0.1.41}/LICENSE.md +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/MANIFEST.in +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/README.rst +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/_quarto.yml +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/1a724af72b16f5a9e607e12b1c721645/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/1b28fc9daac9081847e5161b2c546f8a/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/231f64eee282fa225d1104935cf80a24/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/26e56f6b0ff54851a45145157f2f0dc4/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/311fabd7029ffd050d056e2f316eb50f/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/57da2021e5b156ac3adf01398201c723/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/62b24ea7da75011d92b0f8924faa208d/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/7f1b20d69d889514ab5d1cc92e3cb14f/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/86f74c8c54a87ff892d9b15dd714e8f0/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/88893b4234eac2945d9d6cb2e277f186/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/9a40046ada6f582ee34af00fbdbfb417/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/a1bf4d270d0641ff41faf1d7cce3439a/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/a3789f7d9585a781f2a1c60ce95ff10d/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/be46ecba858d39ad5f0c46902ddf1c02/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/d0cbc57a12f2ccce710a5afd04cc05e7/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/d3e12d320b14228f701231ae32ddd7dd/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/eb434f61555438d020a6970a5dbf9ee8/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/f6b0e73aa7fa029134665d4dde57e096/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/f719a6e4ff09873cb0ffb06ec9d232f9/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/executed/feeee244a7ce3d60e1a227eb604df823/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/example.qmd +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/example.tex +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/index.qmd +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/subproject1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/subproject1/.jupyter_cache/executed/ca90e4df5f4f0583df6554156a68dc7f/base.ipynb +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/subproject1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/subproject1/independent.qmd +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/subproject1/index.qmd +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/subproject1/tasks.qmd +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/subproject1/test_include.qmd +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/subproject1/tryforerror.qmd +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/test_include.qmd +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pyDiffTools.egg-info/SOURCES.txt +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pyDiffTools.egg-info/dependency_links.txt +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pyDiffTools.egg-info/entry_points.txt +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pyDiffTools.egg-info/requires.txt +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pyDiffTools.egg-info/top_level.txt +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/__init__.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/bibliography.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/browser_lifecycle.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/check_numbers.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/citation_dialog.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/command_line.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/command_registry.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/comment_functions.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/comment_migration.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/comment_tags.lua +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/comment_tags_margin.lua +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/comment_tags_no_comments.lua +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/comment_toggle.js +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/comments.css +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/copy_files.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/diff-doc.js +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/doc_contents.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/flowchart/__init__.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/flowchart/comparison.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/flowchart/dot_to_yaml.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/flowchart/graph.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/flowchart/svg-pan-zoom.min.js +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/flowchart/watch_graph.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/flowchart/wgrph_view.js +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/forward_search.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/git_gd.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/git_gd_history.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/git_gd_image.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/git_gd_qt.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/html_comments.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/html_uncomments.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/log_example.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/match_spaces.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/notebook/__init__.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/notebook/display.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/notebook/fast_build.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/notebook/tex_to_qmd.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/onewordify.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/onewordify_undo.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/outline.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/rearrange_tex.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/searchacro.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/separate_comments.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/split_conflict.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/unseparate_comments.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/update_check.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/xml2xlsx.vbs +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/pydifftools/zotero.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/setup.cfg +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_bibliography.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_browser_lifecycle.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_check_single_use.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_command_line_help.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_comment_migration.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_continuous_shutdown.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_forward_search.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_git_gd_history.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_git_gd_image.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_git_gd_qt.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_rrng.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_tex_to_qmd.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_update_check.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_wrapping.py +0 -0
- {pydifftools-0.1.39 → pydifftools-0.1.41}/tests/test_zotero.py +0 -0
|
@@ -310,7 +310,7 @@ def run_pandoc(
|
|
|
310
310
|
|
|
311
311
|
automatic_fixes_were_made = False
|
|
312
312
|
while True:
|
|
313
|
-
report = autofix_markdown_file(filename,
|
|
313
|
+
report = autofix_markdown_file(filename, max_line_length=79)
|
|
314
314
|
automatic_fixes_were_made |= bool(report["fixes"])
|
|
315
315
|
if report["fixes"] or report["warnings"]:
|
|
316
316
|
user_says_fixed = show_markdown_fix_dialog(report)
|
|
@@ -16,6 +16,16 @@ PARAGRAPH_SPLIT = re.compile(r"(\n(?:[ \t]*\n)+)")
|
|
|
16
16
|
SENTENCE_END = r"[^.!?]{3}[.!?]"
|
|
17
17
|
SENTENCE_SPLIT = re.compile("(" + SENTENCE_END + r")[ \n]")
|
|
18
18
|
SENTENCE_BOUNDARY = re.compile("(" + SENTENCE_END + r")([ \n])")
|
|
19
|
+
# A word is a run of non-space characters, except that an attribute block
|
|
20
|
+
# attached to a figure's closing ")" (or any "{#id ...}" block) stays inside
|
|
21
|
+
# the word, so a break never lands inside a pandoc-crossref marker.
|
|
22
|
+
WORD = re.compile(r"(?:(?<=\))\{[^}]*\}|\{#[^}]*\}|\S)+")
|
|
23
|
+
# A pandoc-crossref marker must directly follow the closing "$$" of its
|
|
24
|
+
# equation or the closing ")" of its figure, and must stay on one line.
|
|
25
|
+
DETACHED_CROSSREF = re.compile(
|
|
26
|
+
r"(?:(?<!\\)\$\$|\))(?P<gap>[ \t]*\n?[ \t]*)(?=\{#(?:eq|fig):)"
|
|
27
|
+
r"|(?P<marker>\{#(?:eq|fig):[^}\n]*\n[^}]*\})"
|
|
28
|
+
)
|
|
19
29
|
LATEX_MACRO_SPLIT = re.compile(
|
|
20
30
|
r"(\\(?:begin|end|usepackage|newcommand|section"
|
|
21
31
|
r"|subsection|subsubsection|paragraph|input){[^}]*})"
|
|
@@ -584,6 +594,11 @@ def classify_lines(
|
|
|
584
594
|
if not equation.endswith("\n"):
|
|
585
595
|
equation += "\n"
|
|
586
596
|
equation += "$$"
|
|
597
|
+
# keep a pandoc-crossref marker on its closing "$$"
|
|
598
|
+
marker = re.match(r"\{#eq:[^}]*\}", content[stop:])
|
|
599
|
+
if marker:
|
|
600
|
+
equation += marker[0]
|
|
601
|
+
stop += marker.end()
|
|
587
602
|
if stop < len(content):
|
|
588
603
|
equation += "\n"
|
|
589
604
|
if content[stop] == "\n":
|
|
@@ -686,7 +701,9 @@ def wrap_prose(
|
|
|
686
701
|
lines = []
|
|
687
702
|
indentation = 0
|
|
688
703
|
for sentence in sentences:
|
|
689
|
-
words = [
|
|
704
|
+
words = [
|
|
705
|
+
re.sub(r"\s+", " ", word) for word in WORD.findall(sentence)
|
|
706
|
+
]
|
|
690
707
|
boundaries = _math_boundaries(words)
|
|
691
708
|
if filetype == "latex":
|
|
692
709
|
indentation = 0
|
|
@@ -794,6 +811,15 @@ def _prepare(filename, cleanoo=False, i=-1):
|
|
|
794
811
|
def wr(filename, wrapnumber=45, punctuation_slop=20, cleanoo=False, i=-1):
|
|
795
812
|
alltext, filetype, indent_amount, _ = _prepare(filename, cleanoo, i)
|
|
796
813
|
if filetype == "markdown":
|
|
814
|
+
# reattach detached pandoc-crossref markers before wrapping
|
|
815
|
+
alltext = DETACHED_CROSSREF.sub(
|
|
816
|
+
lambda found: (
|
|
817
|
+
re.sub(r"\s+", " ", found["marker"])
|
|
818
|
+
if found["marker"]
|
|
819
|
+
else found[0][: len(found[0]) - len(found["gap"])]
|
|
820
|
+
),
|
|
821
|
+
alltext,
|
|
822
|
+
)
|
|
797
823
|
result = wrap_blocks(
|
|
798
824
|
markdown_blocks(alltext),
|
|
799
825
|
wrapnumber,
|
|
@@ -865,7 +891,7 @@ def check_prose(content, wrapnumber, punctuation_slop, line_start=1):
|
|
|
865
891
|
if fragment:
|
|
866
892
|
# {{{ check line lengths within this sentence fragment
|
|
867
893
|
words, positions = [], []
|
|
868
|
-
for match in
|
|
894
|
+
for match in WORD.finditer(fragment):
|
|
869
895
|
words.append(match.group())
|
|
870
896
|
positions.append(frag_offset + match.start())
|
|
871
897
|
if words:
|
|
@@ -978,26 +1004,71 @@ def _iter_block_spans(blocks, wrapnumber, line_start=1):
|
|
|
978
1004
|
|
|
979
1005
|
|
|
980
1006
|
def markdown_lint_issues_from_text(
|
|
981
|
-
content, wrapnumber=45, punctuation_slop=20
|
|
1007
|
+
content, wrapnumber=45, punctuation_slop=20, max_line_length=None
|
|
982
1008
|
):
|
|
983
|
-
"""Return Markdown writing issues, including unfinished source spans.
|
|
1009
|
+
"""Return Markdown writing issues, including unfinished source spans.
|
|
1010
|
+
|
|
1011
|
+
With `max_line_length`, a prose line is only too long when it holds
|
|
1012
|
+
more than that many characters, instead of wr's greedy line choice.
|
|
1013
|
+
"""
|
|
984
1014
|
def mask_comment(match):
|
|
985
1015
|
return "".join("\n" if char == "\n" else " " for char in match[0])
|
|
986
1016
|
|
|
987
1017
|
prose_content = re.sub(
|
|
988
1018
|
r"<!--.*?(?:-->|\Z)", mask_comment, content, flags=re.DOTALL
|
|
989
1019
|
)
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1020
|
+
# trailing spaces are reported separately below, and a double space
|
|
1021
|
+
# (a hard line break) must not look like a sentence ending mid-line
|
|
1022
|
+
prose_content = re.sub(r"[ \t]+$", "", prose_content, flags=re.M)
|
|
1023
|
+
issues = []
|
|
1024
|
+
prose_lines = prose_content.splitlines()
|
|
1025
|
+
for line, width, span in _iter_block_spans(
|
|
1026
|
+
markdown_blocks(prose_content), wrapnumber
|
|
1027
|
+
):
|
|
1028
|
+
for issue in check_prose(span, width, punctuation_slop, line):
|
|
1029
|
+
if max_line_length is None or not issue[1].startswith(
|
|
1030
|
+
"line too long:"
|
|
1031
|
+
):
|
|
1032
|
+
issues.append(issue)
|
|
1033
|
+
if max_line_length is not None:
|
|
1034
|
+
# {{{ flag prose lines longer than the character limit
|
|
1035
|
+
for number in range(line, line + len(span.splitlines())):
|
|
1036
|
+
text = prose_lines[number - 1]
|
|
1037
|
+
# a single unbreakable word cannot be shortened
|
|
1038
|
+
if len(text) > max_line_length and len(
|
|
1039
|
+
WORD.findall(text)
|
|
1040
|
+
) > 1:
|
|
1041
|
+
issues.append((
|
|
1042
|
+
number,
|
|
1043
|
+
f"line over {max_line_length} characters: break "
|
|
1044
|
+
"it at a word boundary",
|
|
1045
|
+
))
|
|
1046
|
+
# }}}
|
|
997
1047
|
issues.extend(
|
|
998
1048
|
(issue["line"], issue["message"])
|
|
999
1049
|
for issue in unclosed_markdown_spans(content)
|
|
1000
1050
|
)
|
|
1051
|
+
# {{{ report single trailing spaces and detached crossref markers
|
|
1052
|
+
fence = None
|
|
1053
|
+
for number, line in enumerate(content.splitlines(), start=1):
|
|
1054
|
+
opening = re.match(r"[ \t]*(\x60{3,}|~{3,})", line)
|
|
1055
|
+
if opening and (fence is None or opening[1].startswith(fence)):
|
|
1056
|
+
fence = None if fence else opening[1]
|
|
1057
|
+
elif fence is None and re.search(r"(?<! ) $", line):
|
|
1058
|
+
issues.append((
|
|
1059
|
+
number,
|
|
1060
|
+
"trailing space: remove the single space at the end of this "
|
|
1061
|
+
"line (two spaces are a deliberate hard line break)",
|
|
1062
|
+
))
|
|
1063
|
+
for found in DETACHED_CROSSREF.finditer(prose_content):
|
|
1064
|
+
if found["gap"] == "":
|
|
1065
|
+
continue
|
|
1066
|
+
issues.append((
|
|
1067
|
+
prose_content.count("\n", 0, found.start()) + 1,
|
|
1068
|
+
"detached crossref marker: keep the {#eq:...} or {#fig:...} "
|
|
1069
|
+
"marker on one line, directly after the closing $$ or )",
|
|
1070
|
+
))
|
|
1071
|
+
# }}}
|
|
1001
1072
|
return sorted(issues)
|
|
1002
1073
|
|
|
1003
1074
|
|
|
@@ -1101,6 +1172,33 @@ def apply_markdown_issue_fix(content, line_number, message):
|
|
|
1101
1172
|
raise ValueError(
|
|
1102
1173
|
f"Markdown issue refers to missing line {line_number}"
|
|
1103
1174
|
)
|
|
1175
|
+
if message.startswith("detached crossref marker"):
|
|
1176
|
+
# {{{ join the marker onto its "$$" or ")" (this can remove a line)
|
|
1177
|
+
start = sum(len(line) for line in lines[:index])
|
|
1178
|
+
found = next(
|
|
1179
|
+
found
|
|
1180
|
+
for found in DETACHED_CROSSREF.finditer(content, start)
|
|
1181
|
+
if found["gap"] != ""
|
|
1182
|
+
)
|
|
1183
|
+
if found["gap"] is None:
|
|
1184
|
+
span = found.span("marker")
|
|
1185
|
+
joined = re.sub(r"\s+", " ", found["marker"])
|
|
1186
|
+
else:
|
|
1187
|
+
span = found.span("gap")
|
|
1188
|
+
joined = ""
|
|
1189
|
+
before = content[found.start() : found.end()]
|
|
1190
|
+
updated = content[: span[0]] + joined + content[span[1] :]
|
|
1191
|
+
after = updated[
|
|
1192
|
+
found.start() : span[0] + len(joined) + found.end() - span[1]
|
|
1193
|
+
]
|
|
1194
|
+
return (
|
|
1195
|
+
updated,
|
|
1196
|
+
before,
|
|
1197
|
+
after,
|
|
1198
|
+
"A crossref marker was separated from its equation or figure. "
|
|
1199
|
+
"I joined it back on.",
|
|
1200
|
+
)
|
|
1201
|
+
# }}}
|
|
1104
1202
|
old_line = lines[index]
|
|
1105
1203
|
ending = ""
|
|
1106
1204
|
if old_line.endswith("\r\n"):
|
|
@@ -1123,7 +1221,33 @@ def apply_markdown_issue_fix(content, line_number, message):
|
|
|
1123
1221
|
else:
|
|
1124
1222
|
prefix = re.match(r"^[ \t]*", old_line).group()
|
|
1125
1223
|
# }}}
|
|
1126
|
-
if message.startswith("
|
|
1224
|
+
if message.startswith("trailing space:"):
|
|
1225
|
+
lines[index] = old_line[:-1] + ending
|
|
1226
|
+
return (
|
|
1227
|
+
"".join(lines),
|
|
1228
|
+
old_line,
|
|
1229
|
+
old_line[:-1],
|
|
1230
|
+
"A line ended with a single stray space. I removed it.",
|
|
1231
|
+
)
|
|
1232
|
+
if message.startswith("line over "):
|
|
1233
|
+
# {{{ break at the last word boundary that fits the limit
|
|
1234
|
+
limit = int(re.match(r"line over (\d+)", message)[1])
|
|
1235
|
+
gaps = [
|
|
1236
|
+
found.end()
|
|
1237
|
+
for found in WORD.finditer(old_line)
|
|
1238
|
+
if container.end() < found.end() < len(old_line.rstrip())
|
|
1239
|
+
]
|
|
1240
|
+
fitting = [gap for gap in gaps if gap <= limit]
|
|
1241
|
+
# when even the first word is too long, break right after it
|
|
1242
|
+
split_at = fitting[-1] if fitting else gaps[0]
|
|
1243
|
+
first = old_line[:split_at].rstrip()
|
|
1244
|
+
second = old_line[split_at:].lstrip(" \t")
|
|
1245
|
+
reason = (
|
|
1246
|
+
f"This line was longer than {limit} characters. "
|
|
1247
|
+
"I moved the last words onto a new line."
|
|
1248
|
+
)
|
|
1249
|
+
# }}}
|
|
1250
|
+
elif message.startswith("line too long:"):
|
|
1127
1251
|
match = re.search(r"after '(.+?)' \(before '(.+?)'\); move", message)
|
|
1128
1252
|
if match is None:
|
|
1129
1253
|
raise ValueError(
|
|
@@ -1164,26 +1288,19 @@ def apply_markdown_issue_fix(content, line_number, message):
|
|
|
1164
1288
|
else:
|
|
1165
1289
|
raise ValueError(f"No automatic fix is available for {message!r}")
|
|
1166
1290
|
|
|
1167
|
-
if not first.strip():
|
|
1291
|
+
if not first.strip() or not second.strip():
|
|
1168
1292
|
# a break here would insert a blank line, i.e. a paragraph break
|
|
1169
1293
|
raise ValueError(
|
|
1170
1294
|
f"Automatic fix would leave an empty line at {line_number}"
|
|
1171
1295
|
)
|
|
1172
|
-
|
|
1173
|
-
|
|
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
|
-
)
|
|
1296
|
+
lines[index] = first + ending + prefix + second + ending
|
|
1297
|
+
after = first + "\n" + prefix + second
|
|
1183
1298
|
return "".join(lines), old_line, after, reason
|
|
1184
1299
|
|
|
1185
1300
|
|
|
1186
|
-
def autofix_markdown_file(
|
|
1301
|
+
def autofix_markdown_file(
|
|
1302
|
+
filename, wrapnumber=55, punctuation_slop=20, max_line_length=None
|
|
1303
|
+
):
|
|
1187
1304
|
"""Apply safe source line breaks and report unfinished source spans."""
|
|
1188
1305
|
with open(filename, encoding="utf-8", newline="") as fp:
|
|
1189
1306
|
content = fp.read()
|
|
@@ -1192,7 +1309,10 @@ def autofix_markdown_file(filename, wrapnumber=55, punctuation_slop=20):
|
|
|
1192
1309
|
# issue at a time re-lints the whole file for every single fix.
|
|
1193
1310
|
for _ in range(100):
|
|
1194
1311
|
issues = markdown_lint_issues_from_text(
|
|
1195
|
-
content,
|
|
1312
|
+
content,
|
|
1313
|
+
wrapnumber=wrapnumber,
|
|
1314
|
+
punctuation_slop=punctuation_slop,
|
|
1315
|
+
max_line_length=max_line_length,
|
|
1196
1316
|
)
|
|
1197
1317
|
fixable = {}
|
|
1198
1318
|
for line_number, message in issues:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
import subprocess
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
@@ -150,15 +151,92 @@ def test_long_line_fix_splits_at_the_word_after_the_break():
|
|
|
150
151
|
assert fixed.startswith("for $30\\;\\text{s}$ ($3\\times$\nfor AOT)")
|
|
151
152
|
|
|
152
153
|
|
|
153
|
-
def
|
|
154
|
+
def test_single_trailing_space_is_flagged_and_removed(tmp_path):
|
|
155
|
+
# from RM_ESR: a sentence ending in a stray space
|
|
154
156
|
path = tmp_path / "source.md"
|
|
155
157
|
path.write_text("It melts near 238 K. \nIn contrast, it stays.\n")
|
|
156
158
|
|
|
159
|
+
issues = markdown_lint_issues_from_text(path.read_text(), wrapnumber=55)
|
|
157
160
|
autofix_markdown_file(path)
|
|
158
161
|
|
|
162
|
+
assert (1, "trailing space") in [
|
|
163
|
+
(line, message.split(":")[0]) for line, message in issues
|
|
164
|
+
]
|
|
159
165
|
fixed = path.read_text()
|
|
160
166
|
assert fixed.startswith("It melts near 238 K.\nIn contrast,")
|
|
161
|
-
assert "\n
|
|
167
|
+
assert " \n" not in fixed
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_double_trailing_space_hard_break_is_allowed():
|
|
171
|
+
source = "The first line ends here with a break \nand continues.\n"
|
|
172
|
+
|
|
173
|
+
assert markdown_lint_issues_from_text(source, wrapnumber=55) == []
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@pytest.mark.parametrize(
|
|
177
|
+
"detached",
|
|
178
|
+
[
|
|
179
|
+
# equation markers must sit directly on the closing $$
|
|
180
|
+
"Intro.\n$$\nx = y\n$$ {#eq:x}\nwhere this holds.\n",
|
|
181
|
+
"Intro.\n$$\nx = y\n$$\n{#eq:x}\nwhere this holds.\n",
|
|
182
|
+
# figure markers must sit directly on the closing ) (from eigenmode)
|
|
183
|
+
"\n"
|
|
184
|
+
'{#fig:ProbeMagFieldsDrawing width="4in"}\n',
|
|
185
|
+
# and must not be split across lines (from RM_ESR)
|
|
186
|
+
""
|
|
187
|
+
"{#fig:justSpectraCat16\nwidth=4in}\n",
|
|
188
|
+
],
|
|
189
|
+
)
|
|
190
|
+
def test_detached_crossref_marker_is_flagged_and_joined(tmp_path, detached):
|
|
191
|
+
path = tmp_path / "source.md"
|
|
192
|
+
path.write_text(detached)
|
|
193
|
+
|
|
194
|
+
issues = markdown_lint_issues_from_text(detached, wrapnumber=55)
|
|
195
|
+
report = autofix_markdown_file(path)
|
|
196
|
+
|
|
197
|
+
fixed = path.read_text()
|
|
198
|
+
assert [message.split(":")[0] for _, message in issues] == [
|
|
199
|
+
"detached crossref marker"
|
|
200
|
+
]
|
|
201
|
+
assert report["fixes"]
|
|
202
|
+
assert markdown_lint_issues_from_text(fixed, wrapnumber=55) == []
|
|
203
|
+
assert re.search(r"(\$\$|\))\{#(eq|fig):[^}\n]*\}", fixed)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def test_figure_crossref_marker_is_never_split_from_its_figure(tmp_path):
|
|
207
|
+
# from RM_ESR: a caption spanning lines, ending in a marker with
|
|
208
|
+
# attributes; wr and the linter must treat "...){#fig:... }" as one word
|
|
209
|
+
source = (
|
|
210
|
+
"\n"
|
|
214
|
+
"{#fig:HESimul width=5.5in}\n"
|
|
215
|
+
)
|
|
216
|
+
path = tmp_path / "source.md"
|
|
217
|
+
path.write_text(source)
|
|
218
|
+
|
|
219
|
+
wr(str(path), 55)
|
|
220
|
+
|
|
221
|
+
wrapped = path.read_text()
|
|
222
|
+
assert (
|
|
223
|
+
"(green)](Figures/HE_Easyspin_Simul_100mM.png)"
|
|
224
|
+
"{#fig:HESimul width=5.5in}\n"
|
|
225
|
+
) in wrapped
|
|
226
|
+
assert markdown_lint_issues_from_text(wrapped, wrapnumber=55) == []
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_wr_keeps_equation_marker_on_closing_dollars(tmp_path):
|
|
230
|
+
path = tmp_path / "source.md"
|
|
231
|
+
path.write_text(
|
|
232
|
+
"Intro.\n$$\nx = y\n$$ {#eq:tauFromLinewidth}\nwhere this holds.\n"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
wr(str(path), 55)
|
|
236
|
+
|
|
237
|
+
wrapped = path.read_text()
|
|
238
|
+
assert "\n$${#eq:tauFromLinewidth}\n" in wrapped
|
|
239
|
+
assert markdown_lint_issues_from_text(wrapped, wrapnumber=55) == []
|
|
162
240
|
|
|
163
241
|
|
|
164
242
|
def test_autofix_fixes_every_line_in_one_pass_from_the_end(tmp_path):
|
|
@@ -175,6 +253,51 @@ def test_autofix_fixes_every_line_in_one_pass_from_the_end(tmp_path):
|
|
|
175
253
|
assert markdown_lint_issues_from_text(fixed, wrapnumber=55) == []
|
|
176
254
|
|
|
177
255
|
|
|
256
|
+
def test_character_limit_ignores_lines_wr_would_break_sooner():
|
|
257
|
+
# from RM_ESR: 74 characters, which wr's width-55 rule would break
|
|
258
|
+
source = (
|
|
259
|
+
"temperatures not amenable to other methods, as in "
|
|
260
|
+
"[@Banerjee2009ESREviCoe].\n"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
assert markdown_lint_issues_from_text(source, 55) != []
|
|
264
|
+
assert markdown_lint_issues_from_text(source, max_line_length=79) == []
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@pytest.mark.parametrize("prefix", ["", "- ", "> "])
|
|
268
|
+
def test_character_limit_breaks_at_last_fitting_word(tmp_path, prefix):
|
|
269
|
+
# from RM_ESR: a sentence that grew past 79 characters
|
|
270
|
+
line = (
|
|
271
|
+
prefix + "This script determines the field positions and amplitudes "
|
|
272
|
+
"of the spectral lines for an entire variable-temperature "
|
|
273
|
+
"experiment.\n"
|
|
274
|
+
)
|
|
275
|
+
path = tmp_path / "source.md"
|
|
276
|
+
path.write_text(line)
|
|
277
|
+
|
|
278
|
+
issues = markdown_lint_issues_from_text(line, max_line_length=79)
|
|
279
|
+
report = autofix_markdown_file(path, max_line_length=79)
|
|
280
|
+
|
|
281
|
+
fixed = path.read_text()
|
|
282
|
+
assert [message.split(":")[0] for _, message in issues] == [
|
|
283
|
+
"line over 79 characters"
|
|
284
|
+
]
|
|
285
|
+
assert "79 characters" in report["fixes"][0]["reason"]
|
|
286
|
+
assert all(len(text) <= 79 for text in fixed.splitlines())
|
|
287
|
+
assert fixed.replace("\n> ", "\n").split() == line.split()
|
|
288
|
+
# the first line is filled as far as the limit allows
|
|
289
|
+
first = fixed.splitlines()[0]
|
|
290
|
+
next_word = line.split()[len(first.split())]
|
|
291
|
+
assert len(first) + 1 + len(next_word) > 79
|
|
292
|
+
assert markdown_lint_issues_from_text(fixed, max_line_length=79) == []
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def test_character_limit_skips_a_single_unbreakable_word():
|
|
296
|
+
source = "https://example.org/" + "x" * 90 + "\n"
|
|
297
|
+
|
|
298
|
+
assert markdown_lint_issues_from_text(source, max_line_length=79) == []
|
|
299
|
+
|
|
300
|
+
|
|
178
301
|
def test_unclosed_math_is_reported_after_fifty_lines():
|
|
179
302
|
short_math = "$$\n" + ("x = y\n" * 48)
|
|
180
303
|
long_math = "$$\n" + ("x = y\n" * 49)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydifftools-0.1.39 → pydifftools-0.1.41}/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.39 → pydifftools-0.1.41}/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.39 → pydifftools-0.1.41}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg
RENAMED
|
File without changes
|
{pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/subproject1/independent.qmd
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydifftools-0.1.39 → pydifftools-0.1.41}/example_notebook/project1/subproject1/test_include.qmd
RENAMED
|
File without changes
|
{pydifftools-0.1.39 → pydifftools-0.1.41}/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
|