Squeak Smalltalk と Ruby のクラス階層をグラフ化


Route 477 - GNU Smalltalkのクラス階層をグラフ化した が面白かったので、参考にして Squeak Smalltalk (Squeak3.10J) と Ruby1.9 (Ruby1.9.1p0) のを作ってみました。

Squeak Smalltalk Ruby


Squeak Smalltalk には Traits(ミックスインみたいで、もう少し柔軟な機構)があるのでこれを青で、抽象クラスは #subclassResponsibility を含むメソッドを持つクラス OR 自分が抽象クラスだとコメントで主張しているクラス OR 名前が Abstract で始まるクラス で判断し、赤で塗りつぶしています。Ruby は抽象クラスの判断ができなかったので、モジュールだけ青にしてみました。


Squeak Smalltalk の一部のクラスが孤立しているように見えますが、これはおおざっぱな抜粋のせいで、実際には継承ツリーの一部になっています。念のため。


Squeak Smalltalk
| abstractClasses nodes file packages |

abstractClasses := OrderedCollection new.
nodes := OrderedCollection new.
packages := #('Kernel' 'Collections' 'Exceptions' 'Files' 'Graphics' 'Morphic'
   'Multilingual' 'Network' 'ST80' 'SUnit' 'Sound' 'Compiler' 'Tools' 'Traits').

Smalltalk allClassesDo: [:class |
   (packages includes: (class category copyUpTo: $-)) ifTrue: [
      | hasSubResp comment hasAbstComment hasAbstName |
      hasSubResp := class selectors anySatisfy: [:sel |
         (class >> sel) messages includes: #subclassResponsibility].
      comment := class comment asString asLowercase.
      hasAbstComment := #('i am an abstract' 'i am the abstract') anySatisfy: [:each |
            comment includesSubString: each].
      hasAbstName := class name beginsWith: 'Abstract'.
      (hasSubResp or: [hasAbstComment or: [hasAbstName]])
         ifTrue: [abstractClasses add: class].

      nodes add: class -> class superclass.
      class traitComposition traits do: [:trait | nodes add: class -> trait]]].

file := FileStream forceNewFileNamed: 'squeakClasses.dot'.
file lineEndConvention: #lf.
[  file nextPutAll: 'digraph G{'; cr.
   file nextPutAll: 'graph[rankdir=RL];'; cr.
   file nextPutAll: 'edge[arrowsize=0.5];'; cr.
   file nextPutAll: 'node[fontsize=12, shape=box, width=0.05, height=0.05];'; cr.
   abstractClasses do: [:abst |
      file nextPutAll: abst name, ' [style=filled, fillcolor="#FF8888"];'; cr].
   Trait allInstances do: [:trait |
      file nextPutAll: trait name, ' [style=filled, fillcolor="#8888FF"];'; cr].
   nodes do: [:assoc | file nextPutAll: assoc printString, ';' ; cr].
   file nextPutAll: '}'
] ensure: [file ifNotNil: [file close]]
Ruby
nodes = []
classes = []
ObjectSpace.each_object(Class){ |c| classes << c }
modules = []
ObjectSpace.each_object(Module){ |m| modules << m unless classes.include?(m) }
def nil.name; "nil" end

classes+modules.each{ |c|
  nodes << [c, c.superclass]
  ancestors = c.ancestors
  (ancestors[1..(ancestors.index(c.superclass) || 1)-1]).each{ |m| nodes << [c, m] } }

puts [
  "digraph G{",
  "graph[rankdir=RL];",
  "edge[arrowsize=0.5];",
  "node[fontsize=12, shape=box, width=0.05, height=0.05];",
  modules.collect{ |m| "\"#{m.name}\" [style=filled, fillcolor=\"#8888FF\"];" },
  nodes.collect{ |pair| "\"#{pair.first.name}\" -> \"#{pair.last.name}\";" },
  "}"
].flatten.join("\n")