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.
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.1.dist-info/METADATA +300 -0
  460. oscura-0.1.1.dist-info/RECORD +463 -0
  461. oscura-0.1.1.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.1.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.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,590 @@
1
+ """Index file generation for comprehensive analysis reports.
2
+
3
+ This module provides HTML and Markdown index generation from analysis results
4
+ using a simple template engine (no external dependencies like Jinja2).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ from pathlib import Path
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ if TYPE_CHECKING:
14
+ from oscura.reporting.config import AnalysisResult
15
+ from oscura.reporting.output import OutputManager
16
+
17
+
18
+ class TemplateEngine:
19
+ """Simple template engine for variable substitution and control flow.
20
+
21
+ Supports:
22
+ - {{variable}} - Variable substitution
23
+ - {{#if condition}}...{{/if}} - Conditional blocks
24
+ - {{#each items}}...{{/each}} - Iteration blocks
25
+ - {{this}} - Current item in iteration
26
+
27
+ Requirements:
28
+ """
29
+
30
+ def __init__(self) -> None:
31
+ """Initialize template engine."""
32
+ self._var_pattern = re.compile(r"\{\{([^#/}][^}]*)\}\}")
33
+ self._if_pattern = re.compile(r"\{\{#if\s+([^}]+)\}\}(.*?)\{\{/if\}\}", re.DOTALL)
34
+ self._each_pattern = re.compile(r"\{\{#each\s+([^}]+)\}\}(.*?)\{\{/each\}\}", re.DOTALL)
35
+
36
+ def render(self, template: str, context: dict[str, Any]) -> str:
37
+ """Render template with context.
38
+
39
+ Args:
40
+ template: Template string with placeholders.
41
+ context: Context dictionary for variable substitution.
42
+
43
+ Returns:
44
+ Rendered template string.
45
+
46
+ Examples:
47
+ >>> engine = TemplateEngine()
48
+ >>> engine.render("Hello {{name}}", {"name": "World"})
49
+ 'Hello World'
50
+ >>> engine.render("{{#if show}}visible{{/if}}", {"show": True})
51
+ 'visible'
52
+ >>> engine.render("{{#each items}}{{this}} {{/each}}", {"items": [1, 2]})
53
+ '1 2 '
54
+ """
55
+ # Process each blocks first (innermost to outermost)
56
+ result = self._process_each_blocks(template, context)
57
+
58
+ # Process if blocks
59
+ result = self._process_if_blocks(result, context)
60
+
61
+ # Process variables
62
+ result = self._process_variables(result, context)
63
+
64
+ return result
65
+
66
+ def _process_variables(self, template: str, context: dict[str, Any]) -> str:
67
+ """Replace {{variable}} with values from context.
68
+
69
+ Args:
70
+ template: Template string.
71
+ context: Context dictionary.
72
+
73
+ Returns:
74
+ Template with variables replaced.
75
+ """
76
+
77
+ def replace_var(match: re.Match[str]) -> str:
78
+ var_name = match.group(1).strip()
79
+
80
+ # Handle "this" for current iteration item
81
+ if var_name == "this":
82
+ return str(context.get("_current_item", ""))
83
+
84
+ # Handle nested access like "domain.value"
85
+ value: Any = context
86
+ for key in var_name.split("."):
87
+ if isinstance(value, dict):
88
+ value = value.get(key, "")
89
+ elif hasattr(value, key):
90
+ value = getattr(value, key)
91
+ else:
92
+ value = ""
93
+ break
94
+
95
+ # Handle enum values
96
+ if hasattr(value, "value"):
97
+ value = value.value
98
+
99
+ return str(value) if value is not None else ""
100
+
101
+ return self._var_pattern.sub(replace_var, template)
102
+
103
+ def _process_if_blocks(self, template: str, context: dict[str, Any]) -> str:
104
+ """Process {{#if condition}}...{{/if}} blocks.
105
+
106
+ Args:
107
+ template: Template string.
108
+ context: Context dictionary.
109
+
110
+ Returns:
111
+ Template with conditionals processed.
112
+ """
113
+
114
+ def replace_if(match: re.Match[str]) -> str:
115
+ condition = match.group(1).strip()
116
+ content = match.group(2)
117
+
118
+ # Evaluate condition
119
+ value = context.get(condition, False)
120
+
121
+ # Truthy check
122
+ if value and value != 0 and value != "" and value != []:
123
+ return content
124
+ return ""
125
+
126
+ return self._if_pattern.sub(replace_if, template)
127
+
128
+ def _process_each_blocks(self, template: str, context: dict[str, Any]) -> str:
129
+ """Process {{#each items}}...{{/each}} blocks.
130
+
131
+ Args:
132
+ template: Template string.
133
+ context: Context dictionary.
134
+
135
+ Returns:
136
+ Template with iterations processed.
137
+ """
138
+ # Manually find and process each blocks to handle nesting
139
+ result = []
140
+ pos = 0
141
+
142
+ while pos < len(template):
143
+ # Look for next {{#each}}
144
+ start_match = re.search(r"\{\{#each\s+([^}]+)\}\}", template[pos:])
145
+ if not start_match:
146
+ # No more each blocks
147
+ result.append(template[pos:])
148
+ break
149
+
150
+ # Add everything before this block
151
+ result.append(template[pos : pos + start_match.start()])
152
+
153
+ # Find the matching {{/each}} accounting for nesting
154
+ items_name = start_match.group(1).strip()
155
+ block_start = pos + start_match.end()
156
+ block_end = self._find_matching_end(template, block_start, "each")
157
+
158
+ if block_end == -1:
159
+ # No matching end tag, skip this
160
+ result.append(start_match.group(0))
161
+ pos = block_start
162
+ continue
163
+
164
+ # Extract the item template
165
+ item_template = template[block_start:block_end]
166
+
167
+ # Get the items
168
+ items = context.get(items_name, [])
169
+ if not items:
170
+ # Empty result
171
+ pass
172
+ else:
173
+ # Render each item
174
+ for item in items:
175
+ # Create context for this iteration
176
+ if isinstance(item, dict):
177
+ item_context = {**context, **item, "_current_item": item}
178
+ else:
179
+ item_context = {**context, "this": item, "_current_item": item}
180
+
181
+ # Recursively process nested blocks
182
+ rendered = self._process_each_blocks(item_template, item_context)
183
+ rendered = self._process_if_blocks(rendered, item_context)
184
+ rendered = self._process_variables(rendered, item_context)
185
+ result.append(rendered)
186
+
187
+ # Move past the {{/each}}
188
+ pos = block_end + len("{{/each}}")
189
+
190
+ return "".join(result)
191
+
192
+ def _find_matching_end(self, template: str, start_pos: int, block_type: str) -> int:
193
+ """Find matching end tag for a block, accounting for nesting.
194
+
195
+ Args:
196
+ template: Template string.
197
+ start_pos: Position after the opening tag.
198
+ block_type: Block type (e.g., "each", "if").
199
+
200
+ Returns:
201
+ Position of the start of the matching {{/block_type}} tag, or -1 if not found.
202
+ """
203
+ open_tag = f"{{{{#{block_type}"
204
+ close_tag = f"{{{{/{block_type}}}}}"
205
+ depth = 1
206
+ pos = start_pos
207
+
208
+ while pos < len(template) and depth > 0:
209
+ # Look for next open or close tag
210
+ next_open = template.find(open_tag, pos)
211
+ next_close = template.find(close_tag, pos)
212
+
213
+ if next_close == -1:
214
+ # No closing tag found
215
+ return -1
216
+
217
+ if next_open != -1 and next_open < next_close:
218
+ # Found nested open tag
219
+ depth += 1
220
+ pos = next_open + len(open_tag)
221
+ else:
222
+ # Found close tag
223
+ depth -= 1
224
+ if depth == 0:
225
+ return next_close
226
+ pos = next_close + len(close_tag)
227
+
228
+ return -1
229
+
230
+
231
+ class IndexGenerator:
232
+ """Generate HTML and Markdown index files from analysis results.
233
+
234
+ Creates navigable index pages that link to all analysis outputs including
235
+ plots, data files, and domain-specific results.
236
+
237
+ Attributes:
238
+ output_manager: Output manager for file operations.
239
+
240
+ Requirements:
241
+ """
242
+
243
+ def __init__(self, output_manager: OutputManager) -> None:
244
+ """Initialize index generator.
245
+
246
+ Args:
247
+ output_manager: Output manager for file operations.
248
+
249
+ Examples:
250
+ >>> from pathlib import Path
251
+ >>> om = OutputManager(Path("/tmp/output"), "test")
252
+ >>> generator = IndexGenerator(om)
253
+ """
254
+ self._output_manager = output_manager
255
+ self._engine = TemplateEngine()
256
+
257
+ # Template directory
258
+ self._template_dir = Path(__file__).parent / "templates"
259
+
260
+ def generate(
261
+ self,
262
+ result: AnalysisResult,
263
+ include_formats: list[str] | None = None,
264
+ ) -> dict[str, Path]:
265
+ """Generate index files in requested formats.
266
+
267
+ Args:
268
+ result: Analysis result containing all output metadata.
269
+ include_formats: Formats to generate (e.g., ["html", "md"]).
270
+ Defaults to ["html", "md"] if None.
271
+
272
+ Returns:
273
+ Dictionary mapping format name to generated file path.
274
+
275
+ Requirements:
276
+
277
+ Examples:
278
+ >>> # result = AnalysisResult(...)
279
+ >>> # generator = IndexGenerator(output_manager)
280
+ >>> # paths = generator.generate(result, ["html", "md"])
281
+ >>> # paths["html"] # Path to index.html
282
+ """
283
+ if include_formats is None:
284
+ include_formats = ["html", "md"]
285
+
286
+ # Build context from result
287
+ context = self._build_context(result)
288
+
289
+ # Generate each format
290
+ outputs: dict[str, Path] = {}
291
+
292
+ if "html" in include_formats:
293
+ html_content = self._render_html(context)
294
+ html_path = self._output_manager.save_text("index.html", html_content)
295
+ outputs["html"] = html_path
296
+
297
+ if "md" in include_formats:
298
+ md_content = self._render_markdown(context)
299
+ md_path = self._output_manager.save_text("index.md", md_content)
300
+ outputs["md"] = md_path
301
+
302
+ return outputs
303
+
304
+ def _build_context(self, result: AnalysisResult) -> dict[str, Any]:
305
+ """Build template context from AnalysisResult.
306
+
307
+ Args:
308
+ result: Analysis result.
309
+
310
+ Returns:
311
+ Context dictionary for template rendering.
312
+
313
+ Requirements:
314
+ """
315
+ # Extract timestamp properly from output_dir name
316
+ # Format is: YYYYMMDD_HHMMSS_name_analysis
317
+ dir_name = result.output_dir.name
318
+ timestamp = "N/A"
319
+ if "_" in dir_name:
320
+ parts = dir_name.split("_")
321
+ if len(parts) >= 2:
322
+ date_part = parts[0] # YYYYMMDD
323
+ time_part = parts[1] # HHMMSS
324
+ if len(date_part) == 8 and len(time_part) == 6:
325
+ try:
326
+ timestamp = (
327
+ f"{date_part[:4]}-{date_part[4:6]}-{date_part[6:8]} "
328
+ f"{time_part[:2]}:{time_part[2:4]}:{time_part[4:6]}"
329
+ )
330
+ except (IndexError, ValueError):
331
+ timestamp = f"{date_part}_{time_part}"
332
+
333
+ # Basic metadata
334
+ context: dict[str, Any] = {
335
+ "title": "Analysis Report",
336
+ "input_name": result.input_file or "In-Memory Data",
337
+ "input_size": self._format_size(result.input_file),
338
+ "input_type": result.input_type.value,
339
+ "timestamp": timestamp,
340
+ "duration": self._format_duration(result.duration_seconds),
341
+ "total_analyses": result.total_analyses,
342
+ "successful": result.successful_analyses,
343
+ "failed": result.failed_analyses,
344
+ "domains_count": len(result.domain_summaries),
345
+ "has_errors": len(result.errors) > 0,
346
+ }
347
+
348
+ # Build domain information
349
+ # domain_summaries contains {AnalysisDomain: {func_name: result, ...}}
350
+ domains: list[dict[str, Any]] = []
351
+ for domain, domain_results in result.domain_summaries.items():
352
+ # Count successful analyses in this domain
353
+ # domain_results is a dict of {function_name: result_value}
354
+ analyses_count = len(domain_results) if isinstance(domain_results, dict) else 0
355
+
356
+ # Find plots for this domain
357
+ domain_plots = []
358
+ if result.plot_paths:
359
+ domain_id = domain.value
360
+ for plot_path in result.plot_paths:
361
+ # Check if plot belongs to this domain
362
+ plot_str = str(plot_path)
363
+ if f"/{domain_id}/" in plot_str or plot_str.startswith(domain_id):
364
+ domain_plots.append(
365
+ {
366
+ "title": plot_path.stem.replace("_", " ").title(),
367
+ "path": str(plot_path.name)
368
+ if plot_path.parent == result.output_dir
369
+ else str(plot_path.relative_to(result.output_dir)),
370
+ "filename": plot_path.name,
371
+ }
372
+ )
373
+
374
+ # Find data files for this domain
375
+ domain_data_files = []
376
+ domain_dir = result.domain_dirs.get(domain)
377
+ if domain_dir and domain_dir.exists():
378
+ for data_file in domain_dir.glob("*.json"):
379
+ domain_data_files.append(
380
+ {
381
+ "filename": data_file.name,
382
+ "path": str(data_file.relative_to(result.output_dir)),
383
+ "format": "JSON",
384
+ }
385
+ )
386
+
387
+ # Build key findings from results
388
+ key_findings = self._extract_key_findings(domain_results)
389
+
390
+ domain_data: dict[str, Any] = {
391
+ "domain_id": domain.value,
392
+ "domain_name": domain.value.replace("_", " ").title(),
393
+ "analyses_count": analyses_count,
394
+ "plots_count": len(domain_plots),
395
+ "data_files_count": len(domain_data_files),
396
+ "key_findings": key_findings,
397
+ "plots": domain_plots,
398
+ "data_files": domain_data_files,
399
+ }
400
+ domains.append(domain_data)
401
+
402
+ context["domains"] = domains
403
+
404
+ # Build error information
405
+ if result.errors:
406
+ errors: list[dict[str, Any]] = []
407
+ for error in result.errors:
408
+ errors.append(
409
+ {
410
+ "domain": error.domain.value,
411
+ "analysis_name": error.function,
412
+ "error_message": error.error_message,
413
+ }
414
+ )
415
+ context["errors"] = errors
416
+
417
+ return context
418
+
419
+ def _extract_key_findings(self, domain_results: dict[str, Any]) -> list[str]:
420
+ """Extract key findings from domain results for display.
421
+
422
+ Args:
423
+ domain_results: Dictionary of analysis function results.
424
+
425
+ Returns:
426
+ List of key finding strings.
427
+ """
428
+ findings = []
429
+ for func_name, result in domain_results.items():
430
+ # Extract function short name
431
+ short_name = func_name.split(".")[-1].replace("_", " ").title()
432
+
433
+ # Format result based on type
434
+ if result is None:
435
+ continue
436
+ elif isinstance(result, int | float):
437
+ if not (isinstance(result, float) and (result != result)): # Check for NaN
438
+ findings.append(
439
+ f"{short_name}: {result:.4g}"
440
+ if isinstance(result, float)
441
+ else f"{short_name}: {result}"
442
+ )
443
+ elif isinstance(result, dict) and len(result) <= 3:
444
+ # Show small dicts inline
445
+ items = [
446
+ f"{k}: {v:.4g}" if isinstance(v, float) else f"{k}: {v}"
447
+ for k, v in list(result.items())[:3]
448
+ if v is not None and not (isinstance(v, float) and v != v)
449
+ ]
450
+ if items:
451
+ findings.append(f"{short_name}: {', '.join(items)}")
452
+
453
+ # Limit to most relevant findings
454
+ return findings[:5]
455
+
456
+ def _format_plots(self, plots: list[dict[str, Any]]) -> list[dict[str, str]]:
457
+ """Format plot information for templates.
458
+
459
+ Args:
460
+ plots: List of plot dictionaries.
461
+
462
+ Returns:
463
+ Formatted plot data.
464
+ """
465
+ formatted = []
466
+ for plot in plots:
467
+ formatted.append(
468
+ {
469
+ "title": plot.get("title", "Untitled"),
470
+ "path": str(plot.get("path", "")),
471
+ "filename": Path(plot.get("path", "")).name,
472
+ }
473
+ )
474
+ return formatted
475
+
476
+ def _format_data_files(self, data_files: list[dict[str, Any]]) -> list[dict[str, str]]:
477
+ """Format data file information for templates.
478
+
479
+ Args:
480
+ data_files: List of data file dictionaries.
481
+
482
+ Returns:
483
+ Formatted data file data.
484
+ """
485
+ formatted = []
486
+ for data_file in data_files:
487
+ path = Path(data_file.get("path", ""))
488
+ formatted.append(
489
+ {
490
+ "filename": path.name,
491
+ "path": str(path),
492
+ "format": path.suffix.lstrip(".").upper() or "DATA",
493
+ }
494
+ )
495
+ return formatted
496
+
497
+ def _format_size(self, filepath: str | None) -> str:
498
+ """Format file size in human-readable format.
499
+
500
+ Args:
501
+ filepath: Path to file.
502
+
503
+ Returns:
504
+ Formatted size string (e.g., "1.5 MB").
505
+ """
506
+ if not filepath:
507
+ return "N/A"
508
+
509
+ try:
510
+ path = Path(filepath)
511
+ if not path.exists():
512
+ return "N/A"
513
+
514
+ size_bytes = path.stat().st_size
515
+ size_float = float(size_bytes)
516
+ for unit in ["B", "KB", "MB", "GB"]:
517
+ if size_float < 1024.0:
518
+ return f"{size_float:.1f} {unit}"
519
+ size_float /= 1024.0
520
+ return f"{size_float:.1f} TB"
521
+ except Exception:
522
+ return "N/A"
523
+
524
+ def _format_duration(self, seconds: float) -> str:
525
+ """Format duration in human-readable format.
526
+
527
+ Args:
528
+ seconds: Duration in seconds.
529
+
530
+ Returns:
531
+ Formatted duration string (e.g., "1m 30s").
532
+ """
533
+ if seconds < 60:
534
+ return f"{seconds:.1f}s"
535
+ elif seconds < 3600:
536
+ minutes = int(seconds // 60)
537
+ secs = int(seconds % 60)
538
+ return f"{minutes}m {secs}s"
539
+ else:
540
+ hours = int(seconds // 3600)
541
+ minutes = int((seconds % 3600) // 60)
542
+ return f"{hours}h {minutes}m"
543
+
544
+ def _render_html(self, context: dict[str, Any]) -> str:
545
+ """Render HTML index from template.
546
+
547
+ Args:
548
+ context: Template context.
549
+
550
+ Returns:
551
+ Rendered HTML string.
552
+
553
+ Raises:
554
+ FileNotFoundError: If HTML template file not found.
555
+
556
+ Requirements:
557
+ """
558
+ template_path = self._template_dir / "index.html"
559
+ if not template_path.exists():
560
+ raise FileNotFoundError(f"HTML template not found: {template_path}")
561
+
562
+ template = template_path.read_text()
563
+ return self._engine.render(template, context)
564
+
565
+ def _render_markdown(self, context: dict[str, Any]) -> str:
566
+ """Render Markdown index from template.
567
+
568
+ Args:
569
+ context: Template context.
570
+
571
+ Returns:
572
+ Rendered Markdown string.
573
+
574
+ Raises:
575
+ FileNotFoundError: If Markdown template file not found.
576
+
577
+ Requirements:
578
+ """
579
+ template_path = self._template_dir / "index.md"
580
+ if not template_path.exists():
581
+ raise FileNotFoundError(f"Markdown template not found: {template_path}")
582
+
583
+ template = template_path.read_text()
584
+ return self._engine.render(template, context)
585
+
586
+
587
+ __all__ = [
588
+ "IndexGenerator",
589
+ "TemplateEngine",
590
+ ]