etassia 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +140 -0
- data/LICENSE +25 -0
- data/README.adoc +436 -0
- data/exe/etassia +7 -0
- data/lib/etassia/cli.rb +187 -0
- data/lib/etassia/commands/batch.rb +210 -0
- data/lib/etassia/commands/notation_loader.rb +22 -0
- data/lib/etassia/commands/render.rb +98 -0
- data/lib/etassia/commands/types.rb +33 -0
- data/lib/etassia/commands/version.rb +24 -0
- data/lib/etassia/corpus_type_names.rb +52 -0
- data/lib/etassia/diagram/architecture.rb +108 -0
- data/lib/etassia/diagram/base.rb +61 -0
- data/lib/etassia/diagram/block.rb +102 -0
- data/lib/etassia/diagram/c4.rb +337 -0
- data/lib/etassia/diagram/class_diagram.rb +466 -0
- data/lib/etassia/diagram/class_namespace.rb +17 -0
- data/lib/etassia/diagram/class_note.rb +17 -0
- data/lib/etassia/diagram/containment.rb +107 -0
- data/lib/etassia/diagram/er_diagram.rb +317 -0
- data/lib/etassia/diagram/error.rb +38 -0
- data/lib/etassia/diagram/flowchart.rb +419 -0
- data/lib/etassia/diagram/flowchart_label_text.rb +63 -0
- data/lib/etassia/diagram/gantt.rb +94 -0
- data/lib/etassia/diagram/generic_text.rb +30 -0
- data/lib/etassia/diagram/git_graph.rb +57 -0
- data/lib/etassia/diagram/info.rb +38 -0
- data/lib/etassia/diagram/kanban.rb +198 -0
- data/lib/etassia/diagram/mindmap.rb +71 -0
- data/lib/etassia/diagram/packet.rb +93 -0
- data/lib/etassia/diagram/pie.rb +115 -0
- data/lib/etassia/diagram/quadrant.rb +135 -0
- data/lib/etassia/diagram/radar.rb +65 -0
- data/lib/etassia/diagram/requirement.rb +168 -0
- data/lib/etassia/diagram/sankey.rb +217 -0
- data/lib/etassia/diagram/sequence.rb +299 -0
- data/lib/etassia/diagram/sequence_box.rb +18 -0
- data/lib/etassia/diagram/sequence_frame.rb +37 -0
- data/lib/etassia/diagram/sequence_frame_section.rb +23 -0
- data/lib/etassia/diagram/sequence_text.rb +69 -0
- data/lib/etassia/diagram/sequence_wrap.rb +23 -0
- data/lib/etassia/diagram/state_diagram.rb +282 -0
- data/lib/etassia/diagram/timeline.rb +183 -0
- data/lib/etassia/diagram/treemap.rb +95 -0
- data/lib/etassia/diagram/user_journey.rb +153 -0
- data/lib/etassia/diagram/xy_chart.rb +90 -0
- data/lib/etassia/diagram.rb +8 -0
- data/lib/etassia/diagram_registry.rb +34 -0
- data/lib/etassia/engine.rb +334 -0
- data/lib/etassia/error/diagram_type_error.rb +10 -0
- data/lib/etassia/error/layout_error.rb +12 -0
- data/lib/etassia/error/notation_registration_error.rb +10 -0
- data/lib/etassia/error/parse_error.rb +11 -0
- data/lib/etassia/error/pipeline_error.rb +10 -0
- data/lib/etassia/error/render_error.rb +12 -0
- data/lib/etassia/error/score_error.rb +12 -0
- data/lib/etassia/error.rb +10 -0
- data/lib/etassia/error_report.rb +69 -0
- data/lib/etassia/ir/data.rb +39 -0
- data/lib/etassia/ir/data_value.rb +35 -0
- data/lib/etassia/ir/dimension.rb +10 -0
- data/lib/etassia/ir/edge.rb +34 -0
- data/lib/etassia/ir/graph.rb +28 -0
- data/lib/etassia/ir/integrity.rb +80 -0
- data/lib/etassia/ir/item.rb +46 -0
- data/lib/etassia/ir/model.rb +56 -0
- data/lib/etassia/ir/node.rb +10 -0
- data/lib/etassia/ir/placement.rb +47 -0
- data/lib/etassia/ir/prepositioned.rb +25 -0
- data/lib/etassia/ir/prepositioned_item.rb +26 -0
- data/lib/etassia/ir/property_set.rb +43 -0
- data/lib/etassia/ir/scalar.rb +25 -0
- data/lib/etassia/ir/series.rb +10 -0
- data/lib/etassia/ir.rb +17 -0
- data/lib/etassia/js_math.rb +511 -0
- data/lib/etassia/js_number.rb +64 -0
- data/lib/etassia/layout/architecture.rb +1472 -0
- data/lib/etassia/layout/base.rb +276 -0
- data/lib/etassia/layout/block.rb +738 -0
- data/lib/etassia/layout/c4.rb +876 -0
- data/lib/etassia/layout/c4_boundary_metrics.rb +35 -0
- data/lib/etassia/layout/c4_bounds.rb +106 -0
- data/lib/etassia/layout/c4_intersection.rb +77 -0
- data/lib/etassia/layout/c4_placement.rb +135 -0
- data/lib/etassia/layout/c4_shape_metrics.rb +97 -0
- data/lib/etassia/layout/c4_shape_outline.rb +67 -0
- data/lib/etassia/layout/c4_stereotype.rb +30 -0
- data/lib/etassia/layout/c4_text.rb +54 -0
- data/lib/etassia/layout/class_cell_placement.rb +148 -0
- data/lib/etassia/layout/class_diagram.rb +1116 -0
- data/lib/etassia/layout/class_diagram_namespaces.rb +26 -0
- data/lib/etassia/layout/class_diagram_notes.rb +26 -0
- data/lib/etassia/layout/class_name_advances.rb +31 -0
- data/lib/etassia/layout/class_namespace_box.rb +21 -0
- data/lib/etassia/layout/class_namespace_nodes.rb +197 -0
- data/lib/etassia/layout/class_namespace_placement.rb +71 -0
- data/lib/etassia/layout/class_note.rb +44 -0
- data/lib/etassia/layout/class_note_nodes.rb +84 -0
- data/lib/etassia/layout/elk_notices.rb +48 -0
- data/lib/etassia/layout/elk_placement.rb +197 -0
- data/lib/etassia/layout/er_diagram.rb +1002 -0
- data/lib/etassia/layout/er_diagram_adjacency.rb +20 -0
- data/lib/etassia/layout/er_diagram_alignment.rb +72 -0
- data/lib/etassia/layout/er_diagram_compaction.rb +85 -0
- data/lib/etassia/layout/er_diagram_conflicts.rb +70 -0
- data/lib/etassia/layout/er_diagram_coordinates.rb +104 -0
- data/lib/etassia/layout/er_diagram_entity_size.rb +82 -0
- data/lib/etassia/layout/er_diagram_layering.rb +109 -0
- data/lib/etassia/layout/er_diagram_ordering.rb +143 -0
- data/lib/etassia/layout/er_diagram_placement.rb +163 -0
- data/lib/etassia/layout/er_diagram_placement_node.rb +22 -0
- data/lib/etassia/layout/error.rb +133 -0
- data/lib/etassia/layout/flowchart/brandes_koepf.rb +208 -0
- data/lib/etassia/layout/flowchart/cluster_reach.rb +70 -0
- data/lib/etassia/layout/flowchart/component_packing.rb +129 -0
- data/lib/etassia/layout/flowchart/directed_placement.rb +428 -0
- data/lib/etassia/layout/flowchart/edge_chains.rb +111 -0
- data/lib/etassia/layout/flowchart/edge_order.rb +34 -0
- data/lib/etassia/layout/flowchart/edge_router.rb +533 -0
- data/lib/etassia/layout/flowchart/label_backdrop_geometry.rb +62 -0
- data/lib/etassia/layout/flowchart/layer_spacing.rb +85 -0
- data/lib/etassia/layout/flowchart/level_edges.rb +74 -0
- data/lib/etassia/layout/flowchart/level_frame.rb +47 -0
- data/lib/etassia/layout/flowchart/minimum_rank_spacing.rb +74 -0
- data/lib/etassia/layout/flowchart/row_centering.rb +229 -0
- data/lib/etassia/layout/flowchart.rb +1290 -0
- data/lib/etassia/layout/gantt.rb +814 -0
- data/lib/etassia/layout/gantt_duration.rb +49 -0
- data/lib/etassia/layout/gantt_exclusions.rb +74 -0
- data/lib/etassia/layout/gantt_ticks.rb +165 -0
- data/lib/etassia/layout/git_graph.rb +1075 -0
- data/lib/etassia/layout/grid.rb +165 -0
- data/lib/etassia/layout/info.rb +113 -0
- data/lib/etassia/layout/kanban.rb +452 -0
- data/lib/etassia/layout/kanban_card_text.rb +95 -0
- data/lib/etassia/layout/mindmap.rb +425 -0
- data/lib/etassia/layout/mindmap_cose.rb +81 -0
- data/lib/etassia/layout/mindmap_cose_body.rb +93 -0
- data/lib/etassia/layout/mindmap_cose_geometry.rb +190 -0
- data/lib/etassia/layout/mindmap_cose_grid.rb +96 -0
- data/lib/etassia/layout/mindmap_cose_link.rb +42 -0
- data/lib/etassia/layout/mindmap_cose_relaxation.rb +166 -0
- data/lib/etassia/layout/mindmap_cose_seed.rb +93 -0
- data/lib/etassia/layout/mindmap_cose_wedge.rb +37 -0
- data/lib/etassia/layout/mindmap_node_size.rb +130 -0
- data/lib/etassia/layout/packet.rb +285 -0
- data/lib/etassia/layout/pie.rb +305 -0
- data/lib/etassia/layout/pie_legend.rb +64 -0
- data/lib/etassia/layout/pie_legend_entry.rb +21 -0
- data/lib/etassia/layout/quadrant.rb +402 -0
- data/lib/etassia/layout/radar.rb +442 -0
- data/lib/etassia/layout/requirement.rb +765 -0
- data/lib/etassia/layout/sankey.rb +501 -0
- data/lib/etassia/layout/scene.rb +18 -0
- data/lib/etassia/layout/sequence/actor_gaps.rb +151 -0
- data/lib/etassia/layout/sequence/box_shape.rb +23 -0
- data/lib/etassia/layout/sequence/creations.rb +73 -0
- data/lib/etassia/layout/sequence/frame_divider.rb +22 -0
- data/lib/etassia/layout/sequence/frame_placement.rb +424 -0
- data/lib/etassia/layout/sequence/frame_reader.rb +75 -0
- data/lib/etassia/layout/sequence/frame_shape.rb +45 -0
- data/lib/etassia/layout/sequence/geometry.rb +24 -0
- data/lib/etassia/layout/sequence/message_rows.rb +165 -0
- data/lib/etassia/layout/sequence/note.rb +16 -0
- data/lib/etassia/layout/sequence/note_box.rb +114 -0
- data/lib/etassia/layout/sequence/note_placement.rb +223 -0
- data/lib/etassia/layout/sequence/text_width.rb +69 -0
- data/lib/etassia/layout/sequence/text_wrap.rb +93 -0
- data/lib/etassia/layout/sequence.rb +936 -0
- data/lib/etassia/layout/state_diagram.rb +1277 -0
- data/lib/etassia/layout/timeline.rb +165 -0
- data/lib/etassia/layout/timeline_card.rb +142 -0
- data/lib/etassia/layout/timeline_flow.rb +164 -0
- data/lib/etassia/layout/timeline_line.rb +33 -0
- data/lib/etassia/layout/timeline_rounding.rb +19 -0
- data/lib/etassia/layout/timeline_text.rb +105 -0
- data/lib/etassia/layout/treemap.rb +303 -0
- data/lib/etassia/layout/treemap_placement.rb +52 -0
- data/lib/etassia/layout/treemap_squarify.rb +108 -0
- data/lib/etassia/layout/user_journey.rb +300 -0
- data/lib/etassia/layout/user_journey_face.rb +33 -0
- data/lib/etassia/layout/user_journey_face_geometry.rb +80 -0
- data/lib/etassia/layout/user_journey_geometry.rb +336 -0
- data/lib/etassia/layout/user_journey_legend.rb +84 -0
- data/lib/etassia/layout/xy_chart.rb +620 -0
- data/lib/etassia/layout.rb +19 -0
- data/lib/etassia/lint_debt_scoreboard.rb +219 -0
- data/lib/etassia/markdown_text/emphasis_simulator.rb +373 -0
- data/lib/etassia/markdown_text.rb +393 -0
- data/lib/etassia/notation/builtin.rb +39 -0
- data/lib/etassia/notation/entry.rb +15 -0
- data/lib/etassia/notation/mermaid/class_diagram_config.rb +42 -0
- data/lib/etassia/notation/mermaid/git_graph_config.rb +46 -0
- data/lib/etassia/notation/mermaid/ir_adapter.rb +27 -0
- data/lib/etassia/notation/mermaid/ir_adapters/architecture.rb +196 -0
- data/lib/etassia/notation/mermaid/ir_adapters/block.rb +138 -0
- data/lib/etassia/notation/mermaid/ir_adapters/c4.rb +258 -0
- data/lib/etassia/notation/mermaid/ir_adapters/class_diagram.rb +257 -0
- data/lib/etassia/notation/mermaid/ir_adapters/er_diagram.rb +220 -0
- data/lib/etassia/notation/mermaid/ir_adapters/error.rb +32 -0
- data/lib/etassia/notation/mermaid/ir_adapters/flowchart.rb +208 -0
- data/lib/etassia/notation/mermaid/ir_adapters/gantt.rb +236 -0
- data/lib/etassia/notation/mermaid/ir_adapters/git_graph.rb +186 -0
- data/lib/etassia/notation/mermaid/ir_adapters/info.rb +34 -0
- data/lib/etassia/notation/mermaid/ir_adapters/kanban.rb +110 -0
- data/lib/etassia/notation/mermaid/ir_adapters/mindmap.rb +76 -0
- data/lib/etassia/notation/mermaid/ir_adapters/packet.rb +51 -0
- data/lib/etassia/notation/mermaid/ir_adapters/pie.rb +71 -0
- data/lib/etassia/notation/mermaid/ir_adapters/quadrant.rb +116 -0
- data/lib/etassia/notation/mermaid/ir_adapters/radar.rb +142 -0
- data/lib/etassia/notation/mermaid/ir_adapters/requirement.rb +237 -0
- data/lib/etassia/notation/mermaid/ir_adapters/sankey.rb +74 -0
- data/lib/etassia/notation/mermaid/ir_adapters/sequence.rb +304 -0
- data/lib/etassia/notation/mermaid/ir_adapters/state_diagram.rb +225 -0
- data/lib/etassia/notation/mermaid/ir_adapters/timeline.rb +93 -0
- data/lib/etassia/notation/mermaid/ir_adapters/treemap.rb +143 -0
- data/lib/etassia/notation/mermaid/ir_adapters/user_journey.rb +127 -0
- data/lib/etassia/notation/mermaid/ir_adapters/xychart.rb +131 -0
- data/lib/etassia/notation/mermaid.rb +399 -0
- data/lib/etassia/notation/parsed.rb +31 -0
- data/lib/etassia/notation/plantuml/activity/arrow_head.rb +45 -0
- data/lib/etassia/notation/plantuml/activity/arrow_style.rb +53 -0
- data/lib/etassia/notation/plantuml/activity/bar_columns.rb +75 -0
- data/lib/etassia/notation/plantuml/activity/box.rb +60 -0
- data/lib/etassia/notation/plantuml/activity/builder.rb +77 -0
- data/lib/etassia/notation/plantuml/activity/canvas.rb +117 -0
- data/lib/etassia/notation/plantuml/activity/column_centres.rb +151 -0
- data/lib/etassia/notation/plantuml/activity/columns.rb +224 -0
- data/lib/etassia/notation/plantuml/activity/diagram.rb +35 -0
- data/lib/etassia/notation/plantuml/activity/edge.rb +35 -0
- data/lib/etassia/notation/plantuml/activity/edge_marks.rb +68 -0
- data/lib/etassia/notation/plantuml/activity/exits.rb +61 -0
- data/lib/etassia/notation/plantuml/activity/frame_marks.rb +116 -0
- data/lib/etassia/notation/plantuml/activity/lane_steps.rb +82 -0
- data/lib/etassia/notation/plantuml/activity/lane_titles.rb +46 -0
- data/lib/etassia/notation/plantuml/activity/layout.rb +50 -0
- data/lib/etassia/notation/plantuml/activity/legacy_parser.rb +181 -0
- data/lib/etassia/notation/plantuml/activity/legacy_row_gaps.rb +65 -0
- data/lib/etassia/notation/plantuml/activity/mark.rb +23 -0
- data/lib/etassia/notation/plantuml/activity/marks.rb +62 -0
- data/lib/etassia/notation/plantuml/activity/metrics.rb +169 -0
- data/lib/etassia/notation/plantuml/activity/new_parser/blocks.rb +166 -0
- data/lib/etassia/notation/plantuml/activity/new_parser.rb +177 -0
- data/lib/etassia/notation/plantuml/activity/node.rb +50 -0
- data/lib/etassia/notation/plantuml/activity/node_marks.rb +174 -0
- data/lib/etassia/notation/plantuml/activity/note.rb +27 -0
- data/lib/etassia/notation/plantuml/activity/parser.rb +51 -0
- data/lib/etassia/notation/plantuml/activity/placement.rb +327 -0
- data/lib/etassia/notation/plantuml/activity/ranking.rb +142 -0
- data/lib/etassia/notation/plantuml/activity/reach.rb +58 -0
- data/lib/etassia/notation/plantuml/activity/renderer.rb +220 -0
- data/lib/etassia/notation/plantuml/activity/route.rb +22 -0
- data/lib/etassia/notation/plantuml/activity/router.rb +308 -0
- data/lib/etassia/notation/plantuml/activity/row_gaps.rb +105 -0
- data/lib/etassia/notation/plantuml/activity/row_spread.rb +59 -0
- data/lib/etassia/notation/plantuml/activity/scene.rb +20 -0
- data/lib/etassia/notation/plantuml/activity/text.rb +36 -0
- data/lib/etassia/notation/plantuml/activity.rb +50 -0
- data/lib/etassia/notation/plantuml/arrow.rb +135 -0
- data/lib/etassia/notation/plantuml/caption.rb +50 -0
- data/lib/etassia/notation/plantuml/caption_layout.rb +97 -0
- data/lib/etassia/notation/plantuml/caption_rows.rb +67 -0
- data/lib/etassia/notation/plantuml/class_header.rb +178 -0
- data/lib/etassia/notation/plantuml/class_name.rb +51 -0
- data/lib/etassia/notation/plantuml/component.rb +37 -0
- data/lib/etassia/notation/plantuml/creole.rb +278 -0
- data/lib/etassia/notation/plantuml/creole_icons.rb +33 -0
- data/lib/etassia/notation/plantuml/diagram.rb +60 -0
- data/lib/etassia/notation/plantuml/diagram_builder.rb +442 -0
- data/lib/etassia/notation/plantuml/diagram_name.rb +51 -0
- data/lib/etassia/notation/plantuml/directives.rb +33 -0
- data/lib/etassia/notation/plantuml/drawing/box.rb +28 -0
- data/lib/etassia/notation/plantuml/drawing/face.rb +39 -0
- data/lib/etassia/notation/plantuml/drawing/pen.rb +105 -0
- data/lib/etassia/notation/plantuml/drawing/renderer.rb +36 -0
- data/lib/etassia/notation/plantuml/drawing/scene.rb +28 -0
- data/lib/etassia/notation/plantuml/drawing.rb +19 -0
- data/lib/etassia/notation/plantuml/ebnf/blocks.rb +272 -0
- data/lib/etassia/notation/plantuml/ebnf/layout.rb +117 -0
- data/lib/etassia/notation/plantuml/ebnf/model.rb +22 -0
- data/lib/etassia/notation/plantuml/ebnf/reader.rb +131 -0
- data/lib/etassia/notation/plantuml/ebnf.rb +34 -0
- data/lib/etassia/notation/plantuml/end_texts.rb +78 -0
- data/lib/etassia/notation/plantuml/entity.rb +54 -0
- data/lib/etassia/notation/plantuml/further.rb +38 -0
- data/lib/etassia/notation/plantuml/gantt/layout.rb +269 -0
- data/lib/etassia/notation/plantuml/gantt/reader.rb +210 -0
- data/lib/etassia/notation/plantuml/gantt/renderer.rb +81 -0
- data/lib/etassia/notation/plantuml/gantt.rb +34 -0
- data/lib/etassia/notation/plantuml/include/blocks.rb +66 -0
- data/lib/etassia/notation/plantuml/include/finder.rb +61 -0
- data/lib/etassia/notation/plantuml/include.rb +78 -0
- data/lib/etassia/notation/plantuml/ir_adapter/annotations.rb +40 -0
- data/lib/etassia/notation/plantuml/ir_adapter/links.rb +91 -0
- data/lib/etassia/notation/plantuml/ir_adapter/sink.rb +49 -0
- data/lib/etassia/notation/plantuml/ir_adapter/structure.rb +86 -0
- data/lib/etassia/notation/plantuml/ir_adapter.rb +32 -0
- data/lib/etassia/notation/plantuml/ir_reader/annotations.rb +51 -0
- data/lib/etassia/notation/plantuml/ir_reader/index.rb +49 -0
- data/lib/etassia/notation/plantuml/ir_reader/links.rb +133 -0
- data/lib/etassia/notation/plantuml/ir_reader/structure.rb +83 -0
- data/lib/etassia/notation/plantuml/ir_reader.rb +42 -0
- data/lib/etassia/notation/plantuml/junction.rb +20 -0
- data/lib/etassia/notation/plantuml/klass.rb +41 -0
- data/lib/etassia/notation/plantuml/label_clearance.rb +67 -0
- data/lib/etassia/notation/plantuml/layout.rb +2806 -0
- data/lib/etassia/notation/plantuml/member.rb +37 -0
- data/lib/etassia/notation/plantuml/member_icon.rb +87 -0
- data/lib/etassia/notation/plantuml/member_icon_elements.rb +91 -0
- data/lib/etassia/notation/plantuml/member_pitch.rb +76 -0
- data/lib/etassia/notation/plantuml/member_rule.rb +48 -0
- data/lib/etassia/notation/plantuml/network/grid.rb +170 -0
- data/lib/etassia/notation/plantuml/network/layout.rb +158 -0
- data/lib/etassia/notation/plantuml/network/model.rb +22 -0
- data/lib/etassia/notation/plantuml/network/placement.rb +64 -0
- data/lib/etassia/notation/plantuml/network/reader.rb +126 -0
- data/lib/etassia/notation/plantuml/network/shapes.rb +109 -0
- data/lib/etassia/notation/plantuml/network.rb +37 -0
- data/lib/etassia/notation/plantuml/node_graph/archimate.rb +49 -0
- data/lib/etassia/notation/plantuml/node_graph/box_reader.rb +86 -0
- data/lib/etassia/notation/plantuml/node_graph/cloud.rb +73 -0
- data/lib/etassia/notation/plantuml/node_graph/crows_foot.rb +85 -0
- data/lib/etassia/notation/plantuml/node_graph/curve.rb +66 -0
- data/lib/etassia/notation/plantuml/node_graph/data_reader.rb +77 -0
- data/lib/etassia/notation/plantuml/node_graph/data_shape.rb +39 -0
- data/lib/etassia/notation/plantuml/node_graph/detour.rb +98 -0
- data/lib/etassia/notation/plantuml/node_graph/edge.rb +26 -0
- data/lib/etassia/notation/plantuml/node_graph/entity_metrics.rb +49 -0
- data/lib/etassia/notation/plantuml/node_graph/entity_reader.rb +65 -0
- data/lib/etassia/notation/plantuml/node_graph/entity_shape.rb +111 -0
- data/lib/etassia/notation/plantuml/node_graph/figures.rb +92 -0
- data/lib/etassia/notation/plantuml/node_graph/frame_ports.rb +73 -0
- data/lib/etassia/notation/plantuml/node_graph/graph.rb +40 -0
- data/lib/etassia/notation/plantuml/node_graph/heads.rb +89 -0
- data/lib/etassia/notation/plantuml/node_graph/label_fan.rb +76 -0
- data/lib/etassia/notation/plantuml/node_graph/label_placer.rb +310 -0
- data/lib/etassia/notation/plantuml/node_graph/label_waypoint.rb +47 -0
- data/lib/etassia/notation/plantuml/node_graph/layout.rb +293 -0
- data/lib/etassia/notation/plantuml/node_graph/link.rb +78 -0
- data/lib/etassia/notation/plantuml/node_graph/macros.rb +39 -0
- data/lib/etassia/notation/plantuml/node_graph/metrics.rb +113 -0
- data/lib/etassia/notation/plantuml/node_graph/node.rb +44 -0
- data/lib/etassia/notation/plantuml/node_graph/note_tail.rb +58 -0
- data/lib/etassia/notation/plantuml/node_graph/packer.rb +126 -0
- data/lib/etassia/notation/plantuml/node_graph/parser.rb +309 -0
- data/lib/etassia/notation/plantuml/node_graph/paths.rb +48 -0
- data/lib/etassia/notation/plantuml/node_graph/ranking.rb +231 -0
- data/lib/etassia/notation/plantuml/node_graph/reader.rb +53 -0
- data/lib/etassia/notation/plantuml/node_graph/references.rb +38 -0
- data/lib/etassia/notation/plantuml/node_graph/renderer.rb +160 -0
- data/lib/etassia/notation/plantuml/node_graph/scene.rb +22 -0
- data/lib/etassia/notation/plantuml/node_graph/shapes.rb +302 -0
- data/lib/etassia/notation/plantuml/node_graph/skipped_ranks.rb +61 -0
- data/lib/etassia/notation/plantuml/node_graph/tab.rb +68 -0
- data/lib/etassia/notation/plantuml/node_graph/usecase_ellipse.rb +100 -0
- data/lib/etassia/notation/plantuml/node_graph.rb +38 -0
- data/lib/etassia/notation/plantuml/note.rb +27 -0
- data/lib/etassia/notation/plantuml/note_layout.rb +257 -0
- data/lib/etassia/notation/plantuml/note_outline.rb +68 -0
- data/lib/etassia/notation/plantuml/objects/map_rows.rb +46 -0
- data/lib/etassia/notation/plantuml/objects/reader.rb +208 -0
- data/lib/etassia/notation/plantuml/objects.rb +39 -0
- data/lib/etassia/notation/plantuml/package.rb +45 -0
- data/lib/etassia/notation/plantuml/package_color.rb +32 -0
- data/lib/etassia/notation/plantuml/package_frames.rb +178 -0
- data/lib/etassia/notation/plantuml/parser.rb +622 -0
- data/lib/etassia/notation/plantuml/portion_visibility.rb +91 -0
- data/lib/etassia/notation/plantuml/qualified_association_layout.rb +332 -0
- data/lib/etassia/notation/plantuml/qualifier_box.rb +78 -0
- data/lib/etassia/notation/plantuml/rank_chain.rb +527 -0
- data/lib/etassia/notation/plantuml/rank_hierarchy.rb +347 -0
- data/lib/etassia/notation/plantuml/relation.rb +103 -0
- data/lib/etassia/notation/plantuml/relation_label_separator.rb +199 -0
- data/lib/etassia/notation/plantuml/relation_path_points.rb +84 -0
- data/lib/etassia/notation/plantuml/renderer.rb +490 -0
- data/lib/etassia/notation/plantuml/scene.rb +134 -0
- data/lib/etassia/notation/plantuml/segment_box.rb +85 -0
- data/lib/etassia/notation/plantuml/sequence/activation.rb +28 -0
- data/lib/etassia/notation/plantuml/sequence/appearance.rb +71 -0
- data/lib/etassia/notation/plantuml/sequence/arrow_end.rb +23 -0
- data/lib/etassia/notation/plantuml/sequence/arrow_marks.rb +111 -0
- data/lib/etassia/notation/plantuml/sequence/arrow_style.rb +42 -0
- data/lib/etassia/notation/plantuml/sequence/arrow_syntax.rb +122 -0
- data/lib/etassia/notation/plantuml/sequence/bar_tracker.rb +59 -0
- data/lib/etassia/notation/plantuml/sequence/box.rb +23 -0
- data/lib/etassia/notation/plantuml/sequence/chrome.rb +60 -0
- data/lib/etassia/notation/plantuml/sequence/chrome_rows.rb +184 -0
- data/lib/etassia/notation/plantuml/sequence/create.rb +34 -0
- data/lib/etassia/notation/plantuml/sequence/delay.rb +19 -0
- data/lib/etassia/notation/plantuml/sequence/destroy.rb +20 -0
- data/lib/etassia/notation/plantuml/sequence/diagram.rb +86 -0
- data/lib/etassia/notation/plantuml/sequence/divider.rb +19 -0
- data/lib/etassia/notation/plantuml/sequence/edge.rb +53 -0
- data/lib/etassia/notation/plantuml/sequence/embedded.rb +77 -0
- data/lib/etassia/notation/plantuml/sequence/fill.rb +56 -0
- data/lib/etassia/notation/plantuml/sequence/fragment.rb +28 -0
- data/lib/etassia/notation/plantuml/sequence/fragment_shape.rb +138 -0
- data/lib/etassia/notation/plantuml/sequence/head_style.rb +43 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter/blocks.rb +73 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter/fills.rb +24 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter/items.rb +48 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter/messages.rb +62 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter/participants.rb +42 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter/settings.rb +54 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter.rb +38 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/blocks.rb +77 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/fills.rb +27 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/index.rb +36 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/items.rb +35 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/messages.rb +70 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/participants.rb +42 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/settings.rb +74 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader.rb +38 -0
- data/lib/etassia/notation/plantuml/sequence/keyword_defaults.rb +28 -0
- data/lib/etassia/notation/plantuml/sequence/layout.rb +1567 -0
- data/lib/etassia/notation/plantuml/sequence/message.rb +97 -0
- data/lib/etassia/notation/plantuml/sequence/message_wrap.rb +77 -0
- data/lib/etassia/notation/plantuml/sequence/note.rb +61 -0
- data/lib/etassia/notation/plantuml/sequence/note_fill.rb +35 -0
- data/lib/etassia/notation/plantuml/sequence/note_geometry.rb +126 -0
- data/lib/etassia/notation/plantuml/sequence/note_shape.rb +51 -0
- data/lib/etassia/notation/plantuml/sequence/numbered_label.rb +57 -0
- data/lib/etassia/notation/plantuml/sequence/numbering.rb +49 -0
- data/lib/etassia/notation/plantuml/sequence/outline.rb +204 -0
- data/lib/etassia/notation/plantuml/sequence/page_break.rb +17 -0
- data/lib/etassia/notation/plantuml/sequence/paint.rb +79 -0
- data/lib/etassia/notation/plantuml/sequence/parallel_message.rb +19 -0
- data/lib/etassia/notation/plantuml/sequence/parser.rb +844 -0
- data/lib/etassia/notation/plantuml/sequence/participant.rb +27 -0
- data/lib/etassia/notation/plantuml/sequence/picture.rb +29 -0
- data/lib/etassia/notation/plantuml/sequence/ref.rb +24 -0
- data/lib/etassia/notation/plantuml/sequence/ref_shape.rb +74 -0
- data/lib/etassia/notation/plantuml/sequence/refusals.rb +47 -0
- data/lib/etassia/notation/plantuml/sequence/renderer.rb +501 -0
- data/lib/etassia/notation/plantuml/sequence/scene.rb +200 -0
- data/lib/etassia/notation/plantuml/sequence/skin.rb +76 -0
- data/lib/etassia/notation/plantuml/sequence/spacing.rb +28 -0
- data/lib/etassia/notation/plantuml/sequence/style.rb +151 -0
- data/lib/etassia/notation/plantuml/sequence/tab_size.rb +42 -0
- data/lib/etassia/notation/plantuml/sequence/walker.rb +1190 -0
- data/lib/etassia/notation/plantuml/sequence.rb +47 -0
- data/lib/etassia/notation/plantuml/source_lines.rb +33 -0
- data/lib/etassia/notation/plantuml/state/axis_solver.rb +272 -0
- data/lib/etassia/notation/plantuml/state/cluster_padding.rb +75 -0
- data/lib/etassia/notation/plantuml/state/curve.rb +73 -0
- data/lib/etassia/notation/plantuml/state/dot_axis.rb +248 -0
- data/lib/etassia/notation/plantuml/state/extent.rb +42 -0
- data/lib/etassia/notation/plantuml/state/hierarchy.rb +355 -0
- data/lib/etassia/notation/plantuml/state/label_clearance.rb +148 -0
- data/lib/etassia/notation/plantuml/state/label_reach.rb +139 -0
- data/lib/etassia/notation/plantuml/state/layout.rb +173 -0
- data/lib/etassia/notation/plantuml/state/level_edges.rb +113 -0
- data/lib/etassia/notation/plantuml/state/look.rb +36 -0
- data/lib/etassia/notation/plantuml/state/note_shape.rb +73 -0
- data/lib/etassia/notation/plantuml/state/plane.rb +85 -0
- data/lib/etassia/notation/plantuml/state/ranking.rb +181 -0
- data/lib/etassia/notation/plantuml/state/reader.rb +310 -0
- data/lib/etassia/notation/plantuml/state/renderer.rb +253 -0
- data/lib/etassia/notation/plantuml/state/route_detour.rb +115 -0
- data/lib/etassia/notation/plantuml/state/route_geometry.rb +186 -0
- data/lib/etassia/notation/plantuml/state/routes.rb +196 -0
- data/lib/etassia/notation/plantuml/state/row.rb +88 -0
- data/lib/etassia/notation/plantuml/state/rows.rb +50 -0
- data/lib/etassia/notation/plantuml/state/stretch.rb +71 -0
- data/lib/etassia/notation/plantuml/state.rb +62 -0
- data/lib/etassia/notation/plantuml/stereotype_head.rb +58 -0
- data/lib/etassia/notation/plantuml/style/bound.rb +34 -0
- data/lib/etassia/notation/plantuml/style/colour.rb +41 -0
- data/lib/etassia/notation/plantuml/style/colour_names.rb +89 -0
- data/lib/etassia/notation/plantuml/style/defaults.rb +33 -0
- data/lib/etassia/notation/plantuml/style/inline.rb +56 -0
- data/lib/etassia/notation/plantuml/style/reader.rb +242 -0
- data/lib/etassia/notation/plantuml/style/sheet.rb +103 -0
- data/lib/etassia/notation/plantuml/style.rb +32 -0
- data/lib/etassia/notation/plantuml/style_reader.rb +160 -0
- data/lib/etassia/notation/plantuml/style_sheet.rb +27 -0
- data/lib/etassia/notation/plantuml/styled_relation_curve.rb +71 -0
- data/lib/etassia/notation/plantuml/timing/arrows.rb +81 -0
- data/lib/etassia/notation/plantuml/timing/axis.rb +100 -0
- data/lib/etassia/notation/plantuml/timing/layout.rb +172 -0
- data/lib/etassia/notation/plantuml/timing/model.rb +34 -0
- data/lib/etassia/notation/plantuml/timing/reader.rb +173 -0
- data/lib/etassia/notation/plantuml/timing/signals.rb +328 -0
- data/lib/etassia/notation/plantuml/timing.rb +38 -0
- data/lib/etassia/notation/plantuml/tree/layout.rb +276 -0
- data/lib/etassia/notation/plantuml/tree/mindmap_layout.rb +157 -0
- data/lib/etassia/notation/plantuml/tree/reader.rb +186 -0
- data/lib/etassia/notation/plantuml/tree/renderer.rb +67 -0
- data/lib/etassia/notation/plantuml/tree/role.rb +57 -0
- data/lib/etassia/notation/plantuml/tree/rules.rb +156 -0
- data/lib/etassia/notation/plantuml/tree/wbs_layout.rb +164 -0
- data/lib/etassia/notation/plantuml/tree.rb +41 -0
- data/lib/etassia/notation/plantuml/unsupported_construct_error.rb +39 -0
- data/lib/etassia/notation/plantuml/unsupported_constructs.rb +103 -0
- data/lib/etassia/notation/plantuml/usecase.rb +36 -0
- data/lib/etassia/notation/plantuml.rb +132 -0
- data/lib/etassia/notation/plantuml_data/boxes/frames.rb +101 -0
- data/lib/etassia/notation/plantuml_data/boxes/graph_layout.rb +348 -0
- data/lib/etassia/notation/plantuml_data/boxes/heads.rb +65 -0
- data/lib/etassia/notation/plantuml_data/boxes/highlights.rb +70 -0
- data/lib/etassia/notation/plantuml_data/boxes/inline_rows.rb +99 -0
- data/lib/etassia/notation/plantuml_data/boxes/json_reader.rb +24 -0
- data/lib/etassia/notation/plantuml_data/boxes/link_text.rb +94 -0
- data/lib/etassia/notation/plantuml_data/boxes/marks.rb +76 -0
- data/lib/etassia/notation/plantuml_data/boxes/ornaments.rb +130 -0
- data/lib/etassia/notation/plantuml_data/boxes/renderer.rb +95 -0
- data/lib/etassia/notation/plantuml_data/boxes/row_alignment.rb +58 -0
- data/lib/etassia/notation/plantuml_data/boxes/row_bend.rb +43 -0
- data/lib/etassia/notation/plantuml_data/boxes/scene.rb +27 -0
- data/lib/etassia/notation/plantuml_data/boxes/table.rb +56 -0
- data/lib/etassia/notation/plantuml_data/boxes/table_box.rb +355 -0
- data/lib/etassia/notation/plantuml_data/boxes/text.rb +101 -0
- data/lib/etassia/notation/plantuml_data/boxes/tree_builder.rb +82 -0
- data/lib/etassia/notation/plantuml_data/boxes/tree_layout.rb +82 -0
- data/lib/etassia/notation/plantuml_data/boxes/yaml_reader.rb +105 -0
- data/lib/etassia/notation/plantuml_data/openiconic.LICENSE +6 -0
- data/lib/etassia/notation/plantuml_data/openiconic.json +1 -0
- data/lib/etassia/notation/plantuml_data.rb +84 -0
- data/lib/etassia/notation/plantuml_salt/block.rb +80 -0
- data/lib/etassia/notation/plantuml_salt/box.rb +30 -0
- data/lib/etassia/notation/plantuml_salt/font_metrics.rb +28 -0
- data/lib/etassia/notation/plantuml_salt/grid_marks.rb +66 -0
- data/lib/etassia/notation/plantuml_salt/group_marks.rb +77 -0
- data/lib/etassia/notation/plantuml_salt/layout.rb +206 -0
- data/lib/etassia/notation/plantuml_salt/mark.rb +36 -0
- data/lib/etassia/notation/plantuml_salt/markup.rb +57 -0
- data/lib/etassia/notation/plantuml_salt/parser.rb +239 -0
- data/lib/etassia/notation/plantuml_salt/renderer.rb +138 -0
- data/lib/etassia/notation/plantuml_salt/row.rb +19 -0
- data/lib/etassia/notation/plantuml_salt/run.rb +34 -0
- data/lib/etassia/notation/plantuml_salt/scene.rb +23 -0
- data/lib/etassia/notation/plantuml_salt/scroll_marks.rb +94 -0
- data/lib/etassia/notation/plantuml_salt/tab_marks.rb +74 -0
- data/lib/etassia/notation/plantuml_salt/tree_marks.rb +127 -0
- data/lib/etassia/notation/plantuml_salt/widget.rb +40 -0
- data/lib/etassia/notation/plantuml_salt/widget_marks.rb +319 -0
- data/lib/etassia/notation/plantuml_salt.rb +66 -0
- data/lib/etassia/notation/plugin_failure.rb +42 -0
- data/lib/etassia/notation.rb +289 -0
- data/lib/etassia/parser/architecture.rb +25 -0
- data/lib/etassia/parser/atoms/delimited_run.rb +82 -0
- data/lib/etassia/parser/atoms/greedy_run.rb +116 -0
- data/lib/etassia/parser/atoms/joined.rb +47 -0
- data/lib/etassia/parser/atoms/trimmed_run.rb +47 -0
- data/lib/etassia/parser/base.rb +258 -0
- data/lib/etassia/parser/block.rb +32 -0
- data/lib/etassia/parser/builders/architecture.rb +135 -0
- data/lib/etassia/parser/builders/block.rb +288 -0
- data/lib/etassia/parser/builders/c4.rb +275 -0
- data/lib/etassia/parser/builders/capture_string.rb +17 -0
- data/lib/etassia/parser/builders/class_diagram.rb +539 -0
- data/lib/etassia/parser/builders/er_diagram.rb +308 -0
- data/lib/etassia/parser/builders/error.rb +51 -0
- data/lib/etassia/parser/builders/flowchart.rb +1150 -0
- data/lib/etassia/parser/builders/gantt.rb +267 -0
- data/lib/etassia/parser/builders/git_graph.rb +301 -0
- data/lib/etassia/parser/builders/info.rb +57 -0
- data/lib/etassia/parser/builders/kanban.rb +411 -0
- data/lib/etassia/parser/builders/mindmap.rb +271 -0
- data/lib/etassia/parser/builders/packet.rb +85 -0
- data/lib/etassia/parser/builders/pie.rb +105 -0
- data/lib/etassia/parser/builders/quadrant.rb +165 -0
- data/lib/etassia/parser/builders/radar.rb +157 -0
- data/lib/etassia/parser/builders/requirement.rb +230 -0
- data/lib/etassia/parser/builders/sankey.rb +142 -0
- data/lib/etassia/parser/builders/sequence.rb +431 -0
- data/lib/etassia/parser/builders/sequence_blocks.rb +117 -0
- data/lib/etassia/parser/builders/sequence_box_title.rb +57 -0
- data/lib/etassia/parser/builders/state_diagram.rb +364 -0
- data/lib/etassia/parser/builders/timeline.rb +133 -0
- data/lib/etassia/parser/builders/treemap.rb +138 -0
- data/lib/etassia/parser/builders/xy_chart.rb +148 -0
- data/lib/etassia/parser/c4.rb +37 -0
- data/lib/etassia/parser/class_diagram.rb +58 -0
- data/lib/etassia/parser/class_namespaces.rb +62 -0
- data/lib/etassia/parser/class_notes.rb +54 -0
- data/lib/etassia/parser/er_diagram.rb +28 -0
- data/lib/etassia/parser/error.rb +30 -0
- data/lib/etassia/parser/flowchart.rb +87 -0
- data/lib/etassia/parser/gantt.rb +43 -0
- data/lib/etassia/parser/git_graph.rb +106 -0
- data/lib/etassia/parser/grammars/architecture.rb +165 -0
- data/lib/etassia/parser/grammars/block.rb +190 -0
- data/lib/etassia/parser/grammars/c4.rb +229 -0
- data/lib/etassia/parser/grammars/class_diagram.rb +606 -0
- data/lib/etassia/parser/grammars/common.rb +107 -0
- data/lib/etassia/parser/grammars/er_diagram.rb +232 -0
- data/lib/etassia/parser/grammars/error.rb +32 -0
- data/lib/etassia/parser/grammars/flowchart.rb +1722 -0
- data/lib/etassia/parser/grammars/gantt.rb +198 -0
- data/lib/etassia/parser/grammars/git_graph.rb +234 -0
- data/lib/etassia/parser/grammars/info.rb +53 -0
- data/lib/etassia/parser/grammars/kanban.rb +417 -0
- data/lib/etassia/parser/grammars/mermaid_unicode_text.rb +108 -0
- data/lib/etassia/parser/grammars/mindmap.rb +220 -0
- data/lib/etassia/parser/grammars/packet.rb +71 -0
- data/lib/etassia/parser/grammars/pie.rb +128 -0
- data/lib/etassia/parser/grammars/quadrant.rb +202 -0
- data/lib/etassia/parser/grammars/radar.rb +158 -0
- data/lib/etassia/parser/grammars/requirement.rb +258 -0
- data/lib/etassia/parser/grammars/sankey.rb +104 -0
- data/lib/etassia/parser/grammars/sequence.rb +940 -0
- data/lib/etassia/parser/grammars/state_diagram.rb +382 -0
- data/lib/etassia/parser/grammars/timeline.rb +153 -0
- data/lib/etassia/parser/grammars/treemap.rb +122 -0
- data/lib/etassia/parser/grammars/xy_chart.rb +119 -0
- data/lib/etassia/parser/info.rb +32 -0
- data/lib/etassia/parser/kanban.rb +131 -0
- data/lib/etassia/parser/mermaid_shapes.rb +106 -0
- data/lib/etassia/parser/metadata_yaml.rb +452 -0
- data/lib/etassia/parser/mindmap.rb +92 -0
- data/lib/etassia/parser/native/builders/architecture.rb +103 -0
- data/lib/etassia/parser/native/builders/block.rb +68 -0
- data/lib/etassia/parser/native/builders/c4.rb +16 -0
- data/lib/etassia/parser/native/builders/class_diagram.rb +63 -0
- data/lib/etassia/parser/native/builders/er_diagram.rb +47 -0
- data/lib/etassia/parser/native/builders/flowchart.rb +58 -0
- data/lib/etassia/parser/native/builders/gantt.rb +35 -0
- data/lib/etassia/parser/native/builders/gitgraph.rb +35 -0
- data/lib/etassia/parser/native/builders/info.rb +30 -0
- data/lib/etassia/parser/native/builders/kanban.rb +37 -0
- data/lib/etassia/parser/native/builders/mindmap.rb +26 -0
- data/lib/etassia/parser/native/builders/packet.rb +63 -0
- data/lib/etassia/parser/native/builders/pie.rb +82 -0
- data/lib/etassia/parser/native/builders/quadrant.rb +27 -0
- data/lib/etassia/parser/native/builders/radar.rb +84 -0
- data/lib/etassia/parser/native/builders/requirement.rb +25 -0
- data/lib/etassia/parser/native/builders/sankey.rb +18 -0
- data/lib/etassia/parser/native/builders/sequence.rb +48 -0
- data/lib/etassia/parser/native/builders/state_diagram.rb +61 -0
- data/lib/etassia/parser/native/builders/timeline.rb +46 -0
- data/lib/etassia/parser/native/builders/treemap.rb +60 -0
- data/lib/etassia/parser/native/builders/user_journey.rb +83 -0
- data/lib/etassia/parser/native/builders/xy_chart.rb +94 -0
- data/lib/etassia/parser/native/declined.rb +12 -0
- data/lib/etassia/parser/native/mermaid.json +1 -0
- data/lib/etassia/parser/native/text.rb +31 -0
- data/lib/etassia/parser/native.rb +99 -0
- data/lib/etassia/parser/packet.rb +56 -0
- data/lib/etassia/parser/pie.rb +32 -0
- data/lib/etassia/parser/quadrant.rb +77 -0
- data/lib/etassia/parser/radar.rb +99 -0
- data/lib/etassia/parser/requirement.rb +32 -0
- data/lib/etassia/parser/sankey.rb +47 -0
- data/lib/etassia/parser/sequence.rb +34 -0
- data/lib/etassia/parser/state_diagram.rb +29 -0
- data/lib/etassia/parser/timeline.rb +40 -0
- data/lib/etassia/parser/treemap.rb +25 -0
- data/lib/etassia/parser/user_journey.rb +253 -0
- data/lib/etassia/parser/xy_chart.rb +98 -0
- data/lib/etassia/parser.rb +18 -0
- data/lib/etassia/renderer/architecture.rb +232 -0
- data/lib/etassia/renderer/architecture_edge_router.rb +379 -0
- data/lib/etassia/renderer/base.rb +295 -0
- data/lib/etassia/renderer/block.rb +157 -0
- data/lib/etassia/renderer/c4.rb +294 -0
- data/lib/etassia/renderer/class_diagram.rb +671 -0
- data/lib/etassia/renderer/er_diagram.rb +842 -0
- data/lib/etassia/renderer/error.rb +131 -0
- data/lib/etassia/renderer/flowchart.rb +315 -0
- data/lib/etassia/renderer/gantt.rb +177 -0
- data/lib/etassia/renderer/git_graph.rb +224 -0
- data/lib/etassia/renderer/info.rb +59 -0
- data/lib/etassia/renderer/kanban.rb +157 -0
- data/lib/etassia/renderer/line_break_text.rb +56 -0
- data/lib/etassia/renderer/markdown_text.rb +106 -0
- data/lib/etassia/renderer/mindmap.rb +290 -0
- data/lib/etassia/renderer/packet.rb +87 -0
- data/lib/etassia/renderer/pie.rb +113 -0
- data/lib/etassia/renderer/quadrant.rb +189 -0
- data/lib/etassia/renderer/radar.rb +191 -0
- data/lib/etassia/renderer/requirement.rb +209 -0
- data/lib/etassia/renderer/sankey.rb +114 -0
- data/lib/etassia/renderer/sequence/frame_drawing.rb +169 -0
- data/lib/etassia/renderer/sequence/note_drawing.rb +69 -0
- data/lib/etassia/renderer/sequence.rb +490 -0
- data/lib/etassia/renderer/state_diagram.rb +202 -0
- data/lib/etassia/renderer/timeline.rb +134 -0
- data/lib/etassia/renderer/timeline_palette.rb +18 -0
- data/lib/etassia/renderer/treemap/section_total.rb +33 -0
- data/lib/etassia/renderer/treemap.rb +128 -0
- data/lib/etassia/renderer/user_journey.rb +209 -0
- data/lib/etassia/renderer/xy_chart.rb +180 -0
- data/lib/etassia/renderer.rb +22 -0
- data/lib/etassia/runtime.rb +125 -0
- data/lib/etassia/source/frontmatter.rb +501 -0
- data/lib/etassia/source.rb +224 -0
- data/lib/etassia/svg/arrowhead.rb +205 -0
- data/lib/etassia/svg/circle.rb +19 -0
- data/lib/etassia/svg/document.rb +106 -0
- data/lib/etassia/svg/element.rb +209 -0
- data/lib/etassia/svg/ellipse.rb +23 -0
- data/lib/etassia/svg/escaping.rb +149 -0
- data/lib/etassia/svg/group.rb +64 -0
- data/lib/etassia/svg/line.rb +21 -0
- data/lib/etassia/svg/numbers.rb +53 -0
- data/lib/etassia/svg/path.rb +67 -0
- data/lib/etassia/svg/path_geometry.rb +378 -0
- data/lib/etassia/svg/polygon.rb +25 -0
- data/lib/etassia/svg/polyline.rb +28 -0
- data/lib/etassia/svg/rect.rb +34 -0
- data/lib/etassia/svg/style.rb +50 -0
- data/lib/etassia/svg/text.rb +181 -0
- data/lib/etassia/svg/tspan.rb +48 -0
- data/lib/etassia/svg.rb +38 -0
- data/lib/etassia/text_measurement/arial_advances.rb +122 -0
- data/lib/etassia/text_measurement/dejavu_advances.rb +111 -0
- data/lib/etassia/text_measurement/times_advances.rb +64 -0
- data/lib/etassia/text_measurement/trebuchet_advances.rb +53 -0
- data/lib/etassia/text_measurement/trebuchet_kerning.rb +29 -0
- data/lib/etassia/text_measurement.rb +198 -0
- data/lib/etassia/theme/builtin/dark.yml +229 -0
- data/lib/etassia/theme/builtin/default.yml +239 -0
- data/lib/etassia/theme/builtin/high_contrast.yml +231 -0
- data/lib/etassia/theme/builtin/light.yml +229 -0
- data/lib/etassia/theme/color_palette.rb +97 -0
- data/lib/etassia/theme/completion.rb +50 -0
- data/lib/etassia/theme/effect_styles.rb +34 -0
- data/lib/etassia/theme/registry.rb +45 -0
- data/lib/etassia/theme/shape_styles.rb +34 -0
- data/lib/etassia/theme/spacing_config.rb +30 -0
- data/lib/etassia/theme/typography.rb +40 -0
- data/lib/etassia/theme.rb +73 -0
- data/lib/etassia/version.rb +5 -0
- data/lib/etassia.rb +40 -0
- metadata +865 -0
|
@@ -0,0 +1,1722 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "common"
|
|
4
|
+
require_relative "mermaid_unicode_text"
|
|
5
|
+
|
|
6
|
+
module Etassia
|
|
7
|
+
module Parser
|
|
8
|
+
module Grammars
|
|
9
|
+
# Parslet grammar for Flowchart diagrams.
|
|
10
|
+
#
|
|
11
|
+
# Handles flowchart syntax including nodes with various shapes,
|
|
12
|
+
# edges with labels, edge chaining, subgraphs, and styling directives.
|
|
13
|
+
class Flowchart < Common
|
|
14
|
+
# Mermaid lexes these as keywords, so `end.` and `1end` are not
|
|
15
|
+
# node ids even though the characters are legal.
|
|
16
|
+
#
|
|
17
|
+
# `direction`, `accTitle`, `accDescr`, `default` and `callback` are
|
|
18
|
+
# NOT among them — mmdc draws `default-->Z`. `href`, `call` and
|
|
19
|
+
# `click` are keywords too but end a word differently, so they live
|
|
20
|
+
# in `spaced_keyword` instead. `direction TB` on its own line is
|
|
21
|
+
# still not parsed here, though mmdc draws it.
|
|
22
|
+
#
|
|
23
|
+
# Longest first, because Parslet does not backtrack into an
|
|
24
|
+
# alternative that already matched: `class` ahead of `classDef`
|
|
25
|
+
# would take the first five characters and then fail the boundary.
|
|
26
|
+
RESERVED_WORDS = %w[
|
|
27
|
+
swimlane-beta interpolate flowchart linkStyle subgraph classDef
|
|
28
|
+
_parent _blank _self graph style class _top end
|
|
29
|
+
].freeze
|
|
30
|
+
|
|
31
|
+
# The words reserved in a subgraph id, which is NOT the node list.
|
|
32
|
+
# A statement keyword is plain text here (`subgraph graph [T]`
|
|
33
|
+
# renders), so those drop out. `default` is reserved here and
|
|
34
|
+
# nowhere else: `subgraph default [T]` does not render while
|
|
35
|
+
# `default-->Z` does.
|
|
36
|
+
SUBGRAPH_RESERVED = %w[
|
|
37
|
+
interpolate _parent default _blank _self _top
|
|
38
|
+
].freeze
|
|
39
|
+
private_constant :RESERVED_WORDS, :SUBGRAPH_RESERVED
|
|
40
|
+
|
|
41
|
+
# Mermaid restarts its lexer at certain characters and looks for
|
|
42
|
+
# the target again behind each restart. Several rules walk that
|
|
43
|
+
# path, so they share it here.
|
|
44
|
+
#
|
|
45
|
+
# A `repeat` rather than a recursive rule: a recursive one blew the
|
|
46
|
+
# Ruby stack on a 2000-character id that mmdc draws.
|
|
47
|
+
def token_at_lexer_restart(target)
|
|
48
|
+
(target.absent? >> restart_step).repeat >> target
|
|
49
|
+
end
|
|
50
|
+
private :token_at_lexer_restart
|
|
51
|
+
|
|
52
|
+
root(:diagram)
|
|
53
|
+
|
|
54
|
+
# Main diagram structure
|
|
55
|
+
rule(:diagram) do
|
|
56
|
+
ws? >>
|
|
57
|
+
header >>
|
|
58
|
+
ws? >>
|
|
59
|
+
statements.maybe >>
|
|
60
|
+
ws?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The direction shares the keyword's line, so `graph` newline `TD`
|
|
64
|
+
# is a diagram with a node called TD. Nothing else may follow the
|
|
65
|
+
# keyword: mmdc refuses `graph X;A`, `graph ;A` and `graph TDx`.
|
|
66
|
+
# A word direction needs a gap; a glyph one does not. mmdc renders
|
|
67
|
+
# `graph<` and `graph >` alike, and refuses `graphTD`.
|
|
68
|
+
#
|
|
69
|
+
# The two branches exist because a separator only closes the header
|
|
70
|
+
# when a direction came first — `graph TD;A` renders and `graph;A`
|
|
71
|
+
# does not. A lookahead cannot tell them apart: by the time the
|
|
72
|
+
# header ends, the direction has already been consumed.
|
|
73
|
+
# `flowchart-elk` only selects mmdc's ELK renderer -- the diagram
|
|
74
|
+
# body is ordinary flowchart syntax, and etassia has no ELK layout
|
|
75
|
+
# of its own yet (see the TODO on `Layout::Grid`), so it falls
|
|
76
|
+
# back to the same grid every other flowchart uses. `-elk` is
|
|
77
|
+
# matched here, in the grammar, rather than rewritten out of the
|
|
78
|
+
# source before parsing: a source rewrite shifts every column
|
|
79
|
+
# after the keyword, so a parse error on a `flowchart-elk`
|
|
80
|
+
# diagram would quote the wrong text and point at the wrong
|
|
81
|
+
# column. `flowchart-elk` must be tried before the plain
|
|
82
|
+
# `flowchart` alternative, or the bare keyword would match first
|
|
83
|
+
# and leave `-elk` to fail against the direction.
|
|
84
|
+
rule(:header) do
|
|
85
|
+
(
|
|
86
|
+
str("flowchart-elk") | str("flowchart") | str("graph")
|
|
87
|
+
).as(:header) >>
|
|
88
|
+
(directed_header | undirected_header)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
rule(:directed_header) do
|
|
92
|
+
header_direction.as(:direction) >>
|
|
93
|
+
(separator | (space? >> (newline | eof)))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
rule(:undirected_header) do
|
|
97
|
+
direction.absent?.as(:direction) >> space? >> (newline | eof)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
rule(:header_direction) do
|
|
101
|
+
(space.repeat(1) >> direction) |
|
|
102
|
+
glyph_direction.as(:dir_value)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# A `;` between statements on one line. `line_end` is deliberately
|
|
106
|
+
# left alone: `style_property` and `click_action` scan a value up to
|
|
107
|
+
# the physical end of the line via `line_end.absent?`, so widening it
|
|
108
|
+
# would truncate `style A fill:#f9f;stroke:#333` at the semicolon.
|
|
109
|
+
#
|
|
110
|
+
# Repeat only this rule, never `statement_end` — the `line_end` arm
|
|
111
|
+
# succeeds zero-width at EOF, so repeating that would not terminate.
|
|
112
|
+
# A comment cannot open on the same line as the separator: mmdc
|
|
113
|
+
# rejects `graph TD;%% c`, while `;` then a newline then `%% c` is
|
|
114
|
+
# ordinary.
|
|
115
|
+
rule(:separator) do
|
|
116
|
+
(semicolon >> space?).repeat(1) >> str("%%").absent?
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Node statements tolerate a space before the separator
|
|
120
|
+
# (`graph TD;A ;`). The header and a class assignment do not: mmdc
|
|
121
|
+
# rejects `graph TD ;A` and `class A foo ;B`.
|
|
122
|
+
rule(:loose_separator) { space? >> separator }
|
|
123
|
+
|
|
124
|
+
rule(:statement_end) { separator | line_end }
|
|
125
|
+
rule(:loose_statement_end) { loose_separator | line_end }
|
|
126
|
+
|
|
127
|
+
# mermaid's full set, aliases included: `BR` and `v` are another
|
|
128
|
+
# down, and three arrow glyphs stand in for the words. `v` is a
|
|
129
|
+
# word here, not a glyph — it needs the same gap `TD` does, while
|
|
130
|
+
# `<`, `>` and `^` do not.
|
|
131
|
+
rule(:direction) { (word_direction | glyph_direction).as(:dir_value) }
|
|
132
|
+
|
|
133
|
+
rule(:word_direction) do
|
|
134
|
+
str("TD") | str("TB") | str("BT") | str("BR") | str("LR") |
|
|
135
|
+
str("RL") | str("v")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# These three need no gap after the keyword — mmdc draws `graph<`.
|
|
139
|
+
rule(:glyph_direction) { match["<>^"] }
|
|
140
|
+
|
|
141
|
+
# `direction LR` inside a subgraph turns that box's contents.
|
|
142
|
+
# mmdc 11.12.0 accepts a top-level one too but does not honour it,
|
|
143
|
+
# so it is a statement anywhere and the transform decides whether
|
|
144
|
+
# anything encloses it.
|
|
145
|
+
# `statement_end`, not `line_end`: a semicolon separates this from
|
|
146
|
+
# the next statement the way it separates every other one. mmdc
|
|
147
|
+
# 11.12.0 renders `direction LR;A`, and `line_end` takes the
|
|
148
|
+
# semicolon only when a newline follows it.
|
|
149
|
+
rule(:direction_statement) do
|
|
150
|
+
str("direction").as(:direction_keyword) >> space >>
|
|
151
|
+
statement_direction >> statement_end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# The five plain words only, NOT the header's set. mmdc takes
|
|
155
|
+
# `graph <`, `graph v` and `graph BR`, and refuses every one of
|
|
156
|
+
# them after `direction` — measured on 11.12.0. Reusing the
|
|
157
|
+
# header rule here accepted three sources mermaid will not draw.
|
|
158
|
+
rule(:statement_direction) do
|
|
159
|
+
(str("TD") | str("TB") | str("BT") | str("LR") |
|
|
160
|
+
str("RL")).as(:dir_value)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
rule(:statements) do
|
|
164
|
+
((separator | statement) >> ws?).repeat(1)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
rule(:statement) do
|
|
168
|
+
accessibility_statement |
|
|
169
|
+
direction_statement |
|
|
170
|
+
subgraph_statement |
|
|
171
|
+
style_statement |
|
|
172
|
+
class_def_statement |
|
|
173
|
+
class_assignment_statement |
|
|
174
|
+
link_style_statement |
|
|
175
|
+
click_statement |
|
|
176
|
+
node_edge_statement |
|
|
177
|
+
standalone_node
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# `accTitle:` and `accDescr:` carry the text mermaid puts in the
|
|
181
|
+
# SVG's aria attributes.
|
|
182
|
+
rule(:accessibility_statement) do
|
|
183
|
+
acc_descr_block | acc_line
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Mermaid separates the keyword from its delimiter with \s*, which
|
|
187
|
+
# crosses newlines, so the gap here is `acc_gap` and not `space?`.
|
|
188
|
+
# It is whitespace only, never a comment: mmdc keeps `%% comment`
|
|
189
|
+
# in `accTitle: %% comment` as the title text.
|
|
190
|
+
#
|
|
191
|
+
# The text runs to the PHYSICAL end of the line, not to `line_end`,
|
|
192
|
+
# for the same reason: a `%%` inside a title is ordinary text.
|
|
193
|
+
# The text may be empty — mmdc renders `accTitle:` with nothing
|
|
194
|
+
# after it.
|
|
195
|
+
rule(:acc_line) do
|
|
196
|
+
(str("accTitle") | str("accDescr")).as(:acc_keyword) >>
|
|
197
|
+
acc_gap >> str(":") >> acc_gap >>
|
|
198
|
+
(newline.absent? >> any).repeat.as(:acc_text) >> (newline | eof)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Whitespace crosses newlines, and once a newline is crossed a
|
|
202
|
+
# STANDALONE comment line is skipped whole: mmdc reads
|
|
203
|
+
# `accTitle:` newline `%% c` newline `Real` as the title `Real`.
|
|
204
|
+
# A comment on the delimiter's OWN line stays text, which is why
|
|
205
|
+
# the skipping only starts after the first newline.
|
|
206
|
+
rule(:acc_gap) do
|
|
207
|
+
line_space.repeat >>
|
|
208
|
+
(newline >> (line_space | newline | comment).repeat).repeat
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Whitespace that stays on the line: mermaid's `\s` without the
|
|
212
|
+
# newline. It is wider than a space and a tab — mmdc reads
|
|
213
|
+
# `accTitle` no-break-space `:` as a title.
|
|
214
|
+
#
|
|
215
|
+
# No carriage return: the parser folds every one into a newline
|
|
216
|
+
# before the grammar sees it, the way mermaid does.
|
|
217
|
+
#
|
|
218
|
+
# Ruby's `[[:space:]]` is not the same set in either direction. It
|
|
219
|
+
# misses U+FEFF, which mmdc treats as a space, and it adds U+0085,
|
|
220
|
+
# which mmdc does not. So the set is spelled out here, once, and
|
|
221
|
+
# reused everywhere a raw `Regexp` needs the same characters (see
|
|
222
|
+
# `EDGE_ID_END_AHEAD` below) so a future correction to this
|
|
223
|
+
# hand-tuned set cannot drift between the two.
|
|
224
|
+
LINE_SPACE_CHARS = '\t\v\f\x20\u00A0\u1680' \
|
|
225
|
+
'\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF'
|
|
226
|
+
private_constant :LINE_SPACE_CHARS
|
|
227
|
+
|
|
228
|
+
rule(:line_space) { match[LINE_SPACE_CHARS] }
|
|
229
|
+
|
|
230
|
+
# Mermaid deletes whole comment LINES before it parses anything,
|
|
231
|
+
# so a `}` inside one does not close a block: `accDescr {` /
|
|
232
|
+
# `text` / `%% }` swallows every line after it.
|
|
233
|
+
#
|
|
234
|
+
# A comment starts at a newline and stops at its own line end,
|
|
235
|
+
# leaving that newline behind. The next line claims it, which is
|
|
236
|
+
# how two comment lines in a row both match, and running out of
|
|
237
|
+
# source just ends the repeat. The strip is anchored to the line
|
|
238
|
+
# start, so mmdc closes the block in `accDescr {text %% }`.
|
|
239
|
+
rule(:comment_line) do
|
|
240
|
+
newline >> line_space.repeat >> str("%%") >>
|
|
241
|
+
(newline.absent? >> any).repeat
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
rule(:acc_block_body) do
|
|
245
|
+
(comment_line | (str("}").absent? >> any)).repeat
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# The block form ends at its closing brace, with no line end
|
|
249
|
+
# required after it: mermaid renders `accDescr {Desc}A-->B`.
|
|
250
|
+
#
|
|
251
|
+
# An unterminated block runs to the end of the source rather than
|
|
252
|
+
# failing, because mmdc draws `accDescr {Unterminated` and
|
|
253
|
+
# swallows every line after it.
|
|
254
|
+
rule(:acc_descr_block) do
|
|
255
|
+
str("accDescr").as(:acc_keyword) >> acc_gap >> str("{") >>
|
|
256
|
+
acc_block_body.as(:acc_text) >>
|
|
257
|
+
(str("}") >> line_space.repeat >> semicolon.maybe).maybe
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Subgraph: subgraph id [title] ... end, or `subgraph id title`
|
|
261
|
+
# with the rest of the line as the title.
|
|
262
|
+
rule(:subgraph_statement) do
|
|
263
|
+
str("subgraph").as(:subgraph_keyword) >> space >>
|
|
264
|
+
subgraph_id.as(:subgraph_id) >>
|
|
265
|
+
subgraph_name_and_title >>
|
|
266
|
+
declaration_end >>
|
|
267
|
+
ws? >>
|
|
268
|
+
statements.maybe.as(:subgraph_statements) >>
|
|
269
|
+
ws? >>
|
|
270
|
+
str("end").as(:subgraph_end) >>
|
|
271
|
+
subgraph_close
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# What sits between the subgraph id and the end of its line.
|
|
275
|
+
#
|
|
276
|
+
# Two readings met here. This branch reads the rest of the line as
|
|
277
|
+
# the box TITLE, because the cluster renderer needs that text. The
|
|
278
|
+
# `end`-word walk on main reads extra WORDS in front of an optional
|
|
279
|
+
# bracketed title and throws the words away. Both are kept: the
|
|
280
|
+
# title is tried first, and it only wins when it reaches the end of
|
|
281
|
+
# the declaration, so `subgraph A Title` is titled `A Title` while
|
|
282
|
+
# `subgraph A B [T]` still falls through to the word walk and is
|
|
283
|
+
# titled `T`.
|
|
284
|
+
rule(:subgraph_name_and_title) do
|
|
285
|
+
(subgraph_title >> declaration_end.present?) |
|
|
286
|
+
subgraph_trailing_name
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# The declaration owns the rest of its line. A bracketed title has
|
|
290
|
+
# to be the last thing on it — mmdc refuses `subgraph s [T] A` and
|
|
291
|
+
# `subgraph s [T] %% note`, while `subgraph s %% note` is fine
|
|
292
|
+
# because the comment simply becomes the title text.
|
|
293
|
+
# All the whitespace, not just the first space: consuming one let
|
|
294
|
+
# `subgraph s [Title] A` slip past the bracket guard below and be
|
|
295
|
+
# read as free text, which mmdc refuses.
|
|
296
|
+
# A bracketed title may sit straight against the id — mmdc renders
|
|
297
|
+
# `subgraph s[Title]` — while a free one needs a gap to start.
|
|
298
|
+
# The gap is captured, not just skipped. A free title's label is
|
|
299
|
+
# the source from the id onwards, so `subgraph s Title` is
|
|
300
|
+
# labelled `s Title` and collapsing the run would misquote it.
|
|
301
|
+
rule(:subgraph_title) do
|
|
302
|
+
(space.repeat >> bracketed_title) |
|
|
303
|
+
(space.repeat(1).as(:subgraph_free_gap) >> free_title)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
rule(:bracketed_title) { bracket_title >> bracket_title_end }
|
|
307
|
+
|
|
308
|
+
rule(:bracket_title) do
|
|
309
|
+
lbracket >> (rbracket.absent? >> any).repeat(1).as(:subgraph_title) >>
|
|
310
|
+
rbracket
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Nothing at all may follow a bracketed title, not even a space:
|
|
314
|
+
# mmdc refuses `subgraph s [Title] ` before the newline.
|
|
315
|
+
rule(:bracket_title_end) do
|
|
316
|
+
((semicolon_run >> no_comment) | newline | eof).present?
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# A flat character class, NOT `declaration_end.absent? >> any`.
|
|
320
|
+
# That re-ran `space?` at every byte, so a title carrying a run of
|
|
321
|
+
# spaces parsed in quadratic time — 4k spaces took 3.7 seconds and
|
|
322
|
+
# 16k took over a minute, on a source mmdc renders.
|
|
323
|
+
#
|
|
324
|
+
# The structural characters are excluded because mermaid refuses
|
|
325
|
+
# them here: `subgraph s Title (More)`, `<More>` and `{More}` are
|
|
326
|
+
# all rejected.
|
|
327
|
+
rule(:free_title) do
|
|
328
|
+
lbracket.absent? >>
|
|
329
|
+
(title_run >> (space.repeat(1) >> title_run).repeat)
|
|
330
|
+
.as(:subgraph_free_title)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
rule(:title_run) { comment_word | title_word }
|
|
334
|
+
|
|
335
|
+
# Mermaid only strips a comment at the start of a line, so behind
|
|
336
|
+
# the id it stays text and owns the rest of the line.
|
|
337
|
+
rule(:comment_word) do
|
|
338
|
+
str("%%") >> (newline.absent? >> any).repeat
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Main's guarded name word, not a loose character run. A free
|
|
342
|
+
# title's words are the same words a trailing name may hold, so
|
|
343
|
+
# `subgraph A end` and `subgraph A interpolate` stay refused while
|
|
344
|
+
# the text still becomes the box title.
|
|
345
|
+
rule(:title_word) { subgraph_name_word }
|
|
346
|
+
|
|
347
|
+
# A semicolon run separates statements here as it does after `end`,
|
|
348
|
+
# and a comment is not a statement: mmdc renders `subgraph s;;A`
|
|
349
|
+
# and refuses `subgraph s; %% note`.
|
|
350
|
+
rule(:declaration_end) do
|
|
351
|
+
line_space.repeat >> ((semicolon_run >> no_comment) | newline | eof)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
rule(:subgraph_close) do
|
|
355
|
+
(space.repeat(1) >> close_at_space) |
|
|
356
|
+
(space? >> (close_at_line_end | close_at_semicolon))
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Whitespace alone separates `end` from what follows: mmdc renders
|
|
360
|
+
# `end B-->C`. Referencing `statement` here would be recursive, so
|
|
361
|
+
# the guard is what a statement cannot start with — a comment, a
|
|
362
|
+
# separator, or the end of the line.
|
|
363
|
+
rule(:close_at_space) do
|
|
364
|
+
(str("%%") | semicolon | newline | eof).absent?
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# `end` finishes its line, with any number of trailing semicolons.
|
|
368
|
+
# Nothing else may follow, so `end %% note` is refused here — mmdc
|
|
369
|
+
# takes `end` and then a comment on its own line.
|
|
370
|
+
rule(:close_at_line_end) do
|
|
371
|
+
semicolon_run.maybe >> space? >> (newline | eof)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# A semicolon separates statements, so the next one may sit on the
|
|
375
|
+
# same line: mmdc renders `end; B-->C` and `end;end`. A comment is
|
|
376
|
+
# still not a statement, and `end; %% note` is refused.
|
|
377
|
+
rule(:close_at_semicolon) do
|
|
378
|
+
semicolon_run >> str("%%").absent?
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
rule(:semicolon_run) { (semicolon >> space?).repeat(1) }
|
|
382
|
+
|
|
383
|
+
# A comment is not a statement, so it cannot follow a separator on
|
|
384
|
+
# the same line: mmdc refuses `end; %% note` and
|
|
385
|
+
# `subgraph s; %% note`.
|
|
386
|
+
rule(:no_comment) { str("%%").absent? }
|
|
387
|
+
|
|
388
|
+
# An unbracketed name runs on past the first space: mermaid titles
|
|
389
|
+
# `subgraph 1 abc` "1 abc". Every trailing word carries the same
|
|
390
|
+
# guards as the first one, `end` included, because they all go
|
|
391
|
+
# through `subgraph_name_word`. So mmdc's refusals
|
|
392
|
+
# (`subgraph A interpolate`, `subgraph A 1default`,
|
|
393
|
+
# `subgraph A .-`) are refused here too, while `subgraph A end [T]`
|
|
394
|
+
# and `subgraph A end B` draw: what ends a subgraph is `end` with
|
|
395
|
+
# the LINE behind it, the position and not the word.
|
|
396
|
+
#
|
|
397
|
+
# The charset is narrower than mermaid's title text, so only id
|
|
398
|
+
# words are consumed. mmdc titles `subgraph A B:C` "A B:C" and this
|
|
399
|
+
# refuses it — left for a change that models mermaid's own
|
|
400
|
+
# `textNoTags` production. Nothing reads the name yet, so the extra
|
|
401
|
+
# words are consumed, not kept.
|
|
402
|
+
#
|
|
403
|
+
# The bracketed title comes AFTER those words and this is the only
|
|
404
|
+
# rule that reads it, so `subgraph A B [T]` and `subgraph A B C [T]`
|
|
405
|
+
# draw. `subgraph A [T] X` stays refused, as mmdc refuses it:
|
|
406
|
+
# nothing may follow the title on the line.
|
|
407
|
+
rule(:subgraph_trailing_name) do
|
|
408
|
+
(space.repeat(1) >> subgraph_name_word).repeat >>
|
|
409
|
+
subgraph_title.maybe
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
rule(:subgraph_end_word) { str("end") >> word_boundary }
|
|
413
|
+
|
|
414
|
+
# Style: style nodeId fill:#f9f
|
|
415
|
+
rule(:style_statement) do
|
|
416
|
+
str("style").as(:style_keyword) >> space >>
|
|
417
|
+
reserved_keyword.absent? >> node_id.as(:style_target) >>
|
|
418
|
+
(space >> style_property_list).as(:style_props) >>
|
|
419
|
+
statement_end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# What a `;` does inside a declaration turns on the value, not on
|
|
423
|
+
# the text after it:
|
|
424
|
+
#
|
|
425
|
+
# style A fill:red;B -> nodes A and B (the `;` ends the style)
|
|
426
|
+
# style A fill:#f9f;B -> node A only (the `;` is value text)
|
|
427
|
+
#
|
|
428
|
+
# The plain branch is guarded rather than merely second, so a bad
|
|
429
|
+
# hashed tail fails the statement instead of falling through and
|
|
430
|
+
# drawing a node mermaid refuses.
|
|
431
|
+
rule(:style_property_list) do
|
|
432
|
+
empty_comma_item.absent? >>
|
|
433
|
+
(hashed_property_list | (hashed_head.absent? >> style_property))
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# mermaid joins declarations with a comma and refuses an empty one:
|
|
437
|
+
# `,a:b`, `a:b,,c:d` and `a:b,`. After a `#` a `;` no longer ends
|
|
438
|
+
# the item, so `fill:#f9f,;B` is fine while `fill:red,;B` is not.
|
|
439
|
+
rule(:empty_comma_item) do
|
|
440
|
+
comma |
|
|
441
|
+
(hash_char.maybe >>
|
|
442
|
+
hash_terminated_run(comma_gap).repeat >>
|
|
443
|
+
(comma_gap | (hash_char >> hashed_comma_gap_scan)))
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
rule(:hashed_comma_gap_scan) do
|
|
447
|
+
declaration_run(hashed_comma_gap).repeat >> hashed_comma_gap
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
rule(:comma_gap) do
|
|
451
|
+
hashed_comma_gap | (comma >> semicolon)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
rule(:hashed_comma_gap) { comma >> (comma | newline | eof) }
|
|
455
|
+
|
|
456
|
+
# After a `#` the declaration carries at most one `;`. mmdc
|
|
457
|
+
# takes `fill:#f9f;stroke:#333` and `fill:#f9f;B`, and refuses
|
|
458
|
+
# `fill:#f9f;B;C` and `fill:#f9f;;B`, so the tail has to end at the
|
|
459
|
+
# line rather than hand a second `;` back as a separator.
|
|
460
|
+
rule(:hashed_property_list) do
|
|
461
|
+
hashed_head >> declaration_run.repeat >>
|
|
462
|
+
((semicolon >> space? >> hashed_tail) | semicolon.absent?)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# The `#` has to arrive before the first `;` for the swallow to
|
|
466
|
+
# start: mmdc reads `style A fill:red;stroke:#333` as a style plus a
|
|
467
|
+
# node, because the `;` comes first.
|
|
468
|
+
# A `#` after a space, as in `stroke: #fff`, is ordinary text.
|
|
469
|
+
rule(:hashed_head) do
|
|
470
|
+
hash_char.maybe >> hash_terminated_run.repeat >> hash_char
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# What may follow that one `;` is a style component, not a node and
|
|
474
|
+
# not an edge, so it stops at anything structural. It ends where
|
|
475
|
+
# `line_end` ends and nowhere else: a `%%` here is ordinary
|
|
476
|
+
# declaration text.
|
|
477
|
+
rule(:hashed_tail) do
|
|
478
|
+
declaration_run(structural_token).repeat >>
|
|
479
|
+
space? >> (newline | eof).present?
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# What mermaid keeps for shapes and edges. It is refused wherever
|
|
483
|
+
# mermaid expects a bare word — in a style declaration after a `#`
|
|
484
|
+
# and in a callback name alike.
|
|
485
|
+
rule(:structural_token) { compound_token | structural_char }
|
|
486
|
+
|
|
487
|
+
# Some of what mermaid reserves is longer than one character, and
|
|
488
|
+
# every character in it is legal on its own. In a style tail and in
|
|
489
|
+
# a callback name alike, `B-C`, `B.C` and `B::C` are ordinary text
|
|
490
|
+
# while `B--C`, `B-.C` and `B:::C` are errors.
|
|
491
|
+
rule(:compound_token) { str(":::") | str("--") | str("-.") }
|
|
492
|
+
|
|
493
|
+
# The single-character half of the set. Every other printable ASCII
|
|
494
|
+
# character is fine in both places.
|
|
495
|
+
rule(:structural_char) { match['\\[\\]{}()<>|~@=^'] }
|
|
496
|
+
|
|
497
|
+
# A run of declaration text, or of interior spaces, matched whole
|
|
498
|
+
# rather than one character at a time: checking `line_end` (hence
|
|
499
|
+
# the unbounded `space?` inside it) at every character re-scans to
|
|
500
|
+
# the end of a space run from every offset inside it — quadratic in
|
|
501
|
+
# the run's length. A space run costs one pass here instead, kept
|
|
502
|
+
# or dropped exactly as `line_end.absent?` did: followed by more
|
|
503
|
+
# content it is ordinary text, running straight to `;`/newline/eof
|
|
504
|
+
# it is not. `extra_guards` exclude characters a caller treats as
|
|
505
|
+
# its own boundary, on top of `;` and a line's end.
|
|
506
|
+
def declaration_run(*extra_guards)
|
|
507
|
+
guards = extra_guards + [semicolon, newline, space]
|
|
508
|
+
guarded_declaration_char(guards).repeat(1) | declaration_space
|
|
509
|
+
end
|
|
510
|
+
private :declaration_run
|
|
511
|
+
|
|
512
|
+
def guarded_declaration_char(guards)
|
|
513
|
+
guards.map(&:absent?).reduce(:>>) >> any
|
|
514
|
+
end
|
|
515
|
+
private :guarded_declaration_char
|
|
516
|
+
|
|
517
|
+
def declaration_space
|
|
518
|
+
space.repeat(1) >> (newline | eof).absent?
|
|
519
|
+
end
|
|
520
|
+
private :declaration_space
|
|
521
|
+
|
|
522
|
+
# Same shape as `declaration_run`, but a run also stops at a bare
|
|
523
|
+
# `#`: a space immediately before one, as in `stroke: #fff`, is
|
|
524
|
+
# ordinary text, so both are swallowed together — otherwise the `#`
|
|
525
|
+
# would be mistaken for the marker `hashed_head`/`empty_comma_item`
|
|
526
|
+
# scan for.
|
|
527
|
+
def hash_terminated_run(*extra_guards)
|
|
528
|
+
guards = extra_guards + [hash_char, semicolon, newline, space]
|
|
529
|
+
guarded_declaration_char(guards).repeat(1) | hash_terminated_space
|
|
530
|
+
end
|
|
531
|
+
private :hash_terminated_run
|
|
532
|
+
|
|
533
|
+
def hash_terminated_space
|
|
534
|
+
space.repeat(1) >> (hash_char | (newline | eof).absent?)
|
|
535
|
+
end
|
|
536
|
+
private :hash_terminated_space
|
|
537
|
+
|
|
538
|
+
# Permissive: mermaid takes `style A red`, `style A fill:` and
|
|
539
|
+
# `style A fill :red`, so `name:value` is not required. One
|
|
540
|
+
# character minimum though — mmdc refuses a bare `style A`.
|
|
541
|
+
#
|
|
542
|
+
# Without a hash the `;` always ends the statement, so a second
|
|
543
|
+
# declaration is left to be parsed as one. mermaid draws
|
|
544
|
+
# `style A fill:red;stroke:blue` as a style plus a node called
|
|
545
|
+
# `stroke:blue`; a node id here takes no colon, so we refuse the
|
|
546
|
+
# line rather than draw a diagram one node short.
|
|
547
|
+
rule(:style_property) { declaration_run.repeat(1) }
|
|
548
|
+
|
|
549
|
+
# `linkStyle 0,1 stroke:red` styles edges by position, and
|
|
550
|
+
# `linkStyle default ...` every one. Mermaid takes the numbers with
|
|
551
|
+
# no space around a comma, and refuses `linkStyle 0 ,1 ...`.
|
|
552
|
+
rule(:link_style_statement) do
|
|
553
|
+
str("linkStyle").as(:link_style_keyword) >> line_space >>
|
|
554
|
+
link_style_targets.as(:link_targets) >> line_space >>
|
|
555
|
+
link_style_props >> statement_end.as(:link_end)
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
# `interpolate basis` goes first, with one space before the curve
|
|
559
|
+
# name: mermaid refuses `interpolate basis` and
|
|
560
|
+
# `stroke:red interpolate basis`. The builder refuses the word
|
|
561
|
+
# `interpolate` or `default` anywhere else.
|
|
562
|
+
rule(:link_style_props) do
|
|
563
|
+
(str("interpolate") >> line_space >>
|
|
564
|
+
match["^#{LINE_SPACE_CHARS}\n;"].repeat(1).as(:link_curve) >>
|
|
565
|
+
(line_space.repeat(1) >> style_property_list.as(:link_props))
|
|
566
|
+
.maybe) |
|
|
567
|
+
style_property_list.as(:link_props)
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
rule(:link_style_targets) do
|
|
571
|
+
str("default") | (link_index >> (comma >> link_index).repeat)
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
rule(:link_index) { match["0-9"].repeat(1) }
|
|
575
|
+
|
|
576
|
+
# ClassDef: classDef className fill:#f9f
|
|
577
|
+
rule(:class_def_statement) do
|
|
578
|
+
str("classDef").as(:classdef_keyword) >> space >>
|
|
579
|
+
class_name_list.as(:class_name) >>
|
|
580
|
+
(space >> style_property_list).as(:class_props) >>
|
|
581
|
+
statement_end
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# `classDef a,b props` styles both classes.
|
|
585
|
+
rule(:class_name_list) do
|
|
586
|
+
identifier >> (comma >> identifier).repeat
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# Class assignment: class nodeId className
|
|
590
|
+
rule(:class_assignment_statement) do
|
|
591
|
+
str("class").as(:class_keyword) >> space >>
|
|
592
|
+
reserved_keyword.absent? >> node_id.as(:class_target) >> space >>
|
|
593
|
+
identifier.as(:class_name) >>
|
|
594
|
+
statement_end
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# Click: click nodeId href (may not fully implement, just parse)
|
|
598
|
+
#
|
|
599
|
+
# The gap after the keyword is a RUN of whitespace, unlike the one
|
|
600
|
+
# before the action. mermaid opens its click state on `"click"\s+`,
|
|
601
|
+
# so mmdc draws `click A "url"` where a single space refused it.
|
|
602
|
+
rule(:click_statement) do
|
|
603
|
+
str("click").as(:click_keyword) >> space.repeat(1) >>
|
|
604
|
+
click_target.as(:click_target) >>
|
|
605
|
+
(space >> click_action.as(:click_action)) >>
|
|
606
|
+
statement_end
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# An action is required — mmdc rejects a bare `click A`. mermaid
|
|
610
|
+
# takes exactly four shapes here and anything else is an error, so
|
|
611
|
+
# the action is spelled out rather than scanned to the end of the
|
|
612
|
+
# line.
|
|
613
|
+
#
|
|
614
|
+
# Every gap between these tokens is ONE space or ONE tab. mermaid
|
|
615
|
+
# counts the characters: `click A href "u"`, `"u" "tip"` and
|
|
616
|
+
# `"u" _blank` are all errors. Only inside a `call` is whitespace
|
|
617
|
+
# free-form.
|
|
618
|
+
rule(:click_action) do
|
|
619
|
+
callback_action | href_action | link_action | callback_name_action
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
# `click A call cb(foo;bar)` — the semicolon belongs to the callback
|
|
623
|
+
# argument list, so the parens are only special after `call`.
|
|
624
|
+
#
|
|
625
|
+
# The name runs to the opening paren, dots and semicolons included:
|
|
626
|
+
# mmdc reads `call cb;B()` as one callback named `cb;B`, and takes
|
|
627
|
+
# `call ns.cb()` and any run of spaces after `call`.
|
|
628
|
+
#
|
|
629
|
+
# Only a quoted tooltip may trail the parens. mmdc refuses
|
|
630
|
+
# `call cb() _blank` and `call cb() nope`, which the old open-ended
|
|
631
|
+
# tail accepted.
|
|
632
|
+
rule(:callback_action) do
|
|
633
|
+
call_opener >> callback_name >>
|
|
634
|
+
callback_gap? >> lparen >>
|
|
635
|
+
(rparen.absent? >> any).repeat >> rparen >>
|
|
636
|
+
(space >> quoted_run).maybe
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
# Once `call` has opened a callback the parens are compulsory: mmdc
|
|
640
|
+
# exits 1 on `click A call cb`.
|
|
641
|
+
rule(:call_opener) { str("call") >> callback_gap }
|
|
642
|
+
|
|
643
|
+
# mermaid stops caring about line structure inside a callback, so
|
|
644
|
+
# whitespace and comments are ignorable after `call` and again
|
|
645
|
+
# before the `(`. mmdc renders all four of `call cb()`,
|
|
646
|
+
# `call` nl `cb()`, `call cb` nl `()` and `call cb` nl `%% c` nl
|
|
647
|
+
# `()`.
|
|
648
|
+
rule(:callback_gap) { (space | newline | comment).repeat(1) }
|
|
649
|
+
rule(:callback_gap?) { callback_gap.maybe }
|
|
650
|
+
|
|
651
|
+
# The name still stops at the end of its line. mermaid keeps reading
|
|
652
|
+
# past it — `call cb` nl `B --> C` nl `D()` swallows the whole edge
|
|
653
|
+
# and draws neither B nor C — and following it there would let a
|
|
654
|
+
# callback eat statements we can still draw.
|
|
655
|
+
rule(:callback_name) do
|
|
656
|
+
(lparen.absent? >> line_end.absent? >> any).repeat(1)
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
# `href` takes a quoted url, then at most a quoted tooltip and a
|
|
660
|
+
# link target, in that order. mmdc refuses `href` on its own,
|
|
661
|
+
# `href cb`, `href "u" nope` and a third quoted run.
|
|
662
|
+
rule(:href_action) { str("href") >> space >> quoted_run >> link_tail }
|
|
663
|
+
|
|
664
|
+
# The same shape without the keyword: `click A "u" "tip" _blank`.
|
|
665
|
+
rule(:link_action) { quoted_run >> link_tail }
|
|
666
|
+
|
|
667
|
+
# A quoted tooltip then a link target, both optional and in that
|
|
668
|
+
# order. mmdc refuses `"u" _blank "tip"` and a third quoted run.
|
|
669
|
+
rule(:link_tail) do
|
|
670
|
+
(space >> quoted_run).maybe >> (space >> link_target).maybe
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# A bare token is a callback name, and only a quoted tooltip may
|
|
674
|
+
# follow it. mmdc draws `click A clickByFlow "Add a div"` and
|
|
675
|
+
# `click A http://x`, and refuses `click A cb _blank` and
|
|
676
|
+
# `click A my callback`.
|
|
677
|
+
rule(:callback_name_action) do
|
|
678
|
+
bare_token >> (space >> quoted_run).maybe
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
# A bare token runs to the first space, `;` or structural token,
|
|
682
|
+
# so `click A cb()` and `click A cb--x` are errors and
|
|
683
|
+
# `click A http://x;B` draws both nodes. A quote only opens a url
|
|
684
|
+
# when it comes first — mmdc draws `click A cb"x`. A keyword is
|
|
685
|
+
# not a callback name either: mmdc refuses `click A href`,
|
|
686
|
+
# `click A end` and `click A _blank`, and takes `click A callback`
|
|
687
|
+
# and `click A clickByFlow`.
|
|
688
|
+
rule(:bare_token) do
|
|
689
|
+
reserved_keyword.absent? >> str('"').absent? >>
|
|
690
|
+
token_char >> token_char.repeat
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
rule(:token_char) do
|
|
694
|
+
space.absent? >> line_end.absent? >> semicolon.absent? >>
|
|
695
|
+
structural_token.absent? >> any
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
# Never empty. mmdc refuses `click A ""` and every empty tooltip,
|
|
699
|
+
# and takes `click A " "`.
|
|
700
|
+
rule(:quoted_run) do
|
|
701
|
+
str('"') >> (str('"').absent? >> any).repeat(1) >> str('"')
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
# A statement keyword is not a node id. Without this a malformed
|
|
705
|
+
# directive falls through to the node rules: `click ;B` would
|
|
706
|
+
# produce nodes `click` and `B`, and mmdc rejects the whole source.
|
|
707
|
+
#
|
|
708
|
+
# The same words that end an id end a statement, so this asks
|
|
709
|
+
# `node_keyword` rather than keeping a second list to drift out of
|
|
710
|
+
# step. Every call site spells `reserved_keyword.absent? >> node_id`.
|
|
711
|
+
rule(:reserved_keyword) { node_keyword }
|
|
712
|
+
|
|
713
|
+
# `click`, `href` and `call` are directive words wherever a space,
|
|
714
|
+
# a newline or the end of input follows, and ordinary node ids
|
|
715
|
+
# before a `;` or a shape. mmdc refuses `href --- B`, `call --- B`
|
|
716
|
+
# and a bare `href`, and draws `href;B` and `href[x]` as nodes — so
|
|
717
|
+
# the reservation hangs off the boundary, not the word.
|
|
718
|
+
#
|
|
719
|
+
# NOT `line_end`: it swallows a trailing semicolon, and mmdc draws
|
|
720
|
+
# `graph TD;click;B` as two nodes.
|
|
721
|
+
#
|
|
722
|
+
# A comment is not one of the endings either. Behind a word it is
|
|
723
|
+
# not a comment at all, because mermaid only strips one at the
|
|
724
|
+
# start of a line: mmdc reads `href%%c` as a SINGLE node.
|
|
725
|
+
#
|
|
726
|
+
# The end of the source IS one. Mermaid appends a newline before it
|
|
727
|
+
# lexes, so a word at the very end is followed by one after all.
|
|
728
|
+
#
|
|
729
|
+
# These three words also guard an id, which is why `node_keyword`
|
|
730
|
+
# reaches for this rule rather than restating it.
|
|
731
|
+
rule(:spaced_keyword) do
|
|
732
|
+
(str("click") | str("href") | str("call")) >>
|
|
733
|
+
(space | newline | eof).present?
|
|
734
|
+
end
|
|
735
|
+
|
|
736
|
+
# mermaid's link targets, as they appear at the END of a click
|
|
737
|
+
# action (`click A "url" "_blank"`). They are reserved as node ids
|
|
738
|
+
# too, but that is `RESERVED_WORDS`' job now — this rule is only
|
|
739
|
+
# the click-action tail, which is why it survives on its own.
|
|
740
|
+
rule(:link_target) do
|
|
741
|
+
(str("_parent") | str("_blank") | str("_self") | str("_top")) >>
|
|
742
|
+
word_boundary
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
# A word ends where the next character cannot continue it, and
|
|
746
|
+
# mermaid counts an accent as the end: `1endé` is refused while
|
|
747
|
+
# `1end_`, `1end2` and `1endx` are all ids.
|
|
748
|
+
rule(:word_boundary) { match["a-zA-Z0-9_"].absent? }
|
|
749
|
+
|
|
750
|
+
# One `x` or `o` and then a link body, with nothing between them.
|
|
751
|
+
rule(:flush_link_marker) { match["xo"] >> link_body }
|
|
752
|
+
|
|
753
|
+
# Node with optional shape and edges
|
|
754
|
+
rule(:node_edge_statement) do
|
|
755
|
+
reserved_keyword.absent? >>
|
|
756
|
+
node_with_shape.as(:node) >> amp_group.as(:group) >>
|
|
757
|
+
edge_chain.maybe.as(:edges) >>
|
|
758
|
+
loose_statement_end
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
# Standalone node (just an identifier)
|
|
762
|
+
rule(:standalone_node) do
|
|
763
|
+
reserved_keyword.absent? >> node_id.as(:node_id) >>
|
|
764
|
+
loose_statement_end
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
# Node with its optional shape, inline class and metadata.
|
|
768
|
+
# Every optional part is captured whether or not it is present, so
|
|
769
|
+
# the tree has one shape instead of one per combination. Parslet
|
|
770
|
+
# omits a `.maybe` that wraps its own `.as`, which is why the
|
|
771
|
+
# transform previously needed a rule per combination.
|
|
772
|
+
# On its second branch the `:shape` slot comes back nil:
|
|
773
|
+
# `dot_absent` is a zero-width lookahead and captures nothing. It
|
|
774
|
+
# is there only to refuse the flush dot a bare name would
|
|
775
|
+
# otherwise swallow.
|
|
776
|
+
#
|
|
777
|
+
# The shape opening abuts the id, with no gap of any kind: mmdc
|
|
778
|
+
# refuses `A [B]`, `A\t[B]`, `A\n[B]` and `A %% c\n[B]` alike.
|
|
779
|
+
#
|
|
780
|
+
# `flush_link_marker.absent?` on the first line is the other
|
|
781
|
+
# refusal. A lone `x` or `o` sitting flush against a link body is
|
|
782
|
+
# mermaid's link-START marker, not a node called `x`: mmdc reads
|
|
783
|
+
# `x===B` as a link with nothing on its left and refuses it, while
|
|
784
|
+
# it draws `xx===B` and `x === B` — the marker has to be one
|
|
785
|
+
# character and flush. This rule is the edge TARGET as well as the
|
|
786
|
+
# statement's own node, so the guard governs both positions, and
|
|
787
|
+
# mmdc refuses `A --> x---B` for the same reason it refuses
|
|
788
|
+
# `x===B`.
|
|
789
|
+
rule(:node_with_shape) do
|
|
790
|
+
flush_link_marker.absent? >>
|
|
791
|
+
node_id.as(:node_id) >>
|
|
792
|
+
(node_shape.as(:shape) | dot_absent.as(:shape)) >>
|
|
793
|
+
(inline_class >> dot_absent).maybe.as(:inline_class) >>
|
|
794
|
+
node_metadata.maybe.as(:metadata)
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
# A shape and a `@{...}` block each close the node's name, and a
|
|
798
|
+
# link may then sit flush against it: mmdc draws `A[x].-B` and
|
|
799
|
+
# `A@{ shape: rect }.->B`. A bare name is still growing, and so is
|
|
800
|
+
# the class name of `A:::c` — each takes a `.` in mermaid, so mmdc
|
|
801
|
+
# reads the dot of `A.-B` and of `A[x]:::c.-B` as name text and
|
|
802
|
+
# refuses `A.->B`, `A:::c.->B` and `A[x]:::c.->B` outright.
|
|
803
|
+
#
|
|
804
|
+
# `node_id` carries a dot the same way a bare name does — see its
|
|
805
|
+
# own comment above — so `A.-B` reads exactly as mmdc reads it,
|
|
806
|
+
# as the single node `"A.-B"`, not a link: that is the no-shape
|
|
807
|
+
# fallback above, where `dot_absent` stands in for `:shape`
|
|
808
|
+
# itself. A real shape bypasses the guard outright — `A[x].-B`
|
|
809
|
+
# and `A@{ shape: rect }.->B` both draw as mmdc draws them —
|
|
810
|
+
# unless an inline class follows the shape, which is the second
|
|
811
|
+
# place the guard runs. That second use is where Etassia and mmdc
|
|
812
|
+
# part ways: the other five — `A:::c.-B`, `A[x]:::c.-B` and the
|
|
813
|
+
# three `.->` forms — are still refused rather than read as a
|
|
814
|
+
# link.
|
|
815
|
+
rule(:dot_absent) { str(".").absent? }
|
|
816
|
+
|
|
817
|
+
# `D@{ shape: rounded, label: "DD" }` — mermaid's newer way of
|
|
818
|
+
# giving a node a shape or a label, usable as a statement of its own
|
|
819
|
+
# or as a suffix inside an edge chain.
|
|
820
|
+
# The body is captured raw and handed to YAML, because that is what
|
|
821
|
+
# mermaid does with it. A single-line body is a flow mapping and a
|
|
822
|
+
# multiline one is block YAML, so commas are required on one line
|
|
823
|
+
# and rejected across several — a grammar rule cannot express that
|
|
824
|
+
# without reimplementing YAML badly.
|
|
825
|
+
#
|
|
826
|
+
# A metadata block cannot be the final source before an edge id:
|
|
827
|
+
# mermaid leaves the following `e1@` out of its LINK_ID state and
|
|
828
|
+
# refuses the statement, though an ordinary link may still follow.
|
|
829
|
+
rule(:node_metadata) do
|
|
830
|
+
str("@{") >> metadata_body.as(:body) >> str("}") >>
|
|
831
|
+
(line_space.repeat(1) >> edge_id_ahead).absent?
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
# An unmatched `"` is not body text. mermaid's lexer stays in its
|
|
835
|
+
# string state to the end of the block and refuses the source;
|
|
836
|
+
# falling through to the generic branch took `A@{ label: a"b }`,
|
|
837
|
+
# which mmdc rejects.
|
|
838
|
+
#
|
|
839
|
+
# A caret is not body text either. mermaid takes the run between
|
|
840
|
+
# the braces with `[^}^"]+`, so a `^` outside a quoted value ends
|
|
841
|
+
# the block early and mmdc refuses `A@{ label: a^b }`. Inside the
|
|
842
|
+
# quotes the rule is `[^"]+`, and `A@{ label: "a^b" }` draws.
|
|
843
|
+
rule(:metadata_body) do
|
|
844
|
+
(metadata_comment_line | metadata_quoted |
|
|
845
|
+
(metadata_stop.absent? >> any)).repeat
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
rule(:metadata_stop) { str('"') | str("}") | str("^") }
|
|
849
|
+
|
|
850
|
+
# Mermaid strips comments before lexing, so their stops are text.
|
|
851
|
+
# `line_space` rather than Ruby's `\s`: the indent has to be the set
|
|
852
|
+
# mmdc calls whitespace, or a comment indented with a no-break space
|
|
853
|
+
# keeps its `%%` in the label and its `}` closes the block.
|
|
854
|
+
#
|
|
855
|
+
# `%%{` opens a directive rather than a comment and is left alone,
|
|
856
|
+
# which is what makes etassia refuse `A@{ shape: rect` nl `%%{ x }`
|
|
857
|
+
# nl `}` the way mmdc does.
|
|
858
|
+
rule(:metadata_comment_line) do
|
|
859
|
+
newline >> line_space.repeat >> str("%%") >> str("{").absent? >>
|
|
860
|
+
(newline.absent? >> any).repeat(1)
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
# A double-quoted run is skipped whole so a brace inside it is
|
|
864
|
+
# text. Only the double quote does this: mermaid's metadata lexer
|
|
865
|
+
# has one string state and `"` opens it, so `label: 'a}b'` ends the
|
|
866
|
+
# block at that brace and mmdc rejects the line.
|
|
867
|
+
# A comment line inside the quotes is skipped first, because the
|
|
868
|
+
# quote that closes this run cannot be one mermaid already deleted:
|
|
869
|
+
# `label: "one` nl `%% has " quote` nl `two"` is one label to mmdc.
|
|
870
|
+
rule(:metadata_quoted) do
|
|
871
|
+
str('"') >>
|
|
872
|
+
(metadata_comment_line | (str('"').absent? >> any)).repeat >>
|
|
873
|
+
str('"')
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
# Inline class syntax: :::className
|
|
877
|
+
rule(:inline_class) do
|
|
878
|
+
str(":::") >> identifier
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
# Node shape with label
|
|
882
|
+
rule(:node_shape) do
|
|
883
|
+
# Order matters: try longer delimiters first
|
|
884
|
+
shape_triple_circle |
|
|
885
|
+
shape_stadium |
|
|
886
|
+
shape_subroutine |
|
|
887
|
+
shape_cylindrical |
|
|
888
|
+
shape_double_circle |
|
|
889
|
+
shape_hexagon |
|
|
890
|
+
shape_parallelogram |
|
|
891
|
+
shape_parallelogram_alt |
|
|
892
|
+
shape_trapezoid |
|
|
893
|
+
shape_trapezoid_alt |
|
|
894
|
+
shape_asymmetric |
|
|
895
|
+
shape_rectangle |
|
|
896
|
+
shape_rounded |
|
|
897
|
+
shape_rhombus
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
# Shape definitions (15+ shapes)
|
|
901
|
+
# Rectangle: [label]
|
|
902
|
+
# A bare `)` is still a round-node closer in Mermaid's text lexer,
|
|
903
|
+
# even while a rectangular label is open. Quoting shields it, so
|
|
904
|
+
# `A[hello ) world]` is invalid but `A["hello ) world"]` renders.
|
|
905
|
+
rule(:shape_rectangle) do
|
|
906
|
+
lbracket.as(:open) >>
|
|
907
|
+
(
|
|
908
|
+
quoted_label | ((rbracket | rparen).absent? >> any)
|
|
909
|
+
).repeat.as(:label) >>
|
|
910
|
+
rbracket.as(:close)
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
# Rounded: (label)
|
|
914
|
+
# A double-quoted run is label text whole, so `("a (1)")` ends at
|
|
915
|
+
# the last paren and not the one inside the quotes.
|
|
916
|
+
# A quote that never closes is refused, not read as plain text,
|
|
917
|
+
# except on a comment line, which mermaid deletes before it looks.
|
|
918
|
+
rule(:shape_rounded) do
|
|
919
|
+
lparen.as(:open) >>
|
|
920
|
+
(quoted_label | inline_comment_line |
|
|
921
|
+
((rparen | str('"')).absent? >> any)).repeat.as(:label) >>
|
|
922
|
+
rparen.as(:close)
|
|
923
|
+
end
|
|
924
|
+
|
|
925
|
+
# Stadium: ([label])
|
|
926
|
+
rule(:shape_stadium) do
|
|
927
|
+
str("([").as(:open) >>
|
|
928
|
+
(str("])").absent? >> any).repeat.as(:label) >>
|
|
929
|
+
str("])").as(:close)
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
# Subroutine: [[label]]
|
|
933
|
+
rule(:shape_subroutine) do
|
|
934
|
+
str("[[").as(:open) >>
|
|
935
|
+
(str("]]").absent? >> any).repeat.as(:label) >>
|
|
936
|
+
str("]]").as(:close)
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
# Cylindrical/Database: [(label)]
|
|
940
|
+
rule(:shape_cylindrical) do
|
|
941
|
+
str("[(").as(:open) >>
|
|
942
|
+
(str(")]").absent? >> any).repeat.as(:label) >>
|
|
943
|
+
str(")]").as(:close)
|
|
944
|
+
end
|
|
945
|
+
|
|
946
|
+
# Circle: ((label))
|
|
947
|
+
rule(:shape_double_circle) do
|
|
948
|
+
str("((").as(:open) >>
|
|
949
|
+
(str("))").absent? >> any).repeat.as(:label) >>
|
|
950
|
+
str("))").as(:close)
|
|
951
|
+
end
|
|
952
|
+
|
|
953
|
+
# Triple Circle: (((label)))
|
|
954
|
+
rule(:shape_triple_circle) do
|
|
955
|
+
str("(((").as(:open) >>
|
|
956
|
+
(str(")))").absent? >> any).repeat.as(:label) >>
|
|
957
|
+
str(")))").as(:close)
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
# Asymmetric: >label]
|
|
961
|
+
rule(:shape_asymmetric) do
|
|
962
|
+
str(">").as(:open) >>
|
|
963
|
+
(rbracket.absent? >> any).repeat.as(:label) >>
|
|
964
|
+
rbracket.as(:close)
|
|
965
|
+
end
|
|
966
|
+
|
|
967
|
+
# Rhombus/Diamond: {label}
|
|
968
|
+
rule(:shape_rhombus) do
|
|
969
|
+
lbrace.as(:open) >>
|
|
970
|
+
(rbrace.absent? >> any).repeat.as(:label) >>
|
|
971
|
+
rbrace.as(:close)
|
|
972
|
+
end
|
|
973
|
+
|
|
974
|
+
# Hexagon: {{label}}
|
|
975
|
+
rule(:shape_hexagon) do
|
|
976
|
+
str("{{").as(:open) >>
|
|
977
|
+
(str("}}").absent? >> any).repeat.as(:label) >>
|
|
978
|
+
str("}}").as(:close)
|
|
979
|
+
end
|
|
980
|
+
|
|
981
|
+
# Parallelogram: [/label/]
|
|
982
|
+
rule(:shape_parallelogram) do
|
|
983
|
+
str("[/").as(:open) >>
|
|
984
|
+
(str("/]").absent? >> any).repeat.as(:label) >>
|
|
985
|
+
str("/]").as(:close)
|
|
986
|
+
end
|
|
987
|
+
|
|
988
|
+
# Parallelogram Alt: [\label\]
|
|
989
|
+
rule(:shape_parallelogram_alt) do
|
|
990
|
+
str("[\\").as(:open) >>
|
|
991
|
+
(str('\\]').absent? >> any).repeat.as(:label) >>
|
|
992
|
+
str('\\]').as(:close)
|
|
993
|
+
end
|
|
994
|
+
|
|
995
|
+
# Trapezoid: [/label\]
|
|
996
|
+
rule(:shape_trapezoid) do
|
|
997
|
+
str("[/").as(:open) >>
|
|
998
|
+
(str('\\]').absent? >> any).repeat.as(:label) >>
|
|
999
|
+
str('\\]').as(:close)
|
|
1000
|
+
end
|
|
1001
|
+
|
|
1002
|
+
# Trapezoid Alt: [\label/]
|
|
1003
|
+
rule(:shape_trapezoid_alt) do
|
|
1004
|
+
str("[\\").as(:open) >>
|
|
1005
|
+
(str("/]").absent? >> any).repeat.as(:label) >>
|
|
1006
|
+
str("/]").as(:close)
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
# Edge chain: can have multiple edges from one node
|
|
1010
|
+
rule(:edge_chain) do
|
|
1011
|
+
edge >> edge.repeat
|
|
1012
|
+
end
|
|
1013
|
+
|
|
1014
|
+
# Single edge. A symbol-only link is tried first, as mermaid's
|
|
1015
|
+
# lexer does: `A --x B` is a link with a cross head, and only a
|
|
1016
|
+
# link that cannot end where it starts opens `A -- text --x B`.
|
|
1017
|
+
#
|
|
1018
|
+
# The rule owns the gap before it, because an edge id has to start
|
|
1019
|
+
# on the line the source ends on, after a space: `A` newline
|
|
1020
|
+
# `e1@--> B` and `A[x]e1@--> B` are refused.
|
|
1021
|
+
rule(:edge) do
|
|
1022
|
+
((line_space.repeat(1) >> edge_id_preempted.absent? >>
|
|
1023
|
+
edge_id_ahead >> edge_id_body) | ws?) >>
|
|
1024
|
+
(piped_edge | inline_label_edge) >>
|
|
1025
|
+
ws? >>
|
|
1026
|
+
reserved_keyword.absent? >> node_with_shape.as(:target) >>
|
|
1027
|
+
amp_group.as(:group)
|
|
1028
|
+
end
|
|
1029
|
+
|
|
1030
|
+
# `A & B`: further nodes joined to the one before, which then
|
|
1031
|
+
# share whatever link comes next. Mermaid needs at least one space
|
|
1032
|
+
# character on each side of the `&`. Which characters count is the
|
|
1033
|
+
# shared `space` rule's business, and it is narrower than mermaid's
|
|
1034
|
+
# `\s`: a tab passes here, an NBSP does not.
|
|
1035
|
+
rule(:amp_group) do
|
|
1036
|
+
(
|
|
1037
|
+
space.repeat(1) >> edge_id_ahead.absent? >> str("&") >>
|
|
1038
|
+
space.repeat(1) >> reserved_keyword.absent? >> node_with_shape
|
|
1039
|
+
).repeat
|
|
1040
|
+
end
|
|
1041
|
+
|
|
1042
|
+
# `A e1@--> B` names the edge, so a later `e1@{ animate: true }`
|
|
1043
|
+
# can address it. The `@` must be followed by the start of a link,
|
|
1044
|
+
# and the id must not itself start like one: `A --me@--> B` or a
|
|
1045
|
+
# quoted `@` is not an id, but `A {x@--> B` has one, and an id may
|
|
1046
|
+
# start with `;`. Trying a rule and failing would send every `A B`
|
|
1047
|
+
# typo to the Deepest reporter in `Base`, so the source is read first.
|
|
1048
|
+
#
|
|
1049
|
+
# Keep every repeat possessive, each branch on its own character
|
|
1050
|
+
# class: two branches that can match the same text make a failed
|
|
1051
|
+
# match exponential in the blank lines after the `@`.
|
|
1052
|
+
EDGE_ID_END_AHEAD = /
|
|
1053
|
+
@
|
|
1054
|
+
[#{LINE_SPACE_CHARS}]*+
|
|
1055
|
+
(?:
|
|
1056
|
+
\r?\n
|
|
1057
|
+
(?:
|
|
1058
|
+
[#{LINE_SPACE_CHARS}] |
|
|
1059
|
+
\r?\n |
|
|
1060
|
+
%%[^\r\n]*+
|
|
1061
|
+
)*+
|
|
1062
|
+
)?+
|
|
1063
|
+
[ox<]?(?:-|=|\.|~~~)
|
|
1064
|
+
/x
|
|
1065
|
+
private_constant :EDGE_ID_END_AHEAD
|
|
1066
|
+
|
|
1067
|
+
EDGE_ID_AHEAD = /
|
|
1068
|
+
(?!@\{)
|
|
1069
|
+
(?![ox<]?(?:--|==|-\.|\.+-|~~~))
|
|
1070
|
+
[^#{LINE_SPACE_CHARS}\r\n"]+#{EDGE_ID_END_AHEAD}
|
|
1071
|
+
/x
|
|
1072
|
+
private_constant :EDGE_ID_AHEAD
|
|
1073
|
+
|
|
1074
|
+
# An edge id can contain `@`. When one of those internal characters
|
|
1075
|
+
# happens to precede something link-like, the id runs on to the last
|
|
1076
|
+
# `@` that can delimit it. The match ends right before that `@`, or
|
|
1077
|
+
# is empty when the `@` here is the last one.
|
|
1078
|
+
EDGE_ID_LAST_END = /
|
|
1079
|
+
(?:@[^#{LINE_SPACE_CHARS}\r\n"]+)?
|
|
1080
|
+
(?=#{EDGE_ID_END_AHEAD})
|
|
1081
|
+
/x
|
|
1082
|
+
private_constant :EDGE_ID_LAST_END
|
|
1083
|
+
|
|
1084
|
+
# These lexer tokens precede LINK_ID in mermaid, so a matching opener
|
|
1085
|
+
# wins before the otherwise broad `[^\s"]+@` rule can name an edge.
|
|
1086
|
+
# `click`, `href` and `call` are intentionally absent: their tokens
|
|
1087
|
+
# require whitespace after the word, and `@` does not provide it.
|
|
1088
|
+
rule(:edge_id_preempted) do
|
|
1089
|
+
reserved_word |
|
|
1090
|
+
(str("default") >> word_boundary) |
|
|
1091
|
+
(str("accTitle") >> str(":")) |
|
|
1092
|
+
(str("accDescr") >> (str(":") | str("{")))
|
|
1093
|
+
end
|
|
1094
|
+
|
|
1095
|
+
# True where an edge id starts, so a caller can leave it alone.
|
|
1096
|
+
#
|
|
1097
|
+
# These are the only raw-`Regexp` lookaheads in the parser tree;
|
|
1098
|
+
# everywhere else builds lookahead from Parslet atoms with
|
|
1099
|
+
# `.absent?`/`.present?`. Do not copy this as the house style. It is
|
|
1100
|
+
# here because deciding whether an `@` ends an edge id needs an
|
|
1101
|
+
# unbounded forward scan past comment and blank lines to the link
|
|
1102
|
+
# opener, which a combinator cannot express without trying the rule
|
|
1103
|
+
# and failing — the Deepest-reporter problem described above.
|
|
1104
|
+
rule(:edge_id_ahead) do
|
|
1105
|
+
dynamic do |source, _|
|
|
1106
|
+
source.matches?(EDGE_ID_AHEAD) ? str("") : str("").absent?
|
|
1107
|
+
end
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1110
|
+
rule(:edge_id_end_ahead) do
|
|
1111
|
+
dynamic do |source, _|
|
|
1112
|
+
edge_id_end?(source) ? str("") : str("").absent?
|
|
1113
|
+
end
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
# Every `@` between an id's first delimiter-like `@` and its last one
|
|
1117
|
+
# shares that last one, so it is found once and remembered. Asking
|
|
1118
|
+
# afresh at each `@` rescans the rest of the id every time.
|
|
1119
|
+
def edge_id_end?(source)
|
|
1120
|
+
pos = source.bytepos
|
|
1121
|
+
cached, first, last = @edge_id_end
|
|
1122
|
+
cached_position = cached.equal?(source) && pos.between?(first, last)
|
|
1123
|
+
return pos == last if cached_position
|
|
1124
|
+
return false unless source.matches?(EDGE_ID_END_AHEAD)
|
|
1125
|
+
|
|
1126
|
+
last = pos + source.matches?(EDGE_ID_LAST_END)
|
|
1127
|
+
@edge_id_end = [source, pos, last]
|
|
1128
|
+
pos == last
|
|
1129
|
+
end
|
|
1130
|
+
private :edge_id_end?
|
|
1131
|
+
|
|
1132
|
+
rule(:edge_id_body) do
|
|
1133
|
+
((edge_id_end_ahead | line_space | newline).absent? >> any)
|
|
1134
|
+
.repeat(1).as(:edge_id) >> str("@") >>
|
|
1135
|
+
line_space.repeat >>
|
|
1136
|
+
(newline >> (line_space | newline | comment).repeat).repeat
|
|
1137
|
+
end
|
|
1138
|
+
|
|
1139
|
+
rule(:piped_edge) do
|
|
1140
|
+
arrow.as(:arrow) >> ws? >> edge_label.maybe.as(:label)
|
|
1141
|
+
end
|
|
1142
|
+
|
|
1143
|
+
# `A -- text --> B`: the label sits between the two halves of the
|
|
1144
|
+
# link, and the transform joins the halves back into one token, so
|
|
1145
|
+
# `<-- t -->` is a double arrow and `-. t .->` a dotted one.
|
|
1146
|
+
rule(:inline_label_edge) do
|
|
1147
|
+
inline_solid | inline_thick | inline_dotted
|
|
1148
|
+
end
|
|
1149
|
+
|
|
1150
|
+
rule(:inline_solid) do
|
|
1151
|
+
inline_halves(str("--"), solid_close, str("--"))
|
|
1152
|
+
end
|
|
1153
|
+
|
|
1154
|
+
rule(:inline_thick) do
|
|
1155
|
+
inline_halves(str("=="), thick_close, str("="))
|
|
1156
|
+
end
|
|
1157
|
+
|
|
1158
|
+
# A thick or dotted label cannot carry even one of its own body
|
|
1159
|
+
# characters, `=` or `.`: mmdc refuses `A == a=b ==> B` and
|
|
1160
|
+
# `A -. a.b .-> B`. Only the solid body may hold a single hyphen.
|
|
1161
|
+
rule(:inline_dotted) do
|
|
1162
|
+
inline_halves(str("-."), dotted_close, str("."))
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
# A closing link carries at least one character more than the
|
|
1166
|
+
# opening half, a head or another body character, so `--` alone
|
|
1167
|
+
# never closes.
|
|
1168
|
+
rule(:solid_close) do
|
|
1169
|
+
str("--") >>
|
|
1170
|
+
(match[">xo"] | (str("-").repeat(1) >> match[">xo"].maybe))
|
|
1171
|
+
end
|
|
1172
|
+
|
|
1173
|
+
rule(:thick_close) do
|
|
1174
|
+
str("==") >>
|
|
1175
|
+
(match[">xo"] | (str("=").repeat(1) >> match[">xo"].maybe))
|
|
1176
|
+
end
|
|
1177
|
+
|
|
1178
|
+
rule(:dotted_close) do
|
|
1179
|
+
str("-").maybe >> str(".").repeat(1) >> str("-") >> match[">xo"].maybe
|
|
1180
|
+
end
|
|
1181
|
+
|
|
1182
|
+
# The label runs to the closing link, across lines as mermaid reads
|
|
1183
|
+
# it, and starts on a character that is not whitespace: mermaid
|
|
1184
|
+
# refuses `A -- --> B`. `forbidden` is the body text mermaid cannot
|
|
1185
|
+
# lex inside the label. Whitespace is mermaid's own set, and a whole
|
|
1186
|
+
# comment line inside a multiline label is deleted before it reads.
|
|
1187
|
+
# `%%{` opens a directive, never a comment, and no label holds one.
|
|
1188
|
+
#
|
|
1189
|
+
# A whitespace run is consumed whole and checked once for what
|
|
1190
|
+
# follows it, so a long run costs one pass, not one per character.
|
|
1191
|
+
def inline_halves(open, close, forbidden)
|
|
1192
|
+
blank = inline_blank
|
|
1193
|
+
closing = inline_closing(close)
|
|
1194
|
+
closing_ahead = inline_closing_ahead(forbidden)
|
|
1195
|
+
label = inline_label_body(closing, blank, forbidden)
|
|
1196
|
+
inline_opening(open) >> blank.repeat >> closing_ahead >> label >>
|
|
1197
|
+
blank.repeat >> closing.as(:close)
|
|
1198
|
+
end
|
|
1199
|
+
private :inline_halves
|
|
1200
|
+
|
|
1201
|
+
def inline_blank
|
|
1202
|
+
inline_comment_line | line_space | match["\r\n"]
|
|
1203
|
+
end
|
|
1204
|
+
private :inline_blank
|
|
1205
|
+
|
|
1206
|
+
def inline_closing(close)
|
|
1207
|
+
link_start.maybe >> close
|
|
1208
|
+
end
|
|
1209
|
+
private :inline_closing
|
|
1210
|
+
|
|
1211
|
+
def inline_opening(open)
|
|
1212
|
+
(link_start.maybe >> open).as(:open)
|
|
1213
|
+
end
|
|
1214
|
+
private :inline_opening
|
|
1215
|
+
|
|
1216
|
+
# Avoid caching every failed per-character lookahead when the
|
|
1217
|
+
# closing delimiter is absent altogether.
|
|
1218
|
+
def inline_closing_ahead(forbidden)
|
|
1219
|
+
dynamic do |source|
|
|
1220
|
+
if source.chars_until(forbidden.str) < source.chars_left
|
|
1221
|
+
str("")
|
|
1222
|
+
else
|
|
1223
|
+
forbidden
|
|
1224
|
+
end
|
|
1225
|
+
end
|
|
1226
|
+
end
|
|
1227
|
+
private :inline_closing_ahead
|
|
1228
|
+
|
|
1229
|
+
def inline_label_char(closing, blank, forbidden)
|
|
1230
|
+
char = closing.absent? >> blank.absent? >> str('"').absent? >>
|
|
1231
|
+
str("%%{").absent? >> any
|
|
1232
|
+
forbidden ? forbidden.absent? >> char : char
|
|
1233
|
+
end
|
|
1234
|
+
private :inline_label_char
|
|
1235
|
+
|
|
1236
|
+
def inline_label_content(char, gap)
|
|
1237
|
+
text = char >> (char | gap).repeat
|
|
1238
|
+
(quoted_label >> (char | gap).repeat) | text
|
|
1239
|
+
end
|
|
1240
|
+
private :inline_label_content
|
|
1241
|
+
|
|
1242
|
+
def inline_label_body(closing, blank, forbidden)
|
|
1243
|
+
char = inline_label_char(closing, blank, forbidden)
|
|
1244
|
+
gap = blank.repeat(1) >> closing.absent?
|
|
1245
|
+
inline_label_content(char, gap).as(:label)
|
|
1246
|
+
end
|
|
1247
|
+
private :inline_label_body
|
|
1248
|
+
|
|
1249
|
+
# A quoted run is text whole, whatever it holds: `A -- "a--b" --> B`.
|
|
1250
|
+
# The transform drops the two quotes, as mermaid does. `%%{` still
|
|
1251
|
+
# opens a directive even inside quotes, so it is refused here too.
|
|
1252
|
+
# A whole comment line is skipped first, quote and all: mermaid
|
|
1253
|
+
# deletes it before the quote inside it can end the label early.
|
|
1254
|
+
rule(:quoted_label) do
|
|
1255
|
+
str('"') >>
|
|
1256
|
+
(inline_comment_line | (str("%%{").absent? >> match['^"']))
|
|
1257
|
+
.repeat >> str('"')
|
|
1258
|
+
end
|
|
1259
|
+
|
|
1260
|
+
# A comment line inside a multiline inline label: mermaid deletes
|
|
1261
|
+
# it whole before reading the label. `%%{` opens a directive
|
|
1262
|
+
# rather than a comment and is left alone.
|
|
1263
|
+
rule(:inline_comment_line) do
|
|
1264
|
+
newline >> line_space.repeat >> str("%%") >> str("{").absent? >>
|
|
1265
|
+
(newline.absent? >> any).repeat
|
|
1266
|
+
end
|
|
1267
|
+
|
|
1268
|
+
# Link forms
|
|
1269
|
+
# Every symbol-only link mermaid draws, probed one at a time
|
|
1270
|
+
# against mmdc rather than counted from the docs. The form that
|
|
1271
|
+
# carries its label in the middle — `A -- text --> B` — is a
|
|
1272
|
+
# different shape, read by `inline_label_edge` above.
|
|
1273
|
+
#
|
|
1274
|
+
# `->` and `==` are deliberately absent: etassia accepted both and
|
|
1275
|
+
# mermaid rejects them.
|
|
1276
|
+
# `~` never opens a visible link, so this alternation is
|
|
1277
|
+
# mutually exclusive and its order is free.
|
|
1278
|
+
rule(:arrow) do
|
|
1279
|
+
(invisible_link | visible_link).as(:token)
|
|
1280
|
+
end
|
|
1281
|
+
|
|
1282
|
+
# `~~~` takes no markers at all. mmdc rejects `~~~>` outright, and
|
|
1283
|
+
# it never reads an `o` or an `x` beside a tilde run as a marker
|
|
1284
|
+
# either — `A o~~~o B` is refused, and written flush `Ao~~~oB` is
|
|
1285
|
+
# DRAWN, as the two nodes `Ao` and `oB` with an invisible link
|
|
1286
|
+
# between them. Etassia gives both the same answers.
|
|
1287
|
+
rule(:invisible_link) { str("~~~") >> str("~").repeat }
|
|
1288
|
+
|
|
1289
|
+
# The vocabulary is generated, not listed. Enumerating it missed
|
|
1290
|
+
# forms mmdc renders — `====`, `-.-x`, `<--x`, `o----o` among them
|
|
1291
|
+
# — and got `o--x` wrong on top of that.
|
|
1292
|
+
#
|
|
1293
|
+
# A leading marker is taken here whatever it is; whether mermaid
|
|
1294
|
+
# honours it depends on the marker at the other end, which the
|
|
1295
|
+
# transform decides.
|
|
1296
|
+
#
|
|
1297
|
+
# Headed first: parslet's alternation takes the first branch that
|
|
1298
|
+
# matches, and `long_link` would swallow the `---` of `--->` and
|
|
1299
|
+
# leave the `>` for the target to start with, which nothing can
|
|
1300
|
+
# parse — the whole diagram would be thrown away.
|
|
1301
|
+
rule(:visible_link) { link_start.maybe >> (headed_link | long_link) }
|
|
1302
|
+
|
|
1303
|
+
rule(:link_start) { match["ox<"] }
|
|
1304
|
+
rule(:link_end) { match[">xo"] }
|
|
1305
|
+
|
|
1306
|
+
rule(:headed_link) { link_body >> link_end }
|
|
1307
|
+
|
|
1308
|
+
# The three bodies part on a `-`, an `=`, or a dot no other body
|
|
1309
|
+
# carries — a dotted one may open with the dot itself — so this
|
|
1310
|
+
# alternation is mutually exclusive and its order is free. Only
|
|
1311
|
+
# the one above is load-bearing.
|
|
1312
|
+
rule(:link_body) { solid_body | thick_body | dotted_body }
|
|
1313
|
+
|
|
1314
|
+
rule(:solid_body) { str("--") >> str("-").repeat }
|
|
1315
|
+
rule(:thick_body) { str("==") >> str("=").repeat }
|
|
1316
|
+
|
|
1317
|
+
# The opening hyphen is optional. mmdc draws `.-`, `..->` and
|
|
1318
|
+
# `<.-x` exactly as it draws `-.-`, `-..->` and `<-.-x`, and this
|
|
1319
|
+
# rule refused the whole leading-dot half of the family.
|
|
1320
|
+
rule(:dotted_body) { str("-").maybe >> str(".").repeat(1) >> str("-") }
|
|
1321
|
+
|
|
1322
|
+
# Without a marker the body has to be longer than its minimum:
|
|
1323
|
+
# mmdc draws `---` and `===` and refuses `--` and `==`. A dotted
|
|
1324
|
+
# body carries a dot already, so its own minimum — `.-` — is a
|
|
1325
|
+
# link on its own and it stands here unchanged.
|
|
1326
|
+
rule(:long_link) { long_solid | long_thick | dotted_body }
|
|
1327
|
+
rule(:long_solid) { str("---") >> str("-").repeat }
|
|
1328
|
+
rule(:long_thick) { str("===") >> str("=").repeat }
|
|
1329
|
+
|
|
1330
|
+
# Edge label: can be in pipes |label|
|
|
1331
|
+
rule(:edge_label) do
|
|
1332
|
+
pipe_label
|
|
1333
|
+
end
|
|
1334
|
+
|
|
1335
|
+
# Pipe label: |label|. The pipes are delimiters, not text, so only
|
|
1336
|
+
# the run between them is captured.
|
|
1337
|
+
rule(:pipe_label) do
|
|
1338
|
+
pipe >> (pipe.absent? >> any).repeat(1).as(:pipe_text) >> pipe
|
|
1339
|
+
end
|
|
1340
|
+
|
|
1341
|
+
# A subgraph is NAMED, not built. It takes a quoted string and the
|
|
1342
|
+
# keyword `end`, but not the other reserved words — mmdc draws
|
|
1343
|
+
# `subgraph end [Title]` and `subgraph "AB" [Title]` and refuses
|
|
1344
|
+
# `subgraph default [Title]` and `subgraph _self [Title]`.
|
|
1345
|
+
#
|
|
1346
|
+
# `quoted_run`, not `quoted_string`: the shared rule takes an EMPTY
|
|
1347
|
+
# body and wraps it in its own `.as(:string)`, while mmdc refuses
|
|
1348
|
+
# `subgraph ""` and draws `subgraph " "`.
|
|
1349
|
+
#
|
|
1350
|
+
# An empty pair of quotes names nothing ON ITS OWN but may stand in
|
|
1351
|
+
# FRONT of a name, so `subgraph "" A` and `subgraph ""A` both draw.
|
|
1352
|
+
# Whitespace between the pair and the name is optional, so the name
|
|
1353
|
+
# is taken here rather than left to `subgraph_trailing_name`, which
|
|
1354
|
+
# only starts at a space.
|
|
1355
|
+
rule(:subgraph_id) do
|
|
1356
|
+
subgraph_quoted_name |
|
|
1357
|
+
(str('""') >> space.repeat >> subgraph_name_word) |
|
|
1358
|
+
subgraph_name_word
|
|
1359
|
+
end
|
|
1360
|
+
|
|
1361
|
+
# `quoted_run`'s body, captured. The quotes are not part of the
|
|
1362
|
+
# name: the transform compares ids and builds the title from this
|
|
1363
|
+
# text, so `subgraph "a b"` has to arrive as `a b`. Never empty,
|
|
1364
|
+
# for the same reason `quoted_run` is not.
|
|
1365
|
+
rule(:subgraph_quoted_name) do
|
|
1366
|
+
str('"') >> (str('"').absent? >> any).repeat(1).as(:string) >>
|
|
1367
|
+
str('"')
|
|
1368
|
+
end
|
|
1369
|
+
|
|
1370
|
+
# A name that hunts up an `end` and then ends the line closes the
|
|
1371
|
+
# subgraph on the spot. mermaid lexes `end\b\s*` as its END token,
|
|
1372
|
+
# so `subgraph end` opens nothing and the body's own `end` is left
|
|
1373
|
+
# over. mmdc refuses it, and refuses `subgraph ""end`,
|
|
1374
|
+
# `subgraph 1end` and `subgraph éend` the same way, while
|
|
1375
|
+
# `subgraph Zend`, `subgraph endx` and `subgraph Z#end` draw.
|
|
1376
|
+
#
|
|
1377
|
+
# Anything but whitespace after the name calls it off — a title,
|
|
1378
|
+
# another word, or a `;` — so `subgraph end [T]`, `subgraph end A`
|
|
1379
|
+
# and `subgraph end;` all draw. Whitespace does not save it in any
|
|
1380
|
+
# mixture, which is mermaid's own `end\b\s*` eating the run. The
|
|
1381
|
+
# run is `line_space`, so a no-break space goes with the keyword
|
|
1382
|
+
# the way a plain one does.
|
|
1383
|
+
#
|
|
1384
|
+
# The whole condition goes INSIDE the walk. Hunting the word alone
|
|
1385
|
+
# and testing the line end behind it commits to the FIRST `end` and
|
|
1386
|
+
# PEG never resumes at a later one, so `subgraph A endéend` would be
|
|
1387
|
+
# taken as a name where mmdc refuses it. `subgraph A endéend [T]`
|
|
1388
|
+
# still draws: with a title behind it no `end` ends the line.
|
|
1389
|
+
rule(:bare_subgraph_end) do
|
|
1390
|
+
token_at_lexer_restart(
|
|
1391
|
+
subgraph_end_word >> line_space.repeat >> (newline | eof),
|
|
1392
|
+
)
|
|
1393
|
+
end
|
|
1394
|
+
|
|
1395
|
+
# The `end` guard belongs here because every subgraph word funnels
|
|
1396
|
+
# through this rule — both arms of `subgraph_id` and every
|
|
1397
|
+
# trailing word — so `subgraph end`, `subgraph ""end` and
|
|
1398
|
+
# `subgraph A end` are refused by the same line.
|
|
1399
|
+
#
|
|
1400
|
+
# A subgraph keyword and a link opening end a name the same way
|
|
1401
|
+
# they end a node id, and mermaid looks for both behind every
|
|
1402
|
+
# lexer restart. So `subgraph 1default [T]`, `subgraph #Zédefault
|
|
1403
|
+
# [T]` and `subgraph Zé.- [T]` are refused, while `subgraph #end
|
|
1404
|
+
# [T]` and `subgraph a.- [T]` draw. Without the link guard all
|
|
1405
|
+
# sixteen measured refusals parsed here.
|
|
1406
|
+
rule(:subgraph_name_word) do
|
|
1407
|
+
bare_subgraph_end.absent? >>
|
|
1408
|
+
token_at_lexer_restart(subgraph_keyword).absent? >>
|
|
1409
|
+
dot_run_before_link.absent? >>
|
|
1410
|
+
token_at_lexer_restart(arrowhead_open).absent? >>
|
|
1411
|
+
id_run
|
|
1412
|
+
end
|
|
1413
|
+
|
|
1414
|
+
rule(:subgraph_reserved) do
|
|
1415
|
+
SUBGRAPH_RESERVED.map { |word| str(word) }.reduce(:|) >>
|
|
1416
|
+
word_boundary
|
|
1417
|
+
end
|
|
1418
|
+
|
|
1419
|
+
# All three directive words are reserved here too, and they end a
|
|
1420
|
+
# word the same way they do in a node id — `subgraph click- [T]`
|
|
1421
|
+
# and `subgraph clickx [T]` are ordinary names.
|
|
1422
|
+
#
|
|
1423
|
+
# `click` looked like the exception and is not. mmdc draws
|
|
1424
|
+
# `subgraph click [T]` only while the body holds no link: mermaid
|
|
1425
|
+
# opens its click state on the word and swallows the title, so the
|
|
1426
|
+
# subgraph is anonymous and the next link is a parse error. Probing
|
|
1427
|
+
# with a bare `X` inside hid that; `X --> Y` shows it.
|
|
1428
|
+
rule(:subgraph_keyword) { subgraph_reserved | spaced_keyword }
|
|
1429
|
+
|
|
1430
|
+
# A click target is named, not built, and mermaid is at its most
|
|
1431
|
+
# permissive here: `click default`, `click _self` and `click end`
|
|
1432
|
+
# all render, and so does `click --> "url"`. The target is simply
|
|
1433
|
+
# everything up to the next space, tab or newline. Only a leading
|
|
1434
|
+
# quote is out — mmdc refuses `click "AB" "https://example.com"`
|
|
1435
|
+
# because the quote opens a string instead.
|
|
1436
|
+
#
|
|
1437
|
+
# The terminator is this grammar's own whitespace, not Ruby's `\s`.
|
|
1438
|
+
# `\s` holds a vertical tab and a form feed, and ending the target
|
|
1439
|
+
# on either refused `click A\vB "url"` and `click A\fB "url"`,
|
|
1440
|
+
# which mmdc draws. It stays aligned with the single `space` in
|
|
1441
|
+
# `click_statement` that holds the action off the target.
|
|
1442
|
+
rule(:click_target) do
|
|
1443
|
+
str('"').absent? >> ((space | newline).absent? >> any).repeat(1)
|
|
1444
|
+
end
|
|
1445
|
+
|
|
1446
|
+
# A node id is a bare word, never a quoted run. It is BUILT here,
|
|
1447
|
+
# so every guard applies and malformed ids cannot bypass them.
|
|
1448
|
+
#
|
|
1449
|
+
# Mermaid's node ids are far wider than a programming identifier:
|
|
1450
|
+
# they may lead with a digit (`1-->2`), and carry dots, slashes and
|
|
1451
|
+
# hyphens (`9e122290`, `a.b`, `a/b`, `a-b`).
|
|
1452
|
+
#
|
|
1453
|
+
# A hyphen is only part of the id when an arrow cannot start there,
|
|
1454
|
+
# so `a-b` is one node while `a-->b` stays two.
|
|
1455
|
+
#
|
|
1456
|
+
# Mermaid does not stop hunting for a keyword at the start of an
|
|
1457
|
+
# id, so a reserved word buried in one is still a keyword there,
|
|
1458
|
+
# and the places it looks again are positions, not characters:
|
|
1459
|
+
# `#end` `1end` `éend` are refused, `Z#end` `Z1end` `$end` draw.
|
|
1460
|
+
rule(:node_id) do
|
|
1461
|
+
id_before_xo_link |
|
|
1462
|
+
(token_at_lexer_restart(node_keyword).absent? >>
|
|
1463
|
+
dot_run_before_link.absent? >>
|
|
1464
|
+
arrowhead_dot_dash.absent? >> id_run)
|
|
1465
|
+
end
|
|
1466
|
+
|
|
1467
|
+
# A node id ends before one `x` or `o` when `--`, `==` or `-.`
|
|
1468
|
+
# follows. A doubled `x`/`o`, as in `1xx`, keeps the marker in the
|
|
1469
|
+
# id, and so does a settled character in front of it — `Zx-->B` and
|
|
1470
|
+
# `A1x-->B` are one node and a plain link.
|
|
1471
|
+
#
|
|
1472
|
+
# Where the marker splits the id is a lexer restart, so the walk is
|
|
1473
|
+
# the same one every other guard here runs. mmdc splits `#x-->B`
|
|
1474
|
+
# into `#` and `B`, `&x---B` into `&` and `B`, and `éx-->B`,
|
|
1475
|
+
# `中o===B` and `Zéo==>B` the same way.
|
|
1476
|
+
#
|
|
1477
|
+
# `repeat(1)` rather than `token_at_lexer_restart`'s `repeat`: at
|
|
1478
|
+
# zero restarts the marker stands at the id START, where mermaid
|
|
1479
|
+
# has no node in front of the link at all. `x-->B` is that shape
|
|
1480
|
+
# and it is left exactly where it was.
|
|
1481
|
+
#
|
|
1482
|
+
# The split link now parses too: `#x-->B` draws `#` and `B`
|
|
1483
|
+
# joined by a plain `arrow`, same as mmdc. `Builders::Flowchart
|
|
1484
|
+
# .link_type` is what reads the marker — it honours a leading
|
|
1485
|
+
# `x`/`o` only when the trailing one matches it, so a lone
|
|
1486
|
+
# leading marker like this one falls back to the head it finds
|
|
1487
|
+
# at the other end rather than failing the statement.
|
|
1488
|
+
rule(:id_before_xo_link) do
|
|
1489
|
+
(xo_link_open.absent? >> restart_step).repeat(1) >>
|
|
1490
|
+
xo_link_open.present?
|
|
1491
|
+
end
|
|
1492
|
+
|
|
1493
|
+
# The opening of a link that carries a crossed or circled head.
|
|
1494
|
+
# Mermaid spells the head on both ends of all three links —
|
|
1495
|
+
# `[xo<]?--+[-xo>]`, `[xo<]?==+[=xo>]`, `[xo<]?-?\.+-[xo>]?` — so
|
|
1496
|
+
# the marker leads a dash, an equals or a dotted run alike.
|
|
1497
|
+
#
|
|
1498
|
+
# The dotted one puts its dash BEFORE the dots and makes it
|
|
1499
|
+
# optional, so it opens as `x-.` and as `x.-` both.
|
|
1500
|
+
# `arrowhead_open` already spells the whole opening, markers and
|
|
1501
|
+
# leading dash included, so it is reused rather than respelt.
|
|
1502
|
+
#
|
|
1503
|
+
# A settled character in front kills the opening, which is what
|
|
1504
|
+
# mmdc does too: `Zéax.-B`, `x1.-` and `xx.-` keep their whole id
|
|
1505
|
+
# while `#x.-B` and `éx.-B` are refused.
|
|
1506
|
+
rule(:xo_link_open) do
|
|
1507
|
+
arrowhead_open |
|
|
1508
|
+
(match["xo"] >> (str("--") | str("==") | str("-.")))
|
|
1509
|
+
end
|
|
1510
|
+
|
|
1511
|
+
# A hyphen joins the id unless another dash or a dot follows, which
|
|
1512
|
+
# is where a link starts. `x`, `o` and `>` are NOT excluded,
|
|
1513
|
+
# because mermaid renders `a-o-->B` and `a-x-->B`. `A->B` fails
|
|
1514
|
+
# because the hyphen joins the id here and `>B` cannot continue a
|
|
1515
|
+
# statement — not because `->` is unknown; `A -> B` is refused by
|
|
1516
|
+
# `visible_link`, which spells its shortest solid body `--`.
|
|
1517
|
+
rule(:id_hyphen) { str("-") >> match["-."].absent? }
|
|
1518
|
+
|
|
1519
|
+
rule(:reserved_word) do
|
|
1520
|
+
RESERVED_WORDS.map { |word| str(word) }.reduce(:|) >> word_boundary
|
|
1521
|
+
end
|
|
1522
|
+
|
|
1523
|
+
rule(:node_keyword) { reserved_word | spaced_keyword }
|
|
1524
|
+
|
|
1525
|
+
# An arrowhead opening standing AT one of these places is a link,
|
|
1526
|
+
# and mermaid starts a fresh token behind it — so the walk has to
|
|
1527
|
+
# carry on past it, or it stalls on the `x.-` and never sees the
|
|
1528
|
+
# word behind it. mmdc refuses `#x.-end --- Z` and `1x.-end --- Z`.
|
|
1529
|
+
#
|
|
1530
|
+
# Only at one of the places, though. A settled character in front
|
|
1531
|
+
# still kills it, so `Zéax.-end` and `Zx.-end` stay whole ids,
|
|
1532
|
+
# which is what mmdc draws.
|
|
1533
|
+
rule(:restart_step) do
|
|
1534
|
+
arrowhead_open | (settled_run.maybe >> restart_run)
|
|
1535
|
+
end
|
|
1536
|
+
|
|
1537
|
+
rule(:restart_run) { (id_carry_char | id_restart_char).repeat(1) }
|
|
1538
|
+
|
|
1539
|
+
rule(:settled_run) do
|
|
1540
|
+
settled_char >> (settled_char | id_carry_char).repeat
|
|
1541
|
+
end
|
|
1542
|
+
|
|
1543
|
+
rule(:settled_char) { id_dot | id_hyphen | id_ascii_char }
|
|
1544
|
+
|
|
1545
|
+
# A dot run against a dash is the opening of a dotted link, not a
|
|
1546
|
+
# node: mmdc refuses `.-->B` while `. --> B` is a node called `.`.
|
|
1547
|
+
# An equals sign is a different story — `.==>B` renders as `.` and
|
|
1548
|
+
# `B`, so excluding it here refused a diagram mermaid draws.
|
|
1549
|
+
#
|
|
1550
|
+
# Mermaid finds that opening wherever its lexer restarts, not only
|
|
1551
|
+
# at the id start, so it is hunted the way a keyword is. `1.-->B`
|
|
1552
|
+
# `#.-->B` `Zé.-->B` and `xé.-->B` are all refused, while
|
|
1553
|
+
# `Z1.-->B` `Z#.-->B` and `Zéa.-->B` draw as a node and a link.
|
|
1554
|
+
#
|
|
1555
|
+
# Nothing about what follows changes it. mmdc draws `1.-a --- Z`
|
|
1556
|
+
# as THREE nodes — `1`, `a` and the target — because `.-` opened a
|
|
1557
|
+
# dotted link between the first two. `dotted_body` does now spell
|
|
1558
|
+
# that opening, so the gap is no longer in the vocabulary: it is
|
|
1559
|
+
# this rule, which stops the id at the dot run rather than let it
|
|
1560
|
+
# start a link, and `1.-a --- Z` is still refused where the
|
|
1561
|
+
# spaced `1 .- a --- Z` draws all three. The id stopping short is
|
|
1562
|
+
# better than drawing a node that is not on mermaid's page, so the
|
|
1563
|
+
# refusal stands until the restart itself is modelled.
|
|
1564
|
+
#
|
|
1565
|
+
# `arrowhead_dot_dash` below keeps the id in the same spot but is
|
|
1566
|
+
# the weaker of the two: it treats the opening as a link only when
|
|
1567
|
+
# nothing follows it. What actually stops `#x.-B` is
|
|
1568
|
+
# `id_before_xo_link`.
|
|
1569
|
+
rule(:dot_run_before_link) { token_at_lexer_restart(dotted_link_open) }
|
|
1570
|
+
|
|
1571
|
+
# `arrowhead_open` without its leading marker. Mermaid's dotted
|
|
1572
|
+
# link also carries a trailing `[xo>]`, and it is deliberately NOT
|
|
1573
|
+
# spelled here: the tail is optional, so a rule holding it succeeds
|
|
1574
|
+
# exactly where one without it does, and the only reader
|
|
1575
|
+
# (`dot_run_before_link`) is consumed under `.absent?`, where the
|
|
1576
|
+
# length never matters either.
|
|
1577
|
+
rule(:dotted_link_open) { str(".").repeat(1) >> str("-") }
|
|
1578
|
+
|
|
1579
|
+
# Everywhere else a dot just joins, dash or no dash: mmdc draws
|
|
1580
|
+
# `A.-->B` as `A.` and `B`, and draws `A.-` `A.-B` `A..-->B` and
|
|
1581
|
+
# `y.- --> Z`.
|
|
1582
|
+
rule(:id_dot) { str(".") }
|
|
1583
|
+
|
|
1584
|
+
# Reads mermaid's dotted left-arrowhead opening — the whole of it,
|
|
1585
|
+
# as `arrowhead_open` spells it out below — as a link instead of an
|
|
1586
|
+
# id. Where that opening is legal decides the guard.
|
|
1587
|
+
#
|
|
1588
|
+
# At the id start nothing sits in front of the link, so mermaid
|
|
1589
|
+
# always refuses: `x.-`, `x..-`, `x.-z` and `x.-B` all fail. Behind
|
|
1590
|
+
# a lexer restart a node does sit in front, so mermaid opens a real
|
|
1591
|
+
# link there and reads THREE nodes — `#x.-B --- Z` is `#`, `B` and
|
|
1592
|
+
# `Z`. This rule alone would let the id carry on past the opening;
|
|
1593
|
+
# `id_before_xo_link` is what stops it at the restart.
|
|
1594
|
+
#
|
|
1595
|
+
# A settled character in front kills it either way, so `X.-`,
|
|
1596
|
+
# `x1.-`, `xx.-` and `xo.-` stay ordinary ids.
|
|
1597
|
+
#
|
|
1598
|
+
# The second arm needs no "at least one restart" of its own: at
|
|
1599
|
+
# zero restarts the arm in front already matches everything
|
|
1600
|
+
# `arrowhead_ends_id` could.
|
|
1601
|
+
rule(:arrowhead_dot_dash) do
|
|
1602
|
+
arrowhead_open | token_at_lexer_restart(arrowhead_ends_id)
|
|
1603
|
+
end
|
|
1604
|
+
|
|
1605
|
+
# Copied from the shape of mermaid's own dotted-link rule. In
|
|
1606
|
+
# mmdc 11.12.0's flow lexer that rule is
|
|
1607
|
+
# `/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/`, so the opening carries a
|
|
1608
|
+
# marker on BOTH ends and may put a dash in front of the dots.
|
|
1609
|
+
# Spelling only the middle of it reads `x.-x` as `x.-` plus a stray
|
|
1610
|
+
# `x` and restarts every guard downstream one character early.
|
|
1611
|
+
#
|
|
1612
|
+
# `<` is in mermaid's leading set too and is left out on purpose:
|
|
1613
|
+
# it is not an id character here, so it can never be reached
|
|
1614
|
+
# inside one.
|
|
1615
|
+
rule(:arrowhead_open) do
|
|
1616
|
+
(str("x") | str("o")) >> str("-").maybe >> str(".").repeat(1) >>
|
|
1617
|
+
str("-") >> match["xo>"].maybe
|
|
1618
|
+
end
|
|
1619
|
+
|
|
1620
|
+
# An arrowhead opening with nothing after it that could continue an
|
|
1621
|
+
# id. The crossed and circled links this opens ARE drawn — `x--x`,
|
|
1622
|
+
# `x-.-x` and the rest parse into real edges, see
|
|
1623
|
+
# `Builders::Flowchart#link_type` — so this rule refuses nothing
|
|
1624
|
+
# on its own; it only decides where the id ends. `id_before_xo_link`
|
|
1625
|
+
# above is what actually stops the id at the restart.
|
|
1626
|
+
rule(:arrowhead_ends_id) { arrowhead_open >> id_body.absent? }
|
|
1627
|
+
|
|
1628
|
+
# Mermaid's lexer spells its id charset out, so an id is not
|
|
1629
|
+
# "anything that is not punctuation". It is printable ASCII, or a
|
|
1630
|
+
# letter in the basic plane. Category and plane both matter: `é`,
|
|
1631
|
+
# `中`, `ª` and `A` draw, while `Ⅰ` (category Nl, not a letter),
|
|
1632
|
+
# astral `𝐀`, control characters, combining marks, non-ASCII
|
|
1633
|
+
# digits, braille and box drawing are all refused.
|
|
1634
|
+
#
|
|
1635
|
+
# `\p{L}` is close but not exact, so the letters come from
|
|
1636
|
+
# mermaid's own table instead (`MERMAID_UNICODE_TEXT`). How the
|
|
1637
|
+
# two differ, and why the table is copied rather than approximated,
|
|
1638
|
+
# is written down once beside the table in
|
|
1639
|
+
# `grammars/mermaid_unicode_text.rb`.
|
|
1640
|
+
#
|
|
1641
|
+
# `:` `,` `"` and `%` are printable ASCII and mermaid joins them —
|
|
1642
|
+
# it draws `A:B`, `A,B`, `A"B` and `A%B` as single nodes — but they
|
|
1643
|
+
# are held back on purpose, because each also opens something else
|
|
1644
|
+
# here: an inline class, a declaration list, a quoted label and a
|
|
1645
|
+
# comment. Letting them in needs those boundaries worked out first,
|
|
1646
|
+
# so it is left for its own change. A `;` is NOT one of them:
|
|
1647
|
+
# mermaid separates on it, and `A;B --- Z` draws `A` and then
|
|
1648
|
+
# `B --- Z`. mmdc refuses `( ) < = > @ [ ] ^ { | } ~` outright.
|
|
1649
|
+
#
|
|
1650
|
+
# `-` and `.` are excluded here and handled by their own rules: a
|
|
1651
|
+
# hyphen only joins when a link cannot start there.
|
|
1652
|
+
#
|
|
1653
|
+
# The class is written as three pieces because `restart_step` has
|
|
1654
|
+
# to tell them apart. An id itself takes any of the three.
|
|
1655
|
+
rule(:id_char) { id_ascii_char | id_carry_char | id_restart_char }
|
|
1656
|
+
|
|
1657
|
+
rule(:id_ascii_char) do
|
|
1658
|
+
match['[\\u0021-\\u007E]&&[^;:,"%()<=>@\\[\\]^{|}~.\\-#&*0-9]']
|
|
1659
|
+
end
|
|
1660
|
+
|
|
1661
|
+
# These carry mermaid's keyword hunt along rather than ending it.
|
|
1662
|
+
rule(:id_carry_char) { match["#&*0-9"] }
|
|
1663
|
+
|
|
1664
|
+
# And a letter outside ASCII starts it over.
|
|
1665
|
+
rule(:id_restart_char) { match[MERMAID_UNICODE_TEXT] }
|
|
1666
|
+
|
|
1667
|
+
# An id stops in front of an entity escape, and the guard belongs
|
|
1668
|
+
# to the RUN rather than to the character. `arrowhead_ends_id` also
|
|
1669
|
+
# reads `id_body`, but it asks a different question — whether
|
|
1670
|
+
# anything could continue an id behind a link opening — and there a
|
|
1671
|
+
# `#` still could. Guarding the character would change that rule
|
|
1672
|
+
# too, so the guard sits here.
|
|
1673
|
+
rule(:id_run) { (entity_escape.absent? >> id_body).repeat(1) }
|
|
1674
|
+
|
|
1675
|
+
rule(:id_body) { id_dot | id_hyphen | id_char }
|
|
1676
|
+
|
|
1677
|
+
# Before mermaid lexes anything it rewrites the whole source. The
|
|
1678
|
+
# rewrite that matters here is the last of `encodeEntities`' three,
|
|
1679
|
+
# `r.replace(/#\w+;/g, …)`, which turns `#\w+;` into a placeholder
|
|
1680
|
+
# standing for `&…;`
|
|
1681
|
+
# (`mermaid/dist/chunks/mermaid.esm.min/chunk-7CWYLC5S.mjs`). The
|
|
1682
|
+
# placeholder is spelt in characters no id may hold, so an id that
|
|
1683
|
+
# swallowed the sequence is one mermaid cannot lex.
|
|
1684
|
+
#
|
|
1685
|
+
# NOT every `#\w+;`, though, and this rule does not model the
|
|
1686
|
+
# difference. Two rewrites run FIRST — `/style.*:\S*#.*;/` and
|
|
1687
|
+
# `/classDef.*:\S*#.*;/` — and each strips the final `;` from what
|
|
1688
|
+
# it matches, so mmdc draws `stylex[foo:#bar]-->A#a;` where this
|
|
1689
|
+
# refuses it. Modelling that needs a source pre-pass, which this
|
|
1690
|
+
# grammar has no place for yet.
|
|
1691
|
+
#
|
|
1692
|
+
# The shape is exact: the run must be `[A-Za-z0-9_]` and the `;`
|
|
1693
|
+
# must abut it. mmdc refuses `#a;B`, `#35;B` and `Z#a;B`, and draws
|
|
1694
|
+
# `#;B`, `#a ;B`, `#é;B` and `#a.b;B` — none of which the rewrite
|
|
1695
|
+
# matches.
|
|
1696
|
+
#
|
|
1697
|
+
# Only the id stops here. mermaid applies the rewrite everywhere,
|
|
1698
|
+
# so `A[#a;]` draws a node labelled `&a;`, and rendering that text
|
|
1699
|
+
# belongs to a change that models the escape rather than refuses it.
|
|
1700
|
+
rule(:entity_escape) do
|
|
1701
|
+
str("#") >> match["a-zA-Z0-9_"].repeat(1) >> semicolon
|
|
1702
|
+
end
|
|
1703
|
+
|
|
1704
|
+
# Line terminator. A statement ends at the newline, and NOT at a
|
|
1705
|
+
# `%%` on the way to it: mermaid strips a comment with
|
|
1706
|
+
# `/^\s*%%(?!{)[^\n]+\n?/gm`, anchored to the line start, so a `%%`
|
|
1707
|
+
# with a statement in front of it is never a comment. It is content,
|
|
1708
|
+
# and mmdc reads `A%%c` as one node called `A%%c` and `A;%%c` as the
|
|
1709
|
+
# two nodes `A` and `%%c`.
|
|
1710
|
+
#
|
|
1711
|
+
# So no trailing comment is taken here. mmdc refuses `A %% c`,
|
|
1712
|
+
# `A --> B %% c` and `A[T] %% c` outright.
|
|
1713
|
+
#
|
|
1714
|
+
# `A%%c` is the one under-acceptance left, and closing it means
|
|
1715
|
+
# letting `%` into a node id — a widening this PR does not make.
|
|
1716
|
+
# `A;%% c` is refused by `separator`, which is where the `%%` guard
|
|
1717
|
+
# lives.
|
|
1718
|
+
rule(:line_end) { semicolon.maybe >> space? >> (newline | eof) }
|
|
1719
|
+
end
|
|
1720
|
+
end
|
|
1721
|
+
end
|
|
1722
|
+
end
|