tac0x2a/yasuri

View on GitHub
lib/yasuri/yasuri.rb

Summary

Maintainability
A
35 mins
Test Coverage

require 'mechanize'
require 'json'
require 'yaml'

require_relative 'yasuri_node'
require_relative 'yasuri_text_node'
require_relative 'yasuri_struct_node'
require_relative 'yasuri_paginate_node'
require_relative 'yasuri_links_node'
require_relative 'yasuri_map_node'
require_relative 'yasuri_node_generator'

module Yasuri

  DefaultRetryCount = 5
  DefaultInterval_ms = 0

  def self.json2tree(json_string)
    raise RuntimeError if json_string.nil? or json_string.empty?

    node_hash = JSON.parse(json_string, {symbolize_names: true})
    self.hash2node(node_hash)
  end

  def self.yaml2tree(yaml_string)
    raise RuntimeError if yaml_string.nil? or yaml_string.empty?

    node_hash = YAML.safe_load(yaml_string, [Symbol], symbolize_names: true)
    self.hash2node(node_hash.deep_symbolize_keys)
  end

  def self.tree2json(node)
    raise RuntimeError if node.nil?

    self.node2hash(node).to_json
  end

  def self.with_retry(
    retry_count = DefaultRetryCount,
    interval_ms = DefaultInterval_ms)

    begin
      Kernel.sleep(interval_ms * 0.001)
      return yield() if block_given?
    rescue => e
      if retry_count > 0
        retry_count -= 1
        retry
      end
      fail e
    end
  end

  def self.node_name(name, opt)
    symbolize_names = opt[:symbolize_names]
    symbolize_names ? name.to_sym : name
  end

  # private

  def self.hash2node(node_hash, node_name = nil, node_type_class = nil)
    raise RuntimeError.new("") if node_name.nil? and node_hash.empty?

    child_nodes = []
    opt = {}
    path = nil

    if node_hash.is_a?(String)
      path = node_hash
    else
      child_nodes, opt, path = self.hash2child_node(node_hash)
    end

    # If only single node under root, return only the node.
    return child_nodes.first if node_name.nil? and child_nodes.size == 1

    node = if node_type_class.nil?
      Yasuri::MapNode.new(node_name, child_nodes, **opt)
    else
      node_type_class::new(path, node_name, child_nodes, **opt)
    end

    node
  end

  Text2Node = {
    text:   Yasuri::TextNode,
    struct: Yasuri::StructNode,
    links:  Yasuri::LinksNode,
    pages:  Yasuri::PaginateNode,
    map:    Yasuri::MapNode
  }

  NodeRegexps = Text2Node.keys.map { |node_type_sym| /^(#{node_type_sym})_(.+)$/ }

  def self.hash2child_node(node_hash)
    child_nodes = []
    opt = {}
    path = nil

    node_hash.each do |key, value|
      # is node?

      node_regexp = NodeRegexps.find { |r| key =~ r }

      case key
      when node_regexp
        node_type_sym = $1.to_sym
        child_node_name = $2
        child_node_type = Text2Node[node_type_sym]
        child_nodes << self.hash2node(value, child_node_name, child_node_type)
      when :path
        path = value
      else
        opt[key] = value
      end
    end

    [child_nodes, opt, path]
  end

  def self.node2hash(node)
    return node.to_h if node.instance_of?(Yasuri::MapNode)

    {
      "#{node.node_type_str}_#{node.name}" => node.to_h
    }
  end

  def self.method_missing(method_name, pattern=nil, **opt, &block)
    generated = Yasuri::NodeGenerator.gen(method_name, pattern, **opt, &block)
    generated || super(method_name, **opt)
  end

  private_constant :Text2Node, :NodeRegexps
  private_class_method :method_missing, :hash2child_node, :hash2node, :node2hash
end

class Hash
  def deep_symbolize_keys
    Hash[
      self.map do |k, v|
        v = v.deep_symbolize_keys if v.kind_of?(Hash)
        [k.to_sym, v]
      end
    ]
  end
end