pyDiffTools 0.1.55__tar.gz → 0.1.56__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.55/pyDiffTools.egg-info → pydifftools-0.1.56}/PKG-INFO +1 -1
- {pydifftools-0.1.55 → pydifftools-0.1.56/pyDiffTools.egg-info}/PKG-INFO +1 -1
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pyDiffTools.egg-info/SOURCES.txt +1 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/flowchart/source_jump.js +12 -3
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/line_alignment.py +54 -27
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/notebook/fast_build.py +115 -5
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pyproject.toml +1 -1
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_forward_search.py +20 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_line_alignment.py +55 -0
- pydifftools-0.1.56/tests/test_qt_lifecycle.py +51 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/LICENSE.md +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/MANIFEST.in +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/README.rst +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/_quarto.yml +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/1a724af72b16f5a9e607e12b1c721645/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/1b28fc9daac9081847e5161b2c546f8a/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/231f64eee282fa225d1104935cf80a24/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/26e56f6b0ff54851a45145157f2f0dc4/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/311fabd7029ffd050d056e2f316eb50f/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/57da2021e5b156ac3adf01398201c723/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/62b24ea7da75011d92b0f8924faa208d/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/7f1b20d69d889514ab5d1cc92e3cb14f/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/86f74c8c54a87ff892d9b15dd714e8f0/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/88893b4234eac2945d9d6cb2e277f186/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/9a40046ada6f582ee34af00fbdbfb417/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/a1bf4d270d0641ff41faf1d7cce3439a/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/a3789f7d9585a781f2a1c60ce95ff10d/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/be46ecba858d39ad5f0c46902ddf1c02/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/d0cbc57a12f2ccce710a5afd04cc05e7/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/d3e12d320b14228f701231ae32ddd7dd/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/eb434f61555438d020a6970a5dbf9ee8/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/f6b0e73aa7fa029134665d4dde57e096/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/f719a6e4ff09873cb0ffb06ec9d232f9/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/executed/feeee244a7ce3d60e1a227eb604df823/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/example.qmd +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/example.tex +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/index.qmd +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/subproject1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/subproject1/.jupyter_cache/executed/ca90e4df5f4f0583df6554156a68dc7f/base.ipynb +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/subproject1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/subproject1/independent.qmd +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/subproject1/index.qmd +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/subproject1/tasks.qmd +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/subproject1/test_include.qmd +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/subproject1/tryforerror.qmd +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/test_include.qmd +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pyDiffTools.egg-info/dependency_links.txt +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pyDiffTools.egg-info/entry_points.txt +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pyDiffTools.egg-info/requires.txt +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pyDiffTools.egg-info/top_level.txt +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/__init__.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/bibliography.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/browser_lifecycle.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/check_numbers.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/citation_dialog.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/command_line.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/command_registry.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/comment_filter_history.json +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/comment_functions.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/comment_migration.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/comment_tags.lua +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/comment_tags_margin.lua +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/comment_tags_no_comments.lua +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/comment_toggle.js +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/comments.css +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/comments_author_colors.css +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/completions/git-mergein +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/continuous.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/copy_files.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/diff-doc.js +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/doc_contents.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/flowchart/__init__.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/flowchart/comparison.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/flowchart/dot_to_yaml.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/flowchart/graph.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/flowchart/svg-pan-zoom.min.js +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/flowchart/watch_graph.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/flowchart/wgrph_view.js +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/forward_search.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/git_aliases.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/git_gd.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/git_gd_history.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/git_gd_image.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/git_gd_qt.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/html_comments.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/html_uncomments.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/log_example.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/match_spaces.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/notebook/__init__.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/notebook/display.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/notebook/tex_to_qmd.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/onewordify.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/onewordify_undo.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/outline.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/pdf_diff.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/rearrange_tex.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/searchacro.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/separate_comments.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/source_jump.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/split_conflict.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/unseparate_comments.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/update_check.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/wrap_sentences.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/wrapping_options.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/xml2xlsx.vbs +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/pydifftools/zotero.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/setup.cfg +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_bibliography.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_browser_lifecycle.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_check_single_use.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_command_line_help.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_comment_filters.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_comment_migration.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_continuous_shutdown.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_git_gd_history.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_git_gd_image.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_git_gd_qt.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_git_mergein.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_git_pdf_diff.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_pdf_diff.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_rrng.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_source_jump.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_tex_to_qmd.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_update_check.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_wrap_check.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_wrapping.py +0 -0
- {pydifftools-0.1.55 → pydifftools-0.1.56}/tests/test_zotero.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
// Add a small local-only source jump action to cpb and flowchart previews.
|
|
1
|
+
// Add a small local-only source jump action to cpb, qmdb and flowchart previews.
|
|
2
2
|
(function () {
|
|
3
3
|
const endpoint = window.pydifftSourceJumpEndpoint;
|
|
4
4
|
if (!endpoint) {
|
|
5
5
|
return;
|
|
6
6
|
}
|
|
7
7
|
const excludedSourceElements =
|
|
8
|
-
'.math,.MathJax,mjx-container,.katex,math,' +
|
|
8
|
+
'.pydifft-obs-prefix,.math,.MathJax,mjx-container,.katex,math,' +
|
|
9
9
|
'script[type^="math/tex"],' +
|
|
10
10
|
'a[href*="#fig:"],a[href*="#fig-"],a[href*="#fig%3A"]';
|
|
11
11
|
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
let phrase = '';
|
|
30
|
+
let source = null;
|
|
30
31
|
|
|
31
32
|
// Preserve HTML tag boundaries without adding spaces to words such as
|
|
32
33
|
// H<sub>2</sub>O. Python treats this reserved marker as a formatting gap.
|
|
@@ -43,6 +44,9 @@
|
|
|
43
44
|
|
|
44
45
|
function showMenu(event, text) {
|
|
45
46
|
phrase = normalizeSourceText(text || '');
|
|
47
|
+
const sourceElement = event.target && event.target.closest
|
|
48
|
+
? event.target.closest('[data-pydifft-source]') : null;
|
|
49
|
+
source = sourceElement ? sourceElement.dataset.pydifftSource : null;
|
|
46
50
|
if (!phrase) {
|
|
47
51
|
hideMenu();
|
|
48
52
|
return;
|
|
@@ -102,6 +106,11 @@
|
|
|
102
106
|
text += ' ';
|
|
103
107
|
return;
|
|
104
108
|
}
|
|
109
|
+
if (node.nodeType === Node.ELEMENT_NODE &&
|
|
110
|
+
(node.tagName === 'BR' || node.tagName === 'IMG')) {
|
|
111
|
+
text += ' ';
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
105
114
|
const tagBoundary = node !== block && node.nodeType === Node.ELEMENT_NODE;
|
|
106
115
|
if (tagBoundary) {
|
|
107
116
|
text += '\uE000';
|
|
@@ -144,7 +153,7 @@
|
|
|
144
153
|
fetch(endpoint, {
|
|
145
154
|
method: 'POST',
|
|
146
155
|
mode: 'no-cors',
|
|
147
|
-
body: phrase,
|
|
156
|
+
body: source ? JSON.stringify({source: source, phrase: phrase}) : phrase,
|
|
148
157
|
}).catch(function () {
|
|
149
158
|
// The preview may be closing while the local editor request is sent.
|
|
150
159
|
});
|
|
@@ -463,6 +463,58 @@ def align_line_breaks(
|
|
|
463
463
|
old_rows = {}
|
|
464
464
|
for number, (row, _, _) in enumerate(old_tokens):
|
|
465
465
|
old_rows.setdefault(row, [number, number])[1] = number
|
|
466
|
+
for row, (first, last) in old_rows.items():
|
|
467
|
+
if (
|
|
468
|
+
all(number in reverse for number in range(first, last + 1))
|
|
469
|
+
and reverse[last] - reverse[first] == last - first
|
|
470
|
+
):
|
|
471
|
+
spans.append((
|
|
472
|
+
reverse[first], reverse[last],
|
|
473
|
+
reference_lines[i1 + row].rstrip(),
|
|
474
|
+
))
|
|
475
|
+
inherited = {(first, last) for first, last, _ in spans}
|
|
476
|
+
token_gaps = [
|
|
477
|
+
before[left[2]:right[1]]
|
|
478
|
+
for left, right in zip(tokens, tokens[1:])
|
|
479
|
+
]
|
|
480
|
+
|
|
481
|
+
def creates_short_fragment(proposed, changed):
|
|
482
|
+
# Inspect only rows beside newly inserted breaks. Token
|
|
483
|
+
# ranges identify the actual inherited line, even when its
|
|
484
|
+
# text occurs elsewhere. Stop counting after four words.
|
|
485
|
+
for number in changed:
|
|
486
|
+
if (
|
|
487
|
+
"\n" not in proposed[number]
|
|
488
|
+
or "\n" in whitespace[number]
|
|
489
|
+
):
|
|
490
|
+
continue
|
|
491
|
+
for direction, first in ((-1, number), (1, number + 1)):
|
|
492
|
+
last = first
|
|
493
|
+
words = len(before[
|
|
494
|
+
tokens[first][1]:tokens[first][2]
|
|
495
|
+
].split())
|
|
496
|
+
while words <= 3 and (
|
|
497
|
+
(direction < 0 and first > 0)
|
|
498
|
+
or (direction > 0 and last < len(tokens) - 1)
|
|
499
|
+
):
|
|
500
|
+
gap = first - 1 if direction < 0 else last
|
|
501
|
+
raw = proposed.get(gap, token_gaps[gap])
|
|
502
|
+
if "\n" in raw:
|
|
503
|
+
break
|
|
504
|
+
adjacent = first - 1 if direction < 0 else last + 1
|
|
505
|
+
words += len(before[
|
|
506
|
+
tokens[adjacent][1]:tokens[adjacent][2]
|
|
507
|
+
].split()) - (not raw)
|
|
508
|
+
if words > 3:
|
|
509
|
+
break
|
|
510
|
+
if direction < 0:
|
|
511
|
+
first -= 1
|
|
512
|
+
else:
|
|
513
|
+
last += 1
|
|
514
|
+
if words <= 3 and (first, last) not in inherited:
|
|
515
|
+
return True
|
|
516
|
+
return False
|
|
517
|
+
|
|
466
518
|
for number in gaps:
|
|
467
519
|
old_number = matches[hunk].get(number)
|
|
468
520
|
if old_number is None and number + 1 in matches[hunk]:
|
|
@@ -481,16 +533,9 @@ def align_line_breaks(
|
|
|
481
533
|
if "\n" in raw and "\n" not in gaps[number] and (
|
|
482
534
|
not inside_region
|
|
483
535
|
):
|
|
484
|
-
original_rows = set(render(whitespace).splitlines())
|
|
485
536
|
proposed = dict(whitespace)
|
|
486
537
|
proposed[number] = raw
|
|
487
|
-
|
|
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
|
-
):
|
|
538
|
+
if creates_short_fragment(proposed, (number,)):
|
|
494
539
|
continue
|
|
495
540
|
if inside_region or (
|
|
496
541
|
current_layout[3][j1 + tokens[number][0]]
|
|
@@ -502,21 +547,8 @@ def align_line_breaks(
|
|
|
502
547
|
ending + continuation[tokens[number][0]]
|
|
503
548
|
if "\n" in raw else raw
|
|
504
549
|
)
|
|
505
|
-
for row, (first, last) in old_rows.items():
|
|
506
|
-
if (
|
|
507
|
-
all(number in reverse for number in range(first, last + 1))
|
|
508
|
-
and reverse[last] - reverse[first] == last - first
|
|
509
|
-
):
|
|
510
|
-
spans.append(
|
|
511
|
-
(
|
|
512
|
-
reverse[first],
|
|
513
|
-
reverse[last],
|
|
514
|
-
reference_lines[i1 + row].rstrip(),
|
|
515
|
-
)
|
|
516
|
-
)
|
|
517
550
|
# }}}
|
|
518
551
|
# {{{ additional breaks preserve a line without tiny new fragments
|
|
519
|
-
inherited = {line.rstrip() for line in reference_lines}
|
|
520
552
|
for first, last, old_line in spans:
|
|
521
553
|
needed = range(
|
|
522
554
|
max(0, first - 1), min(len(tokens) - 1, last + 1)
|
|
@@ -550,12 +582,7 @@ def align_line_breaks(
|
|
|
550
582
|
continue
|
|
551
583
|
if old_line not in {line.rstrip() for line in proposed_rows}:
|
|
552
584
|
continue
|
|
553
|
-
if
|
|
554
|
-
line not in original_rows
|
|
555
|
-
and line.rstrip() not in inherited
|
|
556
|
-
and 0 < len(line.split()) <= 3
|
|
557
|
-
for line in proposed_rows
|
|
558
|
-
):
|
|
585
|
+
if creates_short_fragment(proposed, needed):
|
|
559
586
|
continue
|
|
560
587
|
whitespace = proposed
|
|
561
588
|
# }}}
|
|
@@ -21,6 +21,7 @@ import shutil
|
|
|
21
21
|
import queue
|
|
22
22
|
import yaml
|
|
23
23
|
from pydifftools.command_registry import register_command
|
|
24
|
+
from pydifftools.source_jump import jump_to_source, MAX_SOURCE_JUMP_BYTES
|
|
24
25
|
from pydifftools.browser_lifecycle import (
|
|
25
26
|
browser_window_is_alive,
|
|
26
27
|
close_browser_window,
|
|
@@ -61,6 +62,7 @@ CODE_DISPLAY_MODES = {
|
|
|
61
62
|
}
|
|
62
63
|
# {{{ Rebuild endpoint URL (changeable parameters)
|
|
63
64
|
RERUN_ENDPOINT = "/__pydifft/rerun"
|
|
65
|
+
SOURCE_JUMP_ENDPOINT = "/__pydifft/jump"
|
|
64
66
|
# }}}
|
|
65
67
|
# {{{ Right-click "SOURCE" to rebuild a notebook through the qmdb server
|
|
66
68
|
RERUN_SCRIPT = """
|
|
@@ -80,6 +82,7 @@ RERUN_SCRIPT = """
|
|
|
80
82
|
return;
|
|
81
83
|
}
|
|
82
84
|
e.preventDefault();
|
|
85
|
+
e.stopImmediatePropagation();
|
|
83
86
|
menu = document.createElement("div");
|
|
84
87
|
menu.textContent = "Rebuild";
|
|
85
88
|
menu.style.cssText = "position:fixed;z-index:2147483647;"
|
|
@@ -2210,11 +2213,32 @@ def _ensure_rerun_script(root):
|
|
|
2210
2213
|
head.append(script)
|
|
2211
2214
|
|
|
2212
2215
|
|
|
2216
|
+
def _normalize_observation_html(text):
|
|
2217
|
+
"""Keep raw observation boundaries intact across paragraphs and figures."""
|
|
2218
|
+
# HTML parsers close unknown inline tags at </p>. A block-capable
|
|
2219
|
+
# container keeps the closing tag bounding all of the observed prose.
|
|
2220
|
+
text = re.sub(
|
|
2221
|
+
r"<obs\b([^>]*)>",
|
|
2222
|
+
r'<div class="pydifft-observation"\1>',
|
|
2223
|
+
text,
|
|
2224
|
+
flags=re.IGNORECASE,
|
|
2225
|
+
)
|
|
2226
|
+
text = re.sub(r"</obs\s*>", "</div>", text, flags=re.IGNORECASE)
|
|
2227
|
+
return text
|
|
2228
|
+
|
|
2229
|
+
|
|
2213
2230
|
# also used by: tests/notebook/test_fast_build.py, which calls or
|
|
2214
2231
|
# monkeypatches this notebook-builder entry point.
|
|
2215
2232
|
def postprocess_html(html_path: Path, include_root: Path, resource_root: Path):
|
|
2216
2233
|
"""Replace placeholder nodes with referenced HTML bodies."""
|
|
2217
|
-
root = lxml_html.fromstring(
|
|
2234
|
+
root = lxml_html.fromstring(
|
|
2235
|
+
_normalize_observation_html(html_path.read_text())
|
|
2236
|
+
)
|
|
2237
|
+
# Mark fragments before expansion to retain nested include source paths.
|
|
2238
|
+
source = Path(os.path.relpath(html_path, DISPLAY_DIR)).with_suffix(".qmd")
|
|
2239
|
+
for node in root.xpath("//body") or [root]:
|
|
2240
|
+
if not node.get("data-pydifft-source"):
|
|
2241
|
+
node.set("data-pydifft-source", source.as_posix())
|
|
2218
2242
|
# keep processing until no include placeholders remain so nested includes
|
|
2219
2243
|
# are fully expanded in the served HTML
|
|
2220
2244
|
while True:
|
|
@@ -2229,7 +2253,9 @@ def postprocess_html(html_path: Path, include_root: Path, resource_root: Path):
|
|
|
2229
2253
|
target = (include_root / target_rel).resolve()
|
|
2230
2254
|
if target.exists():
|
|
2231
2255
|
frag_text = target.read_text()
|
|
2232
|
-
frag = lxml_html.fromstring(
|
|
2256
|
+
frag = lxml_html.fromstring(
|
|
2257
|
+
_normalize_observation_html(frag_text)
|
|
2258
|
+
)
|
|
2233
2259
|
body = frag.xpath("body")
|
|
2234
2260
|
if body:
|
|
2235
2261
|
elems = list(body[0])
|
|
@@ -2244,6 +2270,12 @@ def postprocess_html(html_path: Path, include_root: Path, resource_root: Path):
|
|
|
2244
2270
|
start_c = lxml_html.HtmlComment(f"BEGIN include {target_rel}")
|
|
2245
2271
|
parent.insert(idx, end_c)
|
|
2246
2272
|
for elem in reversed(elems):
|
|
2273
|
+
if isinstance(elem.tag, str):
|
|
2274
|
+
elem.set(
|
|
2275
|
+
"data-pydifft-source",
|
|
2276
|
+
elem.get("data-pydifft-source")
|
|
2277
|
+
or Path(target_rel).with_suffix(".qmd").as_posix(),
|
|
2278
|
+
)
|
|
2247
2279
|
parent.insert(idx, elem)
|
|
2248
2280
|
parent.insert(idx, start_c)
|
|
2249
2281
|
progress = True
|
|
@@ -2318,6 +2350,45 @@ def postprocess_html(html_path: Path, include_root: Path, resource_root: Path):
|
|
|
2318
2350
|
# Includes contribute their bodies, so their head scripts are lost.
|
|
2319
2351
|
# Install the handler once on the final page after expanding includes.
|
|
2320
2352
|
_ensure_rerun_script(root)
|
|
2353
|
+
# {{{ Style observations left raw when images split Pandoc inline lists
|
|
2354
|
+
for obs in root.xpath('//div[@class="pydifft-observation"]'):
|
|
2355
|
+
# Inline display preserves observations embedded in running prose.
|
|
2356
|
+
obs.set("style", obs.get("style", "") + ";display:inline;color:blue;")
|
|
2357
|
+
if not obs.xpath('./span[@class="pydifft-obs-prefix"]'):
|
|
2358
|
+
metadata = " ".join(filter(None, (
|
|
2359
|
+
obs.get("time") or obs.get("date"), obs.get("author")
|
|
2360
|
+
)))
|
|
2361
|
+
if metadata:
|
|
2362
|
+
prefix = lxml_html.Element("span")
|
|
2363
|
+
prefix.set("class", "pydifft-obs-prefix")
|
|
2364
|
+
prefix.set("style", "font-size:85%;color:green;")
|
|
2365
|
+
prefix.text = metadata + ":"
|
|
2366
|
+
prefix.tail = " " + (obs.text or "")
|
|
2367
|
+
obs.text = None
|
|
2368
|
+
obs.insert(0, prefix)
|
|
2369
|
+
# }}}
|
|
2370
|
+
# {{{ Reuse CPB's context menu on every final QMDB page
|
|
2371
|
+
head = root.find("head")
|
|
2372
|
+
if head is None:
|
|
2373
|
+
head = lxml_html.Element("head")
|
|
2374
|
+
root.insert(0, head)
|
|
2375
|
+
for old in root.xpath(
|
|
2376
|
+
'//script[@id="pydifft-source-jump-config" or '
|
|
2377
|
+
'@id="pydifft-source-jump"]'
|
|
2378
|
+
):
|
|
2379
|
+
old.getparent().remove(old)
|
|
2380
|
+
config = lxml_html.Element("script", id="pydifft-source-jump-config")
|
|
2381
|
+
config.text = (
|
|
2382
|
+
"window.pydifftSourceJumpEndpoint = "
|
|
2383
|
+
+ json.dumps(SOURCE_JUMP_ENDPOINT) + ";"
|
|
2384
|
+
)
|
|
2385
|
+
head.append(config)
|
|
2386
|
+
script = lxml_html.Element("script", id="pydifft-source-jump")
|
|
2387
|
+
script.text = (
|
|
2388
|
+
Path(__file__).parents[1] / "flowchart" / "source_jump.js"
|
|
2389
|
+
).read_text(encoding="utf-8")
|
|
2390
|
+
head.append(script)
|
|
2391
|
+
# }}}
|
|
2321
2392
|
html_path.write_text(lxml_html.tostring(root, encoding="unicode"))
|
|
2322
2393
|
|
|
2323
2394
|
|
|
@@ -2335,8 +2406,10 @@ def substitute_code_placeholders(
|
|
|
2335
2406
|
if code_display not in CODE_DISPLAY_MODES:
|
|
2336
2407
|
raise ValueError(f"unknown code display mode: {code_display}")
|
|
2337
2408
|
parser = lxml_html.HTMLParser(encoding="utf-8")
|
|
2338
|
-
|
|
2339
|
-
|
|
2409
|
+
root = lxml_html.document_fromstring(
|
|
2410
|
+
_normalize_observation_html(html_path.read_text()), parser=parser
|
|
2411
|
+
)
|
|
2412
|
+
tree = root.getroottree()
|
|
2340
2413
|
formatter = HtmlFormatter()
|
|
2341
2414
|
head = root.xpath("//head")
|
|
2342
2415
|
if head and not root.xpath('//style[@id="pygments-style"]'):
|
|
@@ -2379,7 +2452,9 @@ def substitute_code_placeholders(
|
|
|
2379
2452
|
frags = [details]
|
|
2380
2453
|
# }}}
|
|
2381
2454
|
if not missing_output and html:
|
|
2382
|
-
frags += lxml_html.fragments_fromstring(
|
|
2455
|
+
frags += lxml_html.fragments_fromstring(
|
|
2456
|
+
_normalize_observation_html(html)
|
|
2457
|
+
)
|
|
2383
2458
|
elif missing_output:
|
|
2384
2459
|
# Only show the placeholder when the notebook output entry is
|
|
2385
2460
|
# absent so executed cells that intentionally produce no output
|
|
@@ -2499,6 +2574,41 @@ def watch_and_serve(
|
|
|
2499
2574
|
super().__init__(*args, directory=str(DISPLAY_DIR), **kwargs)
|
|
2500
2575
|
|
|
2501
2576
|
def do_POST(self):
|
|
2577
|
+
if self.path == SOURCE_JUMP_ENDPOINT:
|
|
2578
|
+
# {{{ Dispatch the shared menu to the fragment's QMD source
|
|
2579
|
+
origin = self.headers.get("Origin")
|
|
2580
|
+
if origin and origin.split("://", 1)[-1] != self.headers.get(
|
|
2581
|
+
"Host"
|
|
2582
|
+
):
|
|
2583
|
+
self.send_error(403)
|
|
2584
|
+
return
|
|
2585
|
+
try:
|
|
2586
|
+
length = int(self.headers.get("Content-Length", "0"))
|
|
2587
|
+
if not 0 < length <= MAX_SOURCE_JUMP_BYTES:
|
|
2588
|
+
raise ValueError(length)
|
|
2589
|
+
request = json.loads(self.rfile.read(length))
|
|
2590
|
+
source = (PROJECT_ROOT / request["source"]).resolve()
|
|
2591
|
+
source.relative_to(PROJECT_ROOT.resolve())
|
|
2592
|
+
if source.suffix != ".qmd":
|
|
2593
|
+
raise ValueError("Expected a QMD source")
|
|
2594
|
+
phrase = request["phrase"]
|
|
2595
|
+
if not isinstance(phrase, str):
|
|
2596
|
+
raise ValueError("Expected source text")
|
|
2597
|
+
except (ValueError, KeyError, TypeError):
|
|
2598
|
+
self.send_error(400)
|
|
2599
|
+
return
|
|
2600
|
+
try:
|
|
2601
|
+
jump_to_source(source, phrase)
|
|
2602
|
+
except (
|
|
2603
|
+
OSError, subprocess.SubprocessError, ValueError
|
|
2604
|
+
) as exc:
|
|
2605
|
+
print(f"pydifft source jump failed: {exc}", flush=True)
|
|
2606
|
+
self.send_error(500)
|
|
2607
|
+
return
|
|
2608
|
+
self.send_response(204)
|
|
2609
|
+
self.end_headers()
|
|
2610
|
+
return
|
|
2611
|
+
# }}}
|
|
2502
2612
|
# {{{ Re-run the notebook behind a right-clicked SOURCE label
|
|
2503
2613
|
if self.path != RERUN_ENDPOINT:
|
|
2504
2614
|
self.send_error(404)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import io
|
|
2
3
|
import queue
|
|
3
4
|
import socket
|
|
@@ -380,6 +381,7 @@ def test_second_qmdb_takes_next_ports_and_accepts_rebuild(
|
|
|
380
381
|
build_calls = []
|
|
381
382
|
http_ports = []
|
|
382
383
|
handlers = []
|
|
384
|
+
jumps = []
|
|
383
385
|
urls = []
|
|
384
386
|
httpd = FakeHttpServer()
|
|
385
387
|
|
|
@@ -424,6 +426,20 @@ def test_second_qmdb_takes_next_ports_and_accepts_rebuild(
|
|
|
424
426
|
handler.do_POST()
|
|
425
427
|
assert responses == [202]
|
|
426
428
|
assert built.wait(timeout=2)
|
|
429
|
+
handler.path = fast_build.SOURCE_JUMP_ENDPOINT
|
|
430
|
+
for source, status in [
|
|
431
|
+
("doc.qmd", 204), ("../outside.qmd", 400),
|
|
432
|
+
("doc.html", 400),
|
|
433
|
+
]:
|
|
434
|
+
body = json.dumps({
|
|
435
|
+
"source": source, "phrase": "Source text."
|
|
436
|
+
}).encode()
|
|
437
|
+
handler.headers["Content-Length"] = str(len(body))
|
|
438
|
+
handler.rfile = io.BytesIO(body)
|
|
439
|
+
handler.send_error = responses.append
|
|
440
|
+
handler.do_POST()
|
|
441
|
+
assert responses[-1] == status
|
|
442
|
+
assert jumps == [(tmp_path / "doc.qmd", "Source text.")]
|
|
427
443
|
return False
|
|
428
444
|
|
|
429
445
|
class Observer:
|
|
@@ -445,6 +461,10 @@ def test_second_qmdb_takes_next_ports_and_accepts_rebuild(
|
|
|
445
461
|
classmethod(lambda cls, **_k: Machine()),
|
|
446
462
|
)
|
|
447
463
|
monkeypatch.setattr(fast_build, "load_rendered_files", lambda: [])
|
|
464
|
+
monkeypatch.setattr(
|
|
465
|
+
fast_build, "jump_to_source",
|
|
466
|
+
lambda source, phrase: jumps.append((source, phrase)),
|
|
467
|
+
)
|
|
448
468
|
monkeypatch.setattr(fast_build, "ThreadingHTTPServer", fake_http_server)
|
|
449
469
|
monkeypatch.setattr(fast_build, "_serve_forever", lambda _httpd: None)
|
|
450
470
|
monkeypatch.setattr(fast_build, "BrowserReloader", Reloader)
|
|
@@ -524,6 +524,61 @@ def test_optional_preservation_break_does_not_strand_a_tiny_fragment():
|
|
|
524
524
|
assert result["aligned"] == current
|
|
525
525
|
|
|
526
526
|
|
|
527
|
+
@pytest.mark.parametrize("comma", ["", ","])
|
|
528
|
+
@pytest.mark.parametrize("location", ["elsewhere", "same_hunk"])
|
|
529
|
+
def test_repeated_short_line_does_not_exempt_an_inserted_fragment(
|
|
530
|
+
comma, location,
|
|
531
|
+
):
|
|
532
|
+
citation = "(@fig:TEMPOOH_vs_TEMPOSO4(b))"
|
|
533
|
+
if location == "elsewhere":
|
|
534
|
+
old_prefix = new_prefix = "which\n\nUnchanged anchor.\n\n"
|
|
535
|
+
else:
|
|
536
|
+
old_prefix = "which\noriginal opening words\n"
|
|
537
|
+
new_prefix = "which changed opening words\n"
|
|
538
|
+
reference = (
|
|
539
|
+
old_prefix + citation + comma + "\nlikely arises from larger\n"
|
|
540
|
+
)
|
|
541
|
+
current = (
|
|
542
|
+
new_prefix + citation + ",\nwhich likely arises from larger\n"
|
|
543
|
+
)
|
|
544
|
+
results = align_line_breaks(reference, current, width=55)
|
|
545
|
+
for result in reversed(results):
|
|
546
|
+
current = (
|
|
547
|
+
current[:result["start"]] + result["aligned"]
|
|
548
|
+
+ current[result["stop"]:]
|
|
549
|
+
)
|
|
550
|
+
assert citation + ",\nwhich likely arises from larger\n" in current
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def test_matched_short_line_can_still_be_restored():
|
|
554
|
+
reference = (
|
|
555
|
+
"Former opening has four words\nwhich\n"
|
|
556
|
+
"following stable words remain here\n"
|
|
557
|
+
)
|
|
558
|
+
current = (
|
|
559
|
+
"Changed opening has four words which "
|
|
560
|
+
"following stable words remain here\n"
|
|
561
|
+
)
|
|
562
|
+
(result,) = align_line_breaks(reference, current)
|
|
563
|
+
assert result["aligned"] == reference.replace("Former", "Changed")
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
@pytest.mark.parametrize("fragment,restore", [
|
|
567
|
+
(r"$\alpha\beta\gamma$", False),
|
|
568
|
+
("link){#one two three four}", True),
|
|
569
|
+
])
|
|
570
|
+
def test_fragment_word_count_handles_math_and_attribute_tokens(
|
|
571
|
+
fragment, restore,
|
|
572
|
+
):
|
|
573
|
+
reference = "Former opening\nstable reference words\nformer closing.\n"
|
|
574
|
+
current = (
|
|
575
|
+
fragment
|
|
576
|
+
+ " stable reference words rewritten closing has four words.\n"
|
|
577
|
+
)
|
|
578
|
+
(result,) = align_line_breaks(reference, current)
|
|
579
|
+
assert ("\nstable reference words\n" in result["aligned"]) == restore
|
|
580
|
+
|
|
581
|
+
|
|
527
582
|
@pytest.mark.parametrize("count", [1, 2, 3, 4])
|
|
528
583
|
@pytest.mark.parametrize("side", ["before", "after"])
|
|
529
584
|
def test_optional_break_uses_word_count_for_long_fragments(count, side):
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_qt_windows_are_deleted_before_application_shutdown(tmp_path):
|
|
7
|
+
conftest = Path(__file__).with_name("conftest.py").read_text()
|
|
8
|
+
(tmp_path / "conftest.py").write_text(
|
|
9
|
+
conftest
|
|
10
|
+
+ """
|
|
11
|
+
|
|
12
|
+
def pytest_sessionfinish(session, exitstatus):
|
|
13
|
+
from PySide6.QtWidgets import QApplication
|
|
14
|
+
assert QApplication.instance() is None
|
|
15
|
+
"""
|
|
16
|
+
)
|
|
17
|
+
(tmp_path / "test_windows.py").write_text(
|
|
18
|
+
"""
|
|
19
|
+
from PySide6.QtWidgets import QApplication, QPushButton
|
|
20
|
+
from shiboken6 import isValid
|
|
21
|
+
|
|
22
|
+
retained_windows = []
|
|
23
|
+
retained_applications = []
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_closed_window():
|
|
27
|
+
application = QApplication.instance() or QApplication([])
|
|
28
|
+
retained_applications.append(application)
|
|
29
|
+
window = QPushButton("Close")
|
|
30
|
+
# Keep both a Python reference and a signal closure alive past the test.
|
|
31
|
+
window.clicked.connect(lambda: window.close())
|
|
32
|
+
window.close()
|
|
33
|
+
retained_windows.append(window)
|
|
34
|
+
assert isValid(application)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_previous_window_is_deleted():
|
|
38
|
+
assert not isValid(retained_windows[0])
|
|
39
|
+
assert QApplication.instance() is retained_applications[0]
|
|
40
|
+
assert isValid(retained_applications[0])
|
|
41
|
+
"""
|
|
42
|
+
)
|
|
43
|
+
completed = subprocess.run(
|
|
44
|
+
[sys.executable, "-m", "pytest", "-q", str(tmp_path)],
|
|
45
|
+
cwd=tmp_path,
|
|
46
|
+
capture_output=True,
|
|
47
|
+
text=True,
|
|
48
|
+
timeout=60,
|
|
49
|
+
)
|
|
50
|
+
assert completed.returncode == 0, completed.stdout + completed.stderr
|
|
51
|
+
assert "2 passed" in completed.stdout
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydifftools-0.1.55 → pydifftools-0.1.56}/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.55 → pydifftools-0.1.56}/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.55 → pydifftools-0.1.56}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg
RENAMED
|
File without changes
|
{pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/subproject1/independent.qmd
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydifftools-0.1.55 → pydifftools-0.1.56}/example_notebook/project1/subproject1/test_include.qmd
RENAMED
|
File without changes
|
{pydifftools-0.1.55 → pydifftools-0.1.56}/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
|