require 'fileutils' require 'tempfile' module Webby::Helpers module GraphvizHelper # call-seq: # GraphvizHelper.error_check( file ) # # 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 self.error_check( file ) if ::File.size(file.path) != 0 msg = "\n" << ::File.read(file.path).strip raise ::Webby::Error, msg end end # The +graphviz+ method processes DOT scripts in a webpage and replaces them # with generated image files. The page sections to be processed are given # as blocks of text to the +graphviz+ method. # # Options can be passed to the Graphviz program using attributes in the # +graphviz+ method. # # <% graphviz( :path => "images", :type => "gif", cmd => "dot" ) do %> # digraph graph_1 { # graph [URL="default.html"] # a [URL="a.html"] # b [URL="b.html"] # c [URL="c.html"] # a -> b -> c # a -> c # } # <% end %> # # If the DOT script contains *URL* or *href* attributes 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* attributes 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 # def graphviz( *args, &block ) opts = args.last.instance_of?(Hash) ? args.pop : {} text = capture_erb(&block) return if text.empty? # create a temporary file for holding any error messages # from the graphviz program err = Tempfile.new('graphviz_err') err.close defaults = ::Webby.site.graphviz path = opts.getopt(:path, defaults[:path]) cmd = opts.getopt(:cmd, defaults[:cmd]) type = opts.getopt(:type, defaults[:type]) # 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 = ::File.join('', 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 GraphvizHelper.error_check(err) 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} GraphvizHelper.error_check(err) # put some guards around the output (specifically for textile) out = _guard(out) concat_erb(out, block.binding) return end end # module GraphvizHelper if cmd_available? %w[dot -V] register(GraphvizHelper) end end # module Webby::Helpers # EOF