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
@@ -0,0 +1,75 @@
1
+ module Capistrano
2
+ class Configuration
3
+ module Servers
4
+ # Identifies all servers that the given task should be executed on.
5
+ # The options hash accepts the same arguments as #find_servers, and any
6
+ # preexisting options there will take precedence over the options in
7
+ # the task.
8
+ def find_servers_for_task(task, options={})
9
+ find_servers(task.options.merge(options))
10
+ end
11
+
12
+ # Attempts to find all defined servers that match the given criteria.
13
+ # The options hash may include a :hosts option (which should specify
14
+ # an array of host names or ServerDefinition instances), a :roles
15
+ # option (specifying an array of roles), an :only option (specifying
16
+ # a hash of key/value pairs that any matching server must match), and
17
+ # an :exception option (like :only, but the inverse).
18
+ #
19
+ # Additionally, if the HOSTS environment variable is set, it will take
20
+ # precedence over any other options. Similarly, the ROLES environment
21
+ # variable will take precedence over other options. If both HOSTS and
22
+ # ROLES are given, HOSTS wins.
23
+ #
24
+ # Usage:
25
+ #
26
+ # # return all known servers
27
+ # servers = find_servers
28
+ #
29
+ # # find all servers in the app role that are not exempted from
30
+ # # deployment
31
+ # servers = find_servers :roles => :app,
32
+ # :except => { :no_release => true }
33
+ #
34
+ # # returns the given hosts, translated to ServerDefinition objects
35
+ # servers = find_servers :hosts => "jamis@example.host.com"
36
+ def find_servers(options={})
37
+ hosts = server_list_from(ENV['HOSTS'] || options[:hosts])
38
+ roles = role_list_from(ENV['ROLES'] || options[:roles] || self.roles.keys)
39
+ only = options[:only] || {}
40
+ except = options[:except] || {}
41
+
42
+ if hosts.any?
43
+ hosts.uniq
44
+ else
45
+ servers = roles.inject([]) { |list, role| list.concat(self.roles[role]) }
46
+ servers = servers.select { |server| only.all? { |key,value| server.options[key] == value } }
47
+ servers = servers.reject { |server| except.any? { |key,value| server.options[key] == value } }
48
+ servers.uniq
49
+ end
50
+ end
51
+
52
+ protected
53
+
54
+ def server_list_from(hosts)
55
+ hosts = hosts.split(/,/) if String === hosts
56
+ hosts = build_list(hosts)
57
+ hosts.map { |s| String === s ? ServerDefinition.new(s.strip) : s }
58
+ end
59
+
60
+ def role_list_from(roles)
61
+ roles = roles.split(/,/) if String === roles
62
+ roles = build_list(roles)
63
+ roles.map do |role|
64
+ role = String === role ? role.strip.to_sym : role
65
+ raise ArgumentError, "unknown role `#{role}'" unless self.roles.key?(role)
66
+ role
67
+ end
68
+ end
69
+
70
+ def build_list(list)
71
+ Array(list).map { |item| item.respond_to?(:call) ? item.call : item }.flatten
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,127 @@
1
+ require 'thread'
2
+
3
+ module Capistrano
4
+ class Configuration
5
+ module Variables
6
+ def self.included(base) #:nodoc:
7
+ %w(initialize respond_to? method_missing).each do |m|
8
+ base_name = m[/^\w+/]
9
+ punct = m[/\W+$/]
10
+ base.send :alias_method, "#{base_name}_without_variables#{punct}", m
11
+ base.send :alias_method, m, "#{base_name}_with_variables#{punct}"
12
+ end
13
+ end
14
+
15
+ # The hash of variables that have been defined in this configuration
16
+ # instance.
17
+ attr_reader :variables
18
+
19
+ # Set a variable to the given value.
20
+ def set(variable, *args, &block)
21
+ if variable.to_s !~ /^[_a-z]/
22
+ raise ArgumentError, "invalid variable `#{variable}' (variables must begin with an underscore, or a lower-case letter)"
23
+ end
24
+
25
+ if !block_given? && args.empty? || block_given? && !args.empty?
26
+ raise ArgumentError, "you must specify exactly one of either a value or a block"
27
+ end
28
+
29
+ if args.length > 1
30
+ raise ArgumentError, "wrong number of arguments (#{args.length} for 1)"
31
+ end
32
+
33
+ value = args.empty? ? block : args.first
34
+ sym = variable.to_sym
35
+ protect(sym) { @variables[sym] = value }
36
+ end
37
+
38
+ alias :[]= :set
39
+
40
+ # Removes any trace of the given variable.
41
+ def unset(variable)
42
+ sym = variable.to_sym
43
+ protect(sym) do
44
+ @original_procs.delete(sym)
45
+ @variables.delete(sym)
46
+ end
47
+ end
48
+
49
+ # Returns true if the variable has been defined, and false otherwise.
50
+ def exists?(variable)
51
+ @variables.key?(variable.to_sym)
52
+ end
53
+
54
+ # If the variable was originally a proc value, it will be reset to it's
55
+ # original proc value. Otherwise, this method does nothing. It returns
56
+ # true if the variable was actually reset.
57
+ def reset!(variable)
58
+ sym = variable.to_sym
59
+ protect(sym) do
60
+ if @original_procs.key?(sym)
61
+ @variables[sym] = @original_procs.delete(sym)
62
+ true
63
+ else
64
+ false
65
+ end
66
+ end
67
+ end
68
+
69
+ # Access a named variable. If the value of the variable responds_to? :call,
70
+ # #call will be invoked (without parameters) and the return value cached
71
+ # and returned.
72
+ def fetch(variable, *args)
73
+ if !args.empty? && block_given?
74
+ raise ArgumentError, "you must specify either a default value or a block, but not both"
75
+ end
76
+
77
+ sym = variable.to_sym
78
+ protect(sym) do
79
+ if !@variables.key?(sym)
80
+ return args.first unless args.empty?
81
+ return yield(variable) if block_given?
82
+ raise IndexError, "`#{variable}' not found"
83
+ end
84
+
85
+ if @variables[sym].respond_to?(:call)
86
+ @original_procs[sym] = @variables[sym]
87
+ @variables[sym] = @variables[sym].call
88
+ end
89
+ end
90
+
91
+ @variables[sym]
92
+ end
93
+
94
+ def [](variable)
95
+ fetch(variable, nil)
96
+ end
97
+
98
+ def initialize_with_variables(*args) #:nodoc:
99
+ initialize_without_variables(*args)
100
+ @variables = {}
101
+ @original_procs = {}
102
+ @variable_locks = Hash.new { |h,k| h[k] = Mutex.new }
103
+
104
+ set :ssh_options, {}
105
+ set :logger, logger
106
+ end
107
+ private :initialize_with_variables
108
+
109
+ def protect(variable)
110
+ @variable_locks[variable.to_sym].synchronize { yield }
111
+ end
112
+ private :protect
113
+
114
+ def respond_to_with_variables?(sym) #:nodoc:
115
+ @variables.has_key?(sym) || respond_to_without_variables?(sym)
116
+ end
117
+
118
+ def method_missing_with_variables(sym, *args, &block) #:nodoc:
119
+ if args.length == 0 && block.nil? && @variables.has_key?(sym)
120
+ self[sym]
121
+ else
122
+ method_missing_without_variables(sym, *args, &block)
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,15 @@
1
+ module Capistrano
2
+ class Error < RuntimeError; end
3
+
4
+ class CaptureError < Error; end
5
+ class NoSuchTaskError < Error; end
6
+ class NoMatchingServersError < Error; end
7
+
8
+ class RemoteError < Error
9
+ attr_accessor :hosts
10
+ end
11
+
12
+ class ConnectionError < RemoteError; end
13
+ class UploadError < RemoteError; end
14
+ class CommandError < RemoteError; end
15
+ end
@@ -1,23 +1,35 @@
1
- require 'capistrano/actor'
2
-
3
1
  module Capistrano
4
- class ExtensionProxy
5
- def initialize(actor, mod)
6
- @actor = actor
2
+ class ExtensionProxy #:nodoc:
3
+ def initialize(config, mod)
4
+ @config = config
7
5
  extend(mod)
8
6
  end
9
7
 
10
8
  def method_missing(sym, *args, &block)
11
- @actor.send(sym, *args, &block)
9
+ @config.send(sym, *args, &block)
12
10
  end
13
11
  end
14
12
 
13
+ # Holds the set of registered plugins, keyed by name (where the name is a
14
+ # symbol).
15
15
  EXTENSIONS = {}
16
16
 
17
+ # Register the given module as a plugin with the given name. It will henceforth
18
+ # be available via a proxy object on Configuration instances, accessible by
19
+ # a method with the given name.
17
20
  def self.plugin(name, mod)
21
+ name = name.to_sym
18
22
  return false if EXTENSIONS.has_key?(name)
19
23
 
20
- Capistrano::Actor.class_eval <<-STR, __FILE__, __LINE__+1
24
+ methods = Capistrano::Configuration.public_instance_methods +
25
+ Capistrano::Configuration.protected_instance_methods +
26
+ Capistrano::Configuration.private_instance_methods
27
+
28
+ if methods.include?(name.to_s)
29
+ raise Capistrano::Error, "registering a plugin named `#{name}' would shadow a method on Capistrano::Configuration with the same name"
30
+ end
31
+
32
+ Capistrano::Configuration.class_eval <<-STR, __FILE__, __LINE__+1
21
33
  def #{name}
22
34
  @__#{name}_proxy ||= Capistrano::ExtensionProxy.new(self, Capistrano::EXTENSIONS[#{name.inspect}])
23
35
  end
@@ -27,12 +39,19 @@ module Capistrano
27
39
  return true
28
40
  end
29
41
 
42
+ # Unregister the plugin with the given name.
30
43
  def self.remove_plugin(name)
44
+ name = name.to_sym
31
45
  if EXTENSIONS.delete(name)
32
- Capistrano::Actor.send(:remove_method, name)
46
+ Capistrano::Configuration.send(:remove_method, name)
33
47
  return true
34
48
  end
35
49
 
36
50
  return false
37
51
  end
52
+
53
+ def self.configuration(*args) #:nodoc:
54
+ warn "[DEPRECATION] Capistrano.configuration is deprecated. Use Capistrano::Configuration.instance instead"
55
+ Capistrano::Configuration.instance(*args)
56
+ end
38
57
  end
@@ -1,5 +1,18 @@
1
+ if RUBY_VERSION == "1.8.6"
2
+ begin
3
+ require 'fastthread'
4
+ rescue LoadError
5
+ warn "You are running Ruby 1.8.6, which has a bug in its threading implementation."
6
+ warn "You are liable to encounter deadlocks running Capistrano, unless you install"
7
+ warn "the fastthread library, which is available as a gem:"
8
+ warn " gem install fastthread"
9
+ end
10
+ end
11
+
1
12
  require 'thread'
13
+ require 'capistrano/errors'
2
14
  require 'capistrano/ssh'
15
+ require 'capistrano/server_definition'
3
16
 
4
17
  Thread.abort_on_exception = true
5
18
 
@@ -9,17 +22,16 @@ module Capistrano
9
22
  # gateway server, through which connections to other servers may be
10
23
  # tunnelled.
11
24
  #
12
- # It is used internally by Actor, but may be useful on its own, as well.
25
+ # It is used internally by Capistrano, but may be useful on its own, as well.
13
26
  #
14
27
  # Usage:
15
28
  #
16
- # config = Capistrano::Configuration.new
17
- # gateway = Capistrano::Gateway.new('gateway.example.com', config)
29
+ # gateway = Capistrano::Gateway.new(Capistrano::ServerDefinition.new('gateway.example.com'))
18
30
  #
19
- # sess1 = gateway.connect_to('hidden.example.com')
20
- # sess2 = gateway.connect_to('other.example.com')
31
+ # sess1 = gateway.connect_to(Capistrano::ServerDefinition.new('hidden.example.com'))
32
+ # sess2 = gateway.connect_to(Capistrano::ServerDefinition.new('other.example.com'))
21
33
  class Gateway
22
- # The thread inside which the gateway connection itself is running.
34
+ # The Thread instance driving the gateway connection.
23
35
  attr_reader :thread
24
36
 
25
37
  # The Net::SSH session representing the gateway connection.
@@ -28,8 +40,8 @@ module Capistrano
28
40
  MAX_PORT = 65535
29
41
  MIN_PORT = 1024
30
42
 
31
- def initialize(server, config) #:nodoc:
32
- @config = config
43
+ def initialize(server, options={}) #:nodoc:
44
+ @options = options
33
45
  @next_port = MAX_PORT
34
46
  @terminate_thread = false
35
47
  @port_guard = Mutex.new
@@ -37,30 +49,34 @@ module Capistrano
37
49
  mutex = Mutex.new
38
50
  waiter = ConditionVariable.new
39
51
 
40
- @thread = Thread.new do
41
- @config.logger.trace "starting connection to gateway #{server}"
42
- SSH.connect(server, @config) do |@session|
43
- @config.logger.trace "gateway connection established"
44
- mutex.synchronize { waiter.signal }
45
- @session.loop { !@terminate_thread }
52
+ mutex.synchronize do
53
+ @thread = Thread.new do
54
+ logger.trace "starting connection to gateway `#{server}'" if logger
55
+ SSH.connect(server, @options) do |@session|
56
+ logger.trace "gateway connection established" if logger
57
+ mutex.synchronize { waiter.signal }
58
+ @session.loop do
59
+ !@terminate_thread
60
+ end
61
+ end
46
62
  end
47
- end
48
63
 
49
- mutex.synchronize { waiter.wait(mutex) }
64
+ waiter.wait(mutex)
65
+ end
50
66
  end
51
67
 
52
68
  # Shuts down all forwarded connections and terminates the gateway.
53
69
  def shutdown!
54
70
  # cancel all active forward channels
55
- @session.forward.active_locals.each do |lport, host, port|
56
- @session.forward.cancel_local(lport)
71
+ session.forward.active_locals.each do |lport, host, port|
72
+ session.forward.cancel_local(lport)
57
73
  end
58
74
 
59
75
  # terminate the gateway thread
60
76
  @terminate_thread = true
61
77
 
62
78
  # wait for the gateway thread to stop
63
- @thread.join
79
+ thread.join
64
80
  end
65
81
 
66
82
  # Connects to the given server by opening a forwarded port from the local
@@ -68,32 +84,41 @@ module Capistrano
68
84
  # Net::SSH connection via that port.
69
85
  def connect_to(server)
70
86
  connection = nil
71
- @config.logger.trace "establishing connection to #{server} via gateway"
87
+ logger.debug "establishing connection to `#{server}' via gateway" if logger
72
88
  local_port = next_port
73
89
 
74
90
  thread = Thread.new do
75
91
  begin
76
- user, server_stripped, port = SSH.parse_server(server)
77
- @config.ssh_options[:username] = user if user
78
- remote_port = port || 22
79
- @session.forward.local(local_port, server_stripped, remote_port)
80
- connection = SSH.connect('127.0.0.1', @config, local_port)
81
- @config.logger.trace "connection to #{server} via gateway established"
92
+ local_host = ServerDefinition.new("127.0.0.1", :user => server.user, :port => local_port)
93
+ session.forward.local(local_port, server.host, server.port || 22)
94
+ connection = SSH.connect(local_host, @options)
95
+ connection.xserver = server
96
+ logger.trace "connected: `#{server}' (via gateway)" if logger
82
97
  rescue Errno::EADDRINUSE
83
98
  local_port = next_port
84
99
  retry
85
100
  rescue Exception => e
86
- puts e.class.name
87
- puts e.backtrace.join("\n")
101
+ warn "#{e.class}: #{e.message}"
102
+ warn e.backtrace.join("\n")
88
103
  end
89
104
  end
90
105
 
91
106
  thread.join
92
- connection or raise "Could not establish connection to #{server}"
107
+ if connection.nil?
108
+ error = ConnectionError.new("could not establish connection to `#{server}'")
109
+ error.hosts = [server]
110
+ raise error
111
+ end
112
+
113
+ connection
93
114
  end
94
115
 
95
116
  private
96
117
 
118
+ def logger
119
+ @options[:logger]
120
+ end
121
+
97
122
  def next_port
98
123
  @port_guard.synchronize do
99
124
  port = @next_port
@@ -1,6 +1,7 @@
1
1
  module Capistrano
2
2
  class Logger #:nodoc:
3
3
  attr_accessor :level
4
+ attr_reader :device
4
5
 
5
6
  IMPORTANT = 0
6
7
  INFO = 1
@@ -10,13 +11,12 @@ module Capistrano
10
11
  MAX_LEVEL = 3
11
12
 
12
13
  def initialize(options={})
13
- output = options[:output] || STDERR
14
- case
15
- when output.respond_to?(:puts)
16
- @device = output
17
- else
18
- @device = File.open(output.to_str, "a")
19
- @needs_close = true
14
+ output = options[:output] || $stderr
15
+ if output.respond_to?(:puts)
16
+ @device = output
17
+ else
18
+ @device = File.open(output.to_str, "a")
19
+ @needs_close = true
20
20
  end
21
21
 
22
22
  @options = options
@@ -24,17 +24,17 @@ module Capistrano
24
24
  end
25
25
 
26
26
  def close
27
- @device.close if @needs_close
27
+ device.close if @needs_close
28
28
  end
29
29
 
30
30
  def log(level, message, line_prefix=nil)
31
31
  if level <= self.level
32
32
  indent = "%*s" % [MAX_LEVEL, "*" * (MAX_LEVEL - level)]
33
- message.split(/\r?\n/).each do |line|
33
+ message.each do |line|
34
34
  if line_prefix
35
- @device.print "#{indent} [#{line_prefix}] #{line.strip}\n"
35
+ device.puts "#{indent} [#{line_prefix}] #{line.strip}\n"
36
36
  else
37
- @device.puts "#{indent} #{line.strip}\n"
37
+ device.puts "#{indent} #{line.strip}\n"
38
38
  end
39
39
  end
40
40
  end