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

MuellerSeb / nml-tools / 29619881679

17 Jul 2026 11:03PM UTC coverage: 86.477% (+0.4%) from 86.054%
29619881679

Pull #63

github

MuellerSeb
Allow defaults to satisfy required values

Separate declared requiredness from effective input requirements across validation, namelist evaluation, and generated APIs. Support scalar derived object defaults and broadcast item defaults with deterministic coverage and precedence rules.

Update Fortran, f2py/Python, Markdown, templates, documentation, tests, and regenerated derived-type example artifacts.
Pull Request #63: Allow defaults to satisfy required values

270 of 287 new or added lines in 7 files covered. (94.08%)

3 existing lines in 1 file now uncovered.

5039 of 5827 relevant lines covered (86.48%)

0.86 hits per line

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

83.21
/src/nml_tools/codegen_template.py
1
"""Template namelist generation."""
2

3
from __future__ import annotations
1✔
4

5
from pathlib import Path
1✔
6
from typing import Any, Iterable
1✔
7

8
from ._utils import (
1✔
9
    normalize_constant_values,
10
    normalize_runtime_dimensions,
11
    reject_constant_dimension_overlap,
12
    validate_user_fortran_identifier,
13
)
14
from .codegen_fortran import (
1✔
15
    FieldTypeInfo,
16
    _array_default_value,
17
    _collect_dimension_constants,
18
    _derived_schema,
19
    _enum_values,
20
    _field_type_info,
21
    _format_scalar_default,
22
    _parse_default_dimensions,
23
    _reject_runtime_dimension_lengths,
24
)
25
from .schema import _is_simple_derived_schema
1✔
26
from .validate import (
1✔
27
    analyze_property_requirement,
28
    derived_object_default,
29
    validate_schema_defaults,
30
)
31

32
_MISSING = object()
1✔
33

34

35
def generate_template(
1✔
36
    schemas: Iterable[dict[str, Any]],
37
    output: str | Path,
38
    *,
39
    doc_mode: str = "plain",
40
    value_mode: str = "empty",
41
    simple_derived_mode: str = "components",
42
    title: str | None = None,
43
    description: str | None = None,
44
    constants: dict[str, int] | None = None,
45
    dimensions: dict[str, int] | None = None,
46
    kind_map: dict[str, str] | None = None,
47
    kind_allowlist: set[str] | None = None,
48
    values: dict[str, dict[str, Any]] | None = None,
49
) -> None:
50
    """Generate a template namelist file for *schemas* at *output*."""
51
    rendered = render_template(
1✔
52
        schemas,
53
        doc_mode=doc_mode,
54
        value_mode=value_mode,
55
        simple_derived_mode=simple_derived_mode,
56
        title=title,
57
        description=description,
58
        constants=constants,
59
        dimensions=dimensions,
60
        kind_map=kind_map,
61
        kind_allowlist=kind_allowlist,
62
        values=values,
63
    )
64
    output_path = Path(output)
1✔
65
    output_path.parent.mkdir(parents=True, exist_ok=True)
1✔
66
    output_path.write_text(rendered, encoding="ascii")
1✔
67

68

69
def render_template(
1✔
70
    schemas: Iterable[dict[str, Any]],
71
    *,
72
    doc_mode: str = "plain",
73
    value_mode: str = "empty",
74
    simple_derived_mode: str = "components",
75
    title: str | None = None,
76
    description: str | None = None,
77
    constants: dict[str, int] | None = None,
78
    dimensions: dict[str, int] | None = None,
79
    kind_map: dict[str, str] | None = None,
80
    kind_allowlist: set[str] | None = None,
81
    values: dict[str, dict[str, Any]] | None = None,
82
) -> str:
83
    """Render a template namelist file for *schemas*."""
84
    doc_mode = doc_mode.strip().lower()
1✔
85
    value_mode = value_mode.strip().lower()
1✔
86
    simple_derived_mode = simple_derived_mode.strip().lower()
1✔
87
    if doc_mode not in {"plain", "documented"}:
1✔
88
        raise ValueError("template doc_mode must be 'plain' or 'documented'")
×
89
    if value_mode not in {"empty", "filled", "minimal-empty", "minimal-filled"}:
1✔
90
        raise ValueError(
×
91
            "template value_mode must be one of: empty, filled, minimal-empty, minimal-filled"
92
        )
93
    if simple_derived_mode not in {"components", "buffer"}:
1✔
94
        raise ValueError("template simple_derived_mode must be 'components' or 'buffer'")
1✔
95

96
    return _render_template(
1✔
97
        schemas,
98
        doc_mode=doc_mode,
99
        value_mode=value_mode,
100
        simple_derived_mode=simple_derived_mode,
101
        title=title,
102
        description=description,
103
        constants=constants,
104
        dimensions=dimensions,
105
        kind_map=kind_map,
106
        kind_allowlist=kind_allowlist,
107
        values=values,
108
    )
109

110

111
def _render_template(
1✔
112
    schemas: Iterable[dict[str, Any]],
113
    *,
114
    doc_mode: str,
115
    value_mode: str,
116
    simple_derived_mode: str,
117
    title: str | None,
118
    description: str | None,
119
    constants: dict[str, int] | None,
120
    dimensions: dict[str, int] | None,
121
    kind_map: dict[str, str] | None,
122
    kind_allowlist: set[str] | None,
123
    values: dict[str, dict[str, Any]] | None,
124
) -> str:
125
    lines: list[str] = []
1✔
126
    schemas_list = list(schemas)
1✔
127
    values_map = values or {}
1✔
128
    if not isinstance(values_map, dict):
1✔
129
        raise ValueError("template values must be a table of namelist tables")
×
130
    constants = normalize_constant_values(constants)
1✔
131
    dimensions = normalize_runtime_dimensions(dimensions)
1✔
132
    reject_constant_dimension_overlap(constants, dimensions)
1✔
133
    shape_constants: dict[str, int] = {
1✔
134
        **constants,
135
        **dimensions,
136
    }
137

138
    schema_by_name: dict[str, dict[str, Any]] = {}
1✔
139
    for schema in schemas_list:
1✔
140
        validate_schema_defaults(schema, constants=constants, dimensions=dimensions)
1✔
141
        namelist_name = schema.get("x-fortran-namelist")
1✔
142
        if not isinstance(namelist_name, str) or not namelist_name.strip():
1✔
143
            raise ValueError("schema must define 'x-fortran-namelist'")
×
144
        validate_user_fortran_identifier(namelist_name, label="'x-fortran-namelist'")
1✔
145
        schema_by_name[namelist_name] = schema
1✔
146

147
    for namelist_name, namelist_values in values_map.items():
1✔
148
        if not isinstance(namelist_name, str):
1✔
149
            raise ValueError("template values namelist names must be strings")
×
150
        if namelist_name not in schema_by_name:
1✔
151
            raise ValueError(f"template values namelist '{namelist_name}' not found in schemas")
×
152
        if not isinstance(namelist_values, dict):
1✔
153
            raise ValueError(f"template values for namelist '{namelist_name}' must be a table")
×
154
        properties = schema_by_name[namelist_name].get("properties")
1✔
155
        if not isinstance(properties, dict) or not properties:
1✔
156
            raise ValueError("schema must define object 'properties'")
×
157
        for key in namelist_values:
1✔
158
            if not isinstance(key, str):
1✔
159
                raise ValueError(
×
160
                    f"template values for namelist '{namelist_name}' must use string keys"
161
                )
162
            if key not in properties:
1✔
163
                raise ValueError(
×
164
                    f"template values for namelist '{namelist_name}' include unknown field '{key}'"
165
                )
166

167
    if doc_mode == "documented":
1✔
168
        _append_template_comment(lines, title)
1✔
169
        has_title = isinstance(title, str) and bool(title.strip())
1✔
170
        has_description = isinstance(description, str) and bool(description.strip())
1✔
171
        if has_title and has_description:
1✔
172
            lines.append("!")
1✔
173
        _append_template_comment(lines, description)
1✔
174
        if lines:
1✔
175
            lines.append("")
1✔
176

177
    for schema in schemas_list:
1✔
178
        namelist_name = schema.get("x-fortran-namelist")
1✔
179
        if not isinstance(namelist_name, str) or not namelist_name.strip():
1✔
180
            raise ValueError("schema must define 'x-fortran-namelist'")
×
181
        validate_user_fortran_identifier(namelist_name, label="'x-fortran-namelist'")
1✔
182
        if schema.get("type") != "object":
1✔
183
            raise ValueError("schema root must be of type 'object'")
×
184
        properties = schema.get("properties")
1✔
185
        if not isinstance(properties, dict) or not properties:
1✔
186
            raise ValueError("schema must define object 'properties'")
×
187
        required_raw = schema.get("required", [])
1✔
188
        if not isinstance(required_raw, list):
1✔
NEW
189
            raise ValueError("schema 'required' must be a list")
×
190
        required_names = {
1✔
191
            value.lower() for value in required_raw if isinstance(value, str)
192
        }
193
        override_values = (
1✔
194
            values_map.get(namelist_name, {}) if value_mode in {"filled", "minimal-filled"} else {}
195
        )
196

197
        if doc_mode == "documented":
1✔
198
            title = schema.get("title")
1✔
199
            if isinstance(title, str) and title.strip():
1✔
200
                lines.append(f"! {title.strip()}")
1✔
201

202
        lines.append(f"&{namelist_name}")
1✔
203

204
        current_property: str | None = None
1✔
205
        try:
1✔
206
            for name, prop in properties.items():
1✔
207
                current_property = name
1✔
208
                if not isinstance(prop, dict):
1✔
209
                    raise ValueError(f"property '{name}' must be an object")
×
210
                _reject_runtime_dimension_lengths(prop, dimensions)
1✔
211
                type_info = _field_type_info(prop, constants)
1✔
212
                _collect_dimension_constants(type_info.dimensions, shape_constants)
1✔
213
                _validate_kind_allowlist(type_info, kind_map, kind_allowlist)
1✔
214

215
                requirement = analyze_property_requirement(
1✔
216
                    name,
217
                    prop,
218
                    declared_required=name.lower() in required_names,
219
                )
220
                has_default = requirement.fully_default_initialized
1✔
221
                has_override = name in override_values
1✔
222
                if value_mode in {"minimal-empty", "minimal-filled"} and has_default:
1✔
223
                    if value_mode == "minimal-filled" and has_override:
1✔
224
                        pass
1✔
225
                    else:
226
                        continue
1✔
227

228
                if doc_mode == "documented":
1✔
229
                    title = prop.get("title")
1✔
230
                    if isinstance(title, str) and title.strip():
1✔
231
                        lines.append(f"  ! {title.strip()}")
1✔
232

233
                entries = _value_entries(
1✔
234
                    name,
235
                    prop,
236
                    type_info,
237
                    value_mode=value_mode,
238
                    simple_derived_mode=simple_derived_mode,
239
                    override=override_values.get(name, _MISSING),
240
                    constants=shape_constants,
241
                )
242
                derived = _derived_schema(prop)
1✔
243
                if (
1✔
244
                    doc_mode == "documented"
245
                    and simple_derived_mode == "buffer"
246
                    and derived is not None
247
                    and _is_simple_derived_schema(derived)
248
                    and entries
249
                ):
250
                    component_names = ", ".join(str(key) for key in derived["properties"])
1✔
251
                    lines.append(f"  ! Component order: {component_names}")
1✔
252
                for entry_name, value_text in entries:
1✔
253
                    if value_text is None:
1✔
254
                        lines.append(f"  {entry_name} =")
1✔
255
                    else:
256
                        lines.append(f"  {entry_name} = {value_text}")
1✔
257
        except ValueError as exc:
1✔
258
            if current_property is None:
1✔
259
                raise
×
260
            msg = str(exc)
1✔
261
            if f"property '{current_property}'" in msg:
1✔
262
                raise
×
263
            raise ValueError(f"property '{current_property}': {msg}") from exc
1✔
264

265
        lines.append("/")
1✔
266
        lines.append("")
1✔
267

268
    if lines and lines[-1] == "":
1✔
269
        lines.pop()
1✔
270
    return "\n".join(lines) + "\n"
1✔
271

272

273
def _append_template_comment(lines: list[str], value: str | None) -> None:
1✔
274
    if not isinstance(value, str) or not value.strip():
1✔
275
        return
1✔
276
    for line in value.strip().splitlines():
1✔
277
        lines.append(f"! {line.strip()}" if line.strip() else "!")
1✔
278

279

280
def _array_slice(rank: int) -> str:
1✔
281
    return "(" + ",".join(":" for _ in range(rank)) + ")"
1✔
282

283

284
def _value_entries(
1✔
285
    name: str,
286
    prop: dict[str, Any],
287
    type_info: FieldTypeInfo,
288
    *,
289
    value_mode: str,
290
    simple_derived_mode: str,
291
    override: Any,
292
    constants: dict[str, int] | None,
293
    fallback_default: Any = _MISSING,
294
) -> list[tuple[str, str | None]]:
295
    derived = _derived_schema(prop)
1✔
296
    if derived is not None:
1✔
297
        if simple_derived_mode == "buffer" and _is_simple_derived_schema(derived):
1✔
298
            return _derived_buffer_entries(
1✔
299
                name,
300
                derived,
301
                type_info,
302
                value_mode=value_mode,
303
                override=override,
304
                constants=constants,
305
            )
306
        return _derived_value_entries(
1✔
307
            name,
308
            prop,
309
            derived,
310
            type_info,
311
            value_mode=value_mode,
312
            override=override,
313
            constants=constants,
314
        )
315
    if value_mode in {"empty", "minimal-empty"}:
1✔
316
        entry_name = name
1✔
317
        if type_info.category == "array":
1✔
318
            entry_name = f"{name}{_array_slice(len(type_info.dimensions))}"
×
319
        return [(entry_name, None)]
1✔
320

321
    enum_values = _enum_values(prop, type_info, constants)
1✔
322

323
    if override is not _MISSING:
1✔
324
        return _entries_from_value(name, override, type_info, prop, constants)
1✔
325

326
    example_value = _get_first_example(prop, type_info)
1✔
327
    if example_value is not None:
1✔
328
        return _entries_from_value(name, example_value, type_info, prop, constants)
1✔
329

330
    if fallback_default is not _MISSING:
1✔
331
        return _entries_from_value(name, fallback_default, type_info, prop, constants)
1✔
332

333
    if type_info.category == "array":
1✔
334
        array_default = _array_default_value(prop)
1✔
335
        if array_default is not None:
1✔
336
            default_value, default_from_items = array_default
1✔
337
            if default_from_items and isinstance(default_value, list):
1✔
338
                raise ValueError("array items default must be a scalar")
×
339
            if isinstance(default_value, list):
1✔
340
                return _array_list_entries(name, default_value, type_info, prop, constants)
1✔
341
            scalar = _format_scalar_default(
1✔
342
                default_value,
343
                None,
344
                type_info.element_category,
345
            )
346
            entry_name = f"{name}{_array_slice(len(type_info.dimensions))}"
1✔
347
            return [(entry_name, scalar)]
1✔
348
    elif "default" in prop:
1✔
349
        default_value = prop["default"]
1✔
350
        scalar = _format_scalar_default(default_value, None, type_info.category)
1✔
351
        return [(name, scalar)]
1✔
352

353
    if enum_values:
1✔
354
        return _entries_from_value(name, enum_values[0], type_info, prop, constants)
1✔
355

356
    if type_info.category == "array":
1✔
357
        scalar = _format_scalar_default(
1✔
358
            _fallback_scalar_value(type_info),
359
            None,
360
            type_info.element_category,
361
        )
362
        entry_name = f"{name}{_array_slice(len(type_info.dimensions))}"
1✔
363
        return [(entry_name, scalar)]
1✔
364

365
    scalar = _format_scalar_default(
1✔
366
        _fallback_scalar_value(type_info),
367
        None,
368
        type_info.category,
369
    )
370
    return [(name, scalar)]
1✔
371

372

373
def _derived_value_entries(
1✔
374
    name: str,
375
    prop: dict[str, Any],
376
    derived: dict[str, Any],
377
    type_info: FieldTypeInfo,
378
    *,
379
    value_mode: str,
380
    override: Any,
381
    constants: dict[str, int] | None,
382
) -> list[tuple[str, str | None]]:
383
    properties = derived.get("properties")
1✔
384
    if not isinstance(properties, dict) or not properties:
1✔
385
        raise ValueError(f"derived property '{name}' must define properties")
×
386
    is_array = type_info.category == "array"
1✔
387
    object_default = derived_object_default(name, prop)
1✔
388
    entries: list[tuple[str, str | None]] = []
1✔
389
    if is_array and override is not _MISSING:
1✔
390
        if not isinstance(override, list):
1✔
391
            raise ValueError(f"derived array template value '{name}' must be an array of tables")
×
392
        for index, value in enumerate(override, start=1):
1✔
393
            if not isinstance(value, dict):
1✔
394
                raise ValueError(
×
395
                    f"derived array template value '{name}[{index}]' must be a table"
396
                )
397
            value_map = _as_str_keyed_table(
1✔
398
                value,
399
                f"derived array template value '{name}[{index}]'",
400
            )
401
            entries.extend(
1✔
402
                _derived_component_entries(
403
                    f"{name}({index})",
404
                    properties,
405
                    value_mode=value_mode,
406
                    overrides=value_map,
407
                    object_default=object_default,
408
                    constants=constants,
409
                )
410
            )
411
        return entries
1✔
412
    if override is not _MISSING and not isinstance(override, dict):
1✔
413
        raise ValueError(f"derived template value '{name}' must be a table")
×
414
    override_map: dict[str, Any] = {}
1✔
415
    if override is not _MISSING:
1✔
416
        override_map = _as_str_keyed_table(override, f"derived template value '{name}'")
1✔
417
    prefix = name
1✔
418
    if is_array:
1✔
419
        prefix = f"{name}{_array_slice(len(type_info.dimensions))}"
1✔
420
    return _derived_component_entries(
1✔
421
        prefix,
422
        properties,
423
        value_mode=value_mode,
424
        overrides=override_map,
425
        object_default=object_default,
426
        constants=constants,
427
    )
428

429

430
def _as_str_keyed_table(value: object, context: str) -> dict[str, Any]:
1✔
431
    if not isinstance(value, dict):
1✔
432
        raise ValueError(f"{context} must be a table")
×
433
    table: dict[str, Any] = {}
1✔
434
    for key, entry in value.items():
1✔
435
        if not isinstance(key, str):
1✔
436
            raise ValueError(f"{context} must use string keys")
1✔
437
        table[key] = entry
1✔
438
    return table
1✔
439

440

441
def _derived_component_entries(
1✔
442
    prefix: str,
443
    properties: dict[str, Any],
444
    *,
445
    value_mode: str,
446
    overrides: dict[str, Any],
447
    object_default: dict[str, Any],
448
    constants: dict[str, int] | None,
449
) -> list[tuple[str, str | None]]:
450
    for key in overrides:
1✔
451
        if key not in properties:
1✔
452
            raise ValueError(f"derived template value '{prefix}%{key}' is unknown")
×
453
    entries: list[tuple[str, str | None]] = []
1✔
454
    for child_name, child in properties.items():
1✔
455
        if not isinstance(child_name, str) or not isinstance(child, dict):
1✔
456
            raise ValueError(f"derived property '{prefix}' components must be schema objects")
×
457
        child_type = _field_type_info(child, constants)
1✔
458
        entries.extend(
1✔
459
            _value_entries(
460
                f"{prefix}%{child_name}",
461
                child,
462
                child_type,
463
                value_mode=value_mode,
464
                simple_derived_mode="components",
465
                override=overrides.get(child_name, _MISSING),
466
                constants=constants,
467
                fallback_default=object_default.get(child_name.lower(), _MISSING),
468
            )
469
        )
470
    return entries
1✔
471

472

473
def _derived_buffer_entries(
1✔
474
    name: str,
475
    derived: dict[str, Any],
476
    type_info: FieldTypeInfo,
477
    *,
478
    value_mode: str,
479
    override: Any,
480
    constants: dict[str, int] | None,
481
) -> list[tuple[str, str | None]]:
482
    properties = derived.get("properties")
1✔
483
    if not isinstance(properties, dict) or not properties:
1✔
484
        raise ValueError(f"derived property '{name}' must define properties")
×
485

486
    is_array = type_info.category == "array"
1✔
487
    object_default = derived_object_default(name, derived)
1✔
488
    coordinates: list[list[int]] = [[]]
1✔
489
    if is_array:
1✔
490
        dimensions = _parse_default_dimensions(type_info.dimensions, constants)
1✔
491
        coordinates = [
1✔
492
            [index + 1 for index in coordinate]
493
            for coordinate in _iter_fixed_indices(dimensions, "F")
494
        ]
495

496
    if value_mode in {"empty", "minimal-empty"}:
1✔
497
        return [(_derived_element_name(name, coordinate), None) for coordinate in coordinates]
1✔
498

499
    override_maps: list[dict[str, Any]]
500
    if is_array and override is not _MISSING:
1✔
501
        if not isinstance(override, list):
1✔
502
            raise ValueError(f"derived array template value '{name}' must be an array of tables")
×
503
        if len(override) > len(coordinates):
1✔
504
            raise ValueError(f"derived array template value '{name}' is longer than its shape")
×
505
        coordinates = coordinates[: len(override)]
1✔
506
        override_maps = []
1✔
507
        for index, value in enumerate(override, start=1):
1✔
508
            override_maps.append(
1✔
509
                _as_str_keyed_table(
510
                    value,
511
                    f"derived array template value '{name}[{index}]'",
512
                )
513
            )
514
    else:
515
        if override is not _MISSING and not isinstance(override, dict):
1✔
516
            raise ValueError(f"derived template value '{name}' must be a table")
×
517
        override_map = (
1✔
518
            _as_str_keyed_table(override, f"derived template value '{name}'")
519
            if override is not _MISSING
520
            else {}
521
        )
522
        override_maps = [override_map for _ in coordinates]
1✔
523

524
    entries: list[tuple[str, str | None]] = []
1✔
525
    for coordinate, overrides in zip(coordinates, override_maps):
1✔
526
        prefix = _derived_element_name(name, coordinate)
1✔
527
        entries.append(
1✔
528
            (
529
                prefix,
530
                _derived_buffer_value(
531
                    prefix,
532
                    properties,
533
                    overrides,
534
                    value_mode,
535
                    constants,
536
                    object_default,
537
                ),
538
            )
539
        )
540
    return entries
1✔
541

542

543
def _derived_element_name(name: str, coordinate: list[int]) -> str:
1✔
544
    if not coordinate:
1✔
545
        return name
1✔
546
    return f"{name}({','.join(str(index) for index in coordinate)})"
1✔
547

548

549
def _derived_buffer_value(
1✔
550
    prefix: str,
551
    properties: dict[str, Any],
552
    overrides: dict[str, Any],
553
    value_mode: str,
554
    constants: dict[str, int] | None,
555
    object_default: dict[str, Any] | None = None,
556
) -> str | None:
557
    for key in overrides:
1✔
558
        if key not in properties:
1✔
559
            raise ValueError(f"derived template value '{prefix}%{key}' is unknown")
×
560
    slots: list[str | None] = []
1✔
561
    selected_default = object_default or {}
1✔
562
    for child_name, child in properties.items():
1✔
563
        if not isinstance(child_name, str) or not isinstance(child, dict):
1✔
564
            raise ValueError(f"derived property '{prefix}' components must be schema objects")
×
565
        child_entries = _value_entries(
1✔
566
            child_name,
567
            child,
568
            _field_type_info(child, constants),
569
            value_mode=value_mode,
570
            simple_derived_mode="components",
571
            override=overrides.get(child_name, _MISSING),
572
            constants=constants,
573
            fallback_default=selected_default.get(child_name.lower(), _MISSING),
574
        )
575
        if len(child_entries) != 1:
1✔
576
            raise ValueError(f"derived property '{prefix}%{child_name}' must be scalar")
×
577
        slots.append(child_entries[0][1])
1✔
578
    while slots and slots[-1] is None:
1✔
579
        slots.pop()
×
580
    if not slots:
1✔
581
        return None
×
582
    return ", ".join("" if slot is None else slot for slot in slots)
1✔
583

584

585
def _array_list_entries(
1✔
586
    name: str,
587
    values: list[Any],
588
    type_info: FieldTypeInfo,
589
    prop: dict[str, Any],
590
    constants: dict[str, int] | None,
591
) -> list[tuple[str, str | None]]:
592
    rank = len(type_info.dimensions)
1✔
593
    if rank <= 1:
1✔
594
        entry_name = f"{name}{_array_slice(rank)}"
1✔
595
        return [(entry_name, _format_value_list(values, type_info))]
1✔
596

597
    dims = _parse_default_dimensions(type_info.dimensions, constants)
1✔
598
    order = _resolve_default_order(prop)
1✔
599
    slice_dim = _slice_dim_for_order(rank, order)
1✔
600
    slice_size = dims[slice_dim]
1✔
601
    fixed_dims = [dims[idx] for idx in range(rank) if idx != slice_dim]
1✔
602
    entries: list[tuple[str, str | None]] = []
1✔
603
    offset = 0
1✔
604
    for fixed_indices in _iter_fixed_indices(fixed_dims, order):
1✔
605
        if offset >= len(values):
1✔
606
            break
×
607
        chunk = values[offset : offset + slice_size]
1✔
608
        if not chunk:
1✔
609
            break
×
610
        entry_name = _slice_entry_name(
1✔
611
            name,
612
            rank,
613
            slice_dim,
614
            [index + 1 for index in fixed_indices],
615
        )
616
        entries.append((entry_name, _format_value_list(chunk, type_info)))
1✔
617
        offset += slice_size
1✔
618
    if not entries:
1✔
619
        entry_name = f"{name}{_array_slice(rank)}"
×
620
        entries.append((entry_name, _format_value_list(values, type_info)))
×
621
    return entries
1✔
622

623

624
def _slice_dim_for_order(rank: int, order: str) -> int:
1✔
625
    if order == "F":
1✔
626
        return 0
1✔
627
    return rank - 1
×
628

629

630
def _iter_fixed_indices(dims: list[int], order: str) -> Iterable[list[int]]:
1✔
631
    if not dims:
1✔
632
        yield []
×
633
        return
×
634
    indices = [0] * len(dims)
1✔
635
    while True:
1✔
636
        yield indices.copy()
1✔
637
        positions = range(len(dims)) if order == "F" else range(len(dims) - 1, -1, -1)
1✔
638
        for pos in positions:
1✔
639
            indices[pos] += 1
1✔
640
            if indices[pos] < dims[pos]:
1✔
641
                break
1✔
642
            indices[pos] = 0
1✔
643
        else:
644
            break
1✔
645

646

647
def _slice_entry_name(
1✔
648
    name: str,
649
    rank: int,
650
    slice_dim: int,
651
    fixed_indices: list[int],
652
) -> str:
653
    indices: list[str] = []
1✔
654
    fixed_iter = iter(fixed_indices)
1✔
655
    for idx in range(rank):
1✔
656
        if idx == slice_dim:
1✔
657
            indices.append(":")
1✔
658
        else:
659
            indices.append(str(next(fixed_iter)))
1✔
660
    return f"{name}({','.join(indices)})"
1✔
661

662

663
def _format_value_list(values: list[Any], type_info: FieldTypeInfo) -> str:
1✔
664
    formatted = [
1✔
665
        _format_scalar_default(value, None, type_info.element_category) for value in values
666
    ]
667
    return ", ".join(formatted)
1✔
668

669

670
def _entries_from_value(
1✔
671
    name: str,
672
    value: Any,
673
    type_info: FieldTypeInfo,
674
    prop: dict[str, Any],
675
    constants: dict[str, int] | None,
676
) -> list[tuple[str, str | None]]:
677
    if type_info.category == "array":
1✔
678
        if isinstance(value, list):
×
679
            _validate_example_list(value)
×
680
            return _array_list_entries(name, value, type_info, prop, constants)
×
681
        scalar = _format_scalar_default(value, None, type_info.element_category)
×
682
        entry_name = f"{name}{_array_slice(len(type_info.dimensions))}"
×
683
        return [(entry_name, scalar)]
×
684
    if isinstance(value, list):
1✔
685
        raise ValueError("scalar template values must not be lists")
×
686
    scalar = _format_scalar_default(value, None, type_info.category)
1✔
687
    return [(name, scalar)]
1✔
688

689

690
def _get_first_example(prop: dict[str, Any], type_info: FieldTypeInfo) -> Any | None:
1✔
691
    examples = prop.get("examples")
1✔
692
    if examples is None:
1✔
693
        return None
1✔
694
    if not isinstance(examples, list):
1✔
695
        raise ValueError("property examples must be a list")
×
696
    if not examples:
1✔
697
        return None
×
698
    first = examples[0]
1✔
699
    if type_info.category == "array":
1✔
700
        if isinstance(first, list):
×
701
            _validate_example_list(first)
×
702
        return first
×
703
    if isinstance(first, list):
1✔
704
        raise ValueError("scalar examples must not be lists")
×
705
    return first
1✔
706

707

708
def _validate_example_list(values: list[Any]) -> None:
1✔
709
    for value in values:
×
710
        if isinstance(value, list):
×
711
            raise ValueError("array examples must be flat lists")
×
712

713

714
def _fallback_scalar_value(type_info: FieldTypeInfo) -> Any:
1✔
715
    category = type_info.element_category if type_info.category == "array" else type_info.category
1✔
716
    if category == "integer":
1✔
717
        return 0
1✔
718
    if category == "real":
1✔
719
        return 0.0
1✔
720
    if category == "boolean":
1✔
721
        return False
×
722
    if category == "string":
1✔
723
        return ""
1✔
724
    raise ValueError(f"unsupported template category '{category}'")
×
725

726

727
def _validate_kind_allowlist(
1✔
728
    type_info: FieldTypeInfo,
729
    kind_map: dict[str, str] | None,
730
    allowlist: set[str] | None,
731
) -> None:
732
    if type_info.kind is None or allowlist is None:
1✔
733
        return
1✔
734
    kind_id = type_info.kind
1✔
735
    mapped = None
1✔
736
    if kind_map is not None and kind_id in kind_map:
1✔
737
        mapped = kind_map[kind_id]
1✔
738
        if not isinstance(mapped, str) or not mapped:
1✔
739
            raise ValueError(f"kind map target for '{kind_id}' must be a string")
×
740
        if mapped not in allowlist:
1✔
741
            raise ValueError(
×
742
                f"kind map target '{mapped}' for '{kind_id}' not present in kind module list"
743
            )
744
        return
1✔
745
    if kind_id not in allowlist:
×
746
        raise ValueError(f"kind '{kind_id}' not present in kind module list")
×
747

748

749
def _resolve_default_order(prop: dict[str, Any]) -> str:
1✔
750
    order_raw = prop.get("x-fortran-default-order", "F")
1✔
751
    if not isinstance(order_raw, str):
1✔
752
        raise ValueError("array default order must be 'F' or 'C'")
×
753
    order = order_raw.upper()
1✔
754
    if order not in {"F", "C"}:
1✔
755
        raise ValueError("array default order must be 'F' or 'C'")
×
756
    return order
1✔
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