rsence-pre 2.2.0.28 → 2.2.0.29
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/conf/default_conf.yaml +8 -6
- data/conf/rsence_command_strings.yaml +8 -6
- data/lib/rsence.rb +4 -4
- data/lib/rsence/argv.rb +218 -0
- data/lib/rsence/argv/argv_util.rb +58 -0
- data/lib/rsence/argv/env_check.rb +58 -0
- data/lib/rsence/argv/help_argv.rb +15 -0
- data/lib/rsence/argv/initenv_argv.rb +218 -0
- data/lib/rsence/argv/save_argv.rb +92 -0
- data/lib/rsence/argv/startup_argv.rb +118 -0
- data/lib/rsence/argv/status_argv.rb +132 -0
- data/lib/rsence/argv/test_port.rb +32 -0
- data/lib/{daemon → rsence}/daemon.rb +21 -6
- data/lib/{conf/default.rb → rsence/default_config.rb} +0 -1
- data/lib/{plugins → rsence}/dependencies.rb +0 -0
- data/lib/{util → rsence}/gzstring.rb +0 -0
- data/lib/rsence/http.rb +3 -0
- data/lib/{http → rsence/http}/broker.rb +12 -3
- data/lib/{http → rsence/http}/rackup.rb +0 -0
- data/lib/{http → rsence/http}/request.rb +0 -4
- data/lib/{http → rsence/http}/response.rb +0 -1
- data/lib/{session → rsence}/msg.rb +1 -1
- data/lib/{plugins → rsence}/pluginmanager.rb +2 -2
- data/lib/{plugins → rsence}/plugins.rb +7 -7
- data/lib/{plugins → rsence/plugins}/gui_plugin.rb +0 -0
- data/lib/{plugins → rsence/plugins}/guiparser.rb +0 -0
- data/lib/{plugins → rsence/plugins}/plugin.rb +0 -0
- data/lib/{plugins → rsence/plugins}/plugin_base.rb +0 -0
- data/lib/{plugins → rsence/plugins}/plugin_plugins.rb +0 -0
- data/lib/{plugins → rsence/plugins}/plugin_sqlite_db.rb +0 -0
- data/lib/{plugins → rsence/plugins}/servlet.rb +0 -0
- data/lib/{session → rsence}/sessionmanager.rb +2 -2
- data/lib/{session → rsence}/sessionstorage.rb +1 -1
- data/lib/{daemon → rsence}/sigcomm.rb +0 -0
- data/lib/{transporter → rsence}/transporter.rb +3 -3
- data/lib/{values/hvalue.rb → rsence/value.rb} +0 -0
- data/lib/{values → rsence}/valuemanager.rb +1 -1
- metadata +100 -91
- data/lib/conf/argv.rb +0 -842
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.2.0.
|
1
|
+
2.2.0.29.pre
|
data/conf/default_conf.yaml
CHANGED
@@ -54,6 +54,14 @@
|
|
54
54
|
# network interfaces or the interfaces become available after
|
55
55
|
# RSence is started.
|
56
56
|
:http_delayed_start: 0
|
57
|
+
#
|
58
|
+
# Save plugin and session state every n seconds.
|
59
|
+
# Set to 0 or negative to disable.
|
60
|
+
:autosave_interval: 180 # once every 3 minutes
|
61
|
+
#
|
62
|
+
# Daemon helper files, leave empty for defaults.
|
63
|
+
#:pid_fn: /var/run/rsence.pid
|
64
|
+
#:log_fn: /var/log/rsence.log
|
57
65
|
#
|
58
66
|
# Switches on debug-mode:
|
59
67
|
# - Generates more output
|
@@ -180,12 +188,6 @@
|
|
180
188
|
# Disposable keys, when enabled, changes the value id on each session restoration
|
181
189
|
:disposable_keys: true
|
182
190
|
#
|
183
|
-
# Daemon helper files, leave empty for defaults.
|
184
|
-
:daemon: {
|
185
|
-
#:pid_fn: /var/run/rsence.pid
|
186
|
-
#:log_fn: /var/log/rsence.log
|
187
|
-
}
|
188
|
-
#
|
189
191
|
# Entered by code, empty container
|
190
192
|
:broker_urls: { }
|
191
193
|
|
@@ -63,14 +63,16 @@
|
|
63
63
|
No PID file, unable to check process status.
|
64
64
|
:no_pid_support: |
|
65
65
|
No PID support, unable to check process status.
|
66
|
+
:something_responds:
|
67
|
+
Something responds on <%%= addr_descr %>
|
66
68
|
:no_process_running_but_something_responds: |
|
67
|
-
No process running, but something responds on <%%=
|
69
|
+
No process running, but something responds on <%%= addr_descr %>.
|
68
70
|
:no_process_running_and_nothing_responds: |
|
69
|
-
No process running, and nothing responds on <%%=
|
71
|
+
No process running, and nothing responds on <%%= addr_descr %>.
|
70
72
|
:process_running_and_responds: |
|
71
|
-
Process ID <%%= pid %> is running and responds on <%%=
|
73
|
+
Process ID <%%= pid %> is running and responds on <%%= addr_descr %>.
|
72
74
|
:process_running_but_nothing_responds: |
|
73
|
-
Process ID <%%= pid %> is running, but does not respond on <%%=
|
75
|
+
Process ID <%%= pid %> is running, but does not respond on <%%= addr_descr %>.
|
74
76
|
:saving_message: <
|
75
77
|
Saving session data...
|
76
78
|
:no_pid_unable_to_save: |
|
@@ -214,7 +216,7 @@
|
|
214
216
|
# Help for the ”rsence” command line tool.
|
215
217
|
:help:
|
216
218
|
:head: |+
|
217
|
-
RSence command-line tool, version <%=
|
219
|
+
RSence command-line tool, version <%= @version %>
|
218
220
|
|
219
221
|
:tail: |+
|
220
222
|
RSence is a self-contained web app framework.
|
@@ -258,7 +260,7 @@
|
|
258
260
|
|
259
261
|
--port <number> The port number the http server listens to.
|
260
262
|
|
261
|
-
--
|
263
|
+
--bind <ip address> The IP address or net mask the http server listens to.
|
262
264
|
”0.0.0.0” matches all interfaces.
|
263
265
|
”127.0.0.1” matches the local loopback interface.
|
264
266
|
|
data/lib/rsence.rb
CHANGED
@@ -86,7 +86,7 @@ module RSence
|
|
86
86
|
def self.startup
|
87
87
|
puts "Loading configuration..." if self.args[:verbose]
|
88
88
|
# Use the default configuration:
|
89
|
-
require '
|
89
|
+
require 'rsence/default_config'
|
90
90
|
@@config = Configuration.new(self.args).config
|
91
91
|
|
92
92
|
# RSence runtime configuration data
|
@@ -144,7 +144,7 @@ module RSence
|
|
144
144
|
end
|
145
145
|
|
146
146
|
## Riassence Daemon controls
|
147
|
-
require '
|
147
|
+
require 'rsence/daemon'
|
148
148
|
puts "Starting RSence..." if self.args[:verbose]
|
149
149
|
daemon = HTTPDaemon.new
|
150
150
|
daemon.daemonize!
|
@@ -153,11 +153,11 @@ module RSence
|
|
153
153
|
|
154
154
|
# Includes the Signal Communication utility.
|
155
155
|
# Used to respond via special PID files in the run directory of the environment
|
156
|
-
require '
|
156
|
+
require 'rsence/sigcomm'
|
157
157
|
|
158
158
|
|
159
159
|
# Requires the ARGVParser that functions as the command-line user interface.
|
160
|
-
require '
|
160
|
+
require 'rsence/argv'
|
161
161
|
|
162
162
|
end
|
163
163
|
|
data/lib/rsence/argv.rb
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
## RSence
|
2
|
+
# Copyright 2010 Riassence Inc.
|
3
|
+
# http://riassence.com/
|
4
|
+
#
|
5
|
+
# You should have received a copy of the GNU General Public License along
|
6
|
+
# with this software package. If not, contact licensing@riassence.com
|
7
|
+
##
|
8
|
+
|
9
|
+
|
10
|
+
module RSence
|
11
|
+
|
12
|
+
require 'erb'
|
13
|
+
require 'yaml'
|
14
|
+
|
15
|
+
# @private ARGVParser is the "user interface" as a command-line argument parser.
|
16
|
+
# It parses the command-line arguments and sets up things accordingly.
|
17
|
+
class ARGVParser
|
18
|
+
|
19
|
+
# Returns true if one of the 'start' -type commands were supplied
|
20
|
+
# and the environment seems valid.
|
21
|
+
def startable?; @startable; end
|
22
|
+
|
23
|
+
# Parses an argument of the 'help' command
|
24
|
+
def parse_help_argv
|
25
|
+
if @argv.length >= 2
|
26
|
+
help_cmd = @argv[1].to_sym
|
27
|
+
else
|
28
|
+
help_cmd = :help_main
|
29
|
+
end
|
30
|
+
help( help_cmd )
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
|
34
|
+
# Creates the default and initial @args hash.
|
35
|
+
def init_args
|
36
|
+
@args = {
|
37
|
+
:env_path => Dir.pwd,
|
38
|
+
:conf_files => [ ], # --conf
|
39
|
+
:debug => false, # -d --debug
|
40
|
+
:verbose => false, # -v --verbose
|
41
|
+
:log_fg => false, # -f --log-fg
|
42
|
+
:trace_js => false, # --trace-js
|
43
|
+
:trace_delegate => false, # --trace-delegate
|
44
|
+
:port => nil, # --port
|
45
|
+
:addr => nil, # --addr --bind
|
46
|
+
:server => nil, # --server
|
47
|
+
:reset_ses => false, # -r --reset-sessions
|
48
|
+
:autoupdate => false, # -a --auto-update
|
49
|
+
:latency => 0, # --latency
|
50
|
+
:say => false, # -S --say
|
51
|
+
:http_delayed_start => nil, # --http-delayed-start
|
52
|
+
|
53
|
+
# client_pkg (not supported yet)
|
54
|
+
:client_pkg_no_gzip => false, # --disable-gzip
|
55
|
+
:client_pkg_no_obfuscation => false, # --disable-obfuscation
|
56
|
+
:client_pkg_no_whitespace_removal => false, # --disable-jsmin
|
57
|
+
:suppress_build_messages => true, # --build-report
|
58
|
+
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sets various debug-related options on.
|
63
|
+
def set_debug
|
64
|
+
@args[:debug] = true
|
65
|
+
@args[:verbose] = true
|
66
|
+
@args[:autoupdate] = true
|
67
|
+
@args[:client_pkg_no_obfuscation] = true
|
68
|
+
@args[:client_pkg_no_whitespace_removal] = true
|
69
|
+
@args[:suppress_build_messages] = false
|
70
|
+
end
|
71
|
+
|
72
|
+
# Set the verbose argument on
|
73
|
+
def set_verbose
|
74
|
+
@args[:verbose] = true
|
75
|
+
end
|
76
|
+
|
77
|
+
# Set the foreground logging argument on
|
78
|
+
def set_log_fg
|
79
|
+
@args[:log_fg] = true
|
80
|
+
end
|
81
|
+
|
82
|
+
# Sets the session reset argument on
|
83
|
+
def set_reset_ses
|
84
|
+
@args[:reset_ses] = true
|
85
|
+
end
|
86
|
+
|
87
|
+
# Sets the auto-update argument on
|
88
|
+
def set_autoupdate
|
89
|
+
@args[:autoupdate] = true
|
90
|
+
end
|
91
|
+
|
92
|
+
# Sets the speech synthesis argument on
|
93
|
+
def set_say
|
94
|
+
@args[:say] = true
|
95
|
+
end
|
96
|
+
|
97
|
+
# Error message when an invalid environment is encountered, exits.
|
98
|
+
def invalid_env( env_path=false )
|
99
|
+
env_path = @args[:env_path] unless env_path
|
100
|
+
puts ERB.new( @strs[:messages][:invalid_environment] ).result( binding )
|
101
|
+
exit
|
102
|
+
end
|
103
|
+
|
104
|
+
# Error message when an invalid option is encountered, exits.
|
105
|
+
def invalid_option(arg,chr=false)
|
106
|
+
if chr
|
107
|
+
puts ERB.new( @strs[:messages][:invalid_option_chr] ).result( binding )
|
108
|
+
else
|
109
|
+
puts ERB.new( @strs[:messages][:invalid_option] ).result( binding )
|
110
|
+
end
|
111
|
+
exit
|
112
|
+
end
|
113
|
+
|
114
|
+
require 'rsence/argv/startup_argv'
|
115
|
+
require 'rsence/argv/status_argv'
|
116
|
+
require 'rsence/argv/save_argv'
|
117
|
+
require 'rsence/argv/initenv_argv'
|
118
|
+
require 'rsence/argv/help_argv'
|
119
|
+
require 'rsence/argv/env_check'
|
120
|
+
require 'rsence/argv/test_port'
|
121
|
+
include ArgvUtil
|
122
|
+
|
123
|
+
# Returns the version of RSence
|
124
|
+
def version; @version; end
|
125
|
+
|
126
|
+
# Returns the command the process was started with.
|
127
|
+
def cmd; @cmd; end
|
128
|
+
|
129
|
+
# Returns the parsed optional arguments
|
130
|
+
def args; @args; end
|
131
|
+
|
132
|
+
# Top-level argument parser, checks for command and calls sub-parser, if valid command.
|
133
|
+
def parse_argv
|
134
|
+
if @argv.empty?
|
135
|
+
puts @strs[:help][:help_help]
|
136
|
+
exit
|
137
|
+
else
|
138
|
+
cmd = @argv[0].to_sym
|
139
|
+
cmd = :help if [:h, :'-h', :'--help', :'-help'].include? cmd
|
140
|
+
end
|
141
|
+
if @cmds.include?(cmd)
|
142
|
+
@cmd = cmd
|
143
|
+
unless [:help, :version].include?( cmd )
|
144
|
+
puts "RSence #{@version} -- Ruby #{RUBY_VERSION}"
|
145
|
+
end
|
146
|
+
if cmd == :help
|
147
|
+
parse_help_argv
|
148
|
+
elsif cmd == :version
|
149
|
+
puts version
|
150
|
+
exit
|
151
|
+
elsif [:run,:start,:stop,:restart].include? cmd
|
152
|
+
parse_startup_argv
|
153
|
+
elsif cmd == :status
|
154
|
+
parse_status_argv
|
155
|
+
elsif cmd == :save
|
156
|
+
parse_save_argv
|
157
|
+
elsif cmd == :initenv or cmd == :init
|
158
|
+
parse_initenv_argv
|
159
|
+
end
|
160
|
+
else
|
161
|
+
puts @strs[:help][:unknown] + cmd.to_s.inspect
|
162
|
+
puts @strs[:help][:help_help]
|
163
|
+
exit
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# The constructor sets the @startable flag as false. Use the #parse method with ARGV as the argument to start parsing the ARGV.
|
168
|
+
def initialize
|
169
|
+
@startable = false
|
170
|
+
|
171
|
+
# The RSence version string, read from the VERSION file in
|
172
|
+
# the root directory of RSence.
|
173
|
+
@version = File.read( File.join( SERVER_PATH, 'VERSION' ) ).strip
|
174
|
+
|
175
|
+
# Makes various commands available depending on the platform.
|
176
|
+
# The status/start/stop/restart/save -commands depend on an operating
|
177
|
+
# system that fully implements POSIX standard signals.
|
178
|
+
# These are necessary to send signals to the background process.
|
179
|
+
if not RSence.pid_support?
|
180
|
+
@cmds = [ :run, :init, :initenv, :version, :help ]
|
181
|
+
else
|
182
|
+
@cmds = [ :run, :status, :start, :stop, :restart, :save,
|
183
|
+
:init, :initenv, :version, :help ]
|
184
|
+
end
|
185
|
+
|
186
|
+
help_avail_cmds = @cmds.map{|cmd|cmd.to_s}.join("\n ")
|
187
|
+
|
188
|
+
# Load the strings from the strings file.
|
189
|
+
strs_path = File.join( SERVER_PATH, 'conf',
|
190
|
+
'rsence_command_strings.yaml' )
|
191
|
+
strs_data = File.read( strs_path )
|
192
|
+
|
193
|
+
strs_data = ERB.new( strs_data ).result( binding )
|
194
|
+
|
195
|
+
@strs = YAML.load( strs_data )
|
196
|
+
|
197
|
+
@strs[:help][:run] += @strs[:help][:path]+@strs[:help][:options]
|
198
|
+
@strs[:help][:start] += @strs[:help][:path]+@strs[:help][:options]
|
199
|
+
@strs[:help][:stop] += @strs[:help][:path]+@strs[:help][:options]
|
200
|
+
@strs[:help][:restart] += @strs[:help][:path]+@strs[:help][:options]
|
201
|
+
@strs[:help][:status] += @strs[:help][:path]
|
202
|
+
@strs[:help][:save] += @strs[:help][:path]
|
203
|
+
end
|
204
|
+
|
205
|
+
# Entry point for ARGV parsing
|
206
|
+
def parse( argv )
|
207
|
+
@argv = argv
|
208
|
+
parse_argv
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
# @private The ARGVParser instance and its startup
|
214
|
+
@@argv_parser = ARGVParser.new
|
215
|
+
@@argv_parser.parse( ARGV )
|
216
|
+
|
217
|
+
end
|
218
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module RSence
|
2
|
+
module ArgvUtil
|
3
|
+
|
4
|
+
# Tests for a valid environment
|
5
|
+
def valid_env?( arg, quiet=false )
|
6
|
+
|
7
|
+
# Checks, if the top-level path exists and is a directory
|
8
|
+
path = File.expand_path( arg )
|
9
|
+
if not File.exists?( path )
|
10
|
+
puts ERB.new( @strs[:messages][:no_such_directory] ).result( binding ) unless quiet
|
11
|
+
return false
|
12
|
+
elsif not File.directory?( path )
|
13
|
+
puts ERB.new( @strs[:messages][:not_a_directory] ).result( binding ) unless quiet
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
|
17
|
+
# Checks, if the conf path exists and is a directory
|
18
|
+
conf_path = File.join( path, 'conf' )
|
19
|
+
if not File.exists?( conf_path )
|
20
|
+
puts ERB.new( @strs[:messages][:missing_conf_directory] ).result( binding ) unless quiet
|
21
|
+
return false
|
22
|
+
elsif not File.directory?( conf_path )
|
23
|
+
puts ERB.new( @strs[:messages][:invalid_conf_directory] ).result( binding ) unless quiet
|
24
|
+
return false
|
25
|
+
end
|
26
|
+
|
27
|
+
# Checks, if the conf/config.yaml file exists and is a directory
|
28
|
+
conf_file = File.join( path, 'conf', 'config.yaml' )
|
29
|
+
if not File.exists?(conf_file)
|
30
|
+
puts ERB.new( @strs[:messages][:missing_conf_file] ).result( binding ) unless quiet
|
31
|
+
return false
|
32
|
+
elsif not File.file?( conf_file )
|
33
|
+
puts ERB.new( @strs[:messages][:invalid_conf_file_not_file] ).result( binding ) unless quiet
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
|
37
|
+
# Checks, if the plugins path exists and is a directory
|
38
|
+
plugin_path = File.join( path, 'plugins' )
|
39
|
+
if not File.exists?( plugin_path )
|
40
|
+
warn ERB.new( @strs[:messages][:warn_no_plugin_directory_in_project] ).result( binding ) if @args[:verbose]
|
41
|
+
elsif not File.directory?( plugin_path )
|
42
|
+
puts ERB.new( @strs[:messages][:plugin_directory_not_a_directory] ).result( binding ) unless quiet
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
|
46
|
+
# Checks (and automatically creates if missing) the run, db and log directories
|
47
|
+
['run','log','db'].each do |dir_name|
|
48
|
+
dir_path = File.join( path, dir_name )
|
49
|
+
unless File.exists?( dir_path )
|
50
|
+
warn ERB.new( @strs[:messages][:warn_no_directory_creating] ).result( binding ) if @args[:verbose]
|
51
|
+
Dir.mkdir( dir_path )
|
52
|
+
end
|
53
|
+
end
|
54
|
+
return true
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module RSence
|
2
|
+
module ArgvUtil
|
3
|
+
|
4
|
+
# Tests for a valid environment
|
5
|
+
def valid_env?( arg, quiet=false )
|
6
|
+
|
7
|
+
# Checks, if the top-level path exists and is a directory
|
8
|
+
path = File.expand_path( arg )
|
9
|
+
if not File.exists?( path )
|
10
|
+
puts ERB.new( @strs[:messages][:no_such_directory] ).result( binding ) unless quiet
|
11
|
+
return false
|
12
|
+
elsif not File.directory?( path )
|
13
|
+
puts ERB.new( @strs[:messages][:not_a_directory] ).result( binding ) unless quiet
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
|
17
|
+
# Checks, if the conf path exists and is a directory
|
18
|
+
conf_path = File.join( path, 'conf' )
|
19
|
+
if not File.exists?( conf_path )
|
20
|
+
puts ERB.new( @strs[:messages][:missing_conf_directory] ).result( binding ) unless quiet
|
21
|
+
return false
|
22
|
+
elsif not File.directory?( conf_path )
|
23
|
+
puts ERB.new( @strs[:messages][:invalid_conf_directory] ).result( binding ) unless quiet
|
24
|
+
return false
|
25
|
+
end
|
26
|
+
|
27
|
+
# Checks, if the conf/config.yaml file exists and is a directory
|
28
|
+
conf_file = File.join( path, 'conf', 'config.yaml' )
|
29
|
+
if not File.exists?(conf_file)
|
30
|
+
puts ERB.new( @strs[:messages][:missing_conf_file] ).result( binding ) unless quiet
|
31
|
+
return false
|
32
|
+
elsif not File.file?( conf_file )
|
33
|
+
puts ERB.new( @strs[:messages][:invalid_conf_file_not_file] ).result( binding ) unless quiet
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
|
37
|
+
# Checks, if the plugins path exists and is a directory
|
38
|
+
plugin_path = File.join( path, 'plugins' )
|
39
|
+
if not File.exists?( plugin_path )
|
40
|
+
warn ERB.new( @strs[:messages][:warn_no_plugin_directory_in_project] ).result( binding ) if @args[:verbose]
|
41
|
+
elsif not File.directory?( plugin_path )
|
42
|
+
puts ERB.new( @strs[:messages][:plugin_directory_not_a_directory] ).result( binding ) unless quiet
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
|
46
|
+
# Checks (and automatically creates if missing) the run, db and log directories
|
47
|
+
['run','log','db'].each do |dir_name|
|
48
|
+
dir_path = File.join( path, dir_name )
|
49
|
+
unless File.exists?( dir_path )
|
50
|
+
warn ERB.new( @strs[:messages][:warn_no_directory_creating] ).result( binding ) if @args[:verbose]
|
51
|
+
Dir.mkdir( dir_path )
|
52
|
+
end
|
53
|
+
end
|
54
|
+
return true
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module RSence
|
2
|
+
module ArgvUtil
|
3
|
+
# Main parser for the help command
|
4
|
+
def help( cmd )
|
5
|
+
cmd.to_sym! if cmd.class != Symbol
|
6
|
+
puts @strs[:help][:head]
|
7
|
+
if @strs[:help].has_key?(cmd)
|
8
|
+
puts @strs[:help][cmd]
|
9
|
+
else
|
10
|
+
puts @strs[:help][:help_main]
|
11
|
+
end
|
12
|
+
puts @strs[:help][:tail]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|