louismullie/treat

View on GitHub
lib/treat/entities/entity.rb

Summary

Maintainability
A
25 mins
Test Coverage
module Treat::Entities

  # Basic tree structure.
  require 'birch'
  
  # The Entity class extends a basic tree structure 
  # (written in C for optimal speed) and represents 
  # any form of textual entityin a processing task 
  # (this could be a collection of documents, a 
  # single document, a single paragraph, etc.)
  # 
  # Classes that extend Entity provide the concrete
  # behavior corresponding to the relevant entity type. 
  # See entities.rb for a full list and description of 
  # the different entity types in the document model.
  class Entity < ::Birch::Tree

    # A symbol representing the lowercase
    # version of the class name. This is 
    # the only attribute that the Entity
    # class adds to the Birch::Tree class.
    attr_accessor :type
    
    # Autoload all the classes in /abilities.
    path = File.expand_path(__FILE__)
    patt = File.dirname(path) + '/entity/*.rb'
    Dir.glob(patt).each { |f| require f }
    
    # Implements support for #register, #registry.
    include Registrable

    # Implement support for #self.call_worker, etc.
    extend Delegatable

    # Implement support for #self.print_debug, etc.
    extend Debuggable

    # Implement support for #self.build and #self.from_*
    extend Buildable

    # Implement support for #apply (previously #do).
    include Applicable

    # Implement support for #frequency, #frequency_in,
    # #frequency_of, #position, #position_from_end, etc.
    include Countable

    # Implement support for over 100 #magic methods!
    include Magical

    # Implement support for #to_s, #inspect, etc.
    include Stringable

    # Implement support for #check_has and others.
    include Checkable

    # Implement support for #each_entity, as well as
    # #entities_with_type, #ancestors_with_type,
    # #entities_with_feature, #entities_with_category, etc.
    include Iterable

    # Implement support for #export, allowing to export 
    # a data set row from the receiving entity.
    include Exportable

    # Implement support for #self.compare_with
    extend Comparable

    # Initialize the entity with its value and
    # (optionally) a unique identifier. By default,
    # the object_id will be used as id.
    def initialize(value = '', id = nil)
      id ||= object_id; super(value, id)
      @type = :entity if self == Entity
      @type ||= self.class.mn.ucc.intern
    end

    # Add an entity to the current entity.
    # Registers the entity in the root node
    # token registry if the entity is a leaf.
    # Unsets the parent node's value; in order
    # to keep the tree clean, only the leaf
    # values are stored.
    # 
    # Takes in a single entity or an array of 
    # entities. Returns the first child supplied.
    # If a string is 
    def <<(entities, clear_parent = true)
      entities = (entities.is_a?(::String) ||
      entities.is_a?(::Numeric)) ? 
      entities.to_entity : entities
      entities = entities.is_a?(::Array) ?
      entities : [entities]
      # Register each entity in this node.
      entities.each { |e| register(e) }
      # Pass to the <<() method in Birch.
      super(entities)
      # Unset the parent value if necessary.
      @parent.value = '' if has_parent?
      # Return the first child.
      return entities[0]
    end
    
    # Catch missing methods to support method-like
    # access to features (e.g. entity.category
    # instead of entity.features[:category]) and to
    # support magic methods (see #magic).
    #
    # If the feature or magic method does not exist,
    # or can't be parsed, raises an exception.
    #
    # Also catches the "empty" method call (e.g.
    # Word('hello') or Word 'hello') as syntactic
    # sugar for the #self.build method.
    def method_missing(sym, *args, &block)
      return self.build(*args) if sym == nil
      return @features[sym] if @features.has_key?(sym)
      result = magic(sym, *args, &block)
      return result unless result == :no_magic
      begin; super(sym, *args, &block)
      rescue NoMethodError; invalid_call(sym); end
    end
    
    # Raises a Treat::Exception saying that the 
    # method called was invalid, and that the 
    # requested method does not exist. Also
    # provides suggestions for misspellings.
    def invalid_call(sym)
      msg = Treat::Workers.lookup(sym) ?
      "Method #{sym} can't be called on a #{type}." :
      "Method #{sym} is not defined by Treat." +
      Treat::Helpers::Help.did_you_mean?(
      Treat::Workers.methods, sym)
      raise Treat::Exception, msg
    end
    
  end

end