oscura 0.0.1__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
"""Template system for Oscura reports.
|
|
2
|
+
|
|
3
|
+
This module provides template loading, management, inheritance, and built-in
|
|
4
|
+
report templates.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.reporting.template_system import load_template, register_template
|
|
9
|
+
>>> template = load_template("compliance")
|
|
10
|
+
>>> # Create custom template extending compliance
|
|
11
|
+
>>> custom = load_template("compliance")
|
|
12
|
+
>>> register_template("my_compliance", custom)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import copy
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
import yaml
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class TemplateSection:
|
|
27
|
+
"""A section definition in a template.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
title: Section title.
|
|
31
|
+
content_type: Type of content (text, table, figure, measurement).
|
|
32
|
+
condition: Conditional expression for inclusion.
|
|
33
|
+
template: Jinja2 template for content.
|
|
34
|
+
subsections: Child sections.
|
|
35
|
+
order: Section order (for sorting during inheritance merge).
|
|
36
|
+
override: If True, replaces parent section with same title.
|
|
37
|
+
|
|
38
|
+
References:
|
|
39
|
+
REPORT-007: Template Definition Format
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
title: str
|
|
43
|
+
content_type: str = "text"
|
|
44
|
+
condition: str | None = None
|
|
45
|
+
template: str = ""
|
|
46
|
+
subsections: list[TemplateSection] = field(default_factory=list)
|
|
47
|
+
order: int = 0
|
|
48
|
+
override: bool = False
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class ReportTemplate:
|
|
53
|
+
"""A report template definition.
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
name: Template name.
|
|
57
|
+
version: Template version.
|
|
58
|
+
description: Template description.
|
|
59
|
+
author: Template author.
|
|
60
|
+
extends: Parent template name for inheritance (REPORT-005).
|
|
61
|
+
sections: Template sections.
|
|
62
|
+
styles: Style definitions.
|
|
63
|
+
metadata: Additional metadata.
|
|
64
|
+
overrides: Section-specific overrides (REPORT-008).
|
|
65
|
+
|
|
66
|
+
References:
|
|
67
|
+
REPORT-005: Template Inheritance
|
|
68
|
+
REPORT-007: Template Definition Format
|
|
69
|
+
REPORT-008: Template Overrides
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
name: str
|
|
73
|
+
version: str = "1.0"
|
|
74
|
+
description: str = ""
|
|
75
|
+
author: str = ""
|
|
76
|
+
extends: str | None = None
|
|
77
|
+
sections: list[TemplateSection] = field(default_factory=list)
|
|
78
|
+
styles: dict[str, Any] = field(default_factory=dict)
|
|
79
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
80
|
+
overrides: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# Built-in templates
|
|
84
|
+
BUILTIN_TEMPLATES: dict[str, ReportTemplate] = {
|
|
85
|
+
"default": ReportTemplate(
|
|
86
|
+
name="Default Report",
|
|
87
|
+
version="1.0",
|
|
88
|
+
description="Standard analysis report template",
|
|
89
|
+
sections=[
|
|
90
|
+
TemplateSection(
|
|
91
|
+
title="Executive Summary",
|
|
92
|
+
content_type="text",
|
|
93
|
+
template="{{ summary }}",
|
|
94
|
+
order=0,
|
|
95
|
+
),
|
|
96
|
+
TemplateSection(
|
|
97
|
+
title="Test Results",
|
|
98
|
+
content_type="table",
|
|
99
|
+
template="{{ results_table }}",
|
|
100
|
+
order=10,
|
|
101
|
+
),
|
|
102
|
+
TemplateSection(
|
|
103
|
+
title="Methodology",
|
|
104
|
+
content_type="text",
|
|
105
|
+
condition="verbosity != 'executive'",
|
|
106
|
+
order=20,
|
|
107
|
+
),
|
|
108
|
+
],
|
|
109
|
+
),
|
|
110
|
+
"compliance": ReportTemplate(
|
|
111
|
+
name="Compliance Report",
|
|
112
|
+
version="1.0",
|
|
113
|
+
description="Regulatory compliance testing report",
|
|
114
|
+
extends="default",
|
|
115
|
+
sections=[
|
|
116
|
+
TemplateSection(
|
|
117
|
+
title="Executive Summary",
|
|
118
|
+
content_type="text",
|
|
119
|
+
template="{{ compliance_summary }}",
|
|
120
|
+
override=True,
|
|
121
|
+
order=0,
|
|
122
|
+
),
|
|
123
|
+
TemplateSection(
|
|
124
|
+
title="Test Standards",
|
|
125
|
+
content_type="text",
|
|
126
|
+
template="Standards tested: {{ standards }}",
|
|
127
|
+
order=5,
|
|
128
|
+
),
|
|
129
|
+
TemplateSection(
|
|
130
|
+
title="Violations",
|
|
131
|
+
content_type="table",
|
|
132
|
+
condition="has_violations",
|
|
133
|
+
order=15,
|
|
134
|
+
),
|
|
135
|
+
TemplateSection(
|
|
136
|
+
title="Certificate",
|
|
137
|
+
content_type="text",
|
|
138
|
+
order=30,
|
|
139
|
+
),
|
|
140
|
+
],
|
|
141
|
+
),
|
|
142
|
+
"characterization": ReportTemplate(
|
|
143
|
+
name="Characterization Report",
|
|
144
|
+
version="1.0",
|
|
145
|
+
description="Device characterization report",
|
|
146
|
+
sections=[
|
|
147
|
+
TemplateSection(
|
|
148
|
+
title="Summary",
|
|
149
|
+
content_type="text",
|
|
150
|
+
order=0,
|
|
151
|
+
),
|
|
152
|
+
TemplateSection(
|
|
153
|
+
title="Timing Parameters",
|
|
154
|
+
content_type="table",
|
|
155
|
+
order=10,
|
|
156
|
+
),
|
|
157
|
+
TemplateSection(
|
|
158
|
+
title="Signal Quality",
|
|
159
|
+
content_type="table",
|
|
160
|
+
order=20,
|
|
161
|
+
),
|
|
162
|
+
TemplateSection(
|
|
163
|
+
title="Margin Analysis",
|
|
164
|
+
content_type="table",
|
|
165
|
+
order=30,
|
|
166
|
+
),
|
|
167
|
+
TemplateSection(
|
|
168
|
+
title="Waveform Plots",
|
|
169
|
+
content_type="figure",
|
|
170
|
+
order=40,
|
|
171
|
+
),
|
|
172
|
+
],
|
|
173
|
+
),
|
|
174
|
+
"debug": ReportTemplate(
|
|
175
|
+
name="Debug Report",
|
|
176
|
+
version="1.0",
|
|
177
|
+
description="Detailed debug report with full data",
|
|
178
|
+
sections=[
|
|
179
|
+
TemplateSection(
|
|
180
|
+
title="Summary",
|
|
181
|
+
content_type="text",
|
|
182
|
+
order=0,
|
|
183
|
+
),
|
|
184
|
+
TemplateSection(
|
|
185
|
+
title="Error Analysis",
|
|
186
|
+
content_type="text",
|
|
187
|
+
order=10,
|
|
188
|
+
),
|
|
189
|
+
TemplateSection(
|
|
190
|
+
title="Protocol Decode",
|
|
191
|
+
content_type="table",
|
|
192
|
+
order=20,
|
|
193
|
+
),
|
|
194
|
+
TemplateSection(
|
|
195
|
+
title="Timing Diagram",
|
|
196
|
+
content_type="figure",
|
|
197
|
+
order=30,
|
|
198
|
+
),
|
|
199
|
+
TemplateSection(
|
|
200
|
+
title="Raw Data",
|
|
201
|
+
content_type="text",
|
|
202
|
+
order=40,
|
|
203
|
+
),
|
|
204
|
+
TemplateSection(
|
|
205
|
+
title="Provenance",
|
|
206
|
+
content_type="text",
|
|
207
|
+
order=50,
|
|
208
|
+
),
|
|
209
|
+
],
|
|
210
|
+
),
|
|
211
|
+
"production": ReportTemplate(
|
|
212
|
+
name="Production Report",
|
|
213
|
+
version="1.0",
|
|
214
|
+
description="Production test report with pass/fail and yield",
|
|
215
|
+
sections=[
|
|
216
|
+
TemplateSection(
|
|
217
|
+
title="Test Summary",
|
|
218
|
+
content_type="text",
|
|
219
|
+
template="Tested: {{ total }} | Passed: {{ passed }} | Failed: {{ failed }}",
|
|
220
|
+
order=0,
|
|
221
|
+
),
|
|
222
|
+
TemplateSection(
|
|
223
|
+
title="Results",
|
|
224
|
+
content_type="table",
|
|
225
|
+
order=10,
|
|
226
|
+
),
|
|
227
|
+
TemplateSection(
|
|
228
|
+
title="Yield Analysis",
|
|
229
|
+
content_type="table",
|
|
230
|
+
order=20,
|
|
231
|
+
),
|
|
232
|
+
],
|
|
233
|
+
),
|
|
234
|
+
"comparison": ReportTemplate(
|
|
235
|
+
name="Comparison Report",
|
|
236
|
+
version="1.0",
|
|
237
|
+
description="Before/after comparison report",
|
|
238
|
+
sections=[
|
|
239
|
+
TemplateSection(
|
|
240
|
+
title="Summary",
|
|
241
|
+
content_type="text",
|
|
242
|
+
order=0,
|
|
243
|
+
),
|
|
244
|
+
TemplateSection(
|
|
245
|
+
title="Differences",
|
|
246
|
+
content_type="table",
|
|
247
|
+
order=10,
|
|
248
|
+
),
|
|
249
|
+
TemplateSection(
|
|
250
|
+
title="Side-by-Side Comparison",
|
|
251
|
+
content_type="figure",
|
|
252
|
+
order=20,
|
|
253
|
+
),
|
|
254
|
+
],
|
|
255
|
+
),
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# User-registered templates (REPORT-006)
|
|
259
|
+
_USER_TEMPLATES: dict[str, ReportTemplate] = {}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def register_template(
|
|
263
|
+
name: str,
|
|
264
|
+
template: ReportTemplate,
|
|
265
|
+
*,
|
|
266
|
+
overwrite: bool = False,
|
|
267
|
+
) -> None:
|
|
268
|
+
"""Register a user template.
|
|
269
|
+
|
|
270
|
+
Allows users to define custom templates or extend built-in ones.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
name: Template name for registration.
|
|
274
|
+
template: Template definition.
|
|
275
|
+
overwrite: If True, allows overwriting existing templates.
|
|
276
|
+
|
|
277
|
+
Raises:
|
|
278
|
+
ValueError: If name exists and overwrite=False.
|
|
279
|
+
|
|
280
|
+
Example:
|
|
281
|
+
>>> from oscura.reporting.template_system import (
|
|
282
|
+
... register_template, ReportTemplate, TemplateSection
|
|
283
|
+
... )
|
|
284
|
+
>>> my_template = ReportTemplate(
|
|
285
|
+
... name="Custom Report",
|
|
286
|
+
... sections=[TemplateSection(title="My Section")]
|
|
287
|
+
... )
|
|
288
|
+
>>> register_template("custom", my_template)
|
|
289
|
+
|
|
290
|
+
References:
|
|
291
|
+
REPORT-006: User Template Registration
|
|
292
|
+
"""
|
|
293
|
+
if name in _USER_TEMPLATES and not overwrite:
|
|
294
|
+
raise ValueError(f"Template '{name}' already registered. Use overwrite=True to replace.")
|
|
295
|
+
|
|
296
|
+
if name in BUILTIN_TEMPLATES and not overwrite:
|
|
297
|
+
raise ValueError(f"Cannot overwrite built-in template '{name}'. Use overwrite=True.")
|
|
298
|
+
|
|
299
|
+
_USER_TEMPLATES[name] = template
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def unregister_template(name: str) -> bool:
|
|
303
|
+
"""Unregister a user template.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
name: Template name.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
True if template was removed, False if not found.
|
|
310
|
+
|
|
311
|
+
References:
|
|
312
|
+
REPORT-006: User Template Registration
|
|
313
|
+
"""
|
|
314
|
+
if name in _USER_TEMPLATES:
|
|
315
|
+
del _USER_TEMPLATES[name]
|
|
316
|
+
return True
|
|
317
|
+
return False
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def extend_template(
|
|
321
|
+
base_name: str,
|
|
322
|
+
*,
|
|
323
|
+
name: str | None = None,
|
|
324
|
+
description: str | None = None,
|
|
325
|
+
add_sections: list[TemplateSection] | None = None,
|
|
326
|
+
remove_sections: list[str] | None = None,
|
|
327
|
+
section_overrides: dict[str, dict[str, Any]] | None = None,
|
|
328
|
+
style_overrides: dict[str, Any] | None = None,
|
|
329
|
+
) -> ReportTemplate:
|
|
330
|
+
"""Create a new template by extending an existing one.
|
|
331
|
+
|
|
332
|
+
Implements template inheritance with section merging and overrides.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
base_name: Name of template to extend.
|
|
336
|
+
name: Name for new template.
|
|
337
|
+
description: Description for new template.
|
|
338
|
+
add_sections: New sections to add.
|
|
339
|
+
remove_sections: Section titles to remove.
|
|
340
|
+
section_overrides: Dict of section title -> field overrides.
|
|
341
|
+
style_overrides: Style fields to override.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
New ReportTemplate with inherited and modified sections.
|
|
345
|
+
|
|
346
|
+
Example:
|
|
347
|
+
>>> # Create custom compliance template
|
|
348
|
+
>>> custom = extend_template(
|
|
349
|
+
... "compliance",
|
|
350
|
+
... name="FDA Compliance",
|
|
351
|
+
... add_sections=[TemplateSection(title="FDA Requirements")],
|
|
352
|
+
... section_overrides={
|
|
353
|
+
... "Certificate": {"template": "FDA Certificate: {{ cert_id }}"}
|
|
354
|
+
... }
|
|
355
|
+
... )
|
|
356
|
+
|
|
357
|
+
References:
|
|
358
|
+
REPORT-005: Template Inheritance
|
|
359
|
+
REPORT-008: Template Overrides
|
|
360
|
+
"""
|
|
361
|
+
# Load base template (resolving inheritance chain)
|
|
362
|
+
base = load_template(base_name)
|
|
363
|
+
|
|
364
|
+
# Deep copy to avoid modifying original
|
|
365
|
+
new_template = copy.deepcopy(base)
|
|
366
|
+
|
|
367
|
+
# Update metadata
|
|
368
|
+
if name:
|
|
369
|
+
new_template.name = name
|
|
370
|
+
if description:
|
|
371
|
+
new_template.description = description
|
|
372
|
+
new_template.extends = base_name
|
|
373
|
+
|
|
374
|
+
# Apply section removals
|
|
375
|
+
if remove_sections:
|
|
376
|
+
new_template.sections = [
|
|
377
|
+
sec for sec in new_template.sections if sec.title not in remove_sections
|
|
378
|
+
]
|
|
379
|
+
|
|
380
|
+
# Apply section overrides
|
|
381
|
+
if section_overrides:
|
|
382
|
+
for sec in new_template.sections:
|
|
383
|
+
if sec.title in section_overrides:
|
|
384
|
+
overrides = section_overrides[sec.title]
|
|
385
|
+
for field_name, value in overrides.items():
|
|
386
|
+
if hasattr(sec, field_name):
|
|
387
|
+
setattr(sec, field_name, value)
|
|
388
|
+
|
|
389
|
+
# Add new sections
|
|
390
|
+
if add_sections:
|
|
391
|
+
new_template.sections.extend(add_sections)
|
|
392
|
+
|
|
393
|
+
# Sort sections by order
|
|
394
|
+
new_template.sections.sort(key=lambda s: s.order)
|
|
395
|
+
|
|
396
|
+
# Apply style overrides
|
|
397
|
+
if style_overrides:
|
|
398
|
+
new_template.styles.update(style_overrides)
|
|
399
|
+
|
|
400
|
+
return new_template
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _resolve_inheritance(
|
|
404
|
+
template: ReportTemplate, visited: set[str] | None = None
|
|
405
|
+
) -> ReportTemplate:
|
|
406
|
+
"""Resolve template inheritance chain.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
template: Template to resolve.
|
|
410
|
+
visited: Set of already visited template names (for cycle detection).
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
Template with all inherited sections merged.
|
|
414
|
+
|
|
415
|
+
Raises:
|
|
416
|
+
ValueError: If circular inheritance detected.
|
|
417
|
+
|
|
418
|
+
References:
|
|
419
|
+
REPORT-005: Template Inheritance
|
|
420
|
+
"""
|
|
421
|
+
if visited is None:
|
|
422
|
+
visited = set()
|
|
423
|
+
|
|
424
|
+
if template.name in visited:
|
|
425
|
+
raise ValueError(f"Circular template inheritance detected: {template.name}")
|
|
426
|
+
|
|
427
|
+
if not template.extends:
|
|
428
|
+
return template
|
|
429
|
+
|
|
430
|
+
visited.add(template.name)
|
|
431
|
+
|
|
432
|
+
# Get parent template
|
|
433
|
+
parent_name = template.extends
|
|
434
|
+
if parent_name in _USER_TEMPLATES:
|
|
435
|
+
parent = copy.deepcopy(_USER_TEMPLATES[parent_name])
|
|
436
|
+
elif parent_name in BUILTIN_TEMPLATES:
|
|
437
|
+
parent = copy.deepcopy(BUILTIN_TEMPLATES[parent_name])
|
|
438
|
+
else:
|
|
439
|
+
raise ValueError(f"Parent template not found: {parent_name}")
|
|
440
|
+
|
|
441
|
+
# Recursively resolve parent inheritance
|
|
442
|
+
parent = _resolve_inheritance(parent, visited)
|
|
443
|
+
|
|
444
|
+
# Merge sections
|
|
445
|
+
# Child sections with override=True replace parent sections with same title
|
|
446
|
+
parent_sections = {sec.title: sec for sec in parent.sections}
|
|
447
|
+
|
|
448
|
+
for child_sec in template.sections:
|
|
449
|
+
if child_sec.override or child_sec.title in parent_sections:
|
|
450
|
+
# Override or replace parent section
|
|
451
|
+
parent_sections[child_sec.title] = child_sec
|
|
452
|
+
else:
|
|
453
|
+
# Add new section
|
|
454
|
+
parent_sections[child_sec.title] = child_sec
|
|
455
|
+
|
|
456
|
+
# Sort by order
|
|
457
|
+
merged_sections = sorted(parent_sections.values(), key=lambda s: s.order)
|
|
458
|
+
|
|
459
|
+
# Merge styles (child overrides parent)
|
|
460
|
+
merged_styles = {**parent.styles, **template.styles}
|
|
461
|
+
|
|
462
|
+
# Merge metadata
|
|
463
|
+
merged_metadata = {**parent.metadata, **template.metadata}
|
|
464
|
+
|
|
465
|
+
return ReportTemplate(
|
|
466
|
+
name=template.name,
|
|
467
|
+
version=template.version,
|
|
468
|
+
description=template.description or parent.description,
|
|
469
|
+
author=template.author or parent.author,
|
|
470
|
+
extends=template.extends,
|
|
471
|
+
sections=merged_sections,
|
|
472
|
+
styles=merged_styles,
|
|
473
|
+
metadata=merged_metadata,
|
|
474
|
+
overrides=template.overrides,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def load_template(name_or_path: str, *, resolve_inheritance: bool = True) -> ReportTemplate:
|
|
479
|
+
"""Load a report template.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
name_or_path: Template name (builtin or registered) or path to YAML file.
|
|
483
|
+
resolve_inheritance: If True, resolve template inheritance chain.
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
ReportTemplate instance.
|
|
487
|
+
|
|
488
|
+
Raises:
|
|
489
|
+
ValueError: If template not found.
|
|
490
|
+
|
|
491
|
+
Example:
|
|
492
|
+
>>> template = load_template("compliance")
|
|
493
|
+
>>> template = load_template("custom_template.yaml")
|
|
494
|
+
|
|
495
|
+
References:
|
|
496
|
+
REPORT-005: Template Inheritance
|
|
497
|
+
REPORT-006: User Template Registration
|
|
498
|
+
"""
|
|
499
|
+
template = None
|
|
500
|
+
|
|
501
|
+
# Check user-registered templates first (REPORT-006)
|
|
502
|
+
if name_or_path in _USER_TEMPLATES:
|
|
503
|
+
template = copy.deepcopy(_USER_TEMPLATES[name_or_path])
|
|
504
|
+
# Then check builtin templates
|
|
505
|
+
elif name_or_path in BUILTIN_TEMPLATES:
|
|
506
|
+
template = copy.deepcopy(BUILTIN_TEMPLATES[name_or_path])
|
|
507
|
+
else:
|
|
508
|
+
# Try loading from file
|
|
509
|
+
path = Path(name_or_path)
|
|
510
|
+
if path.exists():
|
|
511
|
+
template = _load_template_file(path)
|
|
512
|
+
else:
|
|
513
|
+
# Try adding .yaml extension
|
|
514
|
+
yaml_path = Path(f"{name_or_path}.yaml")
|
|
515
|
+
if yaml_path.exists():
|
|
516
|
+
template = _load_template_file(yaml_path)
|
|
517
|
+
|
|
518
|
+
if template is None:
|
|
519
|
+
raise ValueError(f"Template not found: {name_or_path}")
|
|
520
|
+
|
|
521
|
+
# Resolve inheritance if requested (REPORT-005)
|
|
522
|
+
if resolve_inheritance and template.extends:
|
|
523
|
+
template = _resolve_inheritance(template)
|
|
524
|
+
|
|
525
|
+
return template
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _load_template_file(path: Path) -> ReportTemplate:
|
|
529
|
+
"""Load template from YAML file.
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
path: Path to template YAML file.
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
ReportTemplate instance loaded from file.
|
|
536
|
+
|
|
537
|
+
References:
|
|
538
|
+
REPORT-007: Template Definition Format
|
|
539
|
+
"""
|
|
540
|
+
with open(path) as f:
|
|
541
|
+
data = yaml.safe_load(f)
|
|
542
|
+
|
|
543
|
+
template_data = data.get("template", data)
|
|
544
|
+
|
|
545
|
+
sections = []
|
|
546
|
+
for idx, sec_data in enumerate(template_data.get("sections", [])):
|
|
547
|
+
section = TemplateSection(
|
|
548
|
+
title=sec_data.get("title", ""),
|
|
549
|
+
content_type=sec_data.get("content_type", "text"),
|
|
550
|
+
condition=sec_data.get("condition"),
|
|
551
|
+
template=sec_data.get("template", sec_data.get("content", "")),
|
|
552
|
+
order=sec_data.get("order", idx * 10),
|
|
553
|
+
override=sec_data.get("override", False),
|
|
554
|
+
)
|
|
555
|
+
sections.append(section)
|
|
556
|
+
|
|
557
|
+
return ReportTemplate(
|
|
558
|
+
name=template_data.get("name", path.stem),
|
|
559
|
+
version=template_data.get("version", "1.0"),
|
|
560
|
+
description=template_data.get("description", ""),
|
|
561
|
+
author=template_data.get("author", ""),
|
|
562
|
+
extends=template_data.get("extends"),
|
|
563
|
+
sections=sections,
|
|
564
|
+
styles=template_data.get("styles", {}),
|
|
565
|
+
metadata=template_data.get("metadata", {}),
|
|
566
|
+
overrides=template_data.get("overrides", {}),
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def list_templates(*, include_user: bool = True) -> list[str]:
|
|
571
|
+
"""List available template names.
|
|
572
|
+
|
|
573
|
+
Args:
|
|
574
|
+
include_user: Include user-registered templates.
|
|
575
|
+
|
|
576
|
+
Returns:
|
|
577
|
+
List of template names.
|
|
578
|
+
|
|
579
|
+
References:
|
|
580
|
+
REPORT-006: User Template Registration
|
|
581
|
+
"""
|
|
582
|
+
names = list(BUILTIN_TEMPLATES.keys())
|
|
583
|
+
if include_user:
|
|
584
|
+
names.extend(_USER_TEMPLATES.keys())
|
|
585
|
+
return sorted(set(names))
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def get_template_info(name: str) -> dict[str, Any]:
|
|
589
|
+
"""Get information about a template.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
name: Template name.
|
|
593
|
+
|
|
594
|
+
Returns:
|
|
595
|
+
Dictionary with template info.
|
|
596
|
+
|
|
597
|
+
Raises:
|
|
598
|
+
ValueError: If template name unknown.
|
|
599
|
+
|
|
600
|
+
References:
|
|
601
|
+
REPORT-005: Template Inheritance
|
|
602
|
+
REPORT-006: User Template Registration
|
|
603
|
+
"""
|
|
604
|
+
if name in _USER_TEMPLATES:
|
|
605
|
+
template = _USER_TEMPLATES[name]
|
|
606
|
+
source = "user"
|
|
607
|
+
elif name in BUILTIN_TEMPLATES:
|
|
608
|
+
template = BUILTIN_TEMPLATES[name]
|
|
609
|
+
source = "builtin"
|
|
610
|
+
else:
|
|
611
|
+
raise ValueError(f"Unknown template: {name}")
|
|
612
|
+
|
|
613
|
+
return {
|
|
614
|
+
"name": template.name,
|
|
615
|
+
"version": template.version,
|
|
616
|
+
"description": template.description,
|
|
617
|
+
"author": template.author,
|
|
618
|
+
"extends": template.extends,
|
|
619
|
+
"num_sections": len(template.sections),
|
|
620
|
+
"section_titles": [sec.title for sec in template.sections],
|
|
621
|
+
"source": source,
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def save_template(template: ReportTemplate, path: str | Path) -> None:
|
|
626
|
+
"""Save template to YAML file.
|
|
627
|
+
|
|
628
|
+
Args:
|
|
629
|
+
template: Template to save.
|
|
630
|
+
path: Output file path.
|
|
631
|
+
|
|
632
|
+
References:
|
|
633
|
+
REPORT-007: Template Definition Format
|
|
634
|
+
"""
|
|
635
|
+
path = Path(path)
|
|
636
|
+
|
|
637
|
+
data = {
|
|
638
|
+
"template": {
|
|
639
|
+
"name": template.name,
|
|
640
|
+
"version": template.version,
|
|
641
|
+
"description": template.description,
|
|
642
|
+
"author": template.author,
|
|
643
|
+
"extends": template.extends,
|
|
644
|
+
"sections": [
|
|
645
|
+
{
|
|
646
|
+
"title": sec.title,
|
|
647
|
+
"content_type": sec.content_type,
|
|
648
|
+
"condition": sec.condition,
|
|
649
|
+
"template": sec.template,
|
|
650
|
+
"order": sec.order,
|
|
651
|
+
"override": sec.override,
|
|
652
|
+
}
|
|
653
|
+
for sec in template.sections
|
|
654
|
+
],
|
|
655
|
+
"styles": template.styles,
|
|
656
|
+
"metadata": template.metadata,
|
|
657
|
+
"overrides": template.overrides,
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
with open(path, "w") as f:
|
|
662
|
+
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def create_template(
|
|
666
|
+
name: str,
|
|
667
|
+
sections: list[TemplateSection],
|
|
668
|
+
*,
|
|
669
|
+
extends: str | None = None,
|
|
670
|
+
description: str = "",
|
|
671
|
+
author: str = "",
|
|
672
|
+
styles: dict[str, Any] | None = None,
|
|
673
|
+
) -> ReportTemplate:
|
|
674
|
+
"""Create a new template.
|
|
675
|
+
|
|
676
|
+
Convenience function for creating templates programmatically.
|
|
677
|
+
|
|
678
|
+
Args:
|
|
679
|
+
name: Template name.
|
|
680
|
+
sections: List of sections.
|
|
681
|
+
extends: Parent template name for inheritance.
|
|
682
|
+
description: Template description.
|
|
683
|
+
author: Template author.
|
|
684
|
+
styles: Style definitions.
|
|
685
|
+
|
|
686
|
+
Returns:
|
|
687
|
+
New ReportTemplate.
|
|
688
|
+
|
|
689
|
+
Example:
|
|
690
|
+
>>> template = create_template(
|
|
691
|
+
... "quick_report",
|
|
692
|
+
... sections=[
|
|
693
|
+
... TemplateSection(title="Summary", template="{{ summary }}"),
|
|
694
|
+
... TemplateSection(title="Results", content_type="table"),
|
|
695
|
+
... ],
|
|
696
|
+
... description="Quick summary report"
|
|
697
|
+
... )
|
|
698
|
+
|
|
699
|
+
References:
|
|
700
|
+
REPORT-007: Template Definition Format
|
|
701
|
+
"""
|
|
702
|
+
return ReportTemplate(
|
|
703
|
+
name=name,
|
|
704
|
+
description=description,
|
|
705
|
+
author=author,
|
|
706
|
+
extends=extends,
|
|
707
|
+
sections=sections,
|
|
708
|
+
styles=styles or {},
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
__all__ = [
|
|
713
|
+
"BUILTIN_TEMPLATES",
|
|
714
|
+
"ReportTemplate",
|
|
715
|
+
"TemplateSection",
|
|
716
|
+
"create_template",
|
|
717
|
+
"extend_template",
|
|
718
|
+
"get_template_info",
|
|
719
|
+
"list_templates",
|
|
720
|
+
"load_template",
|
|
721
|
+
"register_template",
|
|
722
|
+
"save_template",
|
|
723
|
+
"unregister_template",
|
|
724
|
+
]
|