oscura 0.0.1__py3-none-any.whl → 0.1.0__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.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.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.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""Multi-format report export for TraceKit.
|
|
2
|
+
|
|
3
|
+
This module provides unified export interface for generating reports in
|
|
4
|
+
multiple formats (HTML, PDF, DOCX, Markdown) from a single source.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.reporting.export import export_report
|
|
9
|
+
>>> export_report(report, "output", formats=["pdf", "html", "markdown"])
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from oscura.reporting.core import Report
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def export_report(
|
|
22
|
+
report: Report,
|
|
23
|
+
output_path: str | Path,
|
|
24
|
+
*,
|
|
25
|
+
formats: list[Literal["pdf", "html", "docx", "markdown"]] | None = None,
|
|
26
|
+
format_options: dict[str, Any] | None = None,
|
|
27
|
+
) -> dict[str, Path]:
|
|
28
|
+
"""Export report to multiple formats.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
report: Report object to export.
|
|
32
|
+
output_path: Base output path (without extension).
|
|
33
|
+
formats: List of formats to generate (default: ["pdf", "html"]).
|
|
34
|
+
format_options: Format-specific options dictionary.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Dictionary mapping format to output file path.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If unsupported format is specified.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> paths = export_report(
|
|
44
|
+
... report,
|
|
45
|
+
... "report",
|
|
46
|
+
... formats=["pdf", "html", "markdown"],
|
|
47
|
+
... format_options={
|
|
48
|
+
... "pdf": {"dpi": 300, "pdfa_compliance": True},
|
|
49
|
+
... "html": {"interactive": True, "dark_mode": True},
|
|
50
|
+
... }
|
|
51
|
+
... )
|
|
52
|
+
|
|
53
|
+
References:
|
|
54
|
+
REPORT-010
|
|
55
|
+
"""
|
|
56
|
+
if formats is None:
|
|
57
|
+
formats = ["pdf", "html"]
|
|
58
|
+
|
|
59
|
+
if format_options is None:
|
|
60
|
+
format_options = {}
|
|
61
|
+
|
|
62
|
+
output_path = Path(output_path)
|
|
63
|
+
generated_files = {}
|
|
64
|
+
|
|
65
|
+
for fmt in formats:
|
|
66
|
+
fmt_opts = format_options.get(fmt, {})
|
|
67
|
+
|
|
68
|
+
if fmt == "pdf":
|
|
69
|
+
file_path = _export_pdf(report, output_path, **fmt_opts)
|
|
70
|
+
elif fmt == "html":
|
|
71
|
+
file_path = _export_html(report, output_path, **fmt_opts)
|
|
72
|
+
elif fmt == "docx":
|
|
73
|
+
file_path = _export_docx(report, output_path, **fmt_opts)
|
|
74
|
+
elif fmt == "markdown":
|
|
75
|
+
file_path = _export_markdown(report, output_path, **fmt_opts)
|
|
76
|
+
else:
|
|
77
|
+
raise ValueError(f"Unsupported format: {fmt}")
|
|
78
|
+
|
|
79
|
+
generated_files[fmt] = file_path
|
|
80
|
+
|
|
81
|
+
return generated_files # type: ignore[return-value]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _export_pdf(
|
|
85
|
+
report: Report,
|
|
86
|
+
output_path: Path,
|
|
87
|
+
**options: Any,
|
|
88
|
+
) -> Path:
|
|
89
|
+
"""Export report as PDF."""
|
|
90
|
+
from oscura.reporting.pdf import save_pdf_report
|
|
91
|
+
|
|
92
|
+
path = output_path.with_suffix(".pdf")
|
|
93
|
+
save_pdf_report(report, path, **options)
|
|
94
|
+
return path
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _export_html(
|
|
98
|
+
report: Report,
|
|
99
|
+
output_path: Path,
|
|
100
|
+
**options: Any,
|
|
101
|
+
) -> Path:
|
|
102
|
+
"""Export report as HTML."""
|
|
103
|
+
from oscura.reporting.html import save_html_report
|
|
104
|
+
|
|
105
|
+
path = output_path.with_suffix(".html")
|
|
106
|
+
save_html_report(report, path, **options)
|
|
107
|
+
return path
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _export_markdown(
|
|
111
|
+
report: Report,
|
|
112
|
+
output_path: Path,
|
|
113
|
+
**options: Any,
|
|
114
|
+
) -> Path:
|
|
115
|
+
"""Export report as Markdown."""
|
|
116
|
+
path = output_path.with_suffix(".md")
|
|
117
|
+
markdown_content = report.to_markdown()
|
|
118
|
+
path.write_text(markdown_content, encoding="utf-8")
|
|
119
|
+
return path
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _export_docx(
|
|
123
|
+
report: Report,
|
|
124
|
+
output_path: Path,
|
|
125
|
+
**options: Any,
|
|
126
|
+
) -> Path:
|
|
127
|
+
"""Export report as DOCX.
|
|
128
|
+
|
|
129
|
+
Requires python-docx library.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
report: Report object to export.
|
|
133
|
+
output_path: Base output path (extension will be changed to .docx).
|
|
134
|
+
**options: Format-specific options (currently unused).
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Path to the created DOCX file.
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
ImportError: If python-docx library is not installed.
|
|
141
|
+
|
|
142
|
+
References:
|
|
143
|
+
REPORT-019
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
from docx import Document # type: ignore[import-not-found]
|
|
147
|
+
from docx.enum.text import ( # type: ignore[import-not-found]
|
|
148
|
+
WD_ALIGN_PARAGRAPH, # type: ignore[import-not-found]
|
|
149
|
+
)
|
|
150
|
+
from docx.shared import ( # noqa: F401 # type: ignore[import-not-found]
|
|
151
|
+
Inches,
|
|
152
|
+
Pt,
|
|
153
|
+
RGBColor,
|
|
154
|
+
)
|
|
155
|
+
except ImportError:
|
|
156
|
+
raise ImportError( # noqa: B904
|
|
157
|
+
"python-docx is required for DOCX export. Install with: pip install python-docx"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
path = output_path.with_suffix(".docx")
|
|
161
|
+
doc = Document()
|
|
162
|
+
|
|
163
|
+
# Add title
|
|
164
|
+
title = doc.add_heading(report.config.title, level=0)
|
|
165
|
+
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
166
|
+
|
|
167
|
+
# Add metadata
|
|
168
|
+
if report.config.author:
|
|
169
|
+
doc.add_paragraph(f"Author: {report.config.author}")
|
|
170
|
+
doc.add_paragraph(f"Date: {report.config.created.strftime('%Y-%m-%d %H:%M')}")
|
|
171
|
+
doc.add_paragraph() # Blank line
|
|
172
|
+
|
|
173
|
+
# Add sections
|
|
174
|
+
for section in report.sections:
|
|
175
|
+
if not section.visible:
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
# Section heading
|
|
179
|
+
doc.add_heading(section.title, level=section.level)
|
|
180
|
+
|
|
181
|
+
# Section content
|
|
182
|
+
if isinstance(section.content, str):
|
|
183
|
+
doc.add_paragraph(section.content)
|
|
184
|
+
|
|
185
|
+
elif isinstance(section.content, list):
|
|
186
|
+
for item in section.content:
|
|
187
|
+
if isinstance(item, dict):
|
|
188
|
+
if item.get("type") == "table":
|
|
189
|
+
_add_table_to_docx(doc, item)
|
|
190
|
+
elif item.get("type") == "figure":
|
|
191
|
+
# Placeholder for figures
|
|
192
|
+
doc.add_paragraph(f"[Figure: {item.get('caption', 'N/A')}]")
|
|
193
|
+
else:
|
|
194
|
+
doc.add_paragraph(str(item))
|
|
195
|
+
|
|
196
|
+
# Subsections
|
|
197
|
+
for subsec in section.subsections:
|
|
198
|
+
if not subsec.visible:
|
|
199
|
+
continue
|
|
200
|
+
doc.add_heading(subsec.title, level=subsec.level)
|
|
201
|
+
if isinstance(subsec.content, str):
|
|
202
|
+
doc.add_paragraph(subsec.content)
|
|
203
|
+
|
|
204
|
+
# Save document
|
|
205
|
+
doc.save(str(path))
|
|
206
|
+
return path
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _add_table_to_docx(doc: Any, table_dict: dict[str, Any]) -> None:
|
|
210
|
+
"""Add table to DOCX document."""
|
|
211
|
+
headers = table_dict.get("headers", [])
|
|
212
|
+
data = table_dict.get("data", [])
|
|
213
|
+
|
|
214
|
+
if not headers and not data:
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
# Create table
|
|
218
|
+
num_cols = len(headers) if headers else len(data[0]) if data else 0
|
|
219
|
+
num_rows = len(data) + (1 if headers else 0)
|
|
220
|
+
|
|
221
|
+
if num_rows == 0 or num_cols == 0:
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
table = doc.add_table(rows=num_rows, cols=num_cols)
|
|
225
|
+
table.style = "Light Grid Accent 1"
|
|
226
|
+
|
|
227
|
+
# Add headers
|
|
228
|
+
if headers:
|
|
229
|
+
header_cells = table.rows[0].cells
|
|
230
|
+
for i, header in enumerate(headers):
|
|
231
|
+
header_cells[i].text = str(header)
|
|
232
|
+
# Make header bold
|
|
233
|
+
for paragraph in header_cells[i].paragraphs:
|
|
234
|
+
for run in paragraph.runs:
|
|
235
|
+
run.bold = True
|
|
236
|
+
|
|
237
|
+
# Add data
|
|
238
|
+
start_row = 1 if headers else 0
|
|
239
|
+
for i, row in enumerate(data):
|
|
240
|
+
row_cells = table.rows[start_row + i].cells
|
|
241
|
+
for j, cell in enumerate(row):
|
|
242
|
+
row_cells[j].text = str(cell)
|
|
243
|
+
|
|
244
|
+
# Add caption
|
|
245
|
+
if table_dict.get("caption"):
|
|
246
|
+
doc.add_paragraph(table_dict["caption"], style="Caption")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def export_multiple_reports(
|
|
250
|
+
reports: dict[str, Report],
|
|
251
|
+
output_dir: str | Path,
|
|
252
|
+
*,
|
|
253
|
+
format: Literal["pdf", "html", "docx", "markdown"] = "pdf",
|
|
254
|
+
**options: Any,
|
|
255
|
+
) -> dict[str, Path]:
|
|
256
|
+
"""Export multiple reports to a directory.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
reports: Dictionary mapping name to Report object.
|
|
260
|
+
output_dir: Output directory path.
|
|
261
|
+
format: Export format for all reports.
|
|
262
|
+
**options: Format-specific options.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Dictionary mapping report name to output path.
|
|
266
|
+
|
|
267
|
+
References:
|
|
268
|
+
REPORT-010
|
|
269
|
+
"""
|
|
270
|
+
output_dir = Path(output_dir)
|
|
271
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
272
|
+
|
|
273
|
+
generated_files = {}
|
|
274
|
+
|
|
275
|
+
for name, report in reports.items():
|
|
276
|
+
output_path = output_dir / name
|
|
277
|
+
files = export_report(
|
|
278
|
+
report, output_path, formats=[format], format_options={format: options}
|
|
279
|
+
)
|
|
280
|
+
generated_files[name] = files[format]
|
|
281
|
+
|
|
282
|
+
return generated_files
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def batch_export_formats(
|
|
286
|
+
report: Report,
|
|
287
|
+
output_dir: str | Path,
|
|
288
|
+
*,
|
|
289
|
+
formats: list[str] | None = None,
|
|
290
|
+
**options: Any,
|
|
291
|
+
) -> dict[str, Path]:
|
|
292
|
+
"""Export single report to multiple formats in a directory.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
report: Report to export.
|
|
296
|
+
output_dir: Output directory.
|
|
297
|
+
formats: List of formats (default: all supported).
|
|
298
|
+
**options: Common options for all formats.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Dictionary mapping format to output path.
|
|
302
|
+
|
|
303
|
+
References:
|
|
304
|
+
REPORT-010
|
|
305
|
+
"""
|
|
306
|
+
if formats is None:
|
|
307
|
+
formats = ["pdf", "html", "docx", "markdown"]
|
|
308
|
+
|
|
309
|
+
output_dir = Path(output_dir)
|
|
310
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
311
|
+
|
|
312
|
+
base_name = report.config.title.lower().replace(" ", "_")
|
|
313
|
+
output_path = output_dir / base_name
|
|
314
|
+
|
|
315
|
+
return export_report(report, output_path, formats=formats, format_options=options) # type: ignore[arg-type]
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def create_archive(
|
|
319
|
+
files: dict[str, Path],
|
|
320
|
+
archive_path: str | Path,
|
|
321
|
+
*,
|
|
322
|
+
format: Literal["zip", "tar", "tar.gz"] = "zip",
|
|
323
|
+
) -> Path:
|
|
324
|
+
"""Create archive of exported report files.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
files: Dictionary of files to archive.
|
|
328
|
+
archive_path: Output archive path.
|
|
329
|
+
format: Archive format (zip, tar, tar.gz).
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Path to created archive.
|
|
333
|
+
|
|
334
|
+
Raises:
|
|
335
|
+
ValueError: If unsupported archive format is specified.
|
|
336
|
+
|
|
337
|
+
References:
|
|
338
|
+
REPORT-010
|
|
339
|
+
"""
|
|
340
|
+
from pathlib import Path
|
|
341
|
+
|
|
342
|
+
archive_path = Path(archive_path)
|
|
343
|
+
|
|
344
|
+
if format == "zip":
|
|
345
|
+
import zipfile
|
|
346
|
+
|
|
347
|
+
with zipfile.ZipFile(archive_path.with_suffix(".zip"), "w") as zipf:
|
|
348
|
+
for path in files.values():
|
|
349
|
+
zipf.write(path, arcname=path.name)
|
|
350
|
+
|
|
351
|
+
return archive_path.with_suffix(".zip")
|
|
352
|
+
|
|
353
|
+
elif format in ("tar", "tar.gz"):
|
|
354
|
+
import tarfile
|
|
355
|
+
|
|
356
|
+
mode = "w:gz" if format == "tar.gz" else "w"
|
|
357
|
+
suffix = ".tar.gz" if format == "tar.gz" else ".tar"
|
|
358
|
+
|
|
359
|
+
with tarfile.open(archive_path.with_suffix(suffix), mode) as tar: # type: ignore[call-overload]
|
|
360
|
+
for path in files.values():
|
|
361
|
+
tar.add(path, arcname=path.name)
|
|
362
|
+
|
|
363
|
+
return archive_path.with_suffix(suffix)
|
|
364
|
+
|
|
365
|
+
else:
|
|
366
|
+
raise ValueError(f"Unsupported archive format: {format}")
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Formatting utilities for reports."""
|
|
2
|
+
|
|
3
|
+
from oscura.reporting.formatting.emphasis import (
|
|
4
|
+
VisualEmphasis,
|
|
5
|
+
format_callout_box,
|
|
6
|
+
format_severity,
|
|
7
|
+
)
|
|
8
|
+
from oscura.reporting.formatting.numbers import (
|
|
9
|
+
NumberFormatter,
|
|
10
|
+
format_percentage,
|
|
11
|
+
format_range,
|
|
12
|
+
format_value,
|
|
13
|
+
format_with_context,
|
|
14
|
+
format_with_locale,
|
|
15
|
+
format_with_units,
|
|
16
|
+
)
|
|
17
|
+
from oscura.reporting.formatting.standards import (
|
|
18
|
+
ColorScheme,
|
|
19
|
+
FormatStandards,
|
|
20
|
+
Severity,
|
|
21
|
+
apply_formatting_standards,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def format_margin(
|
|
26
|
+
value: float,
|
|
27
|
+
limit: float,
|
|
28
|
+
unit: str = "",
|
|
29
|
+
limit_type: str = "upper",
|
|
30
|
+
) -> str:
|
|
31
|
+
"""Format a margin value with pass/fail indication.
|
|
32
|
+
|
|
33
|
+
Calculates the margin between a measured value and its specification limit,
|
|
34
|
+
then categorizes it as good (>20%), ok (10-20%), marginal (0-10%), or violation.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
value: Measured value.
|
|
38
|
+
limit: Limit value (specification).
|
|
39
|
+
unit: Unit of measurement.
|
|
40
|
+
limit_type: Type of limit ("upper" or "lower").
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Formatted margin string with status indication.
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
>>> format_margin(70, 100, limit_type="upper")
|
|
47
|
+
'30.0% margin (good)'
|
|
48
|
+
>>> format_margin(95, 100, limit_type="upper")
|
|
49
|
+
'5.0% margin (marginal)'
|
|
50
|
+
"""
|
|
51
|
+
# Calculate margin percentage
|
|
52
|
+
if limit != 0:
|
|
53
|
+
if limit_type == "upper":
|
|
54
|
+
margin = limit - value
|
|
55
|
+
margin_pct = (margin / limit) * 100
|
|
56
|
+
is_passing = value < limit
|
|
57
|
+
else: # lower
|
|
58
|
+
margin = value - limit
|
|
59
|
+
margin_pct = (margin / limit) * 100
|
|
60
|
+
is_passing = value > limit
|
|
61
|
+
else:
|
|
62
|
+
margin_pct = 0
|
|
63
|
+
is_passing = False
|
|
64
|
+
|
|
65
|
+
# Determine status based on margin percentage
|
|
66
|
+
if not is_passing:
|
|
67
|
+
status = "violation"
|
|
68
|
+
elif margin_pct >= 20:
|
|
69
|
+
status = "good"
|
|
70
|
+
elif margin_pct >= 10:
|
|
71
|
+
status = "ok"
|
|
72
|
+
else:
|
|
73
|
+
status = "marginal"
|
|
74
|
+
|
|
75
|
+
return f"{abs(margin_pct):.1f}% margin ({status})"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def format_pass_fail(passed: bool, message: str = "", with_symbol: bool = True) -> str:
|
|
79
|
+
"""Format pass/fail status.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
passed: Whether the test passed.
|
|
83
|
+
message: Optional message to append.
|
|
84
|
+
with_symbol: Whether to include Unicode symbol (checkmark/cross).
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Formatted pass/fail string.
|
|
88
|
+
|
|
89
|
+
Examples:
|
|
90
|
+
>>> format_pass_fail(True)
|
|
91
|
+
'PASS ✓'
|
|
92
|
+
>>> format_pass_fail(False, with_symbol=False)
|
|
93
|
+
'FAIL'
|
|
94
|
+
"""
|
|
95
|
+
status = "PASS" if passed else "FAIL"
|
|
96
|
+
|
|
97
|
+
if with_symbol:
|
|
98
|
+
symbol = "\u2713" if passed else "\u2717"
|
|
99
|
+
result = f"{status} {symbol}"
|
|
100
|
+
else:
|
|
101
|
+
result = status
|
|
102
|
+
|
|
103
|
+
if message:
|
|
104
|
+
return f"{result}: {message}"
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
__all__ = [
|
|
109
|
+
"ColorScheme",
|
|
110
|
+
# Standards
|
|
111
|
+
"FormatStandards",
|
|
112
|
+
# Numbers
|
|
113
|
+
"NumberFormatter",
|
|
114
|
+
"Severity",
|
|
115
|
+
# Emphasis
|
|
116
|
+
"VisualEmphasis",
|
|
117
|
+
"apply_formatting_standards",
|
|
118
|
+
"format_callout_box",
|
|
119
|
+
# Convenience
|
|
120
|
+
"format_margin",
|
|
121
|
+
"format_pass_fail",
|
|
122
|
+
"format_percentage",
|
|
123
|
+
"format_range",
|
|
124
|
+
"format_severity",
|
|
125
|
+
"format_value",
|
|
126
|
+
"format_with_context",
|
|
127
|
+
"format_with_locale",
|
|
128
|
+
"format_with_units",
|
|
129
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Text emphasis formatting utilities.
|
|
2
|
+
|
|
3
|
+
Simple text formatting for terminal output.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VisualEmphasis(Enum):
|
|
10
|
+
"""Visual emphasis levels."""
|
|
11
|
+
|
|
12
|
+
NONE = "none"
|
|
13
|
+
SUBTLE = "subtle"
|
|
14
|
+
MODERATE = "moderate"
|
|
15
|
+
STRONG = "strong"
|
|
16
|
+
CRITICAL = "critical"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def bold(text: str) -> str:
|
|
20
|
+
"""Make text bold (terminal)."""
|
|
21
|
+
return f"\033[1m{text}\033[0m"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def italic(text: str) -> str:
|
|
25
|
+
"""Make text italic (terminal)."""
|
|
26
|
+
return f"\033[3m{text}\033[0m"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def underline(text: str) -> str:
|
|
30
|
+
"""Underline text (terminal)."""
|
|
31
|
+
return f"\033[4m{text}\033[0m"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def color(text: str, color_code: str) -> str:
|
|
35
|
+
"""Color text (terminal)."""
|
|
36
|
+
colors = {
|
|
37
|
+
"red": "31",
|
|
38
|
+
"green": "32",
|
|
39
|
+
"yellow": "33",
|
|
40
|
+
"blue": "34",
|
|
41
|
+
"magenta": "35",
|
|
42
|
+
"cyan": "36",
|
|
43
|
+
"white": "37",
|
|
44
|
+
}
|
|
45
|
+
code = colors.get(color_code.lower(), "37")
|
|
46
|
+
return f"\033[{code}m{text}\033[0m"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def format_severity(text: str, severity: str) -> str:
|
|
50
|
+
"""Format text based on severity level."""
|
|
51
|
+
severity_colors = {
|
|
52
|
+
"critical": "red",
|
|
53
|
+
"error": "red",
|
|
54
|
+
"warning": "yellow",
|
|
55
|
+
"info": "blue",
|
|
56
|
+
"success": "green",
|
|
57
|
+
}
|
|
58
|
+
return color(text, severity_colors.get(severity.lower(), "white"))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def format_callout_box(title: str, content: str, severity: str = "info") -> str:
|
|
62
|
+
"""Format a callout box."""
|
|
63
|
+
lines = [
|
|
64
|
+
"=" * 60,
|
|
65
|
+
format_severity(f" {title.upper()} ", severity),
|
|
66
|
+
"-" * 60,
|
|
67
|
+
content,
|
|
68
|
+
"=" * 60,
|
|
69
|
+
]
|
|
70
|
+
return "\n".join(lines)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
"VisualEmphasis",
|
|
75
|
+
"bold",
|
|
76
|
+
"color",
|
|
77
|
+
"format_callout_box",
|
|
78
|
+
"format_severity",
|
|
79
|
+
"italic",
|
|
80
|
+
"underline",
|
|
81
|
+
]
|