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
oscura/plugins/cli.py
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
"""Plugin management CLI.
|
|
2
|
+
|
|
3
|
+
This module provides command-line interface for plugin management including
|
|
4
|
+
list, info, enable/disable, install, and validate operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import logging
|
|
11
|
+
import shutil
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import tempfile
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from urllib.parse import urlparse
|
|
17
|
+
|
|
18
|
+
from oscura.plugins.discovery import discover_plugins, get_plugin_paths
|
|
19
|
+
from oscura.plugins.lifecycle import get_lifecycle_manager
|
|
20
|
+
from oscura.plugins.registry import get_plugin_registry
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PluginInstaller:
|
|
26
|
+
"""Plugin installer with integrity validation.
|
|
27
|
+
|
|
28
|
+
Supports installing plugins from:
|
|
29
|
+
- Repository URLs (git)
|
|
30
|
+
- Archive files (.tar.gz, .zip)
|
|
31
|
+
- Local directories
|
|
32
|
+
|
|
33
|
+
References:
|
|
34
|
+
PLUG-007: Plugin CLI - install from repository URL, integrity validation
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, install_dir: Path | None = None) -> None:
|
|
38
|
+
"""Initialize installer.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
install_dir: Directory to install plugins (defaults to user plugins)
|
|
42
|
+
"""
|
|
43
|
+
if install_dir is None:
|
|
44
|
+
paths = get_plugin_paths()
|
|
45
|
+
# Use first user-writable path
|
|
46
|
+
install_dir = paths[0] if paths else Path.home() / ".oscura" / "plugins"
|
|
47
|
+
|
|
48
|
+
self.install_dir = install_dir
|
|
49
|
+
self.install_dir.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
|
|
51
|
+
def install_from_url(
|
|
52
|
+
self,
|
|
53
|
+
url: str,
|
|
54
|
+
*,
|
|
55
|
+
checksum: str | None = None,
|
|
56
|
+
checksum_algo: str = "sha256",
|
|
57
|
+
) -> Path:
|
|
58
|
+
"""Install plugin from URL.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
url: Plugin repository URL or archive URL
|
|
62
|
+
checksum: Expected checksum for integrity validation
|
|
63
|
+
checksum_algo: Hash algorithm (sha256, sha512, md5)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Path to installed plugin
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
ValueError: If checksum verification fails or unsupported URL type.
|
|
70
|
+
|
|
71
|
+
References:
|
|
72
|
+
PLUG-007: Plugin CLI - install from repository URL, integrity validation
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
>>> installer = PluginInstaller()
|
|
76
|
+
>>> path = installer.install_from_url(
|
|
77
|
+
... "https://github.com/user/plugin.git",
|
|
78
|
+
... checksum="abc123...",
|
|
79
|
+
... )
|
|
80
|
+
"""
|
|
81
|
+
logger.info(f"Installing plugin from URL: {url}")
|
|
82
|
+
|
|
83
|
+
parsed = urlparse(url)
|
|
84
|
+
|
|
85
|
+
# Determine source type
|
|
86
|
+
if parsed.path.endswith(".git") or "github.com" in parsed.netloc:
|
|
87
|
+
return self._install_from_git(url, checksum, checksum_algo)
|
|
88
|
+
elif parsed.path.endswith((".tar.gz", ".zip")):
|
|
89
|
+
return self._install_from_archive(url, checksum, checksum_algo)
|
|
90
|
+
else:
|
|
91
|
+
raise ValueError(f"Unsupported URL type: {url}")
|
|
92
|
+
|
|
93
|
+
def _install_from_git(
|
|
94
|
+
self,
|
|
95
|
+
url: str,
|
|
96
|
+
checksum: str | None,
|
|
97
|
+
checksum_algo: str,
|
|
98
|
+
) -> Path:
|
|
99
|
+
"""Install plugin from git repository.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
url: Git repository URL
|
|
103
|
+
checksum: Expected checksum
|
|
104
|
+
checksum_algo: Hash algorithm
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Path to installed plugin
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
RuntimeError: If git clone fails.
|
|
111
|
+
ValueError: If checksum verification fails.
|
|
112
|
+
|
|
113
|
+
References:
|
|
114
|
+
PLUG-007: Plugin CLI - install from repository URL
|
|
115
|
+
"""
|
|
116
|
+
# Extract plugin name from URL
|
|
117
|
+
plugin_name = Path(urlparse(url).path).stem
|
|
118
|
+
if plugin_name.endswith(".git"):
|
|
119
|
+
plugin_name = plugin_name[:-4]
|
|
120
|
+
|
|
121
|
+
target_dir = self.install_dir / plugin_name
|
|
122
|
+
|
|
123
|
+
# Clone repository to temp directory first
|
|
124
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
125
|
+
temp_path = Path(temp_dir) / plugin_name
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
subprocess.run(
|
|
129
|
+
["git", "clone", url, str(temp_path)],
|
|
130
|
+
check=True,
|
|
131
|
+
capture_output=True,
|
|
132
|
+
text=True,
|
|
133
|
+
)
|
|
134
|
+
except subprocess.CalledProcessError as e:
|
|
135
|
+
raise RuntimeError(f"Git clone failed: {e.stderr}") from e
|
|
136
|
+
|
|
137
|
+
# Validate checksum if provided
|
|
138
|
+
if checksum:
|
|
139
|
+
actual = self._compute_directory_checksum(temp_path, checksum_algo)
|
|
140
|
+
if actual != checksum:
|
|
141
|
+
raise ValueError(f"Checksum mismatch: expected {checksum}, got {actual}")
|
|
142
|
+
|
|
143
|
+
# Move to final location
|
|
144
|
+
if target_dir.exists():
|
|
145
|
+
logger.warning(f"Removing existing plugin at {target_dir}")
|
|
146
|
+
shutil.rmtree(target_dir)
|
|
147
|
+
|
|
148
|
+
shutil.copytree(temp_path, target_dir)
|
|
149
|
+
|
|
150
|
+
logger.info(f"Installed plugin '{plugin_name}' to {target_dir}")
|
|
151
|
+
return target_dir
|
|
152
|
+
|
|
153
|
+
def _install_from_archive(
|
|
154
|
+
self,
|
|
155
|
+
url: str,
|
|
156
|
+
checksum: str | None,
|
|
157
|
+
checksum_algo: str,
|
|
158
|
+
) -> Path:
|
|
159
|
+
"""Install plugin from archive file.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
url: Archive URL
|
|
163
|
+
checksum: Expected checksum
|
|
164
|
+
checksum_algo: Hash algorithm
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Path to installed plugin
|
|
168
|
+
|
|
169
|
+
Raises:
|
|
170
|
+
RuntimeError: If download fails.
|
|
171
|
+
ValueError: If checksum verification fails or archive format is invalid.
|
|
172
|
+
|
|
173
|
+
References:
|
|
174
|
+
PLUG-007: Plugin CLI - integrity validation
|
|
175
|
+
"""
|
|
176
|
+
import urllib.request
|
|
177
|
+
|
|
178
|
+
# Download archive
|
|
179
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
180
|
+
archive_path = Path(temp_dir) / "plugin.archive"
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
urllib.request.urlretrieve(url, archive_path)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
raise RuntimeError(f"Download failed: {e}") from e
|
|
186
|
+
|
|
187
|
+
# Validate checksum
|
|
188
|
+
if checksum:
|
|
189
|
+
actual = self._compute_file_checksum(archive_path, checksum_algo)
|
|
190
|
+
if actual != checksum:
|
|
191
|
+
raise ValueError(f"Checksum mismatch: expected {checksum}, got {actual}")
|
|
192
|
+
|
|
193
|
+
# Extract archive
|
|
194
|
+
extract_dir = Path(temp_dir) / "extracted"
|
|
195
|
+
shutil.unpack_archive(archive_path, extract_dir)
|
|
196
|
+
|
|
197
|
+
# Find plugin directory (should be single top-level dir)
|
|
198
|
+
plugin_dirs = [d for d in extract_dir.iterdir() if d.is_dir()]
|
|
199
|
+
if len(plugin_dirs) != 1:
|
|
200
|
+
raise ValueError(
|
|
201
|
+
f"Archive should contain single plugin directory, found {len(plugin_dirs)}"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
plugin_dir = plugin_dirs[0]
|
|
205
|
+
plugin_name = plugin_dir.name
|
|
206
|
+
|
|
207
|
+
target_dir = self.install_dir / plugin_name
|
|
208
|
+
|
|
209
|
+
# Move to final location
|
|
210
|
+
if target_dir.exists():
|
|
211
|
+
logger.warning(f"Removing existing plugin at {target_dir}")
|
|
212
|
+
shutil.rmtree(target_dir)
|
|
213
|
+
|
|
214
|
+
shutil.copytree(plugin_dir, target_dir)
|
|
215
|
+
|
|
216
|
+
logger.info(f"Installed plugin '{plugin_name}' to {target_dir}")
|
|
217
|
+
return target_dir
|
|
218
|
+
|
|
219
|
+
def _compute_file_checksum(self, path: Path, algo: str) -> str:
|
|
220
|
+
"""Compute checksum of a file.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
path: File path
|
|
224
|
+
algo: Hash algorithm
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Hexadecimal checksum
|
|
228
|
+
|
|
229
|
+
References:
|
|
230
|
+
PLUG-007: Plugin CLI - integrity validation (checksum verification)
|
|
231
|
+
"""
|
|
232
|
+
hasher = hashlib.new(algo)
|
|
233
|
+
|
|
234
|
+
with open(path, "rb") as f:
|
|
235
|
+
while chunk := f.read(8192):
|
|
236
|
+
hasher.update(chunk)
|
|
237
|
+
|
|
238
|
+
return hasher.hexdigest()
|
|
239
|
+
|
|
240
|
+
def _compute_directory_checksum(self, path: Path, algo: str) -> str:
|
|
241
|
+
"""Compute checksum of a directory (all files).
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
path: Directory path
|
|
245
|
+
algo: Hash algorithm
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Hexadecimal checksum
|
|
249
|
+
|
|
250
|
+
References:
|
|
251
|
+
PLUG-007: Plugin CLI - integrity validation (checksum verification)
|
|
252
|
+
"""
|
|
253
|
+
hasher = hashlib.new(algo)
|
|
254
|
+
|
|
255
|
+
# Sort files for consistent ordering
|
|
256
|
+
files = sorted(path.rglob("*"))
|
|
257
|
+
|
|
258
|
+
for file_path in files:
|
|
259
|
+
if file_path.is_file():
|
|
260
|
+
# Include relative path in hash
|
|
261
|
+
rel_path = file_path.relative_to(path)
|
|
262
|
+
hasher.update(str(rel_path).encode())
|
|
263
|
+
|
|
264
|
+
# Include file content
|
|
265
|
+
with open(file_path, "rb") as f:
|
|
266
|
+
while chunk := f.read(8192):
|
|
267
|
+
hasher.update(chunk)
|
|
268
|
+
|
|
269
|
+
return hasher.hexdigest()
|
|
270
|
+
|
|
271
|
+
def validate_integrity(
|
|
272
|
+
self,
|
|
273
|
+
plugin_path: Path,
|
|
274
|
+
expected_checksum: str,
|
|
275
|
+
algo: str = "sha256",
|
|
276
|
+
) -> bool:
|
|
277
|
+
"""Validate plugin integrity.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
plugin_path: Path to plugin
|
|
281
|
+
expected_checksum: Expected checksum
|
|
282
|
+
algo: Hash algorithm
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
True if checksum matches
|
|
286
|
+
|
|
287
|
+
References:
|
|
288
|
+
PLUG-007: Plugin CLI - integrity validation (checksum verification)
|
|
289
|
+
"""
|
|
290
|
+
if plugin_path.is_file():
|
|
291
|
+
actual = self._compute_file_checksum(plugin_path, algo)
|
|
292
|
+
else:
|
|
293
|
+
actual = self._compute_directory_checksum(plugin_path, algo)
|
|
294
|
+
|
|
295
|
+
return actual == expected_checksum
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def cli_list_plugins() -> None:
|
|
299
|
+
"""List all available plugins (CLI command).
|
|
300
|
+
|
|
301
|
+
References:
|
|
302
|
+
PLUG-007: Plugin CLI
|
|
303
|
+
"""
|
|
304
|
+
plugins = discover_plugins(compatible_only=False)
|
|
305
|
+
|
|
306
|
+
if not plugins:
|
|
307
|
+
print("No plugins found")
|
|
308
|
+
return
|
|
309
|
+
|
|
310
|
+
print(f"Found {len(plugins)} plugins:\n")
|
|
311
|
+
|
|
312
|
+
for plugin in plugins:
|
|
313
|
+
status = "enabled" if plugin.metadata.enabled else "disabled"
|
|
314
|
+
compat = "compatible" if plugin.compatible else "incompatible"
|
|
315
|
+
|
|
316
|
+
print(f" {plugin.metadata.name} v{plugin.metadata.version} [{status}]")
|
|
317
|
+
if plugin.path:
|
|
318
|
+
print(f" Path: {plugin.path}")
|
|
319
|
+
print(f" API: {plugin.metadata.api_version} ({compat})")
|
|
320
|
+
|
|
321
|
+
if plugin.metadata.provides:
|
|
322
|
+
provides: list[str] = []
|
|
323
|
+
for key, values in plugin.metadata.provides.items():
|
|
324
|
+
provides.extend(f"{key}:{v}" for v in values)
|
|
325
|
+
print(f" Provides: {', '.join(provides)}")
|
|
326
|
+
|
|
327
|
+
if plugin.load_error:
|
|
328
|
+
print(f" Error: {plugin.load_error}")
|
|
329
|
+
|
|
330
|
+
print()
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def cli_plugin_info(name: str) -> None:
|
|
334
|
+
"""Show detailed plugin information (CLI command).
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
name: Plugin name
|
|
338
|
+
|
|
339
|
+
References:
|
|
340
|
+
PLUG-007: Plugin CLI
|
|
341
|
+
"""
|
|
342
|
+
registry = get_plugin_registry()
|
|
343
|
+
metadata = registry.get_metadata(name)
|
|
344
|
+
|
|
345
|
+
if metadata is None:
|
|
346
|
+
print(f"Plugin '{name}' not found")
|
|
347
|
+
sys.exit(1)
|
|
348
|
+
|
|
349
|
+
print(f"Name: {metadata.name}")
|
|
350
|
+
print(f"Version: {metadata.version}")
|
|
351
|
+
print(f"API Version: {metadata.api_version}")
|
|
352
|
+
|
|
353
|
+
if metadata.author:
|
|
354
|
+
print(f"Author: {metadata.author}")
|
|
355
|
+
|
|
356
|
+
if metadata.description:
|
|
357
|
+
print(f"Description: {metadata.description}")
|
|
358
|
+
|
|
359
|
+
if metadata.homepage:
|
|
360
|
+
print(f"Homepage: {metadata.homepage}")
|
|
361
|
+
|
|
362
|
+
if metadata.license:
|
|
363
|
+
print(f"License: {metadata.license}")
|
|
364
|
+
|
|
365
|
+
if metadata.path:
|
|
366
|
+
print(f"Path: {metadata.path}")
|
|
367
|
+
|
|
368
|
+
print(f"Status: {'enabled' if metadata.enabled else 'disabled'}")
|
|
369
|
+
|
|
370
|
+
if metadata.dependencies:
|
|
371
|
+
print("\nDependencies:")
|
|
372
|
+
for dep, version in metadata.dependencies.items():
|
|
373
|
+
print(f" - {dep} {version}")
|
|
374
|
+
|
|
375
|
+
if metadata.provides:
|
|
376
|
+
print("\nProvides:")
|
|
377
|
+
for key, values in metadata.provides.items():
|
|
378
|
+
for value in values:
|
|
379
|
+
print(f" - {key}: {value}")
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def cli_enable_plugin(name: str) -> None:
|
|
383
|
+
"""Enable a plugin (CLI command).
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
name: Plugin name
|
|
387
|
+
|
|
388
|
+
References:
|
|
389
|
+
PLUG-007: Plugin CLI
|
|
390
|
+
"""
|
|
391
|
+
manager = get_lifecycle_manager()
|
|
392
|
+
|
|
393
|
+
try:
|
|
394
|
+
manager.enable_plugin(name)
|
|
395
|
+
print(f"Plugin '{name}' enabled")
|
|
396
|
+
except Exception as e:
|
|
397
|
+
print(f"Failed to enable plugin: {e}")
|
|
398
|
+
sys.exit(1)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def cli_disable_plugin(name: str) -> None:
|
|
402
|
+
"""Disable a plugin (CLI command).
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
name: Plugin name
|
|
406
|
+
|
|
407
|
+
References:
|
|
408
|
+
PLUG-007: Plugin CLI
|
|
409
|
+
"""
|
|
410
|
+
manager = get_lifecycle_manager()
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
manager.disable_plugin(name)
|
|
414
|
+
print(f"Plugin '{name}' disabled")
|
|
415
|
+
except Exception as e:
|
|
416
|
+
print(f"Failed to disable plugin: {e}")
|
|
417
|
+
sys.exit(1)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def cli_validate_plugin(name: str) -> None:
|
|
421
|
+
"""Validate a plugin (CLI command).
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
name: Plugin name
|
|
425
|
+
|
|
426
|
+
References:
|
|
427
|
+
PLUG-007: Plugin CLI - integrity validation
|
|
428
|
+
"""
|
|
429
|
+
plugins = discover_plugins(compatible_only=False)
|
|
430
|
+
plugin = next((p for p in plugins if p.metadata.name == name), None)
|
|
431
|
+
|
|
432
|
+
if plugin is None:
|
|
433
|
+
print(f"Plugin '{name}' not found")
|
|
434
|
+
sys.exit(1)
|
|
435
|
+
|
|
436
|
+
print(f"Validating {name}...")
|
|
437
|
+
|
|
438
|
+
# Check metadata
|
|
439
|
+
if not plugin.metadata.name:
|
|
440
|
+
print(" ✗ Missing name")
|
|
441
|
+
sys.exit(1)
|
|
442
|
+
print(" ✓ Metadata valid")
|
|
443
|
+
|
|
444
|
+
# Check dependencies
|
|
445
|
+
if plugin.metadata.dependencies:
|
|
446
|
+
print(f" ✓ Dependencies declared: {len(plugin.metadata.dependencies)}")
|
|
447
|
+
else:
|
|
448
|
+
print(" ✓ No dependencies")
|
|
449
|
+
|
|
450
|
+
# Check API compatibility
|
|
451
|
+
if plugin.compatible:
|
|
452
|
+
print(" ✓ API version compatible")
|
|
453
|
+
else:
|
|
454
|
+
print(f" ✗ API version incompatible: {plugin.metadata.api_version}")
|
|
455
|
+
sys.exit(1)
|
|
456
|
+
|
|
457
|
+
# Check for errors
|
|
458
|
+
if plugin.load_error:
|
|
459
|
+
print(f" ✗ Load error: {plugin.load_error}")
|
|
460
|
+
sys.exit(1)
|
|
461
|
+
|
|
462
|
+
print("\nPlugin is valid")
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def cli_install_plugin(
|
|
466
|
+
url: str,
|
|
467
|
+
*,
|
|
468
|
+
checksum: str | None = None,
|
|
469
|
+
) -> None:
|
|
470
|
+
"""Install a plugin from URL (CLI command).
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
url: Plugin repository or archive URL
|
|
474
|
+
checksum: Expected checksum for validation
|
|
475
|
+
|
|
476
|
+
References:
|
|
477
|
+
PLUG-007: Plugin CLI - install from repository URL, integrity validation
|
|
478
|
+
"""
|
|
479
|
+
installer = PluginInstaller()
|
|
480
|
+
|
|
481
|
+
try:
|
|
482
|
+
path = installer.install_from_url(url, checksum=checksum)
|
|
483
|
+
print(f"Successfully installed plugin to {path}")
|
|
484
|
+
except Exception as e:
|
|
485
|
+
print(f"Installation failed: {e}")
|
|
486
|
+
sys.exit(1)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
__all__ = [
|
|
490
|
+
"PluginInstaller",
|
|
491
|
+
"cli_disable_plugin",
|
|
492
|
+
"cli_enable_plugin",
|
|
493
|
+
"cli_install_plugin",
|
|
494
|
+
"cli_list_plugins",
|
|
495
|
+
"cli_plugin_info",
|
|
496
|
+
"cli_validate_plugin",
|
|
497
|
+
]
|