module Nodepile # Pragmas is a parser and organizer class used to interpret the meaning # of pragmas that may appear in source files. By default, pragmas are # coded instructions for rendering, parsing, or layout that may be embedded # in input files. They are often instructions stored in the "_id" field # of an input file that begin with a specific indicating string '#pragma ' # Pragmas may be used to control things like the specific layout engine that # is used to visualize a graph (e.g. dot versus neato) # Example pragma lines are. # # #pragma neato # #pragma unflatten # # Create an instance of a Nodepile::Pragmas object in order to track and # interpret the collective pragmas of a given graph. Note that the most # common rule in #pragma interpretation is that if two pragmas contradict each # other, the furthest down in the file/parsing stream dominates. class Pragmas DEFAULT_PRAGMA_MARKER = "#pragma " @@indicator_patterns = Array.new # array of [pragma_sym,regexp] def initialize(pragma_marker: DEFAULT_PRAGMA_MARKER) @marker = pragma_marker.freeze @indicators = Hash.new #name mapped to value # if you make this method more complex, remember to update #dup end def dup c = self.class.new(pragma_marker: @marker) c._indicators.merge!(@indicators) return c end def [](pragma_sym) @indicators[pragma_sym] end # @yield [pragma_sym,pragma_val] provides pairs of name values as they have # been set. Only set values will appear in the yielded set. def each_setting_pair return enum_for(__method__) unless block_given @indicators.each_pair{|k,v| yield(k,v) } return @indicators.length end # Parse the given pragma and store the meaning for access via square bracket # method or the #each_setting_pair method. # @return [void] def parse(pragma_string) raise "Expecting pragma_string to start with [#{@marker}]" unless pragma_string.start_with?(@marker) #TODO: I there are more complicated parsing rules, they should go before # the simple fallthrough case of whitespace separated indicators # Simple indicators are single "word" values where each value is delimited # by whitespace. If two indicators apply to the same pragma_sym, pragma_string[@marker.length..-1].split(/\s+/).each{|s| prag_sym,_ = @@indicator_patterns.find{|(prag_sym,rx)| rx.match(s) } if prag_sym @indicators[prag_sym] = $1 else raise "Unrecognized pragma encountered [#{s}]" end } return nil end private # Declare simple indicators are pragmas whose presence or absence is the indicator. # such indicators can be stacked (multiple per pragma expression) # For example: # #pragma neato unflatten # # The above line would mean that two sepearate effects were being invoked, # the use of the "neato" rendering engine and the use of the unflatten # setting to improve the aspect ratio of some directed graphs def self._decl_simple_indicator(prag_name,alt_regexp) @@indicator_patterns<< [prag_name,alt_regexp].freeze end _decl_simple_indicator(:layout_engine,/^(dot|neato|fdp|sfdp|circo|twopi|nop2|osage|patchwork)$/) _decl_simple_indicator(:directionality,/^(graph|digraph)$/) #_decl_simple_indicators(:unflatten,/^(unflatten)$/) # not sure this is supported # Crude accessor (protected) for duplication and merge def _indicators = @indicator end #class Nodepile::Pragmas end #module Nodepile