|
8 | 8 | import yaml |
9 | 9 |
|
10 | 10 | FormatLiteral = t.Literal["json", "yaml"] |
| 11 | +_SUPPORTED_CONFIG_SUFFIXES: dict[str, FormatLiteral] = { |
| 12 | + ".json": "json", |
| 13 | + ".yaml": "yaml", |
| 14 | + ".yml": "yaml", |
| 15 | +} |
| 16 | + |
| 17 | + |
| 18 | +def config_format_from_path(path: pathlib.Path) -> FormatLiteral | None: |
| 19 | + """Return config format inferred from a path or symlink target. |
| 20 | +
|
| 21 | + The visible path remains the config identity, but symlinks may point |
| 22 | + at a differently named target. When the target advertises a supported |
| 23 | + config suffix, prefer that format; otherwise fall back to the visible |
| 24 | + path suffix. |
| 25 | +
|
| 26 | + Parameters |
| 27 | + ---------- |
| 28 | + path : pathlib.Path |
| 29 | + Path to inspect. |
| 30 | +
|
| 31 | + Returns |
| 32 | + ------- |
| 33 | + FormatLiteral | None |
| 34 | + ``"json"`` or ``"yaml"`` when a supported suffix is found, |
| 35 | + otherwise ``None``. |
| 36 | +
|
| 37 | + Examples |
| 38 | + -------- |
| 39 | + >>> config_format_from_path(pathlib.Path("config.yaml")) |
| 40 | + 'yaml' |
| 41 | + >>> import tempfile |
| 42 | + >>> with tempfile.TemporaryDirectory() as tmp: |
| 43 | + ... root = pathlib.Path(tmp) |
| 44 | + ... target = root / "config.json" |
| 45 | + ... _ = target.write_text("{}", encoding="utf-8") |
| 46 | + ... link = root / ".vcspull.yaml" |
| 47 | + ... link.symlink_to(target) |
| 48 | + ... config_format_from_path(link) |
| 49 | + 'json' |
| 50 | + """ |
| 51 | + path_format = _SUPPORTED_CONFIG_SUFFIXES.get(path.suffix.lower()) |
| 52 | + |
| 53 | + target_format: FormatLiteral | None = None |
| 54 | + if path.is_symlink(): |
| 55 | + resolved = path.resolve(strict=False) |
| 56 | + target_format = _SUPPORTED_CONFIG_SUFFIXES.get(resolved.suffix.lower()) |
| 57 | + |
| 58 | + return target_format or path_format |
| 59 | + |
| 60 | + |
11 | 61 | RawConfigData: t.TypeAlias = dict[t.Any, t.Any] |
12 | 62 |
|
13 | 63 |
|
@@ -114,14 +164,12 @@ def _from_file(cls, path: pathlib.Path) -> dict[str, t.Any]: |
114 | 164 | # the formatter helpers directly; |
115 | 165 | # 3) Keep this basic loader but add an opt-in path for duplicate-aware |
116 | 166 | # parsing so commands like ``vcspull add`` can avoid data loss. |
117 | | - # Revisit once the new ``vcspull add`` flow lands so both commands share |
118 | | - # the same duplication safeguards. |
| 167 | + # ``vcspull add`` now uses ``DuplicateAwareConfigReader`` for reading |
| 168 | + # (see ``cli/add.py``). This basic loader remains for simpler read |
| 169 | + # contexts. Option 1 (shared utility) is the cleanest long-term path. |
119 | 170 |
|
120 | | - if path.suffix in {".yaml", ".yml"}: |
121 | | - fmt: FormatLiteral = "yaml" |
122 | | - elif path.suffix == ".json": |
123 | | - fmt = "json" |
124 | | - else: |
| 171 | + fmt = config_format_from_path(path) |
| 172 | + if fmt is None: |
125 | 173 | msg = f"{path.suffix} not supported in {path}" |
126 | 174 | raise NotImplementedError(msg) |
127 | 175 |
|
@@ -335,7 +383,7 @@ def _load_from_path( |
335 | 383 | cls, |
336 | 384 | path: pathlib.Path, |
337 | 385 | ) -> tuple[dict[str, t.Any], dict[str, list[t.Any]], list[tuple[str, t.Any]]]: |
338 | | - if path.suffix.lower() in {".yaml", ".yml"}: |
| 386 | + if config_format_from_path(path) == "yaml": |
339 | 387 | content = path.read_text(encoding="utf-8") |
340 | 388 | return cls._load_yaml_with_duplicates(content) |
341 | 389 |
|
|
0 commit comments