sundbp-extlib 0.9.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|