rsence-pre 2.2.0.28 → 2.2.0.29
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/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
|