adhearsion 2.0.0.beta1 → 2.0.0.rc1
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/.travis.yml +2 -4
- data/CHANGELOG.md +34 -4
- data/README.markdown +2 -1
- data/Rakefile +22 -1
- data/adhearsion.gemspec +1 -0
- data/bin/ahn +0 -2
- data/features/cli_daemon.feature +2 -0
- data/features/cli_restart.feature +19 -0
- data/features/cli_start.feature +4 -6
- data/features/cli_stop.feature +3 -0
- data/features/step_definitions/app_generator_steps.rb +2 -0
- data/features/step_definitions/cli_steps.rb +2 -0
- data/features/support/aruba_helper.rb +2 -0
- data/features/support/env.rb +8 -46
- data/features/support/utils.rb +2 -0
- data/lib/adhearsion.rb +4 -6
- data/lib/adhearsion/call.rb +71 -17
- data/lib/adhearsion/call_controller.rb +25 -14
- data/lib/adhearsion/call_controller/dial.rb +34 -15
- data/lib/adhearsion/call_controller/input.rb +186 -144
- data/lib/adhearsion/call_controller/output.rb +10 -6
- data/lib/adhearsion/call_controller/record.rb +11 -13
- data/lib/adhearsion/call_controller/utility.rb +2 -0
- data/lib/adhearsion/calls.rb +4 -2
- data/lib/adhearsion/cli.rb +4 -0
- data/lib/adhearsion/cli_commands.rb +8 -2
- data/lib/adhearsion/configuration.rb +7 -3
- data/lib/adhearsion/console.rb +17 -17
- data/lib/adhearsion/events.rb +10 -4
- data/lib/adhearsion/foundation.rb +9 -0
- data/lib/adhearsion/foundation/custom_daemonizer.rb +3 -1
- data/lib/adhearsion/foundation/exception_handler.rb +2 -0
- data/lib/adhearsion/foundation/libc.rb +2 -0
- data/lib/adhearsion/foundation/object.rb +3 -0
- data/lib/adhearsion/foundation/thread_safety.rb +5 -11
- data/lib/adhearsion/generators.rb +2 -0
- data/lib/adhearsion/generators/app/app_generator.rb +2 -0
- data/lib/adhearsion/generators/app/templates/README.md +9 -0
- data/lib/adhearsion/generators/app/templates/config/adhearsion.rb +38 -16
- data/lib/adhearsion/generators/app/templates/config/environment.rb +2 -0
- data/lib/adhearsion/generators/app/templates/lib/simon_game.rb +5 -3
- data/lib/adhearsion/generators/controller/controller_generator.rb +2 -0
- data/lib/adhearsion/generators/controller/templates/lib/controller.rb +2 -0
- data/lib/adhearsion/generators/controller/templates/spec/controller_spec.rb +2 -0
- data/lib/adhearsion/generators/generator.rb +3 -1
- data/lib/adhearsion/generators/plugin/plugin_generator.rb +2 -0
- data/lib/adhearsion/initializer.rb +31 -17
- data/lib/adhearsion/linux_proc_name.rb +2 -0
- data/lib/adhearsion/logging.rb +5 -3
- data/lib/adhearsion/menu_dsl.rb +2 -0
- data/lib/adhearsion/menu_dsl/calculated_match.rb +2 -0
- data/lib/adhearsion/menu_dsl/calculated_match_collection.rb +2 -0
- data/lib/adhearsion/menu_dsl/fixnum_match_calculator.rb +2 -0
- data/lib/adhearsion/menu_dsl/match_calculator.rb +2 -0
- data/lib/adhearsion/menu_dsl/menu.rb +58 -4
- data/lib/adhearsion/menu_dsl/menu_builder.rb +14 -1
- data/lib/adhearsion/menu_dsl/range_match_calculator.rb +4 -1
- data/lib/adhearsion/menu_dsl/string_match_calculator.rb +2 -0
- data/lib/adhearsion/outbound_call.rb +2 -0
- data/lib/adhearsion/plugin.rb +9 -7
- data/lib/adhearsion/plugin/collection.rb +3 -1
- data/lib/adhearsion/plugin/initializer.rb +3 -1
- data/lib/adhearsion/process.rb +8 -2
- data/lib/adhearsion/punchblock_plugin.rb +3 -1
- data/lib/adhearsion/punchblock_plugin/initializer.rb +34 -11
- data/lib/adhearsion/router.rb +4 -2
- data/lib/adhearsion/router/route.rb +2 -0
- data/lib/adhearsion/script_ahn_loader.rb +2 -0
- data/lib/adhearsion/tasks.rb +2 -0
- data/lib/adhearsion/tasks/configuration.rb +2 -0
- data/lib/adhearsion/tasks/debugging.rb +8 -0
- data/lib/adhearsion/tasks/environment.rb +2 -0
- data/lib/adhearsion/tasks/plugins.rb +2 -0
- data/lib/adhearsion/tasks/testing.rb +2 -0
- data/lib/adhearsion/version.rb +3 -1
- data/pre-commit +2 -0
- data/spec/adhearsion/call_controller/dial_spec.rb +114 -25
- data/spec/adhearsion/call_controller/input_spec.rb +192 -169
- data/spec/adhearsion/call_controller/output_spec.rb +26 -12
- data/spec/adhearsion/call_controller/record_spec.rb +29 -77
- data/spec/adhearsion/call_controller/utility_spec.rb +69 -0
- data/spec/adhearsion/call_controller_spec.rb +90 -15
- data/spec/adhearsion/call_spec.rb +92 -24
- data/spec/adhearsion/calls_spec.rb +9 -7
- data/spec/adhearsion/configuration_spec.rb +58 -56
- data/spec/adhearsion/console_spec.rb +4 -2
- data/spec/adhearsion/events_spec.rb +9 -7
- data/spec/adhearsion/generators_spec.rb +3 -1
- data/spec/adhearsion/initializer_spec.rb +16 -14
- data/spec/adhearsion/logging_spec.rb +11 -9
- data/spec/adhearsion/menu_dsl/calculated_match_collection_spec.rb +6 -4
- data/spec/adhearsion/menu_dsl/calculated_match_spec.rb +6 -4
- data/spec/adhearsion/menu_dsl/fixnum_match_calculator_spec.rb +3 -1
- data/spec/adhearsion/menu_dsl/match_calculator_spec.rb +2 -0
- data/spec/adhearsion/menu_dsl/menu_builder_spec.rb +42 -11
- data/spec/adhearsion/menu_dsl/menu_spec.rb +197 -36
- data/spec/adhearsion/menu_dsl/range_match_calculator_spec.rb +4 -2
- data/spec/adhearsion/menu_dsl/string_match_calculator_spec.rb +5 -3
- data/spec/adhearsion/outbound_call_spec.rb +7 -5
- data/spec/adhearsion/plugin_spec.rb +19 -15
- data/spec/adhearsion/process_spec.rb +12 -7
- data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +35 -15
- data/spec/adhearsion/punchblock_plugin_spec.rb +4 -1
- data/spec/adhearsion/router/route_spec.rb +8 -6
- data/spec/adhearsion/router_spec.rb +12 -10
- data/spec/adhearsion_spec.rb +13 -2
- data/spec/capture_warnings.rb +33 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/call_controller_test_helpers.rb +2 -4
- data/spec/support/initializer_stubs.rb +8 -5
- data/spec/support/logging_helpers.rb +2 -0
- data/spec/support/punchblock_mocks.rb +2 -0
- metadata +84 -71
- data/EVENTS +0 -11
- data/lib/adhearsion/call_controller/menu.rb +0 -124
- data/lib/adhearsion/foundation/all.rb +0 -8
- data/spec/adhearsion/call_controller/menu_spec.rb +0 -120
- data/spec/adhearsion/menu_dsl_spec.rb +0 -12
@@ -1,27 +1,28 @@
|
|
1
|
-
#
|
2
|
-
# - Execute rake adhearsion:config:desc to get the configuration options
|
3
|
-
# - Execute rake adhearsion:config:show to get the configuration values
|
4
|
-
#
|
5
|
-
# To update a plugin configuration you can write either:
|
6
|
-
#
|
7
|
-
# * Option 1
|
8
|
-
# Adhearsion.config.<plugin-name> do |config|
|
9
|
-
# config.<key> = <value>
|
10
|
-
# end
|
11
|
-
#
|
12
|
-
# * Option 2
|
13
|
-
# Adhearsion.config do |config|
|
14
|
-
# config.<plugin-name>.<key> = <value>
|
15
|
-
# end
|
1
|
+
# encoding: utf-8
|
16
2
|
|
17
3
|
Adhearsion.config do |config|
|
18
4
|
|
5
|
+
# Centralized way to specify any Adhearsion platform or plugin configuration
|
6
|
+
# - Execute rake config:show to view the active configuration values
|
7
|
+
#
|
8
|
+
# To update a plugin configuration you can write either:
|
9
|
+
#
|
10
|
+
# * Option 1
|
11
|
+
# Adhearsion.config.<plugin-name> do |config|
|
12
|
+
# config.<key> = <value>
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# * Option 2
|
16
|
+
# Adhearsion.config do |config|
|
17
|
+
# config.<plugin-name>.<key> = <value>
|
18
|
+
# end
|
19
|
+
|
19
20
|
config.development do |dev|
|
20
21
|
dev.platform.logging.level = :debug
|
21
22
|
end
|
22
23
|
|
23
24
|
##
|
24
|
-
# Use with Voxeo PRISM
|
25
|
+
# Use with Rayo (eg Voxeo PRISM)
|
25
26
|
#
|
26
27
|
# config.punchblock.username = "" # Your XMPP JID for use with Rayo
|
27
28
|
# config.punchblock.password = "" # Your XMPP password
|
@@ -36,6 +37,27 @@ Adhearsion.config do |config|
|
|
36
37
|
# config.punchblock.port = 5038 # Your AMI port
|
37
38
|
end
|
38
39
|
|
40
|
+
Adhearsion::Events.draw do
|
41
|
+
|
42
|
+
# Register global handlers for events
|
43
|
+
#
|
44
|
+
# eg. Handling Punchblock events
|
45
|
+
# punchblock do |event|
|
46
|
+
# ...
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# eg Handling PeerStatus AMI events
|
50
|
+
# ami :name => 'PeerStatus' do |event|
|
51
|
+
# ...
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
end
|
55
|
+
|
39
56
|
Adhearsion.router do
|
57
|
+
|
58
|
+
#
|
59
|
+
# Specify your call routes, directing calls with particular attributes to a controller
|
60
|
+
#
|
61
|
+
|
40
62
|
route 'default', SimonGame
|
41
63
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
class SimonGame < Adhearsion::CallController
|
2
4
|
def run
|
3
5
|
answer
|
@@ -19,7 +21,7 @@ class SimonGame < Adhearsion::CallController
|
|
19
21
|
|
20
22
|
def say_number
|
21
23
|
update_number
|
22
|
-
|
24
|
+
say @number
|
23
25
|
end
|
24
26
|
|
25
27
|
def collect_attempt
|
@@ -28,9 +30,9 @@ class SimonGame < Adhearsion::CallController
|
|
28
30
|
|
29
31
|
def verify_attempt
|
30
32
|
if attempt_correct?
|
31
|
-
|
33
|
+
say 'good'
|
32
34
|
else
|
33
|
-
|
35
|
+
say "#{@number.length - 1} times wrong, try again smarty"
|
34
36
|
reset
|
35
37
|
end
|
36
38
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
begin
|
2
4
|
require 'thor/group'
|
3
5
|
rescue LoadError
|
@@ -66,7 +68,7 @@ module Adhearsion
|
|
66
68
|
def self.generator_name
|
67
69
|
@generator_name ||= begin
|
68
70
|
if generator = name.to_s.split('::').last
|
69
|
-
generator.sub!
|
71
|
+
generator.sub!(/Generator$/, '')
|
70
72
|
generator.underscore
|
71
73
|
end
|
72
74
|
end
|
@@ -1,5 +1,8 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'adhearsion/punchblock_plugin'
|
2
4
|
require 'adhearsion/linux_proc_name'
|
5
|
+
require 'rbconfig'
|
3
6
|
|
4
7
|
module Adhearsion
|
5
8
|
class Initializer
|
@@ -41,6 +44,7 @@ module Adhearsion
|
|
41
44
|
initialize_log_paths
|
42
45
|
daemonize! if should_daemonize?
|
43
46
|
start_logging
|
47
|
+
debugging_log
|
44
48
|
launch_console if need_console?
|
45
49
|
catch_termination_signal
|
46
50
|
create_pid_file
|
@@ -52,10 +56,9 @@ module Adhearsion
|
|
52
56
|
run_plugins
|
53
57
|
trigger_after_initialized_hooks
|
54
58
|
|
55
|
-
if Adhearsion.status == :booting
|
56
|
-
|
57
|
-
|
58
|
-
end
|
59
|
+
Adhearsion::Process.booted if Adhearsion.status == :booting
|
60
|
+
|
61
|
+
logger.info "Adhearsion v#{Adhearsion::VERSION} initialized in \"#{Adhearsion.config.platform.environment}\"!" if Adhearsion.status == :running
|
59
62
|
|
60
63
|
# This method will block until all important threads have finished.
|
61
64
|
# When it does, the process will exit.
|
@@ -63,25 +66,37 @@ module Adhearsion
|
|
63
66
|
self
|
64
67
|
end
|
65
68
|
|
69
|
+
def debugging_items
|
70
|
+
[
|
71
|
+
"OS: #{RbConfig::CONFIG['host_os']} - RUBY: #{RUBY_ENGINE} #{RUBY_VERSION}",
|
72
|
+
"Environment: #{ENV.inspect}",
|
73
|
+
Adhearsion.config.description(:all),
|
74
|
+
"Gem versions: #{Gem.loaded_specs.inject([]) { |c,g| c << "#{g[0]} #{g[1].version}" }}"
|
75
|
+
]
|
76
|
+
end
|
77
|
+
|
78
|
+
def debugging_log
|
79
|
+
debugging_items.each do |item|
|
80
|
+
logger.trace item
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
66
84
|
def update_rails_env_var
|
67
85
|
env = ENV['AHN_ENV']
|
68
86
|
if env && Adhearsion.config.valid_environment?(env.to_sym)
|
69
|
-
|
70
|
-
logger.info "
|
71
|
-
else
|
72
|
-
logger.warn "Setting RAILS_ENV variable to <#{env}>"
|
87
|
+
unless ENV['RAILS_ENV']
|
88
|
+
logger.info "Copying AHN_ENV (#{env}) to RAILS_ENV"
|
73
89
|
ENV['RAILS_ENV'] = env
|
74
90
|
end
|
75
91
|
else
|
76
|
-
|
77
|
-
if env
|
78
|
-
logger.info "Using the configured value for RAILS_ENV : <#{env}>"
|
79
|
-
else
|
92
|
+
unless ENV['RAILS_ENV']
|
80
93
|
env = Adhearsion.config.platform.environment.to_s
|
81
|
-
|
94
|
+
ENV['AHN_ENV'] = env
|
95
|
+
logger.info "Setting RAILS_ENV to \"#{env}\""
|
82
96
|
ENV['RAILS_ENV'] = env
|
83
97
|
end
|
84
98
|
end
|
99
|
+
logger.warn "AHN_ENV(#{ENV['AHN_ENV']}) does not match RAILS_ENV(#{ENV['RAILS_ENV']})!" unless ENV['RAILS_ENV'] == ENV['AHN_ENV']
|
85
100
|
env
|
86
101
|
end
|
87
102
|
|
@@ -202,7 +217,7 @@ module Adhearsion
|
|
202
217
|
|
203
218
|
def daemonize!
|
204
219
|
logger.info "Daemonizing now!"
|
205
|
-
logger.
|
220
|
+
logger.debug "Creating PID file #{pid_file}"
|
206
221
|
extend Adhearsion::CustomDaemonizer
|
207
222
|
daemonize resolve_log_file_path
|
208
223
|
end
|
@@ -211,7 +226,6 @@ module Adhearsion
|
|
211
226
|
Adhearsion::Process.important_threads << Thread.new do
|
212
227
|
catching_standard_errors do
|
213
228
|
Adhearsion::Console.run
|
214
|
-
Adhearsion::Process.shutdown
|
215
229
|
end
|
216
230
|
end
|
217
231
|
end
|
@@ -241,8 +255,8 @@ module Adhearsion
|
|
241
255
|
end
|
242
256
|
|
243
257
|
def initialize_exception_logger
|
244
|
-
Events.register_handler :exception do |e|
|
245
|
-
logger.error e
|
258
|
+
Events.register_handler :exception do |e, l|
|
259
|
+
(l || logger).error e
|
246
260
|
end
|
247
261
|
end
|
248
262
|
|
data/lib/adhearsion/logging.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'logging'
|
2
4
|
|
3
5
|
module Adhearsion
|
@@ -11,14 +13,14 @@ module Adhearsion
|
|
11
13
|
|
12
14
|
::Logging.color_scheme 'bright',
|
13
15
|
:levels => {
|
16
|
+
:debug => :magenta,
|
14
17
|
:info => :green,
|
15
18
|
:warn => :yellow,
|
16
19
|
:error => :red,
|
17
20
|
:fatal => [:white, :on_red]
|
18
21
|
},
|
19
|
-
:date => :blue,
|
20
|
-
:logger => :cyan
|
21
|
-
:message => :magenta
|
22
|
+
:date => [:bold, :blue],
|
23
|
+
:logger => :cyan
|
22
24
|
|
23
25
|
def adhearsion_pattern
|
24
26
|
'[%d] %-5l %c: %m\n'
|
data/lib/adhearsion/menu_dsl.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
module Adhearsion
|
2
4
|
module MenuDSL
|
3
5
|
|
@@ -6,21 +8,40 @@ module Adhearsion
|
|
6
8
|
DEFAULT_MAX_NUMBER_OF_TRIES = 1
|
7
9
|
DEFAULT_TIMEOUT = 5
|
8
10
|
|
9
|
-
|
11
|
+
InvalidStructureError = Class.new StandardError
|
12
|
+
|
13
|
+
attr_reader :builder, :timeout, :tries_count, :max_number_of_tries, :terminator, :limit, :interruptible, :status
|
10
14
|
|
11
15
|
def initialize(options = {}, &block)
|
12
16
|
@tries_count = 0 # Counts the number of tries the menu's been executed
|
13
17
|
@timeout = options[:timeout] || DEFAULT_TIMEOUT
|
14
18
|
@max_number_of_tries = options[:tries] || DEFAULT_MAX_NUMBER_OF_TRIES
|
19
|
+
@terminator = options[:terminator].to_s
|
20
|
+
@limit = options[:limit]
|
21
|
+
@interruptible = options.has_key?(:interruptible) ? options[:interruptible] : true
|
15
22
|
@builder = MenuDSL::MenuBuilder.new
|
23
|
+
@terminated = false
|
16
24
|
|
17
|
-
@builder.build
|
25
|
+
@builder.build(&block) if block
|
18
26
|
|
19
27
|
initialize_digit_buffer
|
20
28
|
end
|
21
29
|
|
30
|
+
def validate(mode = nil)
|
31
|
+
case mode
|
32
|
+
when :basic
|
33
|
+
@terminator.present? || !!@limit || raise(InvalidStructureError, "You must specify at least one of limit or terminator")
|
34
|
+
else
|
35
|
+
@builder.has_matchers? || raise(InvalidStructureError, "You must specify one or more matchers")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
22
39
|
def <<(other)
|
23
|
-
|
40
|
+
if other == terminator
|
41
|
+
@terminated = true
|
42
|
+
else
|
43
|
+
digit_buffer << other
|
44
|
+
end
|
24
45
|
end
|
25
46
|
|
26
47
|
def digit_buffer
|
@@ -30,6 +51,7 @@ module Adhearsion
|
|
30
51
|
def digit_buffer_string
|
31
52
|
digit_buffer.to_s
|
32
53
|
end
|
54
|
+
alias :result :digit_buffer_string
|
33
55
|
|
34
56
|
def digit_buffer_empty?
|
35
57
|
digit_buffer.empty?
|
@@ -38,6 +60,11 @@ module Adhearsion
|
|
38
60
|
def continue
|
39
61
|
return get_another_digit_or_timeout! if digit_buffer_empty?
|
40
62
|
|
63
|
+
return menu_terminated! if @terminated
|
64
|
+
return menu_limit_reached! if limit && digit_buffer.size >= limit
|
65
|
+
|
66
|
+
return menu_validator_terminated! if execute_validator_hook
|
67
|
+
|
41
68
|
calculated_matches = builder.calculate_matches_for digit_buffer_string
|
42
69
|
|
43
70
|
if calculated_matches.exact_match_count >= 1
|
@@ -47,7 +74,7 @@ module Adhearsion
|
|
47
74
|
else
|
48
75
|
get_another_digit_or_finish! first_exact_match.match_payload, first_exact_match.query
|
49
76
|
end
|
50
|
-
elsif calculated_matches.potential_match_count >= 1
|
77
|
+
elsif calculated_matches.potential_match_count >= 1 || !@builder.has_matchers?
|
51
78
|
get_another_digit_or_timeout!
|
52
79
|
else
|
53
80
|
invalid!
|
@@ -75,6 +102,10 @@ module Adhearsion
|
|
75
102
|
builder.execute_hook_for :failure, digit_buffer_string
|
76
103
|
end
|
77
104
|
|
105
|
+
def execute_validator_hook
|
106
|
+
builder.execute_hook_for :validator, digit_buffer_string
|
107
|
+
end
|
108
|
+
|
78
109
|
protected
|
79
110
|
|
80
111
|
# If you're using a more complex class in subclasses, you may want to override this method in addition to the
|
@@ -84,18 +115,37 @@ module Adhearsion
|
|
84
115
|
end
|
85
116
|
|
86
117
|
def invalid!
|
118
|
+
@status = :invalid
|
87
119
|
MenuResultInvalid.new
|
88
120
|
end
|
89
121
|
|
90
122
|
def menu_result_found!(match_object, new_extension)
|
123
|
+
@status = :matched
|
91
124
|
MenuResultFound.new(match_object, new_extension)
|
92
125
|
end
|
93
126
|
|
127
|
+
def menu_terminated!
|
128
|
+
@status = :terminated
|
129
|
+
MenuTerminated.new
|
130
|
+
end
|
131
|
+
|
132
|
+
def menu_validator_terminated!
|
133
|
+
@status = :validator_terminated
|
134
|
+
MenuValidatorTerminated.new
|
135
|
+
end
|
136
|
+
|
137
|
+
def menu_limit_reached!
|
138
|
+
@status = :limited
|
139
|
+
MenuLimitReached.new
|
140
|
+
end
|
141
|
+
|
94
142
|
def get_another_digit_or_finish!(match_payload, new_extension)
|
143
|
+
@status = :multi_matched
|
95
144
|
MenuGetAnotherDigitOrFinish.new(match_payload, new_extension)
|
96
145
|
end
|
97
146
|
|
98
147
|
def get_another_digit_or_timeout!
|
148
|
+
@status = :potential
|
99
149
|
MenuGetAnotherDigitOrTimeout.new
|
100
150
|
end
|
101
151
|
|
@@ -128,6 +178,10 @@ module Adhearsion
|
|
128
178
|
|
129
179
|
MenuResultInvalid = Class.new MenuResult
|
130
180
|
|
181
|
+
MenuTerminated = Class.new MenuResultDone
|
182
|
+
MenuValidatorTerminated = Class.new MenuResultDone
|
183
|
+
MenuLimitReached = Class.new MenuResultDone
|
184
|
+
|
131
185
|
# For our default purpose, we need the digit_buffer to behave much like a normal String except that it should
|
132
186
|
# handle its own resetting (clearing)
|
133
187
|
class ClearableStringBuffer < String
|