ztk 1.6.2 → 1.6.3
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.
- 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
|
|