samovar 1.10.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -2
- data/lib/samovar/command.rb +42 -31
- data/lib/samovar/{command/system.rb → error.rb} +26 -26
- data/lib/samovar/many.rb +16 -5
- data/lib/samovar/nested.rb +21 -10
- data/lib/samovar/one.rb +15 -4
- data/lib/samovar/option.rb +107 -0
- data/lib/samovar/options.rb +40 -77
- data/lib/samovar/output.rb +1 -151
- data/lib/samovar/{command/track_time.rb → output/columns.rb} +18 -13
- data/lib/samovar/{command/terminal.rb → output/header.rb} +12 -7
- data/lib/samovar/output/row.rb +38 -0
- data/lib/samovar/output/rows.rb +86 -0
- data/lib/samovar/output/usage_formatter.rb +87 -0
- data/lib/samovar/split.rb +15 -4
- data/lib/samovar/table.rb +61 -13
- data/lib/samovar/version.rb +1 -1
- data/spec/samovar/coerce_spec.rb +19 -1
- data/spec/samovar/command_spec.rb +21 -11
- data/spec/samovar/many_spec.rb +49 -0
- data/spec/samovar/nested_spec.rb +78 -9
- data/spec/samovar/one_spec.rb +49 -0
- data/spec/samovar/options_spec.rb +22 -3
- data/spec/samovar/split_spec.rb +49 -0
- data/spec/samovar/table_spec.rb +40 -0
- data/spec/spec_helper.rb +19 -1
- metadata +17 -6
- data/lib/samovar/output/line_wrapper.rb +0 -87
data/lib/samovar/options.rb
CHANGED
@@ -18,90 +18,16 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
require_relative '
|
21
|
+
require_relative 'option'
|
22
22
|
|
23
23
|
module Samovar
|
24
|
-
class Option
|
25
|
-
def initialize(flags, description, key: nil, default: nil, value: nil, type: nil, &block)
|
26
|
-
@flags = Flags.new(flags)
|
27
|
-
@description = description
|
28
|
-
|
29
|
-
if key
|
30
|
-
@key = key
|
31
|
-
else
|
32
|
-
@key = @flags.first.key
|
33
|
-
end
|
34
|
-
|
35
|
-
@default = default
|
36
|
-
|
37
|
-
# If the value is given, it overrides the user specified input.
|
38
|
-
@value = value
|
39
|
-
@value ||= true if @flags.boolean?
|
40
|
-
|
41
|
-
@type = type
|
42
|
-
@block = block
|
43
|
-
end
|
44
|
-
|
45
|
-
attr :flags
|
46
|
-
attr :description
|
47
|
-
attr :type
|
48
|
-
|
49
|
-
attr :default
|
50
|
-
|
51
|
-
attr :key
|
52
|
-
|
53
|
-
def coerce_type(result)
|
54
|
-
if @type == Integer
|
55
|
-
Integer(result)
|
56
|
-
elsif @type == Float
|
57
|
-
Float(result)
|
58
|
-
elsif @type == Symbol
|
59
|
-
result.to_sym
|
60
|
-
elsif @type.respond_to? :call
|
61
|
-
@type.call(result)
|
62
|
-
elsif @type.respond_to? :new
|
63
|
-
@type.new(result)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def coerce(result)
|
68
|
-
if @type
|
69
|
-
result = coerce_type(result)
|
70
|
-
end
|
71
|
-
|
72
|
-
if @block
|
73
|
-
result = @block.call(result)
|
74
|
-
end
|
75
|
-
|
76
|
-
return result
|
77
|
-
end
|
78
|
-
|
79
|
-
def parse(input, default = @default)
|
80
|
-
if result = @flags.parse(input)
|
81
|
-
@value.nil? ? coerce(result) : @value
|
82
|
-
end || default
|
83
|
-
end
|
84
|
-
|
85
|
-
def to_s
|
86
|
-
@flags
|
87
|
-
end
|
88
|
-
|
89
|
-
def to_a
|
90
|
-
unless @default.nil?
|
91
|
-
[@flags, @description, "Default: #{@default}"]
|
92
|
-
else
|
93
|
-
[@flags, @description]
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
24
|
class Options
|
99
25
|
def self.parse(*args, **options, &block)
|
100
26
|
options = self.new(*args, **options)
|
101
27
|
|
102
28
|
options.instance_eval(&block) if block_given?
|
103
29
|
|
104
|
-
return options
|
30
|
+
return options.freeze
|
105
31
|
end
|
106
32
|
|
107
33
|
def initialize(title = "Options", key: :options)
|
@@ -116,13 +42,50 @@ module Samovar
|
|
116
42
|
@defaults = {}
|
117
43
|
end
|
118
44
|
|
45
|
+
def initialize_dup(source)
|
46
|
+
super
|
47
|
+
|
48
|
+
@ordered = @ordered.dup
|
49
|
+
@keyed = @keyed.dup
|
50
|
+
@defaults = @defaults.dup
|
51
|
+
end
|
52
|
+
|
53
|
+
attr :title
|
54
|
+
attr :ordered
|
55
|
+
|
119
56
|
attr :key
|
120
57
|
attr :defaults
|
121
58
|
|
59
|
+
def freeze
|
60
|
+
return self if frozen?
|
61
|
+
|
62
|
+
@ordered.freeze
|
63
|
+
@keyed.freeze
|
64
|
+
@defaults.freeze
|
65
|
+
|
66
|
+
@ordered.each(&:freeze)
|
67
|
+
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
def each(&block)
|
72
|
+
@ordered.each(&block)
|
73
|
+
end
|
74
|
+
|
75
|
+
def empty?
|
76
|
+
@ordered.empty?
|
77
|
+
end
|
78
|
+
|
122
79
|
def option(*args, **options, &block)
|
123
80
|
self << Option.new(*args, **options, &block)
|
124
81
|
end
|
125
82
|
|
83
|
+
def merge!(options)
|
84
|
+
options.each do |option|
|
85
|
+
self << option
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
126
89
|
def << option
|
127
90
|
@ordered << option
|
128
91
|
option.flags.each do |flag|
|
@@ -138,7 +101,7 @@ module Samovar
|
|
138
101
|
end
|
139
102
|
end
|
140
103
|
|
141
|
-
def parse(input, default)
|
104
|
+
def parse(input, parent = nil, default = nil)
|
142
105
|
values = (default || @defaults).dup
|
143
106
|
|
144
107
|
while option = @keyed[input.first]
|
data/lib/samovar/output.rb
CHANGED
@@ -18,154 +18,4 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
require 'event/terminal'
|
24
|
-
|
25
|
-
module Samovar
|
26
|
-
module Output
|
27
|
-
class Header
|
28
|
-
def initialize(name, object)
|
29
|
-
@name = name
|
30
|
-
@object = object
|
31
|
-
end
|
32
|
-
|
33
|
-
attr :name
|
34
|
-
attr :object
|
35
|
-
|
36
|
-
def align(columns)
|
37
|
-
@object.command_line(@name)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
class Row < Array
|
42
|
-
def initialize(object)
|
43
|
-
@object = object
|
44
|
-
super object.to_a.collect(&:to_s)
|
45
|
-
end
|
46
|
-
|
47
|
-
attr :object
|
48
|
-
|
49
|
-
def align(columns)
|
50
|
-
self.collect.with_index do |value, index|
|
51
|
-
value.ljust(columns.widths[index])
|
52
|
-
end.join(' ')
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
class Columns
|
57
|
-
def initialize(rows)
|
58
|
-
@rows = rows
|
59
|
-
@widths = calculate_widths(rows)
|
60
|
-
end
|
61
|
-
|
62
|
-
attr :widths
|
63
|
-
|
64
|
-
def calculate_widths(rows)
|
65
|
-
widths = []
|
66
|
-
|
67
|
-
rows.each do |row|
|
68
|
-
row.each.with_index do |column, index|
|
69
|
-
(widths[index] ||= []) << column.size
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
return widths.collect(&:max)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
class Rows
|
78
|
-
include Enumerable
|
79
|
-
|
80
|
-
def initialize(level = 0)
|
81
|
-
@level = level
|
82
|
-
@rows = []
|
83
|
-
end
|
84
|
-
|
85
|
-
attr :level
|
86
|
-
|
87
|
-
def empty?
|
88
|
-
@rows.empty?
|
89
|
-
end
|
90
|
-
|
91
|
-
def first
|
92
|
-
@rows.first
|
93
|
-
end
|
94
|
-
|
95
|
-
def last
|
96
|
-
@rows.last
|
97
|
-
end
|
98
|
-
|
99
|
-
def indentation
|
100
|
-
@indentation ||= "\t" * @level
|
101
|
-
end
|
102
|
-
|
103
|
-
def each(ignore_nested: false, &block)
|
104
|
-
return to_enum(:each, ignore_nested: ignore_nested) unless block_given?
|
105
|
-
|
106
|
-
@rows.each do |row|
|
107
|
-
if row.is_a?(self.class)
|
108
|
-
row.each(&block) unless ignore_nested
|
109
|
-
else
|
110
|
-
yield row, self
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def << object
|
116
|
-
@rows << Row.new(object)
|
117
|
-
|
118
|
-
return self
|
119
|
-
end
|
120
|
-
|
121
|
-
def columns
|
122
|
-
@columns ||= Columns.new(@rows.select{|row| row.is_a? Array})
|
123
|
-
end
|
124
|
-
|
125
|
-
def nested(*args)
|
126
|
-
@rows << Header.new(*args)
|
127
|
-
|
128
|
-
nested_rows = self.class.new(@level + 1)
|
129
|
-
|
130
|
-
yield nested_rows
|
131
|
-
|
132
|
-
@rows << nested_rows
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
class DetailedFormatter < Mapping::Model
|
137
|
-
def self.print(rows, output)
|
138
|
-
self.new(rows, output).print
|
139
|
-
end
|
140
|
-
|
141
|
-
def initialize(rows, output)
|
142
|
-
@rows = rows
|
143
|
-
@output = output
|
144
|
-
@width = 80
|
145
|
-
|
146
|
-
@terminal = Event::Terminal.for(@output)
|
147
|
-
@terminal[:header] = @terminal.style(:blue, nil, :bright)
|
148
|
-
@terminal[:description] = @terminal.style(:blue)
|
149
|
-
end
|
150
|
-
|
151
|
-
map(Header) do |header, rows|
|
152
|
-
@terminal.puts unless header == @rows.first
|
153
|
-
@terminal.puts "#{rows.indentation}#{header.object.command_line(header.name)}", style: :header
|
154
|
-
@terminal.puts "#{rows.indentation}\t#{header.object.description}", style: :description
|
155
|
-
@terminal.puts
|
156
|
-
end
|
157
|
-
|
158
|
-
map(Row) do |row, rows|
|
159
|
-
@terminal.puts "#{rows.indentation}#{row.align(rows.columns)}"
|
160
|
-
end
|
161
|
-
|
162
|
-
map(Rows) do |items|
|
163
|
-
items.collect{|row, rows| map(row, rows)}
|
164
|
-
end
|
165
|
-
|
166
|
-
def print
|
167
|
-
map(@rows)
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
21
|
+
require_relative 'output/usage_formatter'
|
@@ -18,22 +18,27 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
require 'time'
|
22
|
-
|
23
|
-
require_relative 'terminal'
|
24
|
-
|
25
21
|
module Samovar
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
module Output
|
23
|
+
class Columns
|
24
|
+
def initialize(rows)
|
25
|
+
@rows = rows
|
26
|
+
@widths = calculate_widths(rows)
|
27
|
+
end
|
29
28
|
|
30
|
-
|
31
|
-
ensure
|
32
|
-
end_time = Time.now
|
33
|
-
elapsed_time = end_time - start_time
|
29
|
+
attr :widths
|
34
30
|
|
35
|
-
|
36
|
-
|
31
|
+
def calculate_widths(rows)
|
32
|
+
widths = []
|
33
|
+
|
34
|
+
rows.each do |row|
|
35
|
+
row.each.with_index do |column, index|
|
36
|
+
(widths[index] ||= []) << column.size
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
return widths.collect(&:max)
|
41
|
+
end
|
37
42
|
end
|
38
43
|
end
|
39
44
|
end
|
@@ -18,14 +18,19 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
require 'time'
|
22
|
-
|
23
21
|
module Samovar
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
module Output
|
23
|
+
class Header
|
24
|
+
def initialize(name, object)
|
25
|
+
@name = name
|
26
|
+
@object = object
|
27
|
+
end
|
28
|
+
|
29
|
+
attr :name
|
30
|
+
attr :object
|
31
|
+
|
32
|
+
def align(columns)
|
33
|
+
@object.command_line(@name)
|
29
34
|
end
|
30
35
|
end
|
31
36
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Samovar
|
22
|
+
module Output
|
23
|
+
class Row < Array
|
24
|
+
def initialize(object)
|
25
|
+
@object = object
|
26
|
+
super object.to_a.collect(&:to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
attr :object
|
30
|
+
|
31
|
+
def align(columns)
|
32
|
+
self.collect.with_index do |value, index|
|
33
|
+
value.ljust(columns.widths[index])
|
34
|
+
end.join(' ')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'header'
|
22
|
+
require_relative 'columns'
|
23
|
+
require_relative 'row'
|
24
|
+
|
25
|
+
module Samovar
|
26
|
+
module Output
|
27
|
+
class Rows
|
28
|
+
include Enumerable
|
29
|
+
|
30
|
+
def initialize(level = 0)
|
31
|
+
@level = level
|
32
|
+
@rows = []
|
33
|
+
end
|
34
|
+
|
35
|
+
attr :level
|
36
|
+
|
37
|
+
def empty?
|
38
|
+
@rows.empty?
|
39
|
+
end
|
40
|
+
|
41
|
+
def first
|
42
|
+
@rows.first
|
43
|
+
end
|
44
|
+
|
45
|
+
def last
|
46
|
+
@rows.last
|
47
|
+
end
|
48
|
+
|
49
|
+
def indentation
|
50
|
+
@indentation ||= "\t" * @level
|
51
|
+
end
|
52
|
+
|
53
|
+
def each(ignore_nested: false, &block)
|
54
|
+
return to_enum(:each, ignore_nested: ignore_nested) unless block_given?
|
55
|
+
|
56
|
+
@rows.each do |row|
|
57
|
+
if row.is_a?(self.class)
|
58
|
+
row.each(&block) unless ignore_nested
|
59
|
+
else
|
60
|
+
yield row, self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def << object
|
66
|
+
@rows << Row.new(object)
|
67
|
+
|
68
|
+
return self
|
69
|
+
end
|
70
|
+
|
71
|
+
def columns
|
72
|
+
@columns ||= Columns.new(@rows.select{|row| row.is_a? Array})
|
73
|
+
end
|
74
|
+
|
75
|
+
def nested(*args)
|
76
|
+
@rows << Header.new(*args)
|
77
|
+
|
78
|
+
nested_rows = self.class.new(@level + 1)
|
79
|
+
|
80
|
+
yield nested_rows
|
81
|
+
|
82
|
+
@rows << nested_rows
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|