|
| list[Path] | _git_changed_files (Path root) |
| str | _get_relative_path (Path path, Path root) |
| int | _prune_archive (Path archive_dir, str original_name, int max_backups) |
| int | _prune_archive_by_size (Path archive_dir, int max_total_bytes) |
| None | _archive_file (Path path, int max_backups=_DEFAULT_MAX_BACKUPS_PER_FILE, int max_archive_bytes=_DEFAULT_MAX_ARCHIVE_BYTES) |
| int | _leading_comment_block_end (List[str] lines) |
| str | _leading_whitespace (str line) |
| int | _statement_start_index (ast.stmt stmt) |
| bool | _begins_its_own_line (ast.stmt stmt, List[str] lines) |
| Optional[Tuple[str, int]] | _module_docstring_span (str text) |
| List[str] | _detect_missing_docstring_sections (str text) |
| Tuple[bool, str, List[str]] | fix_header_block (Path path, str text, FormatterContext ctx) |
| Tuple[bool, str, List[str]] | fix_docstring (Path path, str text, FormatterContext ctx) |
| int | _dbg_import_insert_index (ast.Module tree, List[str] lines) |
| Optional[str] | _import_block_package () |
| Tuple[Optional[str], Optional[str]] | _import_block_binding () |
| Tuple[set, int] | _conflicting_import_lines (ast.Module tree, str module, str symbol) |
| Optional[str] | _owning_top_level_package (Path path) |
| Tuple[bool, str, List[str]] | fix_dbg_import (Path path, str text, FormatterContext ctx) |
| Optional[Tuple[int, str]] | _func_pattern_insert_point (Any node, List[str] lines) |
| Tuple[bool, str, List[str]] | fix_func_pattern (Path path, str text, FormatterContext ctx) |
| Dict[str, Any] | format_file (Path path, FormatterContext ctx) |
| Dict[str, Any] | compute_summary (List[Dict[str, Any]] file_results) |
| None | _print_terminal_report (Dict[str, Any] summary, List[Dict[str, Any]] file_results, bool verbose, bool dry_run) |
| None | _print_json_report (Dict[str, Any] summary, List[Dict[str, Any]] file_results, FormatterContext ctx) |
| List[str] | _formatter_flag_argv (args) |
| bool | _maybe_dispatch_format_by_lens (Path project_root, args) |
| None | run_formatter (Path project_root, Optional[List[str]] argv=None, *, Optional[List[Path]] _files=None, bool _dispatch=True) |
Option C auto-formatter implementation.
Purpose
-------
Detect and fix structural Option C compliance violations automatically.
Complements the linter by fixing violations while preserving code semantics.
Responsibilities
----------------
- Detect formatting violations using reused linter checks.
- Fix violations: headers, docstrings, imports, function patterns.
- Support report-only (dry-run) and fix modes.
- Archive originals before modification.
- Provide terminal and JSON output.
- Respect project exclusions and configuration.
Diagnostics
-----------
Domain: OCT.FORMATTER
Levels:
L2 - formatter lifecycle and command routing
L3 - file discovery and processing, fix application decisions
L4 - detailed fixer implementations, archival operations, output formatting
Contracts
---------
- Files are only modified if fix_mode is True.
- Originals are archived before any modification.
- All code below headers is preserved exactly.
- Docstrings are not replaced; missing sections are appended.
- Each fixer is idempotent (running twice is same as once).
- A module is never given an import of a symbol its own package
provides, so formatting can never make a package import itself.
Dependencies
------------
- oct (package version)
- oct.linter.oct_lint (reused validation checks)
- oct.core.exclusions (directory and wildcard exclusion lists)
- oct.core.project_root (project root detection)
- oct.core.parsing (safe AST parsing helper)
- oct.core.git (lazy; git changed-file discovery)
- oct.core.paths (to_project_relative - the identity header path)
- oct.core.diagnostics (_dbg structured logger)
- oct.core.octrc (lazy; .octrc.json exclusion and archive overrides)
- oct.core.workspace (lazy; subproject lookup for the per-subproject fan-out)
| Tuple[set, int] oct.formatter.oct_format._conflicting_import_lines |
( |
ast.Module | tree, |
|
|
str | module, |
|
|
str | symbol ) |
|
protected |
Return ``(line_indices, statement_count)`` for imports that conflict.
A statement conflicts only when it binds the BARE name ``symbol``, since
that is the sole binding the canonical import can collide with. This is
a structural test over module-level import nodes, replacing a substring
scan that treated any line containing ``"import <symbol>"`` as an import
(198). That scan deleted prose inside docstrings, string literals that
merely spelled an import, and - because ``import _dbg_shim`` contains
``import _dbg`` - the sole body of a ``try:`` block, which does not
parse.
Two conservative limits keep removal from causing the very breakage this
fixer exists to avoid:
- Aliased imports are never removed. They bind a different name, so they
cannot conflict, and deleting one unbinds a name the module still uses.
- A statement that binds anything else is never removed, so a
co-imported symbol can never be taken down with it.
Only module-level statements are considered, matching the scope of
:func:`oct.linter.oct_lint.check_dbg_import`.
Definition at line 613 of file oct_format.py.
| int oct.formatter.oct_format._dbg_import_insert_index |
( |
ast.Module | tree, |
|
|
List[str] | lines ) |
|
protected |
Return the 0-based line index at which the canonical ``_dbg`` import belongs.
The placement is derived from the module structure, never from a fixed
line number (184-D7). The Option C canonical order is: header comments,
module docstring, ``from __future__`` imports, then ordinary imports.
- With a module docstring, start at the line after its closing quotes,
then move past any ``from __future__`` imports - the language requires
those to be the first statement after the docstring.
- Without one, start after the whole leading comment/blank block, however
long it runs, so the import never lands inside it.
In both cases trailing blank lines are skipped, so the import joins the
top of the existing import block instead of hugging the docstring or the
comment header.
Definition at line 529 of file oct_format.py.
| Optional[Tuple[int, str]] oct.formatter.oct_format._func_pattern_insert_point |
( |
Any | node, |
|
|
List[str] | lines ) |
|
protected |
Return ``(line_index, indent)`` for a function's ``func = "..."`` line.
The position is derived from the function's AST body, never by counting
lines from the ``def`` (198). A multi-line signature puts a PARAMETER on
the line after the ``def``, so "the next line" is not the body - taking
it as the insertion point injects the assignment into the parameter list
and the file stops parsing.
Docstring-first ordering is preserved: when the body opens with a
docstring the assignment lands after it, so the string remains the
function's docstring instead of being demoted to a plain expression.
Returns ``None`` when there is no line to insert before - a body written
inline with its signature (``def f(): return x``). Leaving such a
function unfixed is the correct outcome: the fixer's contract is that it
never corrupts a file, not that it always succeeds.
Definition at line 763 of file oct_format.py.
| str oct.formatter.oct_format._get_relative_path |
( |
Path | path, |
|
|
Path | root ) |
|
protected |
Render *path* as a project-relative POSIX string.
200: delegates to :func:`oct.core.paths.to_project_relative`, the helper
OI-533 introduced to standardise path output - and the one the linter's
``expected_path_header`` already uses. This function previously returned
``str(path.relative_to(root))``, which renders with the OS separator, so
on Windows the identity header the formatter WROTE
(``# pkg\\sub\\mod.py``) was never the one the linter EXPECTED
(``# pkg/sub/mod.py``). Every file in a subdirectory therefore had its
header rewritten on every run, forever, each pass archiving the file
again - a direct breach of the module's own per-fixer idempotence
contract.
There is one source of truth for "the project-relative path" and both
tools now call it.
Definition at line 194 of file oct_format.py.
| bool oct.formatter.oct_format._maybe_dispatch_format_by_lens |
( |
Path | project_root, |
|
|
| args ) |
|
protected |
Fan ``oct format`` out to one engine run per owning subproject.
Plan 213 / D-D, the formatter half. Returns True when the invocation
fanned out (the caller then returns immediately), False to let the
ordinary single-root pass run.
This matters more here than for the linter: ``run_formatter`` has no
path targets at all, so before 213 a workspace-root invocation walked
the entire tree under ONE ``FormatterContext`` and judged every
subproject's file against the workspace-root lens. In ``--fix`` mode
that did not merely misreport - it REWROTE correct
subproject-relative identity headers into workspace-relative ones.
Only a workspace-root invocation fans out. From inside a subproject,
project-root discovery already resolves to that subproject, so the
lens is correct and behaviour is byte-identical to before.
Definition at line 1122 of file oct_format.py.
| Optional[Tuple[str, int]] oct.formatter.oct_format._module_docstring_span |
( |
str | text | ) |
|
|
protected |
Locate the module docstring, returning ``(docstring, close_index)``.
``close_index`` is the offset in *text* of the docstring's CLOSING
triple quote, which is where :func:`fix_docstring` appends the Option C
sections it finds missing.
200: the docstring itself comes from the linter's shared
:func:`~oct.linter.oct_lint._extract_module_docstring` - the helper this
module already uses at :func:`_detect_missing_docstring_sections` and at
the top of :func:`fix_docstring`. Only the POSITION is computed here.
That is the whole point: ``fix_docstring`` used to find its insertion
point with a raw scan for the first triple-double-quote in the file,
which is a second, independent opinion about which string is the module
docstring. With a single-quoted module docstring the raw scan found a
FUNCTION's docstring and the module sections were appended there. The
output still parsed, so nothing caught it.
204: the position is no longer recovered by searching the source for the
shared helper's answer - the helper now reports the span it found, so the
formatter and the linter agree on WHERE the docstring is structurally,
not by locating the same substring twice.
Returns ``None`` when there is no module docstring, or when its delimiter
is not a triple quote: the Option C sections are appended as new lines,
which a one-line ``'x'`` literal cannot carry without breaking the parse.
Callers must treat ``None`` as "make no change": misplacing (or
duplicating) Option C sections is worse than declining to add them.
Definition at line 359 of file oct_format.py.