sundbp-extlib 0.9.14
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +21 -0
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +47 -0
- data/README.rdoc +17 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/extlib.gemspec +146 -0
- data/lib/extlib.rb +50 -0
- data/lib/extlib/array.rb +36 -0
- data/lib/extlib/assertions.rb +8 -0
- data/lib/extlib/blank.rb +89 -0
- data/lib/extlib/boolean.rb +11 -0
- data/lib/extlib/byte_array.rb +6 -0
- data/lib/extlib/class.rb +177 -0
- data/lib/extlib/datetime.rb +29 -0
- data/lib/extlib/dictionary.rb +433 -0
- data/lib/extlib/hash.rb +442 -0
- data/lib/extlib/hook.rb +403 -0
- data/lib/extlib/inflection.rb +440 -0
- data/lib/extlib/lazy_array.rb +451 -0
- data/lib/extlib/lazy_module.rb +18 -0
- data/lib/extlib/logger.rb +198 -0
- data/lib/extlib/mash.rb +155 -0
- data/lib/extlib/module.rb +47 -0
- data/lib/extlib/nil.rb +5 -0
- data/lib/extlib/numeric.rb +5 -0
- data/lib/extlib/object.rb +175 -0
- data/lib/extlib/object_space.rb +13 -0
- data/lib/extlib/pathname.rb +20 -0
- data/lib/extlib/pooling.rb +235 -0
- data/lib/extlib/rubygems.rb +38 -0
- data/lib/extlib/simple_set.rb +66 -0
- data/lib/extlib/string.rb +176 -0
- data/lib/extlib/struct.rb +17 -0
- data/lib/extlib/symbol.rb +21 -0
- data/lib/extlib/time.rb +43 -0
- data/lib/extlib/virtual_file.rb +10 -0
- data/spec/array_spec.rb +39 -0
- data/spec/blank_spec.rb +85 -0
- data/spec/byte_array_spec.rb +7 -0
- data/spec/class_spec.rb +157 -0
- data/spec/datetime_spec.rb +22 -0
- data/spec/hash_spec.rb +537 -0
- data/spec/hook_spec.rb +1234 -0
- data/spec/inflection/plural_spec.rb +564 -0
- data/spec/inflection/singular_spec.rb +497 -0
- data/spec/inflection_extras_spec.rb +110 -0
- data/spec/lazy_array_spec.rb +1957 -0
- data/spec/lazy_module_spec.rb +38 -0
- data/spec/mash_spec.rb +311 -0
- data/spec/module_spec.rb +70 -0
- data/spec/object_space_spec.rb +9 -0
- data/spec/object_spec.rb +114 -0
- data/spec/pooling_spec.rb +511 -0
- data/spec/rcov.opts +6 -0
- data/spec/simple_set_spec.rb +57 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/string_spec.rb +221 -0
- data/spec/struct_spec.rb +12 -0
- data/spec/symbol_spec.rb +8 -0
- data/spec/time_spec.rb +29 -0
- data/spec/try_call_spec.rb +73 -0
- data/spec/try_dup_spec.rb +45 -0
- data/spec/virtual_file_spec.rb +21 -0
- data/tasks/ci.rake +1 -0
- data/tasks/metrics.rake +36 -0
- data/tasks/spec.rake +25 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +180 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
class Pathname
|
2
|
+
# Append path segments and expand to absolute path
|
3
|
+
#
|
4
|
+
# file = Pathname(Dir.pwd) / "subdir1" / :subdir2 / "filename.ext"
|
5
|
+
#
|
6
|
+
# @param [Pathname, String, #to_s] path path segment to concatenate with receiver
|
7
|
+
#
|
8
|
+
# @return [Pathname]
|
9
|
+
# receiver with _path_ appended and expanded to an absolute path
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
def /(path)
|
13
|
+
(self + path).expand_path
|
14
|
+
end
|
15
|
+
|
16
|
+
# alias to_s to to_str when to_str not defined
|
17
|
+
unless public_instance_methods(false).any? { |m| m.to_sym == :to_str }
|
18
|
+
alias to_str to_s
|
19
|
+
end
|
20
|
+
end # class Pathname
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Extlib
|
5
|
+
# ==== Notes
|
6
|
+
# Provides pooling support to class it got included in.
|
7
|
+
#
|
8
|
+
# Pooling of objects is a faster way of aquiring instances
|
9
|
+
# of objects compared to regular allocation and initialization
|
10
|
+
# because instances are keeped in memory reused.
|
11
|
+
#
|
12
|
+
# Classes that include Pooling module have re-defined new
|
13
|
+
# method that returns instances acquired from pool.
|
14
|
+
#
|
15
|
+
# Term resource is used for any type of poolable objects
|
16
|
+
# and should NOT be thought as DataMapper Resource or
|
17
|
+
# ActiveResource resource and such.
|
18
|
+
#
|
19
|
+
# In Data Objects connections are pooled so that it is
|
20
|
+
# unnecessary to allocate and initialize connection object
|
21
|
+
# each time connection is needed, like per request in a
|
22
|
+
# web application.
|
23
|
+
#
|
24
|
+
# Pool obviously has to be thread safe because state of
|
25
|
+
# object is reset when it is released.
|
26
|
+
module Pooling
|
27
|
+
|
28
|
+
def self.scavenger?
|
29
|
+
defined?(@scavenger) && !@scavenger.nil? && @scavenger.alive?
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.scavenger
|
33
|
+
unless scavenger?
|
34
|
+
@scavenger = Thread.new do
|
35
|
+
running = true
|
36
|
+
while running do
|
37
|
+
# Sleep before we actually start doing anything.
|
38
|
+
# Otherwise we might clean up something we just made
|
39
|
+
sleep(scavenger_interval)
|
40
|
+
|
41
|
+
lock.synchronize do
|
42
|
+
pools.each do |pool|
|
43
|
+
# This is a useful check, but non-essential, and right now it breaks lots of stuff.
|
44
|
+
# if pool.expired?
|
45
|
+
pool.lock.synchronize do
|
46
|
+
if pool.expired?
|
47
|
+
pool.dispose
|
48
|
+
end
|
49
|
+
end
|
50
|
+
# end
|
51
|
+
end
|
52
|
+
|
53
|
+
# The pool is empty, we stop the scavenger
|
54
|
+
# It wil be restarted if new resources are added again
|
55
|
+
if pools.empty?
|
56
|
+
running = false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end # loop
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
@scavenger.priority = -10
|
64
|
+
@scavenger
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.pools
|
68
|
+
@pools ||= Set.new
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.append_pool(pool)
|
72
|
+
lock.synchronize do
|
73
|
+
pools << pool
|
74
|
+
end
|
75
|
+
Extlib::Pooling.scavenger
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.lock
|
79
|
+
@lock ||= Mutex.new
|
80
|
+
end
|
81
|
+
|
82
|
+
class InvalidResourceError < StandardError
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.included(target)
|
86
|
+
target.class_eval do
|
87
|
+
class << self
|
88
|
+
alias __new new
|
89
|
+
end
|
90
|
+
|
91
|
+
@__pools = {}
|
92
|
+
@__pool_lock = Mutex.new
|
93
|
+
@__pool_wait = ConditionVariable.new
|
94
|
+
|
95
|
+
def self.__pool_lock
|
96
|
+
@__pool_lock
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.__pool_wait
|
100
|
+
@__pool_wait
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.new(*args)
|
104
|
+
(@__pools[args] ||= __pool_lock.synchronize { Pool.new(self.pool_size, self, args) }).new
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.__pools
|
108
|
+
@__pools
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.pool_size
|
112
|
+
8
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def release
|
118
|
+
@__pool.release(self) unless @__pool.nil?
|
119
|
+
end
|
120
|
+
|
121
|
+
def detach
|
122
|
+
@__pool.delete(self) unless @__pool.nil?
|
123
|
+
end
|
124
|
+
|
125
|
+
class Pool
|
126
|
+
attr_reader :available
|
127
|
+
attr_reader :used
|
128
|
+
|
129
|
+
def initialize(max_size, resource, args)
|
130
|
+
raise ArgumentError.new("+max_size+ should be a Fixnum but was #{max_size.inspect}") unless Fixnum === max_size
|
131
|
+
raise ArgumentError.new("+resource+ should be a Class but was #{resource.inspect}") unless Class === resource
|
132
|
+
|
133
|
+
@max_size = max_size
|
134
|
+
@resource = resource
|
135
|
+
@args = args
|
136
|
+
|
137
|
+
@available = []
|
138
|
+
@used = {}
|
139
|
+
Extlib::Pooling.append_pool(self)
|
140
|
+
end
|
141
|
+
|
142
|
+
def lock
|
143
|
+
@resource.__pool_lock
|
144
|
+
end
|
145
|
+
|
146
|
+
def wait
|
147
|
+
@resource.__pool_wait
|
148
|
+
end
|
149
|
+
|
150
|
+
def scavenge_interval
|
151
|
+
@resource.scavenge_interval
|
152
|
+
end
|
153
|
+
|
154
|
+
def new
|
155
|
+
instance = nil
|
156
|
+
begin
|
157
|
+
lock.synchronize do
|
158
|
+
if @available.size > 0
|
159
|
+
instance = @available.pop
|
160
|
+
@used[instance.object_id] = instance
|
161
|
+
elsif @used.size < @max_size
|
162
|
+
instance = @resource.__new(*@args)
|
163
|
+
raise InvalidResourceError.new("#{@resource} constructor created a nil object") if instance.nil?
|
164
|
+
raise InvalidResourceError.new("#{instance} is already part of the pool") if @used.include? instance
|
165
|
+
instance.instance_variable_set(:@__pool, self)
|
166
|
+
instance.instance_variable_set(:@__allocated_in_pool, Time.now)
|
167
|
+
@used[instance.object_id] = instance
|
168
|
+
else
|
169
|
+
# Wait for another thread to release an instance.
|
170
|
+
# If we exhaust the pool and don't release the active instance,
|
171
|
+
# we'll wait here forever, so it's *very* important to always
|
172
|
+
# release your services and *never* exhaust the pool within
|
173
|
+
# a single thread.
|
174
|
+
wait.wait(lock)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end until instance
|
178
|
+
instance
|
179
|
+
end
|
180
|
+
|
181
|
+
def release(instance)
|
182
|
+
lock.synchronize do
|
183
|
+
instance.instance_variable_set(:@__allocated_in_pool, Time.now)
|
184
|
+
@used.delete(instance.object_id)
|
185
|
+
@available.push(instance)
|
186
|
+
wait.signal
|
187
|
+
end
|
188
|
+
nil
|
189
|
+
end
|
190
|
+
|
191
|
+
def delete(instance)
|
192
|
+
lock.synchronize do
|
193
|
+
instance.instance_variable_set(:@__pool, nil)
|
194
|
+
@used.delete(instance.object_id)
|
195
|
+
wait.signal
|
196
|
+
end
|
197
|
+
nil
|
198
|
+
end
|
199
|
+
|
200
|
+
def size
|
201
|
+
@used.size + @available.size
|
202
|
+
end
|
203
|
+
alias length size
|
204
|
+
|
205
|
+
def inspect
|
206
|
+
"#<Extlib::Pooling::Pool<#{@resource.name}> available=#{@available.size} used=#{@used.size} size=#{@max_size}>"
|
207
|
+
end
|
208
|
+
|
209
|
+
def flush!
|
210
|
+
@available.pop.dispose until @available.empty?
|
211
|
+
end
|
212
|
+
|
213
|
+
def dispose
|
214
|
+
flush!
|
215
|
+
@resource.__pools.delete(@args)
|
216
|
+
!Extlib::Pooling.pools.delete?(self).nil?
|
217
|
+
end
|
218
|
+
|
219
|
+
def expired?
|
220
|
+
@available.each do |instance|
|
221
|
+
if Extlib.exiting || instance.instance_variable_get(:@__allocated_in_pool) + Extlib::Pooling.scavenger_interval <= (Time.now + 0.02)
|
222
|
+
instance.dispose
|
223
|
+
@available.delete(instance)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
size == 0
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
def self.scavenger_interval
|
232
|
+
60
|
233
|
+
end
|
234
|
+
end # module Pooling
|
235
|
+
end # module Extlib
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# this is a temporary workaround until rubygems Does the Right thing here
|
2
|
+
require 'rubygems'
|
3
|
+
module Gem
|
4
|
+
class SourceIndex
|
5
|
+
|
6
|
+
# This is resolved in 1.1
|
7
|
+
if Version.new(RubyGemsVersion) < Version.new("1.1")
|
8
|
+
|
9
|
+
# Overwrite this so that a gem of the same name and version won't push one
|
10
|
+
# from the gems directory out entirely.
|
11
|
+
#
|
12
|
+
# @param gem_spec<Gem::Specification> The specification of the gem to add.
|
13
|
+
def add_spec(gem_spec)
|
14
|
+
unless gem_spec.instance_variable_get("@loaded_from") &&
|
15
|
+
@gems[gem_spec.full_name].is_a?(Gem::Specification) &&
|
16
|
+
@gems[gem_spec.full_name].installation_path ==
|
17
|
+
File.join(defined?(Merb) && Merb.respond_to?(:root) ? Merb.root : Dir.pwd,"gems")
|
18
|
+
|
19
|
+
@gems[gem_spec.full_name] = gem_spec
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class Specification
|
28
|
+
|
29
|
+
# Overwrite this so that gems in the gems directory get preferred over gems
|
30
|
+
# from any other location. If there are two gems of different versions in
|
31
|
+
# the gems directory, the later one will load as usual.
|
32
|
+
#
|
33
|
+
# @return [Array<Array>] The object used for sorting gem specs.
|
34
|
+
def sort_obj
|
35
|
+
[@name, installation_path == File.join(defined?(Merb) && Merb.respond_to?(:root) ? Merb.root : Dir.pwd,"gems") ? 1 : -1, @version.to_ints, @new_platform == Gem::Platform::RUBY ? -1 : 1]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Extlib
|
2
|
+
# Simple set implementation
|
3
|
+
# on top of Hash with merging support.
|
4
|
+
#
|
5
|
+
# In particular this is used to store
|
6
|
+
# a set of callable actions of controller.
|
7
|
+
class SimpleSet < Hash
|
8
|
+
|
9
|
+
##
|
10
|
+
# Create a new SimpleSet containing the unique members of _arr_
|
11
|
+
#
|
12
|
+
# @param [Array] arr Initial set values.
|
13
|
+
#
|
14
|
+
# @return [Array] The array the Set was initialized with
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
def initialize(arr = [])
|
18
|
+
Array(arr).each {|x| self[x] = true}
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Add a value to the set, and return it
|
23
|
+
#
|
24
|
+
# @param [Object] value Value to add to set.
|
25
|
+
#
|
26
|
+
# @return [SimpleSet] Receiver
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def <<(value)
|
30
|
+
self[value] = true
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Merge _arr_ with receiver, producing the union of receiver & _arr_
|
36
|
+
#
|
37
|
+
# s = Extlib::SimpleSet.new([:a, :b, :c])
|
38
|
+
# s.merge([:c, :d, :e, f]) #=> #<SimpleSet: {:e, :c, :f, :a, :d, :b}>
|
39
|
+
#
|
40
|
+
# @param [Array] arr Values to merge with set.
|
41
|
+
#
|
42
|
+
# @return [SimpleSet] The set after the Array was merged in.
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
def merge(arr)
|
46
|
+
super(arr.inject({}) {|s,x| s[x] = true; s })
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Get a human readable version of the set.
|
51
|
+
#
|
52
|
+
# s = SimpleSet.new([:a, :b, :c])
|
53
|
+
# s.inspect #=> "#<SimpleSet: {:c, :a, :b}>"
|
54
|
+
#
|
55
|
+
# @return [String] A human readable version of the set.
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def inspect
|
59
|
+
"#<SimpleSet: {#{keys.map {|x| x.inspect}.join(", ")}}>"
|
60
|
+
end
|
61
|
+
|
62
|
+
# def to_a
|
63
|
+
alias_method :to_a, :keys
|
64
|
+
|
65
|
+
end # SimpleSet
|
66
|
+
end # Merb
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
class String
|
4
|
+
##
|
5
|
+
# Escape all regexp special characters.
|
6
|
+
#
|
7
|
+
# "*?{}.".escape_regexp #=> "\\*\\?\\{\\}\\."
|
8
|
+
#
|
9
|
+
# @return [String] Receiver with all regexp special characters escaped.
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
def escape_regexp
|
13
|
+
Regexp.escape self
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Unescape all regexp special characters.
|
18
|
+
#
|
19
|
+
# "\\*\\?\\{\\}\\.".unescape_regexp #=> "*?{}."
|
20
|
+
#
|
21
|
+
# @return [String] Receiver with all regexp special characters unescaped.
|
22
|
+
#
|
23
|
+
# @api public
|
24
|
+
def unescape_regexp
|
25
|
+
self.gsub(/\\([\.\?\|\(\)\[\]\{\}\^\$\*\+\-])/, '\1')
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Convert to snake case.
|
30
|
+
#
|
31
|
+
# "FooBar".snake_case #=> "foo_bar"
|
32
|
+
# "HeadlineCNNNews".snake_case #=> "headline_cnn_news"
|
33
|
+
# "CNN".snake_case #=> "cnn"
|
34
|
+
#
|
35
|
+
# @return [String] Receiver converted to snake case.
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def snake_case
|
39
|
+
return downcase if match(/\A[A-Z]+\z/)
|
40
|
+
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
|
41
|
+
gsub(/([a-z])([A-Z])/, '\1_\2').
|
42
|
+
downcase
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Convert to camel case.
|
47
|
+
#
|
48
|
+
# "foo_bar".camel_case #=> "FooBar"
|
49
|
+
#
|
50
|
+
# @return [String] Receiver converted to camel case.
|
51
|
+
#
|
52
|
+
# @api public
|
53
|
+
def camel_case
|
54
|
+
return self if self !~ /_/ && self =~ /[A-Z]+.*/
|
55
|
+
split('_').map{|e| e.capitalize}.join
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Convert a path string to a constant name.
|
60
|
+
#
|
61
|
+
# "merb/core_ext/string".to_const_string #=> "Merb::CoreExt::String"
|
62
|
+
#
|
63
|
+
# @return [String] Receiver converted to a constant name.
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
def to_const_string
|
67
|
+
gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Convert a constant name to a path, assuming a conventional structure.
|
72
|
+
#
|
73
|
+
# "FooBar::Baz".to_const_path # => "foo_bar/baz"
|
74
|
+
#
|
75
|
+
# @return [String] Path to the file containing the constant named by receiver
|
76
|
+
# (constantized string), assuming a conventional structure.
|
77
|
+
#
|
78
|
+
# @api public
|
79
|
+
def to_const_path
|
80
|
+
snake_case.gsub(/::/, "/")
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Join with _o_ as a file path.
|
85
|
+
#
|
86
|
+
# "merb"/"core_ext" #=> "merb/core_ext"
|
87
|
+
#
|
88
|
+
# @param [String] o Path component to join with receiver.
|
89
|
+
#
|
90
|
+
# @return [String] Receiver joined with o as a file path.
|
91
|
+
#
|
92
|
+
# @api public
|
93
|
+
def /(o)
|
94
|
+
File.join(self, o.to_s)
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Calculate a relative path *from* _other_.
|
99
|
+
#
|
100
|
+
# "/opt/local/lib".relative_path_from("/opt/local/lib/ruby/site_ruby") # => "../.."
|
101
|
+
#
|
102
|
+
# @param [String] other Base path to calculate *from*.
|
103
|
+
#
|
104
|
+
# @return [String] Relative path from _other_ to receiver.
|
105
|
+
#
|
106
|
+
# @api public
|
107
|
+
def relative_path_from(other)
|
108
|
+
Pathname.new(self).relative_path_from(Pathname.new(other)).to_s
|
109
|
+
end
|
110
|
+
|
111
|
+
# Overwrite this method to provide your own translations.
|
112
|
+
def self.translate(value)
|
113
|
+
translations[value] || value
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.translations
|
117
|
+
@translations ||= {}
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# Replace sequences of whitespace (including newlines) with either
|
122
|
+
# a single space or remove them entirely (according to param _spaced_)
|
123
|
+
#
|
124
|
+
# <<QUERY.compress_lines
|
125
|
+
# SELECT name
|
126
|
+
# FROM users
|
127
|
+
# QUERY => "SELECT name FROM users"
|
128
|
+
#
|
129
|
+
# @param [TrueClass, FalseClass] spaced (default=true)
|
130
|
+
# Determines whether returned string has whitespace collapsed or removed
|
131
|
+
#
|
132
|
+
# @return [String] Receiver with whitespace (including newlines) replaced
|
133
|
+
#
|
134
|
+
# @api public
|
135
|
+
def compress_lines(spaced = true)
|
136
|
+
split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
|
137
|
+
end
|
138
|
+
|
139
|
+
##
|
140
|
+
# Remove whitespace margin.
|
141
|
+
#
|
142
|
+
# @param [Object] indicator ???
|
143
|
+
#
|
144
|
+
# @return [String] receiver with whitespace margin removed
|
145
|
+
#
|
146
|
+
# @api public
|
147
|
+
def margin(indicator = nil)
|
148
|
+
lines = self.dup.split($/)
|
149
|
+
|
150
|
+
min_margin = 0
|
151
|
+
lines.each do |line|
|
152
|
+
if line =~ /^(\s+)/ && (min_margin == 0 || $1.size < min_margin)
|
153
|
+
min_margin = $1.size
|
154
|
+
end
|
155
|
+
end
|
156
|
+
lines.map { |line| line.sub(/^\s{#{min_margin}}/, '') }.join($/)
|
157
|
+
end
|
158
|
+
|
159
|
+
##
|
160
|
+
# Formats String for easy translation. Replaces an arbitrary number of
|
161
|
+
# values using numeric identifier replacement.
|
162
|
+
#
|
163
|
+
# "%s %s %s" % %w(one two three) #=> "one two three"
|
164
|
+
# "%3$s %2$s %1$s" % %w(one two three) #=> "three two one"
|
165
|
+
#
|
166
|
+
# @param [#to_s] values
|
167
|
+
# A list of values to translate and interpolate into receiver
|
168
|
+
#
|
169
|
+
# @return [String]
|
170
|
+
# Receiver translated with values translated and interpolated positionally
|
171
|
+
#
|
172
|
+
# @api public
|
173
|
+
def t(*values)
|
174
|
+
self.class::translate(self) % values.collect! { |value| value.frozen? ? value : self.class::translate(value.to_s) }
|
175
|
+
end
|
176
|
+
end # class String
|