activesupport 2.3.2 → 2.3.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- data/CHANGELOG +7 -0
- data/lib/active_support/cache.rb +14 -1
- data/lib/active_support/cache/mem_cache_store.rb +16 -10
- data/lib/active_support/cache/strategy/local_cache.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +1 -1
- data/lib/active_support/core_ext/hash/conversions.rb +13 -4
- data/lib/active_support/core_ext/kernel/debugger.rb +4 -2
- data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -0
- data/lib/active_support/core_ext/module/delegation.rb +12 -3
- data/lib/active_support/core_ext/module/model_naming.rb +8 -6
- data/lib/active_support/core_ext/numeric/bytes.rb +15 -9
- data/lib/active_support/core_ext/string/access.rb +29 -5
- data/lib/active_support/duration.rb +4 -2
- data/lib/active_support/json.rb +1 -22
- data/lib/active_support/json/backends/jsongem.rb +38 -0
- data/lib/active_support/json/backends/yaml.rb +85 -0
- data/lib/active_support/json/decoding.rb +23 -72
- data/lib/active_support/json/encoders/date.rb +9 -8
- data/lib/active_support/json/encoders/date_time.rb +9 -8
- data/lib/active_support/json/encoders/enumerable.rb +14 -9
- data/lib/active_support/json/encoders/false_class.rb +4 -2
- data/lib/active_support/json/encoders/hash.rb +21 -11
- data/lib/active_support/json/encoders/nil_class.rb +4 -2
- data/lib/active_support/json/encoders/numeric.rb +16 -0
- data/lib/active_support/json/encoders/object.rb +6 -2
- data/lib/active_support/json/encoders/regexp.rb +4 -0
- data/lib/active_support/json/encoders/string.rb +5 -32
- data/lib/active_support/json/encoders/symbol.rb +2 -2
- data/lib/active_support/json/encoders/time.rb +9 -8
- data/lib/active_support/json/encoders/true_class.rb +4 -2
- data/lib/active_support/json/encoding.rb +80 -9
- data/lib/active_support/ordered_hash.rb +28 -0
- data/lib/active_support/test_case.rb +7 -7
- data/lib/active_support/testing/deprecation.rb +2 -0
- data/lib/active_support/time_with_zone.rb +9 -8
- data/lib/active_support/vendor.rb +6 -7
- data/lib/active_support/vendor/i18n-0.1.3/test/i18n_exceptions_test.rb +0 -1
- data/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb +0 -1
- data/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb +0 -1
- data/lib/active_support/vendor/{memcache-client-1.6.5 → memcache-client-1.7.4}/memcache.rb +242 -70
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +162 -0
- metadata +10 -4
data/CHANGELOG
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
*2.3.3 (July 20, 2009)*
|
2
|
+
|
3
|
+
* JSON: +Object#to_json+ calls +as_json+ to coerce itself into something natively encodable like +Hash+, +Integer+, or +String+. Override +as_json+ instead of +to_json+ so you're JSON-library-agnostic. [Jeremy Kemper]
|
4
|
+
|
5
|
+
* Allow MemCacheStore to be initialized with a MemCache-like object instead of addresses and options [Bryan Helmkamp]
|
6
|
+
|
7
|
+
|
1
8
|
*2.3.2 [Final] (March 15, 2009)*
|
2
9
|
|
3
10
|
* XmlMini supports LibXML and Nokogiri backends. #2084, #2190 [Bart ten Brinke, Aaron Patterson]
|
data/lib/active_support/cache.rb
CHANGED
@@ -91,11 +91,16 @@ module ActiveSupport
|
|
91
91
|
class Store
|
92
92
|
cattr_accessor :logger
|
93
93
|
|
94
|
+
attr_reader :silence, :logger_off
|
95
|
+
|
94
96
|
def silence!
|
95
97
|
@silence = true
|
96
98
|
self
|
97
99
|
end
|
98
100
|
|
101
|
+
alias silence? silence
|
102
|
+
alias logger_off? logger_off
|
103
|
+
|
99
104
|
# Fetches data from the cache, using the given key. If there is data in
|
100
105
|
# the cache with the given key, then that data is returned.
|
101
106
|
#
|
@@ -220,8 +225,16 @@ module ActiveSupport
|
|
220
225
|
end
|
221
226
|
|
222
227
|
private
|
228
|
+
def expires_in(options)
|
229
|
+
expires_in = options && options[:expires_in]
|
230
|
+
|
231
|
+
raise ":expires_in must be a number" if expires_in && !expires_in.is_a?(Numeric)
|
232
|
+
|
233
|
+
expires_in || 0
|
234
|
+
end
|
235
|
+
|
223
236
|
def log(operation, key, options)
|
224
|
-
logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") if logger &&
|
237
|
+
logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") if logger && !silence? && !logger_off?
|
225
238
|
end
|
226
239
|
end
|
227
240
|
end
|
@@ -23,7 +23,12 @@ module ActiveSupport
|
|
23
23
|
DELETED = "DELETED\r\n"
|
24
24
|
end
|
25
25
|
|
26
|
-
|
26
|
+
def self.build_mem_cache(*addresses)
|
27
|
+
addresses = addresses.flatten
|
28
|
+
options = addresses.extract_options!
|
29
|
+
addresses = ["localhost"] if addresses.empty?
|
30
|
+
MemCache.new(addresses, options)
|
31
|
+
end
|
27
32
|
|
28
33
|
# Creates a new MemCacheStore object, with the given memcached server
|
29
34
|
# addresses. Each address is either a host name, or a host-with-port string
|
@@ -34,15 +39,20 @@ module ActiveSupport
|
|
34
39
|
# If no addresses are specified, then MemCacheStore will connect to
|
35
40
|
# localhost port 11211 (the default memcached port).
|
36
41
|
def initialize(*addresses)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
+
if addresses.first.respond_to?(:get)
|
43
|
+
@data = addresses.first
|
44
|
+
else
|
45
|
+
@data = self.class.build_mem_cache(*addresses)
|
46
|
+
end
|
42
47
|
|
43
48
|
extend Strategy::LocalCache
|
44
49
|
end
|
45
50
|
|
51
|
+
# Reads multiple keys from the cache.
|
52
|
+
def read_multi(*keys)
|
53
|
+
@data.get_multi keys
|
54
|
+
end
|
55
|
+
|
46
56
|
def read(key, options = nil) # :nodoc:
|
47
57
|
super
|
48
58
|
@data.get(key, raw?(options))
|
@@ -120,10 +130,6 @@ module ActiveSupport
|
|
120
130
|
end
|
121
131
|
|
122
132
|
private
|
123
|
-
def expires_in(options)
|
124
|
-
(options && options[:expires_in]) || 0
|
125
|
-
end
|
126
|
-
|
127
133
|
def raw?(options)
|
128
134
|
options && options[:raw]
|
129
135
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module ActiveSupport #:nodoc:
|
2
2
|
module CoreExtensions #:nodoc:
|
3
3
|
module Date #:nodoc:
|
4
|
-
# Enables the use of time calculations within
|
4
|
+
# Enables the use of time calculations within Date itself
|
5
5
|
module Calculations
|
6
6
|
def self.included(base) #:nodoc:
|
7
7
|
base.extend ClassMethods
|
@@ -1,6 +1,14 @@
|
|
1
1
|
require 'date'
|
2
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
3
|
|
3
4
|
module ActiveSupport #:nodoc:
|
5
|
+
# these accessors are here because people using ActiveResource and REST to integrate with other systems
|
6
|
+
# have to be able to control the default behavior of rename_key. dasherize_xml is set to true to emulate
|
7
|
+
# existing behavior. In a future version it should be set to false by default.
|
8
|
+
mattr_accessor :dasherize_xml
|
9
|
+
mattr_accessor :camelize_xml
|
10
|
+
self.dasherize_xml = true
|
11
|
+
self.camelize_xml = false
|
4
12
|
module CoreExtensions #:nodoc:
|
5
13
|
module Hash #:nodoc:
|
6
14
|
module Conversions
|
@@ -143,10 +151,11 @@ module ActiveSupport #:nodoc:
|
|
143
151
|
end
|
144
152
|
|
145
153
|
def rename_key(key, options = {})
|
146
|
-
camelize
|
147
|
-
dasherize =
|
154
|
+
camelize = options.has_key?(:camelize) ? options[:camelize] : ActiveSupport.camelize_xml
|
155
|
+
dasherize = options.has_key?(:dasherize) ? options[:dasherize] : ActiveSupport.dasherize_xml
|
148
156
|
key = key.camelize if camelize
|
149
|
-
|
157
|
+
key = key.dasherize if dasherize
|
158
|
+
key
|
150
159
|
end
|
151
160
|
|
152
161
|
module ClassMethods
|
@@ -221,7 +230,7 @@ module ActiveSupport #:nodoc:
|
|
221
230
|
case params.class.to_s
|
222
231
|
when "Hash"
|
223
232
|
params.inject({}) do |h,(k,v)|
|
224
|
-
h[k.to_s.
|
233
|
+
h[k.to_s.tr("-", "_")] = unrename_keys(v)
|
225
234
|
h
|
226
235
|
end
|
227
236
|
when "Array"
|
@@ -2,12 +2,14 @@ module Kernel
|
|
2
2
|
unless respond_to?(:debugger)
|
3
3
|
# Starts a debugging session if ruby-debug has been loaded (call script/server --debugger to do load it).
|
4
4
|
def debugger
|
5
|
-
|
5
|
+
message = "\n***** Debugger requested, but was not available: Start server with --debugger to enable *****\n"
|
6
|
+
defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message)
|
6
7
|
end
|
7
8
|
end
|
8
9
|
|
9
10
|
def breakpoint
|
10
|
-
|
11
|
+
message = "\n***** The 'breakpoint' command has been renamed 'debugger' -- please change *****\n"
|
12
|
+
defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message)
|
11
13
|
debugger
|
12
14
|
end
|
13
15
|
end
|
@@ -108,12 +108,21 @@ class Module
|
|
108
108
|
|
109
109
|
prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_"
|
110
110
|
|
111
|
-
|
111
|
+
file, line = caller.first.split(':', 2)
|
112
|
+
line = line.to_i
|
112
113
|
|
113
114
|
methods.each do |method|
|
114
|
-
|
115
|
+
on_nil =
|
116
|
+
if options[:allow_nil]
|
117
|
+
'return'
|
118
|
+
else
|
119
|
+
%(raise "#{prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
|
120
|
+
end
|
121
|
+
|
122
|
+
module_eval(<<-EOS, file, line)
|
115
123
|
def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block)
|
116
|
-
#{
|
124
|
+
#{on_nil} if #{to}.nil?
|
125
|
+
#{to}.__send__(#{method.inspect}, *args, &block) # client && client.__send__(:name, *args, &block)
|
117
126
|
end # end
|
118
127
|
EOS
|
119
128
|
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
module ActiveSupport
|
2
2
|
class ModelName < String
|
3
|
-
attr_reader :singular, :plural, :
|
3
|
+
attr_reader :singular, :plural, :element, :collection, :partial_path
|
4
|
+
alias_method :cache_key, :collection
|
4
5
|
|
5
6
|
def initialize(name)
|
6
7
|
super
|
7
|
-
@singular = underscore.tr('/', '_').freeze
|
8
|
-
@plural = @singular.
|
9
|
-
@
|
10
|
-
@
|
8
|
+
@singular = ActiveSupport::Inflector.underscore(self).tr('/', '_').freeze
|
9
|
+
@plural = ActiveSupport::Inflector.pluralize(@singular).freeze
|
10
|
+
@element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze
|
11
|
+
@collection = ActiveSupport::Inflector.tableize(self).freeze
|
12
|
+
@partial_path = "#{@collection}/#{@element}".freeze
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
@@ -16,7 +18,7 @@ module ActiveSupport
|
|
16
18
|
# Returns an ActiveSupport::ModelName object for module. It can be
|
17
19
|
# used to retrieve all kinds of naming-related information.
|
18
20
|
def model_name
|
19
|
-
@model_name ||= ModelName.new(name)
|
21
|
+
@model_name ||= ::ActiveSupport::ModelName.new(name)
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
@@ -3,41 +3,47 @@ module ActiveSupport #:nodoc:
|
|
3
3
|
module Numeric #:nodoc:
|
4
4
|
# Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
|
5
5
|
module Bytes
|
6
|
+
KILOBYTE = 1024
|
7
|
+
MEGABYTE = KILOBYTE * 1024
|
8
|
+
GIGABYTE = MEGABYTE * 1024
|
9
|
+
TERABYTE = GIGABYTE * 1024
|
10
|
+
PETABYTE = TERABYTE * 1024
|
11
|
+
EXABYTE = PETABYTE * 1024
|
12
|
+
|
6
13
|
def bytes
|
7
14
|
self
|
8
15
|
end
|
9
16
|
alias :byte :bytes
|
10
17
|
|
11
18
|
def kilobytes
|
12
|
-
self *
|
19
|
+
self * KILOBYTE
|
13
20
|
end
|
14
21
|
alias :kilobyte :kilobytes
|
15
22
|
|
16
23
|
def megabytes
|
17
|
-
self *
|
24
|
+
self * MEGABYTE
|
18
25
|
end
|
19
26
|
alias :megabyte :megabytes
|
20
27
|
|
21
28
|
def gigabytes
|
22
|
-
self *
|
29
|
+
self * GIGABYTE
|
23
30
|
end
|
24
31
|
alias :gigabyte :gigabytes
|
25
32
|
|
26
33
|
def terabytes
|
27
|
-
self *
|
34
|
+
self * TERABYTE
|
28
35
|
end
|
29
36
|
alias :terabyte :terabytes
|
30
|
-
|
37
|
+
|
31
38
|
def petabytes
|
32
|
-
self *
|
39
|
+
self * PETABYTE
|
33
40
|
end
|
34
41
|
alias :petabyte :petabytes
|
35
|
-
|
42
|
+
|
36
43
|
def exabytes
|
37
|
-
self *
|
44
|
+
self * EXABYTE
|
38
45
|
end
|
39
46
|
alias :exabyte :exabytes
|
40
|
-
|
41
47
|
end
|
42
48
|
end
|
43
49
|
end
|
@@ -41,9 +41,15 @@ module ActiveSupport #:nodoc:
|
|
41
41
|
# "hello".first(2) # => "he"
|
42
42
|
# "hello".first(10) # => "hello"
|
43
43
|
def first(limit = 1)
|
44
|
-
|
44
|
+
if limit == 0
|
45
|
+
''
|
46
|
+
elsif limit >= size
|
47
|
+
self
|
48
|
+
else
|
49
|
+
mb_chars[0...limit].to_s
|
50
|
+
end
|
45
51
|
end
|
46
|
-
|
52
|
+
|
47
53
|
# Returns the last character of the string or the last +limit+ characters.
|
48
54
|
#
|
49
55
|
# Examples:
|
@@ -51,7 +57,13 @@ module ActiveSupport #:nodoc:
|
|
51
57
|
# "hello".last(2) # => "lo"
|
52
58
|
# "hello".last(10) # => "hello"
|
53
59
|
def last(limit = 1)
|
54
|
-
|
60
|
+
if limit == 0
|
61
|
+
''
|
62
|
+
elsif limit >= size
|
63
|
+
self
|
64
|
+
else
|
65
|
+
mb_chars[(-limit)..-1].to_s
|
66
|
+
end
|
55
67
|
end
|
56
68
|
end
|
57
69
|
else
|
@@ -69,11 +81,23 @@ module ActiveSupport #:nodoc:
|
|
69
81
|
end
|
70
82
|
|
71
83
|
def first(limit = 1)
|
72
|
-
|
84
|
+
if limit == 0
|
85
|
+
''
|
86
|
+
elsif limit >= size
|
87
|
+
self
|
88
|
+
else
|
89
|
+
to(limit - 1)
|
90
|
+
end
|
73
91
|
end
|
74
92
|
|
75
93
|
def last(limit = 1)
|
76
|
-
|
94
|
+
if limit == 0
|
95
|
+
''
|
96
|
+
elsif limit >= size
|
97
|
+
self
|
98
|
+
else
|
99
|
+
from(-limit)
|
100
|
+
end
|
77
101
|
end
|
78
102
|
end
|
79
103
|
end
|
@@ -67,10 +67,12 @@ module ActiveSupport
|
|
67
67
|
|
68
68
|
def inspect #:nodoc:
|
69
69
|
consolidated = parts.inject(::Hash.new(0)) { |h,part| h[part.first] += part.last; h }
|
70
|
-
[:years, :months, :days, :minutes, :seconds].map do |length|
|
70
|
+
parts = [:years, :months, :days, :minutes, :seconds].map do |length|
|
71
71
|
n = consolidated[length]
|
72
72
|
"#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero?
|
73
|
-
end.compact
|
73
|
+
end.compact
|
74
|
+
parts = ["0 seconds"] if parts.empty?
|
75
|
+
parts.to_sentence(:locale => :en)
|
74
76
|
end
|
75
77
|
|
76
78
|
protected
|
data/lib/active_support/json.rb
CHANGED
@@ -1,23 +1,2 @@
|
|
1
|
-
module ActiveSupport
|
2
|
-
# If true, use ISO 8601 format for dates and times. Otherwise, fall back to the Active Support legacy format.
|
3
|
-
mattr_accessor :use_standard_json_time_format
|
4
|
-
|
5
|
-
class << self
|
6
|
-
def escape_html_entities_in_json
|
7
|
-
@escape_html_entities_in_json
|
8
|
-
end
|
9
|
-
|
10
|
-
def escape_html_entities_in_json=(value)
|
11
|
-
ActiveSupport::JSON::Encoding.escape_regex = \
|
12
|
-
if value
|
13
|
-
/[\010\f\n\r\t"\\><&]/
|
14
|
-
else
|
15
|
-
/[\010\f\n\r\t"\\]/
|
16
|
-
end
|
17
|
-
@escape_html_entities_in_json = value
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
require 'active_support/json/encoding'
|
23
1
|
require 'active_support/json/decoding'
|
2
|
+
require 'active_support/json/encoding'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'json' unless defined?(JSON)
|
2
|
+
|
3
|
+
module ActiveSupport
|
4
|
+
module JSON
|
5
|
+
ParseError = ::JSON::ParserError unless const_defined?(:ParseError)
|
6
|
+
|
7
|
+
module Backends
|
8
|
+
module JSONGem
|
9
|
+
extend self
|
10
|
+
|
11
|
+
# Converts a JSON string into a Ruby object.
|
12
|
+
def decode(json)
|
13
|
+
data = ::JSON.parse(json)
|
14
|
+
if ActiveSupport.parse_json_times
|
15
|
+
convert_dates_from(data)
|
16
|
+
else
|
17
|
+
data
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def convert_dates_from(data)
|
23
|
+
case data
|
24
|
+
when DATE_REGEX
|
25
|
+
DateTime.parse(data)
|
26
|
+
when Array
|
27
|
+
data.map! { |d| convert_dates_from(d) }
|
28
|
+
when Hash
|
29
|
+
data.each do |key, value|
|
30
|
+
data[key] = convert_dates_from(value)
|
31
|
+
end
|
32
|
+
else data
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'active_support/core_ext/string/starts_ends_with'
|
2
|
+
|
3
|
+
module ActiveSupport
|
4
|
+
module JSON
|
5
|
+
unless const_defined?(:ParseError)
|
6
|
+
class ParseError < StandardError
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Backends
|
11
|
+
module Yaml
|
12
|
+
extend self
|
13
|
+
|
14
|
+
# Converts a JSON string into a Ruby object.
|
15
|
+
def decode(json)
|
16
|
+
YAML.load(convert_json_to_yaml(json))
|
17
|
+
rescue ArgumentError => e
|
18
|
+
raise ParseError, "Invalid JSON string"
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
# Ensure that ":" and "," are always followed by a space
|
23
|
+
def convert_json_to_yaml(json) #:nodoc:
|
24
|
+
require 'strscan' unless defined? ::StringScanner
|
25
|
+
scanner, quoting, marks, pos, times = ::StringScanner.new(json), false, [], nil, []
|
26
|
+
while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/)
|
27
|
+
case char = scanner[1]
|
28
|
+
when '"', "'"
|
29
|
+
if !quoting
|
30
|
+
quoting = char
|
31
|
+
pos = scanner.pos
|
32
|
+
elsif quoting == char
|
33
|
+
if json[pos..scanner.pos-2] =~ DATE_REGEX
|
34
|
+
# found a date, track the exact positions of the quotes so we can remove them later.
|
35
|
+
# oh, and increment them for each current mark, each one is an extra padded space that bumps
|
36
|
+
# the position in the final YAML output
|
37
|
+
total_marks = marks.size
|
38
|
+
times << pos+total_marks << scanner.pos+total_marks
|
39
|
+
end
|
40
|
+
quoting = false
|
41
|
+
end
|
42
|
+
when ":",","
|
43
|
+
marks << scanner.pos - 1 unless quoting
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if marks.empty?
|
48
|
+
json.gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do
|
49
|
+
ustr = $1
|
50
|
+
if ustr.start_with?('u')
|
51
|
+
[ustr[1..-1].to_i(16)].pack("U")
|
52
|
+
elsif ustr == '\\'
|
53
|
+
'\\\\'
|
54
|
+
else
|
55
|
+
ustr
|
56
|
+
end
|
57
|
+
end
|
58
|
+
else
|
59
|
+
left_pos = [-1].push(*marks)
|
60
|
+
right_pos = marks << scanner.pos + scanner.rest_size
|
61
|
+
output = []
|
62
|
+
left_pos.each_with_index do |left, i|
|
63
|
+
scanner.pos = left.succ
|
64
|
+
output << scanner.peek(right_pos[i] - scanner.pos + 1).gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do
|
65
|
+
ustr = $1
|
66
|
+
if ustr.start_with?('u')
|
67
|
+
[ustr[1..-1].to_i(16)].pack("U")
|
68
|
+
elsif ustr == '\\'
|
69
|
+
'\\\\'
|
70
|
+
else
|
71
|
+
ustr
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
output = output * " "
|
76
|
+
|
77
|
+
times.each { |i| output[i-1] = ' ' }
|
78
|
+
output.gsub!(/\\\//, '/')
|
79
|
+
output
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|