<commit_before>require 'blankslate'

# Copyright 2010 <NAME> <<EMAIL>> MIT License
#
# For detailed usage notes, please see README.markdown
#
class Metavariable < BlankSlate

  # When you pass an argument with & in ruby, you're actually calling #to_proc
  # on the object. So it's Symbol#to_proc that makes the &:to_s trick work,
  # and Metavariable#to_proc that makes &X work.
  attr_reader :to_proc

  def initialize(&block)
    @to_proc = block || lambda{|x| x}
  end

  # Each time a method is called on a Metavariable, we want to create a new
  # Metavariable that is like the last but does something more.
  #
  # The end result of calling X.one.two will be like:
  #
  # lambda{|x|
  #   (lambda{|x|
  #     (lambda{|x| x}).call(x).one
  #   }).call(x).two
  # }
  #
  def method_missing(name, *args, &block)
    mv = Metavariable.new { |x| @to_proc.call(x).send(name, *args, &block) }
    Metavariable.temporarily_monkeypatch(args.last.class, mv) if name.to_s =~ /[^!=<>]=$/
    mv
  end

  private

  # In order to support assignment via &X (expressions of the form &X['one'] = 2),
  # we need to add 2.to_proc (because assignment in ruby always returns the operand)
  #
  # Luckily, we only need to do this for a very short time.
  #
  # When given an expression such as:
  #
  #  ary.map(&X[args(a)] = :two)
  #
  # the order of execution is:
  #  args(a)
  #  X[_] = :two    \_ need to patch here
  #  :two.to_proc  _/  and  un-patch here
  #  ary.map &_
  #
  # There is a risk if someone uses an amp-less X, and assigns something with to_proc
  # (most likely a symbol), and then uses .map(&:to_i), as &:to_i will return the
  # behaviour of their metavariable.
  #
  # There are other things that might notice us doing this, if people are listening
  # on various method_added hooks, or have overridden class_eval, etc. But I'm not
  # too worried.
  #
  def self.temporarily_monkeypatch(klass, mv)
    klass.send :class_variable_set, :'@@metavariable', mv
    klass.class_eval do

      alias_method(:to_proc_without_metavariable, :to_proc) rescue nil
      def to_proc
        self.class.class_eval do

          undef to_proc
          alias_method(:to_proc, :to_proc_without_metavariable) rescue nil
          undef to_proc_without_metavariable rescue nil

          # Remove the metavariable from the class and return its proc
          remove_class_variable(:'@@metavariable').to_proc
        end
      end
    end
  end
end

X = Metavariable.new<commit_msg>Avoid setting a temporary class variable<commit_after>require 'blankslate'

# Copyright 2010 <NAME> <<EMAIL>> MIT License
#
# For detailed usage notes, please see README.markdown
#
class Metavariable < BlankSlate

  # When you pass an argument with & in ruby, you're actually calling #to_proc
  # on the object. So it's Symbol#to_proc that makes the &:to_s trick work,
  # and Metavariable#to_proc that makes &X work.
  attr_reader :to_proc

  def initialize(&block)
    @to_proc = block || lambda{|x| x}
  end

  # Each time a method is called on a Metavariable, we want to create a new
  # Metavariable that is like the last but does something more.
  #
  # The end result of calling X.one.two will be like:
  #
  # lambda{|x|
  #   (lambda{|x|
  #     (lambda{|x| x}).call(x).one
  #   }).call(x).two
  # }
  #
  def method_missing(name, *args, &block)
    mv = Metavariable.new { |x| @to_proc.call(x).send(name, *args, &block) }
    Metavariable.temporarily_monkeypatch(args.last.class, mv) if name.to_s =~ /[^!=<>]=$/
    mv
  end

  private

  # In order to support assignment via &X (expressions of the form &X['one'] = 2),
  # we need to add 2.to_proc (because assignment in ruby always returns the operand)
  #
  # Luckily, we only need to do this for a very short time.
  #
  # When given an expression such as:
  #
  #  ary.map(&X[args(a)] = :two)
  #
  # the order of execution is:
  #  args(a)
  #  X[_] = :two    \_ need to patch here
  #  :two.to_proc  _/  and  un-patch here
  #  ary.map &_
  #
  # There is a risk if someone uses an amp-less X, and assigns something with to_proc
  # (most likely a symbol), and then uses .map(&:to_i), as &:to_i will return the
  # behaviour of their metavariable.
  #
  # There are other things that might notice us doing this, if people are listening
  # on various method_added hooks, or have overridden class_eval, etc. But I'm not
  # too worried.
  #
  def self.temporarily_monkeypatch(klass, mv)
    klass.class_eval do

      alias_method(:to_proc_without_metavariable, :to_proc) rescue nil
      define_method(:to_proc) do
        klass.class_eval do

          undef :to_proc
          alias_method(:to_proc, :to_proc_without_metavariable) rescue nil
          undef :to_proc_without_metavariable rescue nil

          mv.to_proc
        end
      end
    end
  end
end

X = Metavariable.new
{'fraction_non_alphanumeric': 0.1003725165562914, 'fraction_numerical': 0.0024834437086092716, 'mean_word_length': 3.0511316010058676, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 11, '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': '5233116', 'n_tokens_mistral': 1720, 'n_tokens_neox': 1623, 'n_words': 552}