<commit_before>require 'dslh/version'
require 'stringio'

class Dslh
  INDENT_SPACES = '  '

  class << self
    def eval(expr_or_options = nil, options = nil, &block)
      if options and not options.kind_of?(Hash)
        raise TypeError, "wrong argument type #{options.class} (expected Hash)"
      end

      expr = nil
      options ||= {}

      if expr_or_options
        case expr_or_options
        when String
          expr = expr_or_options
        when Hash
          options.update(expr_or_options)
        else
          raise TypeError, "wrong argument type #{expr_or_options.class} (expected String or Hash)"
        end
      end

      self.new(options).eval(expr, &block)
    end

    def deval(hash, options = {}, &block)
      if [hash, options].all? {|i| not i.kind_of?(Hash) }
        raise TypeError, "wrong argument type #{options.class} (expected Hash)"
      end

      self.new(options).deval(hash, &block)
    end
  end # of class methods

  def initialize(options = {})
    @options = options.dup
  end

  def eval(expr = nil, &block)
    retval = {}
    scope = Scope.new
    scope.instance_variable_set(:@__options__, @options)
    scope.instance_variable_set(:@__hash__, retval)
    @options[:scope_hook].call(scope) if @options[:scope_hook]

    if expr
      eval_args = [expr]

      [:filename, :lineno].each do |k|
        eval_args << @options[k] if @options[k]
      end

      scope.instance_eval(*eval_args)
    else
      scope.instance_eval(&block)
    end

    return retval
  end

  def deval(hash)
    buf = StringIO.new
    deval0(hash, 0, buf)
    buf.string
  end

  private

  def deval0(hash, depth, buf)
    hash.each do |key, value|
      key_conv = @options[:key_conv] || @options[:conv]
      value_conv = @options[:value_conv] || @options[:conv]

      key = key_conv.call(key) if key_conv
      indent = (INDENT_SPACES * depth)

      buf.print(indent + key)

      case value
      when Hash
        buf.puts(' do')
        deval0(value, depth + 1, buf)
        buf.puts(indent + 'end')
      when Array
        buf.puts ' ' + value.map {|v|
          v = value_conv.call(v) if value_conv
          v.inspect
        }.join(', ')
      else
        value = value_conv.call(value) if value_conv
        buf.puts ' ' + value.inspect
      end
    end
  end

  class Scope
    def method_missing(method_name, *args, &block)
      key_conv = @__options__[:key_conv] || @__options__[:conv]
      value_conv = @__options__[:value_conv] || @__options__[:conv]

      nested_hash = nil

      if block
        hash_orig = @__hash__
        @__hash__ = {}
        self.instance_eval(&block)
        nested_hash = @__hash__
        @__hash__ = hash_orig
      end

      method_name = key_conv.call(method_name) if key_conv<commit_msg>Because drunk<commit_after>require 'dslh/version'
require 'stringio'

class Dslh
  INDENT_SPACES = '  '

  class << self
    def eval(expr_or_options = nil, options = nil, &block)
      if options and not options.kind_of?(Hash)
        raise TypeError, "wrong argument type #{options.class} (expected Hash)"
      end

      expr = nil
      options ||= {}

      if expr_or_options
        case expr_or_options
        when String
          expr = expr_or_options
        when Hash
          options.update(expr_or_options)
        else
          raise TypeError, "wrong argument type #{expr_or_options.class} (expected String or Hash)"
        end
      end

      self.new(options).eval(expr, &block)
    end

    def deval(hash, options = {}, &block)
      if [hash, options].all? {|i| not i.kind_of?(Hash) }
        raise TypeError, "wrong argument type #{options.class} (expected Hash)"
      end

      self.new(options).deval(hash, &block)
    end
  end # of class methods

  def initialize(options = {})
    @options = options.dup
  end

  def eval(expr = nil, &block)
    retval = {}
    scope = Scope.new
    scope.instance_variable_set(:@__options__, @options)
    scope.instance_variable_set(:@__hash__, retval)
    @options[:scope_hook].call(scope) if @options[:scope_hook]

    if expr
      eval_args = [expr]

      [:filename, :lineno].each do |k|
        eval_args << @options[k] if @options[k]
      end

      scope.instance_eval(*eval_args)
    else
      scope.instance_eval(&block)
    end

    return retval
  end

  def deval(hash)
    buf = StringIO.new
    deval0(hash, 0, buf)
    buf.string
  end

  private

  def deval0(hash, depth, buf)
    indent = (INDENT_SPACES * depth)
    key_conv = @options[:key_conv] || @options[:conv]
    value_conv = @options[:value_conv] || @options[:conv]
    exclude_key = @options[:exclude_key] || proc {|k| k.to_s !~ /\A[_a-z]\w+\Z/i }
    keys = key_conv ? hash.keys.map {|k| key_conv.call(k) } : hash.keys

    if exclude_key and keys.any? {|k| exclude_key.call(k) }
      buf.puts '(' + hash.inspect + ')'
      return
    end

    hash.each do |key, value|
      key = key_conv.call(key) if key_conv
      buf.print(indent + key)

      case value
      when Hash
        if exclude_key and value.keys.any? {|k| exclude_key.call(k) }
          buf.puts '(' + value.inspect + ')'
        else
          buf.puts(' do')
          deval0(value, depth + 1, buf)
          buf.puts(indent + 'end')
        end
      when Array
        buf.puts ' ' + value.map {|v|
          v = value_conv.call(v) if value_conv

          if v.kind_of?(Hash)
            '(' + v.inspect + ')'
          else
            v.inspect
          end
        }.join(', ')
      else
        value = value_conv.call(value) if value_conv
        buf.puts ' ' + value.inspect
      end
    end
  end

  class Scope
    def method_missing(method_name, *args, &block)
      key_conv = @__options__[:key_conv] || @__options__[:conv]
      value_conv = @__options__[:value_conv] || @__options__[:conv]

      nested_hash = nil

      if block
        hash_orig = @__hash__
        @__hash__ = {}
        self.instance_eval(&block)
        nested_hash = @__hash__
        @__hash__ = hash_orig
      end

      method_name = key_conv.call(method_name) if key_conv
{'fraction_non_alphanumeric': 0.116737915159487, 'fraction_numerical': 0.001644195988161789, 'mean_word_length': 2.270430107526882, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 3, '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': '3651307', 'n_tokens_mistral': 2055, 'n_tokens_neox': 2002, 'n_words': 475}