incline 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ require 'securerandom'
2
+
3
+ module Incline
4
+ class CLI
5
+ class Prepare
6
+
7
+ FLY_TRAP_PING = "ft-" + SecureRandom.urlsafe_base64(9)
8
+
9
+ FLY_TRAP_ROUTES = <<-EOCFG
10
+ Rails.application.routes.draw do
11
+ # [#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}]
12
+ # This route defines the ping address, which is a unique address generated specifically for this web server.
13
+ # No other web server or host knows it.
14
+ # If you decide to change this address, you will want to update the associated crontab job as well.
15
+ get '/#{FLY_TRAP_PING}', controller: 'trap', action: 'ping'
16
+
17
+ # These two simply pour everything else into the trap controller for logging.
18
+ get '/(:trigger)', trigger: /.+/, controller: 'trap', action: 'index'
19
+ root 'trap#index'
20
+ end
21
+ EOCFG
22
+
23
+ FLY_TRAP_SECRETS = <<-EOCFG
24
+ # [#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}]
25
+ # The secrets were generated specifically for this application installation.
26
+ test:
27
+ secret_key_base: #{SecureRandom.urlsafe_base64(60)}
28
+ development:
29
+ secret_key_base: #{SecureRandom.urlsafe_base64(60)}
30
+ production:
31
+ secret_key_base: #{SecureRandom.urlsafe_base64(60)}
32
+ EOCFG
33
+
34
+ private_constant :FLY_TRAP_PING, :FLY_TRAP_ROUTES, :FLY_TRAP_SECRETS
35
+
36
+ def flytrap_path
37
+ "http://#{@options[:host]}/#{FLY_TRAP_PING}"
38
+ end
39
+
40
+ private
41
+
42
+ def install_flytrap(shell)
43
+ shell.with_stat('Installing flytrap') do
44
+ shell.exec "if [ ! -d #{shell.home_path}/apps ]; then mkdir #{shell.home_path}/apps; fi"
45
+ shell.exec "chmod 775 #{shell.home_path}/apps"
46
+ # install the fly_trap app and write the new routes.rb file.
47
+ shell.exec "git clone https://github.com/barkerest/fly_trap.git #{shell.home_path}/apps/fly_trap"
48
+ shell.write_file "#{shell.home_path}/apps/fly_trap/config/routes.rb", FLY_TRAP_ROUTES
49
+ shell.write_file "#{shell.home_path}/apps/fly_trap/config/secrets.yml", FLY_TRAP_SECRETS
50
+ # prep the app.
51
+ shell.exec "cd #{shell.home_path}/apps/fly_trap"
52
+ shell.exec "bundle install --deployment"
53
+ shell.exec "bundle exec rake db:migrate:reset RAILS_ENV=production"
54
+ shell.exec "bundle exec rake assets:precompile RAILS_ENV=production RAILS_GROUPS=assets RAILS_RELATIVE_URL_ROOT=\"/\""
55
+ shell.exec "cd #{shell.home_path}"
56
+ # generate the cron job
57
+ shell.exec "(crontab -l; echo \"*/5 * * * * curl http://localhost/#{FLY_TRAP_PING} >/dev/null 2>&1\";) | crontab -"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,37 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+
6
+ private
7
+
8
+ def install_passenger(shell)
9
+ distros = {
10
+ '12.04' => 'precise',
11
+ '12.10' => 'quantal',
12
+ '13.04' => 'raring',
13
+ '13.10' => 'saucy',
14
+ '14.04' => 'trusty',
15
+ '14.10' => 'utopic',
16
+ '15.04' => 'vivid',
17
+ '15.10' => 'wily',
18
+ '16.04' => 'xenial',
19
+ '16.10' => 'yakkety',
20
+ '17.04' => 'zesty'
21
+ }
22
+
23
+ distro = distros[host_info['VERSION_ID']]
24
+ shell.with_stat('Installing Phusion Passenger') do
25
+ shell.sudo_exec 'apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7'
26
+ shell.apt_get 'install apt-transport-https ca-certificates'
27
+ shell.sudo_exec "echo deb https://oss-binaries.phusionpassenger.com/apt/passenger #{distro} main > /etc/apt/sources.list.d/passenger.list"
28
+ shell.apt_get 'update'
29
+ shell.apt_get 'install nginx-extras passenger'
30
+ shell.sudo_exec_ignore_code 'systemctl stop nginx'
31
+ shell.sudo_exec 'systemctl start nginx'
32
+ shell.sudo_exec 'systemctl enable nginx'
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+ private
6
+
7
+ def install_prereqs(shell)
8
+ shell.with_stat('Installing prerequisites') do
9
+ shell.apt_get 'install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties libffi-dev'
10
+ end
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Incline
2
+ class CLI
3
+ class Prepare
4
+
5
+ private
6
+
7
+ def install_rails(shell)
8
+ shell.with_stat("Installing Rails #{@options[:rails_version]}") do
9
+ shell.exec "gem install rails -v #{@options[:rails_version]}"
10
+ shell.exec 'rbenv rehash'
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+
6
+ private
7
+
8
+ def install_rbenv(shell)
9
+
10
+ shell.with_stat('Installing rbenv') do
11
+ shell.exec "git clone https://github.com/rbenv/rbenv.git #{shell.home_path}/.rbenv"
12
+ shell.exec "git clone https://github.com/rbenv/ruby-build.git #{shell.home_path}/.rbenv/plugins/ruby-build"
13
+
14
+ bashrc = shell.read_file(shell.home_path + '/.bashrc') || ''
15
+ lines = bashrc.split("\n")
16
+ first_line = nil
17
+ lines.each_with_index do |line,index|
18
+ if line.strip[0] != '#'
19
+ first_line = index
20
+ break
21
+ end
22
+ end
23
+ first_line ||= lines.count
24
+ lines.insert first_line, <<-EORC
25
+
26
+ # Initialize rbenv and ruby.
27
+ export PATH="$HOME/.rbenv/bin:$HOME/.rbenv/plugins/ruby-build/bin:$PATH"
28
+ eval "$(rbenv init -)"
29
+
30
+ EORC
31
+
32
+ bashrc = lines.join("\n")
33
+ shell.write_file(shell.home_path + '/.bashrc', bashrc)
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+
6
+ private
7
+
8
+ def install_ruby(shell)
9
+ shell.with_stat("Install Ruby #{@options[:ruby_version]}") do
10
+ result = shell.exec('which rbenv').to_s.strip
11
+ raise 'failed to install rbenv' if result == ''
12
+
13
+ shell.exec "rbenv install -v #{@options[:ruby_version]}"
14
+ shell.exec "rbenv global #{@options[:ruby_version]}"
15
+
16
+ result = shell.exec('which ruby').to_s.partition("\n")[0].strip
17
+ raise 'ruby not where expected' unless result == shell.home_path + '/.rbenv/shims/ruby' || result == '~/.rbenv/shims/ruby'
18
+
19
+ shell.exec "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
20
+ shell.exec 'gem install bundler'
21
+ shell.exec 'rbenv rehash'
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+
6
+ private
7
+
8
+ def restart_nginx(shell)
9
+ shell.with_stat('Restarting nginx') do
10
+ # test the configuration.
11
+ shell.sudo_exec('nginx -t')
12
+
13
+ # stop the service.
14
+ shell.sudo_exec_ignore_code 'systemctl stop nginx.service'
15
+
16
+ # start the service.
17
+ shell.sudo_exec 'systemctl start nginx.service'
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+ private
6
+
7
+ def ssh_copy_id(shell)
8
+ my_id_file = File.expand_path('~/.ssh/rsa_id.pub')
9
+ if File.exist?(my_id_file)
10
+ my_id = File.read(my_id_file)
11
+ shell.with_stat('Enabling public key authentication') do
12
+
13
+ shell.exec 'if [ ! -d ~/.ssh ]; then mkdir ~/.ssh; fi'
14
+ shell.exec 'chmod 700 ~/.ssh'
15
+
16
+ contents = shell.read_file("#{shell.home_path}/.ssh/authorized_keys")
17
+ if contents
18
+ unless contents.split("\n").find{|k| k.to_s.strip == my_id.strip}
19
+ contents += "\n" unless contents[-1] == "\n"
20
+ contents += my_id.strip + "\n"
21
+ shell.write_file("#{shell.home_path}/.ssh/authorized_keys", contents)
22
+ end
23
+ else
24
+ shell.write_file("#{shell.home_path}/.ssh/authorized_keys", my_id + "\n")
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,16 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+
6
+ private
7
+
8
+ def update_system(shell)
9
+ shell.with_stat('Retrieving updates') { shell.apt_get 'update' }
10
+ shell.with_stat('Updating system') { shell.apt_get 'upgrade' }
11
+ shell.with_stat('Updating kernel') { shell.apt_get 'install linux-generic linux-headers-generic linux-image-generic' }
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,270 @@
1
+ require 'securerandom'
2
+ require 'shells'
3
+ require 'io/console'
4
+ require 'ansi/code'
5
+
6
+ require 'incline/cli/prepare/extend_shell'
7
+ require 'incline/cli/prepare/update_system'
8
+ require 'incline/cli/prepare/ssh_copy_id'
9
+ require 'incline/cli/prepare/config_ssh'
10
+ require 'incline/cli/prepare/install_prereqs'
11
+ require 'incline/cli/prepare/install_db'
12
+ require 'incline/cli/prepare/add_deploy_user'
13
+ require 'incline/cli/prepare/install_rbenv'
14
+ require 'incline/cli/prepare/install_ruby'
15
+ require 'incline/cli/prepare/install_rails'
16
+ require 'incline/cli/prepare/install_flytrap'
17
+ require 'incline/cli/prepare/install_passenger'
18
+ require 'incline/cli/prepare/config_passenger'
19
+ require 'incline/cli/prepare/create_nginx_utils'
20
+ require 'incline/cli/prepare/restart_nginx'
21
+
22
+ module Incline
23
+ class CLI
24
+ ##
25
+ # Defines the 'prepare' command for the CLI.
26
+ class Prepare
27
+
28
+ ##
29
+ # Creates a new 'prepare' command for the CLI.
30
+ def initialize(host_name_or_ip, ssh_user, *options)
31
+ @options = {
32
+ port: 22,
33
+ deploy_user: 'deploy',
34
+ deploy_password: SecureRandom.urlsafe_base64(24),
35
+ admin_password: '',
36
+ ruby_version: '2.3.4',
37
+ rails_version: '4.2.9'
38
+ }
39
+ @options[:host] = host_name_or_ip.to_s.strip
40
+
41
+ raise UsageError.new("The 'host_name_or_ip' parameter is required.", 'prepare') if @options[:host] == ''
42
+
43
+ if @options[:host] =~ /\A(?:\[[0-9a-f:]+\]|[a-z0-9]+(?:\.[a-z0-9]+)*):(?:\d+)\z/i
44
+ h,_,p = @options[:host].rpartition(':')
45
+ @options[:host] = h
46
+ @options[:port] = p.to_i
47
+ end
48
+
49
+ @options[:admin_user] = ssh_user.to_s.strip
50
+
51
+ raise UsageError.new("The 'ssh_user' parameter is required.", 'prepare') if @options[:admin_user] == ''
52
+
53
+ while options.any?
54
+ flag = options.delete_at(0)
55
+ case flag
56
+ when '--ssh-password'
57
+ @options[:admin_password] = options.delete_at(0).to_s.strip
58
+ when '--port'
59
+ @options[:port] = options.delete_at(0).to_s.to_i
60
+ when '--deploy-user'
61
+ @options[:deploy_user] = options.delete_at(0).to_s.strip
62
+ raise UsageError.new("The '--deploy-user' parameter requires a valid username.", 'prepare') unless @options[:deploy_user] =~ /\A[a-z][a-z0-9_]*\z/i
63
+ raise UsageError.new("The '--deploy-user' parameter cannot match the 'ssh_user' parameter.", 'prepare') if @options[:deploy_user] == @options[:admin_user]
64
+ when '--ruby-version'
65
+ @options[:ruby_version] = options.delete_at(0).to_s.strip
66
+ raise UsageError.new("The '--ruby-version' parameter must be at least 2.3.", 'prepare') if @options[:ruby_version].to_f < 2.3
67
+ when '--rails-version'
68
+ @options[:rails_version] = options.delete_at(0).to_s.strip
69
+ raise UsageError.new("The '--rails-version' parameter must be at least 4.2.", 'prepare') if @options[:rails_version].to_f < 4.2
70
+
71
+ # These options can be used to customize the self-signed certificate created initially.
72
+ when '--ssl-country'
73
+ @options[:ssl_country] = options.delete_at(0).to_s.strip
74
+ when '--ssl-state', '--ssl-province'
75
+ @options[:ssl_state] = options.delete_at(0).to_s.strip
76
+ when '--ssl-location', '--ssl-city'
77
+ @options[:ssl_location] = options.delete_at(0).to_s.strip
78
+ when '--ssl-org'
79
+ @options[:ssl_org] = options.delete_at(0).to_s.strip
80
+
81
+ else
82
+ raise UsageError.new("The '#{flag}' parameter is not recognized.", 'prepare')
83
+ end
84
+ end
85
+
86
+ @options[:port] = 22 unless (1..65535).include?(@options[:port])
87
+ if @options[:admin_password].to_s.strip == ''
88
+ print 'Please enter the sudo password: '
89
+ @options[:admin_password] = STDIN.noecho(&:gets).to_s.strip
90
+ puts ''
91
+ if @options[:admin_password].to_s.strip == ''
92
+ puts 'WARNING: Sudo password is blank and script may fail because of this.'
93
+ end
94
+ end
95
+
96
+ @options[:ssl_country] = 'US' if @options[:ssl_country].to_s == ''
97
+ @options[:ssl_state] = 'Pennsylvania' if @options[:ssl_state].to_s == ''
98
+ @options[:ssl_location] = 'Pittsburgh' if @options[:ssl_location].to_s == ''
99
+ @options[:ssl_org] = 'WEB' if @options[:ssl_org].to_s == ''
100
+
101
+ end
102
+
103
+ ##
104
+ # Prepares an Ubuntu server with NGINX and Passenger to run Rails applications.
105
+ #
106
+ # Set 'host_name_or_ip' to the DNS name or IP address for the host.
107
+ # Set 'ssh_user' to the user name you want to access the host as.
108
+ # This user must be able to run 'sudo' commands and must not be 'root'.
109
+ #
110
+ # You can provide a port value either appended to the host name or as a
111
+ # separate argument using the '--port' option.
112
+ # e.g. ssh.example.com:22 or --port 22
113
+ #
114
+ # If you are configured with key authentication to the host then you don't
115
+ # need to provide an SSH password to connect. However, this password is
116
+ # also used to run sudo commands. You can specify a password on the command
117
+ # line by using the '--ssh-password' option. If you do not, then the script
118
+ # will prompt you for a "sudo" password to use. You can leave this blank,
119
+ # but the script will warn you that it may lead to failure. Obviously, if
120
+ # you are configured with NOPASSWD in the sudoers file, then you can safely
121
+ # leave the password blank.
122
+ #
123
+ # By default, a deployment user named 'deploy' will be created on the host.
124
+ # If a user by this name already exists, that user will be removed first.
125
+ # This means that any data stored in the user profile will be destroyed by
126
+ # the script prior to creating the new user. You can change the name of the
127
+ # deployment user using the '--deploy-user' option.
128
+ # e.g. --deploy_user bob
129
+ #
130
+ # The script will install 'rbenv' under the deploy user's account and then
131
+ # install Ruby followed by Rails. The default Ruby version installed is
132
+ # 2.3.4 and the Rails version installed is 4.2.9. To change these versions
133
+ # use the '--ruby-version' and '--rails-version' options. The Ruby version
134
+ # must be at least 2.3 and the rails version must be at least 4.2.
135
+ #
136
+ def run
137
+ # reset the host info.
138
+ @host_info = nil
139
+
140
+ admin_shell do |admin|
141
+ # test the connection and sudo capabilities.
142
+ admin.sudo_stat_exec 'Testing connection', 'ls -al /root'
143
+
144
+ # retrieve the host info now that we are connected.
145
+ @host_info = admin.host_info
146
+ raise CliError, "Host OS (#{host_id}) is not supported." unless [ :ubuntu ].include?(host_id)
147
+
148
+ # update the system and configure SSH.
149
+ update_system admin
150
+ ssh_copy_id(admin) unless @options[:admin_password] == ''
151
+ config_ssh admin
152
+ end
153
+ # end the session and reconnect to take advantage of the SSH reset done at the end of config_ssh
154
+ admin_shell do |admin|
155
+ # install ruby prerequisites and mariadb.
156
+ install_prereqs admin
157
+ install_db admin
158
+
159
+ # add the deploy user.
160
+ add_deploy_user admin
161
+
162
+ # log in as deploy user
163
+ deploy_shell do |deploy|
164
+ # enable key auth.
165
+ ssh_copy_id deploy
166
+
167
+ # install rbenv.
168
+ install_rbenv deploy
169
+ end
170
+
171
+ # log out and then back in to load rbenv
172
+ deploy_shell do |deploy|
173
+ # install ruby & rails
174
+ install_ruby deploy
175
+ install_rails deploy
176
+
177
+ # one more fun little addition, we'll add the flytrap app to catch path attacks.
178
+ install_flytrap deploy
179
+ end
180
+ # done with the deploy user, so log out of that session.
181
+
182
+ # install Phusion Passenger to the host and then configure it.
183
+ install_passenger admin
184
+ config_passenger admin
185
+
186
+ # create a few helper utilities to test and reload the configuration.
187
+ create_nginx_utils admin
188
+
189
+ # then restart nginx.
190
+ restart_nginx admin
191
+
192
+ puts 'Testing nginx server...'
193
+ admin.exec_ignore_code 'curl http://localhost/this-is-a-test'
194
+ admin.exec "curl #{flytrap_path}"
195
+ end
196
+
197
+ puts ''
198
+ puts ANSI.ansi(:bold, :white) { 'Host preparation completed.' }
199
+ puts ''
200
+ puts 'Deployment User'
201
+ puts ('-' * 70)
202
+ puts "User: " + ANSI.ansi(:bold) { @options[:deploy_user] }
203
+ puts "Password: " + ANSI.ansi(:bold) { @options[:deploy_password] }
204
+ puts "Home: " + ANSI.ansi(:bold) { @options[:deploy_home] }
205
+ puts ''
206
+ puts 'Server Test Path'
207
+ puts ('-' * 70)
208
+ puts ANSI.ansi(:bold) { flytrap_path }
209
+
210
+ logfile.flush
211
+ logfile.close
212
+ @logfile = nil
213
+
214
+ end
215
+
216
+
217
+ private
218
+
219
+ def host_info
220
+ @host_info ||= {}
221
+ end
222
+
223
+ def host_id
224
+ host_info['ID'] ||= :unknown
225
+ end
226
+
227
+
228
+ def logfile
229
+ @logfile ||=
230
+ begin
231
+ dir = File.expand_path('~/incline-logs')
232
+ Dir.mkdir(dir) unless Dir.exist?(dir)
233
+ File.open(File.expand_path("#{dir}/prepare-#{@options[:host]}.log"), 'wt')
234
+ end
235
+
236
+ end
237
+
238
+ def admin_shell
239
+ Shells::SshSession.new(
240
+ host: @options[:host],
241
+ port: @options[:port],
242
+ user: @options[:admin_user],
243
+ password: @options[:admin_password],
244
+ retrieve_exit_code: true,
245
+ on_non_zero_exit_code: :raise,
246
+ silence_timeout: 5
247
+ ) do |sh|
248
+ extend_shell sh, '# '
249
+ yield sh
250
+ end
251
+ end
252
+
253
+ def deploy_shell
254
+ Shells::SshSession.new(
255
+ host: @options[:host],
256
+ port: @options[:port],
257
+ user: @options[:deploy_user],
258
+ password: @options[:deploy_password],
259
+ retrieve_exit_code: true,
260
+ on_non_zero_exit_code: :raise,
261
+ silence_timeout: 5
262
+ ) do |sh|
263
+ extend_shell sh, '$ '
264
+ yield sh
265
+ end
266
+ end
267
+
268
+ end
269
+ end
270
+ end
@@ -8,38 +8,68 @@ module Incline
8
8
 
9
9
  ##
10
10
  # Creates a new 'usage' command for the CLI.
11
- def initialize
12
-
11
+ def initialize(command = nil)
12
+ @command = command ? command.to_s.downcase.to_sym : nil
13
13
  end
14
14
 
15
15
  ##
16
16
  # Shows usage information for the application.
17
17
  def run
18
-
19
- commands = Incline::CLI::valid_commands.sort{|a,b| a[0] <=> b[0]}
20
-
21
- msg = ANSI.ansi(:bold) { "Usage: #{$PROGRAM_NAME} command <arguments>" }
22
- msg += "\nValid Commands:\n"
23
-
24
- commands.each do |(name,klass,params)|
25
- comment = get_run_comment(klass)
26
- comment = "(No description)" if comment.to_s.strip == ''
27
- comment = ' ' + comment.gsub("\n", "\n ")
28
- msg += " #{name}"
29
- pend = ''
30
- params.each do |t,p|
31
- msg += ' '
32
- if t == :req
33
- msg += p.to_s
34
- elsif t == :opt
35
- msg += '[' + p.to_s
36
- pend += ']'
37
- else
38
- msg += '<...>'
18
+ msg = nil
19
+
20
+ if @command
21
+ command = Incline::CLI::valid_commands.find{|c| c[0] == @command}
22
+ if command
23
+ msg = "Usage: #{$PROGRAM_NAME} #{command[0]}"
24
+ pend = ''
25
+ command[2].each do |t,p|
26
+ msg += ' '
27
+ if t == :req
28
+ msg += p.to_s
29
+ elsif t == :opt
30
+ msg += '[' + p.to_s
31
+ pend += ']'
32
+ else
33
+ msg += '<...>'
34
+ end
35
+ msg += pend
36
+ end
37
+ msg = ANSI.ansi(:bold) { msg }
38
+ msg += "\n"
39
+ comment = get_run_comment(command[1])
40
+ comment = "(No additional information)" if comment.to_s.strip == ''
41
+ comment = ' ' + comment.gsub("\n", "\n ")
42
+ msg += comment + "\n"
43
+ end
44
+ end
45
+
46
+ unless msg
47
+ commands = Incline::CLI::valid_commands.sort{|a,b| a[0] <=> b[0]}
48
+
49
+ msg = ANSI.ansi(:bold) { "Usage: #{$PROGRAM_NAME} command <arguments>" }
50
+ if @command
51
+ msg += "\nInvalid Command: #{@command}\n"
52
+ end
53
+ msg += "\nValid Commands:\n"
54
+ commands.each do |(name,klass,params)|
55
+ msg += " #{name}"
56
+ pend = ''
57
+ params.each do |t,p|
58
+ msg += ' '
59
+ if t == :req
60
+ msg += p.to_s
61
+ elsif t == :opt
62
+ msg += '[' + p.to_s
63
+ pend += ']'
64
+ else
65
+ msg += '<...>'
66
+ end
39
67
  end
68
+ msg += pend + "\n"
40
69
  end
41
- msg += "\n" + comment + "\n"
70
+
42
71
  end
72
+
43
73
 
44
74
  STDOUT.print msg
45
75
  msg