oscura 0.0.1__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
"""Extension documentation auto-generation system.
|
|
2
|
+
|
|
3
|
+
This module provides automatic documentation generation for Oscura extensions
|
|
4
|
+
including API reference, usage examples, and metadata extraction from docstrings.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.extensibility.docs import generate_extension_docs
|
|
9
|
+
>>> from pathlib import Path
|
|
10
|
+
>>>
|
|
11
|
+
>>> # Generate documentation for an extension
|
|
12
|
+
>>> docs = generate_extension_docs(Path("my_plugin/"))
|
|
13
|
+
>>> print(docs.markdown)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import ast
|
|
19
|
+
import inspect
|
|
20
|
+
import logging
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from typing import TYPE_CHECKING, Any
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class FunctionDoc:
|
|
32
|
+
"""Documentation for a function or method.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
name: Function name
|
|
36
|
+
signature: Full function signature
|
|
37
|
+
docstring: Function docstring
|
|
38
|
+
parameters: List of parameter descriptions
|
|
39
|
+
returns: Return value description
|
|
40
|
+
examples: Code examples from docstring
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
name: str
|
|
44
|
+
signature: str = ""
|
|
45
|
+
docstring: str = ""
|
|
46
|
+
parameters: list[tuple[str, str]] = field(default_factory=list)
|
|
47
|
+
returns: str = ""
|
|
48
|
+
examples: list[str] = field(default_factory=list)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class ClassDoc:
|
|
53
|
+
"""Documentation for a class.
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
name: Class name
|
|
57
|
+
docstring: Class docstring
|
|
58
|
+
methods: List of public method documentation
|
|
59
|
+
attributes: List of class/instance attributes
|
|
60
|
+
bases: List of base class names
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
name: str
|
|
64
|
+
docstring: str = ""
|
|
65
|
+
methods: list[FunctionDoc] = field(default_factory=list)
|
|
66
|
+
attributes: list[tuple[str, str]] = field(default_factory=list)
|
|
67
|
+
bases: list[str] = field(default_factory=list)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class ModuleDoc:
|
|
72
|
+
"""Documentation for a Python module.
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
name: Module name
|
|
76
|
+
docstring: Module docstring
|
|
77
|
+
classes: List of class documentation
|
|
78
|
+
functions: List of function documentation
|
|
79
|
+
path: Source file path
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
name: str
|
|
83
|
+
docstring: str = ""
|
|
84
|
+
classes: list[ClassDoc] = field(default_factory=list)
|
|
85
|
+
functions: list[FunctionDoc] = field(default_factory=list)
|
|
86
|
+
path: str = ""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class ExtensionDocs:
|
|
91
|
+
"""Complete documentation for an extension.
|
|
92
|
+
|
|
93
|
+
Attributes:
|
|
94
|
+
name: Extension name
|
|
95
|
+
version: Extension version
|
|
96
|
+
description: Extension description
|
|
97
|
+
author: Extension author
|
|
98
|
+
modules: List of module documentation
|
|
99
|
+
metadata: Extension metadata
|
|
100
|
+
markdown: Generated markdown documentation
|
|
101
|
+
html: Generated HTML documentation
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
name: str
|
|
105
|
+
version: str = "0.1.0"
|
|
106
|
+
description: str = ""
|
|
107
|
+
author: str = ""
|
|
108
|
+
modules: list[ModuleDoc] = field(default_factory=list)
|
|
109
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
110
|
+
markdown: str = ""
|
|
111
|
+
html: str = ""
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def generate_extension_docs(
|
|
115
|
+
extension_path: Path,
|
|
116
|
+
*,
|
|
117
|
+
include_private: bool = False,
|
|
118
|
+
include_examples: bool = True,
|
|
119
|
+
output_format: str = "markdown",
|
|
120
|
+
) -> ExtensionDocs:
|
|
121
|
+
"""Generate documentation for an extension.
|
|
122
|
+
|
|
123
|
+
Extracts documentation from Python modules, docstrings, and metadata files
|
|
124
|
+
to create comprehensive API documentation.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
extension_path: Path to extension directory
|
|
128
|
+
include_private: Include private members (starting with _)
|
|
129
|
+
include_examples: Extract examples from docstrings
|
|
130
|
+
output_format: Output format ("markdown" or "html")
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
ExtensionDocs object with generated documentation
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
>>> from pathlib import Path
|
|
137
|
+
>>> docs = generate_extension_docs(Path("plugins/my_decoder/"))
|
|
138
|
+
>>> print(docs.markdown)
|
|
139
|
+
>>> with open("docs/my_decoder.md", "w") as f:
|
|
140
|
+
... f.write(docs.markdown)
|
|
141
|
+
|
|
142
|
+
References:
|
|
143
|
+
EXT-006: Extension Documentation
|
|
144
|
+
"""
|
|
145
|
+
docs = ExtensionDocs(name=extension_path.name)
|
|
146
|
+
|
|
147
|
+
# Extract metadata
|
|
148
|
+
_extract_metadata(extension_path, docs)
|
|
149
|
+
|
|
150
|
+
# Document Python modules
|
|
151
|
+
_document_modules(extension_path, docs, include_private, include_examples)
|
|
152
|
+
|
|
153
|
+
# Generate output
|
|
154
|
+
if output_format == "markdown":
|
|
155
|
+
docs.markdown = _generate_markdown(docs)
|
|
156
|
+
elif output_format == "html":
|
|
157
|
+
docs.html = _generate_html(docs)
|
|
158
|
+
|
|
159
|
+
return docs
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def generate_decoder_docs(
|
|
163
|
+
decoder_class: type,
|
|
164
|
+
*,
|
|
165
|
+
include_examples: bool = True,
|
|
166
|
+
) -> str:
|
|
167
|
+
"""Generate documentation for a decoder class.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
decoder_class: Decoder class to document
|
|
171
|
+
include_examples: Include usage examples
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Markdown documentation string
|
|
175
|
+
|
|
176
|
+
Example:
|
|
177
|
+
>>> class MyDecoder:
|
|
178
|
+
... '''Custom UART decoder.
|
|
179
|
+
...
|
|
180
|
+
... Example:
|
|
181
|
+
... >>> decoder = MyDecoder()
|
|
182
|
+
... >>> frames = decoder.decode(signal)
|
|
183
|
+
... '''
|
|
184
|
+
... def decode(self, signal):
|
|
185
|
+
... '''Decode signal.'''
|
|
186
|
+
... return []
|
|
187
|
+
>>> docs = generate_decoder_docs(MyDecoder)
|
|
188
|
+
>>> print(docs)
|
|
189
|
+
|
|
190
|
+
References:
|
|
191
|
+
EXT-006: Extension Documentation
|
|
192
|
+
"""
|
|
193
|
+
class_doc = _document_class(
|
|
194
|
+
decoder_class, include_private=False, include_examples=include_examples
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Generate markdown
|
|
198
|
+
lines = []
|
|
199
|
+
lines.append(f"# {class_doc.name}")
|
|
200
|
+
lines.append("")
|
|
201
|
+
|
|
202
|
+
if class_doc.docstring:
|
|
203
|
+
lines.append(class_doc.docstring)
|
|
204
|
+
lines.append("")
|
|
205
|
+
|
|
206
|
+
if class_doc.bases:
|
|
207
|
+
lines.append(f"**Inherits from:** {', '.join(class_doc.bases)}")
|
|
208
|
+
lines.append("")
|
|
209
|
+
|
|
210
|
+
# Attributes
|
|
211
|
+
if class_doc.attributes:
|
|
212
|
+
lines.append("## Attributes")
|
|
213
|
+
lines.append("")
|
|
214
|
+
for name, desc in class_doc.attributes:
|
|
215
|
+
lines.append(f"- **{name}**: {desc}")
|
|
216
|
+
lines.append("")
|
|
217
|
+
|
|
218
|
+
# Methods
|
|
219
|
+
if class_doc.methods:
|
|
220
|
+
lines.append("## Methods")
|
|
221
|
+
lines.append("")
|
|
222
|
+
for method in class_doc.methods:
|
|
223
|
+
lines.append(f"### {method.name}")
|
|
224
|
+
lines.append("")
|
|
225
|
+
if method.signature:
|
|
226
|
+
lines.append("```python")
|
|
227
|
+
lines.append(f"{method.signature}")
|
|
228
|
+
lines.append("```")
|
|
229
|
+
lines.append("")
|
|
230
|
+
if method.docstring:
|
|
231
|
+
lines.append(method.docstring)
|
|
232
|
+
lines.append("")
|
|
233
|
+
if method.parameters:
|
|
234
|
+
lines.append("**Parameters:**")
|
|
235
|
+
lines.append("")
|
|
236
|
+
for param_name, param_desc in method.parameters:
|
|
237
|
+
lines.append(f"- **{param_name}**: {param_desc}")
|
|
238
|
+
lines.append("")
|
|
239
|
+
if method.returns:
|
|
240
|
+
lines.append(f"**Returns:** {method.returns}")
|
|
241
|
+
lines.append("")
|
|
242
|
+
if method.examples:
|
|
243
|
+
lines.append("**Example:**")
|
|
244
|
+
lines.append("")
|
|
245
|
+
for example in method.examples:
|
|
246
|
+
lines.append("```python")
|
|
247
|
+
lines.append(example)
|
|
248
|
+
lines.append("```")
|
|
249
|
+
lines.append("")
|
|
250
|
+
|
|
251
|
+
return "\n".join(lines)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def extract_plugin_metadata(
|
|
255
|
+
extension_path: Path,
|
|
256
|
+
) -> dict[str, Any]:
|
|
257
|
+
"""Extract metadata from extension directory.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
extension_path: Path to extension directory
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Dictionary with metadata fields
|
|
264
|
+
|
|
265
|
+
Example:
|
|
266
|
+
>>> metadata = extract_plugin_metadata(Path("plugins/my_plugin/"))
|
|
267
|
+
>>> print(metadata["name"])
|
|
268
|
+
>>> print(metadata["version"])
|
|
269
|
+
|
|
270
|
+
References:
|
|
271
|
+
EXT-006: Extension Documentation
|
|
272
|
+
"""
|
|
273
|
+
metadata: dict[str, Any] = {}
|
|
274
|
+
|
|
275
|
+
# Try pyproject.toml
|
|
276
|
+
pyproject = extension_path / "pyproject.toml"
|
|
277
|
+
if pyproject.exists():
|
|
278
|
+
try:
|
|
279
|
+
import tomllib
|
|
280
|
+
|
|
281
|
+
with open(pyproject, "rb") as f:
|
|
282
|
+
data = tomllib.load(f)
|
|
283
|
+
|
|
284
|
+
if "project" in data:
|
|
285
|
+
project = data["project"]
|
|
286
|
+
metadata.update(
|
|
287
|
+
{
|
|
288
|
+
"name": project.get("name", ""),
|
|
289
|
+
"version": project.get("version", ""),
|
|
290
|
+
"description": project.get("description", ""),
|
|
291
|
+
"authors": project.get("authors", []),
|
|
292
|
+
"dependencies": project.get("dependencies", []),
|
|
293
|
+
"entry_points": project.get("entry-points", {}),
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
except Exception as e:
|
|
298
|
+
logger.warning(f"Failed to parse pyproject.toml: {e}")
|
|
299
|
+
|
|
300
|
+
# Try plugin.yaml
|
|
301
|
+
plugin_yaml = extension_path / "plugin.yaml"
|
|
302
|
+
if plugin_yaml.exists():
|
|
303
|
+
try:
|
|
304
|
+
import yaml
|
|
305
|
+
|
|
306
|
+
with open(plugin_yaml, encoding="utf-8") as f:
|
|
307
|
+
data = yaml.safe_load(f)
|
|
308
|
+
|
|
309
|
+
if data:
|
|
310
|
+
metadata.update(data)
|
|
311
|
+
|
|
312
|
+
except Exception as e:
|
|
313
|
+
logger.warning(f"Failed to parse plugin.yaml: {e}")
|
|
314
|
+
|
|
315
|
+
return metadata
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _extract_metadata(extension_path: Path, docs: ExtensionDocs) -> None:
|
|
319
|
+
"""Extract metadata from extension files.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
extension_path: Path to extension directory
|
|
323
|
+
docs: ExtensionDocs to populate
|
|
324
|
+
"""
|
|
325
|
+
metadata = extract_plugin_metadata(extension_path)
|
|
326
|
+
|
|
327
|
+
docs.name = metadata.get("name", extension_path.name)
|
|
328
|
+
docs.version = metadata.get("version", "0.1.0")
|
|
329
|
+
docs.description = metadata.get("description", "")
|
|
330
|
+
docs.metadata = metadata
|
|
331
|
+
|
|
332
|
+
# Extract author
|
|
333
|
+
authors = metadata.get("authors", [])
|
|
334
|
+
if authors and isinstance(authors, list) and len(authors) > 0:
|
|
335
|
+
if isinstance(authors[0], dict):
|
|
336
|
+
docs.author = authors[0].get("name", "")
|
|
337
|
+
else:
|
|
338
|
+
docs.author = str(authors[0])
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _document_modules(
|
|
342
|
+
extension_path: Path,
|
|
343
|
+
docs: ExtensionDocs,
|
|
344
|
+
include_private: bool,
|
|
345
|
+
include_examples: bool,
|
|
346
|
+
) -> None:
|
|
347
|
+
"""Document all Python modules in extension.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
extension_path: Path to extension directory
|
|
351
|
+
docs: ExtensionDocs to populate
|
|
352
|
+
include_private: Include private members
|
|
353
|
+
include_examples: Extract examples from docstrings
|
|
354
|
+
"""
|
|
355
|
+
# Find Python files
|
|
356
|
+
py_files = list(extension_path.glob("*.py"))
|
|
357
|
+
py_files = [f for f in py_files if f.name != "__init__.py"]
|
|
358
|
+
|
|
359
|
+
for py_file in py_files:
|
|
360
|
+
try:
|
|
361
|
+
module_doc = _document_module(py_file, include_private, include_examples)
|
|
362
|
+
docs.modules.append(module_doc)
|
|
363
|
+
except Exception as e:
|
|
364
|
+
logger.warning(f"Failed to document {py_file}: {e}")
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _document_module(
|
|
368
|
+
module_path: Path,
|
|
369
|
+
include_private: bool,
|
|
370
|
+
include_examples: bool,
|
|
371
|
+
) -> ModuleDoc:
|
|
372
|
+
"""Document a Python module.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
module_path: Path to Python file
|
|
376
|
+
include_private: Include private members
|
|
377
|
+
include_examples: Extract examples
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
ModuleDoc with extracted documentation
|
|
381
|
+
"""
|
|
382
|
+
with open(module_path, encoding="utf-8") as f:
|
|
383
|
+
source = f.read()
|
|
384
|
+
|
|
385
|
+
tree = ast.parse(source)
|
|
386
|
+
|
|
387
|
+
module_doc = ModuleDoc(
|
|
388
|
+
name=module_path.stem,
|
|
389
|
+
path=str(module_path),
|
|
390
|
+
docstring=ast.get_docstring(tree) or "",
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# Extract classes and functions
|
|
394
|
+
for node in ast.iter_child_nodes(tree):
|
|
395
|
+
if isinstance(node, ast.ClassDef):
|
|
396
|
+
if include_private or not node.name.startswith("_"):
|
|
397
|
+
class_doc = _document_class_ast(node, include_private, include_examples)
|
|
398
|
+
module_doc.classes.append(class_doc)
|
|
399
|
+
|
|
400
|
+
elif isinstance(node, ast.FunctionDef):
|
|
401
|
+
if include_private or not node.name.startswith("_"):
|
|
402
|
+
func_doc = _document_function_ast(node, include_examples)
|
|
403
|
+
module_doc.functions.append(func_doc)
|
|
404
|
+
|
|
405
|
+
return module_doc
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _document_class(
|
|
409
|
+
cls: type,
|
|
410
|
+
include_private: bool,
|
|
411
|
+
include_examples: bool,
|
|
412
|
+
) -> ClassDoc:
|
|
413
|
+
"""Document a class from runtime object.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
cls: Class to document
|
|
417
|
+
include_private: Include private members
|
|
418
|
+
include_examples: Extract examples
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
ClassDoc with extracted documentation
|
|
422
|
+
"""
|
|
423
|
+
class_doc = ClassDoc(
|
|
424
|
+
name=cls.__name__,
|
|
425
|
+
docstring=inspect.getdoc(cls) or "",
|
|
426
|
+
bases=[base.__name__ for base in cls.__bases__ if base is not object],
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
# Document methods
|
|
430
|
+
for name, obj in inspect.getmembers(cls):
|
|
431
|
+
if include_private or not name.startswith("_"):
|
|
432
|
+
if inspect.isfunction(obj) or inspect.ismethod(obj):
|
|
433
|
+
try:
|
|
434
|
+
sig = str(inspect.signature(obj))
|
|
435
|
+
func_doc = FunctionDoc(
|
|
436
|
+
name=name,
|
|
437
|
+
signature=f"def {name}{sig}",
|
|
438
|
+
docstring=inspect.getdoc(obj) or "",
|
|
439
|
+
)
|
|
440
|
+
class_doc.methods.append(func_doc)
|
|
441
|
+
except Exception:
|
|
442
|
+
pass
|
|
443
|
+
|
|
444
|
+
return class_doc
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _document_class_ast(
|
|
448
|
+
node: ast.ClassDef,
|
|
449
|
+
include_private: bool,
|
|
450
|
+
include_examples: bool,
|
|
451
|
+
) -> ClassDoc:
|
|
452
|
+
"""Document a class from AST node.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
node: AST ClassDef node
|
|
456
|
+
include_private: Include private members
|
|
457
|
+
include_examples: Extract examples
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
ClassDoc with extracted documentation
|
|
461
|
+
"""
|
|
462
|
+
class_doc = ClassDoc(
|
|
463
|
+
name=node.name,
|
|
464
|
+
docstring=ast.get_docstring(node) or "",
|
|
465
|
+
bases=[_get_name_from_ast(base) for base in node.bases],
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# Document methods
|
|
469
|
+
for item in node.body:
|
|
470
|
+
if isinstance(item, ast.FunctionDef):
|
|
471
|
+
if include_private or not item.name.startswith("_"):
|
|
472
|
+
func_doc = _document_function_ast(item, include_examples)
|
|
473
|
+
class_doc.methods.append(func_doc)
|
|
474
|
+
|
|
475
|
+
return class_doc
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _document_function_ast(
|
|
479
|
+
node: ast.FunctionDef,
|
|
480
|
+
include_examples: bool,
|
|
481
|
+
) -> FunctionDoc:
|
|
482
|
+
"""Document a function from AST node.
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
node: AST FunctionDef node
|
|
486
|
+
include_examples: Extract examples
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
FunctionDoc with extracted documentation
|
|
490
|
+
"""
|
|
491
|
+
# Build signature
|
|
492
|
+
args = []
|
|
493
|
+
for arg in node.args.args:
|
|
494
|
+
args.append(arg.arg)
|
|
495
|
+
|
|
496
|
+
signature = f"def {node.name}({', '.join(args)})"
|
|
497
|
+
|
|
498
|
+
func_doc = FunctionDoc(
|
|
499
|
+
name=node.name,
|
|
500
|
+
signature=signature,
|
|
501
|
+
docstring=ast.get_docstring(node) or "",
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
# Parse docstring for parameters, returns, examples
|
|
505
|
+
if func_doc.docstring:
|
|
506
|
+
_parse_docstring(func_doc, include_examples)
|
|
507
|
+
|
|
508
|
+
return func_doc
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def _parse_docstring(func_doc: FunctionDoc, include_examples: bool) -> None:
|
|
512
|
+
"""Parse Google-style docstring for structured information.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
func_doc: FunctionDoc to populate
|
|
516
|
+
include_examples: Extract examples
|
|
517
|
+
"""
|
|
518
|
+
lines = func_doc.docstring.split("\n")
|
|
519
|
+
current_section = None
|
|
520
|
+
section_content: list[str] = []
|
|
521
|
+
|
|
522
|
+
for line in lines:
|
|
523
|
+
line_stripped = line.strip()
|
|
524
|
+
|
|
525
|
+
# Detect sections
|
|
526
|
+
if line_stripped.endswith(":") and line_stripped[:-1] in [
|
|
527
|
+
"Args",
|
|
528
|
+
"Arguments",
|
|
529
|
+
"Parameters",
|
|
530
|
+
"Returns",
|
|
531
|
+
"Return",
|
|
532
|
+
"Example",
|
|
533
|
+
"Examples",
|
|
534
|
+
]:
|
|
535
|
+
# Process previous section
|
|
536
|
+
if current_section:
|
|
537
|
+
_process_section(func_doc, current_section, section_content, include_examples)
|
|
538
|
+
|
|
539
|
+
current_section = line_stripped[:-1].lower()
|
|
540
|
+
section_content = []
|
|
541
|
+
else:
|
|
542
|
+
section_content.append(line)
|
|
543
|
+
|
|
544
|
+
# Process final section
|
|
545
|
+
if current_section:
|
|
546
|
+
_process_section(func_doc, current_section, section_content, include_examples)
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _process_section(
|
|
550
|
+
func_doc: FunctionDoc,
|
|
551
|
+
section: str,
|
|
552
|
+
content: list[str],
|
|
553
|
+
include_examples: bool,
|
|
554
|
+
) -> None:
|
|
555
|
+
"""Process a docstring section.
|
|
556
|
+
|
|
557
|
+
Args:
|
|
558
|
+
func_doc: FunctionDoc to populate
|
|
559
|
+
section: Section name
|
|
560
|
+
content: Section content lines
|
|
561
|
+
include_examples: Extract examples
|
|
562
|
+
"""
|
|
563
|
+
if section in ["args", "arguments", "parameters"]:
|
|
564
|
+
# Parse parameters
|
|
565
|
+
for line in content:
|
|
566
|
+
line = line.strip()
|
|
567
|
+
if ":" in line:
|
|
568
|
+
parts = line.split(":", 1)
|
|
569
|
+
param_name = parts[0].strip()
|
|
570
|
+
param_desc = parts[1].strip()
|
|
571
|
+
func_doc.parameters.append((param_name, param_desc))
|
|
572
|
+
|
|
573
|
+
elif section in ["returns", "return"]:
|
|
574
|
+
func_doc.returns = "\n".join(content).strip()
|
|
575
|
+
|
|
576
|
+
elif section in ["example", "examples"] and include_examples:
|
|
577
|
+
# Extract code blocks
|
|
578
|
+
in_code = False
|
|
579
|
+
code_lines = []
|
|
580
|
+
|
|
581
|
+
for line in content:
|
|
582
|
+
if ">>>" in line or "..." in line:
|
|
583
|
+
in_code = True
|
|
584
|
+
code_lines.append(line.strip())
|
|
585
|
+
elif in_code:
|
|
586
|
+
if line.strip() and not line.strip().startswith("#"):
|
|
587
|
+
if not (">>>" in line or "..." in line):
|
|
588
|
+
in_code = False
|
|
589
|
+
if code_lines:
|
|
590
|
+
func_doc.examples.append("\n".join(code_lines))
|
|
591
|
+
code_lines = []
|
|
592
|
+
else:
|
|
593
|
+
code_lines.append(line.strip())
|
|
594
|
+
|
|
595
|
+
if code_lines:
|
|
596
|
+
func_doc.examples.append("\n".join(code_lines))
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def _get_name_from_ast(node: ast.expr) -> str:
|
|
600
|
+
"""Extract name from AST expression.
|
|
601
|
+
|
|
602
|
+
Args:
|
|
603
|
+
node: AST expression node
|
|
604
|
+
|
|
605
|
+
Returns:
|
|
606
|
+
Name string
|
|
607
|
+
"""
|
|
608
|
+
if isinstance(node, ast.Name):
|
|
609
|
+
return node.id
|
|
610
|
+
elif isinstance(node, ast.Attribute):
|
|
611
|
+
return node.attr
|
|
612
|
+
else:
|
|
613
|
+
return str(node)
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _generate_markdown(docs: ExtensionDocs) -> str:
|
|
617
|
+
"""Generate markdown documentation.
|
|
618
|
+
|
|
619
|
+
Args:
|
|
620
|
+
docs: ExtensionDocs to render
|
|
621
|
+
|
|
622
|
+
Returns:
|
|
623
|
+
Markdown string
|
|
624
|
+
"""
|
|
625
|
+
lines = []
|
|
626
|
+
|
|
627
|
+
# Title
|
|
628
|
+
lines.append(f"# {docs.name}")
|
|
629
|
+
lines.append("")
|
|
630
|
+
|
|
631
|
+
# Metadata
|
|
632
|
+
if docs.version:
|
|
633
|
+
lines.append(f"**Version:** {docs.version}")
|
|
634
|
+
lines.append("")
|
|
635
|
+
if docs.author:
|
|
636
|
+
lines.append(f"**Author:** {docs.author}")
|
|
637
|
+
lines.append("")
|
|
638
|
+
if docs.description:
|
|
639
|
+
lines.append(docs.description)
|
|
640
|
+
lines.append("")
|
|
641
|
+
|
|
642
|
+
# Dependencies
|
|
643
|
+
if "dependencies" in docs.metadata:
|
|
644
|
+
lines.append("## Dependencies")
|
|
645
|
+
lines.append("")
|
|
646
|
+
for dep in docs.metadata["dependencies"]:
|
|
647
|
+
lines.append(f"- {dep}")
|
|
648
|
+
lines.append("")
|
|
649
|
+
|
|
650
|
+
# Modules
|
|
651
|
+
for module in docs.modules:
|
|
652
|
+
lines.append(f"## Module: {module.name}")
|
|
653
|
+
lines.append("")
|
|
654
|
+
if module.docstring:
|
|
655
|
+
lines.append(module.docstring)
|
|
656
|
+
lines.append("")
|
|
657
|
+
|
|
658
|
+
# Classes
|
|
659
|
+
for cls in module.classes:
|
|
660
|
+
lines.append(f"### Class: {cls.name}")
|
|
661
|
+
lines.append("")
|
|
662
|
+
if cls.docstring:
|
|
663
|
+
lines.append(cls.docstring)
|
|
664
|
+
lines.append("")
|
|
665
|
+
|
|
666
|
+
# Methods
|
|
667
|
+
if cls.methods:
|
|
668
|
+
lines.append("#### Methods")
|
|
669
|
+
lines.append("")
|
|
670
|
+
for method in cls.methods:
|
|
671
|
+
lines.append(f"##### {method.name}")
|
|
672
|
+
lines.append("")
|
|
673
|
+
if method.signature:
|
|
674
|
+
lines.append("```python")
|
|
675
|
+
lines.append(method.signature)
|
|
676
|
+
lines.append("```")
|
|
677
|
+
lines.append("")
|
|
678
|
+
if method.docstring:
|
|
679
|
+
lines.append(method.docstring)
|
|
680
|
+
lines.append("")
|
|
681
|
+
|
|
682
|
+
# Functions
|
|
683
|
+
for func in module.functions:
|
|
684
|
+
lines.append(f"### Function: {func.name}")
|
|
685
|
+
lines.append("")
|
|
686
|
+
if func.signature:
|
|
687
|
+
lines.append("```python")
|
|
688
|
+
lines.append(func.signature)
|
|
689
|
+
lines.append("```")
|
|
690
|
+
lines.append("")
|
|
691
|
+
if func.docstring:
|
|
692
|
+
lines.append(func.docstring)
|
|
693
|
+
lines.append("")
|
|
694
|
+
|
|
695
|
+
return "\n".join(lines)
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def _generate_html(docs: ExtensionDocs) -> str:
|
|
699
|
+
"""Generate HTML documentation.
|
|
700
|
+
|
|
701
|
+
Args:
|
|
702
|
+
docs: ExtensionDocs to render
|
|
703
|
+
|
|
704
|
+
Returns:
|
|
705
|
+
HTML string
|
|
706
|
+
"""
|
|
707
|
+
# Convert markdown to HTML (simple conversion)
|
|
708
|
+
markdown = _generate_markdown(docs)
|
|
709
|
+
|
|
710
|
+
# Simple markdown-to-HTML conversion
|
|
711
|
+
html_lines = ["<!DOCTYPE html>", "<html>", "<head>"]
|
|
712
|
+
html_lines.append(f"<title>{docs.name} Documentation</title>")
|
|
713
|
+
html_lines.append("<style>")
|
|
714
|
+
html_lines.append("body { font-family: Arial, sans-serif; margin: 40px; }")
|
|
715
|
+
html_lines.append("code { background: #f4f4f4; padding: 2px 4px; }")
|
|
716
|
+
html_lines.append("pre { background: #f4f4f4; padding: 10px; }")
|
|
717
|
+
html_lines.append("</style>")
|
|
718
|
+
html_lines.append("</head>")
|
|
719
|
+
html_lines.append("<body>")
|
|
720
|
+
|
|
721
|
+
# Very simple markdown-to-HTML
|
|
722
|
+
for line in markdown.split("\n"):
|
|
723
|
+
if line.startswith("# "):
|
|
724
|
+
html_lines.append(f"<h1>{line[2:]}</h1>")
|
|
725
|
+
elif line.startswith("## "):
|
|
726
|
+
html_lines.append(f"<h2>{line[3:]}</h2>")
|
|
727
|
+
elif line.startswith("### "):
|
|
728
|
+
html_lines.append(f"<h3>{line[4:]}</h3>")
|
|
729
|
+
elif line.startswith("```"):
|
|
730
|
+
# Toggle code block
|
|
731
|
+
if "<pre><code>" not in html_lines[-1] if html_lines else "":
|
|
732
|
+
html_lines.append("<pre><code>")
|
|
733
|
+
else:
|
|
734
|
+
html_lines.append("</code></pre>")
|
|
735
|
+
elif line.strip():
|
|
736
|
+
html_lines.append(f"<p>{line}</p>")
|
|
737
|
+
|
|
738
|
+
html_lines.append("</body>")
|
|
739
|
+
html_lines.append("</html>")
|
|
740
|
+
|
|
741
|
+
return "\n".join(html_lines)
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
__all__ = [
|
|
745
|
+
"ClassDoc",
|
|
746
|
+
"ExtensionDocs",
|
|
747
|
+
"FunctionDoc",
|
|
748
|
+
"ModuleDoc",
|
|
749
|
+
"extract_plugin_metadata",
|
|
750
|
+
"generate_decoder_docs",
|
|
751
|
+
"generate_extension_docs",
|
|
752
|
+
]
|