# $Id: graphviz.rb 61 2007-12-04 05:48:23Z tim_pease $ require 'hpricot' require 'fileutils' require 'tempfile' module Webby module Filters # The Graphviz filter processes DOT scripts in a webpage and replaces them # with generated image files. A set of ... tags is # used to denote which section(s) of the page contains DOT scripts. # # Options can be passed to the Graphviz filter using attributes in the # tag. # # # digraph graph_1 { # graph [URL="default.html"] # a [URL="a.html"] # b [URL="b.html"] # c [URL="c.html"] # a -> b -> c # a -> c # } # # # If the DOT script contains *URL* or *href* statements on any of the nodes # or edges, then an image map will be generated and the image will be # "clikcable" in the webpage. If *URL* or *href* statements do not appear in # the DOT script, then a regular image will be inserted into the webpage. # # The image is inserted into the page using an HTML tag. A # corresponding ... element will be inserted if needed. # # The supported Graphviz options are the following: # # path : where generated images will be stored # [default is "/"] # type : the type of image to generate (png, jpeg, gif) # [default is png] # cmd : the Graphviz command to use when generating images # (dot, neato, twopi, circo, fdp) [default is dot] # # the following options are passed as-is to the generated tag # style : CSS styles to apply to the # class : CSS class to apply to the # id : HTML identifier # alt : alternate text for the # class Graphviz class Error < StandardError; end # :nodoc: # call-seq: # Graphviz.new( string, filters = nil ) # # Creates a new Graphviz filter that will operate on the given _string_. # The optional _filters_ describe filters that will be applied to the # output string returned by the Graphviz filter. # def initialize( str, filters = nil ) @log = ::Logging::Logger[self] @str = str @filters = filters # create a temporary file for holding any error messages # from the graphviz program @err = Tempfile.new('graphviz_err') @err.close end # call-seq: # to_html => string # # Process the original text string passed to the filter when it was # created and output HTML formatted text. Any text between # ... tags will have the contained DOT syntax # converted into an image and then included into the resulting HTML text. # def to_html doc = Hpricot(@str) doc.search('//graphviz') do |gviz| text = gviz.inner_html.strip # the DOT script to process path = gviz['path'] cmd = gviz['cmd'] || 'dot' type = gviz['type'] || 'png' %x[#{cmd} -V 2>&1] unless 0 == $?.exitstatus raise NameError, "'#{cmd}' not found on the path" end # pull the name of the graph|digraph out of the DOT script name = text.match(%r/\A\s*(?:strict\s+)?(?:di)?graph\s+([A-Za-z_][A-Za-z0-9_]*)\s+\{/o)[1] # see if the user includes any URL or href attributes # if so, then we need to create an imagemap usemap = text.match(%r/(?:URL|href)\s*=/o) != nil # generate the image filename based on the path, graph name, and type # of image to generate image_fn = path.nil? ? name.dup : File.join(path, name) image_fn << '.' << type # create the HTML img tag out = "\n" # generate the image map if needed if usemap IO.popen("#{cmd} -Tcmapx 2> #{@err.path}", 'r+') do |io| io.write text io.close_write out << io.read end error_check end # generate the image using graphviz -- but first ensure that the # path exists out_dir = ::Webby.site.output_dir out_file = File.join(out_dir, image_fn) FileUtils.mkpath(File.join(out_dir, path)) unless path.nil? cmd = "#{cmd} -T#{type} -o #{out_file} 2> #{@err.path}" IO.popen(cmd, 'w') {|io| io.write text} error_check # see if we need to put some guards around the output # (specifically for textile) @filters.each do |f| case f when 'textile' out.insert 0, "\n" out << "\n" end end unless @filters.nil? gviz.swap out end doc.to_html end private # call-seq: # error_check # # Check the temporary error file to see if it contains any error messages # from the graphviz program. If it is not empty, then read the contents # and log an error message and raise an exception. # def error_check if File.size(@err.path) != 0 @log.error File.read(@err.path).strip raise Error end end end # class Graphviz end # module Filters end # module Webby # EOF