シーケンス図をテキストで出力する
PlantUMLをインストールしてみたのだけど、シーケンス図をテキスト形式で出力時に、日本語が含まれると表示がずれる。
まあ大抵の場合png出力で確認するとはいえ、なんか微妙にはがゆいので、日本語専用でPlantUMLのシーケンス図を出力するスクリプトを書いてみた。
(多分車輪の再発明)
日本 -> ソ連 : 日ソ中立条約 日本 -> アメリカ : 真珠湾攻撃 アメリカ --> 日本 : 原爆投下 ソ連 --> 日本 : 対日参戦 日本 -> 日本 : 無条件降伏
例えば、上記↑のテキストを下記↓の様に出力する。
┏━━┓ ┏━━┓ ┏━━━━┓ ┃日本┃ ┃ソ連┃ ┃アメリカ┃ ┗┳━┛ ┗┳━┛ ┗━┳━━┛ ┃日ソ中立条約 ┃ ┠────→┃ ┃ ┃ ┃ ┃ ┃真珠湾攻撃┃ ┃ ┠───────────→┃ ┃ ┃ ┃ ┃原爆投下 ┃ ┃ ┃← - - - - - - - - - - -┨ ┃ ┃ ┃ ┃対日参戦 ┃ ┃ ┃← - - - -┨ ┃ ┃ ┃ ┃ ┃無条件降伏┃ ┃ ┠←─ ┃ ┃ ┃ ┃ ┃ ┏┻━┓ ┏┻━┓ ┏━┻━━┓ ┃日本┃ ┃ソ連┃ ┃アメリカ┃ ┗━━┛ ┗━━┛ ┗━━━━┛
class String def jlen # SJISにおけるバイト数を返す # len = self.size nonAsciiLen = self.gsub(/[\x20-\x7e]/,'').size len + nonAsciiLen end end class Message attr_reader :name,:from,:to,:spec def initialize(name,from,to,spec=:normal) @name,@from,@to,@spec = name,from,to,spec end end class Participants # メッセージの送り手/受け手のクラスの集合 # @@DefOff = 2 def initialize @n = [] # Participantの名前の配列 # @p = [] # Participantそのものの配列 # @m = [] # Messageの配列 # end def add(*a) i = nil a.each do |name| i = @n.index(name) if !i i = @n.size loff = 0 loff = @@DefOff if i>0 @n << name @p << Participant.new(name,loff) end end i end def add_msg(name,from,to,spec=:normal) from = self.add(from) to = self.add(to) @m << Message.new(name,from,to,spec) end def draw_top @p.each{|p| print ' '*p.loff, p.gen_boxline(:top) };puts''#┏━┓ @p.each{|p| print ' '*p.loff, '┃'+(p.name)+'┃' };puts''#┃P ┃ @p.each{|p| print ' '*p.loff, p.gen_boxline(:bottom,'┳') };puts''#┗┳┛ end def draw_bottom @p.each{|p| print ' '*p.loff, p.gen_boxline(:top,'┻') };puts''#┏┻┓ @p.each{|p| print ' '*p.loff, '┃'+(p.name)+'┃' };puts''#┃P ┃ @p.each{|p| print ' '*p.loff, p.gen_boxline(:bottom) };puts''#┗━┛ end def draw_msgs @m.each do |m| start,stop = m.from,m.to if m.from > m.to # 送信元が 右側に居る場合 # start,stop = m.to,m.from end # Message#name の描画 # (0 ... start).each{|i| print ' '*(@p[i].loff+@p[i].coff), '┃', ' '*@p[i].roff} name,len = m.name,m.name.jlen if len%2 == 1 len += 1 name += " " end len /= 2 print (' '*(@p[start].loff+@p[start].coff)) + '┃' + name (start ... (@p.size-1)).each do |i| cnt = @p[i].roff + @p[i+1].loff + @p[i+1].coff if len > 0 cnt,len = cnt-len,len-cnt end print(' '*cnt) if cnt > 0 print (len>0) ? ' ' : '┃' end puts '' # 矢印部の描画 # head,body,tail = ' ',' ┃ ',' ' @p.size.times do |i| if i == start tail = (m.spec==:normal) ? '─' : ' -' body = (start==m.from) ? (' ┠'+tail) : ' ┃←' body = ' ┠←' if start == stop elsif i == stop tail = ' ' body = (stop==m.to) ? '→┃ ' : (head+'┨ ') end print head*(@p[i].loff+@p[i].coff-1), body, tail*(@p[i].roff-1) if i == stop head,body,tail = ' ',' ┃ ',' ' elsif i == start head = (m.spec==:normal) ? '─' : ' -' body = head*3 end end puts '' draw_lifelines # 行を詰めて表示したい場合はこの行をコメントアウトすべし # end end def draw_lifelines @p.each{|p| print ' '*(p.loff+p.coff), '┃', ' '*p.roff }; puts '' end def draw draw_top draw_msgs draw_bottom end end class Participant # メッセージの送り手/受け手のクラス # attr_reader :jlen,:name,:loff,:coff,:roff def initialize(name, loff=0) # loff : 箱部分の左端の、直前の箱からのオフセット距離 @name = name @jlen = name.jlen if @jlen%2 == 1 @jlen += 1 @name += " " end @jlen /= 2 @loff = loff @coff = (@jlen-1)/2+1 @roff = (@jlen%2==1) ? @coff : @coff+1 end def gen_boxline(top_or_bottom=:top,tail=nil,len=@jlen) #"┗┳┛" とか "┏━┓"を生成する # boxline = '' if tail boxline = '━'*(@coff-1) + tail + '━'*(@roff-1) else boxline = '━'*len end top_or_bottom==:top ? ('┏'+boxline+'┓') : ('┗'+boxline+'┛') end end ################################################################ if $0 == __FILE__ and ARGV.size > 0 p = Participants.new ARGF.each do |l| next if /^@/ =~ l or /^\s*$/ =~ l if /\s+([\-<>]+)\s+/ =~ l p1,arrow,p2,mes = $`,$1,$','' p2.chomp!;p2.chomp!;p2.sub!(/\s+$/,'') if /\s+:\s+/ =~ p2 p2,mes = $`,$' end p.add(p1,p2) if /^</=~arrow p1,p2=p2,p1 end arrow = (/\-\-/=~arrow) ? :dotted : :normal p.add_msg(mes,p1,p2,arrow) end end p.draw end __END__ #単独で使用する時は以下の様な感じ。 p = Participants.new #p.add("hogehogeho","foo","日本") p.add_msg('fooから「日本」へのメッセージ',"foo","日本",:dotted) p.add_msg('msg2(fooからhogehogeへ)',"foo","hogehogeho",:dotted) p.add_msg('msg3',"hogehogeho","日本") p.add_msg('msg4',"part1","part2") p.add_msg('msg5',"part2","foo",:dotted) p.draw