lib/diagrams/dot.rb in diagrams-rb-0.0.1 vs lib/diagrams/dot.rb in diagrams-rb-0.0.2

- old
+ new

@@ -2,109 +2,139 @@ require 'open3' require 'tempfile' module Diagrams - class Dot - GRAPH_DEFAULTS = { - pad: '2.0', - splines: 'ortho', - nodesep: '0.60', - ranksep: '0.75', - rankdir: 'TB', - fontname: '"Sans-Serif"', - fontsize: '15', - fontcolor: '"#2D3436"' - }.freeze + class Dot # rubocop:disable Metrics/ClassLength + DEFAULTS = { - NODE_DEFAULTS = { - shape: 'box', - style: 'rounded', - fixedsize: 'true', - width: '1.4', - height: '1.4', - labelloc: 'b', - imagescale: 'true', - penwidth: '0', - fontname: '"Sans-Serif"', - fontsize: '13', - fontcolor: '"#2D3436"' - }.freeze + GRAPH: { + pad: '2.0', + splines: 'ortho', + nodesep: '0.60', + ranksep: '0.75', + rankdir: 'TB', + fontname: '"Sans-Serif"', + fontsize: '15', + fontcolor: '"#2D3436"' + }, - CLUSTER_DEFAULTS = { - shape: 'box', - style: 'rounded', - labeljust: 'l', - pencolor: '"#AEB6BE"' - }.freeze + NODE: { + shape: 'box', + style: 'rounded', + fixedsize: 'true', + width: '1.4', + height: '1.4', + labelloc: 'b', + imagescale: 'true', + penwidth: '0', + fontname: '"Sans-Serif"', + fontsize: '13', + fontcolor: '"#2D3436"' + }, - CLUSTER_BGCOLORS = %w[#E5F5FD #EBF3E7 #ECE8F6 #FDF7E3].freeze + CLUSTER: { + shape: 'box', + style: 'rounded', + labeljust: 'l', + pencolor: '"#AEB6BE"' + }, - EDGE_DEFAULTS = { - color: '"#7B8894"', - fontcolor: '"#2D3436"', - fontname: '"Sans-Serif"', - fontsize: '13' + CLUSTER_BGCOLORS: %w[#E5F5FD #EBF3E7 #ECE8F6 #FDF7E3], + + EDGE: { + color: '"#7B8894"', + fontcolor: '"#2D3436"', + fontname: '"Sans-Serif"', + fontsize: '13' + }, + + PADDING: 0.45 + }.freeze - attr_accessor :dot_output, :format, :space, :clen + attr_accessor :format, :space, :bg_color_len def initialize(**attrs) - @dot_output = "digraph G {\n".dup @format = attrs.delete(:format) || 'png' - GRAPH_DEFAULTS.merge(attrs).each do |key, value| - @dot_output << " #{key}=#{value};\n" - end + @test = attrs.delete(:test) @space = ' ' - @clen = CLUSTER_BGCOLORS.length + @bg_color_len = DEFAULTS[:CLUSTER_BGCOLORS].length @depth = 0 @cluster_idx = -1 + write_digraph_default(attrs) end - def indent - space * @depth - end - def add_node(id, label: '', icon: nil, **attrs) - attributes = NODE_DEFAULTS.merge(attrs).map { |k, v| "#{k}=#{v}" }.join(',') + attributes = DEFAULTS[:NODE].merge(attrs.merge(height: node_height(label))).map { |k, v| "#{k}=#{v}" }.join(',') dot_output << "#{indent} #{id} [label=\"#{label}\", image=\"#{icon}\", #{attributes}];\n" end def add_edge(from, to:, **attrs) - attributes = EDGE_DEFAULTS.merge(attrs).map { |k, v| "#{k}=#{v}" }.join(',') + attributes = DEFAULTS[:EDGE].merge(attrs).map { |k, v| "#{k}=#{v}" }.join(',') dot_output << " #{from} -> #{to} [#{attributes}];\n" end def begin_cluster(label, **attrs) # rubocop:disable Metrics/AbcSize cluster_label = label.empty? ? "cluster_#{@cluster_idx += 1}" : label dot_output << "#{indent}subgraph cluster_#{identifier(cluster_label)} {\n" dot_output << "#{indent} label=\"#{label}\";\n" - dot_output << "#{indent} bgcolor=\"#{CLUSTER_BGCOLORS[@depth % clen]}\";\n" - CLUSTER_DEFAULTS.merge(attrs).each do |key, value| - dot_output << "#{indent} #{key}=#{value};\n" + dot_output << "#{indent} bgcolor=\"#{DEFAULTS[:CLUSTER_BGCOLORS][@depth % bg_color_len]}\";\n" + DEFAULTS[:CLUSTER].merge(attrs).each do |key, value| + dot_output << "#{indent} #{key}=#{value};\n" end @depth += 1 + @dot_output end def end_cluster dot_output << "#{indent}}\n" @depth -= 1 end + def generate_image + dot_output << "}\n" + return dot_output if @test + + write_output + end + + private + + def dot_output + @dot_output ||= "digraph G {\n".dup + end + + def write_digraph_default(attrs) + DEFAULTS[:GRAPH].merge(attrs).each do |key, value| + dot_output << " #{key}=#{value};\n" + end + end + + def indent + space * @depth + end + + def node_height(label) + (DEFAULTS[:NODE][:height].to_f + (DEFAULTS[:PADDING] * label.split("\\n").size)).round(1).to_s + end + def identifier(string) string.gsub(/\s+/, '_') end - def generate_image - output_path = caller_locations.last.path.gsub('.rb', ".#{format}") - dot_output << "}\n" + def write_output Tempfile.open('temp.dot') do |file| File.write(file.path, dot_output) cmd = "dot -T#{format} #{file.path} -o #{output_path}" _, stderr, status = Open3.capture3(cmd) puts stderr unless status.success? end + end + + def output_path + caller_locations.last.path.gsub('.rb', ".#{format}") end end end