lib/polyfill.rb in polyfill-0.6.0 vs lib/polyfill.rb in polyfill-0.7.0
- old
+ new
@@ -1,24 +1,25 @@
+require 'ipaddr'
+require 'stringio'
require 'polyfill/version'
require 'polyfill/utils'
-require 'polyfill/v2_3'
-require 'polyfill/v2_4'
module Polyfill
- include V2_3
- include V2_4
-
module Parcel; end
end
-def Polyfill(options) # rubocop:disable Style/MethodName
+def Polyfill(options = {}) # rubocop:disable Style/MethodName
mod = Module.new
+ #
+ # parse options
+ #
objects, others = options.partition { |key,| key[/\A[A-Z]/] }
others = others.to_h
versions = {
+ '2.2' => Polyfill::V2_2,
'2.3' => Polyfill::V2_3,
'2.4' => Polyfill::V2_4
}
desired_version = others.delete(:version) || versions.keys.max
unless versions.keys.include?(desired_version)
@@ -26,99 +27,192 @@
end
versions.reject! do |version_number, _|
version_number > desired_version
end
+ native = others.delete(:native) { false }
+
unless others.empty?
raise ArgumentError, "unknown keyword: #{others.first[0]}"
end
+ #
+ # useful vars
+ #
current_ruby_version = RUBY_VERSION[/\A(\d+\.\d+)/, 1]
+ all_instance_modules = []
- if objects.empty?
- mod.module_eval do
- versions.each do |_, object_module|
- include object_module
- end
- end
- else
- objects.each do |full_name, methods|
- object_module_names = full_name.to_s.split('::')
+ objects.each do |full_name, methods|
+ #
+ # find all polyfills for the object across all versions
+ #
+ object_module_names = full_name.to_s.split('::')
- object_modules = versions
- .map do |version_number, version_module|
- begin
- final_module = object_module_names
- .reduce(version_module) do |current_mod, name|
- current_mod.const_get(name, false)
- end
+ object_modules = versions
+ .map do |version_number, version_module|
+ begin
+ final_module = object_module_names
+ .reduce(version_module) do |current_mod, name|
+ current_mod.const_get(name, false)
+ end
- [version_number, final_module]
- rescue NameError
- nil
- end
+ [version_number, final_module]
+ rescue NameError
+ nil
end
- .compact
+ end
+ .compact
- if object_modules.empty?
- raise ArgumentError, %Q("#{full_name}" is not a valid class or has no updates)
+ if object_modules.empty?
+ raise ArgumentError, %Q("#{full_name}" is not a valid class or has no updates)
+ end
+
+ #
+ # get all class modules and instance modules from the polyfills
+ #
+ class_modules = object_modules.map do |version_number, object_module|
+ begin
+ [version_number, object_module.const_get(:ClassMethods, false).clone]
+ rescue NameError
+ nil
end
+ end.compact
+ instance_modules = object_modules.map do |version_number, object_module|
+ [version_number, object_module.clone]
+ end
- if methods == :all
- mod.module_eval do
- object_modules.each do |version_number, object_module|
- include object_module if version_number > current_ruby_version
- end
- end
+ #
+ # get all requested class and instance methods
+ #
+ if methods != :all && (method_name = methods.find { |method| method !~ /\A[.#]/ })
+ raise ArgumentError, %Q("#{method_name}" must start with a "." if it's a class method or "#" if it's an instance method)
+ end
+
+ all_methods_for = lambda do |modules|
+ modules.flat_map { |_, m| m.instance_methods }.uniq
+ end
+ available_class_methods = all_methods_for.call(class_modules)
+ available_instance_methods = all_methods_for.call(instance_modules)
+
+ select_and_clean = lambda do |leader|
+ methods.select { |method| method.start_with?(leader) }.map { |method| method[1..-1].to_sym }
+ end
+ requested_class_methods = methods == :all ? available_class_methods : select_and_clean.call('.')
+ requested_instance_methods = methods == :all ? available_instance_methods : select_and_clean.call('#')
+
+ unless (leftovers = (requested_class_methods - available_class_methods)).empty?
+ raise ArgumentError, %Q(".#{leftovers.first}" is not a valid method on #{full_name} or has no updates)
+ end
+ unless (leftovers = (requested_instance_methods - available_instance_methods)).empty?
+ raise ArgumentError, %Q("##{leftovers.first}" is not a valid method on #{full_name} or has no updates)
+ end
+
+ #
+ # get the class(es) to refine
+ #
+ base_class = object_modules.first.last.name.sub(/\APolyfill::V\d_\d::/, '')
+ base_classes =
+ case base_class
+ when 'Comparable'
+ %w[Numeric String Time]
+ when 'Enumerable'
+ %w[Array Dir Enumerator Hash IO Range StringIO Struct]
+ when 'Kernel'
+ %w[Object]
else
- methods.each do |method|
- type =
- case method[0]
- when '.'
- :Class
- when '#'
- :Instance
- else
- raise ArgumentError, %Q("#{method}" must start with a "." if it's a class method or "#" if it's an instance method)
- end
+ [base_class]
+ end
- method_name = method[1..-1]
- symbol_conversions = {
- '-' => 'minus', # must come first otherwise it creates a range in the regexp
- '+' => 'plus',
- '@' => '_unary',
- '=' => 'equal',
- '<' => 'lessthan',
- '>' => 'greaterthan',
- '?' => '_q',
- '!' => '_e'
- }
- method_name.gsub!(/[#{symbol_conversions.keys.join}]/o, symbol_conversions)
- method_name.capitalize!
- method_name.gsub!(/_(.)/) { |match| match[1].capitalize }
+ #
+ # refine in class methods
+ #
+ class_modules.each do |version_number, class_module|
+ next if version_number <= current_ruby_version
- method_modules = object_modules
- .map do |version_number, object_module|
- begin
- [version_number, object_module.const_get(type, false).const_get(method_name, false)]
- rescue NameError
- nil
+ class_module.instance_methods.each do |name|
+ class_module.send(:remove_method, name) unless requested_class_methods.include?(name)
+ end
+
+ next if class_module.instance_methods.empty?
+
+ mod.module_exec(requested_class_methods) do |methods_added|
+ base_classes.each do |klass|
+ refine Object.const_get(klass).singleton_class do
+ include class_module
+
+ if native
+ Polyfill::Utils.ignore_warnings do
+ define_method :respond_to? do |name, include_all = false|
+ return true if methods_added.include?(name)
+
+ super(name, include_all)
+ end
+
+ define_method :__send__ do |name, *args, &block|
+ return super(name, *args, &block) unless methods_added.include?(name)
+
+ class_module.instance_method(name).bind(self).call(*args, &block)
+ end
+ alias_method :send, :__send__
end
end
- .compact
-
- if method_modules.empty?
- raise ArgumentError, %Q("#{method}" is not a valid method on #{full_name} or has no updates)
end
+ end
+ end
+ end
- mod.module_eval do
- method_modules.each do |version_number, method_module|
- include method_module if version_number > current_ruby_version
+ #
+ # refine in instance methods
+ #
+ instance_modules.each do |version_number, instance_module|
+ next if version_number <= current_ruby_version
+
+ instance_module.instance_methods.each do |name|
+ instance_module.send(:remove_method, name) unless requested_instance_methods.include?(name)
+ end
+
+ next if instance_module.instance_methods.empty?
+
+ all_instance_modules << instance_module
+
+ mod.module_exec(requested_instance_methods) do |methods_added|
+ base_classes.each do |klass|
+ refine Object.const_get(klass) do
+ include instance_module
+
+ if native
+ Polyfill::Utils.ignore_warnings do
+ define_method :respond_to? do |name, include_all = false|
+ return super(name, include_all) unless methods_added.include?(name)
+
+ true
+ end
+
+ define_method :__send__ do |name, *args, &block|
+ return super(name, *args, &block) unless methods_added.include?(name)
+
+ instance_module.instance_method(name).bind(self).call(*args, &block)
+ end
+ alias_method :send, :__send__
+ end
end
end
end
end
end
end
+ #
+ # make sure the includes get added if this module is included
+ #
+ mod.singleton_class.send(:define_method, :included) do |base|
+ all_instance_modules.each do |instance_module|
+ base.include instance_module
+ end
+ end
+
Polyfill::Parcel.const_set("O#{mod.object_id}", mod)
end
+
+require 'polyfill/v2_2'
+require 'polyfill/v2_3'
+require 'polyfill/v2_4'