Path:
strictdoc/export/html/html_generator.py
Lines:
783
Non-empty lines:
701
Non-empty lines covered with requirements:
701 / 701 (100.0%)
Functions:
16
Functions covered by requirements:
16 / 16 (100.0%)
1
import os
2
import shutil
3
from collections import defaultdict
4
from functools import partial
5
from pathlib import Path
6
from typing import Any, Dict, List, Optional, Set, Tuple
7
8
import orjson
9
from html2pdf4doc import PATH_TO_HTML2PDF4DOC_JS
10
11
from strictdoc.backend.sdoc.models.document import SDocDocument
12
from strictdoc.core.asset_manager import AssetDir
13
from strictdoc.core.document_meta import DocumentMeta
14
from strictdoc.core.file_system.source_tree import SourceTree
15
from strictdoc.core.project_config import ProjectConfig, ProjectFeature
16
from strictdoc.core.traceability_index import TraceabilityIndex
17
from strictdoc.export.html.document_type import DocumentType
18
from strictdoc.export.html.generators.document import DocumentHTMLGenerator
19
from strictdoc.export.html.generators.document_table import (
20
DocumentTableHTMLGenerator,
21
)22
from strictdoc.export.html.html_templates import (
23
HTMLTemplates,
24
NormalHTMLTemplates,
25
)26
from strictdoc.export.html.renderers.link_renderer import LinkRenderer
27
from strictdoc.export.html.renderers.markup_renderer import MarkupRenderer
28
from strictdoc.features.deep_trace.generator import (
29
DocumentDeepTraceHTMLGenerator,
30
)31
from strictdoc.features.html2pdf.generator import (
32
DocumentHTML2PDFGenerator,
33
)34
from strictdoc.features.project_index.generator import (
35
DocumentTreeHTMLGenerator,
36
)37
from strictdoc.features.project_index.project_map_generator import (
38
ProjectMapGenerator,
39
)40
from strictdoc.features.source_coverage.generator import (
41
SourceFileCoverageHTMLGenerator,
42
)43
from strictdoc.features.source_file_view.generator import (
44
SourceFileViewHTMLGenerator,
45
)46
from strictdoc.features.trace.generator import (
47
DocumentTraceHTMLGenerator,
48
)49
from strictdoc.features.traceability_matrix.generator import (
50
TraceabilityMatrixHTMLGenerator,
51
)52
from strictdoc.features.tree_map.generator import TreeMapGenerator
53
from strictdoc.helpers.cast import assert_cast
54
from strictdoc.helpers.file_modification_time import get_file_modification_time
55
from strictdoc.helpers.file_system import sync_dir
56
from strictdoc.helpers.git_client import GitClient
57
from strictdoc.helpers.mid import MID
58
from strictdoc.helpers.parallelizer import Parallelizer
59
from strictdoc.helpers.paths import SDocRelativePath, path_to_posix_path
60
from strictdoc.helpers.timing import measure_performance, timing_decorator
61
62
63
def render_favicon_svg(
64
project_config: ProjectConfig,
65
html_templates: HTMLTemplates, # noqa: ARG001
66
) -> str:
67
# Deliberately not using html_templates.jinja_environment(): for large68
# projects it is a CompiledHTMLTemplates instance that lazily caches a69
# ModuleLoader-backed Environment (holding unpicklable compiled70
# _TemplateModule objects) on first call. HTMLGenerator instances are71
# captured by the closure passed to the document-export parallelizer,72
# so populating that cache here, in the main process, before parallel73
# export starts, makes the whole HTMLGenerator (and its html_templates)74
# unpicklable for the worker pool. A standalone, uncached environment75
# sidesteps that entirely; favicon.svg.jinja is small enough that76
# skipping template compilation has no measurable cost.77
variant = project_config.get_favicon_variant()
78
return (
79
NormalHTMLTemplates()
80
.jinja_environment()
81
.get_template("_shared/favicon.svg.jinja")
82
.render(variant=variant)
83
)84
85
86
class HTMLGenerator:
87
def __init__(
88
self, project_config: ProjectConfig, html_templates: HTMLTemplates
89
):90
self.project_config: ProjectConfig = project_config
91
self.html_templates = html_templates
92
self.git_client: GitClient = GitClient()
93
94
def export_complete_tree(
95
self,
96
*,
97
traceability_index: TraceabilityIndex,
98
parallelizer: Parallelizer,
99
) -> None:
100
Path(self.project_config.export_output_html_root).mkdir(
101
parents=True, exist_ok=True
102
)103
104
# Export assets.105
HTMLGenerator.export_assets(
106
traceability_index=traceability_index,
107
project_config=self.project_config,
108
html_templates=self.html_templates,
109
export_output_html_root=self.project_config.export_output_html_root,
110
)111
112
# Export static search index.113
self.export_static_html_search_index(
114
traceability_index=traceability_index
115
)116
117
# Export all documents in parallel.118
export_binding = partial(
119
self.export_single_document_with_performance,
120
traceability_index=traceability_index,
121
)122
123
# By default, do not export included documents. Only, if the option to124
# include is provided.125
documents_to_export: List[SDocDocument] = []
126
127
if self.project_config.export_included_documents:
128
documents_to_export[:] = (
129
traceability_index.document_tree.document_list
130
)131
else:
132
for document_ in traceability_index.document_tree.document_list:
133
if document_.document_is_included():
134
continue135
136
document_meta = assert_cast(document_.meta, DocumentMeta)
137
138
input_doc_full_path = document_meta.input_doc_full_path
139
output_doc_full_path = document_meta.output_document_full_path
140
141
if os.path.isfile(output_doc_full_path) and (
142
get_file_modification_time(input_doc_full_path)
143
< get_file_modification_time(output_doc_full_path)
144
and not traceability_index.file_dependency_manager.must_generate(
145
document_meta.output_document_full_path
146
)147
):148
with measure_performance(f"Skip: {document_.title}"):
149
continue150
151
documents_to_export.append(document_)
152
153
if len(documents_to_export) > 0:
154
if len(traceability_index.document_tree.document_list) <= 25:
155
parallelizer.run_parallel(documents_to_export, export_binding)
156
else:
157
print( # noqa: T201
158
"NOTE: Running document export without parallelization "159
"because the document tree contains more than 25 documents."160
)161
for document_ in documents_to_export:
162
export_binding(document_)
163
164
# Export document tree.165
# FIXME: It is important that this export is **after** the parallelized166
# export of single documents. It turns out that Jinja does not play167
# well with the multiprocessing's processed-based parallelization.168
# _pickle.PicklingError: Can't pickle <function sync_do_first at 0x1077bdf80>: it's not the same object as jinja2.filters.sync_do_first.169
self.export_project_tree_screen(traceability_index=traceability_index)
170
171
# Export JavaScript map of the document tree (project map)172
self.export_project_map(traceability_index=traceability_index)
173
174
if self.project_config.is_activated_tree_map():
175
self.export_tree_map_screen(traceability_index)
176
177
# Project statistics is exported by the ExportAction class via the178
# Feature abstraction (see the ProjectStatisticsFeature class), not179
# here.180
181
# Export requirements coverage.182
if self.project_config.is_feature_activated(
183
ProjectFeature.TRACEABILITY_MATRIX_SCREEN
184
):185
self.export_requirements_coverage_screen(
186
traceability_index=traceability_index,
187
)188
189
# Export source coverage.190
if self.project_config.is_feature_activated(
191
ProjectFeature.REQUIREMENT_TO_SOURCE_TRACEABILITY
192
):193
self.export_source_files_screens(
194
traceability_index=traceability_index,
195
)196
self.export_source_coverage_screen(
197
traceability_index=traceability_index,
198
)199
200
print( # noqa: T201
201
"Export completed. Documentation tree can be found at:\n"
202
f"{self.project_config.export_output_html_root}"
203
)204
205
@staticmethod206
def export_assets(
207
*,
208
traceability_index: Optional[TraceabilityIndex],
209
project_config: ProjectConfig,
210
html_templates: HTMLTemplates,
211
export_output_html_root: str,
212
flat_assets: bool = False,
213
) -> None:
214
"""
215
Copy all assets to output dir during HTML/PDF generation.216
217
:param bool flat_assets: This parameter is always set to False except when218
exporting a "bundle document" with HTML2PDF.219
The bundle document contains all documents of220
the documentation tree. In this case, all assets221
are simply copied to the top level _assets folder,222
independently on how nested the contained documents are.223
"""224
225
# Export StrictDoc's own assets.226
output_html_static_files = os.path.join(
227
export_output_html_root,
228
project_config.dir_for_sdoc_assets,
229
)230
for static_files_path in project_config.get_static_files_paths():
231
sync_dir(
232
static_files_path,
233
output_html_static_files,
234
message="Copying StrictDoc's assets",
235
)236
237
# Write the favicon: a project's own custom file (only for the238
# "default" variant, see ProjectConfig.get_custom_favicon_path()),239
# or else render it from the Jinja template so it can encode which240
# kind of StrictDoc instance (dev/test/docs export) rendered it.241
favicon_output_path = os.path.join(
242
output_html_static_files, project_config.get_favicon_filename()
243
)244
custom_favicon_path = project_config.get_custom_favicon_path()
245
if custom_favicon_path is not None:
246
shutil.copyfile(custom_favicon_path, favicon_output_path)
247
else:
248
favicon_svg = render_favicon_svg(project_config, html_templates)
249
with open(favicon_output_path, "w", encoding="utf8") as output_file:
250
output_file.write(favicon_svg)
251
252
# Export HTML2PDF.253
if project_config.is_feature_activated(ProjectFeature.HTML2PDF):
254
sync_dir(
255
os.path.dirname(PATH_TO_HTML2PDF4DOC_JS),
256
output_html_static_files,
257
message="Copying HTML2PDF.js",
258
)259
260
# Export custom html2pdf template.261
if project_config.html2pdf_template is not None:
262
output_custom_html2pdf_template = os.path.join(
263
export_output_html_root,
264
project_config.dir_for_sdoc_assets,
265
"html2pdf_template",
266
)267
sync_dir(
268
os.path.abspath(
269
os.path.dirname(project_config.html2pdf_template)
270
),271
output_custom_html2pdf_template,
272
message="Copying Custom HTML2PDF template assets",
273
)274
275
# Export project's assets.276
277
if traceability_index is not None:
278
redundant_assets: Dict[str, List[SDocRelativePath]] = {}
279
for document_ in traceability_index.document_tree.document_list:
280
assert document_.meta is not None
281
for (
282
included_document_283
) in document_.iterate_included_documents_depth_first():
284
assert included_document_.meta is not None
285
286
redundant_assets.setdefault(
287
document_.meta.input_doc_assets_dir_rel_path.relative_path_posix,
288
[],289
)290
redundant_assets[
291
document_.meta.input_doc_assets_dir_rel_path.relative_path_posix
292
].append(
293
included_document_.meta.input_doc_assets_dir_rel_path
294
)295
296
assert traceability_index.asset_manager is not None
297
298
asset_dir_: AssetDir
299
for asset_dir_ in traceability_index.asset_manager.iterate():
300
source_path = asset_dir_.full_path
301
output_relative_path = asset_dir_.relative_path
302
303
destination_path = os.path.join(
304
export_output_html_root,
305
output_relative_path.relative_path
306
if not flat_assets
307
else "_assets",
308
)309
310
sync_dir(
311
source_path,
312
destination_path,
313
message=f'Copying project assets "{output_relative_path.relative_path}"',
314
)315
redundant_asset_paths = redundant_assets.get(
316
output_relative_path.relative_path_posix
317
)318
if redundant_asset_paths is not None:
319
for redundant_asset_ in redundant_asset_paths:
320
destination_path = os.path.join(
321
export_output_html_root,
322
redundant_asset_.relative_path
323
if not flat_assets
324
else "_assets",
325
)326
sync_dir(
327
source_path,
328
destination_path,
329
message=f'Copying project assets "{output_relative_path.relative_path}"',
330
)331
332
def export_single_document_with_performance(
333
self,
334
document: SDocDocument,
335
traceability_index: TraceabilityIndex,
336
specific_documents: Optional[Tuple[DocumentType, ...]] = None,
337
) -> None:
338
if specific_documents is None:
339
specific_documents = DocumentType.all()
340
341
with measure_performance(f"Published: {document.title}"):
342
self.export_single_document(
343
document,
344
traceability_index,
345
specific_documents=specific_documents,
346
)347
348
def export_single_document(
349
self,
350
document: SDocDocument,
351
traceability_index: TraceabilityIndex,
352
specific_documents: Optional[Tuple[DocumentType, ...]] = None,
353
) -> SDocDocument:
354
if document.config.layout == "Website":
355
specific_documents = (DocumentType.DOCUMENT,)
356
elif specific_documents is None:
357
specific_documents = DocumentType.all()
358
359
assert document.meta is not None
360
361
document_meta: DocumentMeta = document.meta
362
363
document_output_folder = document_meta.output_document_dir_full_path
364
Path(document_output_folder).mkdir(parents=True, exist_ok=True)
365
366
root_path = document.meta.get_root_path_prefix()
367
link_renderer = LinkRenderer(
368
root_path=root_path,
369
static_path=self.project_config.dir_for_sdoc_assets,
370
)371
markup_renderer = MarkupRenderer.create(
372
document.config.markup,
373
traceability_index,
374
link_renderer,
375
self.html_templates,
376
self.project_config,
377
document,
378
)379
380
if DocumentType.DOCUMENT in specific_documents:
381
# Single Document pages.382
document_content = DocumentHTMLGenerator.export(
383
self.project_config,
384
document,
385
traceability_index,
386
markup_renderer,
387
link_renderer,
388
git_client=self.git_client,
389
html_templates=self.html_templates,
390
)391
document_out_file = document_meta.get_html_doc_path()
392
with open(document_out_file, "w", encoding="utf8") as file:
393
file.write(document_content)
394
395
# Single Document Table pages.396
if (
397
self.project_config.is_feature_activated(
398
ProjectFeature.TABLE_SCREEN
399
)400
and DocumentType.TABLE in specific_documents
401
):402
document_content = DocumentTableHTMLGenerator.export(
403
self.project_config,
404
document,
405
traceability_index,
406
markup_renderer,
407
link_renderer,
408
git_client=self.git_client,
409
html_templates=self.html_templates,
410
)411
document_out_file = document_meta.get_html_table_path()
412
with open(document_out_file, "w", encoding="utf8") as file:
413
file.write(document_content)
414
415
# Single Document Traceability pages.416
if (
417
self.project_config.is_feature_activated(
418
ProjectFeature.TRACEABILITY_SCREEN
419
)420
and DocumentType.TRACE in specific_documents
421
):422
document_content = DocumentTraceHTMLGenerator.export(
423
self.project_config,
424
document,
425
traceability_index,
426
markup_renderer,
427
link_renderer,
428
git_client=self.git_client,
429
html_templates=self.html_templates,
430
)431
document_out_file = document_meta.get_html_traceability_path()
432
with open(document_out_file, "w", encoding="utf8") as file:
433
file.write(document_content)
434
435
# Single Document Deep Traceability pages.436
if (
437
self.project_config.is_feature_activated(
438
ProjectFeature.DEEP_TRACEABILITY_SCREEN
439
)440
and DocumentType.DEEPTRACE in specific_documents
441
):442
document_content = DocumentDeepTraceHTMLGenerator.export_deep(
443
self.project_config,
444
document,
445
traceability_index,
446
markup_renderer,
447
link_renderer,
448
git_client=self.git_client,
449
html_templates=self.html_templates,
450
)451
document_out_file = document_meta.get_html_deep_traceability_path()
452
with open(document_out_file, "w", encoding="utf8") as file:
453
file.write(document_content)
454
455
# Single Document PDF pages.456
if (
457
self.project_config.is_feature_activated(ProjectFeature.HTML2PDF)
458
and DocumentType.PDF in specific_documents
459
):460
document_content = DocumentHTML2PDFGenerator.export(
461
self.project_config,
462
document,
463
traceability_index,
464
markup_renderer,
465
link_renderer,
466
git_client=self.git_client,
467
html_templates=self.html_templates,
468
)469
document_out_file = document_meta.get_html_pdf_path()
470
with open(document_out_file, "w", encoding="utf8") as file:
471
file.write(document_content)
472
473
return document
474
475
def export_project_tree_screen(
476
self,
477
*,
478
traceability_index: TraceabilityIndex,
479
) -> None:
480
Path(self.project_config.export_output_html_root).mkdir(
481
parents=True, exist_ok=True
482
)483
output_file = os.path.join(
484
self.project_config.export_output_html_root, "index.html"
485
)486
writer = DocumentTreeHTMLGenerator()
487
output = writer.export(
488
self.project_config,
489
traceability_index=traceability_index,
490
html_templates=self.html_templates,
491
)492
with open(output_file, "w", encoding="utf8") as file:
493
file.write(output)
494
495
def export_project_map(
496
self,
497
*,
498
traceability_index: TraceabilityIndex,
499
) -> None:
500
assets_dir = os.path.join(
501
self.project_config.export_output_html_root,
502
self.project_config.dir_for_sdoc_assets,
503
)504
output_file = os.path.join(assets_dir, "project_map.js")
505
writer = ProjectMapGenerator()
506
output = writer.export(
507
self.project_config,
508
traceability_index=traceability_index,
509
html_templates=self.html_templates,
510
)511
with open(output_file, "w", encoding="utf8") as file:
512
file.write(output)
513
514
def export_requirements_coverage_screen(
515
self,
516
*,
517
traceability_index: TraceabilityIndex,
518
) -> None:
519
requirements_coverage_content = TraceabilityMatrixHTMLGenerator.export(
520
project_config=self.project_config,
521
traceability_index=traceability_index,
522
html_templates=self.html_templates,
523
)524
output_html_requirements_coverage = os.path.join(
525
self.project_config.export_output_html_root,
526
"traceability_matrix.html",
527
)528
with open(
529
output_html_requirements_coverage, "w", encoding="utf8"
530
) as file:
531
file.write(requirements_coverage_content)
532
533
@timing_decorator("Export source file pages")
534
def export_source_files_screens(
535
self,
536
*,
537
traceability_index: TraceabilityIndex,
538
) -> None:
539
assert isinstance(
540
traceability_index.document_tree.source_tree, SourceTree
541
), traceability_index.document_tree.source_tree
542
print("Generating source files:") # noqa: T201
543
for (
544
source_file545
) in traceability_index.document_tree.source_tree.source_files:
546
if not source_file.is_referenced:
547
continue548
549
SourceFileViewHTMLGenerator.export_to_file(
550
project_config=self.project_config,
551
source_file=source_file,
552
traceability_index=traceability_index,
553
html_templates=self.html_templates,
554
)555
556
def export_source_coverage_screen(
557
self,
558
*,
559
traceability_index: TraceabilityIndex,
560
) -> None:
561
assert isinstance(
562
traceability_index.document_tree.source_tree, SourceTree
563
), traceability_index.document_tree.source_tree
564
565
source_coverage_content = SourceFileCoverageHTMLGenerator.export(
566
project_config=self.project_config,
567
traceability_index=traceability_index,
568
html_templates=self.html_templates,
569
)570
output_html_source_coverage = os.path.join(
571
self.project_config.export_output_html_root, "source_coverage.html"
572
)573
with open(output_html_source_coverage, "w", encoding="utf8") as file:
574
file.write(source_coverage_content)
575
576
def export_single_source_file_screen(
577
self,
578
*,
579
traceability_index: TraceabilityIndex,
580
path_to_source_file: str,
581
) -> None:
582
assert isinstance(
583
traceability_index.document_tree.source_tree, SourceTree
584
), traceability_index.document_tree.source_tree
585
586
# FIXME: path_to_source_file must not enter this function with forward slashes.587
# Test and fix this on Windows.588
# https://github.com/strictdoc-project/strictdoc/issues/2068589
relative_path_to_source_file = path_to_posix_path(path_to_source_file)
590
relative_path_to_source_file = (
591
relative_path_to_source_file.removeprefix("_source_files/")
592
)593
relative_path_to_source_file = (
594
relative_path_to_source_file.removesuffix(".html")
595
)596
597
for (
598
source_file599
) in traceability_index.document_tree.source_tree.source_files:
600
if not source_file.is_referenced:
601
continue602
603
if (
604
relative_path_to_source_file605
== source_file.in_doctree_source_file_rel_path_posix
606
):607
SourceFileViewHTMLGenerator.export_to_file(
608
project_config=self.project_config,
609
source_file=source_file,
610
traceability_index=traceability_index,
611
html_templates=self.html_templates,
612
)613
return614
615
raise FileNotFoundError
616
617
@timing_decorator("Export static HTML search index")
- "6.14.1. Content search" (REQUIREMENT)
- "6.1. Static HTML search" (DESIGN)
618
def export_static_html_search_index(
619
self,
620
traceability_index: TraceabilityIndex,
621
*,
622
force_regeneration: bool = False,
623
) -> None:
624
"""
625
Export a static search index as dictionaries in .js files.626
627
@relation(SDOC-SRS-155, scope=function)628
@relation(SDOC-SRS-156, scope=function)629
"""630
631
if not force_regeneration:
632
# First check if there is nothing to do because no documents have633
# been changed or regenerated.634
635
# FIXME: This is wrong. FIX!636
must_regenerate = (
637
len(traceability_index.document_tree.document_list) == 0
638
)639
640
for document_ in traceability_index.document_tree.document_list:
641
assert document_.meta is not None
642
if traceability_index.file_dependency_manager.must_generate(
643
document_.meta.output_document_full_path
644
):645
must_regenerate = True
646
break647
648
if not must_regenerate:
649
print( # noqa: T201
650
"All documents are up-to-date. "651
"Skipping the generation of a search index."652
)653
# If no documents need to be regenerated, set the654
# search_index_timestamp to the timestamp of the first document.655
# The HTML/JS code can rely on this timestamp to decide whether656
# it has to re-read the search index from the JS file or it can657
# fetch it from the DB.658
if len(traceability_index.document_tree.document_list) > 0:
659
first_document = (
660
traceability_index.document_tree.document_list[0]
661
)662
assert first_document.meta is not None
663
traceability_index.search_index_timestamp = (
664
get_file_modification_time(
665
first_document.meta.input_doc_full_path
666
)667
)668
return669
670
if force_regeneration:
671
for document_ in traceability_index.document_tree.document_list:
672
document_.build_search_index()
673
674
global_index: Dict[str, Set[int]] = defaultdict(set)
675
global_map_nodes_by_mid: Dict[int, Dict[str, str]] = {}
676
677
document_index_list: List[Dict[str, Set[str]]] = []
678
document_map_list: List[Dict[int, Dict[str, str]]] = []
679
680
map_mid_to_numbers: Dict[str, int] = {}
681
682
with measure_performance("Build search index"):
683
for document_ in traceability_index.document_tree.document_list:
684
assert document_.meta is not None
685
document_index_list.append(
686
document_.search_index.document_index
687
)688
map_nodes_by_numbers: Dict[int, Dict[str, str]] = {}
689
for (
690
node_mid_,
691
node_dict_,
692
) in document_.search_index.map_nodes_by_mid.items():
693
if node_mid_ not in map_mid_to_numbers:
694
map_mid_to_numbers[node_mid_] = (
695
len(map_mid_to_numbers) + 1
696
)697
document_mid_number = map_mid_to_numbers[node_mid_]
698
assert isinstance(document_mid_number, int)
699
map_nodes_by_numbers[document_mid_number] = node_dict_
700
701
document_map_list.append(map_nodes_by_numbers)
702
for document_index_ in document_index_list:
703
for term_, document_mids_ in document_index_.items():
704
document_mid_numbers = set()
705
for document_mid_ in document_mids_:
706
document_mid_number = map_mid_to_numbers[document_mid_]
707
document_mid_numbers.add(document_mid_number)
708
global_index[term_].update(document_mid_numbers)
709
for map_nodes_by_mid_ in document_map_list:
710
global_map_nodes_by_mid.update(map_nodes_by_mid_)
711
712
link_renderer = LinkRenderer(
713
root_path="",
714
static_path=self.project_config.dir_for_sdoc_assets,
715
)716
for _, node_ in global_map_nodes_by_mid.items():
717
# When running on server, the MID is used as a link to the node.718
# The MID is then resolved to the correct URL by the server when719
# requested at /UID/{uid_or_mid}.720
# This ensures that all nodes can be reached with MID, including721
# the nodes that don't have a UID.722
if self.project_config.is_running_on_server:
723
node_["_LINK"] = node_["MID"]
724
725
# When running static HTML, the resolution of _LINKs happens through726
# the auto-generated static JS project_map.js that has a format of:727
# {<local anchor>: MID}728
else:
729
node = traceability_index.get_node_by_mid(MID(node_["MID"]))
730
node_["_LINK"] = link_renderer.render_local_anchor(node)
731
732
def default(obj: Any) -> Any:
733
if isinstance(obj, set):
734
return list(obj)
735
raise TypeError
736
737
with measure_performance("Serialize search index to JS"):
738
document_content = (
739
b"window.StrictDoc = window.StrictDoc || {};\n"
740
b"window.StrictDoc.search = window.StrictDoc.search || {};\n"
741
b"window.StrictDoc.search.index = "
742
+ orjson.dumps(
743
global_index,
744
option=orjson.OPT_NON_STR_KEYS,
745
default=default,
746
)747
+ b";\n\n"
748
)749
750
with measure_performance("Serialize lookup map {MID => node} to JS"):
751
document_content += (
752
b"window.StrictDoc.search.nodesByMid = "
753
+ orjson.dumps(
754
global_map_nodes_by_mid, option=orjson.OPT_NON_STR_KEYS
755
)756
+ b";\n"
757
)758
759
# Export StrictDoc's own assets.760
output_html_static_files = os.path.join(
761
self.project_config.export_output_html_root,
762
self.project_config.dir_for_sdoc_assets,
763
)764
output_html_source_coverage = os.path.join(
765
output_html_static_files,
766
"static_html_search_index.js",
767
)768
with open(output_html_source_coverage, "wb") as file:
769
file.write(document_content)
770
771
traceability_index.search_index_timestamp = get_file_modification_time(
772
output_html_source_coverage773
)774
775
def export_tree_map_screen(
776
self,
777
traceability_index: TraceabilityIndex,
778
) -> None:
779
TreeMapGenerator.export(
780
project_config=self.project_config,
781
traceability_index=traceability_index,
782
html_templates=self.html_templates,
783
)