From 0fd0eaab3b8bfc154b83a3507c3caa9d9ab556c6 Mon Sep 17 00:00:00 2001 From: Jonas Smedegaard Date: Mon, 1 Apr 2024 03:22:12 +0200 Subject: use PlantUML for Gantt diagrams --- bin/hedgedoc2quarto | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 197 insertions(+), 3 deletions(-) (limited to 'bin/hedgedoc2quarto') diff --git a/bin/hedgedoc2quarto b/bin/hedgedoc2quarto index c0bde3b..c81ddd5 100755 --- a/bin/hedgedoc2quarto +++ b/bin/hedgedoc2quarto @@ -26,7 +26,8 @@ and adapts embedded diagram code. Both HedgeDoc and Quarto uses Markdown, but different flavors, -and they handle different subsets of Mermaid diagrams. +and whereas both handle (different subsets of) Mermaid diagrams, +Quarto also (through plugins) handles PlantUML diagrams. =cut @@ -43,8 +44,12 @@ $content =~ s/^ (?'code'.*?\n) \k'fence' $/ - "{mermaid}\n\%\%| fig-width: 100\%\n" - . &mmd2mmd( $+{type}, $+{code} ) + +# FIXME: implement option to choose output diagram language +# "{mermaid}\n\%\%| fig-width: 100\%\n" +# . &mmd2mmd( $+{type}, $+{code} ) + "{.plantuml}\n\%\%| fig-width: 100\%\n" + . &mmd2puml( $+{type}, $+{code} ) . $+{fence} /gsmex; @@ -65,6 +70,195 @@ sub mmd2mmd ( $type, $code ) return "$type\n$code"; } +sub mmd2puml ( $type, $code ) +{ + my @newcode; + + # strip special comment marker '%%QUARTO%%' + $code =~ s/^\s*+\K%%QUARTO%%//gm; + + open my $fh, '<', \$code or die $!; + + while (<$fh>) { + + /^\s*+$/ + and push @newcode, '' + and next; + + /^(\s*+)%%PLANTUML%%\K.*/ + and push @newcode, "$1$&" + and next; + + # convert comments markers + /^(\s*+)(?:[%]{2,}(?'comment'\s*+))?+\K.*/; + my $indent = defined( $+{comment} ) ? "$1'$2" : $1; + $_ = $&; + + /^title\s/i + and push @newcode, "${indent}$_" + and next; + + /^excludes\s+weekends\b/i + and push @newcode, "${indent}saturday are closed" + and push @newcode, "${indent}sunday are closed" + and next; + /^weekday\s+\K(?:mon|tues|wednes|thurs|fri|satur|sun)day\b/i + and push @newcode, "${indent}weeks start on $&" + and next; + /^(?:date|axis)Format\s/i + and push @newcode, "${indent}'UNSUPPORTED: $_" + and next; + /^todayMarker\s+(off|on)\b/i + and push @newcode, "${indent}'UNSUPPORTED' $_" + and next; + /^section\s+\K\S+(?:\s+\S+)*/i + and push @newcode, "${indent}-- $& --" + and next; + + if (/^tickInterval\s+(?'tickAmount'\d+)(?'tickUnit'millisecond|second|minute|hour|day|week|month)\s*$/i + ) + { + push @newcode, "${indent}projectscale daily" + and next + if $+{tickAmount} eq 1 + and $+{tickUnit} eq 'day'; + push @newcode, "${indent}projectscale weekly" and next + if $+{tickAmount} eq 1 and $+{tickUnit} eq 'week' + or $+{tickAmount} eq 7 and $+{tickUnit} eq 'day'; + push @newcode, "${indent}projectscale monthly" + and next + if $+{tickAmount} eq 1 + and $+{tickUnit} eq 'month'; + push @newcode, "${indent}projectscale quarterly" + and next + if $+{tickAmount} eq 3 + and $+{tickUnit} eq 'month'; + push @newcode, "${indent}projectscale yearly" + and next + if $+{tickAmount} eq 12 + and $+{tickUnit} eq 'month'; + push @newcode, "${indent}'UNSUPPORTED' $&" + and next; + } + + /^ + (?'title'[^:\n]+) + \s*+:\s*+ + + # optional tags + (?: + (?: + (?'active'active) + | + (?'done'done) + | + (?'crit'crit) + | + (?'milestone'milestone) + )\s*+ + ,\s*+ + )?+ + + (?: + # optional tertiary item + (?: + (?'taskID'(?&id))\s*+ + ,\s*+ + (?=.*,) # several items must follow + )?+ + + # optional secondary item + (?: + (?'startDate'(?&date)) + | + after + (?'afterTaskIDs' + (?:\s+(?&id))++ + ) + )\s*+ + ,\s*+ + )?+ + + # required main item + (?: + (?'endDate'(?&date)) + | + until + (?'untilTaskIDs' + (?:\s+(?&id))++ + ) + | + (?'duration'\d+) + \s*+d + )\s*+ + (?(DEFINE) + (?'id'[^\s\d,][^\s,]*+) # assume digit as lead caracter is illegal + (?'date'\d\d\d\d(?:-\d\d(?:-\d\d)?+)?+) + ) + $/x + or defined( $+{comment} ) + and push @newcode, "${indent}$_" + and next + or die "unhandled syntax on line $.: $_"; + + defined( $+{active} ) + or defined( $+{done} ) + or defined( $+{crit} ) + and die "unhandled tag on line $.: $_"; + + my $task = "${indent}\[$+{title}]"; + my $taskref = $task; + + # optional 3rd item + if ( $+{taskID} ) { + $task .= " as [$+{taskID}]"; + $taskref = "${indent}\[$+{taskID}]"; + } + + if ( defined( $+{afterTaskIDs} ) ) { + my @reqs = split ' ', $+{afterTaskIDs}; + + if ( $+{milestone} ) { + push @newcode, "$task happens at [$_]'s end" for @reqs; + } + elsif ( $+{endDate} ) { + push @newcode, "$task ends $+{endDate}"; + push( @newcode, "$taskref starts at [$_]'s end" ) for @reqs; + } + elsif ( defined( $+{untilTaskIDs} ) ) { + my @reqsEnd = split ' ', $+{untilTaskIDs}; + push @newcode, "$task ends at [$_]'s end" for @reqsEnd; + push( @newcode, "$taskref starts at [$_]'s end" ) for @reqs; + } + else { + push @newcode, "$task requires $+{duration} days"; + push( @newcode, "$taskref starts at [$_]'s end" ) for @reqs; + } + } + else { + if ( $+{milestone} ) { + push @newcode, "$task happens $+{startDate}"; + } + elsif ( $+{endDate} ) { + push @newcode, + "$task starts $+{startDate} and ends $+{ednDate}"; + } + elsif ( defined( $+{untilTaskIDs} ) ) { + my @reqsEnd = split ' ', $+{untilTaskIDs}; + push @newcode, "$task starts $+{startDate}"; + push @newcode, "$task ends at [$_]'s end" for @reqsEnd; + } + else { + push @newcode, + "$task starts $+{startDate} and requires $+{duration} days"; + } + } + } + + $" = "\n"; + return "\@start$type\n@newcode\n\@end$type\n"; +} + =encoding UTF-8 =head1 AUTHOR -- cgit v1.2.3