Files
wehub-resource-sync 0ef5fcb1c5
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
CI / lint (push) Has been cancelled
CI / build-wheel (push) Has been cancelled
CI / build-wheel-windows (push) Has been cancelled
CI / prefetch-model (push) Has been cancelled
CI / test-dashboard-ui (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test-extras (push) Has been cancelled
CI / test-agno (push) Has been cancelled
CI / build (push) Has been cancelled
CI / workflow-validation (push) Has been cancelled
CI / docker-native-e2e (push) Has been cancelled
CI / windows-native-wrapper (push) Has been cancelled
CI / macos-native-wrapper (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / promote-latest (push) Has been cancelled
Init Native E2E / init-native (macos-latest, claude) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, codex) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, copilot) (push) Has been cancelled
Install Native E2E / install-native (macos-latest) (push) Has been cancelled
Wrap Native E2E / wrap-native (macos-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:03:20 +08:00

226 lines
7.5 KiB
Python

"""Tabular-text compressor: bridges CSV/TSV/markdown tables to SmartCrusher.
Raw tabular *text* (CSV/TSV files, markdown tables, fixed-width tables) has no
native compressor — it would otherwise fall through to plain-text Kompress,
ignoring its row/column structure. This module parses tabular text into a JSON
array of records and routes it through the existing, battle-tested
`SmartCrusher`, which already does lossless ``csv-schema`` compaction first and
lossy row-drop with reversible ``<<ccr:HASH>>`` markers as a fallback.
No new compression algorithm and no new CCR plumbing live here — only the
text→records bridge.
"""
from __future__ import annotations
import csv
import io
import json
import re
from dataclasses import dataclass
from .content_detector import ContentType, detect_content_type
# Mirrors content_detector's separator-cell pattern (e.g. ``| --- | :--: |``).
_MD_SEP_CELL = re.compile(r"^:?-{2,}:?$")
# ─── Public dataclasses (mirror SearchCompressor / LogCompressor surface) ────
@dataclass
class TabularCompressorConfig:
"""Configuration for tabular-text compression."""
# Pass-through to SmartCrusher's lossless renderer.
compaction_format: str = "csv-schema"
# Only keep SmartCrusher's output if it is strictly smaller than the
# original tabular text (already-compact CSV may not benefit losslessly).
min_savings_chars: int = 1
@dataclass
class TabularCompressionResult:
"""Result of tabular-text compression."""
compressed: str
original: str
was_modified: bool
fmt: str # "csv" | "markdown" | "fixed_width"
rows: int
columns: int
strategy: str = "tabular"
@property
def compression_ratio(self) -> float:
if not self.original:
return 0.0
return len(self.compressed) / len(self.original)
# ─── Parsers (text → headers + rows) ─────────────────────────────────────────
def parse_csv(content: str, delimiter: str = ",") -> tuple[list[str], list[list[str]]]:
"""Parse delimited text via the stdlib csv reader."""
reader = csv.reader(io.StringIO(content), delimiter=delimiter)
parsed = [row for row in reader if any(cell.strip() for cell in row)]
if not parsed:
return [], []
headers = [h.strip() for h in parsed[0]]
return headers, parsed[1:]
def parse_markdown_table(content: str) -> tuple[list[str], list[list[str]]]:
"""Parse a markdown table, dropping the ``|---|`` separator row."""
def split_row(row: str) -> list[str]:
return [c.strip() for c in row.strip().strip("|").split("|")]
def is_separator(row: str) -> bool:
cells = [c for c in split_row(row) if c]
return len(cells) >= 2 and all(_MD_SEP_CELL.match(c) for c in cells)
lines = [ln for ln in content.split("\n") if ln.strip() and "|" in ln]
if len(lines) < 2:
return [], []
headers = split_row(lines[0])
rows = [split_row(ln) for ln in lines[1:] if not is_separator(ln)]
return headers, rows
def parse_fixed_width(content: str) -> tuple[list[str], list[list[str]]]:
"""Parse whitespace-aligned columns (best-effort, ≥ 2 spaces as a gap)."""
lines = [ln for ln in content.split("\n") if ln.strip()]
if len(lines) < 2:
return [], []
splitter = re.compile(r"\s{2,}")
headers = splitter.split(lines[0].strip())
rows = [splitter.split(ln.strip()) for ln in lines[1:]]
return headers, rows
def to_records(headers: list[str], rows: list[list[str]]) -> list[dict[str, str]]:
"""Zip headers with each row into dicts, padding/truncating to width."""
if not headers:
return []
width = len(headers)
records: list[dict[str, str]] = []
for row in rows:
padded = (row + [""] * width)[:width]
records.append({headers[i]: padded[i] for i in range(width)})
return records
def parse_tabular(
content: str,
) -> tuple[list[str], list[list[str]], str] | None:
"""Detect the tabular format and parse to (headers, rows, fmt).
Returns ``None`` if the content is not tabular.
"""
detection = detect_content_type(content)
if detection.content_type is not ContentType.TABULAR:
return None
fmt = detection.metadata.get("format", "csv")
if fmt == "markdown":
headers, rows = parse_markdown_table(content)
elif fmt == "fixed_width":
headers, rows = parse_fixed_width(content)
else:
delimiter = detection.metadata.get("delimiter", ",")
headers, rows = parse_csv(content, delimiter)
if not headers or not rows:
return None
# Ragged tables (rows whose cell count differs from the header count)
# can't be zipped into records without shifting values under the wrong
# column — a compressed table must never state facts the original
# didn't (#1652). Treat them as non-tabular and pass through verbatim.
width = len(headers)
if any(len(row) != width for row in rows):
return None
return headers, rows, fmt
# ─── Compressor (text → records → SmartCrusher) ──────────────────────────────
class TabularCompressor:
"""Compresses tabular text by bridging it through SmartCrusher.
Public surface mirrors the other content-type compressors so the router
and tests treat it uniformly.
"""
def __init__(self, config: TabularCompressorConfig | None = None) -> None:
self.config = config or TabularCompressorConfig()
def compress(
self,
content: str,
context: str = "",
bias: float = 1.0,
) -> TabularCompressionResult:
parsed = parse_tabular(content)
if parsed is None:
return TabularCompressionResult(
compressed=content,
original=content,
was_modified=False,
fmt="unknown",
rows=0,
columns=0,
)
headers, rows, fmt = parsed
records = to_records(headers, rows)
json_str = json.dumps(records, ensure_ascii=False)
# Lazy import keeps the Rust dependency off the import path until a
# tabular payload actually arrives.
from .smart_crusher import SmartCrusher
crusher = SmartCrusher(
with_compaction=True,
compaction_format=self.config.compaction_format,
)
result = crusher.crush(json_str, context, bias)
# SmartCrusher compressed the JSON form; compare its output against the
# original *tabular text*. Already-compact CSV may not beat its own
# source, so only adopt the result when it genuinely saves bytes.
savings = len(content) - len(result.compressed)
if not result.was_modified or savings < self.config.min_savings_chars:
return TabularCompressionResult(
compressed=content,
original=content,
was_modified=False,
fmt=fmt,
rows=len(rows),
columns=len(headers),
)
return TabularCompressionResult(
compressed=result.compressed,
original=content,
was_modified=True,
fmt=fmt,
rows=len(rows),
columns=len(headers),
strategy=result.strategy or "tabular",
)
__all__ = [
"TabularCompressor",
"TabularCompressorConfig",
"TabularCompressionResult",
"parse_csv",
"parse_markdown_table",
"parse_fixed_width",
"parse_tabular",
"to_records",
]