This repository has been archived by the owner on Jul 30, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 14
/
output.rb
244 lines (219 loc) · 8.81 KB
/
output.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# frozen_string_literal: true
# Ruby internal
require 'csv'
require 'json'
require 'yaml'
# External dependencies
require 'paint'
# Pass Station module
module PassStation
# Password database handling
class DB
# Output the data in the chosen format
# @param formatter [String] Engine to use to format the data: +table+, +'pretty-table'+, +JSON+, +CSV+, +YAML+
# @param data [Array<CSV::Row>]
# @return [Array<String>] formatted output
def output(formatter, data)
# Convert string to class
Object.const_get("PassStation::Output::#{normalize(formatter)}").format(data)
end
# Output the data in the chosen format (list command)
# @param formatter [String] Engine to use to format the data: +table+, +'pretty-table'+, +JSON+, +CSV+, +YAML+
# @return [Array<String>] formatted output
def output_list(formatter)
data_nil?
output(formatter, @data)
end
# Output the data in the chosen format (search command)
# @param formatter [String] Engine to use to format the data: +table+, +'pretty-table'+, +JSON+, +CSV+, +YAML+
# @return [Array<String>] formatted output
def output_search(formatter)
return [] if @search_result.empty?
output(formatter, @search_result)
end
# Highlight (colorize) a searched term in the input
# When used with the search command, it will ignore in which column the
# search was made, and will instead colorize in every columns.
# @param term [String] the searched term
# @param text [String] the output in which the colorization must be made
# @param sensitive [Boolean] case sensitive or not
# @return [Array<String>] colorized output
def highlight_found(term, text, sensitive)
text.map do |x|
rgxp = build_regexp(term, sensitive: sensitive)
x.gsub(rgxp) { |s| Paint[s, :red] }
end
end
# Normalize string to be class name compatible
# Join splitted words and capitalize
# @param formatter [String] formatter name
# @return [String] normalized name (class compatible)
def normalize(formatter)
formatter.split('-').map(&:capitalize).join
end
protected :normalize
end
# Output handling module containing all formatter engines
# Meant to be used by the CLI binary but could be re-used in many other ways
module Output
# Simple table formatter
class Table
class << self
# Format the +Array<CSV::Row>+ into a simple table with justified columns
# @param table [Array<CSV::Row>] an +Array<CSV::Row>+
# @return [Array<String>] the formatted table ready to be printed
def format(table)
out = []
colsizes = colsizes_count(table)
out.push(headers(colsizes))
table.each do |r|
out.push(justify_row(r, colsizes))
end
out
end
# Calculate column size (max item size)
# @param table [Array<CSV::Row>]
# @param column [Symbol] the symbol of the column
# @return [Integer] the column size
def colsize_count(table, column)
table.map { |i| i[column].nil? ? 0 : i[column].size }.max + 1
end
# Calculate the size of all columns (max item size)
# @param table [Array<CSV::Row>]
# @return [Hash] keys are columns name, values are columns size
def colsizes_count(table)
colsizes = table.first.to_h.keys.each_with_object({}) do |c, h|
h[c] = colsize_count(table, c)
end
correct_min_colsizes(colsizes)
end
# Correct colsizes to be at least of the size of the headers (case when
# values are shorter than headers and breaks the table display)
# @param colsizes [Hash] hash containing the column size for each column as returned by {colsizes_count}
# @return [Hash] fixed colsizes, keys are columns name, values are columns size
def correct_min_colsizes(colsizes)
min_colsizes = {
productvendor: 14,
username: 9,
password: 9
}
min_colsizes.each_with_object({}) { |(k, v), h| h[k] = [v, colsizes[k]].max }
end
# Left justify an element of the column
# @param row [CSV::Row] +CSV::Row+
# @param column [Symbol] the symbol of the column
# @param colsizes [Hash] hash containing the column size for each column as returned by {colsizes_count}
# @return [String] the justified element
def justify(row, column, colsizes)
row[column].to_s.ljust(colsizes[column])
end
# Left justify all elements of the column
# @param row [CSV::Row] +CSV::Row+
# @param colsizes [Hash] hash containing the column size for each column as returned by {colsizes_count}
# @return [String] the justified row
def justify_row(row, colsizes)
out = ''
row.to_h.each_key do |col|
out += justify(row, col, colsizes)
end
out
end
# Generate justified headers
# @param colsizes [Hash] hash containing the column size for each column as returned by {colsizes_count}
# @return [String] the justified headers
def headers(colsizes)
colsizes.map { |k, v| k.to_s.ljust(v) }.join.to_s
end
protected :colsize_count, :colsizes_count, :justify, :justify_row, :headers, :correct_min_colsizes
end
end
# Pretty table with ASCII borders formatter
class PrettyTable < Table
class << self
# Format the +Array<CSV::Row>+ into a simple table with justified columns
# @param table [Array<CSV::Row>] an +Array<CSV::Row>+
# @return [Array<String>] the formatted table ready to be printed
def format(table)
out = []
colsizes = colsizes_count(table)
out.push(dividers(colsizes))
out.push(headers(colsizes))
out.push(dividers(colsizes))
table.each do |r|
out.push(justify_row(r, colsizes))
end
out.push(dividers(colsizes))
end
# Left justify an element of the column
# @param row [CSV::Row] +CSV::Row+
# @param column [Symbol] the symbol of the column
# @param colsizes [Hash] hash containing the column size for each column as returned by {colsizes_count}
# @return [String] the justified element
def justify(row, column, colsizes)
row[column].to_s.ljust(colsizes[column] - 1)
end
# Left justify all elements of the column
# @param row [CSV::Row] +CSV::Row+
# @param colsizes [Hash] hash containing the column size for each column as returned by {colsizes_count}
# @return [String] the justified row
def justify_row(row, colsizes)
out = '| '
row.to_h.each_key do |col|
out += "#{justify(row, col, colsizes)} | "
end
out
end
# Generate dividers
# @param colsizes [Hash] hash containing the column size for each column as returned by {colsizes_count}
# @return [String] divider line
def dividers(colsizes)
"+#{colsizes.map { |_, cs| '-' * (cs + 1) }.join('+')}+"
end
# Generate justified headers
# @param colsizes [Hash] hash containing the column size for each column as returned by {colsizes_count}
# @return [String] the justified headers
def headers(colsizes)
"| #{colsizes.map { |k, v| k.to_s.ljust(v - 1) }.join(' | ')} |"
end
protected :dividers, :headers, :justify_row, :justify
end
end
# CSV formatter
class Csv
class << self
# Format the +Array<CSV::Row>+ into a CSV
# @param table [Array<CSV::Row>] an +Array<CSV::Row>+
# @return [Array<String>] the formatted CSV ready to be printed
def format(table)
CSV::Table.new(table).to_csv.split("\n")
end
end
end
# JSON formatter
class Json
class << self
# Format the +Array<CSV::Row>+ into JSON
# @param table [Array<CSV::Row>] an +Array<CSV::Row>+
# @return [Array<String>] the formatted JSON ready to be printed (only
# one element on the array, keep an array for compatibility with
# {highlight_found} and homogeneity with other formatters)
def format(table)
[table.map(&:to_h).to_json]
end
end
end
# YAML formatter
class Yaml
class << self
# Format the +Array<CSV::Row>+ into YAML
# @param table [Array<CSV::Row>] an +Array<CSV::Row>+
# @return [Array<String>] the formatted YAML ready to be printed (only
# one element on the array, keep an array for compatibility with
# {highlight_found} and homogeneity with other formatters)
def format(table)
[table.map(&:to_h).to_yaml]
end
end
end
end
end