• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MuellerSeb / nml-tools / 29525046973

16 Jul 2026 06:41PM UTC coverage: 86.028% (+1.3%) from 84.697%
29525046973

Pull #60

github

MuellerSeb
Revert "Preserve trailing namelist null values"

This reverts commit 5fd338717.
Pull Request #60: Add A Schema-Aware Standard Fortran Namelist Parser

842 of 911 new or added lines in 4 files covered. (92.43%)

9 existing lines in 3 files now uncovered.

4778 of 5554 relevant lines covered (86.03%)

0.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.36
/src/nml_tools/_utils.py
1
"""Internal shared helpers."""
2

3
from __future__ import annotations
1✔
4

5
import re
1✔
6
from collections.abc import Mapping
1✔
7

8
FORTRAN_IDENTIFIER = re.compile(r"^[A-Za-z][A-Za-z0-9_]*$")
1✔
9
RESERVED_IDENTIFIER_SEPARATOR = "__"
1✔
10

11

12
def is_fortran_identifier(name: str) -> bool:
1✔
13
    """Return whether *name* is a valid Fortran identifier."""
14
    return FORTRAN_IDENTIFIER.match(name) is not None
1✔
15

16

17
def validate_user_fortran_identifier(name: str, *, label: str) -> None:
1✔
18
    """Validate a user-controlled Fortran identifier.
19

20
    nml-tools reserves double underscores for generated support identifiers.
21
    """
22
    if not is_fortran_identifier(name):
1✔
23
        raise ValueError(f"{label} must be a valid Fortran identifier")
1✔
24
    if RESERVED_IDENTIFIER_SEPARATOR in name:
1✔
25
        raise ValueError(f"{label} must not contain '{RESERVED_IDENTIFIER_SEPARATOR}'")
1✔
26

27

28
def strip_trailing_whitespace(text: str) -> str:
1✔
29
    """Strip trailing horizontal whitespace from each line while preserving final newline."""
30
    cleaned = "\n".join(line.rstrip() for line in text.splitlines())
1✔
31
    if text.endswith("\n"):
1✔
32
        cleaned += "\n"
1✔
33
    return cleaned
1✔
34

35

36
def normalize_constant_values(
1✔
37
    constants: Mapping[str, object] | None,
38
) -> dict[str, int]:
39
    """Validate and normalize static integer constants by lowercase name."""
40
    if constants is None:
1✔
41
        return {}
1✔
42
    normalized: dict[str, int] = {}
1✔
43
    for name, value in constants.items():
1✔
44
        if not isinstance(name, str) or not name.strip():
1✔
45
            raise ValueError("constant names must be non-empty strings")
1✔
46
        validate_user_fortran_identifier(name, label=f"constant '{name}'")
1✔
47
        canonical_name = name.lower()
1✔
48
        if canonical_name in normalized:
1✔
UNCOV
49
            raise ValueError(f"constant '{name}' duplicates another constant name")
×
50
        if isinstance(value, bool) or not isinstance(value, int):
1✔
51
            raise ValueError(f"constant '{name}' must be an integer")
1✔
52
        normalized[canonical_name] = value
1✔
53
    return normalized
1✔
54

55

56
def normalize_runtime_dimensions(
1✔
57
    dimensions: Mapping[str, object] | None,
58
) -> dict[str, int]:
59
    """Validate and normalize runtime dimensions by lowercase name."""
60
    if dimensions is None:
1✔
61
        return {}
1✔
62
    normalized: dict[str, int] = {}
1✔
63
    for name, value in dimensions.items():
1✔
64
        if not isinstance(name, str) or not name.strip():
1✔
65
            raise ValueError("runtime dimension names must be non-empty strings")
1✔
66
        validate_user_fortran_identifier(name, label=f"runtime dimension '{name}'")
1✔
67
        canonical_name = name.lower()
1✔
68
        if canonical_name in normalized:
1✔
69
            raise ValueError(f"runtime dimension '{name}' duplicates another dimension name")
1✔
70
        if isinstance(value, bool) or not isinstance(value, int):
1✔
71
            raise ValueError(f"runtime dimension '{name}' must be an integer")
1✔
72
        if value <= 0:
1✔
73
            raise ValueError(f"runtime dimension '{name}' must be positive")
1✔
74
        normalized[canonical_name] = value
1✔
75
    return normalized
1✔
76

77

78
def constant_dimension_overlap(
1✔
79
    constants: Mapping[str, object],
80
    dimensions: Mapping[str, object],
81
) -> list[str]:
82
    """Return lowercase names present in both constants and dimensions."""
83
    return sorted({name.lower() for name in constants} & {name.lower() for name in dimensions})
1✔
84

85

86
def reject_constant_dimension_overlap(
1✔
87
    constants: Mapping[str, object],
88
    dimensions: Mapping[str, object],
89
) -> None:
90
    """Raise when constants and runtime dimensions share names."""
91
    overlap = constant_dimension_overlap(constants, dimensions)
1✔
92
    if overlap:
1✔
UNCOV
93
        raise ValueError("constants and dimensions must not share names: " + ", ".join(overlap))
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc