capistrano 1.4.2 → 2.0.0

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.
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