tng 0.2.1

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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +15 -0
  3. data/LICENSE.md +32 -0
  4. data/README.md +413 -0
  5. data/Rakefile +124 -0
  6. data/bin/load_dev +22 -0
  7. data/bin/tng +888 -0
  8. data/binaries/tng.bundle +0 -0
  9. data/binaries/tng.so +0 -0
  10. data/lib/generators/tng/install_generator.rb +236 -0
  11. data/lib/tng/analyzers/controller.rb +114 -0
  12. data/lib/tng/analyzers/model.rb +131 -0
  13. data/lib/tng/analyzers/other.rb +277 -0
  14. data/lib/tng/analyzers/service.rb +150 -0
  15. data/lib/tng/api/http_client.rb +100 -0
  16. data/lib/tng/railtie.rb +11 -0
  17. data/lib/tng/services/direct_generation.rb +320 -0
  18. data/lib/tng/services/extract_methods.rb +39 -0
  19. data/lib/tng/services/test_generator.rb +287 -0
  20. data/lib/tng/services/testng.rb +100 -0
  21. data/lib/tng/services/user_app_config.rb +76 -0
  22. data/lib/tng/ui/about_display.rb +66 -0
  23. data/lib/tng/ui/authentication_warning_display.rb +172 -0
  24. data/lib/tng/ui/configuration_display.rb +52 -0
  25. data/lib/tng/ui/controller_test_flow_display.rb +79 -0
  26. data/lib/tng/ui/display_banner.rb +44 -0
  27. data/lib/tng/ui/goodbye_display.rb +41 -0
  28. data/lib/tng/ui/model_test_flow_display.rb +80 -0
  29. data/lib/tng/ui/other_test_flow_display.rb +78 -0
  30. data/lib/tng/ui/post_install_box.rb +80 -0
  31. data/lib/tng/ui/service_test_flow_display.rb +78 -0
  32. data/lib/tng/ui/show_help.rb +78 -0
  33. data/lib/tng/ui/system_status_display.rb +128 -0
  34. data/lib/tng/ui/theme.rb +258 -0
  35. data/lib/tng/ui/user_stats_display.rb +160 -0
  36. data/lib/tng/utils.rb +325 -0
  37. data/lib/tng/version.rb +5 -0
  38. data/lib/tng.rb +308 -0
  39. data/tng.gemspec +56 -0
  40. metadata +293 -0
@@ -0,0 +1,258 @@
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
+ module_function
108
+
109
+ # Cache for background detection to avoid multiple escape sequences
110
+ @background_cache = nil
111
+
112
+ def detect_terminal_background
113
+ # Return cached result if available
114
+ return @background_cache if @background_cache
115
+
116
+ # Get background color and cache the result
117
+ @background_cache = get_background_color
118
+ @background_cache
119
+ end
120
+
121
+ def get_background_color
122
+ return :dark unless $stdout.tty? && $stdin.tty? && interactive_session?
123
+
124
+ print "\e]11;?\e\\"
125
+ $stdout.flush
126
+
127
+ require "timeout"
128
+ begin
129
+ response = ""
130
+ Timeout.timeout(0.1) do
131
+ response = $stdin.read_nonblock(100)
132
+ end
133
+
134
+ case response
135
+ when %r{rgb:([0-9a-f]{4})/([0-9a-f]{4})/([0-9a-f]{4})}i
136
+ r, g, b = [::Regexp.last_match(1), ::Regexp.last_match(2), ::Regexp.last_match(3)].map do |hex|
137
+ hex.to_i(16) >> 8
138
+ end
139
+ brightness = (r * 0.299 + g * 0.587 + b * 0.114)
140
+ return brightness > 128 ? :light : :dark
141
+ when /rgb:([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i
142
+ r, g, b = [::Regexp.last_match(1), ::Regexp.last_match(2), ::Regexp.last_match(3)].map do |hex|
143
+ hex.to_i(16)
144
+ end
145
+ brightness = (r * 0.299 + g * 0.587 + b * 0.114)
146
+ return brightness > 128 ? :light : :dark
147
+ end
148
+ rescue IO::WaitReadable, Errno::EAGAIN
149
+ # No data available
150
+ rescue Timeout::Error, StandardError
151
+ # Fall back to environment detection
152
+ end
153
+
154
+ # Enhanced fallback detection
155
+ case ENV["TERM_PROGRAM"]
156
+ when "iTerm.app"
157
+ ENV["ITERM_PROFILE"]&.downcase&.include?("light") ? :light : :dark
158
+ when "Apple_Terminal"
159
+ :light # Terminal.app defaults to light
160
+ when "vscode"
161
+ :dark # VS Code integrated terminal usually dark
162
+ else
163
+ # Check for common dark theme indicators
164
+ if ENV["COLORTERM"] == "truecolor" || ENV["TERM"]&.include?("256")
165
+ :dark
166
+ else
167
+ :light
168
+ end
169
+ end
170
+ end
171
+
172
+ def interactive_session?
173
+ return false if defined?(Rails) && Rails.respond_to?(:application) && Rails.application&.initialized?
174
+ return false if ENV["BUNDLE_GEMFILE"]
175
+ return false if $PROGRAM_NAME&.include?("bundle")
176
+ return false if $PROGRAM_NAME&.include?("rails") && ARGV.any? { |arg| %w[server console runner].include?(arg) }
177
+
178
+ ENV["TNG_CLI"] == "true" || $PROGRAM_NAME&.include?("tng")
179
+ end
180
+
181
+ def adaptive_color(color_key)
182
+ base_color = base_color(color_key)
183
+
184
+ # Adjust colors based on terminal background for optimal visibility
185
+ if detect_terminal_background == :light
186
+ # Light terminal adjustments
187
+ case base_color
188
+ when :dim
189
+ :black
190
+ when :bright_white
191
+ :black # White text invisible on light backgrounds
192
+ when :cyan
193
+ :blue # Cyan can be hard to read on light backgrounds
194
+ when :yellow
195
+ :red # Yellow invisible on light backgrounds
196
+ else
197
+ base_color
198
+ end
199
+ else
200
+ # Dark terminal adjustments - optimized for macOS Terminal
201
+ case base_color
202
+ when :dim
203
+ # macOS Terminal renders :white better than :bright_white
204
+ :white
205
+ when :black
206
+ :white # Black text invisible on dark backgrounds
207
+ when :cyan
208
+ # macOS Terminal cyan can be hard to read, use blue
209
+ :blue
210
+ when :yellow
211
+ # macOS Terminal yellow can be faded, use brighter alternatives
212
+ :red
213
+ when :bright_white
214
+ # Force to regular white for better macOS Terminal compatibility
215
+ :white
216
+ else
217
+ base_color
218
+ end
219
+ end
220
+ end
221
+
222
+ def icon(name)
223
+ ICONS[name] || ""
224
+ end
225
+
226
+ def color(name)
227
+ adaptive_color(name)
228
+ end
229
+
230
+ def base_color(name)
231
+ COLORS[name] || :white
232
+ end
233
+
234
+ def box_style(name)
235
+ BOX_STYLES[name] || BOX_STYLES[:default]
236
+ end
237
+
238
+ def calculate_box_width(terminal_width)
239
+ [terminal_width - 4, BOX_STYLES[:default][:max_width]].min
240
+ end
241
+
242
+ def center_box(box_content, box_width, terminal_width)
243
+ lines = box_content.split("\n")
244
+ padding = (terminal_width - box_width) / 2
245
+ padding = 0 if padding.negative?
246
+ lines.map { |line| (" " * padding) + line }.join("\n")
247
+ end
248
+
249
+ def center_text(text, terminal_width)
250
+ # Remove ANSI color codes for length calculation
251
+ clean_text = text.gsub(/\e\[[0-9;]*m/, "")
252
+ padding = (terminal_width - clean_text.length) / 2
253
+ padding = 0 if padding.negative?
254
+ (" " * padding) + text
255
+ end
256
+ end
257
+ end
258
+ 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