mostlyright-data 0.9.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.
- mostlyright/data_harness/__init__.py +158 -0
- mostlyright/data_harness/acquisition/__init__.py +55 -0
- mostlyright/data_harness/acquisition/http.py +2773 -0
- mostlyright/data_harness/acquisition/parsing.py +809 -0
- mostlyright/data_harness/acquisition/ranges.py +495 -0
- mostlyright/data_harness/acquisition/result_download.py +360 -0
- mostlyright/data_harness/acquisition/retention_admission.py +248 -0
- mostlyright/data_harness/acquisition/sandbox.py +4888 -0
- mostlyright/data_harness/acquisition/url_policy.py +530 -0
- mostlyright/data_harness/agent_runtime.py +2743 -0
- mostlyright/data_harness/assets/logo-ink.svg +31 -0
- mostlyright/data_harness/backends/__init__.py +28 -0
- mostlyright/data_harness/backends/pandas_backend.py +350 -0
- mostlyright/data_harness/backends/polars_backend.py +366 -0
- mostlyright/data_harness/backends/protocol.py +124 -0
- mostlyright/data_harness/backends/reference.py +83 -0
- mostlyright/data_harness/backends/registry.py +55 -0
- mostlyright/data_harness/backends/restrictions.py +126 -0
- mostlyright/data_harness/canonical.py +333 -0
- mostlyright/data_harness/catalog_job.py +625 -0
- mostlyright/data_harness/cli.py +5398 -0
- mostlyright/data_harness/contracts.py +53 -0
- mostlyright/data_harness/coordinator.py +1307 -0
- mostlyright/data_harness/deploy.py +924 -0
- mostlyright/data_harness/deploy_target.py +312 -0
- mostlyright/data_harness/deployment_evidence.py +1067 -0
- mostlyright/data_harness/event_presentation.py +576 -0
- mostlyright/data_harness/events.py +2152 -0
- mostlyright/data_harness/fast_delimited.py +239 -0
- mostlyright/data_harness/fleet.py +237 -0
- mostlyright/data_harness/formats.py +236 -0
- mostlyright/data_harness/governors.py +1163 -0
- mostlyright/data_harness/hosted_bootstrap.py +972 -0
- mostlyright/data_harness/hosted_crawler.py +1115 -0
- mostlyright/data_harness/hosted_crawler_container_smoke.py +351 -0
- mostlyright/data_harness/hosted_crawler_fetch.py +423 -0
- mostlyright/data_harness/hosted_crawler_job.py +1277 -0
- mostlyright/data_harness/hosted_crawler_protocol.py +676 -0
- mostlyright/data_harness/hosted_dataset.py +1500 -0
- mostlyright/data_harness/hosted_deploy.py +3037 -0
- mostlyright/data_harness/hosted_handoff.py +62 -0
- mostlyright/data_harness/hosted_ingestion_contract.py +504 -0
- mostlyright/data_harness/hosted_ingestion_job.py +356 -0
- mostlyright/data_harness/hosted_ingestion_job_smoke.py +40 -0
- mostlyright/data_harness/hosted_session_container_smoke.py +194 -0
- mostlyright/data_harness/hosted_session_worker.py +3554 -0
- mostlyright/data_harness/hosted_session_worker_job_smoke.py +46 -0
- mostlyright/data_harness/hosted_worker.py +6784 -0
- mostlyright/data_harness/ingestion/__init__.py +56 -0
- mostlyright/data_harness/ingestion/contracts.py +461 -0
- mostlyright/data_harness/ingestion/faults.py +42 -0
- mostlyright/data_harness/ingestion/gcs_store.py +1162 -0
- mostlyright/data_harness/ingestion/spool.py +130 -0
- mostlyright/data_harness/ingestion/store.py +885 -0
- mostlyright/data_harness/key_seam.py +434 -0
- mostlyright/data_harness/linux_process_boundary.py +262 -0
- mostlyright/data_harness/local_contracts.py +2880 -0
- mostlyright/data_harness/local_search/__init__.py +5 -0
- mostlyright/data_harness/local_search/build_index.py +1087 -0
- mostlyright/data_harness/local_search/contracts.py +920 -0
- mostlyright/data_harness/local_search/query_trace.py +266 -0
- mostlyright/data_harness/local_search/retrieval.py +700 -0
- mostlyright/data_harness/local_search/sealed.py +474 -0
- mostlyright/data_harness/local_search/service.py +784 -0
- mostlyright/data_harness/nbrender/CONTRACT.md +212 -0
- mostlyright/data_harness/nbrender/__init__.py +12 -0
- mostlyright/data_harness/nbrender/chrome.py +359 -0
- mostlyright/data_harness/nbrender/code_body.py +266 -0
- mostlyright/data_harness/nbrender/document.py +407 -0
- mostlyright/data_harness/nbrender/frame.py +275 -0
- mostlyright/data_harness/nbrender/interactive.py +337 -0
- mostlyright/data_harness/nbrender/markdown_body.py +477 -0
- mostlyright/data_harness/nbrender/mr_components.py +134 -0
- mostlyright/data_harness/nbrender/outputs_data.py +595 -0
- mostlyright/data_harness/nbrender/outputs_rich.py +906 -0
- mostlyright/data_harness/nbrender/outputs_source.py +260 -0
- mostlyright/data_harness/nbrender/outputs_stage.py +176 -0
- mostlyright/data_harness/nbrender/outputs_text.py +400 -0
- mostlyright/data_harness/nbrender/parse.py +394 -0
- mostlyright/data_harness/nbrender/status.py +40 -0
- mostlyright/data_harness/nbrender/tokens.py +1295 -0
- mostlyright/data_harness/notebook.py +1710 -0
- mostlyright/data_harness/offline.py +2049 -0
- mostlyright/data_harness/operation_registry.py +1007 -0
- mostlyright/data_harness/operator_setup.py +239 -0
- mostlyright/data_harness/pipeline.py +6428 -0
- mostlyright/data_harness/plan_graph.py +2026 -0
- mostlyright/data_harness/preparation/__init__.py +104 -0
- mostlyright/data_harness/preparation/contracts.py +1017 -0
- mostlyright/data_harness/preparation/engine.py +221 -0
- mostlyright/data_harness/preparation/errors.py +14 -0
- mostlyright/data_harness/preparation/gates.py +751 -0
- mostlyright/data_harness/preparation/joins.py +574 -0
- mostlyright/data_harness/preparation/profile.py +384 -0
- mostlyright/data_harness/preparation/table.py +217 -0
- mostlyright/data_harness/preparation/transforms.py +568 -0
- mostlyright/data_harness/progress_events.py +534 -0
- mostlyright/data_harness/readers/__init__.py +46 -0
- mostlyright/data_harness/readers/containers.py +963 -0
- mostlyright/data_harness/readers/contracts.py +542 -0
- mostlyright/data_harness/readers/delimited.py +257 -0
- mostlyright/data_harness/readers/grib2/__init__.py +33 -0
- mostlyright/data_harness/readers/grib2/admission.py +722 -0
- mostlyright/data_harness/readers/grib2/decode.py +1009 -0
- mostlyright/data_harness/readers/grib2/geometry.py +1133 -0
- mostlyright/data_harness/readers/grib2/portable_math.py +501 -0
- mostlyright/data_harness/readers/json_tabular.py +485 -0
- mostlyright/data_harness/readers/registry.py +514 -0
- mostlyright/data_harness/readers/samples/README.md +110 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.0.0/cities_one_stream/cities.csv.gz +0 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.0.0/cities_one_stream/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.1.0/cities_one_stream/cities.csv.gz +0 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.1.0/cities_one_stream/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.0.0/cities_beside_a_directory_entry/cities.tar +0 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.0.0/cities_beside_a_directory_entry/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.1.0/cities_beside_a_directory_entry/cities.tar +0 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.1.0/cities_beside_a_directory_entry/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.0.0/cities_beside_a_second_member/cities.zip +0 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.0.0/cities_beside_a_second_member/expected.json +25 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.1.0/dwd_semicolon_station_member/dwd-station.zip +0 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.1.0/dwd_semicolon_station_member/expected.json +25 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.2.0/dwd_semicolon_station_member/dwd-station.zip +0 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.2.0/dwd_semicolon_station_member/expected.json +25 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/an_ordinary_comma_separated_table/cities.csv +3 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/an_ordinary_comma_separated_table/expected.json +23 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/quoted_fields_holding_the_delimiter/cities.tsv +5 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/quoted_fields_holding_the_delimiter/expected.json +25 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.1.0/an_hourly_observation_table_served_as_plain_text/expected.json +30 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.1.0/an_hourly_observation_table_served_as_plain_text/observations.csv +5 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.0.0/nested_hourly_observations/expected.json +44 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.0.0/nested_hourly_observations/stations.json +1 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.1.0/an_observation_stream_served_as_plain_text/expected.json +48 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.1.0/an_observation_stream_served_as_plain_text/observations.ndjson +4 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/an_ordinary_table_beside_a_second_sheet/cities.xlsx +0 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/an_ordinary_table_beside_a_second_sheet/expected.json +24 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/shares_the_workbook_had_already_computed/expected.json +27 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/shares_the_workbook_had_already_computed/shares.xlsx +0 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.1.0/shares_the_workbook_had_already_computed/expected.json +27 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.1.0/shares_the_workbook_had_already_computed/shares.xlsx +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/README.md +20 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/gfs_2m_temperature/expected.json +55 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/gfs_2m_temperature/gfs-2m-temperature.grib2 +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_2m_temperature/expected.json +54 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_2m_temperature/hrrr-2m-temperature.grib2 +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_categorical_rain/expected.json +54 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_categorical_rain/hrrr-categorical-rain.grib2 +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/2.0.0/hrrr_2m_temperature/expected.json +54 -0
- mostlyright/data_harness/readers/samples/weather.grib2/2.0.0/hrrr_2m_temperature/hrrr-2m-temperature.grib2 +0 -0
- mostlyright/data_harness/readers/samples.py +582 -0
- mostlyright/data_harness/readers/spreadsheet.py +803 -0
- mostlyright/data_harness/readers/tabular.py +510 -0
- mostlyright/data_harness/recipe.py +5321 -0
- mostlyright/data_harness/repair/__init__.py +78 -0
- mostlyright/data_harness/repair/adapters.py +274 -0
- mostlyright/data_harness/repair/contracts.py +872 -0
- mostlyright/data_harness/repair/coordinator.py +1099 -0
- mostlyright/data_harness/repair/errors.py +16 -0
- mostlyright/data_harness/review.py +2533 -0
- mostlyright/data_harness/rowset.py +283 -0
- mostlyright/data_harness/serving.py +1975 -0
- mostlyright/data_harness/serving_edge.py +590 -0
- mostlyright/data_harness/serving_http.py +1031 -0
- mostlyright/data_harness/session_probes.py +759 -0
- mostlyright/data_harness/signing.py +101 -0
- mostlyright/data_harness/source_discovery.py +898 -0
- mostlyright/data_harness/sources/__init__.py +209 -0
- mostlyright/data_harness/sources/_adapter_steps.py +213 -0
- mostlyright/data_harness/sources/adapters.py +1214 -0
- mostlyright/data_harness/sources/cadence.py +1428 -0
- mostlyright/data_harness/sources/cadence_emission.py +453 -0
- mostlyright/data_harness/sources/cadence_history.py +546 -0
- mostlyright/data_harness/sources/catalog/__init__.py +17 -0
- mostlyright/data_harness/sources/catalog/admission.py +477 -0
- mostlyright/data_harness/sources/catalog/authoring.py +1701 -0
- mostlyright/data_harness/sources/catalog/authoring_policy.py +701 -0
- mostlyright/data_harness/sources/catalog/authoring_shards.py +1217 -0
- mostlyright/data_harness/sources/catalog/bounded_io.py +231 -0
- mostlyright/data_harness/sources/catalog/channel.py +523 -0
- mostlyright/data_harness/sources/catalog/channel_client.py +296 -0
- mostlyright/data_harness/sources/catalog/contracts.py +825 -0
- mostlyright/data_harness/sources/catalog/coverage.py +137 -0
- mostlyright/data_harness/sources/catalog/delta.py +1340 -0
- mostlyright/data_harness/sources/catalog/embedding.py +532 -0
- mostlyright/data_harness/sources/catalog/entry_v2.py +1182 -0
- mostlyright/data_harness/sources/catalog/fill.py +3889 -0
- mostlyright/data_harness/sources/catalog/fill_partitions.py +459 -0
- mostlyright/data_harness/sources/catalog/fill_staging.py +1105 -0
- mostlyright/data_harness/sources/catalog/gating.py +374 -0
- mostlyright/data_harness/sources/catalog/generation_receipt.py +1607 -0
- mostlyright/data_harness/sources/catalog/harvest/__init__.py +7 -0
- mostlyright/data_harness/sources/catalog/harvest/ckan.py +384 -0
- mostlyright/data_harness/sources/catalog/harvest/datagov_v4.py +798 -0
- mostlyright/data_harness/sources/catalog/harvest/protocol.py +964 -0
- mostlyright/data_harness/sources/catalog/harvest/sdmx.py +445 -0
- mostlyright/data_harness/sources/catalog/harvest/stac.py +384 -0
- mostlyright/data_harness/sources/catalog/health.py +447 -0
- mostlyright/data_harness/sources/catalog/hosted_catalog.py +105 -0
- mostlyright/data_harness/sources/catalog/identity_history.py +1549 -0
- mostlyright/data_harness/sources/catalog/neural.py +1618 -0
- mostlyright/data_harness/sources/catalog/packed_catalog.py +2345 -0
- mostlyright/data_harness/sources/catalog/packed_retrieval.py +1517 -0
- mostlyright/data_harness/sources/catalog/packed_writer.py +2802 -0
- mostlyright/data_harness/sources/catalog/query_trace.py +1037 -0
- mostlyright/data_harness/sources/catalog/recommend.py +171 -0
- mostlyright/data_harness/sources/catalog/retrieval.py +230 -0
- mostlyright/data_harness/sources/catalog/retrieval_manifest.py +995 -0
- mostlyright/data_harness/sources/catalog/rights_decisions.py +254 -0
- mostlyright/data_harness/sources/catalog/sealed.py +560 -0
- mostlyright/data_harness/sources/catalog/search.py +230 -0
- mostlyright/data_harness/sources/catalog/streaming_delta.py +1097 -0
- mostlyright/data_harness/sources/catalog/update.py +891 -0
- mostlyright/data_harness/sources/collections.py +815 -0
- mostlyright/data_harness/sources/contracts.py +2223 -0
- mostlyright/data_harness/sources/deletion.py +761 -0
- mostlyright/data_harness/sources/fitness.py +162 -0
- mostlyright/data_harness/sources/governance.py +163 -0
- mostlyright/data_harness/sources/hosted.py +173 -0
- mostlyright/data_harness/sources/integration.py +218 -0
- mostlyright/data_harness/sources/range_reader.py +418 -0
- mostlyright/data_harness/sources/registry.py +514 -0
- mostlyright/data_harness/sources/rights_rule.py +59 -0
- mostlyright/data_harness/sources/source_cadence_vectors.v1.json +1 -0
- mostlyright/data_harness/sources/sports.py +521 -0
- mostlyright/data_harness/sources/stream.py +524 -0
- mostlyright/data_harness/sources/stream_connector.py +418 -0
- mostlyright/data_harness/sources/stream_recorder.py +1404 -0
- mostlyright/data_harness/studio_boundary.py +2019 -0
- mostlyright/data_harness/thin/__init__.py +37 -0
- mostlyright/data_harness/thin/acquire.py +1137 -0
- mostlyright/data_harness/thin/acquire_cancel.py +579 -0
- mostlyright/data_harness/thin/approvals.py +617 -0
- mostlyright/data_harness/thin/commands.py +406 -0
- mostlyright/data_harness/thin/download.py +194 -0
- mostlyright/data_harness/thin/narrative.py +589 -0
- mostlyright/data_harness/thin/parity.py +1070 -0
- mostlyright/data_harness/thin/propose.py +2759 -0
- mostlyright/data_harness/thin/research.py +1663 -0
- mostlyright/data_harness/thin/router.py +924 -0
- mostlyright/data_harness/thin/runs.py +519 -0
- mostlyright/data_harness/thin/session.py +281 -0
- mostlyright/data_harness/thin/stream.py +501 -0
- mostlyright/data_harness/thin/transport.py +187 -0
- mostlyright/data_harness/thin/vocabulary.py +368 -0
- mostlyright/data_harness/thin/workers.py +164 -0
- mostlyright/data_harness/ucum/TABLE-PIN.json +40 -0
- mostlyright/data_harness/ucum/ucum-subset.v1.json +632 -0
- mostlyright/data_harness/unit_flow.py +927 -0
- mostlyright/data_harness/units.py +572 -0
- mostlyright/data_harness/ux/__init__.py +9 -0
- mostlyright/data_harness/ux/approve.py +485 -0
- mostlyright/data_harness/ux/author_yaml.py +597 -0
- mostlyright/data_harness/ux/cloud_auth.py +447 -0
- mostlyright/data_harness/ux/commands/__init__.py +260 -0
- mostlyright/data_harness/ux/commands/approve.py +136 -0
- mostlyright/data_harness/ux/commands/auth.py +744 -0
- mostlyright/data_harness/ux/commands/author.py +79 -0
- mostlyright/data_harness/ux/commands/catalog_author.py +403 -0
- mostlyright/data_harness/ux/commands/catalog_fill.py +523 -0
- mostlyright/data_harness/ux/commands/catalog_harvest.py +545 -0
- mostlyright/data_harness/ux/commands/catalog_publish.py +1838 -0
- mostlyright/data_harness/ux/commands/catalog_search.py +71 -0
- mostlyright/data_harness/ux/commands/catalog_update.py +437 -0
- mostlyright/data_harness/ux/commands/deploy.py +134 -0
- mostlyright/data_harness/ux/commands/deploy_dataset.py +98 -0
- mostlyright/data_harness/ux/commands/deploy_plan.py +105 -0
- mostlyright/data_harness/ux/commands/deploy_status.py +104 -0
- mostlyright/data_harness/ux/commands/diff.py +74 -0
- mostlyright/data_harness/ux/commands/index.py +84 -0
- mostlyright/data_harness/ux/commands/inventory.py +47 -0
- mostlyright/data_harness/ux/commands/list_builds.py +143 -0
- mostlyright/data_harness/ux/commands/login.py +63 -0
- mostlyright/data_harness/ux/commands/peek.py +236 -0
- mostlyright/data_harness/ux/commands/plan_check.py +90 -0
- mostlyright/data_harness/ux/commands/preflight.py +97 -0
- mostlyright/data_harness/ux/commands/record.py +107 -0
- mostlyright/data_harness/ux/commands/review_setup.py +47 -0
- mostlyright/data_harness/ux/commands/search.py +440 -0
- mostlyright/data_harness/ux/commands/show.py +61 -0
- mostlyright/data_harness/ux/commands/whoami.py +37 -0
- mostlyright/data_harness/ux/credential_native.py +551 -0
- mostlyright/data_harness/ux/credential_store.py +1055 -0
- mostlyright/data_harness/ux/credentials.py +631 -0
- mostlyright/data_harness/ux/diffing.py +444 -0
- mostlyright/data_harness/ux/headline.py +671 -0
- mostlyright/data_harness/ux/hosted_acquisition.py +974 -0
- mostlyright/data_harness/ux/hosted_run_status.py +619 -0
- mostlyright/data_harness/ux/inventory.py +427 -0
- mostlyright/data_harness/ux/local_review.py +375 -0
- mostlyright/data_harness/ux/login.py +691 -0
- mostlyright/data_harness/ux/path_kind.py +147 -0
- mostlyright/data_harness/ux/peek.py +1000 -0
- mostlyright/data_harness/ux/plain_file.py +178 -0
- mostlyright/data_harness/ux/plan_check.py +311 -0
- mostlyright/data_harness/ux/preflight.py +918 -0
- mostlyright/data_harness/ux/readers.py +1124 -0
- mostlyright/data_harness/ux/remediation.py +2195 -0
- mostlyright/data_harness/ux/render.py +657 -0
- mostlyright/data_harness/ux/workload.py +1077 -0
- mostlyright/data_harness/viewer.py +3713 -0
- mostlyright/data_harness/visual_run/__init__.py +83 -0
- mostlyright/data_harness/visual_run/authoring.py +235 -0
- mostlyright/data_harness/visual_run/contracts.py +673 -0
- mostlyright/data_harness/visual_run/materialize.py +486 -0
- mostlyright/data_harness/visual_run/observations.py +874 -0
- mostlyright/data_harness/visual_run/query.py +259 -0
- mostlyright/data_harness/visual_run/reducer.py +280 -0
- mostlyright/data_harness/visual_run/sdk.py +892 -0
- mostlyright/data_harness/visual_run/store.py +584 -0
- mostlyright/data_harness/visual_run/transport.py +239 -0
- mostlyright/data_harness/watch.py +2999 -0
- mostlyright_data-0.9.0.dist-info/METADATA +607 -0
- mostlyright_data-0.9.0.dist-info/RECORD +314 -0
- mostlyright_data-0.9.0.dist-info/WHEEL +4 -0
- mostlyright_data-0.9.0.dist-info/entry_points.txt +12 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Group B · cell frame (B06-B11): gutter prompt, states, hover toolbar, collapse, tags, meta.
|
|
2
|
+
|
|
3
|
+
These are the chrome that wraps a cell body; the body itself is rendered by
|
|
4
|
+
``code_body``/``markdown_body`` and the outputs by the ``outputs_*`` modules, then ``document``
|
|
5
|
+
slots them into the 62px grid this group decorates. State is expressed as box-shadow classes
|
|
6
|
+
(B07), never borders, so a selection can never shift layout. ``document`` calls
|
|
7
|
+
:func:`render_prompt` for every cell and output row, :func:`cell_state_classes` and
|
|
8
|
+
:func:`render_hover_toolbar` on code cells, :func:`render_exec_meta` when a cell has an execution
|
|
9
|
+
count, and :func:`render_tags` on tagged markdown cells; :func:`render_collapse_bar` (B09) is a
|
|
10
|
+
public helper with no call site in ``document``.
|
|
11
|
+
|
|
12
|
+
Every class emitted here resolves against a rule in :func:`tokens.build_css`, including
|
|
13
|
+
``.nb-panel-head-left`` (the language/tags group) and ``.nb-panel-head--static`` (the
|
|
14
|
+
always-visible static toolbar). No literal color, size, or font is written here — the
|
|
15
|
+
design-system values live only in ``tokens``. Every value interpolated from notebook JSON passes
|
|
16
|
+
through :func:`parse.esc` at the point of interpolation.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from mostlyright.data_harness.nbrender.parse import Cell, RenderContext, esc
|
|
24
|
+
|
|
25
|
+
# --- B06 · gutter prompt ----------------------------------------------------------------------
|
|
26
|
+
# The gutter prompt variants. Key == the ``nb-prompt--<key>`` CSS modifier;
|
|
27
|
+
# value is the literal glyph, with ``{n}`` the sole interpolation slot (the execution count).
|
|
28
|
+
_PROMPT_LABEL: dict[str, str] = {
|
|
29
|
+
"in": "In [{n}]", # executed code cell · cobalt
|
|
30
|
+
"running": "In [*]", # running · orange + pulse
|
|
31
|
+
"idle": "In [ ]", # never run · faint
|
|
32
|
+
"out": "Out [{n}]", # execute_result / display_data · orange
|
|
33
|
+
"stream": "Out [*]", # streaming · faint
|
|
34
|
+
"err": "Err", # error output · rust
|
|
35
|
+
"wgt": "Wgt", # widget output · faint
|
|
36
|
+
"md": "md", # markdown cell · hairline
|
|
37
|
+
"raw": "raw", # raw cell · hairline
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _derive_prompt(cell: Cell) -> tuple[str | None, int | None]:
|
|
42
|
+
"""Pick the gutter variant for a cell from its type and execution state.
|
|
43
|
+
|
|
44
|
+
Output prompts (out/stream/err/wgt) and the live ``running`` state are not derivable from a
|
|
45
|
+
frozen cell — callers request them explicitly through ``variant``.
|
|
46
|
+
"""
|
|
47
|
+
if cell.cell_type == "code":
|
|
48
|
+
if cell.execution_count is not None:
|
|
49
|
+
return "in", cell.execution_count
|
|
50
|
+
return "idle", None
|
|
51
|
+
if cell.cell_type == "markdown":
|
|
52
|
+
return "md", None
|
|
53
|
+
if cell.cell_type == "raw":
|
|
54
|
+
return "raw", None
|
|
55
|
+
return None, None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def render_prompt(
|
|
59
|
+
cell: Cell,
|
|
60
|
+
ctx: RenderContext,
|
|
61
|
+
*,
|
|
62
|
+
variant: str | None = None,
|
|
63
|
+
count: int | None = None,
|
|
64
|
+
) -> str:
|
|
65
|
+
"""B06 · gutter prompt — one of the eight variants for the 62px column, excluded from copy.
|
|
66
|
+
|
|
67
|
+
With no ``variant`` the cell decides: executed code -> ``In [n]``, unrun code -> ``In [ ]``,
|
|
68
|
+
markdown -> ``md``, raw -> ``raw``. Output prompts (``out``/``stream``/``err``/``wgt``) and the
|
|
69
|
+
live ``running`` state are requested explicitly by ``document`` when it re-enters an output into
|
|
70
|
+
the gutter column. The span is ``aria-hidden`` so it stays out of the accessibility tree even
|
|
71
|
+
when placed outside the (already aria-hidden) gutter; copy-exclusion comes from the gutter's
|
|
72
|
+
``user-select: none``. An unknown cell type yields no prompt.
|
|
73
|
+
"""
|
|
74
|
+
if variant is None:
|
|
75
|
+
variant, count = _derive_prompt(cell)
|
|
76
|
+
template = _PROMPT_LABEL.get(variant) if variant is not None else None
|
|
77
|
+
if template is None:
|
|
78
|
+
return ""
|
|
79
|
+
if "{n}" in template:
|
|
80
|
+
label = template.replace("{n}", esc(count) if count is not None else " ")
|
|
81
|
+
else:
|
|
82
|
+
label = template
|
|
83
|
+
return f'<span class="nb-prompt nb-prompt--{variant}" aria-hidden="true">{label}</span>'
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# --- B07 · cell states ------------------------------------------------------------------------
|
|
87
|
+
# Live UI states (selected/editing/running) map to a box-shadow class on the code panel. Only the
|
|
88
|
+
# error state is derivable from frozen data — a cell carrying an error output always gets it.
|
|
89
|
+
_STATE_CLASS: dict[str, str] = {
|
|
90
|
+
"selected": "is-selected",
|
|
91
|
+
"editing": "is-selected", # panel reads selected; the gutter bar is a cell-level concern
|
|
92
|
+
"running": "is-running",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def cell_state_classes(cell: Cell, ctx: RenderContext, *, state: str | None = None) -> str:
|
|
97
|
+
"""B07 · cell states — space-joined panel classes; ``''`` when resting.
|
|
98
|
+
|
|
99
|
+
Never emits a ``border`` class: state is box-shadow only so a selection cannot shift layout.
|
|
100
|
+
``state`` names a live UI state (selected/editing/running) for connected mode; the error ring
|
|
101
|
+
is data-driven and combines with any live state. Every returned token resolves against a
|
|
102
|
+
``.nb-panel.is-*`` rule, so the fragment is safe to concatenate onto the panel's class list.
|
|
103
|
+
"""
|
|
104
|
+
classes: list[str] = []
|
|
105
|
+
live = _STATE_CLASS.get(state) if state is not None else None
|
|
106
|
+
if live is not None:
|
|
107
|
+
classes.append(live)
|
|
108
|
+
if any(out.kind == "error" for out in cell.outputs):
|
|
109
|
+
classes.append("is-error")
|
|
110
|
+
return " ".join(classes)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# --- B08 · hover toolbar ----------------------------------------------------------------------
|
|
114
|
+
_COPY_BTN = '<button class="nb-panel-btn" type="button">copy</button>'
|
|
115
|
+
_RUN_BTN = '<button class="nb-panel-btn" type="button">run</button>'
|
|
116
|
+
_FOLD_BTN = '<button class="nb-panel-btn" type="button">fold</button>'
|
|
117
|
+
_INTERRUPT_BTN = (
|
|
118
|
+
'<button class="nb-panel-btn nb-panel-btn--interrupt" type="button">interrupt</button>'
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def render_hover_toolbar(
|
|
123
|
+
cell: Cell,
|
|
124
|
+
ctx: RenderContext,
|
|
125
|
+
*,
|
|
126
|
+
running: bool = False,
|
|
127
|
+
language: str = "python",
|
|
128
|
+
) -> str:
|
|
129
|
+
"""B08 · hover toolbar — panel header with the language label, cell tags, and ghost actions.
|
|
130
|
+
|
|
131
|
+
Left group is the language label plus the B10 tag chips. Right group is the action set:
|
|
132
|
+
connected mode shows ``copy`` / ``run`` / ``fold`` (``run`` becomes ``interrupt`` while the
|
|
133
|
+
cell runs); static and embed modes show ``copy`` only and stay visible without a hover via the
|
|
134
|
+
``--static`` modifier. Opacity/hover behaviour lives entirely in the stylesheet.
|
|
135
|
+
"""
|
|
136
|
+
left = (
|
|
137
|
+
'<div class="nb-panel-head-left">'
|
|
138
|
+
f'<span class="nb-panel-lang">{esc(language)}</span>'
|
|
139
|
+
f"{render_tags(cell, ctx)}"
|
|
140
|
+
"</div>"
|
|
141
|
+
)
|
|
142
|
+
if ctx.mode == "connected":
|
|
143
|
+
run = _INTERRUPT_BTN if running else _RUN_BTN
|
|
144
|
+
actions = _COPY_BTN + run + _FOLD_BTN
|
|
145
|
+
head_class = "nb-panel-head"
|
|
146
|
+
else:
|
|
147
|
+
actions = _COPY_BTN
|
|
148
|
+
head_class = "nb-panel-head nb-panel-head--static"
|
|
149
|
+
right = f'<div class="nb-panel-actions">{actions}</div>'
|
|
150
|
+
return f'<div class="{head_class}">{left}{right}</div>'
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# --- B09 · collapse affordances ---------------------------------------------------------------
|
|
154
|
+
def _int_or_zero(value: Any) -> int:
|
|
155
|
+
"""Coerce a hidden-line count to a non-negative int; anything malformed reads as zero."""
|
|
156
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
157
|
+
return 0
|
|
158
|
+
return max(0, value)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def render_collapse_bar(cell: Cell, ctx: RenderContext, *, hidden_lines: int, kind: str) -> str:
|
|
162
|
+
"""B09 · collapse affordance — dashed bar for a folded input or output.
|
|
163
|
+
|
|
164
|
+
``kind`` is ``input`` (``metadata.jupyter.source_hidden``) or ``output``
|
|
165
|
+
(``metadata.jupyter.outputs_hidden``); ``hidden_lines`` is the caller's line count. A folded
|
|
166
|
+
input reading ``hide-input`` also surfaces that tag chip. The bar is a full-width button; in
|
|
167
|
+
static mode it is an inert affordance.
|
|
168
|
+
"""
|
|
169
|
+
count = _int_or_zero(hidden_lines)
|
|
170
|
+
if kind == "output":
|
|
171
|
+
text = f"output hidden · {count} lines"
|
|
172
|
+
chip = ""
|
|
173
|
+
else:
|
|
174
|
+
text = f"{count} lines hidden"
|
|
175
|
+
chip = (
|
|
176
|
+
'<span class="nb-tag nb-tag--hide-input">hide-input</span>'
|
|
177
|
+
if "hide-input" in cell.tags
|
|
178
|
+
else ""
|
|
179
|
+
)
|
|
180
|
+
return (
|
|
181
|
+
'<button class="nb-collapse" type="button">'
|
|
182
|
+
'<span class="nb-collapse-caret">▸</span>'
|
|
183
|
+
f"<span>{esc(text)}</span>"
|
|
184
|
+
f"{chip}"
|
|
185
|
+
'<span class="nb-collapse-spacer"></span>'
|
|
186
|
+
'<span class="nb-collapse-show">show</span>'
|
|
187
|
+
"</button>"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# --- B10 · cell tags --------------------------------------------------------------------------
|
|
192
|
+
def render_tags(cell: Cell, ctx: RenderContext) -> str:
|
|
193
|
+
"""B10 · cell tags — the four known chips from ``cell.tags`` in display order; ``''`` when none.
|
|
194
|
+
|
|
195
|
+
``cell.tags`` has already narrowed to the known set (``cached``/``slow``/``skip``/
|
|
196
|
+
``hide-input``), so each name is a safe class modifier; the chips are returned bare for the
|
|
197
|
+
caller to place (the B08 left group, or before a markdown body).
|
|
198
|
+
"""
|
|
199
|
+
tags = cell.tags
|
|
200
|
+
if not tags:
|
|
201
|
+
return ""
|
|
202
|
+
return "".join(f'<span class="nb-tag nb-tag--{tag}">{esc(tag)}</span>' for tag in tags)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def render_research_activity(cell: Cell, ctx: RenderContext) -> str:
|
|
206
|
+
"""Render only the exact persisted activity declaration carried by a research cell."""
|
|
207
|
+
|
|
208
|
+
state = cell.research_activity
|
|
209
|
+
if state is None:
|
|
210
|
+
return ""
|
|
211
|
+
label = "Writing" if state == "writing" else "Executing"
|
|
212
|
+
caret = (
|
|
213
|
+
'<span class="nb-activity-caret" aria-hidden="true"></span>' if state == "writing" else ""
|
|
214
|
+
)
|
|
215
|
+
return (
|
|
216
|
+
f'<span class="nb-activity nb-activity--{state}" data-cell-activity>'
|
|
217
|
+
f"{esc(label)}{caret}</span>"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# --- B11 · execution meta ---------------------------------------------------------------------
|
|
222
|
+
def _fmt_number(value: int | float) -> str:
|
|
223
|
+
"""Compact numeric string: integers and whole floats drop the decimal point."""
|
|
224
|
+
if isinstance(value, int):
|
|
225
|
+
return str(value)
|
|
226
|
+
number = float(value)
|
|
227
|
+
return str(int(number)) if number.is_integer() else f"{number:g}"
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _exec_meta_facts(cell: Cell) -> list[str]:
|
|
231
|
+
"""Duration / finish-time / peak-memory facts from ``metadata.mostlyright.execution``.
|
|
232
|
+
|
|
233
|
+
Keys are ``duration_s`` (seconds), ``finished_at`` (a clock string), and ``peak_mb`` (MB).
|
|
234
|
+
Only well-typed fields render; the free-text ``finished_at`` is escaped.
|
|
235
|
+
"""
|
|
236
|
+
block = cell.metadata.get("mostlyright")
|
|
237
|
+
execution = block.get("execution") if isinstance(block, dict) else None
|
|
238
|
+
if not isinstance(execution, dict):
|
|
239
|
+
return []
|
|
240
|
+
facts: list[str] = []
|
|
241
|
+
duration = execution.get("duration_s")
|
|
242
|
+
if isinstance(duration, (int, float)) and not isinstance(duration, bool):
|
|
243
|
+
facts.append(f"{float(duration):.2f} s")
|
|
244
|
+
finished = execution.get("finished_at")
|
|
245
|
+
if isinstance(finished, str) and finished.strip():
|
|
246
|
+
facts.append(esc(finished))
|
|
247
|
+
peak = execution.get("peak_mb")
|
|
248
|
+
if isinstance(peak, (int, float)) and not isinstance(peak, bool):
|
|
249
|
+
facts.append(f"peak {_fmt_number(peak)} MB")
|
|
250
|
+
return facts
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _is_int(value: Any) -> bool:
|
|
254
|
+
return isinstance(value, int) and not isinstance(value, bool)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def render_exec_meta(cell: Cell, ctx: RenderContext) -> str:
|
|
258
|
+
"""B11 · execution meta — duration/finish/peak row plus the out-of-order chip.
|
|
259
|
+
|
|
260
|
+
Facts come from cell metadata when present. When ``execution_count`` is below the previous
|
|
261
|
+
code cell's (``ctx.prev_execution_count``), an orange ``ran out of order`` chip is appended.
|
|
262
|
+
The row is omitted entirely when there is nothing to say.
|
|
263
|
+
"""
|
|
264
|
+
facts = _exec_meta_facts(cell)
|
|
265
|
+
out_of_order = ""
|
|
266
|
+
if (
|
|
267
|
+
_is_int(cell.execution_count)
|
|
268
|
+
and _is_int(ctx.prev_execution_count)
|
|
269
|
+
and cell.execution_count < ctx.prev_execution_count # type: ignore[operator]
|
|
270
|
+
):
|
|
271
|
+
out_of_order = '<span class="nb-exec-ooo">↯ ran out of order</span>'
|
|
272
|
+
if not facts and not out_of_order:
|
|
273
|
+
return ""
|
|
274
|
+
separator = '<span class="nb-exec-mid">·</span>'
|
|
275
|
+
return f'<div class="nb-exec-meta">{separator.join(facts)}{out_of_order}</div>'
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""Group E · interactive (E25, E26): ipywidgets frozen render and the stale-output banner.
|
|
2
|
+
|
|
3
|
+
Routed to from ``document._render_output``. There is no kernel in ``static``/``embed``: E25 renders
|
|
4
|
+
a frozen control state from ``application/vnd.jupyter.widget-view+json`` plus widget-state metadata
|
|
5
|
+
when present, otherwise it dims a placeholder and pairs it with the E26 banner. When the banner is
|
|
6
|
+
shown the controls take ``opacity: 0.62; pointer-events: none``. Escape every label and value from
|
|
7
|
+
the widget JSON.
|
|
8
|
+
|
|
9
|
+
Frozen state is resolved from what a single :class:`~parse.Output` can reach — the fixed
|
|
10
|
+
``render_widget(out, ctx)`` signature does not expose notebook-level ``metadata.widgets`` — so this
|
|
11
|
+
module reads either an embedded ``state`` object on the widget-view bundle (a Mostly Right export
|
|
12
|
+
convenience) or the widget's ``text/plain`` repr (``IntSlider(value=5)``). Neither present, or an
|
|
13
|
+
unrecognized model, degrades to the dimmed placeholder.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
from typing import Any, NamedTuple
|
|
20
|
+
|
|
21
|
+
from mostlyright.data_harness.nbrender.parse import Output, RenderContext, esc
|
|
22
|
+
|
|
23
|
+
_WIDGET_MIME = "application/vnd.jupyter.widget-view+json"
|
|
24
|
+
|
|
25
|
+
# The E26 banner's cobalt affordance is the same element that becomes a re-run button in connected
|
|
26
|
+
# mode; in a no-kernel export it renders inert (a span, no action) so the banner height is stable.
|
|
27
|
+
_STALE_ACTION = "re-run to refresh"
|
|
28
|
+
|
|
29
|
+
# A slider with no declared bounds falls back to the ipywidgets integer defaults.
|
|
30
|
+
_DEFAULT_MIN = 0.0
|
|
31
|
+
_DEFAULT_MAX = 100.0
|
|
32
|
+
|
|
33
|
+
# A checkmark glyph is not sanctioned iconography, so the checked tick is an inline SVG stroke. The
|
|
34
|
+
# stroke references the paper token (near-white); the ``.nb-checkbox.is-checked svg`` rule in
|
|
35
|
+
# ``tokens`` wins over this presentation attribute and upgrades it to pure white.
|
|
36
|
+
_CHECK_TICK = (
|
|
37
|
+
'<svg viewBox="0 0 12 12" width="12" height="12" aria-hidden="true" focusable="false">'
|
|
38
|
+
'<path d="M2.5 6.2 L5 8.7 L9.5 3.3" fill="none" stroke="var(--nb-paper)" '
|
|
39
|
+
'stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path>'
|
|
40
|
+
"</svg>"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
_REPR_RE = re.compile(r"^\s*([A-Za-z_]\w*)\s*\((.*)\)\s*$", re.DOTALL)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _WidgetState(NamedTuple):
|
|
47
|
+
"""Normalized frozen widget state, source-agnostic (embedded object or text/plain repr)."""
|
|
48
|
+
|
|
49
|
+
kind: str | None # slider | toggle | button | checkbox | output | None (unrecognized)
|
|
50
|
+
label: str # model/class name, used only for the placeholder label
|
|
51
|
+
description: str
|
|
52
|
+
value: Any
|
|
53
|
+
vmin: float | None
|
|
54
|
+
vmax: float | None
|
|
55
|
+
options: list[str]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _classify(name: str) -> str | None:
|
|
59
|
+
"""Map an ipywidgets model/class name to one of the five controls drawn here, else None."""
|
|
60
|
+
lowered = (name or "").lower()
|
|
61
|
+
if "slider" in lowered:
|
|
62
|
+
return "slider"
|
|
63
|
+
if "togglebuttons" in lowered: # plural group; must precede the bare "button" test below
|
|
64
|
+
return "toggle"
|
|
65
|
+
if "checkbox" in lowered:
|
|
66
|
+
return "checkbox"
|
|
67
|
+
if "button" in lowered:
|
|
68
|
+
return "button"
|
|
69
|
+
if "output" in lowered:
|
|
70
|
+
return "output"
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _as_num(value: Any) -> float | None:
|
|
75
|
+
"""Coerce a numeric bound to float; reject bools and non-numerics (``None`` -> use defaults)."""
|
|
76
|
+
if isinstance(value, bool):
|
|
77
|
+
return None
|
|
78
|
+
if isinstance(value, (int, float)):
|
|
79
|
+
return float(value)
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _fmt_value(value: Any) -> str:
|
|
84
|
+
"""Human value readout: bools verbatim, floats without trailing-zero noise, else ``str``."""
|
|
85
|
+
if isinstance(value, bool):
|
|
86
|
+
return "True" if value else "False"
|
|
87
|
+
if isinstance(value, float):
|
|
88
|
+
return f"{value:g}"
|
|
89
|
+
if value is None:
|
|
90
|
+
return ""
|
|
91
|
+
return str(value)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _fraction(value: Any, vmin: float | None, vmax: float | None) -> float:
|
|
95
|
+
"""Slider fill fraction in [0, 1] from value and bounds: bounds default, range clamps."""
|
|
96
|
+
numeric = _as_num(value)
|
|
97
|
+
if numeric is None:
|
|
98
|
+
return 0.0
|
|
99
|
+
lo = vmin if vmin is not None else _DEFAULT_MIN
|
|
100
|
+
hi = vmax if vmax is not None else _DEFAULT_MAX
|
|
101
|
+
if hi == lo:
|
|
102
|
+
return 0.0
|
|
103
|
+
return max(0.0, min(1.0, (numeric - lo) / (hi - lo)))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# --- state resolution -------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _split_top_level(source: str) -> list[str]:
|
|
110
|
+
"""Split a repr arg string on top-level commas, honoring quotes and bracket nesting."""
|
|
111
|
+
parts: list[str] = []
|
|
112
|
+
buf: list[str] = []
|
|
113
|
+
depth = 0
|
|
114
|
+
quote: str | None = None
|
|
115
|
+
escaped = False
|
|
116
|
+
for ch in source:
|
|
117
|
+
if escaped:
|
|
118
|
+
buf.append(ch)
|
|
119
|
+
escaped = False
|
|
120
|
+
continue
|
|
121
|
+
if quote is not None:
|
|
122
|
+
buf.append(ch)
|
|
123
|
+
if ch == "\\":
|
|
124
|
+
escaped = True
|
|
125
|
+
elif ch == quote:
|
|
126
|
+
quote = None
|
|
127
|
+
continue
|
|
128
|
+
if ch in "\"'":
|
|
129
|
+
quote = ch
|
|
130
|
+
buf.append(ch)
|
|
131
|
+
elif ch in "([{":
|
|
132
|
+
depth += 1
|
|
133
|
+
buf.append(ch)
|
|
134
|
+
elif ch in ")]}":
|
|
135
|
+
depth = max(0, depth - 1)
|
|
136
|
+
buf.append(ch)
|
|
137
|
+
elif ch == "," and depth == 0:
|
|
138
|
+
parts.append("".join(buf))
|
|
139
|
+
buf = []
|
|
140
|
+
else:
|
|
141
|
+
buf.append(ch)
|
|
142
|
+
if buf:
|
|
143
|
+
parts.append("".join(buf))
|
|
144
|
+
return [part.strip() for part in parts if part.strip()]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _parse_val(token: str) -> Any:
|
|
148
|
+
"""Parse one repr scalar/sequence: quoted string, bool, None, number, or a tuple/list."""
|
|
149
|
+
text = token.strip()
|
|
150
|
+
if len(text) >= 2 and text[0] in "\"'" and text[-1] == text[0]:
|
|
151
|
+
return text[1:-1]
|
|
152
|
+
if text == "True":
|
|
153
|
+
return True
|
|
154
|
+
if text == "False":
|
|
155
|
+
return False
|
|
156
|
+
if text == "None":
|
|
157
|
+
return None
|
|
158
|
+
if len(text) >= 2 and text[0] in "([" and text[-1] in ")]":
|
|
159
|
+
return [_parse_val(item) for item in _split_top_level(text[1:-1])]
|
|
160
|
+
if re.fullmatch(r"[+-]?\d+", text):
|
|
161
|
+
return int(text)
|
|
162
|
+
try:
|
|
163
|
+
return float(text)
|
|
164
|
+
except ValueError:
|
|
165
|
+
return text
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _options_of(raw: Any) -> list[str]:
|
|
169
|
+
"""Coerce an options sequence to a list of strings; anything else yields no options."""
|
|
170
|
+
if isinstance(raw, list):
|
|
171
|
+
return [str(item) for item in raw]
|
|
172
|
+
return []
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _from_embedded(state: dict[str, Any]) -> _WidgetState:
|
|
176
|
+
"""Normalize an embedded widget-state object (``_model_name`` + fields)."""
|
|
177
|
+
model = str(state.get("_model_name", ""))
|
|
178
|
+
options = _options_of(state.get("_options_labels"))
|
|
179
|
+
if not options:
|
|
180
|
+
options = _options_of(state.get("options"))
|
|
181
|
+
value = state.get("value")
|
|
182
|
+
# ToggleButtons serializes the selection as an index into the labels, not a value.
|
|
183
|
+
index = state.get("index")
|
|
184
|
+
if value is None and isinstance(index, int) and not isinstance(index, bool) and options:
|
|
185
|
+
if 0 <= index < len(options):
|
|
186
|
+
value = options[index]
|
|
187
|
+
label = re.sub(r"Model$", "", model) or "widget"
|
|
188
|
+
return _WidgetState(
|
|
189
|
+
kind=_classify(model),
|
|
190
|
+
label=label,
|
|
191
|
+
description=str(state.get("description", "") or ""),
|
|
192
|
+
value=value,
|
|
193
|
+
vmin=_as_num(state.get("min")),
|
|
194
|
+
vmax=_as_num(state.get("max")),
|
|
195
|
+
options=options,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _from_repr(text: str) -> _WidgetState | None:
|
|
200
|
+
"""Normalize a widget ``text/plain`` repr, e.g. ``ToggleButtons(options=('a','b'))``."""
|
|
201
|
+
match = _REPR_RE.match(text.strip())
|
|
202
|
+
if match is None:
|
|
203
|
+
return None
|
|
204
|
+
class_name = match.group(1)
|
|
205
|
+
kwargs: dict[str, Any] = {}
|
|
206
|
+
for part in _split_top_level(match.group(2)):
|
|
207
|
+
if "=" in part:
|
|
208
|
+
key, _, raw = part.partition("=")
|
|
209
|
+
kwargs[key.strip()] = _parse_val(raw)
|
|
210
|
+
return _WidgetState(
|
|
211
|
+
kind=_classify(class_name),
|
|
212
|
+
label=class_name,
|
|
213
|
+
description=str(kwargs.get("description", "") or ""),
|
|
214
|
+
value=kwargs.get("value"),
|
|
215
|
+
vmin=_as_num(kwargs.get("min")),
|
|
216
|
+
vmax=_as_num(kwargs.get("max")),
|
|
217
|
+
options=_options_of(kwargs.get("options")),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _resolve_state(out: Output) -> _WidgetState | None:
|
|
222
|
+
"""Frozen state from an embedded view ``state`` object, else the text/plain repr, else None."""
|
|
223
|
+
view = out.data.get(_WIDGET_MIME)
|
|
224
|
+
if isinstance(view, dict):
|
|
225
|
+
embedded = view.get("state")
|
|
226
|
+
if isinstance(embedded, dict):
|
|
227
|
+
return _from_embedded(embedded)
|
|
228
|
+
repr_text = out.text_of("text/plain")
|
|
229
|
+
if repr_text.strip():
|
|
230
|
+
return _from_repr(repr_text)
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# --- control rows -----------------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _row(label_html: str, control_html: str) -> str:
|
|
238
|
+
return (
|
|
239
|
+
'<div class="nb-widget-row">'
|
|
240
|
+
'<span class="nb-widget-label">' + label_html + "</span>" + control_html + "</div>"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _slider_row(state: _WidgetState) -> str:
|
|
245
|
+
pct = f"{_fraction(state.value, state.vmin, state.vmax) * 100:g}"
|
|
246
|
+
control = (
|
|
247
|
+
'<div class="nb-slider-track" style="position: relative">'
|
|
248
|
+
'<div class="nb-slider-fill" style="width: ' + pct + '%"></div>'
|
|
249
|
+
'<div class="nb-slider-thumb" style="position: absolute; top: 50%; left: ' + pct + "%; "
|
|
250
|
+
'transform: translate(-50%, -50%)"></div>'
|
|
251
|
+
"</div>"
|
|
252
|
+
'<span class="nb-widget-value">' + esc(_fmt_value(state.value)) + "</span>"
|
|
253
|
+
)
|
|
254
|
+
return _row(esc(state.description), control)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _toggle_row(state: _WidgetState) -> str:
|
|
258
|
+
selected = _fmt_value(state.value)
|
|
259
|
+
options = state.options if state.options else ([selected] if selected else [])
|
|
260
|
+
segments: list[str] = []
|
|
261
|
+
for option in options:
|
|
262
|
+
cls = "nb-toggle-seg is-active" if _fmt_value(option) == selected else "nb-toggle-seg"
|
|
263
|
+
segments.append('<span class="' + cls + '">' + esc(str(option)) + "</span>")
|
|
264
|
+
control = '<div class="nb-toggle">' + "".join(segments) + "</div>"
|
|
265
|
+
return _row(esc(state.description), control)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _button_row(state: _WidgetState) -> str:
|
|
269
|
+
# A Button carries its own label; the left description column stays empty for column alignment.
|
|
270
|
+
text = state.description or "run"
|
|
271
|
+
control = '<button type="button" class="nb-widget-btn">' + esc(text) + "</button>"
|
|
272
|
+
return _row("", control)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _checkbox_row(state: _WidgetState) -> str:
|
|
276
|
+
checked = bool(state.value)
|
|
277
|
+
cls = "nb-checkbox is-checked" if checked else "nb-checkbox"
|
|
278
|
+
control = '<span class="' + cls + '">' + (_CHECK_TICK if checked else "") + "</span>"
|
|
279
|
+
return _row(esc(state.description), control)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _placeholder_row(state: _WidgetState | None) -> str:
|
|
283
|
+
"""Dimmed stand-in for an absent or unrecognized widget: a labeled, empty control bar."""
|
|
284
|
+
label = state.label if state is not None and state.label else "widget"
|
|
285
|
+
control = (
|
|
286
|
+
'<div class="nb-slider-track" style="position: relative">'
|
|
287
|
+
'<div class="nb-slider-fill" style="width: 0%"></div>'
|
|
288
|
+
"</div>"
|
|
289
|
+
)
|
|
290
|
+
return _row(esc(label), control)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def render_widget(out: Output, ctx: RenderContext) -> str:
|
|
294
|
+
"""E25 · ipywidgets — frozen slider/toggle/button/checkbox state, or a dimmed placeholder.
|
|
295
|
+
|
|
296
|
+
State is recovered from the output alone (embedded view ``state`` object or the text/plain
|
|
297
|
+
repr); an unrecognized model degrades to the placeholder. ``static``/``embed`` add
|
|
298
|
+
``is-frozen`` so the shared rule dims the controls and blocks pointer events — no kernel to
|
|
299
|
+
drive them; ``connected`` leaves them live.
|
|
300
|
+
"""
|
|
301
|
+
state = _resolve_state(out)
|
|
302
|
+
if state is None:
|
|
303
|
+
row = _placeholder_row(None)
|
|
304
|
+
elif state.kind == "slider":
|
|
305
|
+
row = _slider_row(state)
|
|
306
|
+
elif state.kind == "toggle":
|
|
307
|
+
row = _toggle_row(state)
|
|
308
|
+
elif state.kind == "button":
|
|
309
|
+
row = _button_row(state)
|
|
310
|
+
elif state.kind == "checkbox":
|
|
311
|
+
row = _checkbox_row(state)
|
|
312
|
+
else: # recognized-but-undrawn (output) or unrecognized model
|
|
313
|
+
row = _placeholder_row(state)
|
|
314
|
+
|
|
315
|
+
frozen = " is-frozen" if ctx.mode in ("static", "embed") else ""
|
|
316
|
+
return (
|
|
317
|
+
'<div class="nb-widget' + frozen + '">'
|
|
318
|
+
'<div class="nb-widget-controls">' + row + "</div>"
|
|
319
|
+
"</div>"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def render_stale_banner(ctx: RenderContext, *, message: str) -> str:
|
|
324
|
+
"""E26 · stale-output banner — dot, frozen-state message, cobalt text (no action in static).
|
|
325
|
+
|
|
326
|
+
``document`` shows this only in ``static``/``embed`` (the paths with no kernel), so the cobalt
|
|
327
|
+
affordance renders inert: a ``span``, never a link or button. ``message`` is caller-supplied
|
|
328
|
+
and escaped at interpolation.
|
|
329
|
+
"""
|
|
330
|
+
return (
|
|
331
|
+
'<div class="nb-stale">'
|
|
332
|
+
'<span class="nb-stale-dot"></span>'
|
|
333
|
+
'<span class="nb-stale-msg">' + esc(message) + "</span>"
|
|
334
|
+
'<span class="nb-stale-spacer"></span>'
|
|
335
|
+
'<span class="nb-stale-action">' + esc(_STALE_ACTION) + "</span>"
|
|
336
|
+
"</div>"
|
|
337
|
+
)
|