lib/gv.rb in gv-0.0.3 vs lib/gv.rb in gv-0.1.0

- old
+ new

@@ -1,74 +1,85 @@ require 'gv/version' require 'ffi' module GV - module FFI - extend ::FFI::Library + module LibCGraph + extend FFI::Library - ffi_lib 'gvc', 'cgraph', 'cgraph' + ffi_lib 'cgraph' - class AGraph < ::FFI::ManagedStruct - # dummy layout, only ever used by reference - layout :_1, :int, - :_2, :int - + class AGraph < FFI::AutoPointer def self.release(ptr) - FFI.agclose(ptr) unless ptr.null? + LibCGraph.agclose(ptr) unless ptr.null? end end - typedef :pointer, :gvc + typedef :pointer, :ag_node typedef :pointer, :ag_edge - attach_function :gvContext, [], :pointer - attach_function :gvFreeLayout, [:gvc, AGraph.by_ref], :int - attach_function :gvLayout, [:gvc, AGraph.by_ref, :string], :int - attach_function :gvRenderFilename, [:gvc, AGraph.by_ref, :string, :string], :int - attach_function :gvRenderData, [:gvc, AGraph.by_ref, :string, :pointer, :pointer], :int - attach_function :gvFreeRenderData, [:pointer], :void + attach_function :agmemread, [:string], AGraph + attach_function :agopen, [:string, :long, :pointer], AGraph + attach_function :agclose, [AGraph], :int - attach_function :agmemread, [:string], AGraph.by_ref - attach_function :agopen, [:string, :long, :pointer], AGraph.by_ref - attach_function :agclose, [AGraph.by_ref], :int - attach_variable :Agundirected, :long attach_variable :Agstrictundirected, :long attach_variable :Agdirected, :long attach_variable :Agstrictdirected, :long - attach_function :agnode, [AGraph.by_ref, :string, :int], :ag_node - attach_function :agedge, [AGraph.by_ref, :ag_node, :ag_node, :string, :int], :ag_edge - attach_function :agsubg, [AGraph.by_ref, :string, :int], :pointer + attach_function :agnode, [AGraph, :string, :int], :ag_node + attach_function :agedge, [AGraph, :ag_node, :ag_node, :string, :int], :ag_edge + attach_function :agsubg, [AGraph, :string, :int], :pointer attach_function :agnameof, [:pointer], :string attach_function :agraphof, [:pointer], :pointer attach_function :agtail, [:ag_edge], :ag_node attach_function :aghead, [:ag_edge], :ag_node attach_function :agget, [:pointer, :string], :string attach_function :agsafeset, [:pointer, :string, :string, :string], :pointer - attach_function :agstrdup_html, [AGraph.by_ref, :string], :pointer - attach_function :agstrfree, [AGraph.by_ref, :pointer], :int + attach_function :agstrdup_html, [AGraph, :string], :pointer + attach_function :agstrfree, [AGraph, :pointer], :int - attach_function :agisdirected, [AGraph.by_ref], :int - attach_function :agisstrict, [AGraph.by_ref], :int + attach_function :agisdirected, [AGraph], :int + attach_function :agisstrict, [AGraph], :int end + private_constant :LibCGraph + module LibGVC + extend FFI::Library + ffi_lib 'gvc' + + typedef :pointer, :gvc + + attach_function :gvContext, [], :pointer + attach_function :gvFreeLayout, [:gvc, LibCGraph::AGraph], :int + attach_function :gvLayout, [:gvc, LibCGraph::AGraph, :string], :int + + attach_function :gvRenderFilename, [:gvc, LibCGraph::AGraph, :string, :string], :int + attach_function :gvRenderData, [:gvc, LibCGraph::AGraph, :string, :pointer, :pointer], :int + attach_function :gvFreeRenderData, [:pointer], :void + end + private_constant :LibGVC + class Component - @@gvc = FFI.gvContext() + # @!visibility private + @@gvc = LibGVC.gvContext() + # @return [Graph, SubGraph] the graph this component belongs to attr_reader :graph + # Creates an HTML label + # @param string [String] the HTML to parse + # @return [Object] a HTML label def html(string) - ptr = FFI.agstrdup_html(graph.ptr, string) + ptr = LibCGraph.agstrdup_html(graph.ptr, string) string = ptr.read_string - FFI.agstrfree graph.ptr, ptr + LibCGraph.agstrfree graph.ptr, ptr string end def hash @@ -79,20 +90,29 @@ other.is_a?(Component) && ptr == other.ptr end alias :eql? :== + # @return [String] the component's name def name - FFI.agnameof ptr + LibCGraph.agnameof ptr end + # Sets an attribute + # @param attr [Symbol, String] attribute name + # @see http://www.graphviz.org/doc/info/attrs.html Node, Edge and Graph Attributes + # @param value [Object] attribute value def []=(attr, value) - FFI.agsafeset(ptr, attr.to_s, value.to_s, "") + LibCGraph.agsafeset(ptr, attr.to_s, value.to_s, "") end + # Retrieves the value of an attribute + # @param attr [Symbol, String] attribute name + # @see http://www.graphviz.org/doc/info/attrs.html Node, Edge and Graph Attributes + # @return [Object] the attribute value def [](attr) - FFI.agget(ptr, attr.to_s) + LibCGraph.agget(ptr, attr.to_s) end protected attr_reader :ptr end @@ -100,56 +120,80 @@ class Node < Component def initialize(graph, name_or_ptr) @graph = graph case name_or_ptr when String - @ptr = FFI.agnode(graph.ptr, name_or_ptr, 1) + @ptr = LibCGraph.agnode(graph.ptr, name_or_ptr, 1) else @ptr = name_or_ptr end end end class Edge < Component def initialize(graph, name, tail, head) @graph = graph - @ptr = FFI.agedge(graph.ptr, tail.ptr, head.ptr, name, 1) + @ptr = LibCGraph.agedge(graph.ptr, tail.ptr, head.ptr, name, 1) end + # @return [Node] the head node of the edge def head - Node.new @graph, FFI.aghead(ptr) + Node.new @graph, LibCGraph.aghead(ptr) end + # @return [Node] the tail node of the edge def tail - Node.new @graph, FFI.agtail(ptr) + Node.new @graph, LibCGraph.agtail(ptr) end end class BaseGraph < Component + # Creates a new node + # @param name [String] the name (identifier) of the node + # @param attrs [Hash{String, Symbol => Object}] the attributes + # to associate with this node + # @see http://www.graphviz.org/doc/info/attrs.html Node, Edge and Graph Attributes + # @return [Node] the newly created node def node(name, attrs = {}) component Node, [name], attrs end + # Creates a new edge + # @param name [String] the name (identifier) of the edge + # @param tail [Node] the edge's tail node + # @param head [Node] the edge's head node + # @param attrs [Hash{String, Symbol => Object}] the attributes + # to associate with this edge + # @see http://www.graphviz.org/doc/info/attrs.html Node, Edge and Graph Attributes + # @return [Edge] the newly created edge def edge(name, tail, head, attrs = {}) component Edge, [name, tail, head], attrs end + # Creates a new sub-graph + # @param name [String] the name (identifier) of the sub-graph + # @param attrs [Hash{String, Symbol => Object}] the attributes + # to associate with this sub-graph + # @see http://www.graphviz.org/doc/info/attrs.html Node, Edge and Graph Attributes + # @return [SubGraph] the newly created sub-graph def sub_graph(name, attrs = {}) graph = component SubGraph, [name], attrs yield graph if block_given? graph end + # @return whether this graph is directed def directed? - FFI.agisdirected(ptr) == 1 + LibCGraph.agisdirected(ptr) == 1 end + # @return whether this graph is strict def strict? - FFI.agisstrict(ptr) == 1 + LibCGraph.agisstrict(ptr) == 1 end private def component(klass, args, attrs = {}) comp = klass.new self, *args @@ -164,44 +208,54 @@ end class SubGraph < BaseGraph def initialize(graph, name) @graph = graph - @ptr = FFI.agsubg(graph.ptr, name, 1) + @ptr = LibCGraph.agsubg(graph.ptr, name, 1) end end class Graph < BaseGraph class << self private :new - def open(name, type = :directed, strict = :normal) - ag_type = case [type, strict] - when [:directed, :normal] then FFI.Agdirected - when [:undirected, :normal] then FFI.Agundirected - when [:directed, :strict] then FFI.Agstrictdirected - when [:undirected, :strict] then FFI.Agstrictundirected + + # Creates a new graph + # @param type [:directed, :undirected] the graphs type + # @param strictness [:strict, :normal] the graphs strict type + # @see http://www.graphviz.org/doc/info/attrs.html Node, Edge and Graph Attributes + # @yieldparam graph [Graph] the newly created graph + # @return [Graph] the newly created graph + def open(name, type = :directed, strictness = :normal) + ag_type = case [type, strictness] + when [:directed, :normal] then LibCGraph.Agdirected + when [:undirected, :normal] then LibCGraph.Agundirected + when [:directed, :strict] then LibCGraph.Agstrictdirected + when [:undirected, :strict] then LibCGraph.Agstrictundirected else - raise ArgumentError, "invalid graph type #{[type, strict]}" + raise ArgumentError, "invalid graph type #{[type, strictness]}" end - graph = new(FFI.agopen(name, ag_type, ::FFI::Pointer::NULL)) + graph = new(LibCGraph.agopen(name, ag_type, FFI::Pointer::NULL)) if block_given? yield graph end graph end + # Loads a graph from a string of file + # @param io [IO, String] the resource to load from + # @return the newly loaded graph def load(io) data = if io.is_a? String io else io.read end - new FFI.agmemread(data) + new LibCGraph.agmemread(data) end end def initialize(ptr) @ptr = ptr @@ -209,29 +263,40 @@ def graph self end - def write(filename, format = 'png', layout = 'dot') - FFI.gvLayout(@@gvc, ptr, layout.to_s) - FFI.gvRenderFilename(@@gvc, ptr, format.to_s, filename); - FFI.gvFreeLayout(@@gvc, ptr) + # Renders the graph to an images and saves the result to a file + # @param filename [String] the filename + # @param format [String] the image format to use, e.g. 'svg', 'pdf' etc. + # @param layout [String] the layout to use, e.g. 'dot' or 'neato' etc. + # @return [nil] + def save(filename, format = 'png', layout = 'dot') + LibGVC.gvLayout(@@gvc, ptr, layout.to_s) + LibGVC.gvRenderFilename(@@gvc, ptr, format.to_s, filename); + LibGVC.gvFreeLayout(@@gvc, ptr) + + nil end + # Renders the graph to an image and returns the result as a string + # @param format [String] the image format to use, e.g. 'svg', 'pdf' etc. + # @param layout [String] the layout to use, e.g. 'dot' or 'neato' etc. + # @return [String] the rendered graph in the given format def render(format = 'png', layout = 'dot') - FFI.gvLayout(@@gvc, ptr, layout.to_s) + LibGVC.gvLayout(@@gvc, ptr, layout.to_s) - data_ptr = ::FFI::MemoryPointer.new(:pointer, 1) - len_ptr = ::FFI::MemoryPointer.new(:int, 1) + data_ptr = FFI::MemoryPointer.new(:pointer, 1) + len_ptr = FFI::MemoryPointer.new(:int, 1) - FFI.gvRenderData(@@gvc, ptr, format.to_s, data_ptr, len_ptr); + LibGVC.gvRenderData(@@gvc, ptr, format.to_s, data_ptr, len_ptr); len = len_ptr.read_uint data_ptr = data_ptr.read_pointer - data = data_ptr.read_string_length len + data = data_ptr.read_string len - FFI.gvFreeRenderData(data_ptr) - FFI.gvFreeLayout(@@gvc, ptr) + LibGVC.gvFreeRenderData(data_ptr) + LibGVC.gvFreeLayout(@@gvc, ptr) data end end end