ruby-graph-walker 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/colorize.rb +28 -0
- data/lib/dijkstra.rb +57 -0
- data/lib/graph.rb +153 -0
- data/lib/pq.rb +23 -0
- data/lib/ruby_graph_walker.rb +2 -0
- data/lib/test_planner.rb +98 -0
- metadata +49 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 098a0fa35d5773837d9b335237aa86d664835da3
         | 
| 4 | 
            +
              data.tar.gz: 2450e77e9c9de04c866daeaad447f81f34f2d58b
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 43d80e28ce884734f03b408099f8ee123e558ed631300d36f4342e5443606f45b9e94c9d22d8af579a3724313d453a55f7f2614e8e5c79081f942e4188b273a5
         | 
| 7 | 
            +
              data.tar.gz: ab81246bc8d2c9f36f3aa49e598fe4086e33d89c604f29e3e1f49dee49b1528e346ef959c8f8004cb90bd7362c0191eca3ee76a2bdcb04466e9fbf0d6f1cbb3c
         | 
    
        data/lib/colorize.rb
    ADDED
    
    | @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            module RubyGraphWalker
         | 
| 2 | 
            +
              def colorize(text, color_code)
         | 
| 3 | 
            +
                "\e[#{color_code}m#{text}\e[0m"
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def red(text); colorize(text, 31); end
         | 
| 7 | 
            +
              def green(text); colorize(text, 32); end
         | 
| 8 | 
            +
              def yellow(text); colorize(text, 33); end
         | 
| 9 | 
            +
              def blue(text); colorize(text, 34); end
         | 
| 10 | 
            +
              def magenta(text); colorize(text, 35); end
         | 
| 11 | 
            +
              def cyan(text); colorize(text, 36); end
         | 
| 12 | 
            +
              def yellow_background(text); colorize(text, 43); end
         | 
| 13 | 
            +
              def magenta_background(text); colorize(text, 45); end
         | 
| 14 | 
            +
              def exchange_foreground_and_background(text); colorize(text, 7); end
         | 
| 15 | 
            +
              def light_red(text); colorize(text, 91); end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def log_warning(msg)
         | 
| 18 | 
            +
                puts magenta(msg)
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              def log_error(msg)
         | 
| 22 | 
            +
                puts light_red(msg)
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              def log_info(msg)
         | 
| 26 | 
            +
                puts green(msg)
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
    
        data/lib/dijkstra.rb
    ADDED
    
    | @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            require_relative 'pq'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Dijkstra
         | 
| 4 | 
            +
              def initialize(graph, source_vertex)
         | 
| 5 | 
            +
                @graph = graph
         | 
| 6 | 
            +
                @vertices = @graph.vertices
         | 
| 7 | 
            +
                @source_vertex = source_vertex
         | 
| 8 | 
            +
                @path_to = {}
         | 
| 9 | 
            +
                @distance_to = {}
         | 
| 10 | 
            +
                @pq = PriorityQueue.new
         | 
| 11 | 
            +
                compute_shortest_path
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def shortest_path_to(vertex)
         | 
| 15 | 
            +
                return [] unless reachable?(vertex)
         | 
| 16 | 
            +
                return [] if @distance_to[vertex] == 0
         | 
| 17 | 
            +
                path = []
         | 
| 18 | 
            +
                while vertex != @source_vertex
         | 
| 19 | 
            +
                  path.unshift(vertex)
         | 
| 20 | 
            +
                  vertex = @path_to[vertex]
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
                path.unshift(@source_vertex)
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              private
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              def reachable?(vertex)
         | 
| 28 | 
            +
                not @distance_to[vertex] == Float::INFINITY
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              def compute_shortest_path
         | 
| 32 | 
            +
                update_distance_of_all_edges_to(Float::INFINITY)
         | 
| 33 | 
            +
                @distance_to[@source_vertex] = 0
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                @pq.insert(@source_vertex, 0)
         | 
| 36 | 
            +
                while @pq.any?
         | 
| 37 | 
            +
                  vertex = @pq.remove_min
         | 
| 38 | 
            +
                  @vertices[vertex].edges.each do |edge|
         | 
| 39 | 
            +
                    update(edge)
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              def update_distance_of_all_edges_to(distance)
         | 
| 45 | 
            +
                @vertices.each do |key, value|
         | 
| 46 | 
            +
                  @distance_to[key] = distance
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              def update(edge)
         | 
| 51 | 
            +
                raise "#{edge.to} doesn't exist, check edge[:to] in #{edge.inspect}" if @distance_to[edge.to].nil?
         | 
| 52 | 
            +
                return if @distance_to[edge.to] <= @distance_to[edge.from] + edge.weight
         | 
| 53 | 
            +
                @distance_to[edge.to] = @distance_to[edge.from] + edge.weight
         | 
| 54 | 
            +
                @path_to[edge.to] = edge.from
         | 
| 55 | 
            +
                @pq.insert(edge.to, @distance_to[edge.to])
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
    
        data/lib/graph.rb
    ADDED
    
    | @@ -0,0 +1,153 @@ | |
| 1 | 
            +
            require 'dijkstra'
         | 
| 2 | 
            +
            require 'colorize'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module RubyGraphWalker
         | 
| 5 | 
            +
              class Vertex
         | 
| 6 | 
            +
                attr_accessor :name, :weight, :edges, :trait, :visited, :zindex
         | 
| 7 | 
            +
                def initialize(args = {})
         | 
| 8 | 
            +
                  [:name, :edges, :trait].each { |key| raise "#{key} is not defined for Vertex #{args}" unless args[key] }
         | 
| 9 | 
            +
                  @name = args[:name]
         | 
| 10 | 
            +
                  @weight = args[:weight] || 1
         | 
| 11 | 
            +
                  @trait = args[:trait]
         | 
| 12 | 
            +
                  @visited = args[:visited] || false
         | 
| 13 | 
            +
                  @weight = args[:zindex] || 0
         | 
| 14 | 
            +
                  @edges = []
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  args[:edges].each do |edge|
         | 
| 17 | 
            +
                    e = Edge.new(edge)
         | 
| 18 | 
            +
                    e.from = @name
         | 
| 19 | 
            +
                    e.to = edge[:to]
         | 
| 20 | 
            +
                    add_edge(e)
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                private 
         | 
| 25 | 
            +
                def add_edge(e)
         | 
| 26 | 
            +
                  @edges << e
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              class Edge
         | 
| 31 | 
            +
                attr_accessor :name, :from, :to, :weight, :visited, :proc, :error_count
         | 
| 32 | 
            +
                def initialize(args = {})
         | 
| 33 | 
            +
                  [:name, :to, :proc].each { |key| raise "#{key} is not defined for Edge #{args}" unless args[key] }
         | 
| 34 | 
            +
                  @name = args[:name]
         | 
| 35 | 
            +
                  @from = args[:from]
         | 
| 36 | 
            +
                  @to = args[:to]
         | 
| 37 | 
            +
                  @weight = args[:weight] || 1
         | 
| 38 | 
            +
                  @visited = args[:visited] || false
         | 
| 39 | 
            +
                  @proc = args[:proc]
         | 
| 40 | 
            +
                  @error_count = 0
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def run
         | 
| 44 | 
            +
                  @proc.call
         | 
| 45 | 
            +
                  @visited = true
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              class Graph
         | 
| 50 | 
            +
                attr_accessor :vertices, :edges, :edges_by_name
         | 
| 51 | 
            +
                def initialize(vertices)
         | 
| 52 | 
            +
                  @vertices = {}
         | 
| 53 | 
            +
                  @edges_by_name = {}
         | 
| 54 | 
            +
                  @edges = []
         | 
| 55 | 
            +
                  vertices.each do |v_params|
         | 
| 56 | 
            +
                    v = Vertex.new(v_params)
         | 
| 57 | 
            +
                    @vertices[v_params[:name]] = v
         | 
| 58 | 
            +
                    v.edges.each do |edge|
         | 
| 59 | 
            +
                      puts "Warning: multiple edges named '#{edge.name}'" if @edges_by_name[edge.name]
         | 
| 60 | 
            +
                      @edges_by_name[edge.name] = edge
         | 
| 61 | 
            +
                      @edges << edge
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def find_path_via_edge(from, to, edge_name)
         | 
| 67 | 
            +
                  log_info "#{from} -> #{to} via: (#{edge_name}): "
         | 
| 68 | 
            +
                  matched_edges = @edges.select { |edge| edge.name == edge_name }
         | 
| 69 | 
            +
                  log_error "no edge found for '#{edge_name}'" if matched_edges.size == 0
         | 
| 70 | 
            +
                  log_error "multiple edges matched for '#{edge_name}'" if matched_edges.size > 1
         | 
| 71 | 
            +
                  via_edge = matched_edges.first
         | 
| 72 | 
            +
                  plan = []
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  first_path = Dijkstra.new(self, from).shortest_path_to(via_edge.from)
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  first_path.each_cons(2) do |path|
         | 
| 77 | 
            +
                    start, dest = path
         | 
| 78 | 
            +
                    vertex = @vertices[start]
         | 
| 79 | 
            +
                    edge = vertex.edges.select { |e| e.to == dest}.first
         | 
| 80 | 
            +
                    plan << {v: vertex, e: edge}
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  via_v = @vertices[via_edge.from]
         | 
| 84 | 
            +
                  plan << {v: via_v, e: via_edge}
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                  second_path = Dijkstra.new(self, via_edge.to).shortest_path_to(to)
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  second_path.each_cons(2) do |path|
         | 
| 89 | 
            +
                    start, dest = path
         | 
| 90 | 
            +
                    vertex = @vertices[start]
         | 
| 91 | 
            +
                    edge = vertex.edges.select { |e| e.to == dest }.first
         | 
| 92 | 
            +
                    plan << {v: vertex, e: edge}
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
                  plan 
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                def find_path(from, to, args = {})  
         | 
| 98 | 
            +
                  vertex_path = []
         | 
| 99 | 
            +
                  plan = []
         | 
| 100 | 
            +
                  via_type = args.keys.join
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  case via_type
         | 
| 103 | 
            +
                  when "via"
         | 
| 104 | 
            +
                    via = args[:via]
         | 
| 105 | 
            +
                    vertex_path = Dijkstra.new(self, from).shortest_path_to(via) + Dijkstra.new(self, via).shortest_path_to(to).drop(1)
         | 
| 106 | 
            +
                    vertex_path.each_cons(2) do |path|
         | 
| 107 | 
            +
                      f, d = path
         | 
| 108 | 
            +
                      vertex = @vertices[d]
         | 
| 109 | 
            +
                      edge = vertex.edges.select { |e| e.to == d}.first
         | 
| 110 | 
            +
                      plan << {v: vertex, e: edge}
         | 
| 111 | 
            +
                    end
         | 
| 112 | 
            +
                  when "via_edge"
         | 
| 113 | 
            +
                    via_edge = args[:via_edge]
         | 
| 114 | 
            +
                    plan = find_path_via_edge(from, to, via_edge)
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  # when "via_edges"
         | 
| 117 | 
            +
                  #   edges = args[:via_edges]
         | 
| 118 | 
            +
                  #   if edges.size < 2
         | 
| 119 | 
            +
                  #     raise "please specify multiple edges"
         | 
| 120 | 
            +
                  #   end
         | 
| 121 | 
            +
                  #   v = nil
         | 
| 122 | 
            +
                  #   edges[0..-2].each do |edge|
         | 
| 123 | 
            +
                  #     start = (plan.last[:v].name if plan.last) || from
         | 
| 124 | 
            +
                  #     v = @edges_by_name[edge].to
         | 
| 125 | 
            +
                  #     plan += find_path_via_edge(start, v, edge)
         | 
| 126 | 
            +
                  #   end
         | 
| 127 | 
            +
                  #   plan += find_path_via_edge(v, to, edges.last)
         | 
| 128 | 
            +
                  when ""
         | 
| 129 | 
            +
                    vertex_path = Dijkstra.new(self, from).shortest_path_to(to)
         | 
| 130 | 
            +
                    
         | 
| 131 | 
            +
                    vertex_path.each_cons(2) do |path|
         | 
| 132 | 
            +
                      from, d = path
         | 
| 133 | 
            +
                      vertex = @vertices[from]
         | 
| 134 | 
            +
                      edge = vertex.edges.select { |e| e.to == d}.first
         | 
| 135 | 
            +
                      plan << {v: vertex, e: edge}
         | 
| 136 | 
            +
                    end
         | 
| 137 | 
            +
                    if plan.empty? and from == to
         | 
| 138 | 
            +
                      return  :on_the_spot
         | 
| 139 | 
            +
                    end
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  if plan.any?
         | 
| 143 | 
            +
                    puts plan.map {|p| "#{p[:v].name if p[:v]} (#{p[:e].name if p[:e]})" }.join(" -> ") + " >> #{@vertices[to].name}" 
         | 
| 144 | 
            +
                  else
         | 
| 145 | 
            +
                    raise "No path from #{from} to #{to}" 
         | 
| 146 | 
            +
                  end
         | 
| 147 | 
            +
                  plan
         | 
| 148 | 
            +
                end
         | 
| 149 | 
            +
              end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
            end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
             | 
    
        data/lib/pq.rb
    ADDED
    
    | @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            class PriorityQueue
         | 
| 2 | 
            +
              def initialize
         | 
| 3 | 
            +
                @queue = {}
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def any?
         | 
| 7 | 
            +
                @queue.any?
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def insert(key, value)
         | 
| 11 | 
            +
                @queue[key] = value
         | 
| 12 | 
            +
                order_queue
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def remove_min
         | 
| 16 | 
            +
                @queue.shift.first
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              private
         | 
| 20 | 
            +
              def order_queue
         | 
| 21 | 
            +
                @queue.sort_by {|_key, value| value }
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
    
        data/lib/test_planner.rb
    ADDED
    
    | @@ -0,0 +1,98 @@ | |
| 1 | 
            +
            require 'colorize'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RubyGraphWalker
         | 
| 4 | 
            +
              module TestPlanner
         | 
| 5 | 
            +
                def search_vertex(query_method = 'query')
         | 
| 6 | 
            +
                  vertices = @graph.vertices.values
         | 
| 7 | 
            +
                  candidates = vertices.select do |v|
         | 
| 8 | 
            +
                    trait = v.trait
         | 
| 9 | 
            +
                    if trait.is_a?(Proc)
         | 
| 10 | 
            +
                      trait.call
         | 
| 11 | 
            +
                    elsif trait.is_a?(Array)
         | 
| 12 | 
            +
                      trait.any? { |t| (send query_method, t).any? }
         | 
| 13 | 
            +
                    else
         | 
| 14 | 
            +
                      send(query_method, trait).any?
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  if candidates.size > 1
         | 
| 19 | 
            +
                    log_warning "Warning: multiple vertices found: #{candidates.map {|c| c[:name]}.join(' ') }"   
         | 
| 20 | 
            +
                  elsif candidates.size == 0
         | 
| 21 | 
            +
                    if send(query_method, "*").any?
         | 
| 22 | 
            +
                      raise "No vertex found"
         | 
| 23 | 
            +
                    else
         | 
| 24 | 
            +
                      raise "Connection refused"
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                  candidates.max_by {|v| v.zindex}
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def path_to(to, args = {})
         | 
| 31 | 
            +
                  v = search_vertex
         | 
| 32 | 
            +
                  @graph.find_path(v.name, to, args)
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def run(plan, args = {})
         | 
| 36 | 
            +
                  raise "on the spot" if plan == :on_the_spot
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  plan.each do |p|
         | 
| 39 | 
            +
                    edge = p[:e]
         | 
| 40 | 
            +
                    raise "edge is nil" if edge.nil?
         | 
| 41 | 
            +
                    edge.run
         | 
| 42 | 
            +
                    @graph.vertices[edge.from].visited = true
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def text_logger(args = {})
         | 
| 47 | 
            +
                  log_file_name = "#{Time.now.strftime('%Y_%m_%d_%H_%M_%S')}.txt"
         | 
| 48 | 
            +
                  @log_file ||= File.open(log_file_name, 'w') 
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def log(msg)
         | 
| 52 | 
            +
                  text_logger.puts(msg)
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def start(graph, args = {})
         | 
| 56 | 
            +
                  @graph = graph
         | 
| 57 | 
            +
                  unvisited_edges = @graph.edges_by_name.select { |k, v| v.visited == false and v.weight > 1 }
         | 
| 58 | 
            +
                  starting_point = args[:start] || @graph.vertices.keys.first
         | 
| 59 | 
            +
                  while unvisited_edges.any?
         | 
| 60 | 
            +
                    edge_name = largest_weight(unvisited_edges).first
         | 
| 61 | 
            +
                    edge = @graph.edges_by_name[edge_name]
         | 
| 62 | 
            +
                    plan = path_to(starting_point, via_edge: edge_name)
         | 
| 63 | 
            +
                    begin
         | 
| 64 | 
            +
                      run(plan)
         | 
| 65 | 
            +
                    rescue => e
         | 
| 66 | 
            +
                      edge.error_count += 1
         | 
| 67 | 
            +
                      log edge.name + " failed! #{edge.error_count}"
         | 
| 68 | 
            +
                      e.backtrace.each do |text|
         | 
| 69 | 
            +
                        log text
         | 
| 70 | 
            +
                      end 
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                      retries = 3
         | 
| 73 | 
            +
                      begin
         | 
| 74 | 
            +
                        sleep 10
         | 
| 75 | 
            +
                        new_plan = path_to(edge.from)
         | 
| 76 | 
            +
                        run(new_plan, args)
         | 
| 77 | 
            +
                      rescue
         | 
| 78 | 
            +
                        edge.error_count += 1
         | 
| 79 | 
            +
                        retry unless (retries -= 1).zero?
         | 
| 80 | 
            +
                      end
         | 
| 81 | 
            +
                    end
         | 
| 82 | 
            +
                    unvisited_edges = @graph.edges_by_name.select { |k, v| v.visited == false and v.weight > 1 and v.error_count < 3}
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  @graph.edges_by_name.each do |k, v|
         | 
| 86 | 
            +
                    log "#{k} error count: #{v.error_count}"
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  text_logger.close
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                private
         | 
| 93 | 
            +
                def largest_weight(hash)
         | 
| 94 | 
            +
                  hash.max_by{|k,v| v.weight}
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: ruby-graph-walker
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.1
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - astro
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: bin
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2017-10-09 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies: []
         | 
| 13 | 
            +
            description: ''
         | 
| 14 | 
            +
            email: astro2linus@gmail.com
         | 
| 15 | 
            +
            executables: []
         | 
| 16 | 
            +
            extensions: []
         | 
| 17 | 
            +
            extra_rdoc_files: []
         | 
| 18 | 
            +
            files:
         | 
| 19 | 
            +
            - lib/colorize.rb
         | 
| 20 | 
            +
            - lib/dijkstra.rb
         | 
| 21 | 
            +
            - lib/graph.rb
         | 
| 22 | 
            +
            - lib/pq.rb
         | 
| 23 | 
            +
            - lib/ruby_graph_walker.rb
         | 
| 24 | 
            +
            - lib/test_planner.rb
         | 
| 25 | 
            +
            homepage: ''
         | 
| 26 | 
            +
            licenses:
         | 
| 27 | 
            +
            - MIT
         | 
| 28 | 
            +
            metadata: {}
         | 
| 29 | 
            +
            post_install_message: 
         | 
| 30 | 
            +
            rdoc_options: []
         | 
| 31 | 
            +
            require_paths:
         | 
| 32 | 
            +
            - lib
         | 
| 33 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 34 | 
            +
              requirements:
         | 
| 35 | 
            +
              - - ">="
         | 
| 36 | 
            +
                - !ruby/object:Gem::Version
         | 
| 37 | 
            +
                  version: '0'
         | 
| 38 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 39 | 
            +
              requirements:
         | 
| 40 | 
            +
              - - ">="
         | 
| 41 | 
            +
                - !ruby/object:Gem::Version
         | 
| 42 | 
            +
                  version: '0'
         | 
| 43 | 
            +
            requirements: []
         | 
| 44 | 
            +
            rubyforge_project: 
         | 
| 45 | 
            +
            rubygems_version: 2.4.8
         | 
| 46 | 
            +
            signing_key: 
         | 
| 47 | 
            +
            specification_version: 4
         | 
| 48 | 
            +
            summary: Ruby Graph Walker
         | 
| 49 | 
            +
            test_files: []
         |