ztk 1.6.2 → 1.6.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/Rakefile +1 -1
- data/lib/ztk/command.rb +14 -160
- data/lib/ztk/command/download.rb +16 -0
- data/lib/ztk/command/exec.rb +139 -0
- data/lib/ztk/command/private.rb +26 -0
- data/lib/ztk/command/upload.rb +16 -0
- data/lib/ztk/locator.rb +13 -0
- data/lib/ztk/report.rb +11 -155
- data/lib/ztk/report/list.rb +67 -0
- data/lib/ztk/report/private.rb +44 -0
- data/lib/ztk/report/spreadsheet.rb +72 -0
- data/lib/ztk/ssh.rb +20 -114
- data/lib/ztk/ssh/bootstrap.rb +1 -1
- data/lib/ztk/ssh/console.rb +28 -0
- data/lib/ztk/ssh/core.rb +37 -0
- data/lib/ztk/ssh/download.rb +2 -2
- data/lib/ztk/ssh/file.rb +1 -1
- data/lib/ztk/ssh/private.rb +67 -0
- data/lib/ztk/ssh/upload.rb +2 -2
- data/lib/ztk/ui.rb +14 -0
- data/lib/ztk/version.rb +1 -1
- data/spec/ztk/logger_spec.rb +1 -1
- data/spec/ztk/ssh_spec.rb +8 -8
- metadata +18 -2
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -39,7 +39,7 @@ require 'yard'
|
|
39
39
|
require 'yard/rake/yardoc_task'
|
40
40
|
|
41
41
|
GEM_NAME = File.basename(Dir.pwd)
|
42
|
-
DOC_PATH = File.expand_path(File.join("..", "
|
42
|
+
DOC_PATH = File.expand_path(File.join("..", "", "#{GEM_NAME}.doc"))
|
43
43
|
|
44
44
|
namespace :doc do
|
45
45
|
YARD::Rake::YardocTask.new(:pages) do |t|
|
data/lib/ztk/command.rb
CHANGED
@@ -1,7 +1,3 @@
|
|
1
|
-
require 'ostruct'
|
2
|
-
require 'timeout'
|
3
|
-
require 'socket'
|
4
|
-
|
5
1
|
module ZTK
|
6
2
|
|
7
3
|
# Command Error Class
|
@@ -22,6 +18,18 @@ module ZTK
|
|
22
18
|
#
|
23
19
|
# @author Zachary Patten <zachary AT jovelabs DOT com>
|
24
20
|
class Command < ZTK::Base
|
21
|
+
require 'ostruct'
|
22
|
+
require 'timeout'
|
23
|
+
require 'socket'
|
24
|
+
|
25
|
+
require 'ztk/command/download'
|
26
|
+
require 'ztk/command/exec'
|
27
|
+
require 'ztk/command/private'
|
28
|
+
require 'ztk/command/upload'
|
29
|
+
|
30
|
+
include ZTK::Command::Download
|
31
|
+
include ZTK::Command::Exec
|
32
|
+
include ZTK::Command::Upload
|
25
33
|
|
26
34
|
# @param [Hash] configuration Sets the overall default configuration for the
|
27
35
|
# class. For example, all calls to *exec* against this instance will use
|
@@ -46,168 +54,14 @@ module ZTK
|
|
46
54
|
:exit_code => 0,
|
47
55
|
:silence => false
|
48
56
|
}.merge(configuration))
|
49
|
-
config.ui.logger.debug { "config=#{config.send(:table).inspect}" }
|
50
|
-
end
|
51
|
-
|
52
|
-
# Execute Command
|
53
|
-
#
|
54
|
-
# @example Execute a command:
|
55
|
-
# cmd = ZTK::Command.new(:silence => true)
|
56
|
-
# puts cmd.exec("hostname", :silence => false).inspect
|
57
|
-
#
|
58
|
-
# @param [String] command The command to execute.
|
59
|
-
# @param [Hash] options The options hash for executing the command.
|
60
|
-
# @option options [Integer] :timeout (600) How long in seconds before
|
61
|
-
# the command will timeout.
|
62
|
-
# @option options [Boolean] :ignore_exit_status (false) Whether or not
|
63
|
-
# we should ignore the exit status of the the process we spawn. By
|
64
|
-
# default we do not ignore the exit status and throw an exception if it is
|
65
|
-
# non-zero.
|
66
|
-
# @option options [Integer] :exit_code (0) The exit code we expect the
|
67
|
-
# process to return. This is ignore if *ignore_exit_status* is true.
|
68
|
-
# @option options [Boolean] :silence (false) Whether or not we should
|
69
|
-
# squelch the output of the process. The output will always go to the
|
70
|
-
# logging device supplied in the ZTK::UI object. The output is always
|
71
|
-
# available in the return value from the method additionally.
|
72
|
-
#
|
73
|
-
# @return [OpenStruct#output] The output of the command, both STDOUT and
|
74
|
-
# STDERR combined.
|
75
|
-
# @return [OpenStruct#exit_code] The exit code of the process.
|
76
|
-
def exec(command, options={})
|
77
|
-
options = OpenStruct.new(config.send(:table).merge(options))
|
78
|
-
|
79
|
-
options.ui.logger.debug { "config=#{options.send(:table).inspect}" }
|
80
|
-
options.ui.logger.debug { "options=#{options.send(:table).inspect}" }
|
81
|
-
options.ui.logger.info { "command(#{command.inspect})" }
|
82
|
-
|
83
|
-
if options.replace_current_process
|
84
|
-
options.ui.logger.fatal { "REPLACING CURRENT PROCESS - GOODBYE!" }
|
85
|
-
Kernel.exec(command)
|
86
|
-
end
|
87
|
-
|
88
|
-
output = ""
|
89
|
-
exit_code = -1
|
90
|
-
stdout_header = false
|
91
|
-
stderr_header = false
|
92
|
-
|
93
|
-
parent_stdout_reader, child_stdout_writer = IO.pipe
|
94
|
-
parent_stderr_reader, child_stderr_writer = IO.pipe
|
95
|
-
|
96
|
-
start_time = Time.now.utc
|
97
|
-
|
98
|
-
pid = Process.fork do
|
99
|
-
parent_stdout_reader.close
|
100
|
-
parent_stderr_reader.close
|
101
|
-
|
102
|
-
STDOUT.reopen(child_stdout_writer)
|
103
|
-
STDERR.reopen(child_stderr_writer)
|
104
|
-
STDIN.reopen("/dev/null")
|
105
|
-
|
106
|
-
child_stdout_writer.close
|
107
|
-
child_stderr_writer.close
|
108
57
|
|
109
|
-
|
110
|
-
end
|
111
|
-
child_stdout_writer.close
|
112
|
-
child_stderr_writer.close
|
113
|
-
|
114
|
-
reader_writer_key = {parent_stdout_reader => :stdout, parent_stderr_reader => :stderr}
|
115
|
-
reader_writer_map = {parent_stdout_reader => options.ui.stdout, parent_stderr_reader => options.ui.stderr}
|
116
|
-
|
117
|
-
direct_log(:info) { log_header("COMMAND") }
|
118
|
-
direct_log(:info) { "#{command.inspect}\n" }
|
119
|
-
direct_log(:info) { log_header("STARTED") }
|
120
|
-
|
121
|
-
begin
|
122
|
-
Timeout.timeout(options.timeout) do
|
123
|
-
loop do
|
124
|
-
pipes = IO.select(reader_writer_map.keys, [], reader_writer_map.keys).first
|
125
|
-
pipes.each do |pipe|
|
126
|
-
data = pipe.read
|
127
|
-
|
128
|
-
if (data.nil? || data.empty?)
|
129
|
-
sleep(0.1)
|
130
|
-
next
|
131
|
-
end
|
132
|
-
|
133
|
-
case reader_writer_key[pipe]
|
134
|
-
when :stdout then
|
135
|
-
if !stdout_header
|
136
|
-
direct_log(:info) { log_header("STDOUT") }
|
137
|
-
stdout_header = true
|
138
|
-
stderr_header = false
|
139
|
-
end
|
140
|
-
reader_writer_map[pipe].write(data) unless options.silence
|
141
|
-
direct_log(:info) { data }
|
142
|
-
|
143
|
-
when :stderr then
|
144
|
-
if !stderr_header
|
145
|
-
direct_log(:warn) { log_header("STDERR") }
|
146
|
-
stderr_header = true
|
147
|
-
stdout_header = false
|
148
|
-
end
|
149
|
-
reader_writer_map[pipe].write(data) unless options.silence
|
150
|
-
direct_log(:warn) { data }
|
151
|
-
end
|
152
|
-
|
153
|
-
output += data
|
154
|
-
|
155
|
-
options.on_progress.nil? or options.on_progress.call
|
156
|
-
end
|
157
|
-
|
158
|
-
break if reader_writer_map.keys.all?{ |reader| reader.eof? }
|
159
|
-
end
|
160
|
-
end
|
161
|
-
rescue Timeout::Error => e
|
162
|
-
direct_log(:fatal) { log_header("TIMEOUT") }
|
163
|
-
log_and_raise(CommandError, "Process timed out after #{options.timeout} seconds!")
|
164
|
-
end
|
165
|
-
|
166
|
-
Process.waitpid(pid)
|
167
|
-
exit_code = $?.exitstatus
|
168
|
-
direct_log(:info) { log_header("STOPPED") }
|
169
|
-
|
170
|
-
parent_stdout_reader.close
|
171
|
-
parent_stderr_reader.close
|
172
|
-
|
173
|
-
options.ui.logger.debug { "exit_code(#{exit_code})" }
|
174
|
-
|
175
|
-
if !options.ignore_exit_status && (exit_code != options.exit_code)
|
176
|
-
log_and_raise(CommandError, "exec(#{command.inspect}, #{options.inspect}) failed! [#{exit_code}]")
|
177
|
-
end
|
178
|
-
OpenStruct.new(:command => command, :output => output, :exit_code => exit_code)
|
179
|
-
end
|
180
|
-
|
181
|
-
# Not Supported
|
182
|
-
# @raise [CommandError] Not Supported
|
183
|
-
def upload(*args)
|
184
|
-
log_and_raise(CommandError, "Not Supported")
|
185
|
-
end
|
186
|
-
|
187
|
-
# Not Supported
|
188
|
-
# @raise [CommandError] Not Supported
|
189
|
-
def download(*args)
|
190
|
-
log_and_raise(CommandError, "Not Supported")
|
58
|
+
config.ui.logger.debug { "config=#{config.send(:table).inspect}" }
|
191
59
|
end
|
192
60
|
|
193
61
|
|
194
62
|
private
|
195
63
|
|
196
|
-
|
197
|
-
# shell.
|
198
|
-
def tag
|
199
|
-
@@hostname ||= Socket.gethostname.split('.').first.strip
|
200
|
-
"#{ENV['USER']}@#{@@hostname}"
|
201
|
-
end
|
202
|
-
|
203
|
-
# Formats a header suitable for writing to the direct logger when logging
|
204
|
-
# sessions.
|
205
|
-
def log_header(what)
|
206
|
-
count = 8
|
207
|
-
sep = ("=" * count)
|
208
|
-
header = [sep, "[ #{what} ]", sep, "[ #{tag} ]", sep, "[ #{what} ]", sep].join
|
209
|
-
"#{header}\n"
|
210
|
-
end
|
64
|
+
include ZTK::Command::Private
|
211
65
|
|
212
66
|
end
|
213
67
|
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module ZTK
|
2
|
+
class Command
|
3
|
+
|
4
|
+
# Command Exec Functionality
|
5
|
+
module Exec
|
6
|
+
|
7
|
+
# Execute Command
|
8
|
+
#
|
9
|
+
# @example Execute a command:
|
10
|
+
# cmd = ZTK::Command.new(:silence => true)
|
11
|
+
# puts cmd.exec("hostname", :silence => false).inspect
|
12
|
+
#
|
13
|
+
# @param [String] command The command to execute.
|
14
|
+
# @param [Hash] options The options hash for executing the command.
|
15
|
+
# @option options [Integer] :timeout (600) How long in seconds before
|
16
|
+
# the command will timeout.
|
17
|
+
# @option options [Boolean] :ignore_exit_status (false) Whether or not
|
18
|
+
# we should ignore the exit status of the the process we spawn. By
|
19
|
+
# default we do not ignore the exit status and throw an exception if it is
|
20
|
+
# non-zero.
|
21
|
+
# @option options [Integer] :exit_code (0) The exit code we expect the
|
22
|
+
# process to return. This is ignore if *ignore_exit_status* is true.
|
23
|
+
# @option options [Boolean] :silence (false) Whether or not we should
|
24
|
+
# squelch the output of the process. The output will always go to the
|
25
|
+
# logging device supplied in the ZTK::UI object. The output is always
|
26
|
+
# available in the return value from the method additionally.
|
27
|
+
#
|
28
|
+
# @return [OpenStruct#output] The output of the command, both STDOUT and
|
29
|
+
# STDERR combined.
|
30
|
+
# @return [OpenStruct#exit_code] The exit code of the process.
|
31
|
+
def exec(command, options={})
|
32
|
+
options = OpenStruct.new(config.send(:table).merge(options))
|
33
|
+
|
34
|
+
options.ui.logger.debug { "config=#{options.send(:table).inspect}" }
|
35
|
+
options.ui.logger.debug { "options=#{options.send(:table).inspect}" }
|
36
|
+
options.ui.logger.info { "command(#{command.inspect})" }
|
37
|
+
|
38
|
+
if options.replace_current_process
|
39
|
+
options.ui.logger.fatal { "REPLACING CURRENT PROCESS - GOODBYE!" }
|
40
|
+
Kernel.exec(command)
|
41
|
+
end
|
42
|
+
|
43
|
+
output = ""
|
44
|
+
exit_code = -1
|
45
|
+
stdout_header = false
|
46
|
+
stderr_header = false
|
47
|
+
|
48
|
+
parent_stdout_reader, child_stdout_writer = IO.pipe
|
49
|
+
parent_stderr_reader, child_stderr_writer = IO.pipe
|
50
|
+
|
51
|
+
start_time = Time.now.utc
|
52
|
+
|
53
|
+
pid = Process.fork do
|
54
|
+
parent_stdout_reader.close
|
55
|
+
parent_stderr_reader.close
|
56
|
+
|
57
|
+
STDOUT.reopen(child_stdout_writer)
|
58
|
+
STDERR.reopen(child_stderr_writer)
|
59
|
+
STDIN.reopen("/dev/null")
|
60
|
+
|
61
|
+
child_stdout_writer.close
|
62
|
+
child_stderr_writer.close
|
63
|
+
|
64
|
+
Kernel.exec(command)
|
65
|
+
end
|
66
|
+
child_stdout_writer.close
|
67
|
+
child_stderr_writer.close
|
68
|
+
|
69
|
+
reader_writer_key = {parent_stdout_reader => :stdout, parent_stderr_reader => :stderr}
|
70
|
+
reader_writer_map = {parent_stdout_reader => options.ui.stdout, parent_stderr_reader => options.ui.stderr}
|
71
|
+
|
72
|
+
direct_log(:info) { log_header("COMMAND") }
|
73
|
+
direct_log(:info) { "#{command.inspect}\n" }
|
74
|
+
direct_log(:info) { log_header("STARTED") }
|
75
|
+
|
76
|
+
begin
|
77
|
+
Timeout.timeout(options.timeout) do
|
78
|
+
loop do
|
79
|
+
pipes = IO.select(reader_writer_map.keys, [], reader_writer_map.keys).first
|
80
|
+
pipes.each do |pipe|
|
81
|
+
data = pipe.read
|
82
|
+
|
83
|
+
if (data.nil? || data.empty?)
|
84
|
+
sleep(0.1)
|
85
|
+
next
|
86
|
+
end
|
87
|
+
|
88
|
+
case reader_writer_key[pipe]
|
89
|
+
when :stdout then
|
90
|
+
if !stdout_header
|
91
|
+
direct_log(:info) { log_header("STDOUT") }
|
92
|
+
stdout_header = true
|
93
|
+
stderr_header = false
|
94
|
+
end
|
95
|
+
reader_writer_map[pipe].write(data) unless options.silence
|
96
|
+
direct_log(:info) { data }
|
97
|
+
|
98
|
+
when :stderr then
|
99
|
+
if !stderr_header
|
100
|
+
direct_log(:warn) { log_header("STDERR") }
|
101
|
+
stderr_header = true
|
102
|
+
stdout_header = false
|
103
|
+
end
|
104
|
+
reader_writer_map[pipe].write(data) unless options.silence
|
105
|
+
direct_log(:warn) { data }
|
106
|
+
end
|
107
|
+
|
108
|
+
output += data
|
109
|
+
|
110
|
+
options.on_progress.nil? or options.on_progress.call
|
111
|
+
end
|
112
|
+
|
113
|
+
break if reader_writer_map.keys.all?{ |reader| reader.eof? }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
rescue Timeout::Error => e
|
117
|
+
direct_log(:fatal) { log_header("TIMEOUT") }
|
118
|
+
log_and_raise(CommandError, "Process timed out after #{options.timeout} seconds!")
|
119
|
+
end
|
120
|
+
|
121
|
+
Process.waitpid(pid)
|
122
|
+
exit_code = $?.exitstatus
|
123
|
+
direct_log(:info) { log_header("STOPPED") }
|
124
|
+
|
125
|
+
parent_stdout_reader.close
|
126
|
+
parent_stderr_reader.close
|
127
|
+
|
128
|
+
options.ui.logger.debug { "exit_code(#{exit_code})" }
|
129
|
+
|
130
|
+
if !options.ignore_exit_status && (exit_code != options.exit_code)
|
131
|
+
log_and_raise(CommandError, "exec(#{command.inspect}, #{options.inspect}) failed! [#{exit_code}]")
|
132
|
+
end
|
133
|
+
OpenStruct.new(:command => command, :output => output, :exit_code => exit_code)
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ZTK
|
2
|
+
class Command
|
3
|
+
|
4
|
+
# Command Private Functionality
|
5
|
+
module Private
|
6
|
+
|
7
|
+
# Returns a string in the format of "user@hostname" for the current
|
8
|
+
# shell.
|
9
|
+
def tag
|
10
|
+
@@hostname ||= Socket.gethostname.split('.').first.strip
|
11
|
+
"#{ENV['USER']}@#{@@hostname}"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Formats a header suitable for writing to the direct logger when logging
|
15
|
+
# sessions.
|
16
|
+
def log_header(what)
|
17
|
+
count = 8
|
18
|
+
sep = ("=" * count)
|
19
|
+
header = [sep, "[ #{what} ]", sep, "[ #{tag} ]", sep, "[ #{what} ]", sep].join
|
20
|
+
"#{header}\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/ztk/locator.rb
CHANGED
@@ -31,6 +31,19 @@ module ZTK
|
|
31
31
|
raise LocatorError, "Could not locate '#{File.join(args)}'!"
|
32
32
|
end
|
33
33
|
|
34
|
+
# Returns the root for the filesystem we are operating on. Ignores
|
35
|
+
# mount boundries on *nix.
|
36
|
+
#
|
37
|
+
# For all flavors of *nix this should always return "/".
|
38
|
+
#
|
39
|
+
# Windows should expect something similar to "C:\".
|
40
|
+
#
|
41
|
+
# @return [String] The root path of the file-system. For unix this should
|
42
|
+
# always be "/". For windows this should be something like "C:\".
|
43
|
+
def root
|
44
|
+
Dir.pwd.split(File::SEPARATOR).first
|
45
|
+
end
|
46
|
+
|
34
47
|
end
|
35
48
|
|
36
49
|
end
|
data/lib/ztk/report.rb
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
require 'socket'
|
2
|
-
require 'timeout'
|
3
|
-
|
4
1
|
module ZTK
|
5
2
|
|
6
3
|
# Report Error Class
|
@@ -19,169 +16,28 @@ module ZTK
|
|
19
16
|
#
|
20
17
|
# @author Zachary Patten <zachary AT jovelabs DOT com>
|
21
18
|
class Report < ZTK::Base
|
19
|
+
require 'socket'
|
20
|
+
require 'timeout'
|
21
|
+
|
22
|
+
require 'ztk/report/list'
|
23
|
+
require 'ztk/report/private'
|
24
|
+
require 'ztk/report/spreadsheet'
|
25
|
+
|
26
|
+
include ZTK::Report::List
|
27
|
+
include ZTK::Report::Spreadsheet
|
22
28
|
|
23
29
|
# @param [Hash] configuration Configuration options hash.
|
24
30
|
def initialize(configuration={})
|
25
31
|
super({
|
26
32
|
}.merge(configuration))
|
27
|
-
config.ui.logger.debug { "config=#{config.send(:table).inspect}" }
|
28
|
-
end
|
29
|
-
|
30
|
-
# Displays data in a spreadsheet style.
|
31
|
-
#
|
32
|
-
# +-------------+-------+-------+--------+----------------+-------------------+--------------+---------+
|
33
|
-
# | NAME | ALIVE | ARCH | DISTRO | IP | MAC | CHEF VERSION | PERSIST |
|
34
|
-
# +-------------+-------+-------+--------+----------------+-------------------+--------------+---------+
|
35
|
-
# | sudo | false | amd64 | ubuntu | 192.168.99.110 | 00:00:5e:34:d6:aa | N/A | true |
|
36
|
-
# | timezone | false | amd64 | ubuntu | 192.168.122.47 | 00:00:5e:92:d7:f6 | N/A | true |
|
37
|
-
# | chef-client | false | amd64 | ubuntu | 192.168.159.98 | 00:00:5e:c7:ce:26 | N/A | true |
|
38
|
-
# | users | false | amd64 | ubuntu | 192.168.7.78 | 00:00:5e:89:f9:50 | N/A | true |
|
39
|
-
# +-------------+-------+-------+--------+----------------+-------------------+--------------+---------+
|
40
|
-
#
|
41
|
-
# @param [Array<Object>,Object] dataset A single object or an array of
|
42
|
-
# objects for which we want to generate a report
|
43
|
-
# @param [Array] headers An array of headers used for ordering the output.
|
44
|
-
# @return [OpenStruct]
|
45
|
-
def spreadsheet(dataset, headers, &block)
|
46
|
-
!block_given? and log_and_raise(ReportError, "You must supply a block!")
|
47
|
-
headers.nil? and log_and_raise(ReportError, "Headers can not be nil!")
|
48
|
-
dataset.nil? and log_and_raise(ReportError, "Dataset can not be nil!")
|
49
|
-
|
50
|
-
rows = Array.new
|
51
|
-
max_lengths = OpenStruct.new
|
52
|
-
headers = headers.map(&:to_s).map(&:downcase).map(&:to_sym)
|
53
|
-
|
54
|
-
if dataset.is_a?(Array)
|
55
|
-
dataset.each do |data|
|
56
|
-
rows << block.call(data)
|
57
|
-
end
|
58
|
-
else
|
59
|
-
rows << block.call(dataset)
|
60
|
-
end
|
61
|
-
rows.compact!
|
62
|
-
|
63
|
-
if rows.count > 0
|
64
|
-
max_lengths = max_spreadsheet_lengths(headers, rows)
|
65
|
-
header_line = headers.collect { |header| "%-#{max_lengths.send(:table)[header]}s" % header.to_s.upcase }
|
66
|
-
header_line = format_row(header_line)
|
67
|
-
|
68
|
-
config.ui.stdout.puts(format_header(headers, max_lengths))
|
69
|
-
config.ui.stdout.puts(header_line)
|
70
|
-
config.ui.stdout.puts(format_header(headers, max_lengths))
|
71
|
-
|
72
|
-
rows.each do |row|
|
73
|
-
row_line = headers.collect do |header|
|
74
|
-
header_length = max_lengths.send(:table)[header]
|
75
|
-
content = row.send(:table)[header]
|
76
|
-
|
77
|
-
"%-#{header_length}s" % content
|
78
|
-
end
|
79
|
-
|
80
|
-
row_line = format_row(row_line)
|
81
|
-
config.ui.stdout.puts(row_line)
|
82
|
-
end
|
83
|
-
|
84
|
-
config.ui.stdout.puts(format_header(headers, max_lengths))
|
85
|
-
OpenStruct.new(:rows => rows, :max_lengths => max_lengths, :width => calculate_spreadsheet_width(headers, max_lengths))
|
86
|
-
else
|
87
|
-
OpenStruct.new(:rows => rows, :max_lengths => 0, :width => 0)
|
88
|
-
end
|
89
|
-
|
90
|
-
end
|
91
|
-
|
92
|
-
# Displays data in a key-value list style.
|
93
|
-
#
|
94
|
-
# +-------------------------------------------------------------------+
|
95
|
-
# | PROVIDER: Cucumber::Chef::Provider::Vagrant |
|
96
|
-
# | ID: default |
|
97
|
-
# | STATE: aborted |
|
98
|
-
# | USERNAME: vagrant |
|
99
|
-
# | IP ADDRESS: 127.0.0.1 |
|
100
|
-
# | PORT: 2222 |
|
101
|
-
# | CHEF-SERVER API: http://127.0.0.1:4000 |
|
102
|
-
# | CHEF-SERVER WEBUI: http://127.0.0.1:4040 |
|
103
|
-
# | CHEF-SERVER DEFAULT USER: admin |
|
104
|
-
# | CHEF-SERVER DEFAULT PASSWORD: p@ssw0rd1 |
|
105
|
-
# +-------------------------------------------------------------------+
|
106
|
-
#
|
107
|
-
# @param [Array<Object>,Object] dataset A single object or an array of
|
108
|
-
# objects for which we want to generate a report
|
109
|
-
# @param [Array] headers An array of headers used for ordering the output.
|
110
|
-
# @return [OpenStruct]
|
111
|
-
def list(dataset, headers, &block)
|
112
|
-
!block_given? and log_and_raise(ReportError, "You must supply a block!")
|
113
|
-
headers.nil? and log_and_raise(ReportError, "Headers can not be nil!")
|
114
|
-
dataset.nil? and log_and_raise(ReportError, "Dataset can not be nil!")
|
115
33
|
|
116
|
-
|
117
|
-
max_lengths = OpenStruct.new
|
118
|
-
headers = headers.map(&:to_s).map(&:downcase).map(&:to_sym)
|
119
|
-
|
120
|
-
if dataset.is_a?(Array)
|
121
|
-
dataset.each do |data|
|
122
|
-
rows << block.call(data)
|
123
|
-
end
|
124
|
-
else
|
125
|
-
rows << block.call(dataset)
|
126
|
-
end
|
127
|
-
rows.compact!
|
128
|
-
|
129
|
-
if rows.count > 0
|
130
|
-
max_key_length = headers.collect{ |header| header.to_s.length }.max
|
131
|
-
max_value_length = rows.collect{ |row| headers.collect{ |header| row.send(:table)[header].to_s.length }.max }.max
|
132
|
-
|
133
|
-
width = (max_key_length + max_value_length + 2 + 2 + 2)
|
134
|
-
|
135
|
-
rows.each do |row|
|
136
|
-
config.ui.stdout.puts("+#{"-" * width}+")
|
137
|
-
headers.each do |header|
|
138
|
-
entry_line = format_entry(header, max_key_length, row.send(:table)[header], max_value_length)
|
139
|
-
config.ui.stdout.puts(entry_line)
|
140
|
-
end
|
141
|
-
end
|
142
|
-
config.ui.stdout.puts("+#{"-" * width}+")
|
143
|
-
OpenStruct.new(:rows => rows, :max_key_length => max_key_length, :max_value_length => max_value_length, :width => width)
|
144
|
-
else
|
145
|
-
OpenStruct.new(:rows => rows, :max_key_length => 0, :max_value_length => 0, :width => 0)
|
146
|
-
end
|
34
|
+
config.ui.logger.debug { "config=#{config.send(:table).inspect}" }
|
147
35
|
end
|
148
36
|
|
149
37
|
|
150
38
|
private
|
151
39
|
|
152
|
-
|
153
|
-
max_lengths = OpenStruct.new
|
154
|
-
headers.each do |header|
|
155
|
-
collection = [header, rows.collect{|r| r.send(:table)[header] } ].flatten
|
156
|
-
maximum = collection.map(&:to_s).map(&:length).max
|
157
|
-
max_lengths.send(:table)[header] = maximum
|
158
|
-
end
|
159
|
-
|
160
|
-
max_lengths
|
161
|
-
end
|
162
|
-
|
163
|
-
def calculate_spreadsheet_width(headers, max_lengths)
|
164
|
-
header_lengths = ((headers.count * 3) - 3)
|
165
|
-
max_length = max_lengths.send(:table).values.reduce(:+)
|
166
|
-
(2 + max_length + header_lengths + 2)
|
167
|
-
end
|
168
|
-
|
169
|
-
def format_header(headers, lengths)
|
170
|
-
line = headers.collect do |header|
|
171
|
-
"-" * lengths.send(:table)[header]
|
172
|
-
end
|
173
|
-
|
174
|
-
["+-", line.join("-+-"), "-+"].join.strip
|
175
|
-
end
|
176
|
-
|
177
|
-
def format_row(*args)
|
178
|
-
spacer = " "
|
179
|
-
[spacer, args, spacer].flatten.join(" | ").strip
|
180
|
-
end
|
181
|
-
|
182
|
-
def format_entry(key, key_length, value, value_length)
|
183
|
-
"| %#{key_length}s: %-#{value_length}s |" % [key.to_s.upcase, value.to_s]
|
184
|
-
end
|
40
|
+
include ZTK::Report::Private
|
185
41
|
|
186
42
|
end
|
187
43
|
|