robsharp-extlib 0.9.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/.autotest +21 -0
  2. data/.document +5 -0
  3. data/.gitignore +22 -0
  4. data/LICENSE +47 -0
  5. data/README.rdoc +17 -0
  6. data/Rakefile +28 -0
  7. data/VERSION +1 -0
  8. data/extlib.gemspec +147 -0
  9. data/lib/extlib.rb +50 -0
  10. data/lib/extlib/array.rb +38 -0
  11. data/lib/extlib/assertions.rb +8 -0
  12. data/lib/extlib/blank.rb +89 -0
  13. data/lib/extlib/boolean.rb +11 -0
  14. data/lib/extlib/byte_array.rb +6 -0
  15. data/lib/extlib/class.rb +179 -0
  16. data/lib/extlib/datetime.rb +29 -0
  17. data/lib/extlib/dictionary.rb +433 -0
  18. data/lib/extlib/hash.rb +450 -0
  19. data/lib/extlib/hook.rb +407 -0
  20. data/lib/extlib/inflection.rb +442 -0
  21. data/lib/extlib/lazy_array.rb +453 -0
  22. data/lib/extlib/lazy_module.rb +18 -0
  23. data/lib/extlib/logger.rb +198 -0
  24. data/lib/extlib/mash.rb +157 -0
  25. data/lib/extlib/module.rb +51 -0
  26. data/lib/extlib/nil.rb +5 -0
  27. data/lib/extlib/numeric.rb +5 -0
  28. data/lib/extlib/object.rb +178 -0
  29. data/lib/extlib/object_space.rb +13 -0
  30. data/lib/extlib/pathname.rb +20 -0
  31. data/lib/extlib/pooling.rb +235 -0
  32. data/lib/extlib/rubygems.rb +38 -0
  33. data/lib/extlib/simple_set.rb +66 -0
  34. data/lib/extlib/string.rb +176 -0
  35. data/lib/extlib/struct.rb +17 -0
  36. data/lib/extlib/symbol.rb +21 -0
  37. data/lib/extlib/time.rb +44 -0
  38. data/lib/extlib/try_dup.rb +44 -0
  39. data/lib/extlib/virtual_file.rb +10 -0
  40. data/spec/array_spec.rb +40 -0
  41. data/spec/blank_spec.rb +86 -0
  42. data/spec/byte_array_spec.rb +8 -0
  43. data/spec/class_spec.rb +158 -0
  44. data/spec/datetime_spec.rb +22 -0
  45. data/spec/hash_spec.rb +536 -0
  46. data/spec/hook_spec.rb +1235 -0
  47. data/spec/inflection/plural_spec.rb +565 -0
  48. data/spec/inflection/singular_spec.rb +498 -0
  49. data/spec/inflection_extras_spec.rb +111 -0
  50. data/spec/lazy_array_spec.rb +1961 -0
  51. data/spec/lazy_module_spec.rb +38 -0
  52. data/spec/mash_spec.rb +312 -0
  53. data/spec/module_spec.rb +71 -0
  54. data/spec/object_space_spec.rb +10 -0
  55. data/spec/object_spec.rb +114 -0
  56. data/spec/pooling_spec.rb +511 -0
  57. data/spec/rcov.opts +6 -0
  58. data/spec/simple_set_spec.rb +58 -0
  59. data/spec/spec.opts +4 -0
  60. data/spec/spec_helper.rb +7 -0
  61. data/spec/string_spec.rb +222 -0
  62. data/spec/struct_spec.rb +13 -0
  63. data/spec/symbol_spec.rb +9 -0
  64. data/spec/time_spec.rb +31 -0
  65. data/spec/try_call_spec.rb +74 -0
  66. data/spec/try_dup_spec.rb +46 -0
  67. data/spec/virtual_file_spec.rb +22 -0
  68. data/tasks/ci.rake +1 -0
  69. data/tasks/metrics.rake +36 -0
  70. data/tasks/spec.rake +25 -0
  71. data/tasks/yard.rake +9 -0
  72. data/tasks/yardstick.rake +19 -0
  73. metadata +198 -0
@@ -0,0 +1,18 @@
1
+ class LazyModule < Module
2
+ def self.new(&blk)
3
+ # passing no-op block overrides &blk
4
+ m = super{ }
5
+ class << m
6
+ include ClassMethods
7
+ end
8
+ m.lazy_evaluated_body = blk
9
+ m
10
+ end
11
+
12
+ module ClassMethods
13
+ attr_accessor :lazy_evaluated_body
14
+ def included(host)
15
+ host.class_eval(&@lazy_evaluated_body)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,198 @@
1
+ require "time" # httpdate
2
+ # ==== Public Extlib Logger API
3
+ #
4
+ # To replace an existing logger with a new one:
5
+ # Extlib::Logger.set_log(log{String, IO},level{Symbol, String})
6
+ #
7
+ # Available logging levels are
8
+ # Extlib::Logger::{ Fatal, Error, Warn, Info, Debug }
9
+ #
10
+ # Logging via:
11
+ # Extlib.logger.fatal(message<String>,&block)
12
+ # Extlib.logger.error(message<String>,&block)
13
+ # Extlib.logger.warn(message<String>,&block)
14
+ # Extlib.logger.info(message<String>,&block)
15
+ # Extlib.logger.debug(message<String>,&block)
16
+ #
17
+ # Logging with autoflush:
18
+ # Extlib.logger.fatal!(message<String>,&block)
19
+ # Extlib.logger.error!(message<String>,&block)
20
+ # Extlib.logger.warn!(message<String>,&block)
21
+ # Extlib.logger.info!(message<String>,&block)
22
+ # Extlib.logger.debug!(message<String>,&block)
23
+ #
24
+ # Flush the buffer to
25
+ # Extlib.logger.flush
26
+ #
27
+ # Remove the current log object
28
+ # Extlib.logger.close
29
+ #
30
+ # ==== Private Extlib Logger API
31
+ #
32
+ # To initialize the logger you create a new object, proxies to set_log.
33
+ # Extlib::Logger.new(log{String, IO},level{Symbol, String})
34
+ module Extlib
35
+
36
+ class << self
37
+ attr_accessor :logger
38
+ end
39
+
40
+ class Logger
41
+
42
+ attr_accessor :level
43
+ attr_accessor :delimiter
44
+ attr_accessor :auto_flush
45
+ attr_reader :buffer
46
+ attr_reader :log
47
+ attr_reader :init_args
48
+
49
+ # ==== Notes
50
+ # Ruby (standard) logger levels:
51
+ # :fatal:: An unhandleable error that results in a program crash
52
+ # :error:: A handleable error condition
53
+ # :warn:: A warning
54
+ # :info:: generic (useful) information about system operation
55
+ # :debug:: low-level information for developers
56
+ Levels =
57
+ {
58
+ :fatal => 7,
59
+ :error => 6,
60
+ :warn => 4,
61
+ :info => 3,
62
+ :debug => 0
63
+ }
64
+
65
+ private
66
+
67
+ # Readies a log for writing.
68
+ #
69
+ # ==== Parameters
70
+ # log<IO, String>:: Either an IO object or a name of a logfile.
71
+ def initialize_log(log)
72
+ close if @log # be sure that we don't leave open files laying around.
73
+
74
+ if log.respond_to?(:write)
75
+ @log = log
76
+ elsif File.exist?(log)
77
+ @log = open(log, (File::WRONLY | File::APPEND))
78
+ @log.sync = true
79
+ else
80
+ FileUtils.mkdir_p(File.dirname(log)) unless File.directory?(File.dirname(log))
81
+ @log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
82
+ @log.sync = true
83
+ @log.write("#{Time.now.httpdate} #{delimiter} info #{delimiter} Logfile created\n")
84
+ end
85
+ end
86
+
87
+ public
88
+
89
+ # To initialize the logger you create a new object, proxies to set_log.
90
+ #
91
+ # ==== Parameters
92
+ # *args:: Arguments to create the log from. See set_logs for specifics.
93
+ def initialize(*args)
94
+ @init_args = args
95
+ set_log(*args)
96
+ end
97
+
98
+ # Replaces an existing logger with a new one.
99
+ #
100
+ # ==== Parameters
101
+ # log<IO, String>:: Either an IO object or a name of a logfile.
102
+ # log_level<~to_sym>::
103
+ # The log level from, e.g. :fatal or :info. Defaults to :error in the
104
+ # production environment and :debug otherwise.
105
+ # delimiter<String>::
106
+ # Delimiter to use between message sections. Defaults to " ~ ".
107
+ # auto_flush<Boolean>::
108
+ # Whether the log should automatically flush after new messages are
109
+ # added. Defaults to false.
110
+ def set_log(log, log_level = nil, delimiter = " ~ ", auto_flush = false)
111
+ if log_level && Levels[log_level.to_sym]
112
+ @level = Levels[log_level.to_sym]
113
+ else
114
+ @level = Levels[:debug]
115
+ end
116
+ @buffer = []
117
+ @delimiter = delimiter
118
+ @auto_flush = auto_flush
119
+
120
+ initialize_log(log)
121
+ end
122
+
123
+ # Flush the entire buffer to the log object.
124
+ def flush
125
+ return unless @buffer.size > 0
126
+ @log.write(@buffer.slice!(0..-1).join)
127
+ end
128
+
129
+ # Close and remove the current log object.
130
+ def close
131
+ flush
132
+ @log.close if @log.respond_to?(:close) && !@log.tty?
133
+ @log = nil
134
+ end
135
+
136
+ # Appends a message to the log. The methods yield to an optional block and
137
+ # the output of this block will be appended to the message.
138
+ #
139
+ # ==== Parameters
140
+ # string<String>:: The message to be logged. Defaults to nil.
141
+ #
142
+ # ==== Returns
143
+ # String:: The resulting message added to the log file.
144
+ def <<(string = nil)
145
+ message = ""
146
+ message << delimiter
147
+ message << string if string
148
+ message << "\n" unless message[-1] == ?\n
149
+ @buffer << message
150
+ flush if @auto_flush
151
+
152
+ message
153
+ end
154
+ alias :push :<<
155
+
156
+ # Generate the logging methods for Extlib.logger for each log level.
157
+ Levels.each_pair do |name, number|
158
+ class_eval <<-LEVELMETHODS, __FILE__, __LINE__
159
+
160
+ # Appends a message to the log if the log level is at least as high as
161
+ # the log level of the logger.
162
+ #
163
+ # ==== Parameters
164
+ # string<String>:: The message to be logged. Defaults to nil.
165
+ #
166
+ # ==== Returns
167
+ # self:: The logger object for chaining.
168
+ def #{name}(message = nil)
169
+ self << message if #{number} >= level
170
+ self
171
+ end
172
+
173
+ # Appends a message to the log if the log level is at least as high as
174
+ # the log level of the logger. The bang! version of the method also auto
175
+ # flushes the log buffer to disk.
176
+ #
177
+ # ==== Parameters
178
+ # string<String>:: The message to be logged. Defaults to nil.
179
+ #
180
+ # ==== Returns
181
+ # self:: The logger object for chaining.
182
+ def #{name}!(message = nil)
183
+ self << message if #{number} >= level
184
+ flush if #{number} >= level
185
+ self
186
+ end
187
+
188
+ # ==== Returns
189
+ # Boolean:: True if this level will be logged by this logger.
190
+ def #{name}?
191
+ #{number} >= level
192
+ end
193
+ LEVELMETHODS
194
+ end
195
+
196
+ end
197
+
198
+ end
@@ -0,0 +1,157 @@
1
+ require 'extlib/hash'
2
+
3
+ # This class has dubious semantics and we only have it so that people can write
4
+ # params[:key] instead of params['key'].
5
+ class Mash < Hash
6
+
7
+ # @param constructor<Object>
8
+ # The default value for the mash. Defaults to an empty hash.
9
+ #
10
+ # @details [Alternatives]
11
+ # If constructor is a Hash, a new mash will be created based on the keys of
12
+ # the hash and no default value will be set.
13
+ def initialize(constructor = {})
14
+ if constructor.is_a?(Hash)
15
+ super()
16
+ update(constructor)
17
+ else
18
+ super(constructor)
19
+ end
20
+ end
21
+
22
+ # @param key<Object> The default value for the mash. Defaults to nil.
23
+ #
24
+ # @details [Alternatives]
25
+ # If key is a Symbol and it is a key in the mash, then the default value will
26
+ # be set to the value matching the key.
27
+ def default(key = nil)
28
+ if key.is_a?(Symbol) && include?(key = key.to_s)
29
+ self[key]
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
36
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
37
+
38
+ # @param key<Object> The key to set.
39
+ # @param value<Object>
40
+ # The value to set the key to.
41
+ #
42
+ # @see Mash#convert_key
43
+ # @see Mash#convert_value
44
+ def []=(key, value)
45
+ regular_writer(convert_key(key), convert_value(value))
46
+ end
47
+
48
+ # @param other_hash<Hash>
49
+ # A hash to update values in the mash with. The keys and the values will be
50
+ # converted to Mash format.
51
+ #
52
+ # @return [Mash] The updated mash.
53
+ def update(other_hash)
54
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
55
+ self
56
+ end
57
+
58
+ alias_method :merge!, :update
59
+
60
+ # @param key<Object> The key to check for. This will be run through convert_key.
61
+ #
62
+ # @return [Boolean] True if the key exists in the mash.
63
+ def key?(key)
64
+ super(convert_key(key))
65
+ end
66
+
67
+ # def include? def has_key? def member?
68
+ alias_method :include?, :key?
69
+ alias_method :has_key?, :key?
70
+ alias_method :member?, :key?
71
+
72
+ # @param key<Object> The key to fetch. This will be run through convert_key.
73
+ # @param *extras<Array> Default value.
74
+ #
75
+ # @return [Object] The value at key or the default value.
76
+ def fetch(key, *extras)
77
+ super(convert_key(key), *extras)
78
+ end
79
+
80
+ # @param *indices<Array>
81
+ # The keys to retrieve values for. These will be run through +convert_key+.
82
+ #
83
+ # @return [Array] The values at each of the provided keys
84
+ def values_at(*indices)
85
+ indices.collect {|key| self[convert_key(key)]}
86
+ end
87
+
88
+ # @param hash<Hash> The hash to merge with the mash.
89
+ #
90
+ # @return [Mash] A new mash with the hash values merged in.
91
+ def merge(hash)
92
+ self.dup.update(hash)
93
+ end
94
+
95
+ # @param key<Object>
96
+ # The key to delete from the mash.\
97
+ def delete(key)
98
+ super(convert_key(key))
99
+ end
100
+
101
+ # @param *rejected<Array[(String, Symbol)] The mash keys to exclude.
102
+ #
103
+ # @return [Mash] A new mash without the selected keys.
104
+ #
105
+ # @example
106
+ # { :one => 1, :two => 2, :three => 3 }.except(:one)
107
+ # #=> { "two" => 2, "three" => 3 }
108
+ def except(*keys)
109
+ super(*keys.map {|k| convert_key(k)})
110
+ end
111
+
112
+ # Used to provide the same interface as Hash.
113
+ #
114
+ # @return [Mash] This mash unchanged.
115
+ def stringify_keys!; self end
116
+
117
+ # @return [Hash] The mash as a Hash with symbolized keys.
118
+ def symbolize_keys
119
+ h = Hash.new(default)
120
+ each { |key, val| h[key.to_sym] = val }
121
+ h
122
+ end
123
+
124
+ # @return [Hash] The mash as a Hash with string keys.
125
+ def to_hash
126
+ Hash.new(default).merge(self)
127
+ end
128
+
129
+ protected
130
+ # @param key<Object> The key to convert.
131
+ #
132
+ # @param [Object]
133
+ # The converted key. If the key was a symbol, it will be converted to a
134
+ # string.
135
+ #
136
+ # @api private
137
+ def convert_key(key)
138
+ key.kind_of?(Symbol) ? key.to_s : key
139
+ end
140
+
141
+ # @param value<Object> The value to convert.
142
+ #
143
+ # @return [Object]
144
+ # The converted value. A Hash or an Array of hashes, will be converted to
145
+ # their Mash equivalents.
146
+ #
147
+ # @api private
148
+ def convert_value(value)
149
+ if value.class == Hash
150
+ value.to_mash
151
+ elsif value.is_a?(Array)
152
+ value.collect { |e| convert_value(e) }
153
+ else
154
+ value
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,51 @@
1
+ require 'extlib/object'
2
+ require 'extlib/class'
3
+ require 'extlib/blank'
4
+
5
+ class Module
6
+ def find_const(const_name)
7
+ if const_name[0..1] == '::'
8
+ Object.full_const_get(const_name[2..-1])
9
+ else
10
+ nested_const_lookup(const_name)
11
+ end
12
+ end
13
+
14
+ def try_dup
15
+ self
16
+ end
17
+
18
+ private
19
+
20
+ # Doesn't do any caching since constants can change with remove_const
21
+ def nested_const_lookup(const_name)
22
+ unless equal?(Object)
23
+ constants = []
24
+
25
+ name.split('::').each do |part|
26
+ const = constants.last || Object
27
+ constants << const.const_get(part)
28
+ end
29
+
30
+ parts = const_name.split('::')
31
+
32
+ # from most to least specific constant, use each as a base and try
33
+ # to find a constant with the name const_name within them
34
+ constants.reverse_each do |const|
35
+ # return the nested constant if available
36
+ return const if parts.all? do |part|
37
+ const = if RUBY_VERSION >= '1.9.0'
38
+ const.const_defined?(part, false) ? const.const_get(part, false) : nil
39
+ else
40
+ const.const_defined?(part) ? const.const_get(part) : nil
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ # no relative constant found, fallback to an absolute lookup and
47
+ # use const_missing if not found
48
+ Object.full_const_get(const_name)
49
+ end
50
+
51
+ end # class Module
@@ -0,0 +1,5 @@
1
+ class NilClass
2
+ def try_dup
3
+ self
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Numeric
2
+ def try_dup
3
+ self
4
+ end
5
+ end
@@ -0,0 +1,178 @@
1
+ require 'set'
2
+ require 'extlib/blank'
3
+
4
+ class Object
5
+ # Extracts the singleton class, so that metaprogramming can be done on it.
6
+ #
7
+ # @return [Class] The meta class.
8
+ #
9
+ # @example [Setup]
10
+ # class MyString < String; end
11
+ #
12
+ # MyString.instance_eval do
13
+ # define_method :foo do
14
+ # puts self
15
+ # end
16
+ # end
17
+ #
18
+ # MyString.meta_class.instance_eval do
19
+ # define_method :bar do
20
+ # puts self
21
+ # end
22
+ # end
23
+ #
24
+ # def String.add_meta_var(var)
25
+ # self.meta_class.instance_eval do
26
+ # define_method var do
27
+ # puts "HELLO"
28
+ # end
29
+ # end
30
+ # end
31
+ #
32
+ # @example
33
+ # MyString.new("Hello").foo #=> "Hello"
34
+ # @example
35
+ # MyString.new("Hello").bar
36
+ # #=> NoMethodError: undefined method `bar' for "Hello":MyString
37
+ # @example
38
+ # MyString.foo
39
+ # #=> NoMethodError: undefined method `foo' for MyString:Class
40
+ # @example
41
+ # MyString.bar
42
+ # #=> MyString
43
+ # @example
44
+ # String.bar
45
+ # #=> NoMethodError: undefined method `bar' for String:Class
46
+ # @example
47
+ # MyString.add_meta_var(:x)
48
+ # MyString.x #=> HELLO
49
+ #
50
+ # @details [Description of Examples]
51
+ # As you can see, using #meta_class allows you to execute code (and here,
52
+ # define a method) on the metaclass itself. It also allows you to define
53
+ # class methods that can be run on subclasses, and then be able to execute
54
+ # code on the metaclass of the subclass (here MyString).
55
+ #
56
+ # In this case, we were able to define a class method (add_meta_var) on
57
+ # String that was executable by the MyString subclass. It was then able to
58
+ # define a method on the subclass by adding it to the MyString metaclass.
59
+ #
60
+ # For more information, you can check out _why's excellent article at:
61
+ # http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
62
+ def meta_class() class << self; self end end
63
+
64
+ # @param name<String> The name of the constant to get, e.g. "Merb::Router".
65
+ #
66
+ # @return [Object] The constant corresponding to the name.
67
+ def full_const_get(name)
68
+ list = name.split("::")
69
+ list.shift if list.first.blank?
70
+ obj = self
71
+ list.each do |x|
72
+ # This is required because const_get tries to look for constants in the
73
+ # ancestor chain, but we only want constants that are HERE
74
+ obj = obj.const_defined?(x) ? obj.const_get(x) : obj.const_missing(x)
75
+ end
76
+ obj
77
+ end
78
+
79
+ # @param name<String> The name of the constant to get, e.g. "Merb::Router".
80
+ # @param value<Object> The value to assign to the constant.
81
+ #
82
+ # @return [Object] The constant corresponding to the name.
83
+ def full_const_set(name, value)
84
+ list = name.split("::")
85
+ toplevel = list.first.blank?
86
+ list.shift if toplevel
87
+ last = list.pop
88
+ obj = list.empty? ? Object : Object.full_const_get(list.join("::"))
89
+ obj.const_set(last, value) if obj && !obj.const_defined?(last)
90
+ end
91
+
92
+ # Defines module from a string name (e.g. Foo::Bar::Baz)
93
+ # If module already exists, no exception raised.
94
+ #
95
+ # @param name<String> The name of the full module name to make
96
+ #
97
+ # @return [nil]
98
+ def make_module(str)
99
+ mod = str.split("::")
100
+ current_module = self
101
+ mod.each do |x|
102
+ unless current_module.const_defined?(x)
103
+ current_module.class_eval "module #{x}; end", __FILE__, __LINE__
104
+ end
105
+ current_module = current_module.const_get(x)
106
+ end
107
+ current_module
108
+ end
109
+
110
+ # @param duck<Symbol, Class, Array> The thing to compare the object to.
111
+ #
112
+ # @note
113
+ # The behavior of the method depends on the type of duck as follows:
114
+ # Symbol:: Check whether the object respond_to?(duck).
115
+ # Class:: Check whether the object is_a?(duck).
116
+ # Array::
117
+ # Check whether the object quacks_like? at least one of the options in the
118
+ # array.
119
+ #
120
+ # @return [Boolean]
121
+ # True if the object quacks like duck.
122
+ def quacks_like?(duck)
123
+ case duck
124
+ when Symbol
125
+ self.respond_to?(duck)
126
+ when Class
127
+ self.is_a?(duck)
128
+ when Array
129
+ duck.any? {|d| self.quacks_like?(d) }
130
+ else
131
+ false
132
+ end
133
+ end
134
+
135
+ # Override this in a child if it cannot be dup'ed
136
+ #
137
+ # @return [Object]
138
+ def try_dup
139
+ self.dup
140
+ end
141
+
142
+ # If receiver is callable, calls it and
143
+ # returns result. If not, just returns receiver
144
+ # itself
145
+ #
146
+ # @return [Object]
147
+ def try_call(*args)
148
+ if self.respond_to?(:call)
149
+ self.call(*args)
150
+ else
151
+ self
152
+ end
153
+ end
154
+
155
+ # @param arrayish<#include?> Container to check, to see if it includes the object.
156
+ # @param *more<Array>:: additional args, will be flattened into arrayish
157
+ #
158
+ # @return [Boolean]
159
+ # True if the object is included in arrayish (+ more)
160
+ #
161
+ # @example 1.in?([1,2,3]) #=> true
162
+ # @example 1.in?(1,2,3) #=> true
163
+ def in?(arrayish,*more)
164
+ arrayish = more.unshift(arrayish) unless more.empty?
165
+ arrayish.include?(self)
166
+ end
167
+
168
+ # Add instance_variable_defined? for backward compatibility
169
+ # @param variable<Symbol, String>
170
+ #
171
+ # @return [Boolean]
172
+ # True if the object has the given instance variable defined
173
+ unless respond_to?(:instance_variable_defined?)
174
+ def instance_variable_defined?(variable)
175
+ instance_variables.include?(variable.to_s)
176
+ end
177
+ end
178
+ end