sirena 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/.github/workflows/build_deploy.yml +59 -0
- data/.github/workflows/links.yml +85 -0
- data/.github/workflows/rake.yml +15 -0
- data/.github/workflows/release.yml +27 -0
- data/.gitignore +68 -0
- data/.rspec +3 -0
- data/.rubocop.yml +14 -0
- data/.rubocop_todo.yml +70 -0
- data/ARCHITECTURE.md +744 -0
- data/Gemfile +12 -0
- data/LICENSE +25 -0
- data/README.adoc +357 -0
- data/Rakefile +11 -0
- data/docs/.gitignore +1 -0
- data/docs/Gemfile +13 -0
- data/docs/_config.yml +182 -0
- data/docs/_diagram_types/architecture-diagram.adoc +314 -0
- data/docs/_diagram_types/block-diagram.adoc +345 -0
- data/docs/_diagram_types/c4-diagram.adoc +559 -0
- data/docs/_diagram_types/class-diagram.adoc +816 -0
- data/docs/_diagram_types/er-diagram.adoc +719 -0
- data/docs/_diagram_types/error-diagram.adoc +114 -0
- data/docs/_diagram_types/examples/flowchart-examples.adoc +29 -0
- data/docs/_diagram_types/flowchart.adoc +488 -0
- data/docs/_diagram_types/gantt-chart.adoc +502 -0
- data/docs/_diagram_types/git-graph.adoc +600 -0
- data/docs/_diagram_types/index.adoc +192 -0
- data/docs/_diagram_types/info-diagram.adoc +103 -0
- data/docs/_diagram_types/kanban-diagram.adoc +262 -0
- data/docs/_diagram_types/mindmap.adoc +603 -0
- data/docs/_diagram_types/packet-diagram.adoc +378 -0
- data/docs/_diagram_types/pie-chart.adoc +335 -0
- data/docs/_diagram_types/quadrant-chart.adoc +406 -0
- data/docs/_diagram_types/radar-chart.adoc +528 -0
- data/docs/_diagram_types/requirement-diagram.adoc +416 -0
- data/docs/_diagram_types/sankey-diagram.adoc +357 -0
- data/docs/_diagram_types/sequence-diagram.adoc +664 -0
- data/docs/_diagram_types/state-diagram.adoc +658 -0
- data/docs/_diagram_types/timeline.adoc +352 -0
- data/docs/_diagram_types/treemap-diagram.adoc +462 -0
- data/docs/_diagram_types/user-journey.adoc +602 -0
- data/docs/_features/index.adoc +129 -0
- data/docs/_guides/cli-reference.adoc +203 -0
- data/docs/_guides/index.adoc +56 -0
- data/docs/_guides/installation.adoc +100 -0
- data/docs/_guides/quick-start.adoc +132 -0
- data/docs/_pages/comparison.adoc +441 -0
- data/docs/_pages/compatibility.adoc +300 -0
- data/docs/_pages/index.adoc +39 -0
- data/docs/_references/index.adoc +103 -0
- data/docs/_tutorials/index.adoc +57 -0
- data/docs/index.adoc +166 -0
- data/docs/lychee.toml +54 -0
- data/examples/.gitignore +10 -0
- data/examples/README.adoc +196 -0
- data/examples/README.md +64 -0
- data/examples/architecture/01-basic-services.mmd +9 -0
- data/examples/architecture/01-basic-services.svg +37 -0
- data/examples/architecture/02-service-groups.mmd +16 -0
- data/examples/architecture/02-service-groups.svg +55 -0
- data/examples/architecture/README.adoc +79 -0
- data/examples/block/01-basic-blocks.mmd +13 -0
- data/examples/block/01-basic-blocks.svg +44 -0
- data/examples/block/02-block-shapes.mmd +13 -0
- data/examples/block/02-block-shapes.svg +47 -0
- data/examples/block/README.adoc +85 -0
- data/examples/c4/01-context-diagram.mmd +10 -0
- data/examples/c4/01-context-diagram.svg +45 -0
- data/examples/c4/02-container-diagram.mmd +24 -0
- data/examples/c4/02-container-diagram.svg +105 -0
- data/examples/c4/README.adoc +92 -0
- data/examples/class_diagram/01-basic-classes.mmd +61 -0
- data/examples/class_diagram/01-basic-classes.svg +117 -0
- data/examples/class_diagram/02-relationships.mmd +61 -0
- data/examples/class_diagram/02-relationships.svg +129 -0
- data/examples/class_diagram/README.adoc +93 -0
- data/examples/er_diagram/01-basic-entities.mmd +64 -0
- data/examples/er_diagram/01-basic-entities.svg +5 -0
- data/examples/er_diagram/02-cardinality.mmd +57 -0
- data/examples/er_diagram/02-cardinality.svg +125 -0
- data/examples/er_diagram/README.adoc +88 -0
- data/examples/error/01-basic-error.mmd +1 -0
- data/examples/error/01-basic-error.svg +13 -0
- data/examples/error/02-error-display.mmd +1 -0
- data/examples/error/02-error-display.svg +13 -0
- data/examples/error/README.adoc +71 -0
- data/examples/error_message_example.svg +13 -0
- data/examples/flowchart/00-original.mmd +13 -0
- data/examples/flowchart/00-original.svg +5 -0
- data/examples/flowchart/01-basic-flow.mmd +7 -0
- data/examples/flowchart/01-basic-flow.svg +52 -0
- data/examples/flowchart/01-basic-flow.yml +13 -0
- data/examples/flowchart/02*.svg +87 -0
- data/examples/flowchart/02-node-shapes.mmd +9 -0
- data/examples/flowchart/02-node-shapes.svg +33 -0
- data/examples/flowchart/03-edge-types.mmd +7 -0
- data/examples/flowchart/03-edge-types.svg +53 -0
- data/examples/flowchart/04-subgraphs.mmd +9 -0
- data/examples/flowchart/04-subgraphs.svg +33 -0
- data/examples/flowchart/05-styling.mmd +9 -0
- data/examples/flowchart/05-styling.svg +33 -0
- data/examples/flowchart/06-complex-flow.mmd +8 -0
- data/examples/flowchart/06-complex-flow.svg +59 -0
- data/examples/flowchart/README.adoc +167 -0
- data/examples/gantt/01-simple-timeline.* +14 -0
- data/examples/gantt/01-simple-timeline.mmd +6 -0
- data/examples/gantt/01-simple-timeline.svg +26 -0
- data/examples/gantt/02-task-dependencies.mmd +6 -0
- data/examples/gantt/02-task-dependencies.svg +26 -0
- data/examples/gantt/README.adoc +86 -0
- data/examples/git_graph/01-linear-history.mmd +12 -0
- data/examples/git_graph/01-linear-history.svg +26 -0
- data/examples/git_graph/02-branching.mmd +12 -0
- data/examples/git_graph/02-branching.svg +26 -0
- data/examples/git_graph/README.adoc +73 -0
- data/examples/info/02-showinfo.mmd +1 -0
- data/examples/info/02-showinfo.svg +10 -0
- data/examples/info/README.adoc +58 -0
- data/examples/info_showinfo_example.svg +10 -0
- data/examples/kanban/01-simple-board.mmd +8 -0
- data/examples/kanban/01-simple-board.svg +43 -0
- data/examples/kanban/02-workflow.mmd +8 -0
- data/examples/kanban/02-workflow.svg +43 -0
- data/examples/kanban/README.adoc +79 -0
- data/examples/mindmap/01-simple-tree.mmd +19 -0
- data/examples/mindmap/01-simple-tree.svg +61 -0
- data/examples/mindmap/02-knowledge-map.mmd +19 -0
- data/examples/mindmap/02-knowledge-map.svg +61 -0
- data/examples/mindmap/README.adoc +77 -0
- data/examples/packet/01-basic-packet.* +17 -0
- data/examples/packet/01-basic-packet.mmd +4 -0
- data/examples/packet/01-basic-packet.svg +82 -0
- data/examples/packet/README.adoc +58 -0
- data/examples/pie/01-simple-chart.mmd +5 -0
- data/examples/pie/01-simple-chart.svg +17 -0
- data/examples/pie/02-labeled-slices.mmd +6 -0
- data/examples/pie/02-labeled-slices.svg +19 -0
- data/examples/pie/README.adoc +75 -0
- data/examples/quadrant/01-basic-quadrant.mmd +13 -0
- data/examples/quadrant/01-basic-quadrant.svg +33 -0
- data/examples/quadrant/02-positioned-items.mmd +14 -0
- data/examples/quadrant/02-positioned-items.svg +35 -0
- data/examples/quadrant/README.adoc +84 -0
- data/examples/radar/01-simple-radar.* +5 -0
- data/examples/radar/01-simple-radar.mmd +3 -0
- data/examples/radar/01-simple-radar.svg +25 -0
- data/examples/radar/02-multiple-curves.mmd +4 -0
- data/examples/radar/02-multiple-curves.svg +43 -0
- data/examples/radar/README.adoc +75 -0
- data/examples/requirement/01-basic-requirements.mmd +23 -0
- data/examples/requirement/01-basic-requirements.svg +49 -0
- data/examples/requirement/02-risk-levels.mmd +23 -0
- data/examples/requirement/02-risk-levels.svg +49 -0
- data/examples/requirement/README.adoc +85 -0
- data/examples/sankey/01-simple-flow.mmd +7 -0
- data/examples/sankey/01-simple-flow.svg +34 -0
- data/examples/sankey/02-multi-stage.mmd +11 -0
- data/examples/sankey/02-multi-stage.svg +44 -0
- data/examples/sankey/README.adoc +74 -0
- data/examples/sequence/01-basic-sequence.mmd +27 -0
- data/examples/sequence/01-basic-sequence.svg +5 -0
- data/examples/sequence/02-activations.mmd +17 -0
- data/examples/sequence/02-activations.svg +78 -0
- data/examples/sequence/README.adoc +86 -0
- data/examples/state_diagram/01-simple-states.mmd +29 -0
- data/examples/state_diagram/01-simple-states.svg +5 -0
- data/examples/state_diagram/02-composite.mmd +19 -0
- data/examples/state_diagram/02-composite.svg +81 -0
- data/examples/state_diagram/README.adoc +90 -0
- data/examples/timeline/01-simple-timeline.mmd +11 -0
- data/examples/timeline/01-simple-timeline.svg +36 -0
- data/examples/timeline/02-periods.mmd +15 -0
- data/examples/timeline/02-periods.svg +47 -0
- data/examples/timeline/README.adoc +78 -0
- data/examples/treemap/01-basic-treemap.mmd +12 -0
- data/examples/treemap/01-basic-treemap.svg +59 -0
- data/examples/treemap/README.adoc +59 -0
- data/examples/user_journey/01-simple-journey.mmd +23 -0
- data/examples/user_journey/01-simple-journey.svg +5 -0
- data/examples/user_journey/02-multi-actor.mmd +18 -0
- data/examples/user_journey/02-multi-actor.svg +129 -0
- data/examples/user_journey/README.adoc +81 -0
- data/examples/xychart/01-line-chart.mmd +5 -0
- data/examples/xychart/01-line-chart.svg +43 -0
- data/examples/xychart/02-bar-chart.mmd +7 -0
- data/examples/xychart/02-bar-chart.svg +48 -0
- data/examples/xychart/README.adoc +80 -0
- data/exe/sirena +7 -0
- data/lib/sirena/cli.rb +138 -0
- data/lib/sirena/commands/batch.rb +117 -0
- data/lib/sirena/commands/render.rb +80 -0
- data/lib/sirena/commands/types.rb +29 -0
- data/lib/sirena/commands/version.rb +24 -0
- data/lib/sirena/diagram/architecture.rb +46 -0
- data/lib/sirena/diagram/base.rb +61 -0
- data/lib/sirena/diagram/block.rb +81 -0
- data/lib/sirena/diagram/c4.rb +328 -0
- data/lib/sirena/diagram/class_diagram.rb +385 -0
- data/lib/sirena/diagram/er_diagram.rb +238 -0
- data/lib/sirena/diagram/error.rb +38 -0
- data/lib/sirena/diagram/flowchart.rb +160 -0
- data/lib/sirena/diagram/gantt.rb +71 -0
- data/lib/sirena/diagram/git_graph.rb +36 -0
- data/lib/sirena/diagram/info.rb +38 -0
- data/lib/sirena/diagram/kanban.rb +178 -0
- data/lib/sirena/diagram/mindmap.rb +54 -0
- data/lib/sirena/diagram/packet.rb +79 -0
- data/lib/sirena/diagram/pie.rb +115 -0
- data/lib/sirena/diagram/quadrant.rb +138 -0
- data/lib/sirena/diagram/radar.rb +52 -0
- data/lib/sirena/diagram/requirement.rb +133 -0
- data/lib/sirena/diagram/sankey.rb +217 -0
- data/lib/sirena/diagram/sequence.rb +242 -0
- data/lib/sirena/diagram/state_diagram.rb +237 -0
- data/lib/sirena/diagram/timeline.rb +171 -0
- data/lib/sirena/diagram/treemap.rb +84 -0
- data/lib/sirena/diagram/user_journey.rb +149 -0
- data/lib/sirena/diagram/xy_chart.rb +76 -0
- data/lib/sirena/diagram.rb +8 -0
- data/lib/sirena/diagram_registry.rb +101 -0
- data/lib/sirena/engine.rb +292 -0
- data/lib/sirena/parser/architecture.rb +41 -0
- data/lib/sirena/parser/base.rb +41 -0
- data/lib/sirena/parser/block.rb +72 -0
- data/lib/sirena/parser/c4.rb +53 -0
- data/lib/sirena/parser/class_diagram.rb +63 -0
- data/lib/sirena/parser/er_diagram.rb +40 -0
- data/lib/sirena/parser/error.rb +49 -0
- data/lib/sirena/parser/flowchart.rb +71 -0
- data/lib/sirena/parser/gantt.rb +60 -0
- data/lib/sirena/parser/git_graph.rb +95 -0
- data/lib/sirena/parser/grammars/architecture.rb +145 -0
- data/lib/sirena/parser/grammars/block.rb +190 -0
- data/lib/sirena/parser/grammars/c4.rb +226 -0
- data/lib/sirena/parser/grammars/class_diagram.rb +284 -0
- data/lib/sirena/parser/grammars/common.rb +84 -0
- data/lib/sirena/parser/grammars/er_diagram.rb +114 -0
- data/lib/sirena/parser/grammars/error.rb +40 -0
- data/lib/sirena/parser/grammars/flowchart.rb +298 -0
- data/lib/sirena/parser/grammars/gantt.rb +252 -0
- data/lib/sirena/parser/grammars/git_graph.rb +167 -0
- data/lib/sirena/parser/grammars/info.rb +58 -0
- data/lib/sirena/parser/grammars/kanban.rb +83 -0
- data/lib/sirena/parser/grammars/mindmap.rb +115 -0
- data/lib/sirena/parser/grammars/packet.rb +73 -0
- data/lib/sirena/parser/grammars/pie.rb +128 -0
- data/lib/sirena/parser/grammars/quadrant.rb +199 -0
- data/lib/sirena/parser/grammars/radar.rb +150 -0
- data/lib/sirena/parser/grammars/requirement.rb +188 -0
- data/lib/sirena/parser/grammars/sankey.rb +104 -0
- data/lib/sirena/parser/grammars/sequence.rb +247 -0
- data/lib/sirena/parser/grammars/state_diagram.rb +172 -0
- data/lib/sirena/parser/grammars/timeline.rb +142 -0
- data/lib/sirena/parser/grammars/treemap.rb +120 -0
- data/lib/sirena/parser/grammars/xy_chart.rb +120 -0
- data/lib/sirena/parser/info.rb +49 -0
- data/lib/sirena/parser/kanban.rb +97 -0
- data/lib/sirena/parser/mindmap.rb +106 -0
- data/lib/sirena/parser/packet.rb +76 -0
- data/lib/sirena/parser/pie.rb +49 -0
- data/lib/sirena/parser/quadrant.rb +57 -0
- data/lib/sirena/parser/radar.rb +104 -0
- data/lib/sirena/parser/requirement.rb +70 -0
- data/lib/sirena/parser/sankey.rb +64 -0
- data/lib/sirena/parser/sequence.rb +51 -0
- data/lib/sirena/parser/state_diagram.rb +69 -0
- data/lib/sirena/parser/timeline.rb +57 -0
- data/lib/sirena/parser/transforms/architecture.rb +97 -0
- data/lib/sirena/parser/transforms/block.rb +254 -0
- data/lib/sirena/parser/transforms/c4.rb +347 -0
- data/lib/sirena/parser/transforms/class_diagram.rb +352 -0
- data/lib/sirena/parser/transforms/er_diagram.rb +169 -0
- data/lib/sirena/parser/transforms/error.rb +58 -0
- data/lib/sirena/parser/transforms/flowchart.rb +293 -0
- data/lib/sirena/parser/transforms/gantt.rb +215 -0
- data/lib/sirena/parser/transforms/git_graph.rb +160 -0
- data/lib/sirena/parser/transforms/info.rb +58 -0
- data/lib/sirena/parser/transforms/kanban.rb +176 -0
- data/lib/sirena/parser/transforms/mindmap.rb +227 -0
- data/lib/sirena/parser/transforms/packet.rb +63 -0
- data/lib/sirena/parser/transforms/pie.rb +143 -0
- data/lib/sirena/parser/transforms/quadrant.rb +177 -0
- data/lib/sirena/parser/transforms/radar.rb +126 -0
- data/lib/sirena/parser/transforms/requirement.rb +272 -0
- data/lib/sirena/parser/transforms/sankey.rb +122 -0
- data/lib/sirena/parser/transforms/sequence.rb +342 -0
- data/lib/sirena/parser/transforms/state_diagram.rb +292 -0
- data/lib/sirena/parser/transforms/timeline.rb +177 -0
- data/lib/sirena/parser/transforms/treemap.rb +81 -0
- data/lib/sirena/parser/transforms/xy_chart.rb +132 -0
- data/lib/sirena/parser/treemap.rb +98 -0
- data/lib/sirena/parser/user_journey.rb +120 -0
- data/lib/sirena/parser/xy_chart.rb +114 -0
- data/lib/sirena/parser.rb +8 -0
- data/lib/sirena/renderer/architecture.rb +251 -0
- data/lib/sirena/renderer/base.rb +251 -0
- data/lib/sirena/renderer/block.rb +286 -0
- data/lib/sirena/renderer/c4.rb +490 -0
- data/lib/sirena/renderer/class_diagram.rb +499 -0
- data/lib/sirena/renderer/er_diagram.rb +417 -0
- data/lib/sirena/renderer/error.rb +131 -0
- data/lib/sirena/renderer/flowchart.rb +301 -0
- data/lib/sirena/renderer/gantt.rb +331 -0
- data/lib/sirena/renderer/git_graph.rb +368 -0
- data/lib/sirena/renderer/info.rb +93 -0
- data/lib/sirena/renderer/kanban.rb +295 -0
- data/lib/sirena/renderer/mindmap.rb +396 -0
- data/lib/sirena/renderer/packet.rb +239 -0
- data/lib/sirena/renderer/pie.rb +235 -0
- data/lib/sirena/renderer/quadrant.rb +292 -0
- data/lib/sirena/renderer/radar.rb +323 -0
- data/lib/sirena/renderer/requirement.rb +371 -0
- data/lib/sirena/renderer/sankey.rb +255 -0
- data/lib/sirena/renderer/sequence.rb +424 -0
- data/lib/sirena/renderer/state_diagram.rb +328 -0
- data/lib/sirena/renderer/timeline.rb +304 -0
- data/lib/sirena/renderer/treemap.rb +152 -0
- data/lib/sirena/renderer/user_journey.rb +331 -0
- data/lib/sirena/renderer/xy_chart.rb +452 -0
- data/lib/sirena/renderer.rb +8 -0
- data/lib/sirena/svg/circle.rb +41 -0
- data/lib/sirena/svg/document.rb +103 -0
- data/lib/sirena/svg/element.rb +65 -0
- data/lib/sirena/svg/ellipse.rb +33 -0
- data/lib/sirena/svg/group.rb +71 -0
- data/lib/sirena/svg/line.rb +49 -0
- data/lib/sirena/svg/path.rb +76 -0
- data/lib/sirena/svg/polygon.rb +43 -0
- data/lib/sirena/svg/polyline.rb +35 -0
- data/lib/sirena/svg/rect.rb +57 -0
- data/lib/sirena/svg/style.rb +44 -0
- data/lib/sirena/svg/text.rb +72 -0
- data/lib/sirena/svg.rb +19 -0
- data/lib/sirena/text_measurement.rb +71 -0
- data/lib/sirena/theme/builtin/dark.yml +70 -0
- data/lib/sirena/theme/builtin/default.yml +80 -0
- data/lib/sirena/theme/builtin/high_contrast.yml +70 -0
- data/lib/sirena/theme/builtin/light.yml +70 -0
- data/lib/sirena/theme/color_palette.rb +48 -0
- data/lib/sirena/theme/effect_styles.rb +28 -0
- data/lib/sirena/theme/registry.rb +41 -0
- data/lib/sirena/theme/shape_styles.rb +28 -0
- data/lib/sirena/theme/spacing_config.rb +24 -0
- data/lib/sirena/theme/typography.rb +30 -0
- data/lib/sirena/theme.rb +69 -0
- data/lib/sirena/transform/architecture.rb +273 -0
- data/lib/sirena/transform/base.rb +199 -0
- data/lib/sirena/transform/block.rb +215 -0
- data/lib/sirena/transform/c4.rb +288 -0
- data/lib/sirena/transform/class_diagram.rb +296 -0
- data/lib/sirena/transform/er_diagram.rb +204 -0
- data/lib/sirena/transform/error.rb +39 -0
- data/lib/sirena/transform/flowchart.rb +161 -0
- data/lib/sirena/transform/gantt.rb +253 -0
- data/lib/sirena/transform/git_graph.rb +283 -0
- data/lib/sirena/transform/info.rb +39 -0
- data/lib/sirena/transform/kanban.rb +180 -0
- data/lib/sirena/transform/mindmap.rb +251 -0
- data/lib/sirena/transform/packet.rb +185 -0
- data/lib/sirena/transform/pie.rb +62 -0
- data/lib/sirena/transform/quadrant.rb +167 -0
- data/lib/sirena/transform/radar.rb +227 -0
- data/lib/sirena/transform/requirement.rb +233 -0
- data/lib/sirena/transform/sankey.rb +212 -0
- data/lib/sirena/transform/sequence.rb +143 -0
- data/lib/sirena/transform/state_diagram.rb +228 -0
- data/lib/sirena/transform/timeline.rb +139 -0
- data/lib/sirena/transform/treemap.rb +120 -0
- data/lib/sirena/transform/user_journey.rb +207 -0
- data/lib/sirena/transform/xy_chart.rb +273 -0
- data/lib/sirena/transform.rb +8 -0
- data/lib/sirena/version.rb +5 -0
- data/lib/sirena.rb +328 -0
- data/lib/tasks/benchmark.rake +532 -0
- data/lib/tasks/examples.rake +468 -0
- data/lib/tasks/generate_mermaid_fixtures.rake +363 -0
- data/lib/tasks/mermaid_fixtures.rake +46 -0
- data/scripts/extract_mermaid_tests.rb +493 -0
- data/scripts/rename_to_sirena.rb +73 -0
- data/sirena.gemspec +47 -0
- metadata +529 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lutaml/model'
|
|
4
|
+
require_relative 'base'
|
|
5
|
+
|
|
6
|
+
module Sirena
|
|
7
|
+
module Diagram
|
|
8
|
+
# Represents a sequence diagram participant.
|
|
9
|
+
#
|
|
10
|
+
# A participant represents an actor or entity in the sequence diagram
|
|
11
|
+
# that can send and receive messages.
|
|
12
|
+
class SequenceParticipant < Lutaml::Model::Serializable
|
|
13
|
+
# Unique identifier for the participant
|
|
14
|
+
attribute :id, :string
|
|
15
|
+
|
|
16
|
+
# Display text/label for the participant
|
|
17
|
+
attribute :label, :string
|
|
18
|
+
|
|
19
|
+
# Participant type: :participant, :actor
|
|
20
|
+
attribute :actor_type, :string
|
|
21
|
+
|
|
22
|
+
# Initialize with default actor type
|
|
23
|
+
def initialize(*args)
|
|
24
|
+
super
|
|
25
|
+
self.actor_type ||= 'participant'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Validates the participant has required attributes.
|
|
29
|
+
#
|
|
30
|
+
# @return [Boolean] true if participant is valid
|
|
31
|
+
def valid?
|
|
32
|
+
!id.nil? && !id.empty? && !label.nil?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Represents a sequence diagram message.
|
|
37
|
+
#
|
|
38
|
+
# A message represents communication between participants in a
|
|
39
|
+
# sequence diagram, with various arrow types and optional text.
|
|
40
|
+
class SequenceMessage < Lutaml::Model::Serializable
|
|
41
|
+
# Source participant identifier
|
|
42
|
+
attribute :from_id, :string
|
|
43
|
+
|
|
44
|
+
# Target participant identifier
|
|
45
|
+
attribute :to_id, :string
|
|
46
|
+
|
|
47
|
+
# Message text content
|
|
48
|
+
attribute :message_text, :string
|
|
49
|
+
|
|
50
|
+
# Arrow type: :solid, :dotted, :async, :async_dotted,
|
|
51
|
+
# :solid_cross, :dotted_cross
|
|
52
|
+
attribute :arrow_type, :string
|
|
53
|
+
|
|
54
|
+
# Initialize with default arrow type
|
|
55
|
+
def initialize(*args)
|
|
56
|
+
super
|
|
57
|
+
self.arrow_type ||= 'solid'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Validates the message has required attributes.
|
|
61
|
+
#
|
|
62
|
+
# @return [Boolean] true if message is valid
|
|
63
|
+
def valid?
|
|
64
|
+
!from_id.nil? && !from_id.empty? &&
|
|
65
|
+
!to_id.nil? && !to_id.empty?
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Represents an activation box (lifecycle) in a sequence diagram.
|
|
70
|
+
#
|
|
71
|
+
# An activation shows when a participant is active or processing.
|
|
72
|
+
class SequenceActivation < Lutaml::Model::Serializable
|
|
73
|
+
# Participant identifier
|
|
74
|
+
attribute :participant_id, :string
|
|
75
|
+
|
|
76
|
+
# Start message index (position in messages array)
|
|
77
|
+
attribute :start_index, :integer
|
|
78
|
+
|
|
79
|
+
# End message index (position in messages array)
|
|
80
|
+
attribute :end_index, :integer
|
|
81
|
+
|
|
82
|
+
# Validates the activation has required attributes.
|
|
83
|
+
#
|
|
84
|
+
# @return [Boolean] true if activation is valid
|
|
85
|
+
def valid?
|
|
86
|
+
!participant_id.nil? && !participant_id.empty? &&
|
|
87
|
+
!start_index.nil? && !end_index.nil? &&
|
|
88
|
+
start_index <= end_index
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Represents a note in a sequence diagram.
|
|
93
|
+
#
|
|
94
|
+
# A note provides additional context or information in the diagram.
|
|
95
|
+
class SequenceNote < Lutaml::Model::Serializable
|
|
96
|
+
# Note text content
|
|
97
|
+
attribute :text, :string
|
|
98
|
+
|
|
99
|
+
# Position: :left_of, :right_of, :over
|
|
100
|
+
attribute :position, :string
|
|
101
|
+
|
|
102
|
+
# Participant identifier(s) - array for :over with multiple
|
|
103
|
+
attribute :participant_ids, :string, collection: true,
|
|
104
|
+
default: -> { [] }
|
|
105
|
+
|
|
106
|
+
# Message index where the note appears
|
|
107
|
+
attribute :message_index, :integer
|
|
108
|
+
|
|
109
|
+
# Validates the note has required attributes.
|
|
110
|
+
#
|
|
111
|
+
# @return [Boolean] true if note is valid
|
|
112
|
+
def valid?
|
|
113
|
+
!text.nil? && !text.empty? &&
|
|
114
|
+
!position.nil? && !participant_ids.empty?
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Sequence diagram model.
|
|
119
|
+
#
|
|
120
|
+
# Represents a complete sequence diagram with participants, messages,
|
|
121
|
+
# activations, and notes. Sequence diagrams show interactions between
|
|
122
|
+
# participants over time in a vertical timeline format.
|
|
123
|
+
#
|
|
124
|
+
# @example Creating a simple sequence diagram
|
|
125
|
+
# sequence = Sequence.new
|
|
126
|
+
# sequence.participants << SequenceParticipant.new(
|
|
127
|
+
# id: 'Alice',
|
|
128
|
+
# label: 'Alice',
|
|
129
|
+
# actor_type: 'actor'
|
|
130
|
+
# )
|
|
131
|
+
# sequence.participants << SequenceParticipant.new(
|
|
132
|
+
# id: 'Bob',
|
|
133
|
+
# label: 'Bob',
|
|
134
|
+
# actor_type: 'actor'
|
|
135
|
+
# )
|
|
136
|
+
# sequence.messages << SequenceMessage.new(
|
|
137
|
+
# from_id: 'Alice',
|
|
138
|
+
# to_id: 'Bob',
|
|
139
|
+
# message_text: 'Hello Bob',
|
|
140
|
+
# arrow_type: 'solid'
|
|
141
|
+
# )
|
|
142
|
+
class Sequence < Base
|
|
143
|
+
# Collection of participants in the sequence diagram
|
|
144
|
+
attribute :participants, SequenceParticipant, collection: true,
|
|
145
|
+
default: -> { [] }
|
|
146
|
+
|
|
147
|
+
# Collection of messages between participants
|
|
148
|
+
attribute :messages, SequenceMessage, collection: true,
|
|
149
|
+
default: -> { [] }
|
|
150
|
+
|
|
151
|
+
# Collection of activation boxes
|
|
152
|
+
attribute :activations, SequenceActivation, collection: true,
|
|
153
|
+
default: -> { [] }
|
|
154
|
+
|
|
155
|
+
# Collection of notes
|
|
156
|
+
attribute :notes, SequenceNote, collection: true,
|
|
157
|
+
default: -> { [] }
|
|
158
|
+
|
|
159
|
+
# Returns the diagram type identifier.
|
|
160
|
+
#
|
|
161
|
+
# @return [Symbol] :sequence
|
|
162
|
+
def diagram_type
|
|
163
|
+
:sequence
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Validates the sequence diagram structure.
|
|
167
|
+
#
|
|
168
|
+
# A sequence diagram is valid if:
|
|
169
|
+
# - It has at least one participant
|
|
170
|
+
# - All participants are valid
|
|
171
|
+
# - All messages are valid
|
|
172
|
+
# - All message references point to existing participants
|
|
173
|
+
# - All activations are valid
|
|
174
|
+
# - All notes are valid
|
|
175
|
+
#
|
|
176
|
+
# @return [Boolean] true if sequence diagram is valid
|
|
177
|
+
def valid?
|
|
178
|
+
return false if participants.nil? || participants.empty?
|
|
179
|
+
return false unless participants.all?(&:valid?)
|
|
180
|
+
return false unless messages.nil? || messages.all?(&:valid?)
|
|
181
|
+
return false unless activations.nil? || activations.all?(&:valid?)
|
|
182
|
+
return false unless notes.nil? || notes.all?(&:valid?)
|
|
183
|
+
|
|
184
|
+
# Validate message references
|
|
185
|
+
participant_ids = participants.map(&:id)
|
|
186
|
+
messages&.each do |message|
|
|
187
|
+
return false unless participant_ids.include?(message.from_id)
|
|
188
|
+
return false unless participant_ids.include?(message.to_id)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Validate activation references
|
|
192
|
+
activations&.each do |activation|
|
|
193
|
+
return false unless participant_ids.include?(
|
|
194
|
+
activation.participant_id
|
|
195
|
+
)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Validate note references
|
|
199
|
+
notes&.each do |note|
|
|
200
|
+
note.participant_ids.each do |pid|
|
|
201
|
+
return false unless participant_ids.include?(pid)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
true
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Finds a participant by its identifier.
|
|
209
|
+
#
|
|
210
|
+
# @param id [String] the participant identifier to find
|
|
211
|
+
# @return [SequenceParticipant, nil] the participant or nil if not
|
|
212
|
+
# found
|
|
213
|
+
def find_participant(id)
|
|
214
|
+
participants.find { |p| p.id == id }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Finds all messages from a specific participant.
|
|
218
|
+
#
|
|
219
|
+
# @param participant_id [String] the source participant identifier
|
|
220
|
+
# @return [Array<SequenceMessage>] messages from the participant
|
|
221
|
+
def messages_from(participant_id)
|
|
222
|
+
messages.select { |m| m.from_id == participant_id }
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Finds all messages to a specific participant.
|
|
226
|
+
#
|
|
227
|
+
# @param participant_id [String] the target participant identifier
|
|
228
|
+
# @return [Array<SequenceMessage>] messages to the participant
|
|
229
|
+
def messages_to(participant_id)
|
|
230
|
+
messages.select { |m| m.to_id == participant_id }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Finds activations for a specific participant.
|
|
234
|
+
#
|
|
235
|
+
# @param participant_id [String] the participant identifier
|
|
236
|
+
# @return [Array<SequenceActivation>] activations for the participant
|
|
237
|
+
def activations_for(participant_id)
|
|
238
|
+
activations.select { |a| a.participant_id == participant_id }
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lutaml/model'
|
|
4
|
+
require_relative 'base'
|
|
5
|
+
|
|
6
|
+
module Sirena
|
|
7
|
+
module Diagram
|
|
8
|
+
# Represents a state node in a state diagram.
|
|
9
|
+
#
|
|
10
|
+
# A state node can represent different types of states in a state
|
|
11
|
+
# machine, including normal states, start/end states, choice points,
|
|
12
|
+
# and fork/join nodes.
|
|
13
|
+
class StateNode < Lutaml::Model::Serializable
|
|
14
|
+
# Unique identifier for the state
|
|
15
|
+
attribute :id, :string
|
|
16
|
+
|
|
17
|
+
# Display label for the state
|
|
18
|
+
attribute :label, :string
|
|
19
|
+
|
|
20
|
+
# State type: :normal, :start, :end, :choice, :fork, :join
|
|
21
|
+
attribute :state_type, :string
|
|
22
|
+
|
|
23
|
+
# Optional description for composite states
|
|
24
|
+
attribute :description, :string
|
|
25
|
+
|
|
26
|
+
# Child states for composite/nested states
|
|
27
|
+
attribute :children, StateNode, collection: true, default: -> { [] }
|
|
28
|
+
|
|
29
|
+
# Initialize with default state type
|
|
30
|
+
def initialize(*args)
|
|
31
|
+
super
|
|
32
|
+
self.state_type ||= 'normal'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Validates the state node has required attributes.
|
|
36
|
+
#
|
|
37
|
+
# @return [Boolean] true if state node is valid
|
|
38
|
+
def valid?
|
|
39
|
+
!id.nil? && !id.empty? &&
|
|
40
|
+
!state_type.nil? && !state_type.empty?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Checks if this is a start state.
|
|
44
|
+
#
|
|
45
|
+
# @return [Boolean] true if start state
|
|
46
|
+
def start_state?
|
|
47
|
+
state_type == 'start'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Checks if this is an end state.
|
|
51
|
+
#
|
|
52
|
+
# @return [Boolean] true if end state
|
|
53
|
+
def end_state?
|
|
54
|
+
state_type == 'end'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Checks if this is a choice state.
|
|
58
|
+
#
|
|
59
|
+
# @return [Boolean] true if choice state
|
|
60
|
+
def choice_state?
|
|
61
|
+
state_type == 'choice'
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Checks if this is a fork state.
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean] true if fork state
|
|
67
|
+
def fork_state?
|
|
68
|
+
state_type == 'fork'
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Checks if this is a join state.
|
|
72
|
+
#
|
|
73
|
+
# @return [Boolean] true if join state
|
|
74
|
+
def join_state?
|
|
75
|
+
state_type == 'join'
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Checks if this is a composite state.
|
|
79
|
+
#
|
|
80
|
+
# @return [Boolean] true if has child states
|
|
81
|
+
def composite_state?
|
|
82
|
+
!children.nil? && !children.empty?
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Represents a state transition in a state diagram.
|
|
87
|
+
#
|
|
88
|
+
# A transition connects two states with optional trigger event
|
|
89
|
+
# and guard condition.
|
|
90
|
+
class StateTransition < Lutaml::Model::Serializable
|
|
91
|
+
# Source state identifier
|
|
92
|
+
attribute :from_id, :string
|
|
93
|
+
|
|
94
|
+
# Target state identifier
|
|
95
|
+
attribute :to_id, :string
|
|
96
|
+
|
|
97
|
+
# Optional trigger event for the transition
|
|
98
|
+
attribute :trigger, :string
|
|
99
|
+
|
|
100
|
+
# Optional guard condition for the transition
|
|
101
|
+
attribute :guard_condition, :string
|
|
102
|
+
|
|
103
|
+
# Validates the transition has required attributes.
|
|
104
|
+
#
|
|
105
|
+
# @return [Boolean] true if transition is valid
|
|
106
|
+
def valid?
|
|
107
|
+
!from_id.nil? && !from_id.empty? &&
|
|
108
|
+
!to_id.nil? && !to_id.empty?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Returns the full transition label.
|
|
112
|
+
#
|
|
113
|
+
# @return [String] formatted label with trigger and guard
|
|
114
|
+
def label
|
|
115
|
+
parts = []
|
|
116
|
+
parts << trigger if trigger && !trigger.empty?
|
|
117
|
+
parts << "[#{guard_condition}]" if guard_condition && !guard_condition.empty?
|
|
118
|
+
parts.join(' ')
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# State diagram model.
|
|
123
|
+
#
|
|
124
|
+
# Represents a complete state machine diagram with states and
|
|
125
|
+
# transitions. State diagrams show the dynamic behavior of a
|
|
126
|
+
# system through states, events, and transitions.
|
|
127
|
+
#
|
|
128
|
+
# @example Creating a simple state diagram
|
|
129
|
+
# diagram = StateDiagram.new(direction: 'TB')
|
|
130
|
+
# diagram.states << StateNode.new(
|
|
131
|
+
# id: 'start',
|
|
132
|
+
# label: '[*]',
|
|
133
|
+
# state_type: 'start'
|
|
134
|
+
# )
|
|
135
|
+
# diagram.states << StateNode.new(
|
|
136
|
+
# id: 'idle',
|
|
137
|
+
# label: 'Idle',
|
|
138
|
+
# state_type: 'normal'
|
|
139
|
+
# )
|
|
140
|
+
# diagram.transitions << StateTransition.new(
|
|
141
|
+
# from_id: 'start',
|
|
142
|
+
# to_id: 'idle'
|
|
143
|
+
# )
|
|
144
|
+
class StateDiagram < Base
|
|
145
|
+
# Collection of states in the diagram
|
|
146
|
+
attribute :states, StateNode, collection: true, default: -> { [] }
|
|
147
|
+
|
|
148
|
+
# Collection of transitions between states
|
|
149
|
+
attribute :transitions, StateTransition, collection: true,
|
|
150
|
+
default: -> { [] }
|
|
151
|
+
|
|
152
|
+
# Returns the diagram type identifier.
|
|
153
|
+
#
|
|
154
|
+
# @return [Symbol] :state_diagram
|
|
155
|
+
def diagram_type
|
|
156
|
+
:state_diagram
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Validates the state diagram structure.
|
|
160
|
+
#
|
|
161
|
+
# A state diagram is valid if:
|
|
162
|
+
# - It has at least one state
|
|
163
|
+
# - All states are valid
|
|
164
|
+
# - All transitions are valid
|
|
165
|
+
# - All transition references point to existing states
|
|
166
|
+
#
|
|
167
|
+
# @return [Boolean] true if state diagram is valid
|
|
168
|
+
def valid?
|
|
169
|
+
return false if states.nil? || states.empty?
|
|
170
|
+
return false unless states.all?(&:valid?)
|
|
171
|
+
return false unless transitions.nil? ||
|
|
172
|
+
transitions.all?(&:valid?)
|
|
173
|
+
|
|
174
|
+
# Validate transition references
|
|
175
|
+
state_ids = states.map(&:id)
|
|
176
|
+
transitions&.each do |transition|
|
|
177
|
+
return false unless state_ids.include?(transition.from_id)
|
|
178
|
+
return false unless state_ids.include?(transition.to_id)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
true
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Finds a state by its identifier.
|
|
185
|
+
#
|
|
186
|
+
# @param id [String] the state identifier to find
|
|
187
|
+
# @return [StateNode, nil] the state or nil if not found
|
|
188
|
+
def find_state(id)
|
|
189
|
+
states.find { |s| s.id == id }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Finds all transitions originating from a specific state.
|
|
193
|
+
#
|
|
194
|
+
# @param state_id [String] the source state identifier
|
|
195
|
+
# @return [Array<StateTransition>] transitions from the state
|
|
196
|
+
def transitions_from(state_id)
|
|
197
|
+
transitions.select { |t| t.from_id == state_id }
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Finds all transitions targeting a specific state.
|
|
201
|
+
#
|
|
202
|
+
# @param state_id [String] the target state identifier
|
|
203
|
+
# @return [Array<StateTransition>] transitions to the state
|
|
204
|
+
def transitions_to(state_id)
|
|
205
|
+
transitions.select { |t| t.to_id == state_id }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Finds the start state.
|
|
209
|
+
#
|
|
210
|
+
# @return [StateNode, nil] the start state or nil if not found
|
|
211
|
+
def start_state
|
|
212
|
+
states.find(&:start_state?)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Finds all end states.
|
|
216
|
+
#
|
|
217
|
+
# @return [Array<StateNode>] end states
|
|
218
|
+
def end_states
|
|
219
|
+
states.select(&:end_state?)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Finds all composite states.
|
|
223
|
+
#
|
|
224
|
+
# @return [Array<StateNode>] composite states
|
|
225
|
+
def composite_states
|
|
226
|
+
states.select(&:composite_state?)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Finds all choice states.
|
|
230
|
+
#
|
|
231
|
+
# @return [Array<StateNode>] choice states
|
|
232
|
+
def choice_states
|
|
233
|
+
states.select(&:choice_state?)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
require_relative "base"
|
|
5
|
+
|
|
6
|
+
module Sirena
|
|
7
|
+
module Diagram
|
|
8
|
+
# Represents an event in a timeline.
|
|
9
|
+
#
|
|
10
|
+
# An event represents a point in time with associated description(s).
|
|
11
|
+
# Events can have multiple descriptions (when multiple things happened
|
|
12
|
+
# at the same time).
|
|
13
|
+
class TimelineEvent < Lutaml::Model::Serializable
|
|
14
|
+
# The time/date/year for this event
|
|
15
|
+
attribute :time, :string
|
|
16
|
+
|
|
17
|
+
# Event descriptions (can be multiple for same time)
|
|
18
|
+
attribute :descriptions, :string, collection: true, default: -> { [] }
|
|
19
|
+
|
|
20
|
+
# Validates the event has required attributes.
|
|
21
|
+
#
|
|
22
|
+
# @return [Boolean] true if event is valid
|
|
23
|
+
def valid?
|
|
24
|
+
!time.nil? && !time.empty? && !descriptions.empty?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Get primary description (first one).
|
|
28
|
+
#
|
|
29
|
+
# @return [String, nil] the first description or nil
|
|
30
|
+
def primary_description
|
|
31
|
+
descriptions.first
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Check if event has multiple descriptions.
|
|
35
|
+
#
|
|
36
|
+
# @return [Boolean] true if more than one description
|
|
37
|
+
def multiple_descriptions?
|
|
38
|
+
descriptions.size > 1
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Represents a section/period in a timeline.
|
|
43
|
+
#
|
|
44
|
+
# A section groups related events together, typically representing
|
|
45
|
+
# a period, category, or theme.
|
|
46
|
+
class TimelineSection < Lutaml::Model::Serializable
|
|
47
|
+
# Name/title of this section
|
|
48
|
+
attribute :name, :string
|
|
49
|
+
|
|
50
|
+
# Events within this section
|
|
51
|
+
attribute :events, TimelineEvent, collection: true,
|
|
52
|
+
default: -> { [] }
|
|
53
|
+
|
|
54
|
+
# Task names (for sections without timestamps)
|
|
55
|
+
attribute :tasks, :string, collection: true, default: -> { [] }
|
|
56
|
+
|
|
57
|
+
def initialize(name = nil)
|
|
58
|
+
super()
|
|
59
|
+
@name = name
|
|
60
|
+
@events = []
|
|
61
|
+
@tasks = []
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Validates the section.
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean] true if section has a name
|
|
67
|
+
def valid?
|
|
68
|
+
!name.nil? && !name.empty?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Check if section has events.
|
|
72
|
+
#
|
|
73
|
+
# @return [Boolean] true if section has events
|
|
74
|
+
def has_events?
|
|
75
|
+
!events.empty?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check if section has tasks.
|
|
79
|
+
#
|
|
80
|
+
# @return [Boolean] true if section has tasks
|
|
81
|
+
def has_tasks?
|
|
82
|
+
!tasks.empty?
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Timeline diagram model.
|
|
87
|
+
#
|
|
88
|
+
# Represents a timeline showing chronological events, optionally
|
|
89
|
+
# grouped into sections. Timelines are useful for showing historical
|
|
90
|
+
# progression, project milestones, or any time-based sequence.
|
|
91
|
+
#
|
|
92
|
+
# @example Creating a simple timeline
|
|
93
|
+
# timeline = Timeline.new
|
|
94
|
+
# timeline.title = 'History of Computing'
|
|
95
|
+
#
|
|
96
|
+
# section = TimelineSection.new('Early Era')
|
|
97
|
+
# event = TimelineEvent.new
|
|
98
|
+
# event.time = '1936'
|
|
99
|
+
# event.descriptions << 'Turing machine'
|
|
100
|
+
# section.events << event
|
|
101
|
+
#
|
|
102
|
+
# timeline.sections << section
|
|
103
|
+
class Timeline < Base
|
|
104
|
+
# Collection of sections in the timeline
|
|
105
|
+
attribute :sections, TimelineSection, collection: true,
|
|
106
|
+
default: -> { [] }
|
|
107
|
+
|
|
108
|
+
# Collection of events not in any section
|
|
109
|
+
attribute :events, TimelineEvent, collection: true,
|
|
110
|
+
default: -> { [] }
|
|
111
|
+
|
|
112
|
+
# Accessibility title (for screen readers)
|
|
113
|
+
attribute :acc_title, :string
|
|
114
|
+
|
|
115
|
+
# Accessibility description (for screen readers)
|
|
116
|
+
attribute :acc_description, :string
|
|
117
|
+
|
|
118
|
+
def initialize
|
|
119
|
+
@sections = []
|
|
120
|
+
@events = []
|
|
121
|
+
super
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Returns the diagram type identifier.
|
|
125
|
+
#
|
|
126
|
+
# @return [Symbol] :timeline
|
|
127
|
+
def diagram_type
|
|
128
|
+
:timeline
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Validates the timeline structure.
|
|
132
|
+
#
|
|
133
|
+
# A timeline is always valid (can be empty for parsing tests).
|
|
134
|
+
# In practice, useful timelines would have events or sections.
|
|
135
|
+
#
|
|
136
|
+
# @return [Boolean] true if timeline is valid
|
|
137
|
+
def valid?
|
|
138
|
+
true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Check if timeline has any events.
|
|
142
|
+
#
|
|
143
|
+
# @return [Boolean] true if has events (in sections or standalone)
|
|
144
|
+
def has_events?
|
|
145
|
+
!events.empty? || sections.any?(&:has_events?)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Check if timeline has sections.
|
|
149
|
+
#
|
|
150
|
+
# @return [Boolean] true if has sections
|
|
151
|
+
def has_sections?
|
|
152
|
+
!sections.empty?
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Get all events across all sections and standalone.
|
|
156
|
+
#
|
|
157
|
+
# @return [Array<TimelineEvent>] all events
|
|
158
|
+
def all_events
|
|
159
|
+
section_events = sections.flat_map(&:events)
|
|
160
|
+
events + section_events
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Get all unique time values from events.
|
|
164
|
+
#
|
|
165
|
+
# @return [Array<String>] sorted array of unique times
|
|
166
|
+
def all_times
|
|
167
|
+
all_events.map(&:time).uniq.sort
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|