make_menu 0.1.1 → 1.1.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/make_menu/badge_set.rb +34 -0
- data/lib/make_menu/builder.rb +51 -0
- data/lib/make_menu/console/color_string.rb +135 -0
- data/lib/make_menu/console/prompter.rb +47 -0
- data/lib/make_menu/field_set.rb +52 -0
- data/lib/make_menu/menu.rb +103 -112
- data/lib/make_menu/menu_item.rb +2 -6
- data/lib/make_menu/text/column.rb +41 -0
- data/lib/make_menu/text/table.rb +77 -0
- data/lib/make_menu/version.rb +4 -2
- data/lib/make_menu.rb +8 -26
- data/make_menu.gemspec +10 -8
- metadata +9 -5
- data/lib/make_menu/color_string.rb +0 -141
- data/lib/make_menu/text_column.rb +0 -43
- data/lib/make_menu/text_table.rb +0 -75
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 220d243f1715ae665a12f8ed497f109209ba10fc5282298b7d9fec956ff99856
|
|
4
|
+
data.tar.gz: dd1adb0c2bed5934598a72e2b3d5ffc4c48477b438b2a1c6206351d742868e9e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e0f933ee0f3af0635c97dfc166e1c416784e0a5b24fac03cadab830179ed36fdce07711fc32e03ba353615de72128534b52d2cbf7b2e6009765043cb8bfb5d7e
|
|
7
|
+
data.tar.gz: 87c98103a07b39f4d54e4278042b5f110754ccad21b299b073928945b142c507636e1685ef811f2f93f9f52309b710876d052f0c23c1e7b6ceddbd918f1b9df5
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module MakeMenu
|
|
2
|
+
class BadgeSet
|
|
3
|
+
def initialize
|
|
4
|
+
@badges = []
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def display
|
|
8
|
+
rows = []
|
|
9
|
+
row = ''
|
|
10
|
+
@badges.each do |badge|
|
|
11
|
+
label = badge[:label]
|
|
12
|
+
value = badge[:handler].call ? badge[:on] : badge[:off]
|
|
13
|
+
if row.decolor.size + label.decolor.size + value.decolor.size >= (0.7 * ::TTY::Screen.cols)
|
|
14
|
+
rows << row
|
|
15
|
+
row = ''
|
|
16
|
+
end
|
|
17
|
+
row += " #{label}#{value} "
|
|
18
|
+
end
|
|
19
|
+
rows << row
|
|
20
|
+
|
|
21
|
+
puts rows.join("\n\n").align_block(:center)
|
|
22
|
+
puts
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def add(label = '', on: ' ON '.green_bg.bold, off: ' OFF '.red_bg.dark, &block)
|
|
26
|
+
@badges << {
|
|
27
|
+
label: label,
|
|
28
|
+
on: on,
|
|
29
|
+
off: off,
|
|
30
|
+
handler: block
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MakeMenu
|
|
4
|
+
module Builder
|
|
5
|
+
def self.build(makefile, menu)
|
|
6
|
+
File.open(makefile, 'r') do |file|
|
|
7
|
+
option_number = 1
|
|
8
|
+
current_group = nil
|
|
9
|
+
|
|
10
|
+
file.each_line do |line|
|
|
11
|
+
if line.start_with? '###'
|
|
12
|
+
# Group header
|
|
13
|
+
group_title = line.gsub(/###\s+/, '').strip
|
|
14
|
+
current_group = menu.add_group MenuItemGroup.new(group_title.color(menu.group_title_color))
|
|
15
|
+
|
|
16
|
+
elsif line.match(/^[a-zA-Z_-]+:.*?## .*$$/)
|
|
17
|
+
# Menu item
|
|
18
|
+
target = line.split(':').first.strip
|
|
19
|
+
description = line.split('##').last.strip
|
|
20
|
+
|
|
21
|
+
# Target 'menu' should not appear
|
|
22
|
+
next if target == 'menu'
|
|
23
|
+
|
|
24
|
+
current_group ||= menu.add_group MenuItemGroup.new('Commands'.color(menu.group_title_color))
|
|
25
|
+
|
|
26
|
+
menu.add_item current_group.add_item(MenuItem.new(option_number, target, description))
|
|
27
|
+
|
|
28
|
+
option_number += 1
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if option_number == 1
|
|
33
|
+
puts
|
|
34
|
+
puts 'No annotated targets found!'.red.bold
|
|
35
|
+
puts
|
|
36
|
+
puts 'Expecting something like this....'
|
|
37
|
+
puts " #{'my_target:'.cyan} #{'## Do some things'.yellow}"
|
|
38
|
+
puts
|
|
39
|
+
exit 1
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
rescue Errno::ENOENT => _e
|
|
43
|
+
puts
|
|
44
|
+
puts 'No Makefile!'.red.bold
|
|
45
|
+
puts
|
|
46
|
+
puts "File '#{makefile}' could not be found.".yellow
|
|
47
|
+
puts
|
|
48
|
+
exit 1
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MakeMenu
|
|
4
|
+
module Console
|
|
5
|
+
# Monkeypatch for `String`, adds methods to change console text colo(u)r
|
|
6
|
+
module ColorString
|
|
7
|
+
COLORS = {
|
|
8
|
+
white: 0,
|
|
9
|
+
normal: 0,
|
|
10
|
+
bold: 1,
|
|
11
|
+
dark: 2,
|
|
12
|
+
underline: 4,
|
|
13
|
+
blink: 5,
|
|
14
|
+
invert: 7,
|
|
15
|
+
|
|
16
|
+
black: 30,
|
|
17
|
+
red: 31,
|
|
18
|
+
green: 32,
|
|
19
|
+
yellow: 33,
|
|
20
|
+
blue: 34,
|
|
21
|
+
magenta: 35,
|
|
22
|
+
cyan: 36,
|
|
23
|
+
grey: 37,
|
|
24
|
+
|
|
25
|
+
black_bg: 40,
|
|
26
|
+
red_bg: 41,
|
|
27
|
+
green_bg: 42,
|
|
28
|
+
yellow_bg: 43,
|
|
29
|
+
blue_bg: 44,
|
|
30
|
+
magenta_bg: 45,
|
|
31
|
+
cyan_bg: 46,
|
|
32
|
+
grey_bg: 47,
|
|
33
|
+
|
|
34
|
+
dark_grey: 90,
|
|
35
|
+
light_red: 91,
|
|
36
|
+
light_green: 92,
|
|
37
|
+
light_yellow: 93,
|
|
38
|
+
light_blue: 94,
|
|
39
|
+
light_magenta: 95,
|
|
40
|
+
light_cyan: 96,
|
|
41
|
+
light_grey: 97,
|
|
42
|
+
|
|
43
|
+
dark_grey_bg: 100,
|
|
44
|
+
light_red_bg: 101,
|
|
45
|
+
light_green_bg: 102,
|
|
46
|
+
light_yellow_bg: 103,
|
|
47
|
+
light_blue_bg: 104,
|
|
48
|
+
light_magenta_bg: 105,
|
|
49
|
+
light_cyan_bg: 106,
|
|
50
|
+
light_grey_bg: 107
|
|
51
|
+
}.freeze
|
|
52
|
+
|
|
53
|
+
COLORS.each do |name, code|
|
|
54
|
+
define_method name do
|
|
55
|
+
color(code)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Apply specified color code to the String
|
|
60
|
+
# @param [Array, Symbol, Integer] color_code Can be a key in the COLORS array,
|
|
61
|
+
# an integer ANSI code for text color, or an array of either to be applied in order
|
|
62
|
+
# @return [String] String enclosed by formatting characters
|
|
63
|
+
def color(color_code)
|
|
64
|
+
case color_code
|
|
65
|
+
when Array
|
|
66
|
+
color_code.inject(self) { |string, code| string.color(code) }
|
|
67
|
+
when Symbol
|
|
68
|
+
color(COLORS[color_code])
|
|
69
|
+
else
|
|
70
|
+
"\e[#{color_code}m#{self}\e[0m"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Changes all occurrences of `char` to `fore_color`
|
|
75
|
+
# and all other characters to `back_color`
|
|
76
|
+
def highlight(char, fore_color, back_color)
|
|
77
|
+
inside_highlight = false
|
|
78
|
+
output = ''
|
|
79
|
+
buffer = ''
|
|
80
|
+
each_char do |c|
|
|
81
|
+
if c == char
|
|
82
|
+
unless inside_highlight
|
|
83
|
+
output += buffer.color(COLORS[back_color.to_sym])
|
|
84
|
+
buffer = ''
|
|
85
|
+
inside_highlight = true
|
|
86
|
+
end
|
|
87
|
+
elsif inside_highlight
|
|
88
|
+
output += buffer.color(COLORS[fore_color.to_sym]).bold
|
|
89
|
+
buffer = ''
|
|
90
|
+
inside_highlight = false
|
|
91
|
+
end
|
|
92
|
+
buffer += c
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
output += if inside_highlight
|
|
96
|
+
buffer.color(COLORS[fore_color.to_sym]).bold
|
|
97
|
+
else
|
|
98
|
+
buffer.color(COLORS[back_color.to_sym])
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
output
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Remove color codes from the string
|
|
105
|
+
def decolor
|
|
106
|
+
gsub(/\e\[\d+m/, '')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Align the string, ignoring color code characters which would otherwise mess up String#center, etc.
|
|
110
|
+
def align(alignment = :left, width: nil, char: ' ', pad_right: false)
|
|
111
|
+
width ||= ::TTY::Screen.cols
|
|
112
|
+
|
|
113
|
+
case alignment
|
|
114
|
+
when :left
|
|
115
|
+
right_pad = width - decolor.length
|
|
116
|
+
"#{self}#{char * right_pad}"
|
|
117
|
+
when :center
|
|
118
|
+
left_pad = [(width - decolor.length) / 2, 0].max
|
|
119
|
+
right_pad = width - left_pad - decolor.length
|
|
120
|
+
"#{char * left_pad}#{self}#{pad_right ? char * right_pad : ''}"
|
|
121
|
+
when :right
|
|
122
|
+
left_pad = width - decolor.length
|
|
123
|
+
"#{char * left_pad}#{self}"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Align each line of a string
|
|
128
|
+
def align_block(alignment = :left)
|
|
129
|
+
split("\n")
|
|
130
|
+
.map { |line| line.align(alignment) }
|
|
131
|
+
.join("\n")
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'io/console'
|
|
4
|
+
|
|
5
|
+
module MakeMenu
|
|
6
|
+
module Console
|
|
7
|
+
module Prompter
|
|
8
|
+
PressedEscape = Class.new(StandardError)
|
|
9
|
+
|
|
10
|
+
def self.prompt(text = '', obscure: false)
|
|
11
|
+
print text
|
|
12
|
+
|
|
13
|
+
input = ''
|
|
14
|
+
char = ''
|
|
15
|
+
|
|
16
|
+
until !char.empty? && char.ord == 13
|
|
17
|
+
char = $stdin.getch
|
|
18
|
+
|
|
19
|
+
case char.ord
|
|
20
|
+
when 127
|
|
21
|
+
# BACKSPACE
|
|
22
|
+
input = input[0..-2]
|
|
23
|
+
print "\r#{text}#{' ' * input.size} "
|
|
24
|
+
print "\r#{text}#{obscure ? '*' * input.size : input}"
|
|
25
|
+
|
|
26
|
+
when 27
|
|
27
|
+
# ESC
|
|
28
|
+
raise PressedEscape
|
|
29
|
+
|
|
30
|
+
when 13
|
|
31
|
+
# ENTER
|
|
32
|
+
|
|
33
|
+
else
|
|
34
|
+
input += char
|
|
35
|
+
if obscure
|
|
36
|
+
print '*'
|
|
37
|
+
else
|
|
38
|
+
print char
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
input
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module MakeMenu
|
|
2
|
+
class FieldSet
|
|
3
|
+
def initialize
|
|
4
|
+
@fields = []
|
|
5
|
+
@max_widths = {}
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def display
|
|
9
|
+
build
|
|
10
|
+
@fields.each do |field|
|
|
11
|
+
label_cell = field[:label]
|
|
12
|
+
.align(
|
|
13
|
+
:right,
|
|
14
|
+
width: @max_widths[:label]
|
|
15
|
+
).color(field[:label_color])
|
|
16
|
+
|
|
17
|
+
value_cel = field[:value]
|
|
18
|
+
.align(
|
|
19
|
+
:left,
|
|
20
|
+
width: @max_widths[:value],
|
|
21
|
+
pad_right: true
|
|
22
|
+
).color(field[:value_color])
|
|
23
|
+
|
|
24
|
+
puts "#{label_cell}#{value_cel}".align(:center)
|
|
25
|
+
end
|
|
26
|
+
puts
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build
|
|
30
|
+
@max_widths[:label] = 0
|
|
31
|
+
@max_widths[:value] = 0
|
|
32
|
+
|
|
33
|
+
@fields.each do |field|
|
|
34
|
+
label = field[:label]
|
|
35
|
+
value = field[:value] = field[:handler].call
|
|
36
|
+
label_width = label.decolor.size
|
|
37
|
+
value_width = value.decolor.size
|
|
38
|
+
@max_widths[:label] = label_width if label_width > @max_widths[:label]
|
|
39
|
+
@max_widths[:value] = value_width if value_width > @max_widths[:value]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def add(label = nil, label_color: :normal, value_color: :normal, &block)
|
|
44
|
+
@fields << {
|
|
45
|
+
label: label || '',
|
|
46
|
+
label_color: label_color,
|
|
47
|
+
value_color: value_color,
|
|
48
|
+
handler: block
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/make_menu/menu.rb
CHANGED
|
@@ -1,39 +1,61 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'builder'
|
|
4
|
+
require_relative 'badge_set'
|
|
5
|
+
require_relative 'field_set'
|
|
3
6
|
require_relative 'menu_item_group'
|
|
4
|
-
require_relative '
|
|
5
|
-
require_relative '
|
|
7
|
+
require_relative 'text/table'
|
|
8
|
+
require_relative 'console/prompter'
|
|
6
9
|
|
|
7
10
|
module MakeMenu
|
|
8
11
|
# This class builds and displays a number-selection menu from a Makefile
|
|
9
12
|
# then prompts for a number and executes the target.
|
|
10
13
|
class Menu
|
|
11
|
-
# @param [String] makefile Makefile name
|
|
12
14
|
def initialize(makefile)
|
|
15
|
+
@makefile = makefile
|
|
16
|
+
|
|
13
17
|
@groups = []
|
|
14
18
|
@items = []
|
|
15
|
-
|
|
19
|
+
|
|
20
|
+
@options = {
|
|
21
|
+
group_title_color: :underline,
|
|
22
|
+
clear_screen: true,
|
|
23
|
+
pause_on_success: false
|
|
24
|
+
}
|
|
25
|
+
@highlights = {}
|
|
26
|
+
|
|
27
|
+
@header = nil
|
|
28
|
+
@field_set = nil
|
|
29
|
+
@badge_set = nil
|
|
16
30
|
end
|
|
17
31
|
|
|
18
|
-
attr_reader :groups, :items
|
|
32
|
+
attr_reader :makefile, :groups, :items, :options, :highlights
|
|
19
33
|
|
|
20
|
-
# Display menu and prompt for command
|
|
21
|
-
# rubocop:disable Metrics/MethodLength
|
|
22
34
|
def run
|
|
23
|
-
|
|
35
|
+
yield self if block_given?
|
|
36
|
+
|
|
37
|
+
Builder.build(makefile, self)
|
|
24
38
|
|
|
25
|
-
|
|
39
|
+
loop do
|
|
26
40
|
system 'clear' if clear_screen?
|
|
27
41
|
|
|
28
42
|
display_header
|
|
43
|
+
display_badges
|
|
44
|
+
display_fields
|
|
29
45
|
|
|
30
|
-
puts colorize(
|
|
46
|
+
puts colorize(MakeMenu::Text::Table.new(groups).to_s)
|
|
31
47
|
puts
|
|
32
|
-
puts 'Press
|
|
48
|
+
puts 'Press ESC to quit'.align(:center).bold
|
|
33
49
|
puts
|
|
34
|
-
print 'Select option: '.align(:center)
|
|
35
50
|
|
|
36
|
-
|
|
51
|
+
prompt = 'Select option: '.align(:center)
|
|
52
|
+
|
|
53
|
+
begin
|
|
54
|
+
execute_option(MakeMenu::Console::Prompter.prompt(prompt).strip)
|
|
55
|
+
puts
|
|
56
|
+
rescue MakeMenu::Console::Prompter::PressedEscape
|
|
57
|
+
break
|
|
58
|
+
end
|
|
37
59
|
end
|
|
38
60
|
|
|
39
61
|
puts
|
|
@@ -41,133 +63,102 @@ module MakeMenu
|
|
|
41
63
|
system 'clear' if clear_screen?
|
|
42
64
|
end
|
|
43
65
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# Display the company logo and the status bar (if set)
|
|
47
|
-
def display_header
|
|
48
|
-
puts formatted_logo if logo
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
private
|
|
52
|
-
|
|
53
|
-
# Build a menu from the specified Makefile
|
|
54
|
-
# @param [String] makefile Filename
|
|
55
|
-
# rubocop:disable Metrics/MethodLength
|
|
56
|
-
def build(makefile)
|
|
57
|
-
File.open(makefile, 'r') do |file|
|
|
58
|
-
option_number = 1
|
|
59
|
-
current_group = nil
|
|
60
|
-
|
|
61
|
-
file.each_line do |line|
|
|
62
|
-
if line.start_with? '###'
|
|
63
|
-
# Group header
|
|
64
|
-
group_title = line.gsub(/###\s+/, '').strip
|
|
65
|
-
current_group = MenuItemGroup.new(group_title.color(group_title_color))
|
|
66
|
-
groups << current_group
|
|
67
|
-
|
|
68
|
-
elsif line.match(/^[a-zA-Z_-]+:.*?## .*$$/)
|
|
69
|
-
# Menu item
|
|
70
|
-
target = line.split(':').first.strip
|
|
71
|
-
description = line.split('##').last.strip
|
|
72
|
-
|
|
73
|
-
# Target 'menu' should not appear
|
|
74
|
-
next if target == 'menu'
|
|
66
|
+
def execute_option(selected)
|
|
67
|
+
selected = selected.to_i
|
|
75
68
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
groups << current_group
|
|
79
|
-
end
|
|
69
|
+
items.each do |item|
|
|
70
|
+
next unless item.option_number == selected
|
|
80
71
|
|
|
81
|
-
|
|
82
|
-
MenuItem.new(option_number, target, description)
|
|
83
|
-
)
|
|
72
|
+
system 'clear' if clear_screen?
|
|
84
73
|
|
|
85
|
-
|
|
86
|
-
end
|
|
87
|
-
end
|
|
74
|
+
item.execute
|
|
88
75
|
|
|
89
|
-
if
|
|
90
|
-
puts
|
|
91
|
-
|
|
92
|
-
puts
|
|
93
|
-
puts 'Expecting something like this....'
|
|
94
|
-
puts " #{'my_target:'.cyan} #{'## Do some things'.yellow}"
|
|
95
|
-
puts
|
|
96
|
-
exit 1
|
|
76
|
+
if pause_on_success?
|
|
77
|
+
puts "\nPress ENTER to continue....".dark
|
|
78
|
+
gets
|
|
97
79
|
end
|
|
98
80
|
end
|
|
99
|
-
|
|
100
|
-
rescue Errno::ENOENT => _e
|
|
101
|
-
puts
|
|
102
|
-
puts 'No Makefile!'.red.bold
|
|
103
|
-
puts
|
|
104
|
-
puts "File '#{makefile}' could not be found.".yellow
|
|
105
|
-
puts
|
|
106
|
-
exit 1
|
|
107
81
|
end
|
|
108
82
|
|
|
109
|
-
|
|
83
|
+
def header(&block)
|
|
84
|
+
@header = block
|
|
85
|
+
end
|
|
110
86
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
87
|
+
def display_header
|
|
88
|
+
if @header
|
|
89
|
+
@header.call
|
|
90
|
+
else
|
|
91
|
+
logo = " #{Dir.pwd.split('/').last.upcase} ".invert.bold.to_s
|
|
92
|
+
puts "\n#{logo.align(:center)}\n \n"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
116
95
|
|
|
117
|
-
|
|
96
|
+
def add_field(field, &block)
|
|
97
|
+
fields.add field, &block
|
|
98
|
+
end
|
|
118
99
|
|
|
119
|
-
|
|
120
|
-
|
|
100
|
+
def fields
|
|
101
|
+
@field_set ||= FieldSet.new
|
|
102
|
+
end
|
|
121
103
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
end
|
|
104
|
+
def display_fields
|
|
105
|
+
@field_set.display if @field_set
|
|
106
|
+
end
|
|
126
107
|
|
|
127
|
-
|
|
108
|
+
def add_badge(label = '', on: ' ON '.green_bg.bold, off: ' OFF '.red_bg.dark, &block)
|
|
109
|
+
badges.add label, on: on, off: off, &block
|
|
128
110
|
end
|
|
129
111
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def colorize(string)
|
|
134
|
-
return string unless Object.const_defined?("#{self.class.name}::HIGHLIGHTS")
|
|
112
|
+
def badges
|
|
113
|
+
@badge_set ||= BadgeSet.new
|
|
114
|
+
end
|
|
135
115
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
when Array
|
|
139
|
-
color.each { |c| string.gsub!(word, word.send(c)) }
|
|
140
|
-
else
|
|
141
|
-
string.gsub!(word, word.send(color))
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
string
|
|
116
|
+
def display_badges
|
|
117
|
+
@badge_set.display if @badge_set
|
|
145
118
|
end
|
|
146
119
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
.map { |line| line.align(:center) }
|
|
151
|
-
.join("\n")
|
|
120
|
+
def options
|
|
121
|
+
@options.merge!(yield) if block_given?
|
|
122
|
+
@options
|
|
152
123
|
end
|
|
153
124
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
125
|
+
def highlights
|
|
126
|
+
@highlights.merge!(yield) if block_given?
|
|
127
|
+
@highlights
|
|
128
|
+
end
|
|
157
129
|
|
|
158
|
-
|
|
130
|
+
def add_group(group)
|
|
131
|
+
groups << group
|
|
132
|
+
group
|
|
159
133
|
end
|
|
160
134
|
|
|
161
|
-
|
|
135
|
+
def add_item(item)
|
|
136
|
+
items << item
|
|
137
|
+
item
|
|
138
|
+
end
|
|
162
139
|
|
|
163
|
-
# @return [Symbol,Array[Symbol]] Color for group title
|
|
164
140
|
def group_title_color
|
|
165
|
-
|
|
141
|
+
options[:group_title_color]
|
|
166
142
|
end
|
|
167
143
|
|
|
168
|
-
# Clear screen before and after each command
|
|
169
144
|
def clear_screen?
|
|
170
|
-
|
|
145
|
+
options[:clear_screen]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def pause_on_success?
|
|
149
|
+
options[:pause_on_success]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def colorize(text)
|
|
153
|
+
highlights.each do |word, color|
|
|
154
|
+
case color
|
|
155
|
+
when Array
|
|
156
|
+
color.each { |c| text.gsub!(word, word.send(c)) }
|
|
157
|
+
else
|
|
158
|
+
text.gsub!(word, word.send(color))
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
text
|
|
171
162
|
end
|
|
172
163
|
end
|
|
173
164
|
end
|
data/lib/make_menu/menu_item.rb
CHANGED
|
@@ -5,9 +5,6 @@ module MakeMenu
|
|
|
5
5
|
class MenuItem
|
|
6
6
|
INDENT = 6
|
|
7
7
|
|
|
8
|
-
# @param [Integer] option_number Number user enters for this command
|
|
9
|
-
# @param [String] target Name of target defined in Makefile
|
|
10
|
-
# @param [String] description Text to display for this command, taken from Makefile comment
|
|
11
8
|
def initialize(option_number = nil, target = nil, description = nil)
|
|
12
9
|
@option_number = option_number
|
|
13
10
|
@target = target
|
|
@@ -16,17 +13,16 @@ module MakeMenu
|
|
|
16
13
|
|
|
17
14
|
attr_reader :option_number, :target, :description
|
|
18
15
|
|
|
19
|
-
# Run the make target
|
|
20
16
|
def execute
|
|
21
17
|
cmd = ['make', target]
|
|
22
18
|
puts "> #{cmd.join(' ').cyan}\n"
|
|
23
19
|
unless system(*cmd)
|
|
24
20
|
# Indicates the command failed, so we pause to allow user to see error message
|
|
25
|
-
puts "\nPress ENTER
|
|
21
|
+
puts "\nPress ENTER to continue....".dark
|
|
26
22
|
gets
|
|
27
23
|
end
|
|
28
24
|
rescue StandardError => _e
|
|
29
|
-
#
|
|
25
|
+
# Sink keyboard interrupt from within Make target
|
|
30
26
|
end
|
|
31
27
|
|
|
32
28
|
# @return [Integer] Number of characters required to display the item
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MakeMenu
|
|
4
|
+
module Text
|
|
5
|
+
# A column of text with a fixed with
|
|
6
|
+
class Column
|
|
7
|
+
def initialize(width)
|
|
8
|
+
@width = width
|
|
9
|
+
@rows = []
|
|
10
|
+
@row_index = 0
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :width
|
|
14
|
+
|
|
15
|
+
attr_accessor :rows, :row_index
|
|
16
|
+
|
|
17
|
+
# Add a block of text to the column. Each row will be padded to the column width
|
|
18
|
+
def add(text)
|
|
19
|
+
self.rows += text.split("\n").map do |row|
|
|
20
|
+
row.gsub("\r", '')
|
|
21
|
+
end
|
|
22
|
+
self.row_index += text.lines.size
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @return [String] The row at the specified index
|
|
26
|
+
def row(index)
|
|
27
|
+
(rows[index] || '').align(width: width)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [Integer] The number of rows in the column
|
|
31
|
+
def height
|
|
32
|
+
row_index
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Boolean] True if the column is empty
|
|
36
|
+
def empty?
|
|
37
|
+
row_index.zero?
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'column'
|
|
4
|
+
|
|
5
|
+
module MakeMenu
|
|
6
|
+
module Text
|
|
7
|
+
# This class displays the menu groups in columns across the screen.
|
|
8
|
+
# Each group is kept together in a column, and once a column has exceeded the
|
|
9
|
+
# calculated height, a new column is added.
|
|
10
|
+
class Table
|
|
11
|
+
MAX_COLUMNS = 4
|
|
12
|
+
|
|
13
|
+
# @param [Array<MenuItemGroup>] groups
|
|
14
|
+
def initialize(groups)
|
|
15
|
+
@groups = groups
|
|
16
|
+
@columns = []
|
|
17
|
+
calculate_table_dimensions
|
|
18
|
+
build_table
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [String] The entire table, centered on the screen
|
|
22
|
+
def to_s
|
|
23
|
+
buffer = ''
|
|
24
|
+
|
|
25
|
+
max_height.times do |i|
|
|
26
|
+
row = ''
|
|
27
|
+
columns.each do |column|
|
|
28
|
+
row += column.row(i) unless column.empty?
|
|
29
|
+
end
|
|
30
|
+
buffer += "#{row.align(:center)}\n"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
buffer
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
attr_reader :groups, :columns, :column_width, :column_height
|
|
39
|
+
|
|
40
|
+
attr_accessor :current_column
|
|
41
|
+
|
|
42
|
+
# Calculate width and minimum height of columns
|
|
43
|
+
def calculate_table_dimensions
|
|
44
|
+
@column_width = groups.map(&:width).max + 5
|
|
45
|
+
total_rows = groups.map(&:height).sum
|
|
46
|
+
column_count = (::TTY::Screen.cols / column_width).clamp(1, MAX_COLUMNS)
|
|
47
|
+
@column_height = total_rows / column_count
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Build columns from groups
|
|
51
|
+
def build_table
|
|
52
|
+
column_break
|
|
53
|
+
groups.each do |group|
|
|
54
|
+
add_text_block group.to_s
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Add a block of text to the current column. If the column is now larger than
|
|
59
|
+
# the minimum height, a new column is added
|
|
60
|
+
def add_text_block(text)
|
|
61
|
+
current_column.add(text)
|
|
62
|
+
column_break if current_column.height >= column_height
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Add a new column to the table
|
|
66
|
+
def column_break
|
|
67
|
+
self.current_column = Column.new(column_width)
|
|
68
|
+
columns << current_column
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @return [Integer] Maximum column height (rows)
|
|
72
|
+
def max_height
|
|
73
|
+
columns.map(&:height).max
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/make_menu/version.rb
CHANGED
data/lib/make_menu.rb
CHANGED
|
@@ -1,34 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'make_menu/console/color_string'
|
|
2
4
|
require_relative 'make_menu/menu'
|
|
3
5
|
|
|
4
6
|
require 'tty-screen'
|
|
5
7
|
|
|
6
8
|
module MakeMenu
|
|
7
|
-
String.include MakeMenu::ColorString
|
|
8
|
-
|
|
9
|
-
def self.run
|
|
10
|
-
# Allows CTRL+C to return to the menu instead of exiting the script
|
|
11
|
-
trap('SIGINT') { throw StandardError }
|
|
12
|
-
|
|
13
|
-
makefile = ENV.fetch('MAKEFILE', './Makefile')
|
|
9
|
+
String.include MakeMenu::Console::ColorString
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
require "./#{menu_name.downcase}_menu.rb"
|
|
17
|
-
Object.const_get("#{menu_name.capitalize}Menu").new(makefile).run
|
|
18
|
-
else
|
|
19
|
-
MakeMenu::Menu.new(makefile).run
|
|
20
|
-
end
|
|
11
|
+
trap('SIGINT') { throw StandardError }
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
puts 'No customisation class found!'.red.bold
|
|
25
|
-
puts
|
|
26
|
-
puts 'Expected file:'
|
|
27
|
-
puts " ./#{menu_name.downcase}_menu.rb".cyan
|
|
28
|
-
puts
|
|
29
|
-
puts 'To define class:'
|
|
30
|
-
puts " #{menu_name.capitalize}Menu < MakeMenu::Menu".yellow
|
|
31
|
-
puts
|
|
32
|
-
exit 1
|
|
13
|
+
def self.run(makefile = './Makefile', &block)
|
|
14
|
+
MakeMenu::Menu.new(makefile).run(&block)
|
|
33
15
|
end
|
|
34
|
-
end
|
|
16
|
+
end
|
data/make_menu.gemspec
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require File.expand_path('lib/make_menu/version', __dir__)
|
|
2
4
|
|
|
3
5
|
Gem::Specification.new do |s|
|
|
4
|
-
s.name =
|
|
6
|
+
s.name = 'make_menu'
|
|
5
7
|
s.version = MakeMenu::VERSION
|
|
6
|
-
s.summary =
|
|
7
|
-
s.authors = [
|
|
8
|
-
s.email =
|
|
8
|
+
s.summary = 'Generates an interactive menu from a Makefile'
|
|
9
|
+
s.authors = ['Barri Mason']
|
|
10
|
+
s.email = 'loki@amarantha.net'
|
|
9
11
|
s.files = Dir[
|
|
10
12
|
'lib/**/*.rb',
|
|
11
13
|
'make_menu.gemspec',
|
|
@@ -13,8 +15,8 @@ Gem::Specification.new do |s|
|
|
|
13
15
|
'Gemfile.lock'
|
|
14
16
|
]
|
|
15
17
|
s.homepage =
|
|
16
|
-
|
|
17
|
-
s.license =
|
|
18
|
+
'https://github.com/MisterGrimalkin/make_menu'
|
|
19
|
+
s.license = 'MIT'
|
|
18
20
|
s.add_dependency 'tty-screen', '~> 0.8.2'
|
|
19
|
-
s.description =
|
|
20
|
-
end
|
|
21
|
+
s.description = ''
|
|
22
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: make_menu
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Barri Mason
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2024-01-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: tty-screen
|
|
@@ -33,12 +33,16 @@ files:
|
|
|
33
33
|
- Gemfile
|
|
34
34
|
- Gemfile.lock
|
|
35
35
|
- lib/make_menu.rb
|
|
36
|
-
- lib/make_menu/
|
|
36
|
+
- lib/make_menu/badge_set.rb
|
|
37
|
+
- lib/make_menu/builder.rb
|
|
38
|
+
- lib/make_menu/console/color_string.rb
|
|
39
|
+
- lib/make_menu/console/prompter.rb
|
|
40
|
+
- lib/make_menu/field_set.rb
|
|
37
41
|
- lib/make_menu/menu.rb
|
|
38
42
|
- lib/make_menu/menu_item.rb
|
|
39
43
|
- lib/make_menu/menu_item_group.rb
|
|
40
|
-
- lib/make_menu/
|
|
41
|
-
- lib/make_menu/
|
|
44
|
+
- lib/make_menu/text/column.rb
|
|
45
|
+
- lib/make_menu/text/table.rb
|
|
42
46
|
- lib/make_menu/version.rb
|
|
43
47
|
- make_menu.gemspec
|
|
44
48
|
homepage: https://github.com/MisterGrimalkin/make_menu
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module MakeMenu
|
|
4
|
-
# Monkeypatch for `String`, adds methods to change console text colo(u)r
|
|
5
|
-
module ColorString
|
|
6
|
-
COLORS = {
|
|
7
|
-
white: 0,
|
|
8
|
-
normal: 0,
|
|
9
|
-
bold: 1,
|
|
10
|
-
dark: 2,
|
|
11
|
-
underline: 4,
|
|
12
|
-
blink: 5,
|
|
13
|
-
invert: 7,
|
|
14
|
-
|
|
15
|
-
black: 30,
|
|
16
|
-
red: 31,
|
|
17
|
-
green: 32,
|
|
18
|
-
yellow: 33,
|
|
19
|
-
blue: 34,
|
|
20
|
-
magenta: 35,
|
|
21
|
-
cyan: 36,
|
|
22
|
-
grey: 37,
|
|
23
|
-
|
|
24
|
-
black_bg: 40,
|
|
25
|
-
red_bg: 41,
|
|
26
|
-
green_bg: 42,
|
|
27
|
-
yellow_bg: 43,
|
|
28
|
-
blue_bg: 44,
|
|
29
|
-
magenta_bg: 45,
|
|
30
|
-
cyan_bg: 46,
|
|
31
|
-
grey_bg: 47,
|
|
32
|
-
|
|
33
|
-
dark_grey: 90,
|
|
34
|
-
light_red: 91,
|
|
35
|
-
light_green: 92,
|
|
36
|
-
light_yellow: 93,
|
|
37
|
-
light_blue: 94,
|
|
38
|
-
light_magenta: 95,
|
|
39
|
-
light_cyan: 96,
|
|
40
|
-
light_grey: 97,
|
|
41
|
-
|
|
42
|
-
dark_grey_bg: 100,
|
|
43
|
-
light_red_bg: 101,
|
|
44
|
-
light_green_bg: 102,
|
|
45
|
-
light_yellow_bg: 103,
|
|
46
|
-
light_blue_bg: 104,
|
|
47
|
-
light_magenta_bg: 105,
|
|
48
|
-
light_cyan_bg: 106,
|
|
49
|
-
light_grey_bg: 107
|
|
50
|
-
}.freeze
|
|
51
|
-
|
|
52
|
-
COLORS.each do |name, code|
|
|
53
|
-
define_method name do
|
|
54
|
-
color(code)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Apply specified color code to the String
|
|
59
|
-
# @param [Array, Symbol, Integer] color_code Can be a key in the COLORS array,
|
|
60
|
-
# an integer ANSI code for text color, or an array of either to be applied in order
|
|
61
|
-
# @return [String] String enclosed by formatting characters
|
|
62
|
-
def color(color_code)
|
|
63
|
-
case color_code
|
|
64
|
-
when Array
|
|
65
|
-
color_code.inject(self) { |string, code| string.color(code) }
|
|
66
|
-
when Symbol
|
|
67
|
-
color(COLORS[color_code])
|
|
68
|
-
else
|
|
69
|
-
"\e[#{color_code}m#{self}\e[0m"
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Changes all occurrences of a specified character to one color,
|
|
74
|
-
# and all other characters to another
|
|
75
|
-
# @param [String] char Character to highlight
|
|
76
|
-
# @param [Symbol] fore_color Key of color to use for highlighted character
|
|
77
|
-
# @param [Symbol] back_color Key of color to use for other characters
|
|
78
|
-
# @return [String] Highlighted text
|
|
79
|
-
# @example "==$$==".highlight('$', :light_yellow, :red)
|
|
80
|
-
# rubocop:disable Metrics/MethodLength
|
|
81
|
-
def highlight(char, fore_color, back_color)
|
|
82
|
-
inside_highlight = false
|
|
83
|
-
output = ''
|
|
84
|
-
buffer = ''
|
|
85
|
-
each_char do |c|
|
|
86
|
-
if c == char
|
|
87
|
-
unless inside_highlight
|
|
88
|
-
output += buffer.color(COLORS[back_color.to_sym])
|
|
89
|
-
buffer = ''
|
|
90
|
-
inside_highlight = true
|
|
91
|
-
end
|
|
92
|
-
elsif inside_highlight
|
|
93
|
-
output += buffer.color(COLORS[fore_color.to_sym]).bold
|
|
94
|
-
buffer = ''
|
|
95
|
-
inside_highlight = false
|
|
96
|
-
end
|
|
97
|
-
buffer += c
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
output += if inside_highlight
|
|
101
|
-
buffer.color(COLORS[fore_color.to_sym]).bold
|
|
102
|
-
else
|
|
103
|
-
buffer.color(COLORS[back_color.to_sym])
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
output
|
|
107
|
-
end
|
|
108
|
-
# rubocop:enable Metrics/MethodLength
|
|
109
|
-
|
|
110
|
-
# Remove color codes from the string
|
|
111
|
-
# @return [String] The modified string
|
|
112
|
-
def decolor
|
|
113
|
-
gsub(/\e\[\d+m/, '')
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# Align the string, ignoring color code characters which would otherwise mess up String#center, etc.
|
|
117
|
-
# @param [Symbol] alignment :left, :center, or :right
|
|
118
|
-
# @param [Integer] width The number of characters to spread the string over (default to terminal width)
|
|
119
|
-
# @param [String] char The character to use for padding
|
|
120
|
-
# @param [Boolean] pad_right Set true to include trailing spaces when aligning to :center
|
|
121
|
-
# @return [String] The padded string
|
|
122
|
-
# rubocop:disable Metrics/MethodLength
|
|
123
|
-
def align(alignment = :left, width: nil, char: ' ', pad_right: false)
|
|
124
|
-
width = ::TTY::Screen.cols unless width
|
|
125
|
-
|
|
126
|
-
case alignment
|
|
127
|
-
when :left
|
|
128
|
-
right_pad = width - decolor.length
|
|
129
|
-
"#{self}#{char * right_pad}"
|
|
130
|
-
when :center
|
|
131
|
-
left_pad = [(width - decolor.length) / 2, 0].max
|
|
132
|
-
right_pad = width - left_pad - decolor.length
|
|
133
|
-
"#{char * left_pad}#{self}#{pad_right ? char * right_pad : ''}"
|
|
134
|
-
when :right
|
|
135
|
-
left_pad = width - decolor.length
|
|
136
|
-
"#{char * left_pad}#{self}"
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
# rubocop:enable Metrics/MethodLength
|
|
140
|
-
end
|
|
141
|
-
end
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module MakeMenu
|
|
4
|
-
# A column of text with a fixed with
|
|
5
|
-
class TextColumn
|
|
6
|
-
# @param [Integer] width Width of the column, in characters
|
|
7
|
-
def initialize(width)
|
|
8
|
-
@width = width
|
|
9
|
-
@rows = []
|
|
10
|
-
@row_index = 0
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
attr_reader :width
|
|
14
|
-
|
|
15
|
-
attr_accessor :rows, :row_index
|
|
16
|
-
|
|
17
|
-
# Add a block of text to the column. Each row will be padded to the column width
|
|
18
|
-
# @param [String] text The text to add, may be multi-line
|
|
19
|
-
def add(text)
|
|
20
|
-
self.rows += text.split("\n").map do |row|
|
|
21
|
-
row.gsub("\r", '')
|
|
22
|
-
end
|
|
23
|
-
self.row_index += text.lines.size
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Return the specified row of text
|
|
27
|
-
# @param [Integer] index The index of the row
|
|
28
|
-
# @return [String] The row at the specified index
|
|
29
|
-
def row(index)
|
|
30
|
-
(rows[index] || '').align(width: width)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# @return [Integer] The number of rows in the column
|
|
34
|
-
def height
|
|
35
|
-
row_index
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# @return [Boolean] True if the column is empty
|
|
39
|
-
def empty?
|
|
40
|
-
row_index.zero?
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
data/lib/make_menu/text_table.rb
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'text_column'
|
|
4
|
-
|
|
5
|
-
module MakeMenu
|
|
6
|
-
# This class displays the menu groups in columns across the screen.
|
|
7
|
-
# Each group is kept together in a column, and once a column has exceeded the
|
|
8
|
-
# calculated height a new column is added.
|
|
9
|
-
class TextTable
|
|
10
|
-
MAX_COLUMNS = 4
|
|
11
|
-
|
|
12
|
-
# @param [Array<MenuItemGroup>] groups
|
|
13
|
-
def initialize(groups)
|
|
14
|
-
@groups = groups
|
|
15
|
-
@columns = []
|
|
16
|
-
calculate_table_dimensions
|
|
17
|
-
build_table
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# @return [String] The entire table, centered on the screen
|
|
21
|
-
def to_s
|
|
22
|
-
buffer = ''
|
|
23
|
-
|
|
24
|
-
max_height.times do |i|
|
|
25
|
-
row = ''
|
|
26
|
-
columns.each do |column|
|
|
27
|
-
row += column.row(i) unless column.empty?
|
|
28
|
-
end
|
|
29
|
-
buffer += "#{row.align(:center)}\n"
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
buffer
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
attr_reader :groups, :columns, :column_width, :column_height
|
|
38
|
-
|
|
39
|
-
attr_accessor :current_column
|
|
40
|
-
|
|
41
|
-
# Calculate width and minimum height of columns
|
|
42
|
-
def calculate_table_dimensions
|
|
43
|
-
@column_width = groups.map(&:width).max + 5
|
|
44
|
-
total_rows = groups.map(&:height).sum
|
|
45
|
-
column_count = (::TTY::Screen.cols / column_width).clamp(1, MAX_COLUMNS)
|
|
46
|
-
@column_height = total_rows / column_count
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Build columns from groups
|
|
50
|
-
def build_table
|
|
51
|
-
column_break
|
|
52
|
-
groups.each do |group|
|
|
53
|
-
add_text_block group.to_s
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Add a block of text to the current column. If the column is now larger than
|
|
58
|
-
# the minimum height, a new column is added
|
|
59
|
-
def add_text_block(text)
|
|
60
|
-
current_column.add(text)
|
|
61
|
-
column_break if current_column.height >= column_height
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Add a new column to the table
|
|
65
|
-
def column_break
|
|
66
|
-
self.current_column = TextColumn.new(column_width)
|
|
67
|
-
columns << current_column
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# @return [Integer] Maximum column height (rows)
|
|
71
|
-
def max_height
|
|
72
|
-
columns.map(&:height).max
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|