<commit_before>class FeedMe::AbstractParser
  
  class << self
    
    attr_accessor :properties, :root_nodes
    
    def build(xml, format)
      # in a world with activesupport this would have been written as
      #   format_parser = (format.to_s.camelize + self.to_s).constantize
      camelized_format = format.to_s.split('_').map{ |w| w.capitalize }.join('')
      bare_class = self.to_s.split('::').last
      
      begin
        format_parser = FeedMe.const_get(camelized_format + bare_class)
        if format_parser.is_a?(Class) and format_parser.ancestors.include?(self)
          format_parser.new(xml)
        else
          self.new(xml, format)
        end
      rescue NameError
        self.new(xml, format)
      end
    end
    
  end

  def initialize(xml, format = nil)
    self.xml = xml
    self.format = format
    self.properties = self.class.properties
  end
  
  attr_accessor :xml, :format, :properties

  alias_method :root_node, :xml
  
  protected
  
  def method_missing(method)
    p = property(method)
    if p == :undefined
      return nil
    elsif p.class == Array
      return fetch("/#{p[0]}", root_node, p[1].to_sym)
    elsif p
      return fetch("/#{p}", root_node)
    else
      super
    end
  end

  def property(name)
    self.properties[format][name]
  end

  def fetch(selector, search_in = xml, method = :inner_html)
    item = search_in.at(selector)
    item[method] or item.try(method) if item
  end
  
end<commit_msg>metaprogramming goodness<commit_after>class FeedMe::AbstractParser
  
  class << self
    
    attr_accessor :properties, :root_nodes
    
    def build(xml, format)
      # in a world with activesupport this would have been written as
      #   format_parser = (format.to_s.camelize + self.to_s).constantize
      camelized_format = format.to_s.split('_').map{ |w| w.capitalize }.join('')
      bare_class = self.to_s.split('::').last
      
      begin
        format_parser = FeedMe.const_get(camelized_format + bare_class)
        if format_parser.is_a?(Class) and format_parser.ancestors.include?(self)
          format_parser.new(xml)
        else
          self.new(xml, format)
        end
      rescue NameError
        self.new(xml, format)
      end
    end
    
  end

  def initialize(xml, format = nil)
    self.xml = xml
    self.format = format
    self.properties = self.class.properties
    
    append_methods
  end
  
  attr_accessor :xml, :format, :properties

  alias_method :root_node, :xml
  
  protected
  
  def append_methods
    self.properties[format].each do |method, p|
      unless respond_to?(method)
        block = get_proc_for_property(p)
        # meta programming magic
        (class << self; self; end).module_eval do
          define_method method, &block
        end
      end
    end
  end
  
  def get_proc_for_property(p)
    if p.class == Array
      return proc { fetch("/#{p[0]}", root_node, p[1].to_sym) }
    elsif p != :undefined
      return proc { fetch("/#{p}", root_node) }
    else
      return proc { nil }
    end
  end

  def property(name)
    self.properties[format][name]
  end

  def fetch(selector, search_in = xml, method = :inner_html)
    item = search_in.at(selector)
    item[method] or item.try(method) if item
  end
  
end
{'fraction_non_alphanumeric': 0.1022344658708295, 'fraction_numerical': 0.001224364860728497, 'mean_word_length': 2.487726787620064, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 20}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1053508', 'n_tokens_mistral': 1076, 'n_tokens_neox': 1074, 'n_words': 252}