tng 0.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.
@@ -0,0 +1,54 @@
1
+ require "tty-box"
2
+ require "pastel"
3
+ require "tty-screen"
4
+
5
+ class DisplayBanner
6
+ attr_reader :pastel, :version, :terminal_width
7
+
8
+ def initialize(pastel, version)
9
+ @pastel = pastel
10
+ @version = version
11
+ @terminal_width = begin
12
+ TTY::Screen.width
13
+ rescue StandardError
14
+ 80
15
+ end
16
+ end
17
+
18
+ def render
19
+ banner_content = [
20
+ pastel.cyan.bold("Tng - LLM-Powered Rails Test Generator"),
21
+ pastel.dim("Version: #{version}"),
22
+ "",
23
+ pastel.yellow("Generate comprehensive tests for your Rails application"),
24
+ pastel.dim("Powered by LLM and static analysis")
25
+ ].join("\n")
26
+
27
+ box_width = 64
28
+ box_width = terminal_width if box_width > terminal_width
29
+
30
+ box = TTY::Box.frame(
31
+ title: { top_left: " Tng ", bottom_right: " Ready " },
32
+ style: {
33
+ fg: :bright_white,
34
+ border: { fg: :cyan },
35
+ title: { fg: :bright_cyan }
36
+ },
37
+ padding: [1, 2],
38
+ align: :center,
39
+ width: box_width
40
+ ) do
41
+ banner_content
42
+ end
43
+
44
+ "#{center_box(box, box_width)}\n"
45
+ end
46
+
47
+ private
48
+
49
+ def center_box(box_string, box_width)
50
+ padding = (terminal_width - box_width) / 2
51
+ padding = 0 if padding.negative?
52
+ box_string.lines.map { |line| (" " * padding) + line.chomp }.join("\n")
53
+ end
54
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-box"
4
+ require "pastel"
5
+ require "tty-screen"
6
+
7
+ class GoodbyeDisplay
8
+ def initialize(pastel)
9
+ @pastel = pastel
10
+ @terminal_width = begin
11
+ TTY::Screen.width
12
+ rescue StandardError
13
+ 80
14
+ end
15
+ end
16
+
17
+ def display
18
+ box_width = 54
19
+ goodbye_box = TTY::Box.frame(
20
+ title: { top_left: " Thank You " },
21
+ style: {
22
+ fg: :bright_white,
23
+ border: { fg: :green },
24
+ title: { fg: :bright_green }
25
+ },
26
+ padding: [1, 2],
27
+ align: :center,
28
+ width: box_width
29
+ ) do
30
+ [
31
+ @pastel.green.bold("Thanks for using Tng!"),
32
+ "",
33
+ @pastel.bright_white("Happy testing!"),
34
+ "",
35
+ @pastel.dim("Your Rails tests are now supercharged with LLM")
36
+ ].join("\n")
37
+ end
38
+
39
+ puts center_box(goodbye_box, box_width)
40
+ puts
41
+ end
42
+
43
+ private
44
+
45
+ def center_box(box_string, box_width)
46
+ padding = (@terminal_width - box_width) / 2
47
+ padding = 0 if padding.negative?
48
+ box_string.lines.map { |line| (" " * padding) + line.chomp }.join("\n")
49
+ end
50
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-prompt"
4
+ require "tty-spinner"
5
+ require "pastel"
6
+ require "tty-screen"
7
+
8
+ class ModelTestFlowDisplay
9
+ def initialize(prompt, pastel)
10
+ @prompt = prompt
11
+ @pastel = pastel
12
+ @terminal_width = begin
13
+ TTY::Screen.width
14
+ rescue StandardError
15
+ 80
16
+ end
17
+ end
18
+
19
+ def select_model(models)
20
+ header = @pastel.bright_white.bold("📋 Select model to test:")
21
+ puts center_text(header)
22
+
23
+ @prompt.select(
24
+ "",
25
+ cycle: true,
26
+ per_page: 12,
27
+ filter: true,
28
+ symbols: { marker: "▶" }
29
+ ) do |menu|
30
+ models.each do |model|
31
+ display_name = "#{model[:name]} #{@pastel.dim("(#{model[:path]})")}"
32
+ menu.choice display_name, model
33
+ end
34
+ menu.choice @pastel.cyan("⬅️ Back"), :back
35
+ end
36
+ end
37
+
38
+ def select_test_option(model_choice)
39
+ header = @pastel.bright_white.bold("🎯 Test Generation Options for #{model_choice[:name]}")
40
+ puts center_text(header)
41
+ puts
42
+
43
+ @prompt.select(
44
+ @pastel.bright_white("Choose test generation type:"),
45
+ cycle: true,
46
+ symbols: { marker: "▶" }
47
+ ) do |menu|
48
+ menu.choice @pastel.green("Generate all possible tests"), :all_possible_tests
49
+ menu.choice @pastel.blue("Generate per method"), :per_method
50
+ menu.choice @pastel.cyan("⬅️ Back"), :back
51
+ end
52
+ end
53
+
54
+ def show_no_models_message
55
+ puts
56
+ puts center_text(@pastel.yellow("⚠️ No models found in app/models"))
57
+ puts center_text(@pastel.dim("Make sure you're in a Rails project with models"))
58
+ puts
59
+ end
60
+
61
+ def select_model_method(model, methods)
62
+ header = @pastel.bright_white.bold("🎯 Select method to test in #{model[:name]}")
63
+ puts center_text(header)
64
+
65
+ @prompt.select(
66
+ "",
67
+ cycle: true,
68
+ per_page: 10,
69
+ filter: true,
70
+ symbols: { marker: "▶" }
71
+ ) do |menu|
72
+ methods.each do |method|
73
+ menu.choice method[:name], method
74
+ end
75
+ menu.choice @pastel.cyan("⬅️ Back"), :back
76
+ end
77
+ end
78
+
79
+ def show_no_methods_message(model)
80
+ error_msg = "#{@pastel.red.bold("❌ No methods found in #{model[:name]}")}\n#{@pastel.dim("Model may be empty or have syntax errors")}"
81
+ puts center_text(error_msg)
82
+ @prompt.keypress(center_text(@pastel.dim("Press any key to continue...")))
83
+ end
84
+
85
+ private
86
+
87
+ def center_text(text)
88
+ padding = [(@terminal_width - strip_ansi(text).length) / 2, 0].max
89
+ "#{" " * padding}#{text}"
90
+ end
91
+
92
+ def strip_ansi(text)
93
+ text.gsub(/\e\[([;\d]+)?m/, "")
94
+ end
95
+ end
@@ -0,0 +1,73 @@
1
+ require "tty-box"
2
+ require "pastel"
3
+
4
+ class PostInstallBox
5
+ attr_reader :version, :pastel
6
+
7
+ def initialize(version)
8
+ @version = version
9
+ @pastel = Pastel.new
10
+ @terminal_width = begin
11
+ TTY::Screen.width
12
+ rescue StandardError
13
+ 80
14
+ end
15
+ end
16
+
17
+ def content
18
+ [
19
+ pastel.green.bold("✅ Tng installed successfully!"),
20
+ "",
21
+ pastel.yellow.bold("📋 SETUP REQUIRED"),
22
+ "",
23
+ pastel.bright_white("1. Generate configuration:"),
24
+ pastel.cyan(" rails g tng:install"),
25
+ "",
26
+ pastel.bright_white("2. Edit configuration file:"),
27
+ pastel.cyan(" config/initializers/tng.rb"),
28
+ "",
29
+ pastel.bright_white("3. Add your license key:"),
30
+ pastel.cyan(" config.api_key = 'your-license-key-here'"),
31
+ "",
32
+ pastel.bright_white("📖 Check documentation for the correct authentication setup"),
33
+ "",
34
+ pastel.magenta.bold("🚀 Usage:"),
35
+ "",
36
+ pastel.bright_white("Interactive mode:"),
37
+ pastel.green("• bundle exec tng") + pastel.dim(" - Full interactive CLI"),
38
+ "",
39
+ pastel.bright_white("Direct generation:"),
40
+ pastel.green("• bundle exec tng -t c -f ping") + pastel.dim(" - Short"),
41
+ pastel.green("• bundle exec tng -t=c f=ping") + pastel.dim(" - Mixed"),
42
+ pastel.green("• bundle exec tng --type=controller --file=ping"),
43
+ "",
44
+ pastel.bright_white("Help:"),
45
+ pastel.green("• bundle exec tng --help") + pastel.dim(" - Show all options"),
46
+ "",
47
+ pastel.dim("💡 Use -t c for controller, -t m for model")
48
+ ].join("\n")
49
+ end
50
+
51
+ def render
52
+ # Use a reasonable width that won't cause issues
53
+ box_width = [@terminal_width, 70].min
54
+
55
+ TTY::Box.frame(
56
+ title: { top_left: " TestNG ", bottom_right: " v#{@version} " },
57
+ style: {
58
+ fg: :bright_white,
59
+ border: { fg: :cyan },
60
+ title: { fg: :bright_cyan }
61
+ },
62
+ padding: [1, 2],
63
+ align: :left,
64
+ width: box_width
65
+ ) do
66
+ content
67
+ end
68
+ rescue StandardError => e
69
+ "TNG v#{@version} installed successfully!\n" +
70
+ "Run 'rails g tng:install' to get started.\n" +
71
+ "Use 'bundle exec tng --help' for usage information."
72
+ end
73
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-prompt"
4
+ require "tty-spinner"
5
+ require "pastel"
6
+ require "tty-screen"
7
+
8
+ class ServiceTestFlowDisplay
9
+ def initialize(prompt, pastel)
10
+ @prompt = prompt
11
+ @pastel = pastel
12
+ @terminal_width = begin
13
+ TTY::Screen.width
14
+ rescue StandardError
15
+ 80
16
+ end
17
+ end
18
+
19
+ def select_service(services)
20
+ header = @pastel.bright_white.bold("📋 Select service to test:")
21
+ puts center_text(header)
22
+
23
+ @prompt.select(
24
+ "",
25
+ cycle: true,
26
+ per_page: 12,
27
+ filter: true,
28
+ symbols: { marker: "▶" }
29
+ ) do |menu|
30
+ services.each do |service|
31
+ display_name = "#{service[:name]} #{@pastel.dim("(#{service[:path]})")}"
32
+ menu.choice display_name, service
33
+ end
34
+ menu.choice @pastel.cyan("⬅️ Back"), :back
35
+ end
36
+ end
37
+
38
+ def select_test_option(service_choice)
39
+ header = @pastel.bright_white.bold("🎯 Test Generation Options for #{service_choice[:name]}")
40
+ puts center_text(header)
41
+ puts
42
+
43
+ @prompt.select(
44
+ @pastel.bright_white("Choose test generation type:"),
45
+ cycle: true,
46
+ symbols: { marker: "▶" }
47
+ ) do |menu|
48
+ menu.choice @pastel.green("Generate all possible tests"), :all_possible_tests
49
+ menu.choice @pastel.blue("Generate per method"), :per_method
50
+ menu.choice @pastel.cyan("⬅️ Back"), :back
51
+ end
52
+ end
53
+
54
+ def show_no_services_message
55
+ error_msg = "#{@pastel.red.bold("❌ No services found in your application")}\n#{@pastel.dim("Make sure you have services in app/services/ or app/service/")}"
56
+ puts center_text(error_msg)
57
+ @prompt.keypress(center_text(@pastel.dim("Press any key to continue...")))
58
+ end
59
+
60
+ def select_service_method(service, methods)
61
+ header = @pastel.bright_white.bold("🔍 Select method to test for #{service[:name]}")
62
+ puts center_text(header)
63
+
64
+ @prompt.select(
65
+ "",
66
+ cycle: true,
67
+ per_page: 12,
68
+ filter: true,
69
+ symbols: { marker: "▶" }
70
+ ) do |menu|
71
+ methods.each do |method|
72
+ menu.choice "#{method[:name]}", method
73
+ end
74
+ menu.choice @pastel.cyan("⬅️ Back"), :back
75
+ end
76
+ end
77
+
78
+ def show_no_methods_message(service)
79
+ error_msg = "#{@pastel.red.bold("❌ No methods found in #{service[:name]}")}\n#{@pastel.dim("The service might not have any public methods or could not be analyzed.")}"
80
+ puts center_text(error_msg)
81
+ @prompt.keypress(center_text(@pastel.dim("Press any key to continue...")))
82
+ end
83
+
84
+ def center_text(text)
85
+ padding = (@terminal_width - @pastel.strip(text).length) / 2
86
+ padding = 0 if padding.negative?
87
+ (" " * padding) + text
88
+ end
89
+ end
@@ -0,0 +1,89 @@
1
+ require "tty-box"
2
+ require "pastel"
3
+ require "tty-screen"
4
+
5
+ class ShowHelp
6
+ attr_reader :pastel
7
+
8
+ def initialize(pastel, version)
9
+ @pastel = pastel
10
+ @version = version
11
+ @terminal_width = begin
12
+ TTY::Screen.width
13
+ rescue StandardError
14
+ 80
15
+ end
16
+ end
17
+
18
+ def content
19
+ [
20
+ @pastel.cyan.bold("TestNG - LLM-Powered Rails Test Generator"),
21
+ @pastel.dim("Version: #{@version}"),
22
+ "",
23
+ @pastel.yellow.bold("Usage:"),
24
+ @pastel.bright_white(" bundle exec tng") + @pastel.dim(" # Interactive mode"),
25
+ @pastel.bright_white(" bundle exec tng --type=TYPE --file=FILE") + @pastel.dim(" # Direct mode"),
26
+ @pastel.bright_white(" bundle exec tng -t TYPE -f FILE") + @pastel.dim(" # Short aliases"),
27
+ @pastel.bright_white(" bundle exec tng -t=TYPE f=FILE") + @pastel.dim(" # Mixed format"),
28
+ "",
29
+ @pastel.yellow.bold("Examples:"),
30
+ @pastel.green(" bundle exec tng -t c -f ping") + @pastel.dim(" # Controller test"),
31
+ @pastel.green(" bundle exec tng -t=c f=ping") + @pastel.dim(" # Mixed format"),
32
+ @pastel.green(" bundle exec tng -t m -f user") + @pastel.dim(" # Model test"),
33
+ @pastel.green(" bundle exec tng --type=controller --file=users_controller"),
34
+ @pastel.green(" bundle exec tng -t c -f \"admin/users_controller\"") + @pastel.dim(" # Namespaced"),
35
+ "",
36
+ @pastel.yellow.bold("Options:"),
37
+ @pastel.bright_white(" -t, --type=TYPE") + @pastel.dim(" Test type (controller, model)"),
38
+ @pastel.bright_white(" -f, --file=FILE") + @pastel.dim(" File name (without .rb extension)"),
39
+ @pastel.bright_white(" -m, --method=METHOD") + @pastel.dim(" Method name (for per-method tests)"),
40
+ @pastel.bright_white(" -h, --help") + @pastel.dim(" Show this help message"),
41
+ "",
42
+ @pastel.yellow.bold("Type Aliases:"),
43
+ @pastel.dim(" • Controllers: ") + @pastel.cyan("c, controller"),
44
+ @pastel.dim(" • Models: ") + @pastel.cyan("m, mo, model"),
45
+ "",
46
+ @pastel.yellow.bold("File Format:"),
47
+ @pastel.dim(" • Controllers: ") + @pastel.green("users_controller, api/v1/posts_controller"),
48
+ @pastel.dim(" • Models: ") + @pastel.green("user, blog/post"),
49
+ @pastel.dim(" • Don't include .rb extension"),
50
+ "",
51
+ @pastel.magenta.bold("Pro Tips:"),
52
+ @pastel.dim(" • Mix formats: ") + @pastel.cyan("-t=c f=ping") + @pastel.dim(" works perfectly"),
53
+ @pastel.dim(" • Use filtering in interactive mode for large projects"),
54
+ @pastel.dim(" • All formats are equivalent - use what feels natural"),
55
+ "",
56
+ @pastel.cyan.bold("Happy testing! ") + @pastel.yellow("★")
57
+ ].join("\n")
58
+ end
59
+
60
+ def render
61
+ # Use a reasonable width that matches other UI components
62
+ box_width = [@terminal_width - 4, 80].min
63
+ box_width = 60 if box_width < 60
64
+
65
+ box = TTY::Box.frame(
66
+ title: { top_left: " TNG Help ", bottom_right: " v#{@version} " },
67
+ style: {
68
+ fg: :bright_white,
69
+ border: { fg: :cyan },
70
+ title: { fg: :bright_cyan }
71
+ },
72
+ padding: [1, 2],
73
+ align: :left,
74
+ width: box_width
75
+ ) do
76
+ content
77
+ end
78
+
79
+ center_box(box, box_width)
80
+ end
81
+
82
+ private
83
+
84
+ def center_box(box_string, box_width)
85
+ padding = (@terminal_width - box_width) / 2
86
+ padding = 0 if padding.negative?
87
+ box_string.lines.map { |line| (" " * padding) + line.chomp }.join("\n")
88
+ end
89
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pastel"
4
+
5
+ class SystemStatusDisplay
6
+ def initialize(pastel, args)
7
+ @pastel = pastel
8
+ @args = args
9
+ end
10
+
11
+ def display(status)
12
+ case status[:status]
13
+ when :ok
14
+ puts @pastel.green("✅ System operational") unless @args[:type] && @args[:file]
15
+ true
16
+ when :version_mismatch
17
+ puts @pastel.red.bold("❌ Version Mismatch!")
18
+ puts @pastel.yellow("Your gem version: #{status[:gem_version]}")
19
+ puts @pastel.yellow("Required version: #{status[:current_version]}")
20
+ puts
21
+ puts @pastel.bright_white("Please update your gem:")
22
+ puts @pastel.cyan(" gem update tng")
23
+ puts @pastel.dim(" or")
24
+ puts @pastel.cyan(" bundle update tng")
25
+ false
26
+ when :base_url_mismatch
27
+ puts @pastel.red.bold("❌ Endpoint URL Mismatch!")
28
+ puts @pastel.yellow("Your configured URL: #{status[:user_base_url]}")
29
+ puts @pastel.yellow("Server's current URL: #{status[:server_base_url]}")
30
+ puts
31
+ puts @pastel.bright_white("Please update your configuration:")
32
+ puts @pastel.cyan(" Edit config/initializers/tng.rb")
33
+ puts @pastel.cyan(" Set: config.base_url = '#{status[:server_base_url]}'")
34
+ puts
35
+ puts @pastel.dim("This ensures you're connecting to the correct endpoint.")
36
+ false
37
+ when :service_down
38
+ puts @pastel.red.bold("❌ Service Unavailable")
39
+ puts @pastel.dim(status[:message])
40
+ puts
41
+ puts @pastel.yellow("The TNG service is currently down.")
42
+ puts @pastel.dim("Please try again later or contact support.")
43
+ false
44
+ when :error
45
+ puts @pastel.red.bold("❌ Connection Error")
46
+ puts @pastel.dim(status[:message])
47
+ puts
48
+ puts @pastel.yellow("Unable to connect to TNG service.")
49
+ puts @pastel.dim("Please check your internet connection and try again.")
50
+ false
51
+ else
52
+ puts @pastel.red.bold("❌ Unknown Error")
53
+ puts @pastel.dim(status[:message] || "An unknown error occurred")
54
+ false
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-box"
4
+ require "tty-progressbar"
5
+ require "pastel"
6
+ require "tty-screen"
7
+
8
+ class UserStatsDisplay
9
+ def initialize(pastel, prompt)
10
+ @pastel = pastel
11
+ @prompt = prompt
12
+ @terminal_width = begin
13
+ TTY::Screen.width
14
+ rescue StandardError
15
+ 80
16
+ end
17
+ end
18
+
19
+ def display(stats_data)
20
+ header = @pastel.bright_white.bold("📊 User Statistics")
21
+ puts center_text(header)
22
+
23
+ if stats_data
24
+ display_stats_box(stats_data)
25
+ display_usage_progress(stats_data)
26
+ display_usage_info(stats_data)
27
+ else
28
+ display_error
29
+ end
30
+
31
+ puts
32
+ tip_msg = @pastel.dim("💡 Tip: Contact support if you need more test generations")
33
+ puts center_text(tip_msg)
34
+ @prompt.keypress(center_text(@pastel.dim("Press any key to continue...")))
35
+ end
36
+
37
+ def display_stats_box(stats_data)
38
+ stats_content = [
39
+ @pastel.cyan.bold("Account Information"),
40
+ "",
41
+ @pastel.bright_white("Test Runs: ") + @pastel.green.bold(stats_data["runs"].to_s),
42
+ @pastel.bright_white("Max Runs: ") + @pastel.yellow.bold(stats_data["max_runs"].to_s),
43
+ @pastel.bright_white("Gem Version: ") + @pastel.magenta(stats_data["gem_version"] || "N/A"),
44
+ "",
45
+ @pastel.dim("Request ID: #{stats_data["request_id"]}")
46
+ ].join("\n")
47
+
48
+ box_width = 50
49
+ box = TTY::Box.frame(
50
+ title: { top_left: " Your TNG Stats " },
51
+ style: { fg: :bright_white, border: { fg: :green }, title: { fg: :bright_green } },
52
+ padding: [1, 2],
53
+ align: :left,
54
+ width: box_width
55
+ ) { stats_content }
56
+
57
+ puts center_box(box, box_width)
58
+ end
59
+
60
+ def display_usage_progress(stats_data)
61
+ runs = stats_data["runs"]
62
+ max_runs = stats_data["max_runs"]
63
+
64
+ usage_percent = max_runs > 0 ? (runs.to_f / max_runs * 100).round(1) : 0
65
+
66
+ bar_color = determine_bar_color(usage_percent)
67
+
68
+ puts
69
+ puts center_text(@pastel.bright_white.bold("Usage Overview"))
70
+ puts
71
+
72
+ progress_width = 40
73
+ padding = (@terminal_width - progress_width - 20) / 2
74
+ padding = 0 if padding.negative?
75
+
76
+ bar_format = "#{" " * padding}Usage: [:bar] :percent (:current/:total)"
77
+
78
+ bar = TTY::ProgressBar.new(bar_format,
79
+ total: max_runs,
80
+ width: progress_width,
81
+ complete: bar_color,
82
+ incomplete: @pastel.dim("░"),
83
+ head: bar_color)
84
+
85
+ current_progress = 0
86
+ while current_progress < runs
87
+ step = [runs / 10, 1].max
88
+ current_progress = [current_progress + step, runs].min
89
+ bar.current = current_progress
90
+ bar.render
91
+ sleep(0.05) if runs > 10
92
+ end
93
+
94
+ bar.current = runs
95
+ bar.render
96
+
97
+ puts
98
+
99
+ status_msg = case usage_percent
100
+ when 0..50
101
+ @pastel.green("🟢 Good usage - plenty of runs remaining")
102
+ when 51..80
103
+ @pastel.yellow("🟡 Moderate usage - consider monitoring")
104
+ when 81..95
105
+ @pastel.cyan("🟠 High usage - approaching limit")
106
+ else
107
+ @pastel.red("🔴 Limit reached - contact support for more runs")
108
+ end
109
+
110
+ puts center_text(status_msg)
111
+ end
112
+
113
+ def determine_bar_color(usage_percent)
114
+ case usage_percent
115
+ when 0..50
116
+ @pastel.on_green(" ")
117
+ when 51..80
118
+ @pastel.on_yellow(" ")
119
+ when 81..95
120
+ @pastel.on_cyan(" ")
121
+ else
122
+ @pastel.on_red(" ")
123
+ end
124
+ end
125
+
126
+ def display_usage_info(stats_data)
127
+ usage_remaining = stats_data["max_runs"] - stats_data["runs"]
128
+ usage_msg = if usage_remaining.positive?
129
+ @pastel.green("✅ You have #{usage_remaining} test generations remaining")
130
+ else
131
+ @pastel.red("⚠️ You have reached your test generation limit")
132
+ end
133
+ puts center_text(usage_msg)
134
+ end
135
+
136
+ def display_error
137
+ error_msg = @pastel.red.bold("❌ Failed to fetch user statistics")
138
+ puts center_text(error_msg)
139
+ puts center_text(@pastel.dim("Please check your API key and internet connection"))
140
+ end
141
+
142
+ def center_text(text)
143
+ padding = (@terminal_width - @pastel.strip(text).length) / 2
144
+ padding = 0 if padding.negative?
145
+ (" " * padding) + text
146
+ end
147
+
148
+ def center_box(box_string, box_width)
149
+ padding = (@terminal_width - box_width) / 2
150
+ padding = 0 if padding.negative?
151
+ box_string.lines.map { |line| (" " * padding) + line.chomp }.join("\n")
152
+ end
153
+ end