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
oscura/ui/formatters.py
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
"""UI-specific formatting utilities for Oscura displays.
|
|
2
|
+
|
|
3
|
+
This module provides formatting functions tailored for user interface display,
|
|
4
|
+
including text alignment, truncation, color codes, and structured output.
|
|
5
|
+
|
|
6
|
+
- UI formatting for terminal and web outputs
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
>>> from oscura.ui.formatters import format_text, truncate, colorize
|
|
10
|
+
>>> format_text("Status", "active", align="left", width=20)
|
|
11
|
+
' Status: active'
|
|
12
|
+
>>> truncate("Very long text here", max_length=10)
|
|
13
|
+
'Very lon...'
|
|
14
|
+
>>> colorize("Success", color="green")
|
|
15
|
+
'\x1b[32mSuccess\x1b[0m'
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from enum import Enum
|
|
22
|
+
from typing import Any, Literal
|
|
23
|
+
|
|
24
|
+
# Type alias for color names accepted by colorize()
|
|
25
|
+
type ColorName = Literal["black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Color(Enum):
|
|
29
|
+
"""ANSI color codes for terminal output."""
|
|
30
|
+
|
|
31
|
+
BLACK = "\033[30m"
|
|
32
|
+
RED = "\033[31m"
|
|
33
|
+
GREEN = "\033[32m"
|
|
34
|
+
YELLOW = "\033[33m"
|
|
35
|
+
BLUE = "\033[34m"
|
|
36
|
+
MAGENTA = "\033[35m"
|
|
37
|
+
CYAN = "\033[36m"
|
|
38
|
+
WHITE = "\033[37m"
|
|
39
|
+
RESET = "\033[0m"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TextAlignment(Enum):
|
|
43
|
+
"""Text alignment options."""
|
|
44
|
+
|
|
45
|
+
LEFT = "left"
|
|
46
|
+
CENTER = "center"
|
|
47
|
+
RIGHT = "right"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class FormattedText:
|
|
52
|
+
"""Container for formatted text output.
|
|
53
|
+
|
|
54
|
+
Attributes:
|
|
55
|
+
content: The formatted text content.
|
|
56
|
+
color: Optional color code.
|
|
57
|
+
bold: Whether text should be bold.
|
|
58
|
+
width: Display width of the text.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
content: str
|
|
62
|
+
color: Color | None = None
|
|
63
|
+
bold: bool = False
|
|
64
|
+
width: int = 0
|
|
65
|
+
|
|
66
|
+
def __str__(self) -> str:
|
|
67
|
+
"""Get string representation with ANSI codes."""
|
|
68
|
+
result = self.content
|
|
69
|
+
if self.bold:
|
|
70
|
+
result = f"\033[1m{result}\033[0m"
|
|
71
|
+
if self.color and self.color != Color.RESET:
|
|
72
|
+
result = f"{self.color.value}{result}{Color.RESET.value}"
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def colorize(
|
|
77
|
+
text: str,
|
|
78
|
+
color: ColorName = "white",
|
|
79
|
+
bold: bool = False,
|
|
80
|
+
) -> str:
|
|
81
|
+
"""Apply ANSI color codes to text.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
text: Text to colorize.
|
|
85
|
+
color: Color name (black, red, green, yellow, blue, magenta, cyan, white).
|
|
86
|
+
bold: Apply bold formatting.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Text with ANSI color codes.
|
|
90
|
+
|
|
91
|
+
Example:
|
|
92
|
+
>>> colorize("Error", color="red")
|
|
93
|
+
'\x1b[31mError\x1b[0m'
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
color_enum = Color[color.upper()]
|
|
97
|
+
except KeyError:
|
|
98
|
+
color_enum = Color.WHITE
|
|
99
|
+
|
|
100
|
+
result = text
|
|
101
|
+
if bold:
|
|
102
|
+
result = f"\033[1m{result}\033[0m"
|
|
103
|
+
result = f"{color_enum.value}{result}{Color.RESET.value}"
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def truncate(
|
|
108
|
+
text: str,
|
|
109
|
+
max_length: int,
|
|
110
|
+
suffix: str = "...",
|
|
111
|
+
) -> str:
|
|
112
|
+
"""Truncate text to maximum length with suffix.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
text: Text to truncate.
|
|
116
|
+
max_length: Maximum length including suffix.
|
|
117
|
+
suffix: Suffix to append if truncated (default "...").
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Truncated text.
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
>>> truncate("Very long text here", max_length=10)
|
|
124
|
+
'Very lon...'
|
|
125
|
+
"""
|
|
126
|
+
if len(text) <= max_length:
|
|
127
|
+
return text
|
|
128
|
+
|
|
129
|
+
# Account for suffix length
|
|
130
|
+
truncate_at = max(0, max_length - len(suffix))
|
|
131
|
+
return text[:truncate_at] + suffix
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def align_text(
|
|
135
|
+
text: str,
|
|
136
|
+
width: int,
|
|
137
|
+
alignment: Literal["left", "center", "right"] = "left",
|
|
138
|
+
fill_char: str = " ",
|
|
139
|
+
) -> str:
|
|
140
|
+
"""Align text within a given width.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
text: Text to align.
|
|
144
|
+
width: Target width.
|
|
145
|
+
alignment: How to align (left, center, right).
|
|
146
|
+
fill_char: Character to use for padding.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Aligned text.
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
>>> align_text("Hello", 10, "center")
|
|
153
|
+
' Hello '
|
|
154
|
+
"""
|
|
155
|
+
if len(text) >= width:
|
|
156
|
+
return text
|
|
157
|
+
|
|
158
|
+
if alignment == "center":
|
|
159
|
+
return text.center(width, fill_char)
|
|
160
|
+
elif alignment == "right":
|
|
161
|
+
return text.rjust(width, fill_char)
|
|
162
|
+
else: # left
|
|
163
|
+
return text.ljust(width, fill_char)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def format_text(
|
|
167
|
+
label: str,
|
|
168
|
+
value: Any,
|
|
169
|
+
align: Literal["left", "center", "right"] = "left",
|
|
170
|
+
width: int | None = None,
|
|
171
|
+
separator: str = ": ",
|
|
172
|
+
color: ColorName | None = None,
|
|
173
|
+
) -> str:
|
|
174
|
+
"""Format a label-value pair for display.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
label: Label/key.
|
|
178
|
+
value: Value to display.
|
|
179
|
+
align: Text alignment.
|
|
180
|
+
width: Total width for alignment (including label, separator, value).
|
|
181
|
+
separator: Separator between label and value.
|
|
182
|
+
color: Optional color for the value.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Formatted text.
|
|
186
|
+
|
|
187
|
+
Example:
|
|
188
|
+
>>> format_text("Status", "active", align="left", width=20)
|
|
189
|
+
' Status: active'
|
|
190
|
+
"""
|
|
191
|
+
result = f"{label}{separator}{value}"
|
|
192
|
+
|
|
193
|
+
if color:
|
|
194
|
+
result = f"{label}{separator}{colorize(str(value), color=color)}"
|
|
195
|
+
|
|
196
|
+
if width:
|
|
197
|
+
result = align_text(result, width, alignment=align)
|
|
198
|
+
|
|
199
|
+
return result
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def format_table(
|
|
203
|
+
data: list[list[Any]],
|
|
204
|
+
headers: list[str] | None = None,
|
|
205
|
+
column_widths: list[int] | None = None,
|
|
206
|
+
align_columns: list[Literal["left", "center", "right"]] | None = None,
|
|
207
|
+
) -> str:
|
|
208
|
+
"""Format data as a simple table.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
data: List of rows (each row is a list of values).
|
|
212
|
+
headers: Optional header row.
|
|
213
|
+
column_widths: Optional column widths (auto-calculated if not provided).
|
|
214
|
+
align_columns: Alignment for each column.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Formatted table as string.
|
|
218
|
+
|
|
219
|
+
Example:
|
|
220
|
+
>>> data = [["Alice", 85], ["Bob", 92]]
|
|
221
|
+
>>> format_table(data, headers=["Name", "Score"])
|
|
222
|
+
"""
|
|
223
|
+
if not data:
|
|
224
|
+
return ""
|
|
225
|
+
|
|
226
|
+
# Calculate column widths
|
|
227
|
+
num_cols = len(data[0]) if data else 0
|
|
228
|
+
if headers:
|
|
229
|
+
num_cols = len(headers)
|
|
230
|
+
|
|
231
|
+
if column_widths is None:
|
|
232
|
+
column_widths = []
|
|
233
|
+
for col_idx in range(num_cols):
|
|
234
|
+
max_width = 0
|
|
235
|
+
if headers and col_idx < len(headers):
|
|
236
|
+
max_width = len(str(headers[col_idx]))
|
|
237
|
+
for row in data:
|
|
238
|
+
if col_idx < len(row):
|
|
239
|
+
max_width = max(max_width, len(str(row[col_idx])))
|
|
240
|
+
column_widths.append(max_width + 2)
|
|
241
|
+
|
|
242
|
+
# Default alignment
|
|
243
|
+
if align_columns is None:
|
|
244
|
+
align_columns = ["left"] * num_cols
|
|
245
|
+
|
|
246
|
+
output_lines = []
|
|
247
|
+
|
|
248
|
+
# Add headers if provided
|
|
249
|
+
if headers:
|
|
250
|
+
header_cells = []
|
|
251
|
+
for col_idx, header in enumerate(headers):
|
|
252
|
+
width = column_widths[col_idx] if col_idx < len(column_widths) else 10
|
|
253
|
+
aligned = align_text(str(header), width, align_columns[col_idx])
|
|
254
|
+
header_cells.append(aligned)
|
|
255
|
+
output_lines.append(" ".join(header_cells))
|
|
256
|
+
# Add separator
|
|
257
|
+
separator = "-" * sum(column_widths) + ("-" * (num_cols - 1))
|
|
258
|
+
output_lines.append(separator)
|
|
259
|
+
|
|
260
|
+
# Add data rows
|
|
261
|
+
for row in data:
|
|
262
|
+
row_cells = []
|
|
263
|
+
for col_idx, cell in enumerate(row):
|
|
264
|
+
width = column_widths[col_idx] if col_idx < len(column_widths) else 10
|
|
265
|
+
aligned = align_text(str(cell), width, align_columns[col_idx])
|
|
266
|
+
row_cells.append(aligned)
|
|
267
|
+
output_lines.append(" ".join(row_cells))
|
|
268
|
+
|
|
269
|
+
return "\n".join(output_lines)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def format_status(
|
|
273
|
+
status: Literal["pass", "fail", "warning", "info", "pending"],
|
|
274
|
+
message: str = "",
|
|
275
|
+
use_symbols: bool = True,
|
|
276
|
+
) -> str:
|
|
277
|
+
"""Format a status message with optional symbol.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
status: Status type (pass, fail, warning, info, pending).
|
|
281
|
+
message: Optional message text.
|
|
282
|
+
use_symbols: Use Unicode symbols or text.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
Formatted status string.
|
|
286
|
+
|
|
287
|
+
Example:
|
|
288
|
+
>>> format_status("pass", "All tests passed")
|
|
289
|
+
'✓ All tests passed'
|
|
290
|
+
"""
|
|
291
|
+
symbols = {
|
|
292
|
+
"pass": "✓",
|
|
293
|
+
"fail": "✗",
|
|
294
|
+
"warning": "⚠",
|
|
295
|
+
"info": "ℹ", # noqa: RUF001 - intentional unicode info symbol
|
|
296
|
+
"pending": "⏳",
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
colors: dict[str, ColorName] = {
|
|
300
|
+
"pass": "green",
|
|
301
|
+
"fail": "red",
|
|
302
|
+
"warning": "yellow",
|
|
303
|
+
"info": "blue",
|
|
304
|
+
"pending": "cyan",
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
# Get color with default, cast needed because dict.get default is str
|
|
308
|
+
status_color: ColorName = colors.get(status, "white") # type: ignore[assignment]
|
|
309
|
+
|
|
310
|
+
if use_symbols:
|
|
311
|
+
symbol = symbols.get(status, "•")
|
|
312
|
+
colored_symbol = colorize(symbol, color=status_color)
|
|
313
|
+
return f"{colored_symbol} {message}" if message else colored_symbol
|
|
314
|
+
else:
|
|
315
|
+
text = status.upper()
|
|
316
|
+
return (
|
|
317
|
+
colorize(f"{text}: {message}", color=status_color)
|
|
318
|
+
if message
|
|
319
|
+
else colorize(text, color=status_color)
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def format_percentage(
|
|
324
|
+
value: float,
|
|
325
|
+
decimals: int = 1,
|
|
326
|
+
show_bar: bool = False,
|
|
327
|
+
bar_width: int = 10,
|
|
328
|
+
) -> str:
|
|
329
|
+
"""Format a percentage with optional progress bar.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
value: Percentage value (0-100 or 0-1).
|
|
333
|
+
decimals: Decimal places.
|
|
334
|
+
show_bar: Include ASCII progress bar.
|
|
335
|
+
bar_width: Width of progress bar.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Formatted percentage string.
|
|
339
|
+
|
|
340
|
+
Example:
|
|
341
|
+
>>> format_percentage(0.75, show_bar=True)
|
|
342
|
+
'75.0% [████████ ]'
|
|
343
|
+
"""
|
|
344
|
+
# Normalize to 0-100 range
|
|
345
|
+
if value <= 1:
|
|
346
|
+
pct = value * 100
|
|
347
|
+
else:
|
|
348
|
+
pct = value
|
|
349
|
+
|
|
350
|
+
result = f"{pct:.{decimals}f}%"
|
|
351
|
+
|
|
352
|
+
if show_bar and 0 <= pct <= 100:
|
|
353
|
+
filled = int((pct / 100) * bar_width)
|
|
354
|
+
bar = "[" + "█" * filled + "░" * (bar_width - filled) + "]"
|
|
355
|
+
result = f"{result} {bar}"
|
|
356
|
+
|
|
357
|
+
return result
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def format_duration(seconds: float) -> str:
|
|
361
|
+
"""Format duration in seconds as human-readable string.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
seconds: Duration in seconds.
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
Human-readable duration (e.g., "1h 23m 45s").
|
|
368
|
+
|
|
369
|
+
Example:
|
|
370
|
+
>>> format_duration(5025)
|
|
371
|
+
'1h 23m 45s'
|
|
372
|
+
"""
|
|
373
|
+
if seconds < 0:
|
|
374
|
+
return "invalid"
|
|
375
|
+
|
|
376
|
+
hours = int(seconds // 3600)
|
|
377
|
+
minutes = int((seconds % 3600) // 60)
|
|
378
|
+
secs = int(seconds % 60)
|
|
379
|
+
millis = int((seconds % 1) * 1000)
|
|
380
|
+
|
|
381
|
+
if hours > 0:
|
|
382
|
+
return f"{hours}h {minutes}m {secs}s"
|
|
383
|
+
elif minutes > 0:
|
|
384
|
+
return f"{minutes}m {secs}s"
|
|
385
|
+
elif secs > 0:
|
|
386
|
+
return f"{secs}s"
|
|
387
|
+
else:
|
|
388
|
+
return f"{millis}ms"
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def format_size(bytes_value: int, precision: int = 2) -> str:
|
|
392
|
+
"""Format byte size as human-readable string.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
bytes_value: Size in bytes.
|
|
396
|
+
precision: Decimal precision.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
Human-readable size (e.g., "1.23 MB").
|
|
400
|
+
|
|
401
|
+
Example:
|
|
402
|
+
>>> format_size(1234567)
|
|
403
|
+
'1.18 MB'
|
|
404
|
+
"""
|
|
405
|
+
units = ["B", "KB", "MB", "GB", "TB"]
|
|
406
|
+
size = float(bytes_value)
|
|
407
|
+
|
|
408
|
+
for unit in units:
|
|
409
|
+
if size < 1024:
|
|
410
|
+
return f"{size:.{precision}f} {unit}"
|
|
411
|
+
size /= 1024
|
|
412
|
+
|
|
413
|
+
return f"{size:.{precision}f} PB"
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def format_list(
|
|
417
|
+
items: list[str],
|
|
418
|
+
style: Literal["bullet", "numbered", "comma", "newline"] = "bullet",
|
|
419
|
+
prefix: str = "",
|
|
420
|
+
) -> str:
|
|
421
|
+
"""Format a list of items with various styles.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
items: List of items to format.
|
|
425
|
+
style: Formatting style (bullet, numbered, comma, newline).
|
|
426
|
+
prefix: Prefix for each item.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
Formatted list as string.
|
|
430
|
+
|
|
431
|
+
Example:
|
|
432
|
+
>>> format_list(["a", "b", "c"], style="bullet")
|
|
433
|
+
' • a\\n • b\\n • c'
|
|
434
|
+
"""
|
|
435
|
+
if not items:
|
|
436
|
+
return ""
|
|
437
|
+
|
|
438
|
+
if style == "bullet":
|
|
439
|
+
return "\n".join(f"{prefix}• {item}" for item in items)
|
|
440
|
+
elif style == "numbered":
|
|
441
|
+
return "\n".join(f"{prefix}{i}. {item}" for i, item in enumerate(items, 1))
|
|
442
|
+
elif style == "comma":
|
|
443
|
+
return ", ".join(items)
|
|
444
|
+
else: # newline
|
|
445
|
+
return "\n".join(items)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def format_key_value_pairs(
|
|
449
|
+
pairs: dict[str, Any],
|
|
450
|
+
indent: int = 2,
|
|
451
|
+
separator: str = ": ",
|
|
452
|
+
) -> str:
|
|
453
|
+
"""Format key-value pairs with indentation.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
pairs: Dictionary of key-value pairs.
|
|
457
|
+
indent: Indentation level (spaces).
|
|
458
|
+
separator: Separator between key and value.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
Formatted key-value pairs as string.
|
|
462
|
+
|
|
463
|
+
Example:
|
|
464
|
+
>>> format_key_value_pairs({"name": "Alice", "age": 30})
|
|
465
|
+
' name: Alice\\n age: 30'
|
|
466
|
+
"""
|
|
467
|
+
if not pairs:
|
|
468
|
+
return ""
|
|
469
|
+
|
|
470
|
+
indent_str = " " * indent
|
|
471
|
+
lines = []
|
|
472
|
+
for key, value in pairs.items():
|
|
473
|
+
lines.append(f"{indent_str}{key}{separator}{value}")
|
|
474
|
+
return "\n".join(lines)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def format_code_block(
|
|
478
|
+
code: str,
|
|
479
|
+
line_numbers: bool = False,
|
|
480
|
+
indent: int = 0,
|
|
481
|
+
language: str | None = None,
|
|
482
|
+
) -> str:
|
|
483
|
+
"""Format code with optional line numbers and indentation.
|
|
484
|
+
|
|
485
|
+
Args:
|
|
486
|
+
code: Code content.
|
|
487
|
+
line_numbers: Include line numbers.
|
|
488
|
+
indent: Indentation level.
|
|
489
|
+
language: Programming language for syntax highlighting (currently unused).
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
Formatted code block.
|
|
493
|
+
|
|
494
|
+
Example:
|
|
495
|
+
>>> format_code_block("x = 1\\nprint(x)", line_numbers=True)
|
|
496
|
+
"""
|
|
497
|
+
indent_str = " " * indent
|
|
498
|
+
lines = code.split("\n")
|
|
499
|
+
|
|
500
|
+
if line_numbers:
|
|
501
|
+
max_num_width = len(str(len(lines)))
|
|
502
|
+
formatted_lines = []
|
|
503
|
+
for num, line in enumerate(lines, 1):
|
|
504
|
+
formatted_lines.append(f"{indent_str}{num:>{max_num_width}} | {line}")
|
|
505
|
+
return "\n".join(formatted_lines)
|
|
506
|
+
else:
|
|
507
|
+
return "\n".join(f"{indent_str}{line}" for line in lines)
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
__all__ = [
|
|
511
|
+
"Color",
|
|
512
|
+
"FormattedText",
|
|
513
|
+
"TextAlignment",
|
|
514
|
+
"align_text",
|
|
515
|
+
"colorize",
|
|
516
|
+
"format_code_block",
|
|
517
|
+
"format_duration",
|
|
518
|
+
"format_key_value_pairs",
|
|
519
|
+
"format_list",
|
|
520
|
+
"format_percentage",
|
|
521
|
+
"format_size",
|
|
522
|
+
"format_status",
|
|
523
|
+
"format_table",
|
|
524
|
+
"format_text",
|
|
525
|
+
"truncate",
|
|
526
|
+
]
|