dataface 0.1.2__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 (455) hide show
  1. d3_format/__init__.py +14 -0
  2. d3_format/errors.py +19 -0
  3. d3_format/format.py +551 -0
  4. d3_format/spec.py +159 -0
  5. dataface/DATAFACE_SYNTAX.md +1135 -0
  6. dataface/__init__.py +93 -0
  7. dataface/_docs_site.py +20 -0
  8. dataface/_install_hint.py +26 -0
  9. dataface/agent_api/__init__.py +79 -0
  10. dataface/agent_api/_init_templates/__init__.py +0 -0
  11. dataface/agent_api/_init_templates/agents_dft_snippet.md +26 -0
  12. dataface/agent_api/_init_templates/dataface.yml +15 -0
  13. dataface/agent_api/_init_templates/faces-dataface.yml +144 -0
  14. dataface/agent_api/_init_templates/index.md +24 -0
  15. dataface/agent_api/_paths.py +118 -0
  16. dataface/agent_api/_project_agents_md.py +43 -0
  17. dataface/agent_api/_session_store.py +486 -0
  18. dataface/agent_api/_state.py +28 -0
  19. dataface/agent_api/chat.py +221 -0
  20. dataface/agent_api/dashboards.py +257 -0
  21. dataface/agent_api/describe.py +366 -0
  22. dataface/agent_api/describe_query.py +120 -0
  23. dataface/agent_api/docs/__init__.py +25 -0
  24. dataface/agent_api/docs/_loader.py +292 -0
  25. dataface/agent_api/docs/yaml-reference.md +2757 -0
  26. dataface/agent_api/file_refs.py +118 -0
  27. dataface/agent_api/init.py +126 -0
  28. dataface/agent_api/inspect.py +128 -0
  29. dataface/agent_api/mcp_install.py +170 -0
  30. dataface/agent_api/query.py +274 -0
  31. dataface/agent_api/schema.py +658 -0
  32. dataface/agent_api/schema_search.py +284 -0
  33. dataface/agent_api/search.py +270 -0
  34. dataface/agent_api/skill_install.py +141 -0
  35. dataface/agent_api/skill_render.py +90 -0
  36. dataface/agent_api/skills.py +293 -0
  37. dataface/agent_api/surface_aliases.yaml +128 -0
  38. dataface/agent_api/validate.py +175 -0
  39. dataface/agent_api/validate_query.py +84 -0
  40. dataface/ai/__init__.py +39 -0
  41. dataface/ai/agent.py +139 -0
  42. dataface/ai/context.py +45 -0
  43. dataface/ai/events.py +62 -0
  44. dataface/ai/external_mcp.py +610 -0
  45. dataface/ai/generate_sql.py +96 -0
  46. dataface/ai/llm.py +403 -0
  47. dataface/ai/mcp/__init__.py +51 -0
  48. dataface/ai/mcp/server.py +289 -0
  49. dataface/ai/memories.py +85 -0
  50. dataface/ai/prompts.py +177 -0
  51. dataface/ai/schema_context.py +138 -0
  52. dataface/ai/skills/before-after-comparison/SKILL.md +102 -0
  53. dataface/ai/skills/before-after-comparison/examples/before-after-comparison.yml +24 -0
  54. dataface/ai/skills/dashboard-build/SKILL.md +212 -0
  55. dataface/ai/skills/dashboard-build/examples/_smoke.yml +15 -0
  56. dataface/ai/skills/dashboard-design/SKILL.md +182 -0
  57. dataface/ai/skills/dashboard-review/SKILL.md +113 -0
  58. dataface/ai/skills/dashboard-structural-review/SKILL.md +173 -0
  59. dataface/ai/skills/dashboard-visual-review/SKILL.md +139 -0
  60. dataface/ai/skills/dataface-mcp-setup/SKILL.md +177 -0
  61. dataface/ai/skills/dataface-troubleshooting/SKILL.md +225 -0
  62. dataface/ai/skills/drill-down-link/SKILL.md +112 -0
  63. dataface/ai/skills/drill-down-link/examples/drill-down-link.yml +27 -0
  64. dataface/ai/skills/faceted-small-multiples/SKILL.md +116 -0
  65. dataface/ai/skills/faceted-small-multiples/examples/faceted-small-multiples.yml +33 -0
  66. dataface/ai/skills/filter-bar-with-variables/SKILL.md +105 -0
  67. dataface/ai/skills/filter-bar-with-variables/examples/filter-bar-with-variables.yml +49 -0
  68. dataface/ai/skills/kpi-row/SKILL.md +101 -0
  69. dataface/ai/skills/kpi-row/examples/kpi-row.yml +55 -0
  70. dataface/ai/skills/report-design/SKILL.md +184 -0
  71. dataface/ai/skills/single-metric-bignum/SKILL.md +90 -0
  72. dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +27 -0
  73. dataface/ai/skills/table-heavy-ops-dashboard/SKILL.md +114 -0
  74. dataface/ai/skills/table-heavy-ops-dashboard/examples/table-heavy-ops-dashboard.yml +48 -0
  75. dataface/ai/skills/time-series-trend/SKILL.md +93 -0
  76. dataface/ai/skills/time-series-trend/examples/time-series-trend.yml +26 -0
  77. dataface/ai/skills/top-n-with-detail/SKILL.md +98 -0
  78. dataface/ai/skills/top-n-with-detail/examples/top-n-with-detail.yml +45 -0
  79. dataface/ai/skills/two-by-two-grid-overview/SKILL.md +78 -0
  80. dataface/ai/skills/two-by-two-grid-overview/examples/two-by-two-grid-overview.yml +64 -0
  81. dataface/ai/tool_schemas.py +132 -0
  82. dataface/ai/tools/__init__.py +312 -0
  83. dataface/ai/yaml_utils.py +57 -0
  84. dataface/cli/__init__.py +3 -0
  85. dataface/cli/_console.py +48 -0
  86. dataface/cli/_error_format.py +83 -0
  87. dataface/cli/_extras.py +190 -0
  88. dataface/cli/_json_output.py +8 -0
  89. dataface/cli/_parsing.py +17 -0
  90. dataface/cli/_version_info.py +56 -0
  91. dataface/cli/commands/__init__.py +3 -0
  92. dataface/cli/commands/_agent_input.py +205 -0
  93. dataface/cli/commands/_agent_server.py +115 -0
  94. dataface/cli/commands/chat.py +645 -0
  95. dataface/cli/commands/describe.py +107 -0
  96. dataface/cli/commands/docs.py +131 -0
  97. dataface/cli/commands/extension.py +179 -0
  98. dataface/cli/commands/init.py +240 -0
  99. dataface/cli/commands/inspect.py +94 -0
  100. dataface/cli/commands/mcp_init.py +167 -0
  101. dataface/cli/commands/query.py +386 -0
  102. dataface/cli/commands/render.py +291 -0
  103. dataface/cli/commands/schema.py +411 -0
  104. dataface/cli/commands/search.py +49 -0
  105. dataface/cli/commands/serve.py +114 -0
  106. dataface/cli/commands/skills.py +133 -0
  107. dataface/cli/commands/skills_init.py +161 -0
  108. dataface/cli/commands/validate.py +63 -0
  109. dataface/cli/main.py +1501 -0
  110. dataface/core/__init__.py +75 -0
  111. dataface/core/compile/__init__.py +244 -0
  112. dataface/core/compile/_jinja_helpers.py +78 -0
  113. dataface/core/compile/channel.py +222 -0
  114. dataface/core/compile/chart_focus.py +101 -0
  115. dataface/core/compile/chart_resolved.py +169 -0
  116. dataface/core/compile/chart_type_detection.py +489 -0
  117. dataface/core/compile/chart_update.py +261 -0
  118. dataface/core/compile/colors.py +64 -0
  119. dataface/core/compile/compiler.py +904 -0
  120. dataface/core/compile/config.py +823 -0
  121. dataface/core/compile/custom_chart_types.py +208 -0
  122. dataface/core/compile/data_table_attachment.py +1287 -0
  123. dataface/core/compile/detect.py +110 -0
  124. dataface/core/compile/errors.py +302 -0
  125. dataface/core/compile/filter_injection.py +319 -0
  126. dataface/core/compile/introspection.py +527 -0
  127. dataface/core/compile/jinja.py +511 -0
  128. dataface/core/compile/labels_env.py +52 -0
  129. dataface/core/compile/markdown.py +154 -0
  130. dataface/core/compile/meta.py +388 -0
  131. dataface/core/compile/models/__init__.py +0 -0
  132. dataface/core/compile/models/chart/__init__.py +0 -0
  133. dataface/core/compile/models/chart/authored.py +2137 -0
  134. dataface/core/compile/models/chart/compiled.py +398 -0
  135. dataface/core/compile/models/config.py +347 -0
  136. dataface/core/compile/models/face/__init__.py +0 -0
  137. dataface/core/compile/models/face/authored.py +659 -0
  138. dataface/core/compile/models/face/compiled.py +522 -0
  139. dataface/core/compile/models/factories.py +201 -0
  140. dataface/core/compile/models/markers.py +40 -0
  141. dataface/core/compile/models/palette.py +36 -0
  142. dataface/core/compile/models/primitives.py +415 -0
  143. dataface/core/compile/models/query/__init__.py +0 -0
  144. dataface/core/compile/models/query/authored.py +246 -0
  145. dataface/core/compile/models/query/compiled.py +710 -0
  146. dataface/core/compile/models/refs.py +137 -0
  147. dataface/core/compile/models/source.py +611 -0
  148. dataface/core/compile/models/style/__init__.py +0 -0
  149. dataface/core/compile/models/style/authored.py +481 -0
  150. dataface/core/compile/models/style/compiled.py +3399 -0
  151. dataface/core/compile/models/style/merged.py +1682 -0
  152. dataface/core/compile/models/theme.py +362 -0
  153. dataface/core/compile/models/variable/__init__.py +0 -0
  154. dataface/core/compile/models/variable/authored.py +254 -0
  155. dataface/core/compile/models/vega_lite/__init__.py +0 -0
  156. dataface/core/compile/models/vega_lite/config.py +510 -0
  157. dataface/core/compile/models/vega_lite/contracts.py +171 -0
  158. dataface/core/compile/normalize_charts.py +494 -0
  159. dataface/core/compile/normalize_layout.py +1000 -0
  160. dataface/core/compile/normalize_queries.py +297 -0
  161. dataface/core/compile/normalize_variables.py +489 -0
  162. dataface/core/compile/normalizer.py +543 -0
  163. dataface/core/compile/palette.py +1100 -0
  164. dataface/core/compile/parameterized.py +658 -0
  165. dataface/core/compile/parser.py +228 -0
  166. dataface/core/compile/schema.py +20 -0
  167. dataface/core/compile/schema_renderers/__init__.py +0 -0
  168. dataface/core/compile/schema_renderers/json_schema.py +163 -0
  169. dataface/core/compile/schema_renderers/prompt.py +152 -0
  170. dataface/core/compile/schema_renderers/vscode_schema.py +301 -0
  171. dataface/core/compile/sizing.py +2126 -0
  172. dataface/core/compile/sources.py +518 -0
  173. dataface/core/compile/sql_authoring_lint.py +56 -0
  174. dataface/core/compile/style_cascade.py +471 -0
  175. dataface/core/compile/typography.py +299 -0
  176. dataface/core/compile/validator.py +301 -0
  177. dataface/core/compile/variables.py +53 -0
  178. dataface/core/compile/vega_config.py +98 -0
  179. dataface/core/compile/vega_lite/__init__.py +6 -0
  180. dataface/core/compile/vega_lite/validation.py +95 -0
  181. dataface/core/compile/yaml_error_formatter.py +838 -0
  182. dataface/core/connections.py +38 -0
  183. dataface/core/dashboard.py +358 -0
  184. dataface/core/defaults/default_config.yml +101 -0
  185. dataface/core/defaults/palettes/categorical/category-10-dark.yml +32 -0
  186. dataface/core/defaults/palettes/categorical/category-10-light.yml +43 -0
  187. dataface/core/defaults/palettes/categorical/category-10.yml +31 -0
  188. dataface/core/defaults/palettes/categorical/category-6-tonal-blue.yml +22 -0
  189. dataface/core/defaults/palettes/categorical/category-6-tonal-brown.yml +29 -0
  190. dataface/core/defaults/palettes/categorical/category-6-tonal-green.yml +20 -0
  191. dataface/core/defaults/palettes/categorical/category-6-tonal-orange.yml +21 -0
  192. dataface/core/defaults/palettes/categorical/category-6-tonal-purple.yml +20 -0
  193. dataface/core/defaults/palettes/categorical/editorial-10-dark.yml +32 -0
  194. dataface/core/defaults/palettes/categorical/editorial-10.yml +40 -0
  195. dataface/core/defaults/palettes/categorical/hero-6.yml +17 -0
  196. dataface/core/defaults/palettes/categorical/single-blue.yml +11 -0
  197. dataface/core/defaults/palettes/categorical/tableau.yml +20 -0
  198. dataface/core/defaults/palettes/data/xkcd_colors.json +3803 -0
  199. dataface/core/defaults/palettes/diverging/blue-red.yml +25 -0
  200. dataface/core/defaults/palettes/diverging/coolwarm.yml +24 -0
  201. dataface/core/defaults/palettes/diverging/crimson-green.yml +23 -0
  202. dataface/core/defaults/palettes/diverging/orange-teal.yml +23 -0
  203. dataface/core/defaults/palettes/diverging/sunset.yml +24 -0
  204. dataface/core/defaults/palettes/scaffold/dft-creams.yml +38 -0
  205. dataface/core/defaults/palettes/scaffold/dft-grays.yml +53 -0
  206. dataface/core/defaults/palettes/sequential/amber.yml +22 -0
  207. dataface/core/defaults/palettes/sequential/blue.yml +22 -0
  208. dataface/core/defaults/palettes/sequential/brown.yml +22 -0
  209. dataface/core/defaults/palettes/sequential/gray.yml +22 -0
  210. dataface/core/defaults/palettes/sequential/green.yml +22 -0
  211. dataface/core/defaults/palettes/sequential/purple.yml +22 -0
  212. dataface/core/defaults/palettes/sequential/rust.yml +22 -0
  213. dataface/core/defaults/palettes/sequential/teal.yml +22 -0
  214. dataface/core/defaults/palettes/tone/negative.yml +32 -0
  215. dataface/core/defaults/palettes/tone/positive.yml +22 -0
  216. dataface/core/defaults/palettes/tone/warning.yml +22 -0
  217. dataface/core/defaults/themes/_base.yaml +786 -0
  218. dataface/core/defaults/themes/bi.yaml +16 -0
  219. dataface/core/defaults/themes/carbong100.yaml +41 -0
  220. dataface/core/defaults/themes/cream.yaml +122 -0
  221. dataface/core/defaults/themes/dark.yaml +40 -0
  222. dataface/core/defaults/themes/diagnostics-title-angle-extreme.yaml +9 -0
  223. dataface/core/defaults/themes/diagnostics-title-baseline-extreme.yaml +9 -0
  224. dataface/core/defaults/themes/diagnostics-title-baseline.yaml +24 -0
  225. dataface/core/defaults/themes/diagnostics-title-center.yaml +8 -0
  226. dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +24 -0
  227. dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +25 -0
  228. dataface/core/defaults/themes/diagnostics-title-left.yaml +8 -0
  229. dataface/core/defaults/themes/diagnostics-title-offset-extreme.yaml +9 -0
  230. dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +24 -0
  231. dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +24 -0
  232. dataface/core/defaults/themes/editorial.yaml +147 -0
  233. dataface/core/defaults/themes/light.yaml +30 -0
  234. dataface/core/defaults/themes/looker.yaml +17 -0
  235. dataface/core/defaults/themes/stark.yaml +134 -0
  236. dataface/core/errors/__init__.py +67 -0
  237. dataface/core/errors/codes_compile.py +56 -0
  238. dataface/core/errors/codes_execute.py +177 -0
  239. dataface/core/errors/codes_render.py +106 -0
  240. dataface/core/errors/codes_unknown.py +15 -0
  241. dataface/core/errors/hints.py +74 -0
  242. dataface/core/errors/registry.py +42 -0
  243. dataface/core/errors/structured.py +92 -0
  244. dataface/core/execute/__init__.py +91 -0
  245. dataface/core/execute/adapters/__init__.py +49 -0
  246. dataface/core/execute/adapters/adapter_registry.py +400 -0
  247. dataface/core/execute/adapters/base.py +245 -0
  248. dataface/core/execute/adapters/csv_adapter.py +239 -0
  249. dataface/core/execute/adapters/dbt_adapter.py +283 -0
  250. dataface/core/execute/adapters/dbt_adapter_factory.py +212 -0
  251. dataface/core/execute/adapters/dbt_macro_loader.py +95 -0
  252. dataface/core/execute/adapters/dbt_utils.py +150 -0
  253. dataface/core/execute/adapters/http_adapter.py +224 -0
  254. dataface/core/execute/adapters/metricflow_adapter.py +94 -0
  255. dataface/core/execute/adapters/schema_resolver_adapter.py +144 -0
  256. dataface/core/execute/adapters/sql_adapter.py +710 -0
  257. dataface/core/execute/adapters/values_adapter.py +58 -0
  258. dataface/core/execute/batch.py +744 -0
  259. dataface/core/execute/cache_backend.py +135 -0
  260. dataface/core/execute/cache_keys.py +66 -0
  261. dataface/core/execute/dbt_jinja.py +21 -0
  262. dataface/core/execute/dialects/__init__.py +121 -0
  263. dataface/core/execute/dialects/athena.py +75 -0
  264. dataface/core/execute/dialects/base.py +302 -0
  265. dataface/core/execute/dialects/bigquery.py +38 -0
  266. dataface/core/execute/dialects/databricks.py +68 -0
  267. dataface/core/execute/dialects/duckdb.py +35 -0
  268. dataface/core/execute/dialects/mysql.py +68 -0
  269. dataface/core/execute/dialects/postgres.py +39 -0
  270. dataface/core/execute/dialects/redshift.py +12 -0
  271. dataface/core/execute/dialects/snowflake.py +51 -0
  272. dataface/core/execute/dialects/sqlserver.py +92 -0
  273. dataface/core/execute/duckdb_cache.py +712 -0
  274. dataface/core/execute/duckdb_config.py +26 -0
  275. dataface/core/execute/errors.py +213 -0
  276. dataface/core/execute/executor.py +1249 -0
  277. dataface/core/execute/parallel.py +162 -0
  278. dataface/core/execute/setup_sql.py +58 -0
  279. dataface/core/execute/source_registry.py +72 -0
  280. dataface/core/execute/source_resolver.py +255 -0
  281. dataface/core/execute/sql_guard.py +387 -0
  282. dataface/core/execute/sql_literals.py +199 -0
  283. dataface/core/fonts.py +52 -0
  284. dataface/core/inspect/__init__.py +32 -0
  285. dataface/core/inspect/cache_factory.py +98 -0
  286. dataface/core/inspect/db_types.py +162 -0
  287. dataface/core/inspect/dbt_schema.py +96 -0
  288. dataface/core/inspect/defaults.yml +37 -0
  289. dataface/core/inspect/fanout_risk.py +109 -0
  290. dataface/core/inspect/manifest_utils.py +77 -0
  291. dataface/core/inspect/partials/categorical.yml +40 -0
  292. dataface/core/inspect/partials/date.yml +40 -0
  293. dataface/core/inspect/partials/numeric.yml +55 -0
  294. dataface/core/inspect/partition_types.py +38 -0
  295. dataface/core/inspect/query_validator.py +975 -0
  296. dataface/core/inspect/renderer.py +354 -0
  297. dataface/core/inspect/resolver.py +808 -0
  298. dataface/core/inspect/search.py +461 -0
  299. dataface/core/inspect/sources/__init__.py +32 -0
  300. dataface/core/inspect/sources/dbt.py +738 -0
  301. dataface/core/inspect/sources/duckdb_utils.py +66 -0
  302. dataface/core/inspect/templates/__init__.py +1 -0
  303. dataface/core/inspect/templates/categorical_column.yml +196 -0
  304. dataface/core/inspect/templates/charts.yml +109 -0
  305. dataface/core/inspect/templates/date_column.yml +248 -0
  306. dataface/core/inspect/templates/model.yml +138 -0
  307. dataface/core/inspect/templates/numeric_column.yml +261 -0
  308. dataface/core/inspect/templates/quality.yml +80 -0
  309. dataface/core/inspect/templates/string_column.yml +263 -0
  310. dataface/core/project_roots.py +165 -0
  311. dataface/core/render/__init__.py +87 -0
  312. dataface/core/render/board_links.py +176 -0
  313. dataface/core/render/chart/__init__.py +27 -0
  314. dataface/core/render/chart/arc_attached_table.py +251 -0
  315. dataface/core/render/chart/artifacts.py +16 -0
  316. dataface/core/render/chart/callout.py +225 -0
  317. dataface/core/render/chart/decisions.py +358 -0
  318. dataface/core/render/chart/geo.py +700 -0
  319. dataface/core/render/chart/kpi.py +916 -0
  320. dataface/core/render/chart/labels.py +76 -0
  321. dataface/core/render/chart/pipeline.py +818 -0
  322. dataface/core/render/chart/presentation.py +36 -0
  323. dataface/core/render/chart/profile.py +3438 -0
  324. dataface/core/render/chart/render_single.py +347 -0
  325. dataface/core/render/chart/renderers.py +193 -0
  326. dataface/core/render/chart/rendering.py +565 -0
  327. dataface/core/render/chart/serialization.py +90 -0
  328. dataface/core/render/chart/spark.py +496 -0
  329. dataface/core/render/chart/spark_bar.py +370 -0
  330. dataface/core/render/chart/spec_builders.py +154 -0
  331. dataface/core/render/chart/standard_renderer.py +2645 -0
  332. dataface/core/render/chart/table.py +2957 -0
  333. dataface/core/render/chart/table_support.py +1452 -0
  334. dataface/core/render/chart/tick_values.py +66 -0
  335. dataface/core/render/chart/time_unit_detect.py +809 -0
  336. dataface/core/render/chart/title_overflow.py +157 -0
  337. dataface/core/render/chart/type_inference.py +122 -0
  338. dataface/core/render/chart/validation.py +99 -0
  339. dataface/core/render/chart/vega_lite.py +125 -0
  340. dataface/core/render/chart/vega_lite_types.py +268 -0
  341. dataface/core/render/chart/vl_field_maps.py +346 -0
  342. dataface/core/render/chart_interactivity.py +24 -0
  343. dataface/core/render/control_registry.py +287 -0
  344. dataface/core/render/converters/__init__.py +24 -0
  345. dataface/core/render/converters/chart.py +276 -0
  346. dataface/core/render/converters/html.py +98 -0
  347. dataface/core/render/converters/pdf.py +40 -0
  348. dataface/core/render/converters/png.py +41 -0
  349. dataface/core/render/errors.py +144 -0
  350. dataface/core/render/face_api.py +160 -0
  351. dataface/core/render/faces.py +1194 -0
  352. dataface/core/render/font_measurement.py +48 -0
  353. dataface/core/render/font_support.py +197 -0
  354. dataface/core/render/fonts/DFTSansTabular-Regular.ttf +0 -0
  355. dataface/core/render/fonts/DFTSansTabular-Regular.woff2 +0 -0
  356. dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.ttf +0 -0
  357. dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.ttf +0 -0
  358. dataface/core/render/fonts/InterVariable.ttf +0 -0
  359. dataface/core/render/fonts/InterVariable.woff2 +0 -0
  360. dataface/core/render/fonts/NOTO_COLOR_EMOJI_LICENSE.txt +93 -0
  361. dataface/core/render/fonts/NOTO_EMOJI_LICENSE.txt +93 -0
  362. dataface/core/render/fonts/NotoColorEmoji-Regular.ttf +0 -0
  363. dataface/core/render/fonts/NotoColorEmoji-Regular.woff2 +0 -0
  364. dataface/core/render/fonts/NotoEmoji-Regular.ttf +0 -0
  365. dataface/core/render/fonts/NotoEmoji-Regular.woff2 +0 -0
  366. dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -0
  367. dataface/core/render/fonts/SOURCE_SERIF_4_LICENSE.txt +98 -0
  368. dataface/core/render/fonts/SourceCodePro-Regular.ttf +0 -0
  369. dataface/core/render/fonts/SourceSerif4-Regular.ttf +0 -0
  370. dataface/core/render/fonts/_emoji_font_face.css +43 -0
  371. dataface/core/render/fonts/source-serif-4-variable-latin.woff2 +0 -0
  372. dataface/core/render/format_utils.py +329 -0
  373. dataface/core/render/geo_defaults.yml +28 -0
  374. dataface/core/render/json_format.py +146 -0
  375. dataface/core/render/layout_sizing.py +865 -0
  376. dataface/core/render/layouts.py +541 -0
  377. dataface/core/render/markdown_defaults.yml +16 -0
  378. dataface/core/render/missing_vars_prompt.py +79 -0
  379. dataface/core/render/placeholder.py +389 -0
  380. dataface/core/render/render_result.py +14 -0
  381. dataface/core/render/renderer.py +467 -0
  382. dataface/core/render/script_embedding.py +16 -0
  383. dataface/core/render/svg_utils.py +212 -0
  384. dataface/core/render/template_loader.py +69 -0
  385. dataface/core/render/templates/controls/_styles.css +606 -0
  386. dataface/core/render/templates/controls/checkbox.html +16 -0
  387. dataface/core/render/templates/controls/date.html +16 -0
  388. dataface/core/render/templates/controls/number.html +19 -0
  389. dataface/core/render/templates/controls/readonly.html +9 -0
  390. dataface/core/render/templates/controls/select.html +21 -0
  391. dataface/core/render/templates/controls/slider.html +22 -0
  392. dataface/core/render/templates/controls/text.html +16 -0
  393. dataface/core/render/templates/scripts/chart_interactivity.js +191 -0
  394. dataface/core/render/templates/scripts/variables.js +976 -0
  395. dataface/core/render/templates/svg/grid_pattern.svg +3 -0
  396. dataface/core/render/templates/svg/styles.css +51 -0
  397. dataface/core/render/terminal.py +311 -0
  398. dataface/core/render/terminal_charts.py +563 -0
  399. dataface/core/render/terminal_defaults.yml +2 -0
  400. dataface/core/render/terminal_layouts.py +299 -0
  401. dataface/core/render/terminal_text.py +31 -0
  402. dataface/core/render/text/__init__.py +1 -0
  403. dataface/core/render/text/case.py +113 -0
  404. dataface/core/render/text_format.py +129 -0
  405. dataface/core/render/utils.py +106 -0
  406. dataface/core/render/variable_controls.py +946 -0
  407. dataface/core/render/variable_input_refinement.py +140 -0
  408. dataface/core/render/warnings/__init__.py +15 -0
  409. dataface/core/render/warnings/bar_color_1_to_1_with_x.py +80 -0
  410. dataface/core/render/warnings/base.py +44 -0
  411. dataface/core/render/warnings/fanout_risk.py +15 -0
  412. dataface/core/render/warnings/from_query_diagnostic.py +56 -0
  413. dataface/core/render/warnings/missing_join_predicate.py +13 -0
  414. dataface/core/render/warnings/query_parse_error.py +14 -0
  415. dataface/core/render/warnings/query_returned_zero_rows.py +42 -0
  416. dataface/core/render/warnings/reaggregation.py +14 -0
  417. dataface/core/render/warnings/registry.py +45 -0
  418. dataface/core/render/warnings/suppression.py +46 -0
  419. dataface/core/render/warnings/temporal_single_point.py +63 -0
  420. dataface/core/render/warnings/unreferenced_chart.py +15 -0
  421. dataface/core/render/warnings/y_encoding_mostly_null.py +76 -0
  422. dataface/core/render/yaml_format.py +167 -0
  423. dataface/core/resolve_face.py +195 -0
  424. dataface/core/schema/__init__.py +0 -0
  425. dataface/core/schema/guidance.py +151 -0
  426. dataface/core/scoped_paths.py +59 -0
  427. dataface/core/serve/__init__.py +14 -0
  428. dataface/core/serve/bootstrap.py +39 -0
  429. dataface/core/serve/embedded.py +57 -0
  430. dataface/core/serve/port.py +129 -0
  431. dataface/core/serve/server.py +938 -0
  432. dataface/core/serve/templates/__init__.py +0 -0
  433. dataface/core/serve/templates/directory.yml +6 -0
  434. dataface/core/serve/templates/error.html.j2 +217 -0
  435. dataface/core/utils.py +121 -0
  436. dataface/core/validate.py +64 -0
  437. dataface/integrations/__init__.py +0 -0
  438. dataface/integrations/highlighting.py +351 -0
  439. dataface/integrations/markdown.py +537 -0
  440. dataface/py.typed +0 -0
  441. dataface-0.1.2.dist-info/METADATA +375 -0
  442. dataface-0.1.2.dist-info/RECORD +455 -0
  443. dataface-0.1.2.dist-info/WHEEL +4 -0
  444. dataface-0.1.2.dist-info/entry_points.txt +2 -0
  445. dataface-0.1.2.dist-info/licenses/LICENSE +202 -0
  446. mdsvg/__init__.py +168 -0
  447. mdsvg/fonts.py +656 -0
  448. mdsvg/images.py +299 -0
  449. mdsvg/parser.py +629 -0
  450. mdsvg/playground.py +284 -0
  451. mdsvg/py.typed +2 -0
  452. mdsvg/renderer.py +1623 -0
  453. mdsvg/style.py +355 -0
  454. mdsvg/types.py +200 -0
  455. mdsvg/utils.py +86 -0
mdsvg/style.py ADDED
@@ -0,0 +1,355 @@
1
+ """Style configuration for SVG rendering."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, replace
6
+ from typing import Any, Literal, Optional
7
+
8
+ # Code block overflow options
9
+ CodeBlockOverflow = Literal["wrap", "show", "hide", "ellipsis"]
10
+
11
+ # Text alignment options
12
+ TextAlign = Literal["left", "center", "right"]
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class Style:
17
+ """
18
+ Configuration for styling rendered SVG output.
19
+
20
+ All style properties are immutable. Use `with_updates()` to create
21
+ a modified copy of a style.
22
+
23
+ Attributes:
24
+ font_family: Primary font stack for body text.
25
+ mono_font_family: Font stack for code elements.
26
+ base_font_size: Base font size in pixels.
27
+ line_height: Line height multiplier.
28
+ text_color: Default text color (CSS color value).
29
+ heading_color: Color for headings (defaults to text_color if None).
30
+ link_color: Color for links.
31
+ link_underline: Whether to underline links.
32
+ code_color: Color for inline code text.
33
+ code_background: Background color for code elements.
34
+ blockquote_color: Color for blockquote text.
35
+ blockquote_border_color: Color for blockquote left border.
36
+ h1_scale: Font size multiplier for h1 (used when h1_size is None).
37
+ h2_scale: Font size multiplier for h2 (used when h2_size is None).
38
+ h3_scale: Font size multiplier for h3 (used when h3_size is None).
39
+ h4_scale: Font size multiplier for h4 (used when h4_size is None).
40
+ h5_scale: Font size multiplier for h5 (used when h5_size is None).
41
+ h6_scale: Font size multiplier for h6 (used when h6_size is None).
42
+ h1_size: Absolute h1 font size in px. Overrides h1_scale × base_font_size.
43
+ h2_size: Absolute h2 font size in px. Overrides h2_scale × base_font_size.
44
+ h3_size: Absolute h3 font size in px. Overrides h3_scale × base_font_size.
45
+ h4_size: Absolute h4 font size in px. Overrides h4_scale × base_font_size.
46
+ h5_size: Absolute h5 font size in px. Overrides h5_scale × base_font_size.
47
+ h6_size: Absolute h6 font size in px. Overrides h6_scale × base_font_size.
48
+ heading_font_weight: Font weight for headings.
49
+ heading_line_height: Line height multiplier for headings; falls back
50
+ to ``line_height`` when None. Headings typically want a tighter
51
+ multiplier than body text (~1.1-1.25 vs 1.5-1.6).
52
+ heading_margin_top: Top margin for headings (em units).
53
+ heading_margin_bottom: Bottom margin for headings (em units).
54
+ paragraph_spacing: Space between paragraphs in pixels.
55
+ list_indent: Indentation for list items in pixels.
56
+ list_item_spacing: Space between list items in pixels.
57
+ code_block_padding: Padding inside code blocks in pixels.
58
+ code_block_border_radius: Border radius for code blocks in pixels.
59
+ blockquote_padding: Left padding for blockquotes in pixels.
60
+ blockquote_border_width: Width of blockquote left border in pixels.
61
+ table_border_color: Color for table borders.
62
+ table_header_background: Background color for table headers.
63
+ table_cell_padding: Padding inside table cells in pixels.
64
+ hr_color: Color for horizontal rules.
65
+ hr_height: Height of horizontal rules in pixels.
66
+ image_width: Fixed image width in pixels (None = full container width).
67
+ image_height: Fixed image height in pixels (None = auto from aspect ratio).
68
+ image_fallback_aspect_ratio: Aspect ratio when dimensions unknown (default 16:9).
69
+ image_enforce_aspect_ratio: Skip fetching dimensions, always use fallback ratio.
70
+ image_preserve_aspect_ratio: SVG preserveAspectRatio attribute (default "xMidYMid meet").
71
+ char_width_ratio: Reference regular-text ratio used to normalize
72
+ bold/italic scaling.
73
+ bold_char_width_ratio: Character width ratio for bold text.
74
+ text_align: Horizontal text alignment ("left", "center", "right").
75
+ """
76
+
77
+ # Fonts
78
+ font_family: str = (
79
+ "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"
80
+ )
81
+ mono_font_family: str = "ui-monospace, 'SF Mono', Menlo, Consolas, monospace"
82
+ base_font_size: float = 14.0
83
+ line_height: float = 1.5
84
+
85
+ # Colors
86
+ text_color: str = "#1a1a1a"
87
+ heading_color: Optional[str] = None # Falls back to text_color
88
+ link_color: str = "#2563eb"
89
+ link_underline: bool = True
90
+ code_color: str = "#be185d"
91
+ code_background: str = "#f3f4f6"
92
+ blockquote_color: str = "#6b7280"
93
+ blockquote_border_color: str = "#d1d5db"
94
+
95
+ # Heading scales (multipliers of base_font_size)
96
+ h1_scale: float = 2.0
97
+ h2_scale: float = 1.6
98
+ h3_scale: float = 1.35
99
+ h4_scale: float = 1.15
100
+ h5_scale: float = 1.0
101
+ h6_scale: float = 0.9
102
+ # Absolute per-level sizes (px). When set, override the scale × base_font_size
103
+ # calculation. Lets a caller pin headings to an absolute type stack.
104
+ h1_size: Optional[float] = None
105
+ h2_size: Optional[float] = None
106
+ h3_size: Optional[float] = None
107
+ h4_size: Optional[float] = None
108
+ h5_size: Optional[float] = None
109
+ h6_size: Optional[float] = None
110
+ heading_font_weight: str | int = "bold"
111
+ # Optional tighter line-height multiplier for headings; None falls back to
112
+ # the body line_height. Body multipliers (1.5-1.6) read too loose between
113
+ # wrapped heading lines.
114
+ heading_line_height: Optional[float] = None
115
+ heading_margin_top: float = 1.5 # em
116
+ heading_margin_bottom: float = 0.5 # em
117
+
118
+ # Spacing
119
+ paragraph_spacing: float = 12.0
120
+ list_indent: float = 24.0
121
+ list_item_spacing: float = 4.0
122
+ code_block_padding: float = 12.0
123
+ code_block_border_radius: float = 4.0
124
+ code_block_overflow: CodeBlockOverflow = "wrap" # wrap, show, hide, ellipsis
125
+ blockquote_padding: float = 16.0
126
+ blockquote_border_width: float = 3.0
127
+
128
+ # Table
129
+ table_border_color: str = "#e5e7eb"
130
+ table_header_background: str = "#f9fafb"
131
+ table_cell_padding: float = 8.0
132
+
133
+ # Horizontal rule
134
+ hr_color: str = "#e5e7eb"
135
+ hr_height: float = 1.0
136
+
137
+ # Images
138
+ image_width: Optional[float] = None # None = full width (100% of container)
139
+ image_height: Optional[float] = (
140
+ None # None = auto (based on fetched/fallback ratio)
141
+ )
142
+ image_fallback_aspect_ratio: float = (
143
+ 16 / 9
144
+ ) # Used when dimensions can't be detected
145
+ image_enforce_aspect_ratio: bool = False # Skip fetching, always use fallback ratio
146
+ image_preserve_aspect_ratio: str = "xMidYMid meet" # SVG preserveAspectRatio attr
147
+
148
+ # Text measurement
149
+ char_width_ratio: float = 0.48 # Reference ratio for bold/italic scaling
150
+ bold_char_width_ratio: float = 0.58 # Wider for bold (~20% wider than regular)
151
+ italic_char_width_ratio: float = 0.52 # Wider for italic (~8% wider than regular)
152
+ # Text alignment
153
+ text_align: TextAlign = "left" # Horizontal alignment: "left", "center", "right"
154
+
155
+ def with_updates(self, **kwargs: Any) -> Style:
156
+ """
157
+ Create a new Style with updated values.
158
+
159
+ Args:
160
+ **kwargs: Style properties to update.
161
+
162
+ Returns:
163
+ A new Style instance with the specified updates.
164
+
165
+ Example:
166
+ >>> style = Style()
167
+ >>> dark_style = style.with_updates(
168
+ ... text_color="#ffffff",
169
+ ... code_background="#1f2937"
170
+ ... )
171
+ """
172
+ return replace(self, **kwargs)
173
+
174
+ def get_heading_scale(self, level: int) -> float:
175
+ """
176
+ Get the font size scale for a heading level.
177
+
178
+ Args:
179
+ level: Heading level (1-6).
180
+
181
+ Returns:
182
+ The font size multiplier for that heading level.
183
+ """
184
+ scales = {
185
+ 1: self.h1_scale,
186
+ 2: self.h2_scale,
187
+ 3: self.h3_scale,
188
+ 4: self.h4_scale,
189
+ 5: self.h5_scale,
190
+ 6: self.h6_scale,
191
+ }
192
+ return scales.get(level, 1.0)
193
+
194
+ def get_heading_size(self, level: int) -> float:
195
+ """
196
+ Get the absolute font size in pixels for a heading level.
197
+
198
+ If the corresponding ``h*_size`` field is set, return it directly.
199
+ Otherwise fall back to ``base_font_size × get_heading_scale(level)``.
200
+
201
+ Args:
202
+ level: Heading level (1-6).
203
+
204
+ Returns:
205
+ Font size in pixels for that heading level.
206
+ """
207
+ sizes = {
208
+ 1: self.h1_size,
209
+ 2: self.h2_size,
210
+ 3: self.h3_size,
211
+ 4: self.h4_size,
212
+ 5: self.h5_size,
213
+ 6: self.h6_size,
214
+ }
215
+ absolute = sizes.get(level)
216
+ if absolute is not None:
217
+ return absolute
218
+ return self.base_font_size * self.get_heading_scale(level)
219
+
220
+ def get_heading_color(self) -> str:
221
+ """Get the color for headings, falling back to text_color."""
222
+ return self.heading_color or self.text_color
223
+
224
+ def get_text_anchor(self) -> str:
225
+ """Get the SVG text-anchor value corresponding to text_align."""
226
+ anchor_map = {
227
+ "left": "start",
228
+ "center": "middle",
229
+ "right": "end",
230
+ }
231
+ return anchor_map.get(self.text_align, "start")
232
+
233
+
234
+ # Pre-built themes
235
+ LIGHT_THEME = Style()
236
+
237
+ DARK_THEME = Style(
238
+ text_color="#e5e7eb",
239
+ heading_color="#f9fafb",
240
+ link_color="#60a5fa",
241
+ code_color="#f472b6",
242
+ code_background="#374151",
243
+ blockquote_color="#9ca3af",
244
+ blockquote_border_color="#4b5563",
245
+ table_border_color="#4b5563",
246
+ table_header_background="#374151",
247
+ hr_color="#4b5563",
248
+ )
249
+
250
+ GITHUB_THEME = Style(
251
+ font_family="-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif",
252
+ mono_font_family="ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace",
253
+ base_font_size=16.0,
254
+ text_color="#1f2328",
255
+ heading_color="#1f2328",
256
+ link_color="#0969da",
257
+ code_color="#1f2328",
258
+ code_background="#f6f8fa",
259
+ blockquote_color="#59636e",
260
+ blockquote_border_color="#d1d9e0",
261
+ )
262
+
263
+
264
+ # Style presets for different use cases
265
+ # These control spacing/margins rather than colors/fonts
266
+
267
+
268
+ class StylePresets:
269
+ """
270
+ Pre-configured style presets optimized for different rendering contexts.
271
+
272
+ Presets adjust spacing and margins while preserving color and font settings.
273
+ Combine with themes for full customization:
274
+
275
+ >>> from mdsvg import render, COMPACT_PRESET, DARK_THEME
276
+ >>> style = COMPACT_PRESET.with_updates(**vars(DARK_THEME))
277
+
278
+ Or use the merge_styles helper:
279
+
280
+ >>> from mdsvg import merge_styles, COMPACT_PRESET, DARK_THEME
281
+ >>> style = merge_styles(COMPACT_PRESET, DARK_THEME)
282
+ """
283
+
284
+ # Document preset: generous whitespace for long-form reading (original defaults)
285
+ DOCUMENT = Style(
286
+ heading_margin_top=1.5, # 1.5em above headings
287
+ heading_margin_bottom=0.5, # 0.5em below headings
288
+ paragraph_spacing=12.0, # 12px between paragraphs
289
+ list_item_spacing=4.0, # 4px between list items
290
+ )
291
+
292
+ # Compact preset: tighter spacing for dashboards, cards, and UI components
293
+ COMPACT = Style(
294
+ heading_margin_top=0.3, # Reduced from 1.5em
295
+ heading_margin_bottom=0.3, # Reduced from 0.5em
296
+ paragraph_spacing=8.0, # Reduced from 12px
297
+ list_item_spacing=3.0, # Slightly tighter list spacing
298
+ )
299
+
300
+ # Minimal preset: very tight spacing for tooltips and constrained spaces
301
+ MINIMAL = Style(
302
+ heading_margin_top=0.1, # Minimal top margin
303
+ heading_margin_bottom=0.1, # Minimal bottom margin
304
+ paragraph_spacing=4.0, # Tight paragraph spacing
305
+ list_item_spacing=2.0, # Tight list spacing
306
+ )
307
+
308
+
309
+ # Export presets as top-level constants for easy access
310
+ DOCUMENT_PRESET = StylePresets.DOCUMENT
311
+ COMPACT_PRESET = StylePresets.COMPACT
312
+ MINIMAL_PRESET = StylePresets.MINIMAL
313
+
314
+
315
+ def merge_styles(*styles: Style) -> Style:
316
+ """
317
+ Merge multiple Style objects, with later styles overriding earlier ones.
318
+
319
+ This is useful for combining presets (spacing) with themes (colors):
320
+
321
+ >>> from mdsvg import merge_styles, COMPACT_PRESET, DARK_THEME
322
+ >>> style = merge_styles(COMPACT_PRESET, DARK_THEME)
323
+
324
+ Only non-default values from each style are applied:
325
+
326
+ >>> base = Style(text_color="red")
327
+ >>> overlay = Style(link_color="blue")
328
+ >>> merged = merge_styles(base, overlay)
329
+ >>> merged.text_color
330
+ 'red'
331
+ >>> merged.link_color
332
+ 'blue'
333
+
334
+ Args:
335
+ *styles: Style objects to merge, in order of priority (later wins).
336
+
337
+ Returns:
338
+ A new Style with merged values.
339
+ """
340
+ if not styles:
341
+ return Style()
342
+
343
+ # Start with default style
344
+ default = Style()
345
+ result_kwargs: dict[str, Any] = {}
346
+
347
+ for style in styles:
348
+ # Get all fields that differ from default
349
+ for field_name in style.__dataclass_fields__:
350
+ style_value = getattr(style, field_name)
351
+ default_value = getattr(default, field_name)
352
+ if style_value != default_value:
353
+ result_kwargs[field_name] = style_value
354
+
355
+ return Style(**result_kwargs)
mdsvg/types.py ADDED
@@ -0,0 +1,200 @@
1
+ """AST node types for representing parsed Markdown."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from enum import Enum
7
+ from typing import List, Optional, Union
8
+
9
+
10
+ class SpanType(Enum):
11
+ """Types of inline text spans."""
12
+
13
+ TEXT = "text"
14
+ BOLD = "bold"
15
+ ITALIC = "italic"
16
+ BOLD_ITALIC = "bold_italic"
17
+ CODE = "code"
18
+ LINK = "link"
19
+ IMAGE = "image"
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class Span:
24
+ """An inline text span with styling."""
25
+
26
+ text: str
27
+ span_type: SpanType = SpanType.TEXT
28
+ url: Optional[str] = None
29
+ title: Optional[str] = None
30
+
31
+ def __post_init__(self) -> None:
32
+ """Validate span configuration."""
33
+ if self.span_type == SpanType.LINK and not self.url:
34
+ raise ValueError("Link spans must have a URL")
35
+ if self.span_type == SpanType.IMAGE and not self.url:
36
+ raise ValueError("Image spans must have a URL")
37
+
38
+
39
+ class BlockType(Enum):
40
+ """Types of block-level elements."""
41
+
42
+ PARAGRAPH = "paragraph"
43
+ HEADING = "heading"
44
+ CODE_BLOCK = "code_block"
45
+ BLOCKQUOTE = "blockquote"
46
+ UNORDERED_LIST = "unordered_list"
47
+ ORDERED_LIST = "ordered_list"
48
+ LIST_ITEM = "list_item"
49
+ HORIZONTAL_RULE = "horizontal_rule"
50
+ TABLE = "table"
51
+ IMAGE = "image"
52
+ HTML_BLOCK = "html_block"
53
+
54
+
55
+ @dataclass(frozen=True)
56
+ class Block:
57
+ """Base class for block-level elements."""
58
+
59
+ block_type: BlockType
60
+
61
+
62
+ @dataclass(frozen=True)
63
+ class Paragraph(Block):
64
+ """A paragraph containing inline spans."""
65
+
66
+ spans: tuple[Span, ...] = field(default_factory=tuple)
67
+ block_type: BlockType = field(default=BlockType.PARAGRAPH, init=False)
68
+
69
+
70
+ @dataclass(frozen=True)
71
+ class Heading(Block):
72
+ """A heading (h1-h6)."""
73
+
74
+ level: int
75
+ spans: tuple[Span, ...] = field(default_factory=tuple)
76
+ block_type: BlockType = field(default=BlockType.HEADING, init=False)
77
+
78
+ def __post_init__(self) -> None:
79
+ """Validate heading level."""
80
+ if not 1 <= self.level <= 6:
81
+ raise ValueError(f"Heading level must be 1-6, got {self.level}")
82
+
83
+
84
+ @dataclass(frozen=True)
85
+ class CodeBlock(Block):
86
+ """A fenced or indented code block."""
87
+
88
+ code: str
89
+ language: Optional[str] = None
90
+ block_type: BlockType = field(default=BlockType.CODE_BLOCK, init=False)
91
+
92
+
93
+ @dataclass(frozen=True)
94
+ class Blockquote(Block):
95
+ """A blockquote containing other blocks."""
96
+
97
+ blocks: tuple[Block, ...] = field(default_factory=tuple)
98
+ block_type: BlockType = field(default=BlockType.BLOCKQUOTE, init=False)
99
+
100
+
101
+ @dataclass(frozen=True)
102
+ class ListItem(Block):
103
+ """A single item in a list."""
104
+
105
+ spans: tuple[Span, ...] = field(default_factory=tuple)
106
+ block_type: BlockType = field(default=BlockType.LIST_ITEM, init=False)
107
+
108
+
109
+ @dataclass(frozen=True)
110
+ class UnorderedList(Block):
111
+ """An unordered (bullet) list."""
112
+
113
+ items: tuple[ListItem, ...] = field(default_factory=tuple)
114
+ block_type: BlockType = field(default=BlockType.UNORDERED_LIST, init=False)
115
+
116
+
117
+ @dataclass(frozen=True)
118
+ class OrderedList(Block):
119
+ """An ordered (numbered) list."""
120
+
121
+ items: tuple[ListItem, ...] = field(default_factory=tuple)
122
+ start: int = 1
123
+ block_type: BlockType = field(default=BlockType.ORDERED_LIST, init=False)
124
+
125
+
126
+ @dataclass(frozen=True)
127
+ class HorizontalRule(Block):
128
+ """A horizontal rule/divider."""
129
+
130
+ block_type: BlockType = field(default=BlockType.HORIZONTAL_RULE, init=False)
131
+
132
+
133
+ @dataclass(frozen=True)
134
+ class TableCell:
135
+ """A single cell in a table."""
136
+
137
+ spans: tuple[Span, ...] = field(default_factory=tuple)
138
+ align: Optional[str] = None # "left", "center", "right"
139
+
140
+
141
+ @dataclass(frozen=True)
142
+ class TableRow:
143
+ """A row in a table."""
144
+
145
+ cells: tuple[TableCell, ...] = field(default_factory=tuple)
146
+
147
+
148
+ @dataclass(frozen=True)
149
+ class Table(Block):
150
+ """A table with header and body rows."""
151
+
152
+ header: TableRow
153
+ rows: tuple[TableRow, ...] = field(default_factory=tuple)
154
+ block_type: BlockType = field(default=BlockType.TABLE, init=False)
155
+
156
+
157
+ @dataclass(frozen=True)
158
+ class ImageBlock(Block):
159
+ """A standalone image block.
160
+
161
+ Attributes:
162
+ url: The image URL (may be relative or absolute).
163
+ alt: Alt text for accessibility.
164
+ title: Optional title attribute.
165
+ width: Explicit width in pixels (from markdown {width=X} syntax).
166
+ height: Explicit height in pixels (from markdown {height=Y} syntax).
167
+ """
168
+
169
+ url: str
170
+ alt: str = ""
171
+ title: Optional[str] = None
172
+ width: Optional[float] = None
173
+ height: Optional[float] = None
174
+ block_type: BlockType = field(default=BlockType.IMAGE, init=False)
175
+
176
+
177
+ @dataclass(frozen=True)
178
+ class RawHtmlBlock(Block):
179
+ """A raw HTML block to be rendered via foreignObject when enabled."""
180
+
181
+ html: str
182
+ block_type: BlockType = field(default=BlockType.HTML_BLOCK, init=False)
183
+
184
+
185
+ # Type alias for any block
186
+ AnyBlock = Union[
187
+ Paragraph,
188
+ Heading,
189
+ CodeBlock,
190
+ Blockquote,
191
+ UnorderedList,
192
+ OrderedList,
193
+ HorizontalRule,
194
+ Table,
195
+ ImageBlock,
196
+ RawHtmlBlock,
197
+ ]
198
+
199
+ # Type alias for list of blocks (the AST)
200
+ Document = List[AnyBlock]
mdsvg/utils.py ADDED
@@ -0,0 +1,86 @@
1
+ """Utility functions for markdown-svg."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import html
6
+ import re
7
+ from typing import List
8
+
9
+
10
+ def escape_xml(text: str) -> str:
11
+ """
12
+ Escape text for safe inclusion in XML/SVG.
13
+
14
+ Args:
15
+ text: Raw text string.
16
+
17
+ Returns:
18
+ XML-escaped string.
19
+ """
20
+ return html.escape(text, quote=True)
21
+
22
+
23
+ def escape_svg_text(text: str) -> str:
24
+ """
25
+ Escape text for inclusion in SVG text elements.
26
+
27
+ This handles special characters that could break SVG rendering.
28
+
29
+ Args:
30
+ text: Raw text string.
31
+
32
+ Returns:
33
+ Escaped string safe for SVG.
34
+ """
35
+ # Escape XML entities
36
+ result = escape_xml(text)
37
+ # Preserve single spaces but collapse multiple spaces
38
+ result = re.sub(r" +", " ", result)
39
+ return result
40
+
41
+
42
+ def normalize_whitespace(text: str) -> str:
43
+ """
44
+ Normalize whitespace in text (collapse multiple spaces, strip edges).
45
+
46
+ Args:
47
+ text: Text with potentially irregular whitespace.
48
+
49
+ Returns:
50
+ Normalized text.
51
+ """
52
+ return " ".join(text.split())
53
+
54
+
55
+ def split_lines(text: str) -> List[str]:
56
+ """
57
+ Split text into lines, handling different line endings.
58
+
59
+ Args:
60
+ text: Text with line breaks.
61
+
62
+ Returns:
63
+ List of lines (without line ending characters).
64
+ """
65
+ return text.replace("\r\n", "\n").replace("\r", "\n").split("\n")
66
+
67
+
68
+ def format_number(n: float, precision: int = 2) -> str:
69
+ """
70
+ Format a number for SVG attribute output.
71
+
72
+ Removes unnecessary trailing zeros and decimal points.
73
+
74
+ Args:
75
+ n: Number to format.
76
+ precision: Decimal precision.
77
+
78
+ Returns:
79
+ Formatted number string.
80
+ """
81
+ if n == int(n):
82
+ return str(int(n))
83
+ formatted = f"{n:.{precision}f}"
84
+ # Remove trailing zeros
85
+ formatted = formatted.rstrip("0").rstrip(".")
86
+ return formatted