<commit_before>require 'hashie/extensions/coercion'
require 'hashie/extensions/indifferent_access'
require 'hashie/extensions/dash/indifferent_access'

module Trax
  module Core
    module Types
      class Struct < ::Hashie::Dash
        include ::Hashie::Extensions::Dash::IndifferentAccess
        include ::Hashie::Extensions::Coercion
        include ::Hashie::Extensions::IgnoreUndeclared
        include ::Hashie::Extensions::Dash::PropertyTranslation

        # note that we must explicitly set default or blank values for all properties.
        # It defeats the whole purpose of being a 'struct'
        # if we fail to do so, and it makes our data far more error prone
        DEFAULT_VALUES_FOR_PROPERTY_TYPES = {
          :array_property   => [],
          :boolean_property => nil,
          :float_property   => 0.0,
          :string_property  => "",
          :struct_property  => {},
          :enum_property    => nil,
          :integer_property => nil
        }.with_indifferent_access.freeze

        def self.fields_module
          @fields_module ||= begin
            module_name = "#{self.name}::Fields"
            mod = ::Trax::Core::NamedModule.new(module_name, ::Trax::Core::Fields.clone)
            mod.include(superclass.fields) if superclass.instance_variable_defined?("@fields_module")
            mod
          end
        end

        def self.fields
          fields_module
        end

        def self.array_property(name, *args, of:false, **options, &block)
          of_object = of && of.is_a?(::String) ? of.safe_constantize : of
          coercer = of_object ? ::Array[of_object] : ::Array
          define_attribute_class_for_type(:array, name, *args, :coerce => coercer, **options, &block)
        end

        def self.boolean_property(name, *args, **options, &block)
          define_attribute_class_for_type(:boolean, name, *args, :coerce => ->(value){!!value}, **options, &block)
        end

        def self.enum_property(name, *args, **options, &block)
          define_attribute_class_for_type(:enum, name, *args, :coerce => true, **options, &block)
        end

        def self.float_property(name, *args, **options, &block)
          define_attribute_class_for_type(:float, name, *args, :coerce => ::Float, **options, &block)
        end

        def self.integer_property(name, *args, **options, &block)
          define_attribute_class_for_type(:integer, name, *args, :coerce => ::Integer, **options, &block)
        end

        def self.string_property(name, *args, **options, &block)
          define_attribute_class_for_type(:string, name, *args, :coerce => ::String, **options, &block)
        end

        def self.struct_property(name, *args, **options, &block)
          define_attribute_class_for_type(:struct, name, *args, :coerce => true, **options, &block)
        end

        def self.to_schema
          ::Trax::Core::Definition.new(
            :source => self.name,
            :name => self.name.demodulize.underscore,
            :type => :struct,
            :fields => self.fields_module.to_schema
          )
        end

        def self.type; :struct end;

        def to_serializable_hash
          _serializable_hash = to_hash

          self.class.fields_module.enums.keys.each do |attribute_name|
            _serializable_hash[attribute_name] = _serializable_hash[attribute_name].try(:to_i)
          end if self.class.fields_module.enums.keys.any?

          _serializable_hash
        end

        class << self
          alias :array :array_property
          alias :boolean :boolean_property
          alias :enum :enum_property
          alias :float :float_property
          alias :integer :integer_property
          alias :struct :struct_property
          alias :string :string_property
        end

        def value
          self
        end

        private

        #By default, strings/int/bool wont get cast to value objects
        #mainly for the sake of performance/avoid unneccessary object allocation
        def self.define_attribute_class_for_type(type_name, property_name, *args, coerce:false, **options, &block)
          name = name.is_a?(Symbol) ? name.to_s : name
          klass_name = "#{fields_module.name.underscore}/#{property_name}".camelize

          attribute_klass = if options.key?(:extend)
            _klass_prototype = options[:extend].constantize.clone
            _klass = ::Trax::Core::NamedClass.new(klass_name, _klass_prototype, :parent_definition => self, &block)
            _klass
          else
            ::Trax::Core::NamedClass.new(klass_name, "::Trax::Core::Types::#{type_name.to_s.classify}".constantize, :parent_definition => self, &block)
          end

          options[:default] = options.key?(:default) ? options[:default] : DEFAULT_VALUES_FOR_PROPERTY_TYPES[__method__]
          property(property_name.to_sym, *args, **options)

          if coerce.is_a?(::Proc)
            coerce_key(property_name.to_sym, &coerce)
          elsif coerce.is_a?(::Array)
            coerce_key(property_name.to_sym, coerce)
          elsif [ ::Integer, ::Float, ::String ].include?(coerce)
            coerce_key(property_name.to_sym, coerce)
          elsif coerce
            coerce_key(property_name.to_sym, attribute_klass)
          end
        end
      end
    end
  end
end
<commit_msg>dont clone as it creates a new anonymous class<commit_after>require 'hashie/extensions/coercion'
require 'hashie/extensions/indifferent_access'
require 'hashie/extensions/dash/indifferent_access'

module Trax
  module Core
    module Types
      class Struct < ::Hashie::Dash
        include ::Hashie::Extensions::Dash::IndifferentAccess
        include ::Hashie::Extensions::Coercion
        include ::Hashie::Extensions::IgnoreUndeclared
        include ::Hashie::Extensions::Dash::PropertyTranslation

        # note that we must explicitly set default or blank values for all properties.
        # It defeats the whole purpose of being a 'struct'
        # if we fail to do so, and it makes our data far more error prone
        DEFAULT_VALUES_FOR_PROPERTY_TYPES = {
          :array_property   => [],
          :boolean_property => nil,
          :float_property   => 0.0,
          :string_property  => "",
          :struct_property  => {},
          :enum_property    => nil,
          :integer_property => nil
        }.with_indifferent_access.freeze

        def self.fields_module
          @fields_module ||= begin
            module_name = "#{self.name}::Fields"
            mod = ::Trax::Core::NamedModule.new(module_name, ::Trax::Core::Fields.clone)
            mod.include(superclass.fields) if superclass.instance_variable_defined?("@fields_module")
            mod
          end
        end

        def self.fields
          fields_module
        end

        def self.array_property(name, *args, of:false, **options, &block)
          of_object = of && of.is_a?(::String) ? of.safe_constantize : of
          coercer = of_object ? ::Array[of_object] : ::Array
          define_attribute_class_for_type(:array, name, *args, :coerce => coercer, **options, &block)
        end

        def self.boolean_property(name, *args, **options, &block)
          define_attribute_class_for_type(:boolean, name, *args, :coerce => ->(value){!!value}, **options, &block)
        end

        def self.enum_property(name, *args, **options, &block)
          define_attribute_class_for_type(:enum, name, *args, :coerce => true, **options, &block)
        end

        def self.float_property(name, *args, **options, &block)
          define_attribute_class_for_type(:float, name, *args, :coerce => ::Float, **options, &block)
        end

        def self.integer_property(name, *args, **options, &block)
          define_attribute_class_for_type(:integer, name, *args, :coerce => ::Integer, **options, &block)
        end

        def self.string_property(name, *args, **options, &block)
          define_attribute_class_for_type(:string, name, *args, :coerce => ::String, **options, &block)
        end

        def self.struct_property(name, *args, **options, &block)
          define_attribute_class_for_type(:struct, name, *args, :coerce => true, **options, &block)
        end

        def self.to_schema
          ::Trax::Core::Definition.new(
            :source => self.name,
            :name => self.name.demodulize.underscore,
            :type => :struct,
            :fields => self.fields_module.to_schema
          )
        end

        def self.type; :struct end;

        def to_serializable_hash
          _serializable_hash = to_hash

          self.class.fields_module.enums.keys.each do |attribute_name|
            _serializable_hash[attribute_name] = _serializable_hash[attribute_name].try(:to_i)
          end if self.class.fields_module.enums.keys.any?

          _serializable_hash
        end

        class << self
          alias :array :array_property
          alias :boolean :boolean_property
          alias :enum :enum_property
          alias :float :float_property
          alias :integer :integer_property
          alias :struct :struct_property
          alias :string :string_property
        end

        def value
          self
        end

        private

        #By default, strings/int/bool wont get cast to value objects
        #mainly for the sake of performance/avoid unneccessary object allocation
        def self.define_attribute_class_for_type(type_name, property_name, *args, coerce:false, **options, &block)
          name = name.is_a?(Symbol) ? name.to_s : name
          klass_name = "#{fields_module.name.underscore}/#{property_name}".camelize

          attribute_klass = if options.key?(:extend)
            _klass_prototype = options[:extend].constantize
            _klass = ::Trax::Core::NamedClass.new(klass_name, _klass_prototype, :parent_definition => self, &block)
            _klass
          else
            ::Trax::Core::NamedClass.new(klass_name, "::Trax::Core::Types::#{type_name.to_s.classify}".constantize, :parent_definition => self, &block)
          end

          options[:default] = options.key?(:default) ? options[:default] : DEFAULT_VALUES_FOR_PROPERTY_TYPES[__method__]
          property(property_name.to_sym, *args, **options)

          if coerce.is_a?(::Proc)
            coerce_key(property_name.to_sym, &coerce)
          elsif coerce.is_a?(::Array)
            coerce_key(property_name.to_sym, coerce)
          elsif [ ::Integer, ::Float, ::String ].include?(coerce)
            coerce_key(property_name.to_sym, coerce)
          elsif coerce
            coerce_key(property_name.to_sym, attribute_klass)
          end
        end
      end
    end
  end
end

{'fraction_non_alphanumeric': 0.11100720097259889, 'fraction_numerical': 0.00037407649864397267, 'mean_word_length': 2.550464807436919, 'pattern_counts': {'":': 2, '<': 9, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, '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': '7365083', 'n_tokens_mistral': 3166, 'n_tokens_neox': 3105, 'n_words': 676}