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

safl / senfd / 14564770855

16 Apr 2025 08:38AM UTC coverage: 96.2% (-0.08%) from 96.283%
14564770855

push

github

safl
feat(enriched): add more stats to document

Total number of figures added to the top of the enriched document, along
with the number of categorized, uncategorized, non-tabular, and skipped
figures. The maximum figure number is also added to be able to compare
figure number and total number of figures.

Signed-off-by: Nadja Brix Koch <n.koch@samsung.com>

7 of 8 new or added lines in 1 file covered. (87.5%)

1 existing line in 1 file now uncovered.

810 of 842 relevant lines covered (96.2%)

0.96 hits per line

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

96.82
/src/senfd/documents/enriched.py
1
import inspect
1✔
2
import re
1✔
3
from argparse import Namespace
1✔
4
from pathlib import Path
1✔
5
from typing import ClassVar, Dict, List, Optional, Tuple, Type, TypeVar
1✔
6

7
from pydantic import Field
1✔
8

9
import senfd.models
1✔
10
import senfd.schemas
1✔
11
import senfd.tables
1✔
12
from senfd.documents.base import (
1✔
13
    TRANSLATION_TABLE,
14
    Converter,
15
    Document,
16
    strip_all_suffixes,
17
)
18
from senfd.documents.plain import Figure, FigureDocument
1✔
19
from senfd.errors import Error
1✔
20
from senfd.skiplist import SkipPatterns, SkipElement
1✔
21
from senfd.utils import pascal_to_snake
1✔
22

23
REGEX_ALL = r"(?P<all>.*)"
1✔
24

25
REGEX_VAL_NUMBER_OPTIONAL = r"(?P<number>\d+)?.*"
1✔
26
REGEX_VAL_HEXSTR = r"^(?P<hex>[a-zA-Z0-9]{1,2}h)$"
1✔
27
REGEX_VAL_NAME = r"^(?P<name>[a-zA-Z -/]*)[ \d]*$"
1✔
28
REGEX_VAL_FIELD_DESCRIPTION = (
1✔
29
    r"(?P<name>[ \/\-\w]+)" r"(\((?P<acronym>[^\)]+)\))?" r"(:\s*(?P<description>.*))?"
30
)
31
REGEX_VAL_VALUE_DESCRIPTION = r"(?P<name>[ \w]+)" r"(:\s*(?P<description>.*))?"
1✔
32
REGEX_VAL_REQUIREMENT = r"^(?:(?P<requirement>O|M|P|NR|Note)(?:[ \d]*))?$"
1✔
33
REGEX_VAL_REFERENCE = r"^(?P<reference>\d+\.\d+(?:\.\d+)?(?:\.\d+)?(?:\.\d+)?)$"
1✔
34
REGEX_VAL_YESNO = r"(?P<yn>NOTE|Note|Yes|No|Y|N)[ \d]*?"
1✔
35

36
REGEX_HDR_EXPLANATION = r"(Definition|Description).*"
1✔
37

38
REGEX_GRID_RANGE = (
1✔
39
    r"(Bits|Bytes).*",
40
    r"(?!Note|specficiation)(?:(?P<upper>[0-9 \w\+\*]+):)?(?P<lower>[0-9 \w\+\*]+)",
41
)
42
REGEX_GRID_ACRONYM = (r"(Term|Acronym).*", REGEX_ALL.replace("all", "term"))
1✔
43
REGEX_GRID_SCOPE = (
1✔
44
    r"(Scope|Scope.and.Support).*",
45
    REGEX_ALL.replace("all", "scope"),
46
)
47
REGEX_GRID_FIELD_DESCRIPTION = (REGEX_HDR_EXPLANATION, REGEX_VAL_FIELD_DESCRIPTION)
1✔
48
REGEX_GRID_VALUE_DESCRIPTION = (REGEX_HDR_EXPLANATION, REGEX_VAL_VALUE_DESCRIPTION)
1✔
49
REGEX_GRID_EXPLANATION = (
1✔
50
    REGEX_HDR_EXPLANATION,
51
    REGEX_ALL.replace("all", "description"),
52
)
53
REGEX_GRID_FEATURE_NAME = (
1✔
54
    r"(Feature.Name).*",
55
    REGEX_VAL_NAME.replace("name", "feature_name"),
56
)
57
REGEX_GRID_NAME = (
1✔
58
    r".*(Name).*",
59
    REGEX_VAL_NAME,
60
)
61
REGEX_GRID_FEATURE_IDENTIFIER = (
1✔
62
    r"^(Feature|Log Page).Identifier.*",
63
    REGEX_VAL_HEXSTR.replace("hex", "identifier"),
64
)
65
REGEX_GRID_FEATURE_PAPCR = (
1✔
66
    r"(Persistent.Across.Power.Cycle.and.Reset)",
67
    REGEX_VAL_YESNO.replace("<yn>", "<persist>"),
68
)
69
REGEX_GRID_FEATURE_UMBFA = (
1✔
70
    r"(Uses.Memory.Buffer.for.Attributes)",
71
    REGEX_VAL_YESNO.replace("<yn>", "<membuf>"),
72
)
73
REGEX_GRID_REQUIREMENTS = (
1✔
74
    r"^(((:?Command|Feature).+Support.+Requirements)|(:?O\/M)).*$",
75
    REGEX_VAL_REQUIREMENT,
76
)
77
REGEX_GRID_BITS_FUNCTION = (
1✔
78
    r"(Bits|Function).*",
79
    r"(?P<bitstr>\d{4}\s\d{2}b)",
80
)
81
REGEX_GRID_COMMAND_OPCODE = (
1✔
82
    r"(Combined.Opcode|Opcode.Value).*",
83
    REGEX_VAL_HEXSTR.replace("hex", "opcode"),
84
)
85
REGEX_GRID_COMMAND_NAME = (
1✔
86
    r"(Command).*",
87
    REGEX_VAL_NAME.replace("name", "command_name"),
88
)
89
REGEX_GRID_BITS_TRANSFER = (r"(Data.Transfer).*", r"(?P<function>\d{2}b)")
1✔
90
REGEX_GRID_REFERENCE = (
1✔
91
    r"(Reference).*",
92
    REGEX_ALL.replace("all", "reference"),
93
)
94
REGEX_GRID_USES_NSID = (
1✔
95
    r"(Namespace.Identifier.Used|NSID).*",
96
    REGEX_VAL_YESNO.replace("yn", "uses_nsid"),
97
)
98
REGEX_GRID_USES_CNTID = (r"(CNTID).*", REGEX_VAL_YESNO.replace("yn", "uses_cntid"))
1✔
99
REGEX_GRID_USES_CSI = (r"(CSI).*", REGEX_VAL_YESNO.replace("yn", "uses_csi"))
1✔
100
REGEX_GRID_VALUE = (r"(Value).*", REGEX_VAL_HEXSTR.replace("hex", "value"))
1✔
101

102
REGEX_GRID_TYPE = (r".*(Value|Type).*", REGEX_VAL_HEXSTR.replace("hex", "value"))
1✔
103
REGEX_GRID_TYPE_DESCRIPTION = (
1✔
104
    r".*(Event|Definition|Description).*",
105
    REGEX_VAL_VALUE_DESCRIPTION,
106
)
107

108
REGEX_GRID_LPI = (
1✔
109
    r"(Log.Page.Identifier).*",
110
    REGEX_VAL_HEXSTR.replace("hex", "log_page_identifier"),
111
)
112
REGEX_GRID_LPN = (r"(Log.Page.Name).*", REGEX_ALL.replace("all", "log_page_name"))
1✔
113
REGEX_GRID_COMMANDS_AFFECTED = (
1✔
114
    r"(Commands.Affected).*",
115
    REGEX_ALL.replace("all", "comma"),
116
)
117
REGEX_GRID_IO = (
1✔
118
    r"(I/O).*",
119
    REGEX_VAL_REQUIREMENT.replace("requirement", "req_io"),
120
)
121
REGEX_GRID_ADMIN = (
1✔
122
    r"(Admin).*",
123
    REGEX_VAL_REQUIREMENT.replace("requirement", "req_admin"),
124
)
125
REGEX_GRID_DISCOVERY = (
1✔
126
    r"(Disc).*",
127
    REGEX_VAL_REQUIREMENT.replace("requirement", "req_discovery"),
128
)
129

130

131
class EnrichedFigure(Figure):
1✔
132
    REGEX_FIGURE_DESCRIPTION: ClassVar[str]
1✔
133
    REGEX_GRID: ClassVar[List[Tuple]]
1✔
134

135
    grid: senfd.tables.Grid = Field(default_factory=senfd.tables.Grid)
1✔
136

137
    def into_document(self, document):
1✔
138
        key = pascal_to_snake(self.__class__.__name__).replace("_figure", "")
1✔
139
        getattr(document, key).append(self)
1✔
140

141

142
class DataStructureFigure(EnrichedFigure):
1✔
143
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
144
        r"^(?!.*Status Code|.*Vendor|.*Log.Page.Identifiers|.*Types).*(Log.Page|Data.Structure|Data).*$"
145
    )
146
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
147
        REGEX_GRID_RANGE,
148
        REGEX_GRID_FIELD_DESCRIPTION,
149
    ]
150

151

152
class IdentifyDataStructureFigure(EnrichedFigure):
1✔
153
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r".*Identify.*Data.Structure.*"
1✔
154
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
155
        REGEX_GRID_RANGE,
156
        REGEX_GRID_IO,
157
        REGEX_GRID_ADMIN,
158
        REGEX_GRID_DISCOVERY,
159
        REGEX_GRID_FIELD_DESCRIPTION,
160
    ]
161

162

163
class DataTypeFigure(EnrichedFigure):
1✔
164
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r"^.*(Types).*$"
1✔
165
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
166
        REGEX_GRID_TYPE,
167
        REGEX_GRID_TYPE_DESCRIPTION,
168
    ]
169

170

171
class AcronymsFigure(EnrichedFigure):
1✔
172
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r".*Acronym\s+(definitions|Descriptions)"
1✔
173
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
174
        REGEX_GRID_ACRONYM,
175
        REGEX_GRID_EXPLANATION,
176
    ]
177

178

179
class AsynchronousEventInformationFigure(EnrichedFigure):
1✔
180
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
181
        r"^(Asynchronous.Event.Information.-)(?P<event>.*)$"
182
    )
183
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
184
        REGEX_GRID_VALUE,
185
        REGEX_GRID_VALUE_DESCRIPTION,
186
    ]
187

188
    event: str
1✔
189

190

191
class IoControllerCommandSetSupportRequirementFigure(EnrichedFigure):
1✔
192
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
193
        r".*-\s+(?P<command_set_name>.*)Command\s+Set\s+Support"
194
    )
195
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
196
        REGEX_GRID_COMMAND_NAME,
197
        REGEX_GRID_REQUIREMENTS,
198
    ]
199

200
    command_set_name: str
1✔
201

202

203
class CommandSupportRequirementFigure(EnrichedFigure):
1✔
204
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
205
        r"\s*(?P<command_span>.*)\s+Command\s*Support\s*Requirements.*"
206
    )
207
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
208
        REGEX_GRID_COMMAND_NAME,
209
        REGEX_GRID_COMMAND_OPCODE,
210
        REGEX_GRID_IO,
211
        REGEX_GRID_ADMIN,
212
        REGEX_GRID_DISCOVERY,
213
        REGEX_GRID_REFERENCE,
214
    ]
215

216
    command_span: str
1✔
217

218

219
class CnsValueFigure(EnrichedFigure):
1✔
220
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r".*CNS\s+Values.*"
1✔
221
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
222
        (r"(CNS.Value).*", REGEX_VAL_HEXSTR.replace("hex", "cns_value")),
223
        (r"(O\/M).*", REGEX_VAL_REQUIREMENT),
224
        REGEX_GRID_EXPLANATION,
225
        REGEX_GRID_USES_NSID,
226
        REGEX_GRID_USES_CNTID,
227
        REGEX_GRID_USES_CSI,
228
        REGEX_GRID_REFERENCE,
229
    ]
230

231

232
class CommandSqeDataPointerFigure(EnrichedFigure):
1✔
233
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
234
        r"(?P<command_name>[\w\s]+)\s+-\s+Data\s+Pointer"
235
    )
236
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
237
        REGEX_GRID_RANGE,
238
        REGEX_GRID_FIELD_DESCRIPTION,
239
    ]
240

241
    command_name: str
1✔
242

243

244
class ExampleFigure(EnrichedFigure):
1✔
245
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r".*(Example|example).*"
1✔
246
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
247
        REGEX_GRID_RANGE,
248
        REGEX_GRID_FIELD_DESCRIPTION,
249
    ]
250

251

252
class CommandSqeMetadataPointer(EnrichedFigure):
1✔
253
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
254
        r"(?P<command_name>[\w\s]+)\s+-\s+Metadata\s+Pointer"
255
    )
256
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
257
        REGEX_GRID_RANGE,
258
        REGEX_GRID_FIELD_DESCRIPTION,
259
    ]
260

261
    command_name: str
1✔
262

263

264
class CommandSqeDwordLowerUpperFigure(EnrichedFigure):
1✔
265
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
266
        r"(?P<command_name>[\w\s]+)\s*-\s*Command\s*Dword\s*"
267
        r"(?P<command_dword_lower>\d+)"
268
        r".*and.*?\s(?P<command_dword_upper>\d+)$"
269
    )
270
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
271
        REGEX_GRID_RANGE,
272
        REGEX_GRID_FIELD_DESCRIPTION,
273
    ]
274

275
    command_name: str
1✔
276
    command_dword_lower: int
1✔
277
    command_dword_upper: int
1✔
278

279

280
class CommandSqeDwordFigure(EnrichedFigure):
1✔
281
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
282
        r"^(?P<command_name>[-a-zA-Z\w\s\/]+(?:\(\w*\))?)\s*[-–—]\s+"
283
        r"Command\s*Dword\s*(?P<command_dword>\d+)$"
284
    )
285
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
286
        REGEX_GRID_RANGE,
287
        REGEX_GRID_FIELD_DESCRIPTION,
288
    ]
289

290
    command_name: str
1✔
291
    command_dword: int
1✔
292

293

294
class IdentifyCommandSqeDwordFigure(EnrichedFigure):
1✔
295
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
296
        r"^Command\s*Dword\s*(?P<command_dword>\d+).-.CNS.Specific.Identifier$"
297
    )
298
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
299
        REGEX_GRID_RANGE,
300
        REGEX_GRID_FIELD_DESCRIPTION,
301
    ]
302

303
    command_name: str = "Identify"
1✔
304
    command_dword: int
1✔
305

306

307
class CommandCqeDwordFigure(EnrichedFigure):
1✔
308
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
309
        r"(?P<command_name>[\w\s]+)\s+-\s+"
310
        r"Completion\sQueue\sEntry\sDword\s(?P<command_dword>\d+)"
311
    )
312
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
313
        REGEX_GRID_RANGE,
314
        REGEX_GRID_FIELD_DESCRIPTION,
315
    ]
316

317
    command_name: str
1✔
318
    command_dword: str
1✔
319

320

321
class CommandAdminOpcodeFigure(EnrichedFigure):
1✔
322
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
323
        r"Opcodes.for.(?P<command_set_name>Admin).Commands"
324
    )
325
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
326
        REGEX_GRID_BITS_FUNCTION,
327
        REGEX_GRID_BITS_TRANSFER,
328
        REGEX_GRID_COMMAND_OPCODE,
329
        REGEX_GRID_USES_NSID,
330
        REGEX_GRID_COMMAND_NAME,
331
        REGEX_GRID_REFERENCE,
332
    ]
333

334
    command_set_name: str
1✔
335

336

337
class CommandIoOpcodeFigure(EnrichedFigure):
1✔
338
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
339
        r"Opcodes\sfor\s(?P<command_set_name>.*?)"
340
        r"\s(Commands|Command Set|Command Set Commands)"
341
    )
342
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
343
        REGEX_GRID_BITS_FUNCTION,
344
        REGEX_GRID_BITS_TRANSFER,
345
        REGEX_GRID_COMMAND_OPCODE,
346
        REGEX_GRID_COMMAND_NAME,
347
        REGEX_GRID_REFERENCE,
348
    ]
349

350
    command_set_name: str
1✔
351

352

353
class GeneralCommandStatusValueFigure(EnrichedFigure):
1✔
354
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r".*General.Command.Status.Values.*"
1✔
355
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
356
        REGEX_GRID_VALUE,
357
        REGEX_GRID_VALUE_DESCRIPTION,
358
        REGEX_GRID_COMMANDS_AFFECTED,
359
    ]
360

361

362
class GenericCommandStatusValueFigure(EnrichedFigure):
1✔
363
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
364
        r"(?P<command_name>[a-zA-Z -/]*).-.Generic.Command.Status.Values.*"
365
    )
366
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
367
        REGEX_GRID_VALUE,
368
        REGEX_GRID_VALUE_DESCRIPTION,
369
    ]
370

371

372
class CommandSpecificStatusValueFigure(EnrichedFigure):
1✔
373
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
374
        r"(?P<command_name>[\w\s]+)\s+-\s+Command\s+Specific\s+Status\s+Values"
375
    )
376
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
377
        REGEX_GRID_VALUE,
378
        REGEX_GRID_VALUE_DESCRIPTION,
379
        REGEX_GRID_COMMANDS_AFFECTED,
380
    ]
381
    command_name: str
1✔
382

383

384
class FeatureIdentifierFigure(EnrichedFigure):
1✔
385
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r".*Feature\s*Identifiers.*"
1✔
386
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
387
        REGEX_GRID_FEATURE_IDENTIFIER,
388
        REGEX_GRID_FEATURE_PAPCR,
389
        REGEX_GRID_FEATURE_UMBFA,
390
        REGEX_GRID_EXPLANATION,
391
        REGEX_GRID_SCOPE,
392
    ]
393

394

395
class VersionDescriptorFieldValueFigure(EnrichedFigure):
1✔
396
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r"^.*Version Descriptor Field Values$"
1✔
397
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
398
        (r"(Specification.Version.).*", r"^(?P<version>\d\.\d)$"),
399
        (r"(MJR.Field).*", REGEX_VAL_HEXSTR.replace("hex", "version_major")),
400
        (r"(MNR.Field).*", REGEX_VAL_HEXSTR.replace("hex", "version_minor")),
401
        (r"(TER.Field).*", REGEX_VAL_HEXSTR.replace("hex", "version_tertiary")),
402
    ]
403

404

405
class HostSoftwareSpecifiedFieldFigure(EnrichedFigure):
1✔
406
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r"^.*-.Host Software Specified Fields$"
1✔
407
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
408
        REGEX_GRID_RANGE,
409
        REGEX_GRID_FIELD_DESCRIPTION,
410
    ]
411

412

413
class FeatureSupportFigure(EnrichedFigure):
1✔
414
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r"^I.O.Controller.-.Feature.Support$"
1✔
415
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
416
        REGEX_GRID_FEATURE_NAME,
417
        REGEX_GRID_REQUIREMENTS,
418
    ]
419

420

421
class LogPageIdentifierFigure(EnrichedFigure):
1✔
422
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r".*Log\s+Page\s+Identifiers.*"
1✔
423
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
424
        REGEX_GRID_LPI,
425
        REGEX_GRID_SCOPE,
426
        REGEX_GRID_LPN,
427
        REGEX_GRID_REFERENCE,
428
    ]
429

430

431
class OffsetFigure(EnrichedFigure):
1✔
432
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r".*offset"
1✔
433
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
434
        REGEX_GRID_RANGE,
435
        (r"(Type).*", REGEX_ALL),
436
        (r"(Reset).*", REGEX_VAL_HEXSTR.replace("hex", "reset")),
437
        REGEX_GRID_EXPLANATION,
438
    ]
439

440

441
class ParameterFieldFigure(EnrichedFigure):
1✔
442
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r"^.*(Parameter.Field)$"
1✔
443
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
444
        REGEX_GRID_RANGE,
445
        REGEX_GRID_FIELD_DESCRIPTION,
446
    ]
447

448

449
class SubmissionQueueFigure(EnrichedFigure):
1✔
450
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r".*(Submission.Queue.Entry).*"
1✔
451
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
452
        REGEX_GRID_RANGE,
453
        REGEX_GRID_FIELD_DESCRIPTION,
454
    ]
455

456

457
class DescriptorFigure(EnrichedFigure):
1✔
458
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r"^.*(Descriptor)$"
1✔
459
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
460
        REGEX_GRID_RANGE,
461
        REGEX_GRID_FIELD_DESCRIPTION,
462
    ]
463

464

465
class CompletionQueueFigure(EnrichedFigure):
1✔
466
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
467
        r"^(?:Fabrics.Response.-)?(Completion.Queue).*$"
468
    )
469
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
470
        REGEX_GRID_RANGE,
471
        REGEX_GRID_FIELD_DESCRIPTION,
472
    ]
473

474

475
class PropertyDefinitionFigure(EnrichedFigure):
1✔
476
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r".*Property Definition.*"
1✔
477
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
478
        (r"(Offset.\(OFST\)).*", REGEX_VAL_HEXSTR),
479
        (r"(Size.\(in.bytes\)).*", REGEX_VAL_NUMBER_OPTIONAL),
480
        (
481
            r"(I/O Controller).*",
482
            REGEX_VAL_REQUIREMENT.replace("requirement", "req_ioc"),
483
        ),
484
        (
485
            r"(Administrative.Controller).*",
486
            REGEX_VAL_REQUIREMENT.replace("requirement", "req_ac"),
487
        ),
488
        (
489
            r"(Discovery.Controller).*",
490
            REGEX_VAL_REQUIREMENT.replace("requirement", "req_dc"),
491
        ),
492
        (r"(Name).*", REGEX_VAL_FIELD_DESCRIPTION),
493
    ]
494

495

496
class StatusCodeFigure(EnrichedFigure):
1✔
497
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r"^(Status.Code.-).*$"
1✔
498
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
499
        REGEX_GRID_VALUE,
500
        REGEX_GRID_VALUE_DESCRIPTION,
501
    ]
502

503

504
class StatusValueFigure(EnrichedFigure):
1✔
505
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r"^.*(Status.Value).*$"
1✔
506
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
507
        REGEX_GRID_VALUE,
508
        REGEX_GRID_VALUE_DESCRIPTION,
509
    ]
510

511

512
class FormatFigure(EnrichedFigure):
1✔
513
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = (
1✔
514
        r"^(?!.*IEEE|Sanitize.Operations).*\s(Format).*$"
515
    )
516
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
517
        REGEX_GRID_RANGE,
518
        REGEX_GRID_FIELD_DESCRIPTION,
519
    ]
520

521

522
class RequirementsFigure(EnrichedFigure):
1✔
523
    REGEX_FIGURE_DESCRIPTION: ClassVar[str] = r"^.*(Requirements)$"
1✔
524
    REGEX_GRID: ClassVar[List[Tuple]] = [
1✔
525
        REGEX_GRID_NAME,
526
        REGEX_GRID_FEATURE_IDENTIFIER,
527
        REGEX_GRID_IO,
528
        REGEX_GRID_ADMIN,
529
        REGEX_GRID_DISCOVERY,
530
        REGEX_GRID_REFERENCE,
531
    ]
532

533

534
class EnrichedFigureDocument(Document):
1✔
535
    SUFFIX_JSON: ClassVar[str] = ".enriched.figure.document.json"
1✔
536
    SUFFIX_HTML: ClassVar[str] = ".enriched.figure.document.html"
1✔
537

538
    FILENAME_SCHEMA: ClassVar[str] = "enriched.figure.document.schema.json"
1✔
539
    FILENAME_HTML_TEMPLATE: ClassVar[str] = "enriched.figure.document.html.jinja2"
1✔
540

541
    skip_map: Dict[int, SkipElement] = {}
1✔
542
    stats: Dict[str, int] = {
1✔
543
        "skipped": 0,
544
        "nontabular": 0,
545
        "uncategorized": 0,
546
        "categorized": 0,
547
        "max_figure_number": 0,
548
    }
549

550
    acronyms: List[AcronymsFigure] = Field(default_factory=list)
1✔
551
    data_structure: List[DataStructureFigure] = Field(default_factory=list)
1✔
552
    example: List[ExampleFigure] = Field(default_factory=list)
1✔
553
    io_controller_command_set_support_requirement: List[
1✔
554
        IoControllerCommandSetSupportRequirementFigure
555
    ] = Field(default_factory=list)
556
    command_admin_opcode: List[CommandAdminOpcodeFigure] = Field(default_factory=list)
1✔
557
    command_io_opcode: List[CommandIoOpcodeFigure] = Field(default_factory=list)
1✔
558
    command_support_requirement: List[CommandSupportRequirementFigure] = Field(
1✔
559
        default_factory=list
560
    )
561
    identify_data_structure: List[IdentifyDataStructureFigure] = Field(
1✔
562
        default_factory=list
563
    )
564
    identify_command_sqe_dword: List[IdentifyCommandSqeDwordFigure] = Field(
1✔
565
        default_factory=list
566
    )
567
    command_sqe_dword: List[CommandSqeDwordFigure] = Field(default_factory=list)
1✔
568
    command_sqe_dword_lower_upper: List[CommandSqeDwordLowerUpperFigure] = Field(
1✔
569
        default_factory=list
570
    )
571
    command_sqe_data_pointer: List[CommandSqeDataPointerFigure] = Field(
1✔
572
        default_factory=list
573
    )
574
    command_sqe_metadata_pointer: List[CommandSqeMetadataPointer] = Field(
1✔
575
        default_factory=list
576
    )
577
    command_cqe_dword: List[CommandCqeDwordFigure] = Field(default_factory=list)
1✔
578
    command_specific_status_value: List[CommandSpecificStatusValueFigure] = Field(
1✔
579
        default_factory=list
580
    )
581
    general_command_status_value: List[GeneralCommandStatusValueFigure] = Field(
1✔
582
        default_factory=list
583
    )
584
    generic_command_status_value: List[GenericCommandStatusValueFigure] = Field(
1✔
585
        default_factory=list
586
    )
587
    cns_value: List[CnsValueFigure] = Field(default_factory=list)
1✔
588
    feature_support: List[FeatureSupportFigure] = Field(default_factory=list)
1✔
589
    feature_identifier: List[FeatureIdentifierFigure] = Field(default_factory=list)
1✔
590
    log_page_identifier: List[LogPageIdentifierFigure] = Field(default_factory=list)
1✔
591
    offset: List[OffsetFigure] = Field(default_factory=list)
1✔
592
    property_definition: List[PropertyDefinitionFigure] = Field(default_factory=list)
1✔
593
    descriptor: List[DescriptorFigure] = Field(default_factory=list)
1✔
594
    completion_queue: List[CompletionQueueFigure] = Field(default_factory=list)
1✔
595
    parameter_field: List[ParameterFieldFigure] = Field(default_factory=list)
1✔
596
    submission_queue: List[SubmissionQueueFigure] = Field(default_factory=list)
1✔
597
    status_code: List[StatusCodeFigure] = Field(default_factory=list)
1✔
598
    status_value: List[StatusValueFigure] = Field(default_factory=list)
1✔
599
    format: List[FormatFigure] = Field(default_factory=list)
1✔
600
    asynchronous_event_information: List[AsynchronousEventInformationFigure] = Field(
1✔
601
        default_factory=list
602
    )
603
    requirements: List[RequirementsFigure] = Field(default_factory=list)
1✔
604

605
    data_type: List[DataTypeFigure] = Field(default_factory=list)
1✔
606
    version_descriptor_field_value: List[VersionDescriptorFieldValueFigure] = Field(
1✔
607
        default_factory=list
608
    )
609

610
    host_software_specified_field: List[HostSoftwareSpecifiedFieldFigure] = Field(
1✔
611
        default_factory=list
612
    )
613

614
    nontabular: List[Figure] = Field(default_factory=list)
1✔
615
    uncategorized: List[Figure] = Field(default_factory=list)
1✔
616
    skipped: List[Figure] = Field(default_factory=list)
1✔
617

618

619
# EnrichedFigureType is a virtual type that binds to `EnrichedFigure` such
620
# that it enforces that `EnrichedFigureType` needs to be a child of `EnrichedFigure`
621
EnrichedFigureType = TypeVar("EnrichedFigureType", bound=EnrichedFigure)
1✔
622

623

624
class FromFigureDocument(Converter):
1✔
625
    """
626
    Constructs an EnrichedDocument from a given PlainDocument
627

628
    Figures are enriched by extraction, type coercion, and conversion of using data from
629
    the figure description and table content.
630
    """
631

632
    @staticmethod
1✔
633
    def is_applicable(path: Path) -> bool:
1✔
634
        return "".join(path.suffixes).lower() == ".plain.figure.document.json"
1✔
635

636
    @staticmethod
1✔
637
    def check_regex(figure, match) -> List[senfd.errors.Error]:
1✔
638
        shared = set(figure.model_dump().keys()).intersection(
1✔
639
            set(match.groupdict().keys())
640
        )
641
        if shared:
1✔
642
            return [
1✔
643
                senfd.errors.ImplementationError(
644
                    message=f"cls({figure.__class__.__name__}) has overlap({shared})"
645
                )
646
            ]
647

648
        return []
1✔
649

650
    @staticmethod
1✔
651
    def check_table_data(
1✔
652
        figure: EnrichedFigure,
653
    ) -> Tuple[Optional[senfd.errors.Error], List[senfd.errors.Error]]:
654
        """Check for blocking errors, for which enrichment cannot continue"""
655

656
        if figure.table is None:
1✔
657
            return (
×
658
                senfd.errors.FigureTableMissingError(
659
                    figure_nr=figure.figure_nr, message="Missing table"
660
                ),
661
                [],
662
            )
663
        if len(figure.table.rows) < 2:
1✔
664
            return (
×
665
                senfd.errors.FigureTableMissingRowsError(
666
                    figure_nr=figure.figure_nr, message=r"Number of rows < 2"
667
                ),
668
                [],
669
            )
670

671
        lengths = list(set([len(row.cells) for row in figure.table.rows[1:]]))
1✔
672
        if len(lengths) != 1:
1✔
673
            return (
×
674
                senfd.errors.IrregularTableError(
675
                    figure_nr=figure.figure_nr,
676
                    message=f"Varying row lengths({lengths})",
677
                    lengths=lengths,
678
                ),
679
                [],
680
            )
681

682
        return None, []
1✔
683

684
    @staticmethod
1✔
685
    def check_grid(figure: EnrichedFigure) -> List[senfd.errors.Error]:
1✔
686
        """
687
        Checks the state of the grid, assuming state after enrichment, thus expecting
688
        the grid to contain headers, fields, and value. Returning error(s) if it does
689
        not.
690
        """
691

692
        errors: List[senfd.errors.Error] = []
1✔
693

694
        if not figure.grid.headers:
1✔
695
            errors.append(
1✔
696
                senfd.errors.FigureNoGridHeaders(
697
                    figure_nr=figure.figure_nr,
698
                    message=(
699
                        "Grid is missing headers;"
700
                        f" check {figure.__class__.__name__}.REGEX_GRID"
701
                    ),
702
                )
703
            )
704

705
        if not figure.grid.fields:
1✔
706
            errors.append(
1✔
707
                senfd.errors.FigureNoGridHeaders(
708
                    figure_nr=figure.figure_nr,
709
                    message=(
710
                        "Grid is missing fields;"
711
                        f" check {figure.__class__.__name__}.REGEX_GRID"
712
                    ),
713
                )
714
            )
715

716
        if not figure.grid.values:
1✔
717
            errors.append(
1✔
718
                senfd.errors.FigureNoGridValues(
719
                    figure_nr=figure.figure_nr,
720
                    message=(
721
                        "Grid is missing values;"
722
                        f" check {figure.__class__.__name__}.REGEX_GRID"
723
                    ),
724
                )
725
            )
726

727
        return errors
1✔
728

729
    @staticmethod
1✔
730
    def enrich(
1✔
731
        cls: Type[EnrichedFigureType], figure: Figure, match: re.Match
732
    ) -> Tuple[Optional[EnrichedFigure], List[Error]]:
733
        """Returns an EnrichedFigure from the given Figure"""
734

735
        errors: List[Error] = []
1✔
736

737
        # Merge figure data with fields from regex
738
        data = figure.model_dump()
1✔
739
        mdict = match.groupdict()
1✔
740
        if mdict:
1✔
741
            data.update(mdict if mdict else {})
1✔
742

743
        enriched: EnrichedFigure = cls(**data)
1✔
744

745
        # Check for non-blocking error-conditions
746
        errors += FromFigureDocument.check_regex(enriched, match)
1✔
747
        error, non_blocking = FromFigureDocument.check_table_data(enriched)
1✔
748
        errors += non_blocking
1✔
749
        if error:
1✔
750
            errors.append(error)
×
751
            return None, errors
×
752

753
        regex_hdr, regex_val = zip(*enriched.REGEX_GRID)
1✔
754

755
        header_names: List[str] = []
1✔
756
        column_indices: List[int] = []  # which columns matches the REGEX_GRID/Headers
1✔
757
        fields: List[str] = []
1✔
758
        values: List[List[str | int]] = []
1✔
759

760
        assert enriched.table
1✔
761

762
        for row_idx, row in enumerate(enriched.table.rows[1:], 1):
1✔
763
            if not header_names:
1✔
764
                header_matches = []
1✔
765
                rgx_idx, cell_idx = 0, 0
1✔
766
                while rgx_idx < len(regex_hdr) and cell_idx < len(row.cells):
1✔
767
                    m = re.match(
1✔
768
                        regex_hdr[rgx_idx],
769
                        row.cells[cell_idx].text.strip().replace("\n", " "),
770
                    )
771
                    if m:
1✔
772
                        header_matches.append((m.group(1), cell_idx))
1✔
773
                        rgx_idx += 1
1✔
774
                    cell_idx += 1
1✔
775

776
                if len(header_matches) == len(regex_hdr):
1✔
777
                    header_names = [str(hdr[0]) for hdr in header_matches]
1✔
778
                    column_indices = [hdr[1] for hdr in header_matches]
1✔
779
                else:
780
                    mismatches = [
1✔
781
                        (
782
                            idx,
783
                            regex_hdr[idx],
784
                            row.cells[idx].text.strip().replace("\n", " "),
785
                        )
786
                        for idx, hdr in enumerate(header_matches)
787
                        if not hdr
788
                    ]
789
                    errors.append(
1✔
790
                        senfd.errors.FigureTableRowError(
791
                            figure_nr=enriched.figure_nr,
792
                            table_nr=enriched.table.table_nr,
793
                            row_idx=row_idx,
794
                            message=f"No match REGEX_GRID/Headers on idx({mismatches})",
795
                        )
796
                    )
797
                continue
1✔
798

799
            combined = {}
1✔
800
            value_errors: List[Error] = []
1✔
801
            cells = [row.cells[idx] for idx in column_indices]
1✔
802
            for cell_idx, (cell, regex) in enumerate(zip(cells, regex_val)):
1✔
803
                text = cell.text.strip().translate(TRANSLATION_TABLE)
1✔
804
                match = re.match(regex, text)  # type: ignore
1✔
805
                if match:
1✔
806
                    combined.update(match.groupdict())
1✔
807
                    continue
1✔
808

809
                value_errors.append(
1✔
810
                    senfd.errors.FigureTableRowCellError(
811
                        figure_nr=enriched.figure_nr,
812
                        table_nr=enriched.table.table_nr,
813
                        row_idx=row_idx,
814
                        cell_idx=cell_idx,
815
                        message=f"cell.text({text}) no match({regex})",
816
                    )
817
                )
818

819
            if value_errors:
1✔
820
                errors += value_errors
1✔
821
                continue
1✔
822

823
            cur_fields = list(combined.keys())
1✔
824
            if not fields:
1✔
825
                fields = cur_fields
1✔
826

827
            diff = list(set(cur_fields).difference(set(fields)))
1✔
828
            if diff:
1✔
829
                errors.append(
×
830
                    senfd.errors.FigureTableRowError(
831
                        figure_nr=enriched.figure_nr,
832
                        table_nr=enriched.table.table_nr,
833
                        row_idx=row_idx,
834
                        message=f"Unexpected fields ({fields}) != ({cur_fields})",
835
                    )
836
                )
837
                continue
×
838

839
            values.append(list(combined.values()))
1✔
840

841
        data = enriched.table.dict()
1✔
842
        data["headers"] = header_names
1✔
843
        data["fields"] = fields
1✔
844
        data["values"] = values
1✔
845

846
        enriched.grid = senfd.tables.Grid(**data)
1✔
847

848
        return enriched, errors
1✔
849

850
    @staticmethod
1✔
851
    def get_figure_enriching_classes():
1✔
852
        """
853
        To avoid manually crafting a list of classes, this function
854
        introspectively examines this module for applicable
855
        classes with "REGEX_FIGURE_DESCRIPTION" class attribute.
856
        """
857
        return [
1✔
858
            cls
859
            for _, cls in inspect.getmembers(senfd.documents.enriched, inspect.isclass)
860
            if issubclass(cls, EnrichedFigure)
861
            and (cls is not senfd.documents.enriched.EnrichedFigure)
862
            and hasattr(cls, "REGEX_FIGURE_DESCRIPTION")
863
        ]
864

865
    @staticmethod
1✔
866
    def convert(path: Path, args: Namespace) -> Tuple[Document, List[Error]]:
1✔
867
        """Instantiate an 'organized' Document from a 'figure' document"""
868

869
        errors = []
1✔
870

871
        figure_document = FigureDocument.model_validate_json(path.read_text())
1✔
872
        skip_patterns = args.skip_figures
1✔
873

874
        document = EnrichedFigureDocument()
1✔
875
        document.meta.stem = strip_all_suffixes(path.stem)
1✔
876
        skip_patterns = SkipPatterns(skip_patterns, figure_document.figures)
1✔
877
        document.skip_map = skip_patterns.figure_map
1✔
878
        figure_organizers = FromFigureDocument.get_figure_enriching_classes()
1✔
879
        for figure in figure_document.figures:
1✔
880
            if figure.figure_nr > document.stats["max_figure_number"]:
1✔
881
                document.stats["max_figure_number"] = figure.figure_nr
1✔
882

883
            if not figure.table:
1✔
884
                document.nontabular.append(figure)
1✔
885
                document.stats["nontabular"] += 1
1✔
886
                continue
1✔
887
            skip = skip_patterns.skip_figure(figure)
1✔
888
            if skip:
1✔
889
                document.skipped.append(figure)
×
NEW
890
                document.stats["skipped"] += 1
×
UNCOV
891
                continue
×
892

893
            match = None
1✔
894
            description = figure.description.translate(TRANSLATION_TABLE)
1✔
895
            candidates = []
1✔
896
            found: EnrichedFigure | None = None
1✔
897

898
            for candidate in figure_organizers:
1✔
899
                match = re.match(
1✔
900
                    candidate.REGEX_FIGURE_DESCRIPTION, description, flags=re.IGNORECASE
901
                )
902
                if match:
1✔
903
                    enriched, conv_errors = FromFigureDocument.enrich(
1✔
904
                        candidate, figure, match
905
                    )
906
                    if not enriched:
1✔
907
                        errors += conv_errors
×
908
                        break
×
909

910
                    grid_errors = FromFigureDocument.check_grid(enriched)
1✔
911
                    # If three grid errors (no headers, fields nor values), this is not the right candidate
912
                    if len(grid_errors) == 3:
1✔
913
                        errors.append(
1✔
914
                            senfd.errors.FigureError(
915
                                figure_nr=enriched.figure_nr,
916
                                message=(
917
                                    f"{enriched.__class__.__name__}.REGEX_GRID did not match table, continuing"
918
                                ),
919
                            )
920
                        )
921
                        continue
1✔
922

923
                    errors += conv_errors
1✔
924
                    errors += grid_errors
1✔
925

926
                    candidates.append(enriched.__class__.__name__)
1✔
927
                    if not found:
1✔
928
                        found = enriched
1✔
929

930
            if not found:
1✔
931
                document.uncategorized.append(figure)
1✔
932
                document.stats["uncategorized"] += 1
1✔
933
            elif len(candidates) == 1:
1✔
934
                found.into_document(document)
1✔
935
                document.stats["categorized"] += 1
1✔
936
            elif len(candidates) > 1:
1✔
937
                document.uncategorized.append(figure)
1✔
938
                document.stats["uncategorized"] += 1
1✔
939
                errors.append(
1✔
940
                    senfd.errors.FigureError(
941
                        figure_nr=found.figure_nr,
942
                        message=(
943
                            f"Failed classifying table; Matched on multiple figures: {', '.join(c for c in candidates)}"
944
                        ),
945
                    )
946
                )
947

948
        return document, errors
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