capistrano 1.4.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. data/CHANGELOG +140 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README +22 -14
  4. data/bin/cap +1 -8
  5. data/bin/capify +77 -0
  6. data/examples/sample.rb +10 -109
  7. data/lib/capistrano.rb +1 -0
  8. data/lib/capistrano/callback.rb +41 -0
  9. data/lib/capistrano/cli.rb +17 -317
  10. data/lib/capistrano/cli/execute.rb +82 -0
  11. data/lib/capistrano/cli/help.rb +102 -0
  12. data/lib/capistrano/cli/help.txt +53 -0
  13. data/lib/capistrano/cli/options.rb +183 -0
  14. data/lib/capistrano/cli/ui.rb +28 -0
  15. data/lib/capistrano/command.rb +62 -29
  16. data/lib/capistrano/configuration.rb +25 -226
  17. data/lib/capistrano/configuration/actions/file_transfer.rb +35 -0
  18. data/lib/capistrano/configuration/actions/inspect.rb +46 -0
  19. data/lib/capistrano/configuration/actions/invocation.rb +127 -0
  20. data/lib/capistrano/configuration/callbacks.rb +148 -0
  21. data/lib/capistrano/configuration/connections.rb +159 -0
  22. data/lib/capistrano/configuration/execution.rb +126 -0
  23. data/lib/capistrano/configuration/loading.rb +112 -0
  24. data/lib/capistrano/configuration/namespaces.rb +190 -0
  25. data/lib/capistrano/configuration/roles.rb +51 -0
  26. data/lib/capistrano/configuration/servers.rb +75 -0
  27. data/lib/capistrano/configuration/variables.rb +127 -0
  28. data/lib/capistrano/errors.rb +15 -0
  29. data/lib/capistrano/extensions.rb +27 -8
  30. data/lib/capistrano/gateway.rb +54 -29
  31. data/lib/capistrano/logger.rb +11 -11
  32. data/lib/capistrano/recipes/compat.rb +32 -0
  33. data/lib/capistrano/recipes/deploy.rb +483 -0
  34. data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
  35. data/lib/capistrano/recipes/deploy/local_dependency.rb +46 -0
  36. data/lib/capistrano/recipes/deploy/remote_dependency.rb +65 -0
  37. data/lib/capistrano/recipes/deploy/scm.rb +19 -0
  38. data/lib/capistrano/recipes/deploy/scm/base.rb +180 -0
  39. data/lib/capistrano/recipes/deploy/scm/bzr.rb +86 -0
  40. data/lib/capistrano/recipes/deploy/scm/cvs.rb +151 -0
  41. data/lib/capistrano/recipes/deploy/scm/darcs.rb +85 -0
  42. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +129 -0
  43. data/lib/capistrano/recipes/deploy/scm/perforce.rb +126 -0
  44. data/lib/capistrano/recipes/deploy/scm/subversion.rb +103 -0
  45. data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
  46. data/lib/capistrano/recipes/deploy/strategy/base.rb +64 -0
  47. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
  48. data/lib/capistrano/recipes/deploy/strategy/copy.rb +143 -0
  49. data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
  50. data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
  51. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +47 -0
  52. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +53 -0
  53. data/lib/capistrano/recipes/standard.rb +26 -276
  54. data/lib/capistrano/recipes/templates/maintenance.rhtml +1 -1
  55. data/lib/capistrano/recipes/upgrade.rb +33 -0
  56. data/lib/capistrano/server_definition.rb +51 -0
  57. data/lib/capistrano/shell.rb +125 -81
  58. data/lib/capistrano/ssh.rb +80 -36
  59. data/lib/capistrano/task_definition.rb +69 -0
  60. data/lib/capistrano/upload.rb +146 -0
  61. data/lib/capistrano/version.rb +13 -17
  62. data/test/cli/execute_test.rb +132 -0
  63. data/test/cli/help_test.rb +139 -0
  64. data/test/cli/options_test.rb +226 -0
  65. data/test/cli/ui_test.rb +28 -0
  66. data/test/cli_test.rb +17 -0
  67. data/test/command_test.rb +284 -25
  68. data/test/configuration/actions/file_transfer_test.rb +40 -0
  69. data/test/configuration/actions/inspect_test.rb +62 -0
  70. data/test/configuration/actions/invocation_test.rb +195 -0
  71. data/test/configuration/callbacks_test.rb +206 -0
  72. data/test/configuration/connections_test.rb +288 -0
  73. data/test/configuration/execution_test.rb +159 -0
  74. data/test/configuration/loading_test.rb +119 -0
  75. data/test/configuration/namespace_dsl_test.rb +283 -0
  76. data/test/configuration/roles_test.rb +47 -0
  77. data/test/configuration/servers_test.rb +90 -0
  78. data/test/configuration/variables_test.rb +180 -0
  79. data/test/configuration_test.rb +60 -212
  80. data/test/deploy/scm/base_test.rb +55 -0
  81. data/test/deploy/strategy/copy_test.rb +146 -0
  82. data/test/extensions_test.rb +69 -0
  83. data/test/fixtures/cli_integration.rb +5 -0
  84. data/test/fixtures/custom.rb +2 -2
  85. data/test/gateway_test.rb +167 -0
  86. data/test/logger_test.rb +123 -0
  87. data/test/server_definition_test.rb +108 -0
  88. data/test/shell_test.rb +64 -0
  89. data/test/ssh_test.rb +67 -154
  90. data/test/task_definition_test.rb +101 -0
  91. data/test/upload_test.rb +131 -0
  92. data/test/utils.rb +31 -39
  93. data/test/version_test.rb +24 -0
  94. metadata +145 -98
  95. data/THANKS +0 -4
  96. data/lib/capistrano/actor.rb +0 -567
  97. data/lib/capistrano/generators/rails/deployment/deployment_generator.rb +0 -25
  98. data/lib/capistrano/generators/rails/deployment/templates/capistrano.rake +0 -49
  99. data/lib/capistrano/generators/rails/deployment/templates/deploy.rb +0 -122
  100. data/lib/capistrano/generators/rails/loader.rb +0 -20
  101. data/lib/capistrano/scm/base.rb +0 -61
  102. data/lib/capistrano/scm/baz.rb +0 -118
  103. data/lib/capistrano/scm/bzr.rb +0 -70
  104. data/lib/capistrano/scm/cvs.rb +0 -129
  105. data/lib/capistrano/scm/darcs.rb +0 -27
  106. data/lib/capistrano/scm/mercurial.rb +0 -83
  107. data/lib/capistrano/scm/perforce.rb +0 -139
  108. data/lib/capistrano/scm/subversion.rb +0 -128
  109. data/lib/capistrano/transfer.rb +0 -97
  110. data/lib/capistrano/utils.rb +0 -26
  111. data/test/actor_test.rb +0 -402
  112. data/test/scm/cvs_test.rb +0 -196
  113. data/test/scm/subversion_test.rb +0 -145
@@ -1,25 +0,0 @@
1
- class DeploymentGenerator < Rails::Generator::NamedBase
2
- attr_reader :recipe_file
3
-
4
- def initialize(runtime_args, runtime_options = {})
5
- super
6
- @recipe_file = @args.shift || "deploy"
7
- end
8
-
9
- def manifest
10
- record do |m|
11
- m.directory "config"
12
- m.template "deploy.rb", File.join("config", "#{recipe_file}.rb")
13
- m.directory "lib/tasks"
14
- m.template "capistrano.rake", File.join("lib", "tasks", "capistrano.rake")
15
- end
16
- end
17
-
18
- protected
19
-
20
- # Override with your own usage banner.
21
- def banner
22
- "Usage: #{$0} deployment ApplicationName [recipe-name]\n" +
23
- " (recipe-name defaults to \"deploy\")"
24
- end
25
- end
@@ -1,49 +0,0 @@
1
- # =============================================================================
2
- # A set of rake tasks for invoking the Capistrano automation utility.
3
- # =============================================================================
4
-
5
- # Invoke the given actions via Capistrano
6
- def cap(*parameters)
7
- begin
8
- require 'rubygems'
9
- rescue LoadError
10
- # no rubygems to load, so we fail silently
11
- end
12
-
13
- require 'capistrano/cli'
14
-
15
- STDERR.puts "Capistrano/Rake integration is deprecated."
16
- STDERR.puts "Please invoke the 'cap' command directly: `cap #{parameters.join(" ")}'"
17
-
18
- Capistrano::CLI.new(parameters.map { |param| param.to_s }).execute!
19
- end
20
-
21
- namespace :remote do
22
- <%- config = Capistrano::Configuration.new
23
- config.load "standard"
24
- options = { :show_tasks => ", '-q'" }
25
- config.actor.each_task do |info| -%>
26
- <%- unless info[:desc].empty? -%>
27
- desc "<%= info[:desc].scan(/.*?(?:\. |$)/).first.strip.gsub(/"/, "\\\"") %>"
28
- <%- end -%>
29
- task(<%= info[:task].inspect %>) { cap <%= info[:task].inspect %><%= options[info[:task]] %> }
30
-
31
- <%- end -%>
32
- desc "Execute a specific action using capistrano"
33
- task :exec do
34
- unless ENV['ACTION']
35
- raise "Please specify an action (or comma separated list of actions) via the ACTION environment variable"
36
- end
37
-
38
- actions = ENV['ACTION'].split(",")
39
- actions.concat(ENV['PARAMS'].split(" ")) if ENV['PARAMS']
40
-
41
- cap(*actions)
42
- end
43
- end
44
-
45
- desc "Push the latest revision into production (delegates to remote:deploy)"
46
- task :deploy => "remote:deploy"
47
-
48
- desc "Rollback to the release before the current release in production (delegates to remote:rollback)"
49
- task :rollback => "remote:rollback"
@@ -1,122 +0,0 @@
1
- # This defines a deployment "recipe" that you can feed to capistrano
2
- # (http://manuals.rubyonrails.com/read/book/17). It allows you to automate
3
- # (among other things) the deployment of your application.
4
-
5
- # =============================================================================
6
- # REQUIRED VARIABLES
7
- # =============================================================================
8
- # You must always specify the application and repository for every recipe. The
9
- # repository must be the URL of the repository you want this recipe to
10
- # correspond to. The deploy_to path must be the path on each machine that will
11
- # form the root of the application path.
12
-
13
- set :application, "<%= singular_name %>"
14
- set :repository, "http://svn.yourhost.com/#{application}/trunk"
15
-
16
- # =============================================================================
17
- # ROLES
18
- # =============================================================================
19
- # You can define any number of roles, each of which contains any number of
20
- # machines. Roles might include such things as :web, or :app, or :db, defining
21
- # what the purpose of each machine is. You can also specify options that can
22
- # be used to single out a specific subset of boxes in a particular role, like
23
- # :primary => true.
24
-
25
- role :web, "www01.example.com", "www02.example.com"
26
- role :app, "app01.example.com", "app02.example.com", "app03.example.com"
27
- role :db, "db01.example.com", :primary => true
28
- role :db, "db02.example.com", "db03.example.com"
29
-
30
- # =============================================================================
31
- # OPTIONAL VARIABLES
32
- # =============================================================================
33
- # set :deploy_to, "/path/to/app" # defaults to "/u/apps/#{application}"
34
- # set :user, "flippy" # defaults to the currently logged in user
35
- # set :scm, :darcs # defaults to :subversion
36
- # set :svn, "/path/to/svn" # defaults to searching the PATH
37
- # set :darcs, "/path/to/darcs" # defaults to searching the PATH
38
- # set :cvs, "/path/to/cvs" # defaults to searching the PATH
39
- # set :gateway, "gate.host.com" # default to no gateway
40
-
41
- # =============================================================================
42
- # SSH OPTIONS
43
- # =============================================================================
44
- # ssh_options[:keys] = %w(/path/to/my/key /path/to/another/key)
45
- # ssh_options[:port] = 25
46
-
47
- # =============================================================================
48
- # TASKS
49
- # =============================================================================
50
- # Define tasks that run on all (or only some) of the machines. You can specify
51
- # a role (or set of roles) that each task should be executed on. You can also
52
- # narrow the set of servers to a subset of a role by specifying options, which
53
- # must match the options given for the servers to select (like :primary => true)
54
-
55
- desc <<DESC
56
- An imaginary backup task. (Execute the 'show_tasks' task to display all
57
- available tasks.)
58
- DESC
59
- task :backup, :roles => :db, :only => { :primary => true } do
60
- # the on_rollback handler is only executed if this task is executed within
61
- # a transaction (see below), AND it or a subsequent task fails.
62
- on_rollback { delete "/tmp/dump.sql" }
63
-
64
- run "mysqldump -u theuser -p thedatabase > /tmp/dump.sql" do |ch, stream, out|
65
- ch.send_data "thepassword\n" if out =~ /^Enter password:/
66
- end
67
- end
68
-
69
- # Tasks may take advantage of several different helper methods to interact
70
- # with the remote server(s). These are:
71
- #
72
- # * run(command, options={}, &block): execute the given command on all servers
73
- # associated with the current task, in parallel. The block, if given, should
74
- # accept three parameters: the communication channel, a symbol identifying the
75
- # type of stream (:err or :out), and the data. The block is invoked for all
76
- # output from the command, allowing you to inspect output and act
77
- # accordingly.
78
- # * sudo(command, options={}, &block): same as run, but it executes the command
79
- # via sudo.
80
- # * delete(path, options={}): deletes the given file or directory from all
81
- # associated servers. If :recursive => true is given in the options, the
82
- # delete uses "rm -rf" instead of "rm -f".
83
- # * put(buffer, path, options={}): creates or overwrites a file at "path" on
84
- # all associated servers, populating it with the contents of "buffer". You
85
- # can specify :mode as an integer value, which will be used to set the mode
86
- # on the file.
87
- # * render(template, options={}) or render(options={}): renders the given
88
- # template and returns a string. Alternatively, if the :template key is given,
89
- # it will be treated as the contents of the template to render. Any other keys
90
- # are treated as local variables, which are made available to the (ERb)
91
- # template.
92
-
93
- desc "Demonstrates the various helper methods available to recipes."
94
- task :helper_demo do
95
- # "setup" is a standard task which sets up the directory structure on the
96
- # remote servers. It is a good idea to run the "setup" task at least once
97
- # at the beginning of your app's lifetime (it is non-destructive).
98
- setup
99
-
100
- buffer = render("maintenance.rhtml", :deadline => ENV['UNTIL'])
101
- put buffer, "#{shared_path}/system/maintenance.html", :mode => 0644
102
- sudo "killall -USR1 dispatch.fcgi"
103
- run "#{release_path}/script/spin"
104
- delete "#{shared_path}/system/maintenance.html"
105
- end
106
-
107
- # You can use "transaction" to indicate that if any of the tasks within it fail,
108
- # all should be rolled back (for each task that specifies an on_rollback
109
- # handler).
110
-
111
- desc "A task demonstrating the use of transactions."
112
- task :long_deploy do
113
- transaction do
114
- update_code
115
- disable_web
116
- symlink
117
- migrate
118
- end
119
-
120
- restart
121
- enable_web
122
- end
@@ -1,20 +0,0 @@
1
- module Capistrano
2
- module Generators
3
- class RailsLoader
4
- def self.load!(options)
5
- require "#{options[:apply_to]}/config/environment"
6
- require "rails_generator"
7
- require "rails_generator/scripts/generate"
8
-
9
- Rails::Generator::Base.sources << Rails::Generator::PathSource.new(
10
- :capistrano, File.dirname(__FILE__))
11
-
12
- args = ["deployment"]
13
- args << (options[:application] || "Application")
14
- args << (options[:recipe_file] || "deploy")
15
-
16
- Rails::Generator::Scripts::Generate.new.run(args)
17
- end
18
- end
19
- end
20
- end
@@ -1,61 +0,0 @@
1
- module Capistrano
2
- module SCM
3
-
4
- # The ancestor class of the various SCM module implementations.
5
- class Base
6
- attr_reader :configuration
7
-
8
- def initialize(configuration) #:nodoc:
9
- @configuration = configuration
10
- end
11
-
12
- def latest_revision
13
- nil
14
- end
15
-
16
- def current_revision(actor)
17
- raise "#{self.class} doesn't support querying the deployed revision"
18
- end
19
-
20
- def diff(actor, from=nil, to=nil)
21
- raise "#{self.class} doesn't support diff(from, to)"
22
- end
23
-
24
- def update(actor)
25
- raise "#{self.class} doesn't support update(actor)"
26
- end
27
-
28
- private
29
-
30
- def run_checkout(actor, guts, &block)
31
- directory = File.basename(configuration.release_path)
32
-
33
- command = <<-STR
34
- if [[ ! -d #{configuration.release_path} ]]; then
35
- #{guts}
36
- #{logging_commands(directory)}
37
- fi
38
- STR
39
-
40
- actor.run(command, &block)
41
- end
42
-
43
- def run_update(actor, guts, &block)
44
- command = <<-STR
45
- #{guts}
46
- #{logging_commands}
47
- STR
48
-
49
- actor.run(command, &block)
50
- end
51
-
52
- def logging_commands(directory = nil)
53
- log = "#{configuration.deploy_to}/revisions.log"
54
-
55
- "(test -e #{log} || (touch #{log} && chmod 666 #{log})) && " +
56
- "echo `date +\"%Y-%m-%d %H:%M:%S\"` $USER #{configuration.revision} #{directory} >> #{log};"
57
- end
58
- end
59
-
60
- end
61
- end
@@ -1,118 +0,0 @@
1
- require 'capistrano/scm/base'
2
-
3
- module Capistrano
4
- module SCM
5
-
6
- # An SCM module for using Bazaar as your source control tool. This
7
- # module is used by default, but you can explicitly specify it by
8
- # placing the following line in your configuration:
9
- #
10
- # set :scm, :baz
11
- #
12
- # Also, this module accepts a <tt>:baz</tt> configuration variable,
13
- # which (if specified) will be used as the full path to the svn
14
- # executable on the remote machine:
15
- #
16
- # set :baz, "/opt/local/bin/baz"
17
- #
18
- # Set the version you wish to deploy as the repository variable,
19
- # for example:
20
- #
21
- # set :repository, "you@example.com--dev/yourstuff--trunk--1.0"
22
- #
23
- # Ensure that you have already registered the archive on the target
24
- # machines.
25
- #
26
- # As bazaar keeps a great deal of extra information on a checkout,
27
- # you will probably want to use export instead:
28
- #
29
- # set :checkout, "export"
30
- #
31
- # TODO: provide setup recipe to register archive
32
- class Baz < Base
33
- # Return an integer identifying the last known revision in the baz
34
- # repository. (This integer is currently the revision number.)
35
- def latest_revision
36
- `#{baz} revisions #{configuration.repository}`.split.last =~ /\-(\d+)$/
37
- $1
38
- end
39
-
40
- # Return the number of the revision currently deployed.
41
- def current_revision(actor)
42
- latest = actor.releases.last
43
- grep = %(grep " #{latest}$" #{configuration.deploy_to}/revisions.log)
44
- result = ""
45
- actor.run(grep, :once => true) do |ch, str, out|
46
- result << out if str == :out
47
- raise "could not determine current revision" if str == :err
48
- end
49
-
50
- date, time, user, rev, dir = result.split
51
- raise "current revision not found in revisions.log" unless dir == latest
52
- rev.to_i
53
- end
54
-
55
- # Return a string containing the diff between the two revisions. +from+
56
- # and +to+ may be in any format that bzr recognizes as a valid revision
57
- # identifier. If +from+ is +nil+, it defaults to the last deployed
58
- # revision. If +to+ is +nil+, it defaults to the last developed revision.
59
- def diff(actor, from=nil, to=nil)
60
- from ||= current_revision(actor)
61
- to ||= latest_revision
62
- from = baz_revision_name(from)
63
- to = baz_revision_name(to)
64
- `#{baz} delta --diffs -A #{baz_archive} #{baz_version}--#{from} #{baz_version}--#{to}`
65
- end
66
-
67
- # Check out (on all servers associated with the current task) the latest
68
- # revision. Uses the given actor instance to execute the command.
69
- def checkout(actor)
70
- op = configuration[:checkout] || "get"
71
- from = baz_revision_name(configuration.revision)
72
- command = "#{baz} #{op} #{configuration.repository}--#{from} #{actor.release_path} &&"
73
- run_checkout(actor, command, &baz_stream_handler(actor))
74
- end
75
-
76
- def update(actor)
77
- command = "cd #{actor.current_path} && #{baz} update &&"
78
- run_update(actor, command, &baz_stream_handler(actor))
79
- end
80
-
81
- private
82
- def baz
83
- configuration[:baz] || "baz"
84
- end
85
-
86
- def baz_revision_name(number)
87
- if number.to_i == 0 then
88
- "base-0"
89
- else
90
- "patch-#{number}"
91
- end
92
- end
93
-
94
- def baz_archive
95
- configuration[:repository][/(.*)\//, 1]
96
- end
97
-
98
- def baz_version
99
- configuration[:repository][/\/(.*)$/, 1]
100
- end
101
-
102
- def baz_stream_handler(actor)
103
- Proc.new do |ch, stream, out|
104
- prefix = "#{stream} :: #{ch[:host]}"
105
- actor.logger.info out, prefix
106
- if out =~ /\bpassword.*:/i
107
- actor.logger.info "baz is asking for a password", prefix
108
- ch.send_data "#{actor.password}\n"
109
- elsif out =~ %r{passphrase}
110
- message = "baz needs your key's passphrase, sending empty string"
111
- actor.logger.info message, prefix
112
- ch.send_data "\n"
113
- end
114
- end
115
- end
116
- end
117
- end
118
- end
@@ -1,70 +0,0 @@
1
- require 'capistrano/scm/base'
2
-
3
- module Capistrano
4
- module SCM
5
-
6
- # An SCM module for using Bazaar-NG (bzr) as your source control tool.
7
- # You can use it by placing the following line in your configuration:
8
- #
9
- # set :scm, :bzr
10
- #
11
- # Also, this module accepts a <tt>:bzr</tt> configuration variable,
12
- # which (if specified) will be used as the full path to the bzr
13
- # executable on the remote machine:
14
- #
15
- # set :bzr, "/opt/local/bin/bzr"
16
- class Bzr < Base
17
- # Return an integer identifying the last known revision in the bzr
18
- # repository. (This integer is currently the revision number.)
19
- def latest_revision
20
- `#{bzr} revno #{configuration.repository}`.to_i
21
- end
22
-
23
- # Return the number of the revision currently deployed.
24
- def current_revision(actor)
25
- command = "#{bzr} revno #{actor.release_path} &&"
26
- run_update(actor, command, &bzr_stream_handler(actor))
27
- end
28
-
29
- # Return a string containing the diff between the two revisions. +from+
30
- # and +to+ may be in any format that bzr recognizes as a valid revision
31
- # identifier. If +from+ is +nil+, it defaults to the last deployed
32
- # revision. If +to+ is +nil+, it defaults to the last developed revision.
33
- # Pay attention to the fact that as of now bzr does NOT support
34
- # diff on remote locations.
35
- def diff(actor, from=nil, to=nil)
36
- from ||= current_revision(actor)
37
- to ||= ""
38
- `#{bzr} diff -r #{from}..#{to} #{configuration.repository}`
39
- end
40
-
41
- # Check out (on all servers associated with the current task) the latest
42
- # revision. Uses the given actor instance to execute the command. If
43
- # bzr asks for a password this will automatically provide it (assuming
44
- # the requested password is the same as the password for logging into the
45
- # remote server.)
46
- def checkout(actor)
47
- op = configuration[:checkout] || "branch"
48
- command = "#{bzr} #{op} -r#{configuration.revision} #{configuration.repository} #{actor.release_path} &&"
49
- run_checkout(actor, command, &bzr_stream_handler(actor))
50
- end
51
-
52
- def update(actor)
53
- command = "cd #{actor.current_path} && #{bzr} pull -q &&"
54
- run_update(actor, command, &bzr_stream_handler(actor))
55
- end
56
-
57
- private
58
- def bzr
59
- configuration[:bzr] || "bzr"
60
- end
61
-
62
- def bzr_stream_handler(actor)
63
- Proc.new do |ch, stream, out|
64
- prefix = "#{stream} :: #{ch[:host]}"
65
- actor.logger.info out, prefix
66
- end
67
- end
68
- end
69
- end
70
- end