tng 0.1.3 → 0.1.4
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/README.md +0 -79
- data/bin/tng +26 -0
- data/lib/generators/tng/install_generator.rb +21 -13
- data/lib/tng/services/testng.rb +1 -1
- data/lib/tng/ui/about_display.rb +33 -40
- data/lib/tng/ui/authentication_warning_display.rb +172 -0
- data/lib/tng/ui/configuration_display.rb +32 -26
- data/lib/tng/ui/controller_test_flow_display.rb +35 -28
- data/lib/tng/ui/display_banner.rb +13 -23
- data/lib/tng/ui/goodbye_display.rb +14 -23
- data/lib/tng/ui/model_test_flow_display.rb +34 -32
- data/lib/tng/ui/post_install_box.rb +35 -26
- data/lib/tng/ui/service_test_flow_display.rb +34 -28
- data/lib/tng/ui/show_help.rb +82 -47
- data/lib/tng/ui/system_status_display.rb +101 -30
- data/lib/tng/ui/theme.rb +248 -0
- data/lib/tng/ui/user_stats_display.rb +50 -43
- data/lib/tng/version.rb +1 -1
- data/lib/tng.rb +14 -0
- data/tng.gemspec +3 -3
- metadata +10 -6
data/lib/tng/ui/show_help.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "tty-box"
|
2
2
|
require "pastel"
|
3
3
|
require "tty-screen"
|
4
|
+
require_relative "theme"
|
4
5
|
|
5
6
|
class ShowHelp
|
6
7
|
attr_reader :pastel
|
@@ -17,73 +18,107 @@ class ShowHelp
|
|
17
18
|
|
18
19
|
def content
|
19
20
|
[
|
20
|
-
@pastel.
|
21
|
-
@pastel.
|
21
|
+
@pastel.public_send(Tng::UI::Theme.color(:secondary)).bold("TNG - LLM-Powered Rails Test Generator"),
|
22
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted), "Version: #{@version}"),
|
22
23
|
"",
|
23
|
-
@pastel.
|
24
|
-
@pastel.
|
25
|
-
|
26
|
-
|
27
|
-
@pastel.
|
24
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent)).bold("Usage:"),
|
25
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
26
|
+
" bundle exec tng") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
27
|
+
" # Interactive mode"),
|
28
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
29
|
+
" bundle exec tng --type=TYPE --file=FILE") + @pastel.public_send(
|
30
|
+
Tng::UI::Theme.color(:muted), " # Direct mode"
|
31
|
+
),
|
32
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
33
|
+
" bundle exec tng -t TYPE -f FILE") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
34
|
+
" # Short aliases"),
|
35
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
36
|
+
" bundle exec tng -t=TYPE f=FILE") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
37
|
+
" # Mixed format"),
|
28
38
|
"",
|
29
|
-
@pastel.
|
30
|
-
@pastel.
|
31
|
-
|
32
|
-
|
33
|
-
@pastel.
|
34
|
-
|
39
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent)).bold("Examples:"),
|
40
|
+
@pastel.public_send(Tng::UI::Theme.color(:success),
|
41
|
+
" bundle exec tng -t c -f ping") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
42
|
+
" # Controller test"),
|
43
|
+
@pastel.public_send(Tng::UI::Theme.color(:success),
|
44
|
+
" bundle exec tng -t=c f=ping") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
45
|
+
" # Mixed format"),
|
46
|
+
@pastel.public_send(Tng::UI::Theme.color(:success),
|
47
|
+
" bundle exec tng -t m -f user") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
48
|
+
" # Model test"),
|
49
|
+
@pastel.public_send(Tng::UI::Theme.color(:success),
|
50
|
+
" bundle exec tng --type=controller --file=users_controller"),
|
51
|
+
@pastel.public_send(Tng::UI::Theme.color(:success),
|
52
|
+
" bundle exec tng -t c -f \"admin/users_controller\"") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
53
|
+
" # Namespaced"),
|
35
54
|
"",
|
36
|
-
@pastel.
|
37
|
-
@pastel.
|
38
|
-
|
39
|
-
|
40
|
-
@pastel.
|
55
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent)).bold("Options:"),
|
56
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
57
|
+
" -t, --type=TYPE") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
58
|
+
" Test type (controller, model)"),
|
59
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
60
|
+
" -f, --file=FILE") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
61
|
+
" File name (without .rb extension)"),
|
62
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
63
|
+
" -m, --method=METHOD") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
64
|
+
" Method name (for per-method tests)"),
|
65
|
+
@pastel.public_send(Tng::UI::Theme.color(:primary),
|
66
|
+
" -h, --help") + @pastel.public_send(Tng::UI::Theme.color(:muted),
|
67
|
+
" Show this help message"),
|
41
68
|
"",
|
42
|
-
@pastel.
|
43
|
-
@pastel.
|
44
|
-
|
69
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent)).bold("Type Aliases:"),
|
70
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
71
|
+
" #{Tng::UI::Theme.icon(:bullet)} Controllers: ") + @pastel.public_send(Tng::UI::Theme.color(:secondary),
|
72
|
+
"c, controller"),
|
73
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
74
|
+
" #{Tng::UI::Theme.icon(:bullet)} Models: ") + @pastel.public_send(
|
75
|
+
Tng::UI::Theme.color(:secondary), "m, mo, model"
|
76
|
+
),
|
45
77
|
"",
|
46
|
-
@pastel.
|
47
|
-
@pastel.
|
48
|
-
|
49
|
-
|
78
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent)).bold("File Format:"),
|
79
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
80
|
+
" #{Tng::UI::Theme.icon(:bullet)} Controllers: ") + @pastel.public_send(Tng::UI::Theme.color(:success),
|
81
|
+
"users_controller, api/v1/posts_controller"),
|
82
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
83
|
+
" #{Tng::UI::Theme.icon(:bullet)} Models: ") + @pastel.public_send(
|
84
|
+
Tng::UI::Theme.color(:success), "user, blog/post"
|
85
|
+
),
|
86
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
87
|
+
" #{Tng::UI::Theme.icon(:bullet)} Don't include .rb extension"),
|
50
88
|
"",
|
51
|
-
@pastel.
|
52
|
-
@pastel.
|
53
|
-
|
54
|
-
|
89
|
+
@pastel.public_send(Tng::UI::Theme.color(:accent)).bold("Pro Tips:"),
|
90
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
91
|
+
" #{Tng::UI::Theme.icon(:bullet)} Mix formats: ") + @pastel.public_send(Tng::UI::Theme.color(:secondary),
|
92
|
+
"-t=c f=ping") + @pastel.public_send(
|
93
|
+
Tng::UI::Theme.color(:muted), " works perfectly"
|
94
|
+
),
|
95
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
96
|
+
" #{Tng::UI::Theme.icon(:bullet)} Use filtering in interactive mode for large projects"),
|
97
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted),
|
98
|
+
" #{Tng::UI::Theme.icon(:bullet)} All formats are equivalent - use what feels natural"),
|
55
99
|
"",
|
56
|
-
@pastel.
|
100
|
+
@pastel.public_send(Tng::UI::Theme.color(:secondary)).bold("Happy testing! ") + @pastel.public_send(
|
101
|
+
Tng::UI::Theme.color(:accent), "★"
|
102
|
+
)
|
57
103
|
].join("\n")
|
58
104
|
end
|
59
105
|
|
60
106
|
def render
|
61
107
|
# Use a reasonable width that matches other UI components
|
62
|
-
box_width =
|
63
|
-
box_width =
|
108
|
+
box_width = Tng::UI::Theme.calculate_box_width(@terminal_width)
|
109
|
+
box_width = [box_width, 80].min # Allow wider for help content
|
110
|
+
style = Tng::UI::Theme.box_style(:default)
|
64
111
|
|
65
112
|
box = TTY::Box.frame(
|
66
113
|
title: { top_left: " TNG Help ", bottom_right: " v#{@version} " },
|
67
|
-
style:
|
68
|
-
|
69
|
-
border: { fg: :cyan },
|
70
|
-
title: { fg: :bright_cyan }
|
71
|
-
},
|
72
|
-
padding: [1, 2],
|
114
|
+
style: style[:style],
|
115
|
+
padding: style[:padding],
|
73
116
|
align: :left,
|
74
117
|
width: box_width
|
75
118
|
) do
|
76
119
|
content
|
77
120
|
end
|
78
121
|
|
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")
|
122
|
+
Tng::UI::Theme.center_box(box, box_width, @terminal_width)
|
88
123
|
end
|
89
124
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "pastel"
|
4
|
+
require_relative "theme"
|
4
5
|
|
5
6
|
class SystemStatusDisplay
|
6
7
|
def initialize(pastel, args)
|
@@ -11,47 +12,117 @@ class SystemStatusDisplay
|
|
11
12
|
def display(status)
|
12
13
|
case status[:status]
|
13
14
|
when :ok
|
14
|
-
|
15
|
+
unless @args[:type] && @args[:file]
|
16
|
+
puts @pastel.public_send(Tng::UI::Theme.color(:success),
|
17
|
+
"#{Tng::UI::Theme.icon(:success)} System operational")
|
18
|
+
end
|
15
19
|
true
|
16
20
|
when :version_mismatch
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
display_status_message(
|
22
|
+
title: "Version Mismatch",
|
23
|
+
icon: Tng::UI::Theme.icon(:error),
|
24
|
+
color: Tng::UI::Theme.color(:error),
|
25
|
+
content: [
|
26
|
+
"Your gem version: #{status[:gem_version]}",
|
27
|
+
"Required version: #{status[:current_version]}",
|
28
|
+
"",
|
29
|
+
"Quick fix:",
|
30
|
+
"• gem update tng",
|
31
|
+
"• bundle update tng"
|
32
|
+
]
|
33
|
+
)
|
25
34
|
false
|
26
35
|
when :base_url_mismatch
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
+
display_status_message(
|
37
|
+
title: "URL Mismatch",
|
38
|
+
icon: Tng::UI::Theme.icon(:error),
|
39
|
+
color: Tng::UI::Theme.color(:error),
|
40
|
+
content: [
|
41
|
+
"Your URL: #{status[:user_base_url]}",
|
42
|
+
"Server URL: #{status[:server_base_url]}",
|
43
|
+
"",
|
44
|
+
"Quick fix:",
|
45
|
+
"• Edit config/initializers/tng.rb",
|
46
|
+
"• Set: config.base_url = '#{status[:server_base_url]}'"
|
47
|
+
]
|
48
|
+
)
|
36
49
|
false
|
37
50
|
when :service_down
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
51
|
+
display_status_message(
|
52
|
+
title: "Service Unavailable",
|
53
|
+
icon: Tng::UI::Theme.icon(:error),
|
54
|
+
color: Tng::UI::Theme.color(:error),
|
55
|
+
content: [
|
56
|
+
status[:message] || "TNG service is currently down",
|
57
|
+
"",
|
58
|
+
"Please try again later or contact support"
|
59
|
+
]
|
60
|
+
)
|
43
61
|
false
|
44
62
|
when :error
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
63
|
+
display_status_message(
|
64
|
+
title: "Connection Error",
|
65
|
+
icon: Tng::UI::Theme.icon(:error),
|
66
|
+
color: Tng::UI::Theme.color(:error),
|
67
|
+
content: [
|
68
|
+
status[:message] || "Unable to connect to TNG service",
|
69
|
+
"",
|
70
|
+
"Please check your internet connection and try again"
|
71
|
+
]
|
72
|
+
)
|
50
73
|
false
|
51
74
|
else
|
52
|
-
|
53
|
-
|
75
|
+
display_status_message(
|
76
|
+
title: "Unknown Error",
|
77
|
+
icon: Tng::UI::Theme.icon(:error),
|
78
|
+
color: Tng::UI::Theme.color(:error),
|
79
|
+
content: [status[:message] || "An unknown error occurred"]
|
80
|
+
)
|
54
81
|
false
|
55
82
|
end
|
56
83
|
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def display_status_message(title:, icon:, color:, content:)
|
88
|
+
require "tty-box"
|
89
|
+
require "tty-screen"
|
90
|
+
|
91
|
+
terminal_width = begin
|
92
|
+
TTY::Screen.width
|
93
|
+
rescue StandardError
|
94
|
+
80
|
95
|
+
end
|
96
|
+
|
97
|
+
formatted_content = content.map do |line|
|
98
|
+
case line
|
99
|
+
when /^•/
|
100
|
+
@pastel.public_send(Tng::UI::Theme.color(:muted), line)
|
101
|
+
when /^Quick fix:/
|
102
|
+
@pastel.public_send(Tng::UI::Theme.color(:secondary), line)
|
103
|
+
when ""
|
104
|
+
line
|
105
|
+
else
|
106
|
+
@pastel.public_send(Tng::UI::Theme.color(:warning), line)
|
107
|
+
end
|
108
|
+
end.join("\n")
|
109
|
+
|
110
|
+
box_width = Tng::UI::Theme.calculate_box_width(terminal_width)
|
111
|
+
|
112
|
+
status_box = TTY::Box.frame(
|
113
|
+
title: { top_left: " #{title} " },
|
114
|
+
style: {
|
115
|
+
fg: :bright_white,
|
116
|
+
border: { fg: color },
|
117
|
+
title: { fg: color == Tng::UI::Theme.color(:error) ? :bright_red : color }
|
118
|
+
},
|
119
|
+
padding: [1, 2],
|
120
|
+
width: box_width
|
121
|
+
) do
|
122
|
+
formatted_content
|
123
|
+
end
|
124
|
+
|
125
|
+
puts Tng::UI::Theme.center_box(status_box, box_width, terminal_width)
|
126
|
+
puts
|
127
|
+
end
|
57
128
|
end
|
data/lib/tng/ui/theme.rb
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tng
|
4
|
+
module UI
|
5
|
+
module Theme
|
6
|
+
# Icons - consistent across all UI components
|
7
|
+
ICONS = {
|
8
|
+
# Status icons
|
9
|
+
success: "✅",
|
10
|
+
error: "❌",
|
11
|
+
warning: "⚠️",
|
12
|
+
info: "ℹ️",
|
13
|
+
|
14
|
+
# Action icons
|
15
|
+
rocket: "🚀",
|
16
|
+
wave: "👋",
|
17
|
+
stats: "📊",
|
18
|
+
config: "📋",
|
19
|
+
heart: "❤️",
|
20
|
+
lightbulb: "💡",
|
21
|
+
|
22
|
+
# Navigation icons
|
23
|
+
back: "⬅️ ", # Added space after back arrow
|
24
|
+
marker: "▶",
|
25
|
+
bullet: "•"
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
# Colors - terminal-agnostic color scheme
|
29
|
+
COLORS = {
|
30
|
+
# Primary status colors
|
31
|
+
success: :green,
|
32
|
+
error: :red,
|
33
|
+
warning: :yellow,
|
34
|
+
info: :cyan,
|
35
|
+
|
36
|
+
# Text hierarchy colors
|
37
|
+
primary: :bright_white,
|
38
|
+
secondary: :cyan,
|
39
|
+
accent: :yellow,
|
40
|
+
muted: :dim,
|
41
|
+
|
42
|
+
# Border colors for boxes
|
43
|
+
border_success: :green,
|
44
|
+
border_error: :red,
|
45
|
+
border_warning: :yellow,
|
46
|
+
border_info: :cyan,
|
47
|
+
border_primary: :cyan
|
48
|
+
}.freeze
|
49
|
+
|
50
|
+
# Box styling constants
|
51
|
+
BOX_STYLES = {
|
52
|
+
default: {
|
53
|
+
style: {
|
54
|
+
border: {
|
55
|
+
type: :light,
|
56
|
+
top_left: "┌",
|
57
|
+
top_right: "┐",
|
58
|
+
bottom_left: "└",
|
59
|
+
bottom_right: "┘",
|
60
|
+
top: "─",
|
61
|
+
bottom: "─",
|
62
|
+
left: "│",
|
63
|
+
right: "│"
|
64
|
+
}
|
65
|
+
},
|
66
|
+
padding: [1, 2],
|
67
|
+
max_width: 100
|
68
|
+
},
|
69
|
+
success: {
|
70
|
+
style: {
|
71
|
+
border: {
|
72
|
+
type: :light,
|
73
|
+
top_left: "┌",
|
74
|
+
top_right: "┐",
|
75
|
+
bottom_left: "└",
|
76
|
+
bottom_right: "┘",
|
77
|
+
top: "─",
|
78
|
+
bottom: "─",
|
79
|
+
left: "│",
|
80
|
+
right: "│"
|
81
|
+
},
|
82
|
+
fg: :green
|
83
|
+
},
|
84
|
+
padding: [1, 2],
|
85
|
+
max_width: 100
|
86
|
+
},
|
87
|
+
warning: {
|
88
|
+
style: {
|
89
|
+
border: {
|
90
|
+
type: :light,
|
91
|
+
top_left: "┌",
|
92
|
+
top_right: "┐",
|
93
|
+
bottom_left: "└",
|
94
|
+
bottom_right: "┘",
|
95
|
+
top: "─",
|
96
|
+
bottom: "─",
|
97
|
+
left: "│",
|
98
|
+
right: "│"
|
99
|
+
},
|
100
|
+
fg: :yellow
|
101
|
+
},
|
102
|
+
padding: [1, 2],
|
103
|
+
max_width: 100
|
104
|
+
}
|
105
|
+
}.freeze
|
106
|
+
|
107
|
+
# Helper methods for consistent styling
|
108
|
+
module_function
|
109
|
+
|
110
|
+
def detect_terminal_background
|
111
|
+
# Manual override for TNG (highest priority)
|
112
|
+
case ENV["TNG_TERMINAL_THEME"]&.downcase
|
113
|
+
when "light"
|
114
|
+
:light
|
115
|
+
when "dark"
|
116
|
+
:dark
|
117
|
+
else
|
118
|
+
get_background_color
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def get_background_color
|
123
|
+
print "\e]11;?\e\\"
|
124
|
+
$stdout.flush
|
125
|
+
|
126
|
+
require "timeout"
|
127
|
+
begin
|
128
|
+
response = ""
|
129
|
+
Timeout.timeout(0.1) do
|
130
|
+
response = $stdin.read_nonblock(100)
|
131
|
+
end
|
132
|
+
|
133
|
+
case response
|
134
|
+
when %r{rgb:([0-9a-f]{4})/([0-9a-f]{4})/([0-9a-f]{4})}i
|
135
|
+
r, g, b = [::Regexp.last_match(1), ::Regexp.last_match(2), ::Regexp.last_match(3)].map do |hex|
|
136
|
+
hex.to_i(16) >> 8
|
137
|
+
end
|
138
|
+
brightness = (r * 0.299 + g * 0.587 + b * 0.114)
|
139
|
+
return brightness > 128 ? :light : :dark
|
140
|
+
when /rgb:([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i
|
141
|
+
r, g, b = [::Regexp.last_match(1), ::Regexp.last_match(2), ::Regexp.last_match(3)].map do |hex|
|
142
|
+
hex.to_i(16)
|
143
|
+
end
|
144
|
+
brightness = (r * 0.299 + g * 0.587 + b * 0.114)
|
145
|
+
return brightness > 128 ? :light : :dark
|
146
|
+
end
|
147
|
+
rescue IO::WaitReadable, Errno::EAGAIN
|
148
|
+
# No data available
|
149
|
+
rescue Timeout::Error, StandardError
|
150
|
+
# Fall back to environment detection
|
151
|
+
end
|
152
|
+
|
153
|
+
# Enhanced fallback detection
|
154
|
+
case ENV["TERM_PROGRAM"]
|
155
|
+
when "iTerm.app"
|
156
|
+
ENV["ITERM_PROFILE"]&.downcase&.include?("light") ? :light : :dark
|
157
|
+
when "Apple_Terminal"
|
158
|
+
:light # Terminal.app defaults to light
|
159
|
+
when "vscode"
|
160
|
+
:dark # VS Code integrated terminal usually dark
|
161
|
+
else
|
162
|
+
# Check for common dark theme indicators
|
163
|
+
if ENV["COLORTERM"] == "truecolor" || ENV["TERM"]&.include?("256")
|
164
|
+
:dark
|
165
|
+
else
|
166
|
+
:light
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def adaptive_color(color_key)
|
172
|
+
base_color = base_color(color_key)
|
173
|
+
|
174
|
+
# Adjust colors based on terminal background for optimal visibility
|
175
|
+
if detect_terminal_background == :light
|
176
|
+
# Light terminal adjustments
|
177
|
+
case base_color
|
178
|
+
when :dim
|
179
|
+
:black
|
180
|
+
when :bright_white
|
181
|
+
:black # White text invisible on light backgrounds
|
182
|
+
when :cyan
|
183
|
+
:blue # Cyan can be hard to read on light backgrounds
|
184
|
+
when :yellow
|
185
|
+
:red # Yellow invisible on light backgrounds
|
186
|
+
else
|
187
|
+
base_color
|
188
|
+
end
|
189
|
+
else
|
190
|
+
# Dark terminal adjustments - optimized for macOS Terminal
|
191
|
+
case base_color
|
192
|
+
when :dim
|
193
|
+
# macOS Terminal renders :white better than :bright_white
|
194
|
+
:white
|
195
|
+
when :black
|
196
|
+
:white # Black text invisible on dark backgrounds
|
197
|
+
when :cyan
|
198
|
+
# macOS Terminal cyan can be hard to read, use blue
|
199
|
+
:blue
|
200
|
+
when :yellow
|
201
|
+
# macOS Terminal yellow can be faded, use brighter alternatives
|
202
|
+
:red
|
203
|
+
when :bright_white
|
204
|
+
# Force to regular white for better macOS Terminal compatibility
|
205
|
+
:white
|
206
|
+
else
|
207
|
+
base_color
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def icon(name)
|
213
|
+
ICONS[name] || ""
|
214
|
+
end
|
215
|
+
|
216
|
+
def color(name)
|
217
|
+
adaptive_color(name)
|
218
|
+
end
|
219
|
+
|
220
|
+
def base_color(name)
|
221
|
+
COLORS[name] || :white
|
222
|
+
end
|
223
|
+
|
224
|
+
def box_style(name)
|
225
|
+
BOX_STYLES[name] || BOX_STYLES[:default]
|
226
|
+
end
|
227
|
+
|
228
|
+
def calculate_box_width(terminal_width)
|
229
|
+
[terminal_width - 4, BOX_STYLES[:default][:max_width]].min
|
230
|
+
end
|
231
|
+
|
232
|
+
def center_box(box_content, box_width, terminal_width)
|
233
|
+
lines = box_content.split("\n")
|
234
|
+
padding = (terminal_width - box_width) / 2
|
235
|
+
padding = 0 if padding.negative?
|
236
|
+
lines.map { |line| (" " * padding) + line }.join("\n")
|
237
|
+
end
|
238
|
+
|
239
|
+
def center_text(text, terminal_width)
|
240
|
+
# Remove ANSI color codes for length calculation
|
241
|
+
clean_text = text.gsub(/\e\[[0-9;]*m/, "")
|
242
|
+
padding = (terminal_width - clean_text.length) / 2
|
243
|
+
padding = 0 if padding.negative?
|
244
|
+
(" " * padding) + text
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|