texdiff 0.2.1__tar.gz → 0.2.2__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 (40) hide show
  1. {texdiff-0.2.1/src/texdiff.egg-info → texdiff-0.2.2}/PKG-INFO +1 -1
  2. {texdiff-0.2.1 → texdiff-0.2.2}/pyproject.toml +1 -1
  3. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/__init__.py +1 -1
  4. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/align.py +8 -5
  5. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/emit.py +52 -1
  6. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/tables.py +20 -12
  7. {texdiff-0.2.1 → texdiff-0.2.2/src/texdiff.egg-info}/PKG-INFO +1 -1
  8. {texdiff-0.2.1 → texdiff-0.2.2}/LICENSE +0 -0
  9. {texdiff-0.2.1 → texdiff-0.2.2}/README.md +0 -0
  10. {texdiff-0.2.1 → texdiff-0.2.2}/setup.cfg +0 -0
  11. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/api.py +0 -0
  12. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/check.py +0 -0
  13. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/cli.py +0 -0
  14. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/flatten.py +0 -0
  15. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/newlines.py +0 -0
  16. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/nodes.py +0 -0
  17. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/oldlines.py +0 -0
  18. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/parse.py +0 -0
  19. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/preamble.py +0 -0
  20. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff/textdiff.py +0 -0
  21. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff.egg-info/SOURCES.txt +0 -0
  22. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff.egg-info/dependency_links.txt +0 -0
  23. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff.egg-info/entry_points.txt +0 -0
  24. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff.egg-info/requires.txt +0 -0
  25. {texdiff-0.2.1 → texdiff-0.2.2}/src/texdiff.egg-info/top_level.txt +0 -0
  26. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_align.py +0 -0
  27. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_alignment.py +0 -0
  28. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_api.py +0 -0
  29. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_check.py +0 -0
  30. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_cli.py +0 -0
  31. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_corpus.py +0 -0
  32. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_emit.py +0 -0
  33. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_flatten.py +0 -0
  34. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_parse.py +0 -0
  35. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_preamble.py +0 -0
  36. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_table_head_order.py +0 -0
  37. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_tables.py +0 -0
  38. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_tables_render.py +0 -0
  39. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_textdiff.py +0 -0
  40. {texdiff-0.2.1 → texdiff-0.2.2}/tests/test_verbatim.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: texdiff
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: AST-driven semantic diff for LaTeX documents
5
5
  Author: GoBobr
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "texdiff"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "AST-driven semantic diff for LaTeX documents"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -42,4 +42,4 @@ __all__ = [
42
42
  "word_diff",
43
43
  ]
44
44
 
45
- __version__ = "0.1.0"
45
+ __version__ = "0.2.2"
@@ -834,9 +834,13 @@ def _table_keyed_pair_rescue(edits: "list[Edit]", _pair) -> "list[Edit]":
834
834
  by key with a LATER insert's table rows, convert the pair into a
835
835
  Modify so the emit layer's inline row markup applies.
836
836
 
837
- Row keys compare by last path component: HDF-EOS grids get
838
- renamed (NPP_Grid_IMG_2D -> VIIRS_Grid_IMG_2D) without touching
839
- the data fields.
837
+ Row keys compare by the FULL first cell: an HDF-EOS grid
838
+ rename (NPP_Grid_IMG_2D -> VIIRS_Grid_IMG_2D) changes every
839
+ row's path prefix, and the resulting Modify would render as two
840
+ wholesale blocks (all rows struck, then all re-added) inside
841
+ one table - worse than leaving the pair as a clean whole-table
842
+ retire + reintroduction. Regenerated tables keep their row
843
+ keys; only wholesale renames are left alone.
840
844
  """
841
845
  import re
842
846
 
@@ -845,8 +849,7 @@ def _table_keyed_pair_rescue(edits: "list[Edit]", _pair) -> "list[Edit]":
845
849
  return set()
846
850
  keys = set()
847
851
  for m in re.finditer(r"^\s*([^&%\n]+?)\s*&", node.text or "", re.M):
848
- key = m.group(1).strip().replace("\\_", "_")
849
- keys.add(key.rsplit("/", 1)[-1])
852
+ keys.add(m.group(1).strip().replace("\\_", "_"))
850
853
  return keys
851
854
 
852
855
  dels = [
@@ -162,7 +162,9 @@ def render(edits: list[Edit], markup: LatexdiffMarkup = LatexdiffMarkup()) -> st
162
162
  else:
163
163
  out.append(_wrap_node(edit.old, markup, added=False))
164
164
  out.append(_wrap_node(edit.new, markup, added=True))
165
- return _normalize_endmark_colours("".join(out))
165
+ return _collapse_repeated_hlines(
166
+ _normalize_endmark_colours("".join(out))
167
+ )
166
168
 
167
169
 
168
170
  # inline end-marker tokens of longtable HEAD material; NOT preceded
@@ -225,6 +227,55 @@ def _normalize_endmark_colours(text: str) -> str:
225
227
  return _ENDMARK_COLOR_RE.sub(_insert_reset, text)
226
228
 
227
229
 
230
+ # invisible-to-the-typesetter material that can sit between two
231
+ # \hline rules: blank lines, %DIFDELCMD-style comments and the
232
+ # no-op FL markers of flush-left diff regions
233
+ _INVISIBLE_BETWEEN_RULES = (
234
+ r"(?:[ \t]*%[^\n]*\n?|[ \t]*\n|"
235
+ r"\\DIF(?:add|del)(?:begin|end)FL\n?|\\DIF(?:add|del)(?:begin|end)\n?)*"
236
+ )
237
+ # two consecutive \hline rules separated only by invisible material
238
+ # (a deleted row's tail rule + the skeleton rule of the material
239
+ # that follows): both draw the SAME grid line - longtable stacks
240
+ # them with zero offset and the boundary prints twice as thick.
241
+ # The pair is CONSUMED and replaced by a single rule (a lookahead
242
+ # replacement would be a no-op: group 1 is identical to the match).
243
+ # Iteration in _collapse_repeated_hlines handles longer runs.
244
+ _REPEATED_HLINE_RE = re.compile(
245
+ r"\\hline" + _INVISIBLE_BETWEEN_RULES + r"[ \t]*\\hline"
246
+ )
247
+ # same-line adjacency: rules glued to one line by the row split
248
+ # ("\hline\hline") or separated only by spaces/tabs draw on top of
249
+ # each other just the same
250
+ _REPEATED_HLINE_INLINE_RE = re.compile(r"\\hline[ \t]*\\hline")
251
+
252
+
253
+ def _collapse_repeated_hlines(text: str) -> str:
254
+ """Merge grid rules that land on top of each other.
255
+
256
+ Row-region emitters keep skeleton ``\\hline``s on both sides of
257
+ their material: a retired (deleted) row region ends with its
258
+ trailing rule while the following region's lead already carries
259
+ one, and blank lines / ``%DIFDELCMD`` comments / no-op ``FL``
260
+ markers sit between them. Two rules with only invisible
261
+ material between them draw the same grid line - longtable
262
+ typesets them stacked at zero offset, printing a border twice
263
+ as thick as every other rule in the table.
264
+
265
+ One rule of each such pair is dropped. Visible material
266
+ (content, colour declarations) between two rules keeps both -
267
+ they delimit a real row.
268
+ """
269
+ if "\\hline" not in text:
270
+ return text
271
+ while True:
272
+ new = _REPEATED_HLINE_INLINE_RE.sub(r"\\hline", text)
273
+ new = _REPEATED_HLINE_RE.sub(r"\\hline", new)
274
+ if new == text:
275
+ return new
276
+ text = new
277
+
278
+
228
279
  def _hoist_retired_tables(edits: list[Edit]) -> list[Edit]:
229
280
  """Reorder ``Insert(new table) ... Delete(old table)`` pairs.
230
281
 
@@ -355,12 +355,27 @@ def rows_pair_by_key(old_text: str, new_text: str, min_frac: float = 0.5) -> boo
355
355
  generation changes type names, shapes and every attribute), so
356
356
  word similarity collapses and the pair looks mutually
357
357
  unrecognizable - while the logical row structure (one row per
358
- variable: ``lat``, ``lon``, ``crs``, ...) is intact. When at least
359
- ``min_frac`` of the smaller side's rows have a first cell that
360
- also appears as a first cell on the other side, the table is the
358
+ variable: ``lat``, ``lon``, ``crs``, ...) is intact. When at
359
+ least ``min_frac`` of the smaller side's rows have a first cell
360
+ that also appears as a first cell on the other side, the table is the
361
361
  SAME table with edited cells: inline row markup (red struck cell,
362
362
  blue replacement right after) reads far better than retiring the
363
363
  whole table and reintroducing it blue.
364
+
365
+ The FULL key must match - a whole-prefix rename (an HDF-EOS
366
+ grid renamed ``NPP_Grid_IMG_2D`` -> ``VIIRS_Grid_IMG_2D``)
367
+ changes every row's path without making the rows unrelated, but
368
+ the row-level machinery (_row_key_of / _segment_ids) keys on the
369
+ full first cell: with near-zero full-key overlap the pairing
370
+ never happens and the inline path degrades to two wholesale
371
+ blocks (all rows struck, then all rows re-added) in one table.
372
+ Such near-total renames read better as one clean whole-table
373
+ retire and one blue reintroduction, which the caller
374
+ (merge_tables / render_restructured) provides - so they are
375
+ rejected here even though their LAST path components still
376
+ match. Regenerated grouped tables keep ~75% full-key overlap
377
+ (the attribute keys repeat per group) and stay above the gate;
378
+ renamed-grid tables sit at ~25% or below.
364
379
  """
365
380
  lo = [
366
381
  _first_cell(r)
@@ -377,15 +392,8 @@ def rows_pair_by_key(old_text: str, new_text: str, min_frac: float = 0.5) -> boo
377
392
  # structural repeats (header rows repeated via \endhead) must not
378
393
  # count twice per side
379
394
  lo, ln = set(lo), set(ln)
380
- # grid renames (NPP_Grid_IMG_2D -> VIIRS_Grid_IMG_2D) change the
381
- # path prefix of every HDF-EOS field row without touching the
382
- # data field itself: rows also pair when their last path
383
- # component matches
384
- def _last(k: str) -> str:
385
- return k.rsplit("/", 1)[-1] if "/" in k else k
386
- lo_last, ln_last = {_last(k) for k in lo}, {_last(k) for k in ln}
387
- shared = len(lo_last & ln_last)
388
- return shared >= min_frac * min(len(lo_last), len(ln_last)) and shared >= 2
395
+ shared = len(lo & ln)
396
+ return shared >= min_frac * min(len(lo), len(ln)) and shared >= 2
389
397
 
390
398
 
391
399
  _SPEC_RE = re.compile(r"\\begin\{longtable\*?\}\s*\{")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: texdiff
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: AST-driven semantic diff for LaTeX documents
5
5
  Author: GoBobr
6
6
  License: MIT License
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