diff --git a/gallery/doc_examples.pdf b/gallery/doc_examples.pdf new file mode 100644 index 0000000..35074c3 Binary files /dev/null and b/gallery/doc_examples.pdf differ diff --git a/gallery/doc_examples.typ b/gallery/doc_examples.typ new file mode 100644 index 0000000..68def41 --- /dev/null +++ b/gallery/doc_examples.typ @@ -0,0 +1,675 @@ +#import "../src/lib.typ": from-plantuml + +#set page(width: auto, height: auto) + +#let examples = ( + ( + [Basic Examples], + ``` + @startuml + Alice -> Bob: Authentication Request + Bob --> Alice: Authentication Response + + Alice -> Bob: Another authentication Request + Alice <-- Bob: Another authentication Response + @enduml + ``` + ), + ( + [Declaring participant], + ``` + @startuml + participant Participant as Foo + actor Actor as Foo1 + boundary Boundary as Foo2 + control Control as Foo3 + entity Entity as Foo4 + database Database as Foo5 + collections Collections as Foo6 + queue Queue as Foo7 + Foo -> Foo1 : To actor + Foo -> Foo2 : To boundary + Foo -> Foo3 : To control + Foo -> Foo4 : To entity + Foo -> Foo5 : To database + Foo -> Foo6 : To collections + Foo -> Foo7: To queue + @enduml + ``` + ), + ( + [Declaring participant (2)], + ``` + @startuml + actor Bob #red + ' The only difference between actor + 'and participant is the drawing + participant Alice + participant "I have a really\nlong name" as L #99FF99 + /' You can also declare: + participant L as "I have a really\nlong name" #99FF99 + '/ + + Alice->Bob: Authentication Request + Bob->Alice: Authentication Response + Bob->L: Log transaction + @enduml + ``` + ), + ( + [Use non-letters in participants], + ``` + @startuml + Alice -> "Bob()" : Hello + "Bob()" -> "This is very\nlong" as Long + ' You can also declare: + ' "Bob()" -> Long as "This is very\nlong" + Long --> "Bob()" : ok + @enduml + ``` + ), + ( + [Message to Self], + ``` + @startuml + Alice -> Alice: This is a signal to self.\nIt also demonstrates\nmultiline \ntext + @enduml + ``` + ), + ( + [Message to Self (2)], + ``` + @startuml + Alice <- Alice: This is a signal to self.\nIt also demonstrates\nmultiline \ntext + @enduml + ``` + ), + ( + [Change arrow style], + ``` + @startuml + Bob ->x Alice + Bob -> Alice + Bob ->> Alice + Bob -\ Alice + Bob \\- Alice + Bob //-- Alice + + Bob ->o Alice + Bob o\\-- Alice + + Bob <-> Alice + Bob <->o Alice + @enduml + ``` + ), + ( + [Grouping message], + ``` + @startuml + Alice -> Bob: Authentication Request + + alt successful case + + Bob -> Alice: Authentication Accepted + + else some kind of failure + + Bob -> Alice: Authentication Failure + group My own label + Alice -> Log : Log attack start + loop 1000 times + Alice -> Bob: DNS Attack + end + Alice -> Log : Log attack end + end + + else Another type of failure + + Bob -> Alice: Please repeat + + end + @enduml + ``` + ), + ( + [Secondary group label], + ``` + @startuml + Alice -> Bob: Authentication Request + Bob -> Alice: Authentication Failure + group My own label [My own label 2] + Alice -> Log : Log attack start + loop 1000 times + Alice -> Bob: DNS Attack + end + Alice -> Log : Log attack end + end + @enduml + ``` + ), + ( + [Notes on messages], + ``` + @startuml + Alice->Bob : hello + note left: this is a first note + + Bob->Alice : ok + note right: this is another note + + Bob->Bob : I am thinking + note left + a note + can also be defined + on several lines + end note + @enduml + ``` + ), + ( + [Some other notes], + ``` + @startuml + participant Alice + participant Bob + note left of Alice #aqua + This is displayed + left of Alice. + end note + + note right of Alice: This is displayed right of Alice. + + note over Alice: This is displayed over Alice. + + note over Alice, Bob #FFAAAA: This is displayed\n over Bob and Alice. + + note over Bob, Alice + This is yet another + example of + a long note. + end note + @enduml + ``` + ), + ( + [Changing notes shape \[hnote, rnote\]], + ``` + @startuml + caller -> server : conReq + hnote over caller : idle + caller <- server : conConf + rnote over server + "r" as rectangle + "h" as hexagon + endrnote + rnote over server + this is + on several + lines + endrnote + hnote over caller + this is + on several + lines + endhnote + @enduml + ``` + ), + ( + [Note over all participants \[across\]], + ``` + @startuml + Alice->Bob:m1 + Bob->Charlie:m2 + note over Alice, Charlie: Old method for note over all part. with:\n ""note over //FirstPart, LastPart//"". + note across: New method with:\n""note across"" + Bob->Alice + hnote across:Note across all part. + @enduml + ``` + ), + ( + [Several notes aligned at the same level \[/\]], + ``` + @startuml + note over Alice : initial state of Alice + note over Bob : initial state of Bob + Bob -> Alice : hello + @enduml + ``` + ), + ( + [Several notes aligned at the same level \[/\] (2)], + ``` + @startuml + note over Alice : initial state of Alice + / note over Bob : initial state of Bob + Bob -> Alice : hello + @enduml + ``` + ), + ( + [Divider or separator], + ``` + @startuml + + == Initialization == + + Alice -> Bob: Authentication Request + Bob --> Alice: Authentication Response + + == Repetition == + + Alice -> Bob: Another authentication Request + Alice <-- Bob: another authentication Response + + @enduml + ``` + ), + ( + [Space], + ``` + @startuml + + Alice -> Bob: message 1 + Bob --> Alice: ok + ||| + Alice -> Bob: message 2 + Bob --> Alice: ok + ||45|| + Alice -> Bob: message 3 + Bob --> Alice: ok + + @enduml + ``` + ), + ( + [Lifeline Activation and Destruction], + ``` + @startuml + participant User + + User -> A: DoWork + activate A + + A -> B: << createRequest >> + activate B + + B -> C: DoWork + activate C + C --> B: WorkDone + destroy C + + B --> A: RequestCreated + deactivate B + + A -> User: Done + deactivate A + + @enduml + ``` + ), + ( + [Lifeline Activation and Destruction (2)], + ``` + @startuml + participant User + + User -> A: DoWork + activate A #FFBBBB + + A -> A: Internal call + activate A #DarkSalmon + + A -> B: << createRequest >> + activate B + + B --> A: RequestCreated + deactivate B + deactivate A + A -> User: Done + deactivate A + + @enduml + ``` + ), + /*( + [Lifeline Activation and Destruction (3)], + ``` + @startuml + 'autoactivate on + alice -> bob : hello + bob -> bob : self call + bill -> bob /'#005500'/ : hello from thread 2 + bob -> george ** : create + return done in thread 2 + return rc + bob -> george !! : delete + return success + + @enduml + ``` + ),*/ + ( + [Return], + ``` + @startuml + Bob -> Alice : hello + activate Alice + Alice -> Alice : some action + return bye + @enduml + ``` + ), + ( + [Participant creation], + ``` + @startuml + Bob -> Alice : hello + + create Other + Alice -> Other : new + + create /'control'/ String + Alice -> String + note right : You can also put notes! + + Alice --> Bob : ok + + @enduml + ``` + ), + ( + [Shortcut syntax for activation, deactivation, creation], + ``` + @startuml + alice -> bob ++ : hello + bob -> bob ++ : self call + bob -> bib ++ /' #005500'/ : hello + bob -> george ** : create + return done + return rc + bob -> george !! : delete + return success + @enduml + ``` + ), + ( + [Shortcut syntax for activation, deactivation, creation (2)], + ``` + @startuml + alice -> bob ++ : hello1 + bob -> charlie --++ : hello2 + charlie --> alice -- : ok + @enduml + ``` + ), + ( + [Shortcut syntax for activation, deactivation, creation (3)], + ``` + @startuml + alice -> bob ++ /'#gold'/: hello + bob -> alice --++ /'#gold'/: you too + alice -> bob --: step1 + alice -> bob : step2 + @enduml + @enduml + ``` + ), + ( + [Incoming and outgoing messages], + ``` + @startuml + [-> A: DoWork + + activate A + + A -> A: Internal call + activate A + + A ->] : << createRequest >> + + A<--] : RequestCreated + deactivate A + [<- A: Done + deactivate A + @enduml + ``` + ), + ( + [Incoming and outgoing messages (2)], + ``` + @startuml + participant Alice + participant Bob #lightblue + Alice -> Bob + Bob -> Carol + ... + [-> Bob + [o-> Bob + [o->o Bob + [x-> Bob + ... + [<- Bob + [x<- Bob + ... + Bob ->] + Bob ->o] + Bob o->o] + Bob ->x] + ... + Bob <-] + Bob x<-] + + @enduml + ``` + ), + ( + [Short arrows for incoming and outgoing messages], + ``` + @startuml + ?-> Alice : ""?->""\n**short** to actor1 + [-> Alice : ""[->""\n**from start** to actor1 + [-> Bob : ""[->""\n**from start** to actor2 + ?-> Bob : ""?->""\n**short** to actor2 + Alice ->] : ""->]""\nfrom actor1 **to end** + Alice ->? : ""->?""\n**short** from actor1 + Alice -> Bob : ""->"" \nfrom actor1 to actor2 + @enduml + ``` + ), + ( + [Normal arrow], + ``` + @startuml + participant Alice as a + participant Bob as b + a -> b : ""-> "" + a ->> b : ""->> "" + a -\ b : ""-\ "" + a -\\ b : ""-\\\\"" + a -/ b : ""-/ "" + a -// b : ""-// "" + a ->x b : ""->x "" + a x-> b : ""x-> "" + a o-> b : ""o-> "" + a ->o b : ""->o "" + a o->o b : ""o->o "" + a <-> b : ""<-> "" + a o<->o b : ""o<->o"" + a x<->x b : ""x<->x"" + a ->>o b : ""->>o "" + a -\o b : ""-\o "" + a -\\o b : ""-\\\\o"" + a -/o b : ""-/o "" + a -//o b : ""-//o "" + a x->o b : ""x->o "" + @enduml + ``` + ), + ( + [Itself arrow], + ``` + @startuml + participant Alice as a + participant Bob as b + a -> a : ""-> "" + a ->> a : ""->> "" + a -\ a : ""-\ "" + a -\\ a : ""-\\\\"" + a -/ a : ""-/ "" + a -// a : ""-// "" + a ->x a : ""->x "" + a x-> a : ""x-> "" + a o-> a : ""o-> "" + a ->o a : ""->o "" + a o->o a : ""o->o "" + a <-> a : ""<-> "" + a o<->o a : ""o<->o"" + a x<->x a : ""x<->x"" + a ->>o a : ""->>o "" + a -\o a : ""-\o "" + a -\\o a : ""-\\\\o"" + a -/o a : ""-/o "" + a -//o a : ""-//o "" + a x->o a : ""x->o "" + @enduml + ``` + ), + ( + [Incoming messages (with '|')], + ``` + @startuml + participant Alice as a + participant Bob as b + [-> b : ""[-> "" + [->> b : ""[->> "" + [-\ b : ""[-\ "" + [-\\ b : ""[-\\\\"" + [-/ b : ""[-/ "" + [-// b : ""[-// "" + [->x b : ""[->x "" + [x-> b : ""[x-> "" + [o-> b : ""[o-> "" + [->o b : ""[->o "" + [o->o b : ""[o->o "" + [<-> b : ""[<-> "" + [o<->o b : ""[o<->o"" + [x<->x b : ""[x<->x"" + [->>o b : ""[->>o "" + [-\o b : ""[-\o "" + [-\\o b : ""[-\\\\o"" + [-/o b : ""[-/o "" + [-//o b : ""[-//o "" + [x->o b : ""[x->o "" + @enduml + ``` + ), + ( + [Outgoing messages (with '|')], + ``` + @startuml + participant Alice as a + participant Bob as b + a ->] : ""->] "" + a ->>] : ""->>] "" + a -\] : ""-\] "" + a -\\] : ""-\\\\]"" + a -/] : ""-/] "" + a -//] : ""-//] "" + a ->x] : ""->x] "" + a x->] : ""x->] "" + a o->] : ""o->] "" + a ->o] : ""->o] "" + a o->o] : ""o->o] "" + a <->] : ""<->] "" + a o<->o] : ""o<->o]"" + a x<->x] : ""x<->x]"" + a ->>o] : ""->>o] "" + a -\o] : ""-\o] "" + a -\\o] : ""-\\\\o]"" + a -/o] : ""-/o] "" + a -//o] : ""-//o] "" + a x->o] : ""x->o] "" + @enduml + ``` + ), + ( + [Short incoming (with '?')], + ``` + @startuml + participant Alice as a + participant Bob as b + a -> b : //Long long label// + ?-> b : ""?-> "" + ?->> b : ""?->> "" + ?-\ b : ""?-\ "" + ?-\\ b : ""?-\\\\"" + ?-/ b : ""?-/ "" + ?-// b : ""?-// "" + ?->x b : ""?->x "" + ?x-> b : ""?x-> "" + ?o-> b : ""?o-> "" + ?->o b : ""?->o "" + ?o->o b : ""?o->o "" + ?<-> b : ""?<-> "" + ?o<->o b : ""?o<->o"" + ?x<->x b : ""?x<->x"" + ?->>o b : ""?->>o "" + ?-\o b : ""?-\o "" + ?-\\o b : ""?-\\\\o "" + ?-/o b : ""?-/o "" + ?-//o b : ""?-//o "" + ?x->o b : ""?x->o "" + @enduml + ``` + ), + ( + [Short outgoing (with '?')], + ``` + @startuml + participant Alice as a + participant Bob as b + a -> b : //Long long label// + a ->? : ""->? "" + a ->>? : ""->>? "" + a -\? : ""-\? "" + a -\\? : ""-\\\\?"" + a -/? : ""-/? "" + a -//? : ""-//? "" + a ->x? : ""->x? "" + a x->? : ""x->? "" + a o->? : ""o->? "" + a ->o? : ""->o? "" + a o->o? : ""o->o? "" + a <->? : ""<->? "" + a o<->o? : ""o<->o?"" + a x<->x? : ""x<->x?"" + a ->>o? : ""->>o? "" + a -\o? : ""-\o? "" + a -\\o? : ""-\\\\o?"" + a -/o? : ""-/o? "" + a -//o? : ""-//o? "" + a x->o? : ""x->o? "" + @enduml + ``` + ) +) + +#{ + for (title, uml) in examples { + heading(title) + box( + stroke: gray, + inset: 1em, + stack( + dir: ltr, + spacing: 1em, + raw(uml.text, block: true, lang: "plantuml"), + from-plantuml(uml) + ) + ) + pagebreak(weak: true) + } +} \ No newline at end of file diff --git a/gallery/plantuml_test.pdf b/gallery/plantuml_test.pdf new file mode 100644 index 0000000..9c7f014 Binary files /dev/null and b/gallery/plantuml_test.pdf differ diff --git a/gallery/plantuml_test.typ b/gallery/plantuml_test.typ new file mode 100644 index 0000000..daa7dea --- /dev/null +++ b/gallery/plantuml_test.typ @@ -0,0 +1,271 @@ +#import "../src/lib.typ": from-plantuml + +#set page(width: auto, height: auto) + +/* +#from-plantuml(``` +@startuml + +actor User as usr +participant can_message as can +control kartculator as kc +queue XF as xf +entity Drive as drive +entity Steering as steering + +usr -\ xf : set message "move" +xf -> can : new value on joystick + +== If X axis change value == +can -> kc : calculate new position +kc -> can : build message +can -> steering : set new position + +== If Y axis change value == +can -> kc : calculate new torque +kc -> can : build message +can -> xf : set message "torque" +xf -> drive : set new torque + +@enduml +```) + +#pagebreak(weak: true) + +#from-plantuml(``` +@startuml + +actor CAN_BUS as bus +participant interrupt as ISR +queue XF as xf +participant ecan as ecan +participant canInterface as can +control canMessageController as msg + + +bus -\\ ISR ++ : can message +ISR -> can : newMsg +can -> ecan : read +ecan --> can : message +can -> xf : POST XF +destroy ISR + group TICK XF +xf o-> can : receiveCan() +can -> msg : processIncoming() +msg -> can : create message +can -> xf : POST XF + end + group TICK XF +xf o-> can : sendCan() +can -> ecan : write +ecan -\\ bus : can message + end + +@enduml +```) + +#pagebreak(weak: true) +*/ + +/* +#from-plantuml(``` +@startuml + +participant "Behavior::StateMachine" as sm +participant Dispatcher as d +participant TimeoutManager as tm +entity "Event::Timeout" as t +queue "TimeoutManager::timeouts_" as timeouts + +autoactivate off +||| +||| +== Schedule timeout == +||| +sm -> sm++ : scheduleTimeout +sm -> d ++: getDispatcher +d --> sm --: dispatcher +sm -> d --++ : scheduleTimeout +d -> tm ++: getTimeoutManager +tm --> d --: timeoutManager +d -> tm --++ : scheduleTimeout +tm -> t ** : new +t --> tm +tm -> timeouts --++: insert + +||| +||| +== Decrement timeout (and dispatch) == +||| +loop every tickInterval +?->> tm ++: tick +tm -> timeouts : getFront +timeouts -> t ++ +t --> timeouts +timeouts --> tm : timeout +tm -> t --: decrement +end +||| +note left t +When timeout is 0, +dispatch event +end note +t -> timeouts : pop +deactivate timeouts +t ->? --: pushEvent + +||| +||| +== Unschedule timeout == +||| +sm -> sm++ : unscheduleTimeout +sm -> d ++: getDispatcher +d --> sm --: dispatcher +sm -> d --++ : unscheduleTimeout +d -> tm ++: getTimeoutManager +tm --> d --: timeoutManager +d -> tm --++ : unscheduleTimeout +tm -> timeouts --: erase +timeouts -> t !! + +@enduml +```) +*/ + +#pagebreak(weak: true) + +#from-plantuml(``` +@startuml + +participant Behavior as b +participant Dispatcher as d +entity Event as e +participant EventQueue as eq +queue "EventQueue::queue_" as q + +== Create an Event == +||| +?->> b ++ : GEN +b -> e ** : new +b -> b --++ : pushEvent +e -> b : getBehavior +b --> e ++: setBehavior +e --> b -- +b -> d ++ : getDispatcher +d --> b +b -> d -- : pushEvent +d ->? -- : push + + +||| +||| +== Push Event == +||| +?->> d ++: pushEvent +d -> eq--++: push +eq -> q ++ +q --> eq +eq -> q -- : pushEndQueue + +||| +||| +== Dispatch == +||| +?->> d ++: executeOnce +d -> q : getFront +q -> e ++ +e --> q +q --> d : event +d -> q : pop +deactivate q +d -> d --++ : dispatchEvent +d -> b ++ : getBehavior +b --> d +d -> b -- : process +b -> b--: processEvent + +destroy e + +@enduml +```) + +#pagebreak(weak: true) + +#from-plantuml(``` +@startuml +'https://plantuml.com/sequence-diagram +actor User as usr +participant "Pb L" as pbL +participant "Pb R" as pbR +participant "LED L" as ledL +participant "LED R" as ledR + + +== Single click == + +group Single click left +usr -\ pbL ++: pressButton +usr -\ pbL : releaseButton +pbL -> ledL --++ : blink +usr -\ pbL ++: pressButton +usr -\ pbL : releaseButton +pbL -> ledL -- : endBlink +deactivate ledL +end + +group Single click right +usr -\ pbR ++: pressButton +usr -\ pbR : releaseButton +pbR -> ledR --++ : blink +usr -\ pbR ++: pressButton +usr -\ pbR : releaseButton +pbR -> ledR -- : endBlink +deactivate ledR +end + +== Double click == + +group Double click left +usr -\ pbL ++: pressButton +usr -\ pbL : releaseButton +usr -\ pbL : pressButton +pbL -> ledL --++ : blink +note right ledL: blink 3x +ledL ->x ledL -- : finished +end + +group Double click right +usr -\ pbR ++: pressButton +usr -\ pbR : releaseButton +usr -\ pbR : pressButton +pbR -> ledR --++ : blink +note right ledR: blink 3x +ledR ->x ledR -- : finished +end + +== Long click == + +group Long click left +usr -\ pbL ++: pressButton +pbL -> ledR--: blink +activate ledL +activate ledR +usr -\ pbL ++: pressButton +pbL -> ledR -- : endBlink +deactivate ledL +deactivate ledR +end + +group Long click right +usr -\ pbR ++: pressButton +pbR -> ledR--: blink +activate ledL +activate ledR +usr -\ pbL ++: pressButton +pbL -> ledR -- : endBlink +deactivate ledL +deactivate ledR +end + +@enduml +```) \ No newline at end of file diff --git a/src/diagram.typ b/src/diagram.typ index 7644d49..bf223bb 100644 --- a/src/diagram.typ +++ b/src/diagram.typ @@ -243,8 +243,4 @@ set text(font: "Source Sans 3") let canvas = render(participants, elmts) fit-canvas(canvas, width: width) -} - -#let from-plantuml(code) = { - let code = code.text } \ No newline at end of file diff --git a/src/lib.typ b/src/lib.typ index a2568fb..da3f79d 100644 --- a/src/lib.typ +++ b/src/lib.typ @@ -1,5 +1,6 @@ #let version = version(0, 1, 1) -#import "diagram.typ": diagram, from-plantuml, _gap, _evt, _col +#import "diagram.typ": diagram, _gap, _evt, _col +#import "parser.typ": from-plantuml #import "sequence.typ": _seq, _ret #import "group.typ": _grp, _loop, _alt, _opt, _break diff --git a/src/parser.typ b/src/parser.typ new file mode 100644 index 0000000..0a9b8b1 --- /dev/null +++ b/src/parser.typ @@ -0,0 +1,1021 @@ +#import "diagram.typ": diagram, _par, _evt, _gap +#import "participant.typ": SHAPES +#import "separator.typ": _sep +#import "sequence.typ": _seq, _ret +#import "group.typ": _grp, _alt +#import "note.typ": _note, SIDES + +#let COLORS = ( + aliceblue: rgb("#f0f8ff"), + antiquewhite: rgb("#faebd7"), + aqua: rgb("#00ffff"), + aquamarine: rgb("#7fffd4"), + azure: rgb("#f0ffff"), + beige: rgb("#f5f5dc"), + bisque: rgb("#ffe4c4"), + black: rgb("#000000"), + blanchedalmond: rgb("#ffebcd"), + blue: rgb("#0000ff"), + blueviolet: rgb("#8a2be2"), + brown: rgb("#a52a2a"), + burlywood: rgb("#deb887"), + cadetblue: rgb("#5f9ea0"), + chartreuse: rgb("#7fff00"), + chocolate: rgb("#d2691e"), + coral: rgb("#ff7f50"), + cornflowerblue: rgb("#6495ed"), + cornsilk: rgb("#fff8dc"), + crimson: rgb("#dc143c"), + cyan: rgb("#00ffff"), + darkblue: rgb("#00008b"), + darkcyan: rgb("#008b8b"), + darkgoldenrod: rgb("#b8860b"), + darkgray: rgb("#a9a9a9"), + darkgreen: rgb("#006400"), + darkgrey: rgb("#a9a9a9"), + darkkhaki: rgb("#bdb76b"), + darkmagenta: rgb("#8b008b"), + darkolivegreen: rgb("#556b2f"), + darkorange: rgb("#ff8c00"), + darkorchid: rgb("#9932cc"), + darkred: rgb("#8b0000"), + darksalmon: rgb("#e9967a"), + darkseagreen: rgb("#8fbc8f"), + darkslateblue: rgb("#483d8b"), + darkslategray: rgb("#2f4f4f"), + darkslategrey: rgb("#2f4f4f"), + darkturquoise: rgb("#00ced1"), + darkviolet: rgb("#9400d3"), + deeppink: rgb("#ff1493"), + deepskyblue: rgb("#00bfff"), + dimgray: rgb("#696969"), + dimgrey: rgb("#696969"), + dodgerblue: rgb("#1e90ff"), + firebrick: rgb("#b22222"), + floralwhite: rgb("#fffaf0"), + forestgreen: rgb("#228b22"), + fuchsia: rgb("#ff00ff"), + gainsboro: rgb("#dcdcdc"), + ghostwhite: rgb("#f8f8ff"), + gold: rgb("#ffd700"), + goldenrod: rgb("#daa520"), + gray: rgb("#808080"), + green: rgb("#008000"), + greenyellow: rgb("#adff2f"), + grey: rgb("#808080"), + honeydew: rgb("#f0fff0"), + hotpink: rgb("#ff69b4"), + indianred: rgb("#cd5c5c"), + indigo: rgb("#4b0082"), + ivory: rgb("#fffff0"), + khaki: rgb("#f0e68c"), + lavender: rgb("#e6e6fa"), + lavenderblush: rgb("#fff0f5"), + lawngreen: rgb("#7cfc00"), + lemonchiffon: rgb("#fffacd"), + lightblue: rgb("#add8e6"), + lightcoral: rgb("#f08080"), + lightcyan: rgb("#e0ffff"), + lightgoldenrodyellow: rgb("#fafad2"), + lightgray: rgb("#d3d3d3"), + lightgreen: rgb("#90ee90"), + lightgrey: rgb("#d3d3d3"), + lightpink: rgb("#ffb6c1"), + lightsalmon: rgb("#ffa07a"), + lightseagreen: rgb("#20b2aa"), + lightskyblue: rgb("#87cefa"), + lightslategray: rgb("#778899"), + lightslategrey: rgb("#778899"), + lightsteelblue: rgb("#b0c4de"), + lightyellow: rgb("#ffffe0"), + lime: rgb("#00ff00"), + limegreen: rgb("#32cd32"), + linen: rgb("#faf0e6"), + magenta: rgb("#ff00ff"), + maroon: rgb("#800000"), + mediumaquamarine: rgb("#66cdaa"), + mediumblue: rgb("#0000cd"), + mediumorchid: rgb("#ba55d3"), + mediumpurple: rgb("#9370db"), + mediumseagreen: rgb("#3cb371"), + mediumslateblue: rgb("#7b68ee"), + mediumspringgreen: rgb("#00fa9a"), + mediumturquoise: rgb("#48d1cc"), + mediumvioletred: rgb("#c71585"), + midnightblue: rgb("#191970"), + mintcream: rgb("#f5fffa"), + mistyrose: rgb("#ffe4e1"), + moccasin: rgb("#ffe4b5"), + navajowhite: rgb("#ffdead"), + navy: rgb("#000080"), + oldlace: rgb("#fdf5e6"), + olive: rgb("#808000"), + olivedrab: rgb("#6b8e23"), + orange: rgb("#ffa500"), + orangered: rgb("#ff4500"), + orchid: rgb("#da70d6"), + palegoldenrod: rgb("#eee8aa"), + palegreen: rgb("#98fb98"), + paleturquoise: rgb("#afeeee"), + palevioletred: rgb("#db7093"), + papayawhip: rgb("#ffefd5"), + peachpuff: rgb("#ffdab9"), + peru: rgb("#cd853f"), + pink: rgb("#ffc0cb"), + plum: rgb("#dda0dd"), + powderblue: rgb("#b0e0e6"), + purple: rgb("#800080"), + rebeccapurple: rgb("#663399"), + red: rgb("#ff0000"), + rosybrown: rgb("#bc8f8f"), + royalblue: rgb("#4169e1"), + saddlebrown: rgb("#8b4513"), + salmon: rgb("#fa8072"), + sandybrown: rgb("#f4a460"), + seagreen: rgb("#2e8b57"), + seashell: rgb("#fff5ee"), + sienna: rgb("#a0522d"), + silver: rgb("#c0c0c0"), + skyblue: rgb("#87ceeb"), + slateblue: rgb("#6a5acd"), + slategray: rgb("#708090"), + slategrey: rgb("#708090"), + snow: rgb("#fffafa"), + springgreen: rgb("#00ff7f"), + steelblue: rgb("#4682b4"), + tan: rgb("#d2b48c"), + teal: rgb("#008080"), + thistle: rgb("#d8bfd8"), + tomato: rgb("#ff6347"), + turquoise: rgb("#40e0d0"), + violet: rgb("#ee82ee"), + wheat: rgb("#f5deb3"), + white: rgb("#ffffff"), + whitesmoke: rgb("#f5f5f5"), + yellow: rgb("#ffff00"), + yellowgreen: rgb("#9acd32"), +) + +#let CREOLE = ( + "bold": ( + open: "**", + close: "**", + func: strong + ), + "italic": ( + open: "//", + close: "//", + func: emph + ), + "mono": ( + open: "\"\"", + close: "\"\"", + func: it => text(it, font: "DejaVu Sans Mono") + ), + "stricken": ( + open: "--", + close: "--", + func: strike + ), + "under": ( + open: "__", + close: "__", + func: underline + ), + "wavy": ( + open: "~~", + close: "~~", + func: it => panic("Wavy underline is not supported (see https://github.com/typst/typst/issues/2835)") + ) +) + +#let parse-creole(txt) = { + txt = txt.replace("\\n", "\n") + .replace("<<", sym.quote.angle.double.l) + .replace(">>", sym.quote.angle.double.r) + + let part-stack = () + let style-stack = () + let cur-style = none + + let steps = () + let tmp = "" + for char in txt { + cur-style = if style-stack.len() == 0 { + none + } else { + style-stack.last() + } + + tmp += char + + for (name, style) in CREOLE.pairs() { + if tmp.ends-with(style.close) { + //if cur-style == name { + if name in style-stack { + tmp = tmp.slice(0, tmp.len() - style.close.len()) + + let to-append = "" + let rev-style-stack = style-stack.rev() + let i = rev-style-stack.position(s => s == name) + for _ in range(i) { + let style = style-stack.pop() + tmp = CREOLE.at(style).open + tmp + } + style-stack.pop() + + if tmp.len() == 0 { + if part-stack.len() != 0 { + to-append += (style.func)(part-stack.pop()) + } + } else { + to-append += (style.func)(tmp) + } + tmp = "" + part-stack.last() = part-stack.last() + to-append + + break + } + } + if tmp.ends-with(style.open) { + tmp = tmp.slice(0, tmp.len() - style.open.len()) + part-stack.push(tmp) + tmp = "" + style-stack.push(name) + break + } + } + steps.push((part-stack, tmp, style-stack)) + } + if part-stack.len() == 0 { + part-stack.push(tmp) + } else { + part-stack.last() += tmp + } + + if style-stack.len() != 0 { + let dbg-steps = steps + let dbg-txt = txt + panic("Unclosed '" + style-stack.last() + "' style in string '" + txt + "'") + } + + return part-stack.join() +} + +#let parse-color(value) = { + if value.starts-with("#") { + value = value.slice(1) + } + let m = value.match(regex("^(.+)([|/\\\-])(.+)$")) + if m != none { + let (col1, angle, col2) = m.captures + col1 = parse-color(col1) + col2 = parse-color(col2) + angle = ( + "|": 0deg, + "/": 45deg, + "-": 90deg, + "\\": 135deg + ).at(angle) + return gradient.linear(col1, col2, angle: angle) + } + + if lower(value) in COLORS { + return COLORS.at(lower(value)) + } + + return rgb(value) +} + +#let is-boundary-stmt(line) = { + return line.starts-with("@startuml") or line.starts-with("@enduml") +} + +#let is-comment-stmt(line) = { + return line.starts-with("'") +} + +#let is-ret-stmt(line) = { + return line.starts-with("return") +} + +#let parse-ret-stmt(line) = { + let m = line.match(regex("^return(\s+(.*?))?$")) + let comment = m.captures.last() + return _ret(comment: comment) +} + +#let is-par-stmt(line) = { + for shape in SHAPES { + if shape == "custom" { + continue + } + if line.starts-with(shape) { + return true + } + } + return false +} + +#let parse-par-stmt(line) = { + let arg-stack = line.split(" ") + let arg-stack2 = () + let in-string = false + for arg in arg-stack { + if in-string { + if arg.ends-with("\"") { + arg = arg.slice(0, arg.len()-1) + in-string = false + } + arg-stack2.last() += " " + arg + } else { + if arg.starts-with("\"") { + arg = arg.slice(1) + if arg.ends-with("\"") { + arg = arg.slice(0, arg.len()-1) + } else { + in-string = true + } + } + if not in-string and arg.trim().len() == 0 { + continue + } + arg-stack2.push(arg) + } + } + arg-stack = arg-stack2.rev() + + let shape = arg-stack.pop() + let display-name = auto + let name = arg-stack.pop() + let color = auto + + while arg-stack.len() != 0 { + let arg = arg-stack.pop() + if arg == "as" { + display-name = name + name = arg-stack.pop() + } else if arg.starts-with("#") { + color = parse-color(arg) + } + } + + if display-name != auto { + display-name = parse-creole(display-name) + } + + return _par( + name, + display-name: display-name, + shape: shape, + color: color + ) +} + +#let is-gap-stmt(line) = { + return line == "|||" or line.match(regex("\|\|\d+\|\|")) != none +} + +#let parse-gap-stmt(line) = { + if line == "|||" { + return _gap() + } + let size = int(line.slice(2, line.len() - 2)) + return _gap(size: size) +} + +#let is-sep-stmt(line) = { + return line.starts-with("==") and line.ends-with("==") +} + +#let parse-sep-stmt(line) = { + let name = line.slice(2, -2).trim() + return _sep(name) +} + +#let is-evt-stmt(line) = { + return ( + line.starts-with("activate") or + line.starts-with("deactivate") or + line.starts-with("create") or + line.starts-with("destroy") + ) +} + +#let parse-evt-stmt(line) = { + let (evt, par, col) = line.match( + regex(```(?x) + ^ + (?.+?) + \s+ + (?.+?) + (?\s+.*)? + $```.text + ) + ).captures + if col != none { + col = parse-color(col.trim()) + } + + // TODO: lifeline style (i.e. use col) + + let event = ( + "activate": "enable", + "deactivate": "disable", + "create": "create", + "destroy": "destroy" + ).at(evt) + return _evt(par, event) +} + +#let is-grp-stmt(line) = { + return ( + line.starts-with("group") or + line.starts-with("loop") or + line.starts-with("alt") or + line.starts-with("else") + ) +} + +#let parse-grp-stmt(line) = { + let words = line.split(" ") + let group-type = words.first() + let rest = words.slice(1).join(" ") + let is-group = group-type == "group" + let name = group-type + let desc = rest + if is-group { + let m = rest.match( + regex( + ```(?x) + ^ + (.*?) + (\[.*\])? + $ + ```.text + ) + ) + name = m.captures.first() + desc = m.captures.last() + if desc != none { + desc = desc.trim(regex("[\[\]]*")) + } + } + return ( + name: name, + desc: desc, + type: if is-group { + "default" + } else { + group-type + } + ) +} + +#let is-simple-note-stmt(line) = { + return line.starts-with(regex("/?\s*[hr]?note")) and line.contains(":") +} + +#let parse-note-data(stmt) = { + let aligned = stmt.starts-with("/") + stmt = stmt.trim(regex("/?\s*")) + let data-parts = stmt.split(regex("\s+")) + let note-type = data-parts.at(0) + let shape = ( + "note": "default", + "hnote": "hex", + "rnote": "rect" + ).at(note-type) + let side = data-parts.at(1) + if side not in SIDES { + panic("Invalid note side '" + side + "'") + } + + let color = auto + if data-parts.last().starts-with("#") { + color = parse-color(data-parts.pop()) + } + + let pos = none + if side == "left" or side == "right" { + if data-parts.len() >= 3 { + if data-parts.at(2) == "of" and data-parts.len() >= 4 { + pos = data-parts.at(3) + } else { + pos = data-parts.at(2) + } + } + } else if side == "over" { + pos = data-parts.slice(2) + .join(" ") + .split(",") + .map(p => p.trim()) + if pos.len() == 1 { + pos = pos.first() + } + } + + return ( + side: side, + shape: shape, + color: color, + pos: pos, + aligned: aligned + ) +} +#let parse-simple-note-stmt(line) = { + let (data, ..note) = line.split(":") + note = note.join(":") + note = parse-creole(note.trim()) + + let note-data = parse-note-data(data) + + return _note( + note-data.side, + note, + color: note-data.color, + pos: note-data.pos, + shape: note-data.shape, + aligned: note-data.aligned + ) +} + +#let is-multiline-note-stmt(line) = { + return line.starts-with(regex("/?\s*[hr]?note")) and not line.contains(":") +} + +#let parse-multiline-note-stmt(line) = { + let note-data = parse-note-data(line) + note-data.insert("lines", ()) + + return note-data +} + +#let parse-seq-tips(arrow) = { + let m = arrow.trim().match( + regex(```(?x) + ^ + ([ox])? + (<|<<|\\|\\\\|/|//)? + (-{1,2}) + (>|>>|\\|\\\\|/|//)? + ([ox])? + $ + ```.text + ) + ) + + if m == none { + //panic(arrow) + return none + } + + let flip = false + let (pre-start, start, mid, end, post-end) = m.captures + if start != none and end == none { + flip = true + } + + if start != none { + start = ( + "<": ">", + "\\": "/", + "\\\\": "//", + "/": "\\", + "//": "\\\\", + ).at(start, default: start) + } + + let start-tip = (pre-start, start).filter(t => t != none) + let end-tip = (post-end, end).filter(t => t != none) + if start-tip.len() == 1 { + start-tip = start-tip.first() + } else if start-tip.len() == 0 { + start-tip = "" + } + if end-tip.len() == 1 { + end-tip = end-tip.first() + } else if end-tip.len() == 0 { + end-tip = "" + } + + if pre-start == "x" { + start-tip = "x" + } + if post-end == "x" { + end-tip = "x" + } + + return (start-tip, mid == "--", end-tip, flip) +} + +#let parse-seq-stmt(line) = { + let p1 = "" + let arrow = "" + let p2 = "" + let mods = "" + let comment = "" + let color = "" + let state = "idle" + let in-string = false + let steps = () + let post-par = "" + let p1-as = "" + let p2-as = "" + + for char in line.clusters() { + steps.push((state, char, in-string)) + if steps.len() > 12 { + let a = steps + } + + // Idle: go until first non whitespace character + // if first char is " -> in-string + if state == "idle" { + if char.match(regex("\s")) == none { + if char == "\"" { + in-string = true + } else { + p1 += char + } + + if p1 in ("[", "?") { + state = "post-p1" + } else { + state = "p1" + } + } + + // First participant: if in string, go until end of string, + // otherwise go until arrow character + } else if state == "p1" { + if in-string { + if char == "\"" { + in-string = false + state = "post-p1" + } else { + p1 += char + } + } else { + if char.match(regex(`-|<|>|\\|\\\\|/|//`.text)) != none { + arrow += char + state = "arrow" + } else if char.match(regex("\s")) != none { + state = "post-p1" + } else { + p1 += char + if p1 in ("[", "?") { + state = "post-p1" + } + } + } + + } else if state == "post-p1" { + if char.match(regex(`o|x|-|<|>|\\|\\\\|/|//`.text)) != none { + arrow += char + state = "arrow" + + } else if char.match(regex("\s")) == none { + post-par += char + if post-par.match(regex("as\s")) != none { + state = "p1-as" + post-par = "" + + } else if post-par.match("^(a(s(\s?))?)?$") == none { + panic("Unexpected characters '" + post-par + "' after first participant") + } + } + + } else if state == "p1-as" { + if char.match(regex("\s|o|x|-|<|>|\\|\\\\|/|//")) != none { + arrow += char + state = "arrow" + } else { + p1-as += char + } + + // Arrow: + } else if state == "arrow" { + if char.match(regex(`o|x|-|<|>|\\|\\\\|/|//`.text)) != none { + arrow += char + } else { + if char == "\"" { + in-string = true + state = "p2" + } else if char == " " { + state = "pre-p2" + } else { + p2 += char + state = "p2" + } + } + + } else if state == "pre-p2" { + if char.match(regex("\s")) == none { + if char == "\"" { + in-string = true + } else { + p2 += char + } + state = "p2" + } + + + // Second participant: if in string go until end of string + } else if state == "p2" { + if in-string { + if char == "\"" { + in-string = false + state = "post-p2" + } else { + p2 += char + } + } else { + if char.match(regex("\s")) != none { + state = "post-p2" + } else if char in "+-*!" { + state = "mods" + mods += char + } else if char == ":" { + state = "comment" + } else { + p2 += char + } + } + + } else if state == "post-p2" { + if post-par.len() == 0 { + if char.match(regex("\s")) != none { + continue + } + if char in "+-*!" { + state = "mods" + mods += char + } else if char == ":" { + state = "comment" + } else { + post-par += char + } + + } else { + post-par += char + if post-par.match(regex("^\s*as\s$")) != none { + state = "p2-as" + post-par = "" + + } else if post-par.match(regex("^\s*(a(s(\s?))?)?$")) == none { + let a = steps + panic("Unexpected characters '" + post-par + "' after second participant") + } + } + + } else if state == "p2-as" { + if char.match(regex("\s|\+|-|\*|!")) != none { + mods += char + state = "mods" + } else if char == ":" { + state = "comment" + } else { + p2-as += char + } + + } else if state == "mods" { + if char.match(regex("\s|\+|-|\*|!")) != none { + mods += char + } else { + if char == ":" { + state = "comment" + } else if char.match(regex("\s")) != none { + state = "post-mods" + } else { + panic("Unexpected character '" + char + "' after mods") + } + } + + } else if state == "post-mods" { + if char == ":" { + state = "comment" + } + + } else if state == "comment" { + comment += char + } + } + p1 = parse-creole(p1) + p2 = parse-creole(p2) + comment = parse-creole(comment.trim()) + p1-as = p1-as.trim() + p2-as = p2-as.trim() + let elmts = () + + if p1-as != "" { + elmts += _par(p1-as, display-name: p1) + p1 = p1-as + } + if p2-as != "" { + elmts += _par(p2-as, display-name: p2) + p2 = p2-as + } + + /*let m = line.match( + regex(```(?x) + ^ + (?[a-zA-Z0-9_]+) + \s* + (?[^o].*?) + \s* + (?[a-zA-Z0-9_]+) + \s* + (?[+\-*! ]{2,})? + \s* + (:\s*(?.*))? + ```.text + ) + )*/ + + /* + let m = line.match( + regex(```(?x) + ^ + (?[a-zA-Z0-9_\[?]+) + \s* + (?[^o].*?) + \s* + (?[a-zA-Z0-9_\]?]+) + \s* + (?[+\-*! ]*?) + \s* + (?\#.+)? + \s* + (?::\s*(?.*))? + $ + ```.text + ) + ) + + if m == none { + panic(line) + } + let (p1, arrow, p2, mods, color, comment) = m.captures + */ + + /* + let p1 = line.find(regex("^[a-zA-Z0-9_]+")) + + line = line.slice(p1.len()).trim() + let i = line.position(regex("(?:[^o])[a-zA-Z0-9_ ]")) + if i == none { + return () + } + let arrow = line.slice(0, i) + line = line.slice(i).trim() + + let p2 = line.find(regex("^[a-zA-Z0-9_]+")) + line = line.slice(p2.len()).trim() + */ + + //let comment = none + let enable-dst = false + let disable-src = false + let create-dst = false + let destroy-dst = false + /*if line.contains(":") { + (line, comment) = line.split(":") + comment = comment.trim() + line = line.trim() + }*/ + if mods.contains("++") { + enable-dst = true + } + if mods.contains("--") { + disable-src = true + } + if mods.contains("**") { + create-dst = true + } + if mods.contains("!!") { + destroy-dst = true + } + + // TODO start / end tips + let seq-tips = parse-seq-tips(arrow) + if seq-tips == none { + return () + } + let (start-tip, dashed, end-tip, flip) = seq-tips + if flip and p1 == p2 { + (start-tip, end-tip) = (end-tip, start-tip) + } + //panic(start-tip, dashed, end-tip) + //comment += json.encode((start-tip, dashed, end-tip)).replace("\n", "") + + elmts += _seq( + p1, p2, + comment: comment, + dashed: dashed, + start-tip: start-tip, + end-tip: end-tip, + enable-dst: enable-dst, + disable-src: disable-src, + create-dst: create-dst, + destroy-dst: destroy-dst, + flip: flip + ) + return elmts +} + +#let from-plantuml(code, width: auto) = { + let code = code.text + code = code.replace(regex("(?s)/'.*?'/"), "") + + let elmts = () + let lines = code.split("\n") + let group-stack = () + let note-data = none + + for line in lines { + if note-data != none { + let l = line.trim() + if l in ("end note", "endrnote", "endhnote") { + elmts += _note( + note-data.side, + note-data.lines.join("\n"), + color: note-data.color, + pos: note-data.pos, + shape: note-data.shape, + aligned: note-data.aligned + ) + note-data = none + } else { + note-data.lines.push(line) + } + continue + } + + line = line.trim() + if line.len() == 0 { continue } + + if is-boundary-stmt(line) or is-comment-stmt(line) { + continue + } else if is-par-stmt(line) { + elmts += parse-par-stmt(line) + } else if is-gap-stmt(line) { + elmts += parse-gap-stmt(line) + } else if is-sep-stmt(line) { + elmts += parse-sep-stmt(line) + } else if is-evt-stmt(line) { + elmts += parse-evt-stmt(line) + } else if is-ret-stmt(line) { + elmts += parse-ret-stmt(line) + } else if is-grp-stmt(line) { + let group-data = parse-grp-stmt(line) + group-data.insert("start-i", elmts.len()) + group-stack.push(group-data) + } else if line == "end" { + if group-stack.len() == 0 { + continue + } + + let data = group-stack.pop() + + if data.type in ("alt", "else") { + let sections = () + let section-elmts = () + + while true { + section-elmts = elmts.slice(data.start-i) + elmts = elmts.slice(0, data.start-i) + sections.push(section-elmts) + sections.push(data.desc) + if data.type == "alt" { + break + + } else if data.type != "else" { + panic("Alt/else group mismatch") + } + data = group-stack.pop() + } + + elmts += _alt(..sections.rev()) + continue + } + + let grp-elmts = elmts.slice(data.start-i) + elmts = elmts.slice(0, data.start-i) + elmts += _grp( + data.name, + grp-elmts, + desc: data.desc, + type: data.type + ) + } else if is-simple-note-stmt(line) { + elmts += parse-simple-note-stmt(line) + } else if is-multiline-note-stmt(line) { + note-data = parse-multiline-note-stmt(line) + } else { + elmts += parse-seq-stmt(line) + } + } + + return diagram(elmts, width: width) +} \ No newline at end of file