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.
Files changed (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.0.dist-info/METADATA +300 -0
  460. oscura-0.1.0.dist-info/RECORD +463 -0
  461. oscura-0.1.0.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,752 @@
1
+ """Extension documentation auto-generation system.
2
+
3
+ This module provides automatic documentation generation for TraceKit 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
+ ]