Module | Backports |
In: |
lib/backports/tools.rb
|
Methods used internally by the backports.
Undefined | = | Object.new | Used internally to make it easy to deal with optional arguments (from Rubinius) |
Used internally. Safe alias_method that will only alias if the source exists and destination doesn‘t
# File lib/backports/tools.rb, line 249 249: def self.alias_method(mod, new_name, old_name) 250: mod.class_eval do 251: alias_method new_name, old_name if method_defined?(old_name) and not method_defined?(new_name) 252: end 253: end
Modified to avoid polluting Module if so desired (from Rails)
# File lib/backports/tools.rb, line 173 173: def self.alias_method_chain(mod, target, feature) 174: mod.class_eval do 175: # Strip out punctuation on predicates or bang methods since 176: # e.g. target?_without_feature is not a valid method name. 177: aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 178: yield(aliased_target, punctuation) if block_given? 179: 180: with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}" 181: 182: alias_method without_method, target 183: alias_method target, with_method 184: 185: case 186: when public_method_defined?(without_method) 187: public target 188: when protected_method_defined?(without_method) 189: protected target 190: when private_method_defined?(without_method) 191: private target 192: end 193: end 194: end
Helper method to coerce a value into a specific class. Raises a TypeError if the coercion fails or the returned value is not of the right class. (from Rubinius)
# File lib/backports/tools.rb, line 200 200: def self.coerce_to(obj, cls, meth) 201: return obj if obj.kind_of?(cls) 202: 203: begin 204: ret = obj.__send__(meth) 205: rescue Exception => e 206: raise TypeError, "Coercion error: #{obj.inspect}.#{meth} => #{cls} failed:\n" \ 207: "(#{e.message})" 208: end 209: raise TypeError, "Coercion error: obj.#{meth} did NOT return a #{cls} (was #{ret.class})" unless ret.kind_of? cls 210: ret 211: end
# File lib/backports/tools.rb, line 217 217: def self.coerce_to_ary(obj) 218: coerce_to(obj, Array, :to_ary) 219: end
Checks for a failed comparison (in which case it throws an ArgumentError) Additionally, it maps any negative value to -1 and any positive value to +1 (from Rubinius)
# File lib/backports/tools.rb, line 236 236: def self.coerce_to_comparison(a, b, cmp = (a <=> b)) 237: raise ArgumentError, "comparison of #{a} with #{b} failed" if cmp.nil? 238: return 1 if cmp > 0 239: return -1 if cmp < 0 240: 0 241: end
# File lib/backports/tools.rb, line 213 213: def self.coerce_to_int(obj) 214: coerce_to(obj, Integer, :to_int) 215: end
# File lib/backports/tools.rb, line 221 221: def self.coerce_to_str(obj) 222: coerce_to(obj, String, :to_str) 223: end
Used internally to combine {IO|File} options hash into mode (String or Integer)
# File lib/backports/tools.rb, line 264 264: def self.combine_mode_and_option(mode = nil, options = Backports::Undefined) 265: # Can't backport autoclose, {internal|external|}encoding 266: mode, options = nil, mode if mode.is_a?(Hash) and options == Backports::Undefined 267: options = {} if options == Backports::Undefined 268: if mode && options[:mode] 269: raise ArgumentError, "mode specified twice" 270: end 271: mode ||= options[:mode] || "r" 272: if options[:textmode] || options[:binmode] 273: text = options[:textmode] || (mode.is_a?(String) && mode =~ /t/) 274: bin = options[:binmode] || (mode.is_a?(String) ? mode =~ /b/ : mode & File::Constants::BINARY != 0) 275: if text && bin 276: raise ArgumentError, "both textmode and binmode specified" 277: end 278: case 279: when !options[:binmode] 280: when mode.is_a?(String) 281: mode.insert(1, "b") 282: else 283: mode |= File::Constants::BINARY 284: end 285: end 286: mode 287: end
Used internally to combine {IO|File} options hash into mode (String or Integer) and perm
# File lib/backports/tools.rb, line 290 290: def self.combine_mode_perm_and_option(mode = nil, perm = Backports::Undefined, options = Backports::Undefined) 291: mode, options = nil, mode if mode.is_a?(Hash) and perm == Backports::Undefined 292: perm, options = nil, perm if perm.is_a?(Hash) and options == Backports::Undefined 293: perm = nil if perm == Backports::Undefined 294: options = {} if options == Backports::Undefined 295: if perm && options[:perm] 296: raise ArgumentError, "perm specified twice" 297: end 298: [combine_mode_and_option(mode, options), perm || options[:perm]] 299: end
Metaprogramming utility to convert all file arguments to paths
# File lib/backports/tools.rb, line 148 148: def self.convert_all_arguments_to_path(mod, skip, *methods) 149: methods.each do |selector| 150: unless mod.method_defined? selector 151: warn "#{mod}##{selector} is not defined, so arguments can't converted to path" 152: next 153: end 154: first_args = (1..skip).map{|i| "arg_#{i}"}.join(",") + (skip > 0 ? "," : "") 155: alias_method_chain(mod, selector, :potential_path_arguments) do |aliased_target, punctuation| 156: mod.module_eval "def \#{aliased_target}_with_potential_path_arguments\#{punctuation}(\#{first_args}*files, &block)\nfiles = files.map{|f| Backports.convert_to_path f}\n\#{aliased_target}_without_potential_path_arguments\#{punctuation}(\#{first_args}*files, &block)\nend\n" 157: end 158: end 159: end
Metaprogramming utility to convert the first file argument to path
# File lib/backports/tools.rb, line 121 121: def self.convert_first_argument_to_path(mod, *methods) 122: methods.each do |selector| 123: unless mod.method_defined? selector 124: warn "#{mod}##{selector} is not defined, so argument can't converted to path" 125: next 126: end 127: arity = mod.instance_method(selector).arity 128: last_arg = [] 129: if arity < 0 130: last_arg = ["*rest"] 131: arity = -1-arity 132: end 133: arg_sequence = (["file"] + (1...arity).map{|i| "arg_#{i}"} + last_arg + ["&block"]).join(", ") 134: 135: alias_method_chain(mod, selector, :potential_path_argument) do |aliased_target, punctuation| 136: mod.module_eval "def \#{aliased_target}_with_potential_path_argument\#{punctuation}(\#{arg_sequence})\nfile = Backports.convert_to_path(file)\n\#{aliased_target}_without_potential_path_argument\#{punctuation}(\#{arg_sequence})\nend\n" 137: end 138: end 139: end
# File lib/backports/tools.rb, line 167 167: def self.convert_to_path(file_or_path) 168: file_or_path.respond_to?(:to_path) ? file_or_path.to_path : file_or_path 169: end
Metaprogramming utility to make block optional. Tests first if block is already optional when given options
# File lib/backports/tools.rb, line 83 83: def self.make_block_optional(mod, *methods) 84: options = methods.last.is_a?(Hash) ? methods.pop : {} 85: methods.each do |selector| 86: unless mod.method_defined? selector 87: warn "#{mod}##{selector} is not defined, so block can't be made optional" 88: next 89: end 90: unless options[:force] 91: # Check if needed 92: test_on = options.fetch(:test_on) 93: result = begin 94: test_on.send(selector, *options.fetch(:arg, [])) 95: rescue LocalJumpError 96: false 97: end 98: next if result.class.name =~ /Enumerator$/ 99: end 100: arity = mod.instance_method(selector).arity 101: last_arg = [] 102: if arity < 0 103: last_arg = ["*rest"] 104: arity = -1-arity 105: end 106: arg_sequence = ((0...arity).map{|i| "arg_#{i}"} + last_arg + ["&block"]).join(", ") 107: 108: alias_method_chain(mod, selector, :optional_block) do |aliased_target, punctuation| 109: mod.module_eval "def \#{aliased_target}_with_optional_block\#{punctuation}(\#{arg_sequence})\nreturn to_enum(:\#{aliased_target}_without_optional_block\#{punctuation}, \#{arg_sequence}) unless block_given?\n\#{aliased_target}_without_optional_block\#{punctuation}(\#{arg_sequence})\nend\n" 110: end 111: end 112: end
Adapted from Pragmatic‘s "Programming Ruby" (since their version was buggy…)
# File lib/backports/tools.rb, line 4 4: def self.require_relative(relative_feature) 5: file = caller.first.split(/:\d/,2).first 6: if /\A\((.*)\)/ =~ file # eval, etc. 7: raise LoadError, "require_relative is called in #{$1}" 8: end 9: require File.expand_path(relative_feature, File.dirname(file)) 10: end
# File lib/backports/tools.rb, line 12 12: def self.require_relative_dir(relative_dir) 13: dir = File.expand_path(relative_dir, File.dirname(caller.first.split(/:\d/,2).first)) 14: Dir.entries(dir). 15: map{|f| Regexp.last_match(1) if /^(.*)\.rb$/ =~ f}. 16: compact. 17: sort. 18: each do |f| 19: require File.expand_path(f, dir) 20: end 21: end
Used internally to propagate lambda?
# File lib/backports/tools.rb, line 256 256: def self.track_lambda(from, to, default = false) 257: is_lambda = from.instance_variable_get :@is_lambda 258: is_lambda = default if is_lambda.nil? 259: to.instance_variable_set :@is_lambda, is_lambda 260: to 261: end
# File lib/backports/tools.rb, line 225 225: def self.try_convert(obj, cls, meth) 226: return obj if obj.kind_of?(cls) 227: return nil unless obj.respond_to?(meth) 228: ret = obj.__send__(meth) 229: raise TypeError, "Coercion error: obj.#{meth} did NOT return a #{cls} (was #{ret.class})" unless ret.nil? || ret.kind_of?(cls) 230: ret 231: end
# File lib/backports/tools.rb, line 301 301: def self.write(binary, filename, string, offset, options) 302: offset, options = nil, offset if Hash === offset and options == Backports::Undefined 303: options = {} if options == Backports::Undefined 304: options = {:mode => offset.nil? ? "w" : "r+"}.merge(options) 305: args = options[:open_args] || [options] 306: File.open(filename, *args) do |f| 307: f.binmode if binary && f.respond_to?(:binmode) 308: f.seek(offset) unless offset.nil? 309: f.write(string) 310: end 311: end