oct
Loading...
Searching...
No Matches
octrc.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oct/core/octrc.py
4
5"""
6Purpose
7-------
8OI-519 - single source of truth for ``.octrc.json`` loading. Multiple OCT
9modules (linter, formatter, health, typecheck, MCP config) used to each
10carry their own ``json.loads`` + ``try/except`` around the same file; that
11drift-prone pattern is replaced by :func:`load_octrc` and its silent
12convenience wrapper :func:`load_octrc_safe`.
13
14Responsibilities
15----------------
16- Resolve ``<project_root>/.octrc.json``.
17- Tolerate missing file, OSError, invalid JSON, and non-object top-level
18 documents - never raise.
19- Return a ``(config, errors)`` tuple from :func:`load_octrc` so callers
20 that care about strict-mode handling (the linter) can surface messages.
21- Return a plain ``dict`` from :func:`load_octrc_safe` for callers that
22 only want the data.
23
24Diagnostics
25-----------
26Domain: OCT-CORE
27Levels:
28 L2 - (none) callers decide how to react to a load failure
29 L3 - load entry, naming the project root being read
30 L4 - (none)
31
32Contracts
33---------
34- Both functions accept the project root, not the config path, so behavior
35 stays consistent with every other OCT helper.
36- ``load_octrc_safe(root)`` is exactly equivalent to
37 ``load_octrc(root)[0]``.
38- ``load_octrc`` never raises; errors are always returned as strings.
39
40Dependencies
41------------
42- oct.core.option_c_dir (resolve_octrc path resolver)
43- oct.core.diagnostics (_dbg structured logger)
44"""
45
46from __future__ import annotations
47
48import json
49from pathlib import Path
50
51from oct.core.option_c_dir import resolve_octrc
52from oct.core.diagnostics import _dbg
53
54
55OCTRC_FILENAME = ".octrc.json"
56
57
58def load_octrc(project_root: Path) -> tuple[dict, list[str]]:
59 """Load the project's OCT RC config (``.octrc.json`` / ``octrc.json``).
60
61 FS-539: prefers ``.option_c/octrc.json`` when present, falls back to
62 the legacy ``<project_root>/.octrc.json`` otherwise. The concrete
63 path is resolved via :func:`oct.core.option_c_dir.resolve_octrc`.
64
65 Returns
66 -------
67 (config, errors) : tuple[dict, list[str]]
68 ``config`` is the parsed JSON object, or an empty dict if the file
69 is missing / unreadable / malformed / not a JSON object.
70 ``errors`` is a list of human-readable error strings; empty when
71 the file was absent or loaded successfully.
72 """
73 func = "load_octrc"
74 _dbg("OCT-CORE", func, f"loading octrc for {project_root}", 3)
75 octrc_path = resolve_octrc(project_root)
76 label = octrc_path.name # "octrc.json" (modern) or ".octrc.json" (legacy)
77 if not octrc_path.is_file():
78 return {}, []
79
80 try:
81 raw = octrc_path.read_text(encoding="utf-8")
82 except OSError as exc:
83 return {}, [f"{label} unreadable: {exc}"]
84
85 try:
86 data = json.loads(raw)
87 except json.JSONDecodeError as exc:
88 return {}, [f"{label} has invalid JSON: {exc}"]
89
90 if not isinstance(data, dict):
91 return {}, [
92 f"{label} must be a JSON object, got "
93 f"{type(data).__name__}"
94 ]
95
96 return data, []
97
98
99def load_octrc_safe(project_root: Path) -> dict:
100 """Load ``.octrc.json`` quietly, returning ``{}`` on any failure."""
101 func = "load_octrc_safe"
102 _dbg("OCT-CORE", func, f"loading octrc (fail-soft) for {project_root}", 3)
103 data, _errors = load_octrc(project_root)
104 return data
105
106
107__all__ = ["OCTRC_FILENAME", "load_octrc", "load_octrc_safe"]
tuple[dict, list[str]] load_octrc(Path project_root)
Definition octrc.py:58
dict load_octrc_safe(Path project_root)
Definition octrc.py:99