tng 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.

Potentially problematic release.


This version of tng might be problematic. Click here for more details.

@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pastel"
4
+ require_relative "theme"
5
+
6
+ class SystemStatusDisplay
7
+ def initialize(pastel, args)
8
+ @pastel = pastel
9
+ @args = args
10
+ end
11
+
12
+ def display(status)
13
+ case status[:status]
14
+ when :ok
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
19
+ true
20
+ when :version_mismatch
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
+ )
34
+ false
35
+ when :base_url_mismatch
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
+ )
49
+ false
50
+ when :service_down
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
+ )
61
+ false
62
+ when :error
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
+ )
73
+ false
74
+ else
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
+ )
81
+ false
82
+ end
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
128
+ end
@@ -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
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-box"
4
+ require "tty-progressbar"
5
+ require "pastel"
6
+ require "tty-screen"
7
+ require_relative "theme"
8
+
9
+ class UserStatsDisplay
10
+ def initialize(pastel, prompt)
11
+ @pastel = pastel
12
+ @prompt = prompt
13
+ @terminal_width = begin
14
+ TTY::Screen.width
15
+ rescue StandardError
16
+ 80
17
+ end
18
+ end
19
+
20
+ def display(stats_data)
21
+ header = @pastel.public_send(Tng::UI::Theme.color(:primary)).bold("#{Tng::UI::Theme.icon(:stats)} User Statistics")
22
+ puts Tng::UI::Theme.center_text(header, @terminal_width)
23
+
24
+ if stats_data
25
+ display_stats_box(stats_data)
26
+ display_usage_progress(stats_data)
27
+ display_usage_info(stats_data)
28
+ else
29
+ display_error
30
+ end
31
+
32
+ puts
33
+ tip_msg = @pastel.public_send(Tng::UI::Theme.color(:muted),
34
+ "#{Tng::UI::Theme.icon(:lightbulb)} Tip: Contact support if you need more test generations")
35
+ puts Tng::UI::Theme.center_text(tip_msg, @terminal_width)
36
+ @prompt.keypress(Tng::UI::Theme.center_text(
37
+ @pastel.public_send(Tng::UI::Theme.color(:muted),
38
+ "Press any key to continue..."), @terminal_width
39
+ ))
40
+ end
41
+
42
+ def display_stats_box(stats_data)
43
+ stats_content = [
44
+ @pastel.public_send(Tng::UI::Theme.color(:secondary)).bold("Account Information"),
45
+ "",
46
+ @pastel.public_send(Tng::UI::Theme.color(:primary), "Test Runs: ") + @pastel.public_send(Tng::UI::Theme.color(:success)).bold(stats_data["runs"].to_s),
47
+ @pastel.public_send(Tng::UI::Theme.color(:primary), "Max Runs: ") + @pastel.public_send(Tng::UI::Theme.color(:warning)).bold(stats_data["max_runs"].to_s),
48
+ @pastel.public_send(Tng::UI::Theme.color(:primary),
49
+ "Gem Version: ") + @pastel.public_send(Tng::UI::Theme.color(:accent),
50
+ stats_data["gem_version"] || "N/A"),
51
+ "",
52
+ @pastel.public_send(Tng::UI::Theme.color(:muted), "Request ID: #{stats_data["request_id"]}")
53
+ ].join("\n")
54
+
55
+ box_width = Tng::UI::Theme.calculate_box_width(@terminal_width)
56
+ style = Tng::UI::Theme.box_style(:success)
57
+
58
+ box = TTY::Box.frame(
59
+ title: { top_left: " Your TNG Stats " },
60
+ style: style[:style],
61
+ padding: style[:padding],
62
+ align: :left,
63
+ width: box_width
64
+ ) { stats_content }
65
+
66
+ puts Tng::UI::Theme.center_box(box, box_width, @terminal_width)
67
+ end
68
+
69
+ def display_usage_progress(stats_data)
70
+ runs = stats_data["runs"]
71
+ max_runs = stats_data["max_runs"]
72
+
73
+ usage_percent = max_runs > 0 ? (runs.to_f / max_runs * 100).round(1) : 0
74
+
75
+ bar_color = determine_bar_color(usage_percent)
76
+
77
+ puts
78
+ puts Tng::UI::Theme.center_text(@pastel.public_send(Tng::UI::Theme.color(:primary)).bold("Usage Overview"),
79
+ @terminal_width)
80
+ puts
81
+
82
+ progress_width = 40
83
+ padding = (@terminal_width - progress_width - 20) / 2
84
+ padding = 0 if padding.negative?
85
+
86
+ bar_format = "#{" " * padding}Usage: [:bar] :percent (:current/:total)"
87
+
88
+ bar = TTY::ProgressBar.new(bar_format,
89
+ total: max_runs,
90
+ width: progress_width,
91
+ complete: bar_color,
92
+ incomplete: @pastel.public_send(Tng::UI::Theme.color(:muted), "░"),
93
+ head: bar_color)
94
+
95
+ current_progress = 0
96
+ while current_progress < runs
97
+ step = [runs / 10, 1].max
98
+ current_progress = [current_progress + step, runs].min
99
+ bar.current = current_progress
100
+ bar.render
101
+ sleep(0.05) if runs > 10
102
+ end
103
+
104
+ bar.current = runs
105
+ bar.render
106
+
107
+ puts
108
+
109
+ status_msg = case usage_percent
110
+ when 0..50
111
+ @pastel.public_send(Tng::UI::Theme.color(:success),
112
+ "#{Tng::UI::Theme.icon(:success)} Good usage - plenty of runs remaining")
113
+ when 51..80
114
+ @pastel.public_send(Tng::UI::Theme.color(:warning),
115
+ "#{Tng::UI::Theme.icon(:warning)} Moderate usage - consider monitoring")
116
+ when 81..95
117
+ @pastel.public_send(Tng::UI::Theme.color(:info),
118
+ "#{Tng::UI::Theme.icon(:warning)} High usage - approaching limit")
119
+ else
120
+ @pastel.public_send(Tng::UI::Theme.color(:error),
121
+ "#{Tng::UI::Theme.icon(:error)} Limit reached - contact support for more runs")
122
+ end
123
+
124
+ puts Tng::UI::Theme.center_text(status_msg, @terminal_width)
125
+ end
126
+
127
+ def determine_bar_color(usage_percent)
128
+ case usage_percent
129
+ when 0..50
130
+ @pastel.public_send(Tng::UI::Theme.color(:success), "█")
131
+ when 51..80
132
+ @pastel.public_send(Tng::UI::Theme.color(:warning), "█")
133
+ when 81..95
134
+ @pastel.public_send(Tng::UI::Theme.color(:info), "█")
135
+ else
136
+ @pastel.public_send(Tng::UI::Theme.color(:error), "█")
137
+ end
138
+ end
139
+
140
+ def display_usage_info(stats_data)
141
+ usage_remaining = stats_data["max_runs"] - stats_data["runs"]
142
+ usage_msg = if usage_remaining.positive?
143
+ @pastel.public_send(Tng::UI::Theme.color(:success),
144
+ "#{Tng::UI::Theme.icon(:success)} You have #{usage_remaining} test generations remaining")
145
+ else
146
+ @pastel.public_send(Tng::UI::Theme.color(:error),
147
+ "#{Tng::UI::Theme.icon(:warning)} You have reached your test generation limit")
148
+ end
149
+ puts Tng::UI::Theme.center_text(usage_msg, @terminal_width)
150
+ end
151
+
152
+ def display_error
153
+ error_msg = @pastel.public_send(Tng::UI::Theme.color(:error)).bold("#{Tng::UI::Theme.icon(:error)} Failed to fetch user statistics")
154
+ puts Tng::UI::Theme.center_text(error_msg, @terminal_width)
155
+ puts Tng::UI::Theme.center_text(
156
+ @pastel.public_send(Tng::UI::Theme.color(:muted),
157
+ "Please check your API key and internet connection"), @terminal_width
158
+ )
159
+ end
160
+ end