pretty_irb 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.
- checksums.yaml +7 -0
- data/00-MANIFEST.md +356 -0
- data/ADVANCED_FEATURES.md +440 -0
- data/AI_HELP.md +408 -0
- data/AI_QUICK_START.md +247 -0
- data/COMPLETE.md +421 -0
- data/EXAMPLES.md +206 -0
- data/FEATURE_SHOWCASE.md +441 -0
- data/FILE_STRUCTURE.md +231 -0
- data/Gemfile +3 -0
- data/IMPLEMENTATION_COMPLETE.md +309 -0
- data/INDEX.md +708 -0
- data/INSTALL.md +250 -0
- data/LICENSE.txt +21 -0
- data/PUBLISH_TO_RUBYGEMS.md +466 -0
- data/QUICKSTART.md +70 -0
- data/QUICK_REFERENCE.md +253 -0
- data/README.md +251 -0
- data/Rakefile +6 -0
- data/SETUP.md +209 -0
- data/START_HERE.md +462 -0
- data/SUMMARY.md +372 -0
- data/WELCOME.md +300 -0
- data/WHAT_IS_NEW.md +324 -0
- data/exe/irb1 +6 -0
- data/exe/pretty_irb +6 -0
- data/lib/pretty_irb/ai_helper.rb +842 -0
- data/lib/pretty_irb/auto_corrector.rb +76 -0
- data/lib/pretty_irb/benchmarker.rb +94 -0
- data/lib/pretty_irb/cheat_sheet.rb +476 -0
- data/lib/pretty_irb/formatter.rb +66 -0
- data/lib/pretty_irb/history_manager.rb +97 -0
- data/lib/pretty_irb/shell.rb +387 -0
- data/lib/pretty_irb/snippet_manager.rb +119 -0
- data/lib/pretty_irb/variable_inspector.rb +146 -0
- data/lib/pretty_irb/version.rb +3 -0
- data/lib/pretty_irb.rb +26 -0
- metadata +200 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require "time"
|
|
2
|
+
|
|
3
|
+
module PrettyIRB
|
|
4
|
+
class HistoryManager
|
|
5
|
+
attr_reader :entries
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@entries = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Add entry to history
|
|
12
|
+
def add(code, result = nil, error = nil)
|
|
13
|
+
@entries << {
|
|
14
|
+
code: code,
|
|
15
|
+
result: result,
|
|
16
|
+
error: error,
|
|
17
|
+
timestamp: Time.now,
|
|
18
|
+
line_number: @entries.length + 1
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Search history by keyword
|
|
23
|
+
def search(keyword)
|
|
24
|
+
results = @entries.select { |entry| entry[:code].include?(keyword) }
|
|
25
|
+
format_search_results(results)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get last N entries
|
|
29
|
+
def last(n = 10)
|
|
30
|
+
format_history(@entries.last(n))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Get all history
|
|
34
|
+
def all
|
|
35
|
+
format_history(@entries)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Get entry by line number
|
|
39
|
+
def get(line_number)
|
|
40
|
+
entry = @entries[line_number - 1]
|
|
41
|
+
return "" unless entry
|
|
42
|
+
"#{entry[:line_number]}: #{entry[:code]}\n=> #{entry[:result]}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Clear history
|
|
46
|
+
def clear
|
|
47
|
+
@entries = []
|
|
48
|
+
"✓ History cleared"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Export history to file
|
|
52
|
+
def export(filename)
|
|
53
|
+
File.write(filename, format_for_export)
|
|
54
|
+
"✓ History exported to #{filename}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def format_history(entries)
|
|
60
|
+
return "📋 No history" if entries.empty?
|
|
61
|
+
|
|
62
|
+
output = "📋 History (#{entries.length} entries):\n\n"
|
|
63
|
+
entries.each do |entry|
|
|
64
|
+
output += "#{entry[:line_number]}: #{entry[:code]}\n"
|
|
65
|
+
if entry[:result]
|
|
66
|
+
output += " => #{entry[:result]}\n"
|
|
67
|
+
elsif entry[:error]
|
|
68
|
+
output += " ✗ #{entry[:error]}\n"
|
|
69
|
+
end
|
|
70
|
+
output += "\n"
|
|
71
|
+
end
|
|
72
|
+
output
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def format_search_results(results)
|
|
76
|
+
return "🔍 No matches found" if results.empty?
|
|
77
|
+
|
|
78
|
+
output = "🔍 Found #{results.length} matches:\n\n"
|
|
79
|
+
results.each do |entry|
|
|
80
|
+
output += "#{entry[:line_number]}: #{entry[:code]}\n"
|
|
81
|
+
output += " Time: #{entry[:timestamp].strftime('%H:%M:%S')}\n\n"
|
|
82
|
+
end
|
|
83
|
+
output
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def format_for_export
|
|
87
|
+
output = "# Pretty IRB History\n"
|
|
88
|
+
output += "# Generated: #{Time.now}\n\n"
|
|
89
|
+
@entries.each do |entry|
|
|
90
|
+
output += "# Line #{entry[:line_number]} - #{entry[:timestamp]}\n"
|
|
91
|
+
output += entry[:code] + "\n"
|
|
92
|
+
output += "# => #{entry[:result]}\n\n" if entry[:result]
|
|
93
|
+
end
|
|
94
|
+
output
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
require "irb"
|
|
2
|
+
require "reline"
|
|
3
|
+
|
|
4
|
+
module PrettyIRB
|
|
5
|
+
class Shell
|
|
6
|
+
BANNER = <<~BANNER
|
|
7
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
8
|
+
║ 🎨 Welcome to Pretty IRB 🎨 ║
|
|
9
|
+
║ ║
|
|
10
|
+
║ Enhanced Interactive Ruby Shell with Auto-Correct ║
|
|
11
|
+
║ Type 'help' for commands or 'exit' to quit ║
|
|
12
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
13
|
+
BANNER
|
|
14
|
+
|
|
15
|
+
def initialize(config = {})
|
|
16
|
+
@config = config
|
|
17
|
+
@history_manager = HistoryManager.new
|
|
18
|
+
@snippet_manager = SnippetManager.new
|
|
19
|
+
@variable_inspector = VariableInspector.new(TOPLEVEL_BINDING)
|
|
20
|
+
@input_buffer = ""
|
|
21
|
+
@binding = TOPLEVEL_BINDING
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run
|
|
25
|
+
puts BANNER.light_magenta
|
|
26
|
+
start_repl
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def start_repl
|
|
32
|
+
loop do
|
|
33
|
+
begin
|
|
34
|
+
input = read_input
|
|
35
|
+
break if input.nil? || input.downcase == "exit"
|
|
36
|
+
|
|
37
|
+
if input.downcase == "help"
|
|
38
|
+
show_help
|
|
39
|
+
next
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Handle special commands
|
|
43
|
+
if input.downcase == "clear"
|
|
44
|
+
system("clear") || system("cls")
|
|
45
|
+
next
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Handle history commands
|
|
49
|
+
if input.start_with?("history")
|
|
50
|
+
handle_history_command(input)
|
|
51
|
+
next
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Handle snippet commands
|
|
55
|
+
if input.start_with?("snippet")
|
|
56
|
+
handle_snippet_command(input)
|
|
57
|
+
next
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Handle variable commands
|
|
61
|
+
if input.start_with?("vars")
|
|
62
|
+
handle_variable_command(input)
|
|
63
|
+
next
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Handle benchmark commands
|
|
67
|
+
if input.start_with?("benchmark") || input.start_with?("bench")
|
|
68
|
+
handle_benchmark_command(input)
|
|
69
|
+
next
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Handle cheatsheet commands
|
|
73
|
+
if input.start_with?("cheat")
|
|
74
|
+
handle_cheatsheet_command(input)
|
|
75
|
+
next
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Handle AI help commands
|
|
79
|
+
if input.start_with?("?")
|
|
80
|
+
handle_ai_command(input)
|
|
81
|
+
next
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
execute_code(input)
|
|
85
|
+
@history_manager.add(input)
|
|
86
|
+
|
|
87
|
+
rescue Interrupt
|
|
88
|
+
puts "\n" + Formatter.format_warning("Interrupted. Type 'exit' to quit.")
|
|
89
|
+
rescue EOFError
|
|
90
|
+
puts "\n" + Formatter.format_success("Goodbye!")
|
|
91
|
+
break
|
|
92
|
+
rescue StandardError => e
|
|
93
|
+
handle_error(e)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def read_input
|
|
99
|
+
prompt = "pretty_irb".light_magenta + " >> ".light_magenta
|
|
100
|
+
input = Reline.readline(prompt, true)
|
|
101
|
+
return nil if input.nil?
|
|
102
|
+
|
|
103
|
+
# If the line appears to start a block or otherwise is incomplete,
|
|
104
|
+
# collect additional lines until the code is complete.
|
|
105
|
+
if incomplete_code?(input)
|
|
106
|
+
buffer = input + "\n"
|
|
107
|
+
while (more = Reline.readline("... ".light_magenta, true))
|
|
108
|
+
break if more.nil?
|
|
109
|
+
buffer += more + "\n"
|
|
110
|
+
break unless incomplete_code?(buffer)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
return buffer
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
input
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Heuristic to detect incomplete Ruby code.
|
|
120
|
+
# Returns true if there are unclosed block tokens, brackets, or quotes.
|
|
121
|
+
def incomplete_code?(code)
|
|
122
|
+
return true if code.nil? || code.strip.empty?
|
|
123
|
+
|
|
124
|
+
# Count block starters and 'end'
|
|
125
|
+
opens = 0
|
|
126
|
+
opens += code.scan(/\b(do|def|class|module|if|case|begin|for|while|until|unless)\b/).size
|
|
127
|
+
closes = code.scan(/\bend\b/).size
|
|
128
|
+
return true if opens > closes
|
|
129
|
+
|
|
130
|
+
# Brackets / braces / parentheses
|
|
131
|
+
return true if code.count('(') > code.count(')')
|
|
132
|
+
return true if code.count('[') > code.count(']')
|
|
133
|
+
return true if code.count('{') > code.count('}')
|
|
134
|
+
|
|
135
|
+
# Unclosed quotes (simple heuristic, ignores escaped quotes)
|
|
136
|
+
single_quotes = code.scan(/(?<!\\)'/).size
|
|
137
|
+
double_quotes = code.scan(/(?<!\\)"/).size
|
|
138
|
+
return true if single_quotes.odd? || double_quotes.odd?
|
|
139
|
+
|
|
140
|
+
# Line continuation with trailing backslash
|
|
141
|
+
return true if code.rstrip.end_with?('\\')
|
|
142
|
+
|
|
143
|
+
false
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def execute_code(code)
|
|
147
|
+
# Auto-correct the code
|
|
148
|
+
corrected_code = AutoCorrector.auto_correct_code(code)
|
|
149
|
+
|
|
150
|
+
# Show if code was corrected
|
|
151
|
+
if corrected_code != code
|
|
152
|
+
puts Formatter.format_warning("↻ Auto-corrected: #{corrected_code}")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
begin
|
|
156
|
+
# Evaluate the code
|
|
157
|
+
result = eval(corrected_code, @binding)
|
|
158
|
+
|
|
159
|
+
# Display the result
|
|
160
|
+
unless result.nil?
|
|
161
|
+
puts Formatter.format_output(result)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
rescue NameError => e
|
|
165
|
+
handle_name_error(e)
|
|
166
|
+
rescue NoMethodError => e
|
|
167
|
+
handle_method_error(e)
|
|
168
|
+
rescue SyntaxError => e
|
|
169
|
+
handle_syntax_error(e)
|
|
170
|
+
rescue StandardError => e
|
|
171
|
+
handle_error(e)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def handle_name_error(error)
|
|
176
|
+
puts Formatter.format_error("NameError: #{error.message}")
|
|
177
|
+
suggestions = AutoCorrector.suggest_corrections(error)
|
|
178
|
+
puts suggestions if suggestions && !suggestions.empty?
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def handle_method_error(error)
|
|
182
|
+
puts Formatter.format_error("NoMethodError: #{error.message}")
|
|
183
|
+
suggestions = AutoCorrector.suggest_corrections(error)
|
|
184
|
+
puts suggestions if suggestions && !suggestions.empty?
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def handle_syntax_error(error)
|
|
188
|
+
puts Formatter.format_error("SyntaxError: #{error.message}")
|
|
189
|
+
suggestions = AutoCorrector.suggest_corrections(error)
|
|
190
|
+
puts suggestions if suggestions && !suggestions.empty?
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def handle_error(error)
|
|
194
|
+
puts Formatter.format_error("#{error.class}: #{error.message}")
|
|
195
|
+
if error.backtrace
|
|
196
|
+
puts Formatter.format_warning("Backtrace:")
|
|
197
|
+
error.backtrace.first(5).each do |line|
|
|
198
|
+
puts " #{line}"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def show_help
|
|
204
|
+
help_text = <<~HELP
|
|
205
|
+
#{'=== Pretty IRB Commands ==='.light_magenta}
|
|
206
|
+
|
|
207
|
+
exit, quit Exit the shell
|
|
208
|
+
help Show this help message
|
|
209
|
+
clear Clear the screen
|
|
210
|
+
|
|
211
|
+
#{'=== History Commands ==='.light_magenta}
|
|
212
|
+
|
|
213
|
+
history Show all command history
|
|
214
|
+
history search KEYWORD Search history
|
|
215
|
+
history last N Show last N commands
|
|
216
|
+
history export FILE Export to file
|
|
217
|
+
history clear Clear history
|
|
218
|
+
|
|
219
|
+
#{'=== Snippet Commands ==='.light_magenta}
|
|
220
|
+
|
|
221
|
+
snippet list List all snippets
|
|
222
|
+
snippet save NAME CODE Save code snippet
|
|
223
|
+
snippet load NAME Load and execute snippet
|
|
224
|
+
snippet show NAME Show snippet code
|
|
225
|
+
snippet delete NAME Delete snippet
|
|
226
|
+
snippet search KEYWORD Search snippets
|
|
227
|
+
|
|
228
|
+
#{'=== Variable Commands ==='.light_magenta}
|
|
229
|
+
|
|
230
|
+
vars List all variables
|
|
231
|
+
vars VARNAME Inspect variable
|
|
232
|
+
vars type:TYPE Variables of type
|
|
233
|
+
vars search:KEYWORD Search variables
|
|
234
|
+
vars memory Memory usage
|
|
235
|
+
|
|
236
|
+
#{'=== Benchmark Commands ==='.light_magenta}
|
|
237
|
+
|
|
238
|
+
bench CODE Benchmark code
|
|
239
|
+
bench compare CODE1 vs CODE2 Compare two snippets
|
|
240
|
+
bench memory CODE Profile memory
|
|
241
|
+
|
|
242
|
+
#{'=== Cheatsheet Commands ==='.light_magenta}
|
|
243
|
+
|
|
244
|
+
cheat Show Ruby cheatsheet
|
|
245
|
+
cheat TOPIC Cheatsheet for topic
|
|
246
|
+
(array, hash, string, enumerable, file, regex, date)
|
|
247
|
+
|
|
248
|
+
#{'=== AI Help Commands ==='.light_magenta}
|
|
249
|
+
|
|
250
|
+
?explain(method) Explain a Ruby method
|
|
251
|
+
?example(topic) Get code examples
|
|
252
|
+
?debug(code) Analyze code for issues
|
|
253
|
+
?practices(topic) Learn best practices
|
|
254
|
+
?ref(keyword) Quick reference
|
|
255
|
+
|
|
256
|
+
#{'You can type any valid Ruby code and it will be executed!'.light_green}
|
|
257
|
+
HELP
|
|
258
|
+
puts help_text
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def handle_history_command(input)
|
|
262
|
+
case input.downcase
|
|
263
|
+
when "history"
|
|
264
|
+
puts @history_manager.all
|
|
265
|
+
when /^history\s+search\s+(.+)$/
|
|
266
|
+
keyword = $1.strip
|
|
267
|
+
puts @history_manager.search(keyword)
|
|
268
|
+
when /^history\s+last\s+(\d+)$/
|
|
269
|
+
n = $1.to_i
|
|
270
|
+
puts @history_manager.last(n)
|
|
271
|
+
when /^history\s+export\s+(.+)$/
|
|
272
|
+
filename = $1.strip
|
|
273
|
+
puts @history_manager.export(filename)
|
|
274
|
+
when "history clear"
|
|
275
|
+
puts @history_manager.clear
|
|
276
|
+
else
|
|
277
|
+
puts Formatter.format_warning("Usage: history | history search KEYWORD | history last N | history export FILE | history clear")
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def handle_snippet_command(input)
|
|
282
|
+
case input.downcase
|
|
283
|
+
when "snippet list"
|
|
284
|
+
puts @snippet_manager.list
|
|
285
|
+
when /^snippet\s+save\s+(\w+)\s+(.+)$/
|
|
286
|
+
name = $1
|
|
287
|
+
code = $2
|
|
288
|
+
puts @snippet_manager.save(name, code)
|
|
289
|
+
when /^snippet\s+load\s+(\w+)$/
|
|
290
|
+
name = $1
|
|
291
|
+
code = @snippet_manager.load(name)
|
|
292
|
+
if code
|
|
293
|
+
puts Formatter.format_success("📌 Loaded snippet: #{name}")
|
|
294
|
+
puts code
|
|
295
|
+
execute_code(code)
|
|
296
|
+
else
|
|
297
|
+
puts code
|
|
298
|
+
end
|
|
299
|
+
when /^snippet\s+show\s+(\w+)$/
|
|
300
|
+
name = $1
|
|
301
|
+
puts @snippet_manager.show(name)
|
|
302
|
+
when /^snippet\s+delete\s+(\w+)$/
|
|
303
|
+
name = $1
|
|
304
|
+
puts @snippet_manager.delete(name)
|
|
305
|
+
when /^snippet\s+search\s+(.+)$/
|
|
306
|
+
keyword = $1.strip
|
|
307
|
+
puts @snippet_manager.search(keyword)
|
|
308
|
+
else
|
|
309
|
+
puts Formatter.format_warning("Usage: snippet list | snippet save NAME CODE | snippet load NAME | snippet show NAME | snippet delete NAME | snippet search KEYWORD")
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def handle_variable_command(input)
|
|
314
|
+
case input.downcase
|
|
315
|
+
when "vars"
|
|
316
|
+
puts @variable_inspector.list_variables
|
|
317
|
+
when /^vars\s+(.+)$/
|
|
318
|
+
var_name = $1.strip
|
|
319
|
+
if var_name == "memory"
|
|
320
|
+
puts @variable_inspector.memory_usage
|
|
321
|
+
elsif var_name.start_with?("type:")
|
|
322
|
+
type = var_name.sub("type:", "").strip
|
|
323
|
+
puts @variable_inspector.by_type(type)
|
|
324
|
+
elsif var_name.start_with?("search:")
|
|
325
|
+
keyword = var_name.sub("search:", "").strip
|
|
326
|
+
puts @variable_inspector.search(keyword)
|
|
327
|
+
else
|
|
328
|
+
puts @variable_inspector.inspect_var(var_name)
|
|
329
|
+
end
|
|
330
|
+
else
|
|
331
|
+
puts Formatter.format_warning("Usage: vars | vars VARNAME | vars type:TYPE | vars search:KEYWORD | vars memory")
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def handle_benchmark_command(input)
|
|
336
|
+
case input
|
|
337
|
+
when /^bench(?:mark)?\s+(.+)$/
|
|
338
|
+
code = $1
|
|
339
|
+
puts Benchmarker.benchmark(code)
|
|
340
|
+
when /^bench(?:mark)?\s+compare\s+(.+?)\s+vs\s+(.+)$/
|
|
341
|
+
code1 = $1
|
|
342
|
+
code2 = $2
|
|
343
|
+
puts Benchmarker.compare(code1, code2)
|
|
344
|
+
when /^bench(?:mark)?\s+memory\s+(.+)$/
|
|
345
|
+
code = $1
|
|
346
|
+
puts Benchmarker.profile_memory(code)
|
|
347
|
+
else
|
|
348
|
+
puts Formatter.format_warning("Usage: bench CODE | bench compare CODE1 vs CODE2 | bench memory CODE")
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def handle_cheatsheet_command(input)
|
|
353
|
+
case input.downcase
|
|
354
|
+
when "cheat"
|
|
355
|
+
puts CheatSheet.show
|
|
356
|
+
when /^cheat\s+(\w+)$/
|
|
357
|
+
topic = $1
|
|
358
|
+
puts CheatSheet.show(topic)
|
|
359
|
+
else
|
|
360
|
+
puts Formatter.format_warning("Usage: cheat | cheat TOPIC")
|
|
361
|
+
puts "Topics: array, hash, string, enumerable, file, regex, date"
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def handle_ai_command(input)
|
|
366
|
+
case input
|
|
367
|
+
when /^\?explain\((.*?)\)$/
|
|
368
|
+
method_name = $1.gsub(/'|"/, "")
|
|
369
|
+
puts AIHelper.explain_method(method_name)
|
|
370
|
+
when /^\?example\((.*?)\)$/
|
|
371
|
+
topic = $1.gsub(/'|"/, "")
|
|
372
|
+
puts AIHelper.get_example(topic)
|
|
373
|
+
when /^\?debug\((.*)\)$/
|
|
374
|
+
code_snippet = $1
|
|
375
|
+
puts AIHelper.debug_code(code_snippet)
|
|
376
|
+
when /^\?practices\((.*?)\)$/
|
|
377
|
+
topic = $1.gsub(/'|"/, "")
|
|
378
|
+
puts AIHelper.best_practices(topic)
|
|
379
|
+
when /^\?ref\((.*?)\)$/
|
|
380
|
+
keyword = $1.gsub(/'|"/, "")
|
|
381
|
+
puts AIHelper.quick_reference(keyword)
|
|
382
|
+
else
|
|
383
|
+
puts Formatter.format_warning("❓ Unknown AI command. Type 'help' for available commands.")
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module PrettyIRB
|
|
5
|
+
class SnippetManager
|
|
6
|
+
SNIPPET_DIR = File.expand_path("~/.pretty_irb_snippets")
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
ensure_snippet_dir
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Save a code snippet
|
|
13
|
+
def save(name, code, description = "")
|
|
14
|
+
filename = snippet_path(name)
|
|
15
|
+
|
|
16
|
+
snippet = {
|
|
17
|
+
name: name,
|
|
18
|
+
code: code,
|
|
19
|
+
description: description,
|
|
20
|
+
created_at: Time.now.to_s,
|
|
21
|
+
tags: extract_tags(code)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
File.write(filename, JSON.pretty_generate(snippet))
|
|
25
|
+
"✓ Snippet '#{name}' saved"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Load a snippet
|
|
29
|
+
def load(name)
|
|
30
|
+
filename = snippet_path(name)
|
|
31
|
+
return "❌ Snippet '#{name}' not found" unless File.exist?(filename)
|
|
32
|
+
|
|
33
|
+
snippet = JSON.parse(File.read(filename))
|
|
34
|
+
snippet["code"]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# List all snippets
|
|
38
|
+
def list
|
|
39
|
+
return "📋 No snippets saved yet" unless Dir.exist?(SNIPPET_DIR)
|
|
40
|
+
|
|
41
|
+
snippets = Dir.glob("#{SNIPPET_DIR}/*.json").map do |file|
|
|
42
|
+
data = JSON.parse(File.read(file))
|
|
43
|
+
" • #{data['name']}: #{data['description']}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
return "📋 No snippets saved yet" if snippets.empty?
|
|
47
|
+
|
|
48
|
+
"📋 Saved Snippets:\n#{snippets.join("\n")}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Search snippets by tag or keyword
|
|
52
|
+
def search(keyword)
|
|
53
|
+
return "📋 No snippets saved yet" unless Dir.exist?(SNIPPET_DIR)
|
|
54
|
+
|
|
55
|
+
results = Dir.glob("#{SNIPPET_DIR}/*.json").select do |file|
|
|
56
|
+
data = JSON.parse(File.read(file))
|
|
57
|
+
data["name"].include?(keyword) ||
|
|
58
|
+
data["code"].include?(keyword) ||
|
|
59
|
+
data["tags"].any? { |tag| tag.include?(keyword) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
return "🔍 No matches found" if results.empty?
|
|
63
|
+
|
|
64
|
+
output = "🔍 Found #{results.length} snippet(s):\n\n"
|
|
65
|
+
results.each do |file|
|
|
66
|
+
data = JSON.parse(File.read(file))
|
|
67
|
+
output += "📌 #{data['name']}\n"
|
|
68
|
+
output += " Description: #{data['description']}\n"
|
|
69
|
+
output += " Tags: #{data['tags'].join(', ')}\n\n"
|
|
70
|
+
end
|
|
71
|
+
output
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Delete a snippet
|
|
75
|
+
def delete(name)
|
|
76
|
+
filename = snippet_path(name)
|
|
77
|
+
return "❌ Snippet '#{name}' not found" unless File.exist?(filename)
|
|
78
|
+
|
|
79
|
+
File.delete(filename)
|
|
80
|
+
"✓ Snippet '#{name}' deleted"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Show snippet details
|
|
84
|
+
def show(name)
|
|
85
|
+
filename = snippet_path(name)
|
|
86
|
+
return "❌ Snippet '#{name}' not found" unless File.exist?(filename)
|
|
87
|
+
|
|
88
|
+
snippet = JSON.parse(File.read(filename))
|
|
89
|
+
output = "📌 #{snippet['name']}\n"
|
|
90
|
+
output += "Description: #{snippet['description']}\n"
|
|
91
|
+
output += "Created: #{snippet['created_at']}\n"
|
|
92
|
+
output += "Tags: #{snippet['tags'].join(', ')}\n\n"
|
|
93
|
+
output += "Code:\n#{snippet['code']}\n"
|
|
94
|
+
output
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def ensure_snippet_dir
|
|
100
|
+
FileUtils.mkdir_p(SNIPPET_DIR) unless Dir.exist?(SNIPPET_DIR)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def snippet_path(name)
|
|
104
|
+
File.join(SNIPPET_DIR, "#{name}.json")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def extract_tags(code)
|
|
108
|
+
tags = []
|
|
109
|
+
tags << "class" if code.include?("class ")
|
|
110
|
+
tags << "method" if code.include?("def ")
|
|
111
|
+
tags << "loop" if code.match?(/\.(each|map|select|reduce)/)
|
|
112
|
+
tags << "string" if code.match?(/".*"|'.*'/)
|
|
113
|
+
tags << "array" if code.match?(/\[.*\]/)
|
|
114
|
+
tags << "hash" if code.match?(/\{.*\}/)
|
|
115
|
+
tags << "error-handling" if code.include?("begin")
|
|
116
|
+
tags
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|