Conflict resolutions and semantic fixups: - utils.py / hermes_yaml.py: main widened ruamel's round-trip emitter so a long double-quoted scalar is never folded after an escaped backslash. pm-clean builds every rt emitter through hermes_yaml.roundtrip_yaml(), so the width lives there (ROUNDTRIP_YAML_WIDTH moves with it); xai_retirement imports it from hermes_yaml. - hermes_cli/banner.py: keep pm-clean's removal of the banner update check. Main's GIT_NO_LAZY_FETCH fix for it applies to its replacement, source_check: every read-only probe (source_git_env) now refuses promisor lazy fetches, and the partial-clone test targets that probe (red without the flag). - .github/workflows/tests.yml: keep setup-pm; main's uv pin bump does not apply. Main's WAL-capable SQLite gates are kept, run against $HERMES_PYTHON (the PM-pinned interpreter, SQLite 3.53.1). The e2e step takes main's --include-integration invocation. - apps/desktop: package.json has no build block here, so main's macOS locale-marker restore joins the darwin branch of the existing after-pack.mjs, and its test loads the hook from electron-builder.config.cjs and imports PlatformPackager from app-builder-lib's root (electron-builder 27 exports no ./out paths). The win32 row is dropped: this hook sanitizes and signs PE trees on win32 by design. - reconciliation.ts: main's rowId hydration (#119326) was merged into the first of pm-clean's split helpers only; the resolver is now one helper both halves use. - en.ts: both sides' keys kept. tests/tools/test_lazy_deps.py stays deleted. - Tests main added with `import yaml` use hermes_yaml, like the rest of the tree.
99 lines
3.3 KiB
Python
99 lines
3.3 KiB
Python
"""Shared YAML 1.1 policy for config, manifests, and frontmatter.
|
|
|
|
Ruamel's native schema includes bare y/n booleans and rejects duplicate keys.
|
|
Every operation owns its parser/emitter; instances must not be shared by threads.
|
|
"""
|
|
|
|
from io import StringIO
|
|
from typing import Any, IO, overload
|
|
|
|
from ruamel.yaml import YAML
|
|
from ruamel.yaml.error import YAMLError as YAMLError
|
|
from ruamel.yaml.resolver import VersionedResolver
|
|
|
|
|
|
class _Yaml11Resolver(VersionedResolver):
|
|
# Quote strings like "off" without adding a %YAML directive to every config/snippet.
|
|
@property
|
|
def processing_version(self) -> tuple[int, int]:
|
|
return (1, 1)
|
|
|
|
|
|
def _load(document: str | bytes, *, pure: bool) -> Any:
|
|
yaml = YAML(typ="safe", pure=pure)
|
|
yaml.version = (1, 1)
|
|
return yaml.load(document)
|
|
|
|
|
|
def safe_load(stream: str | bytes | IO[str] | IO[bytes]) -> Any:
|
|
"""Read standard YAML data; existing configs use YAML 1.1 booleans.
|
|
|
|
The pure parser defines what parses: Windows ARM64 has no C extension, and libyaml rejects
|
|
documents the pure parser accepts (``[{url: http://h}]``), so a C rejection is re-read pure.
|
|
"""
|
|
document = stream if isinstance(stream, (str, bytes)) else stream.read()
|
|
try:
|
|
return _load(document, pure=False)
|
|
except YAMLError:
|
|
return _load(document, pure=True)
|
|
|
|
|
|
@overload
|
|
def safe_dump(
|
|
data: Any, stream: None = None, *, default_flow_style: bool = False,
|
|
sort_keys: bool = True, allow_unicode: bool = True, width: int = 80,
|
|
) -> str: ...
|
|
|
|
|
|
@overload
|
|
def safe_dump(
|
|
data: Any, stream: IO[str], *, default_flow_style: bool = False,
|
|
sort_keys: bool = True, allow_unicode: bool = True, width: int = 80,
|
|
) -> None: ...
|
|
|
|
|
|
def safe_dump(
|
|
data: Any,
|
|
stream: IO[str] | None = None,
|
|
*,
|
|
default_flow_style: bool = False,
|
|
sort_keys: bool = True,
|
|
allow_unicode: bool = True,
|
|
width: int = 80,
|
|
) -> str | None:
|
|
"""Write standard YAML data with readable Unicode and indented block lists."""
|
|
# The C emitter ignores sequence offsets and escapes astral Unicode.
|
|
yaml = YAML(typ="safe", pure=True)
|
|
yaml.Resolver = _Yaml11Resolver
|
|
yaml.default_flow_style = default_flow_style
|
|
yaml.allow_unicode = allow_unicode
|
|
yaml.width = width
|
|
yaml.sort_base_mapping_type_on_output = sort_keys
|
|
yaml.indent(mapping=2, sequence=4, offset=2)
|
|
if stream is not None:
|
|
yaml.dump(data, stream)
|
|
return None
|
|
output = StringIO()
|
|
yaml.dump(data, output)
|
|
return output.getvalue()
|
|
|
|
|
|
# ruamel's emitter can change a double-quoted value when it folds a long line right after an
|
|
# escaped backslash (``D:\\Cent…`` → ``D:\\`` + bare newline): the fold reloads as a literal space
|
|
# and a no-op save mutates the stored value (#119844). Config writes must be value-preserving, so
|
|
# every round-trip emitter in the tree keeps scalars on one line instead of folding (``None``
|
|
# does NOT disable folding on 0.18.x; only a large width does).
|
|
ROUNDTRIP_YAML_WIDTH = 2**31 - 1
|
|
|
|
|
|
def roundtrip_yaml() -> YAML:
|
|
"""Create a fresh comment/quote-preserving editor for user-authored YAML."""
|
|
yaml = YAML(typ="rt")
|
|
yaml.width = ROUNDTRIP_YAML_WIDTH
|
|
yaml.Resolver = _Yaml11Resolver
|
|
yaml.preserve_quotes = True
|
|
yaml.allow_unicode = True
|
|
yaml.default_flow_style = False
|
|
yaml.indent(mapping=2, sequence=4, offset=2)
|
|
return yaml
|