pretty_irb 0.1.2 → 0.1.3
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/lib/pretty_irb/benchmarker.rb +48 -45
- data/lib/pretty_irb/formatter.rb +89 -66
- data/lib/pretty_irb/history_manager.rb +9 -2
- data/lib/pretty_irb/irb_commands.rb +139 -0
- data/lib/pretty_irb/shell.rb +154 -387
- data/lib/pretty_irb/snippet_manager.rb +8 -0
- data/lib/pretty_irb/version.rb +1 -1
- data/lib/pretty_irb.rb +1 -0
- data/pretty_irb.gemspec +2 -1
- metadata +16 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de7427c34f3650136e317c86f5b32d53a24c4a883c4f3c70dc07684653cab46b
|
|
4
|
+
data.tar.gz: 258568ee7bc6cc9d42c20d56f7ad80ca16f0a7c63b3b303f0823f405ac67cfaa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3cc973dea626a8ee900e7fbbf26b62d4a941a00fb0f95b137bad0d388c0989e6cfec92d33a35d707f37ddfaac64c24dbe2b5ab8b31ab2b5c48d6c14324368a2e
|
|
7
|
+
data.tar.gz: 02f2fcd034a86279c301c20497d0fdba10672e528a3df3026c1b6ac173beb13199a0332341ef67f141c92c034bfc23ba5622376f9db61ba8fd2ccca9bd38ac94
|
|
@@ -2,77 +2,79 @@ require "benchmark"
|
|
|
2
2
|
|
|
3
3
|
module PrettyIRB
|
|
4
4
|
class Benchmarker
|
|
5
|
-
# Benchmark a code snippet
|
|
6
|
-
def self.benchmark(code, iterations = 1000)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
eval(code)
|
|
11
|
-
end
|
|
5
|
+
# Benchmark a code snippet, optionally with caller's binding for variable access
|
|
6
|
+
def self.benchmark(code, iterations = 1000, binding = nil)
|
|
7
|
+
time = Benchmark.measure do
|
|
8
|
+
iterations.times do
|
|
9
|
+
binding ? eval(code, binding) : eval(code)
|
|
12
10
|
end
|
|
11
|
+
end
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
total_ms = time.real * 1000
|
|
14
|
+
per_iteration_us = (total_ms * 1000).to_f / iterations
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
end
|
|
16
|
+
format_benchmark_result(code, iterations, total_ms, per_iteration_us)
|
|
17
|
+
rescue => e
|
|
18
|
+
"❌ Benchmark failed: #{e.message}"
|
|
21
19
|
end
|
|
22
20
|
|
|
23
21
|
# Compare two code snippets
|
|
24
|
-
def self.compare(code1, code2, iterations = 1000)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
time2 = Benchmark.measure do
|
|
31
|
-
iterations.times { eval(code2) }
|
|
32
|
-
end
|
|
22
|
+
def self.compare(code1, code2, iterations = 1000, binding = nil)
|
|
23
|
+
time1 = Benchmark.measure do
|
|
24
|
+
iterations.times { binding ? eval(code1, binding) : eval(code1) }
|
|
25
|
+
end
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"❌ Comparison failed: #{e.message}"
|
|
27
|
+
time2 = Benchmark.measure do
|
|
28
|
+
iterations.times { binding ? eval(code2, binding) : eval(code2) }
|
|
37
29
|
end
|
|
30
|
+
|
|
31
|
+
format_comparison(code1, code2, time1, time2, iterations)
|
|
32
|
+
rescue => e
|
|
33
|
+
"❌ Comparison failed: #{e.message}"
|
|
38
34
|
end
|
|
39
35
|
|
|
40
36
|
# Profile memory
|
|
41
|
-
def self.profile_memory(code, iterations = 100)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"❌ Memory profile failed: #{e.message}"
|
|
52
|
-
end
|
|
37
|
+
def self.profile_memory(code, iterations = 100, binding = nil)
|
|
38
|
+
gc_stat_before = GC.stat
|
|
39
|
+
|
|
40
|
+
iterations.times { binding ? eval(code, binding) : eval(code) }
|
|
41
|
+
|
|
42
|
+
gc_stat_after = GC.stat
|
|
43
|
+
|
|
44
|
+
format_memory_profile(gc_stat_before, gc_stat_after, iterations)
|
|
45
|
+
rescue => e
|
|
46
|
+
"❌ Memory profile failed: #{e.message}"
|
|
53
47
|
end
|
|
54
48
|
|
|
55
49
|
private
|
|
56
50
|
|
|
51
|
+
def self.truncate(str, max)
|
|
52
|
+
str.length > max ? str[0, max - 3] + "..." : str
|
|
53
|
+
end
|
|
54
|
+
|
|
57
55
|
def self.format_benchmark_result(code, iterations, total_ms, per_iteration_us)
|
|
58
56
|
output = "⏱️ Benchmark Results:\n\n"
|
|
59
|
-
output += "Code: #{
|
|
57
|
+
output += "Code: #{truncate(code, 50)}\n"
|
|
60
58
|
output += "Iterations: #{iterations}\n\n"
|
|
61
59
|
output += "Total Time: #{total_ms.round(2)} ms\n"
|
|
62
60
|
output += "Per Iteration: #{per_iteration_us.round(4)} µs\n"
|
|
63
|
-
output += "Rate: #{(iterations / (total_ms / 1000)).round(0)} ops/sec\n"
|
|
61
|
+
output += "Rate: #{(iterations / (total_ms / 1000.0)).round(0)} ops/sec\n"
|
|
64
62
|
output
|
|
65
63
|
end
|
|
66
64
|
|
|
67
65
|
def self.format_comparison(code1, code2, time1, time2, iterations)
|
|
68
|
-
ratio = time2.real / time1.real
|
|
69
|
-
faster = ratio < 1
|
|
66
|
+
ratio = time1.real > 0 ? time2.real / time1.real : 1.0
|
|
67
|
+
faster = if ratio < 1
|
|
68
|
+
"Code 2 is #{((1.0 / ratio - 1) * 100).round(1)}% faster"
|
|
69
|
+
else
|
|
70
|
+
"Code 1 is #{((ratio - 1) * 100).round(1)}% faster"
|
|
71
|
+
end
|
|
70
72
|
|
|
71
73
|
output = "⚡ Comparison Results:\n\n"
|
|
72
74
|
output += "Iterations: #{iterations}\n\n"
|
|
73
|
-
output += "Code 1 (#{
|
|
75
|
+
output += "Code 1 (#{truncate(code1, 40)}):\n"
|
|
74
76
|
output += " Time: #{(time1.real * 1000).round(2)} ms\n\n"
|
|
75
|
-
output += "Code 2 (#{
|
|
77
|
+
output += "Code 2 (#{truncate(code2, 40)}):\n"
|
|
76
78
|
output += " Time: #{(time2.real * 1000).round(2)} ms\n\n"
|
|
77
79
|
output += "Winner: #{faster}\n"
|
|
78
80
|
output
|
|
@@ -81,13 +83,14 @@ module PrettyIRB
|
|
|
81
83
|
def self.format_memory_profile(before, after, iterations)
|
|
82
84
|
allocated = after[:total_allocated_objects] - before[:total_allocated_objects]
|
|
83
85
|
freed = after[:total_freed_objects] - before[:total_freed_objects]
|
|
86
|
+
net = allocated - freed
|
|
84
87
|
|
|
85
88
|
output = "💾 Memory Profile:\n\n"
|
|
86
89
|
output += "Iterations: #{iterations}\n\n"
|
|
87
90
|
output += "Objects Allocated: #{allocated}\n"
|
|
88
91
|
output += "Objects Freed: #{freed}\n"
|
|
89
|
-
output += "Net Objects: #{
|
|
90
|
-
output += "Per Iteration: #{(
|
|
92
|
+
output += "Net Objects: #{net}\n"
|
|
93
|
+
output += "Per Iteration: #{(net.to_f / iterations).round(2)} objects\n"
|
|
91
94
|
output
|
|
92
95
|
end
|
|
93
96
|
end
|
data/lib/pretty_irb/formatter.rb
CHANGED
|
@@ -1,66 +1,89 @@
|
|
|
1
|
-
require "colorize"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
when
|
|
18
|
-
|
|
19
|
-
when
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
rescue
|
|
55
|
-
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
1
|
+
require "colorize"
|
|
2
|
+
require "rouge"
|
|
3
|
+
|
|
4
|
+
module PrettyIRB
|
|
5
|
+
class Formatter
|
|
6
|
+
class << self
|
|
7
|
+
# "=> value" — identical to how IRB shows results
|
|
8
|
+
def format_result(output)
|
|
9
|
+
"=> ".white + format_output(output)
|
|
10
|
+
rescue
|
|
11
|
+
"=> #{output}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def format_output(value)
|
|
15
|
+
case value
|
|
16
|
+
when String then value.inspect.light_yellow
|
|
17
|
+
when Integer then value.to_s.light_green
|
|
18
|
+
when Float then value.to_s.light_green
|
|
19
|
+
when Numeric then value.to_s.light_green
|
|
20
|
+
when TrueClass then "true".cyan
|
|
21
|
+
when FalseClass then "false".cyan
|
|
22
|
+
when NilClass then "nil".light_black
|
|
23
|
+
when Symbol then value.inspect.light_magenta
|
|
24
|
+
when Array then format_array(value)
|
|
25
|
+
when Hash then format_hash(value)
|
|
26
|
+
else value.inspect.white
|
|
27
|
+
end
|
|
28
|
+
rescue
|
|
29
|
+
value.to_s
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Rouge Monokai syntax highlight for Ruby code (used in history / snippets)
|
|
33
|
+
def highlight_code(code)
|
|
34
|
+
formatter = Rouge::Formatters::Terminal256.new(Rouge::Themes::Monokai.new)
|
|
35
|
+
lexer = Rouge::Lexers::Ruby.new
|
|
36
|
+
formatter.format(lexer.lex(code))
|
|
37
|
+
rescue
|
|
38
|
+
code
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# "ErrorType: message" — red type, dim message
|
|
42
|
+
def format_error(type, message = nil)
|
|
43
|
+
if message
|
|
44
|
+
type.red.bold + ": ".red + message.light_red
|
|
45
|
+
else
|
|
46
|
+
type.red.bold
|
|
47
|
+
end
|
|
48
|
+
rescue
|
|
49
|
+
"#{type}: #{message}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def format_success(message)
|
|
53
|
+
message.green
|
|
54
|
+
rescue
|
|
55
|
+
message
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def format_warning(message)
|
|
59
|
+
message.yellow
|
|
60
|
+
rescue
|
|
61
|
+
message
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def format_array(arr)
|
|
67
|
+
return "[]".white if arr.empty?
|
|
68
|
+
|
|
69
|
+
items = arr.first(30).map { |v| format_output(v) }
|
|
70
|
+
items << "… (#{arr.length} total)".light_black if arr.length > 30
|
|
71
|
+
"[".white + items.join(", ".white) + "]".white
|
|
72
|
+
rescue
|
|
73
|
+
arr.inspect
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def format_hash(hash)
|
|
77
|
+
return "{}".white if hash.empty?
|
|
78
|
+
|
|
79
|
+
pairs = hash.first(30).map do |k, v|
|
|
80
|
+
format_output(k) + " => ".white + format_output(v)
|
|
81
|
+
end
|
|
82
|
+
pairs << "… (#{hash.length} total)".light_black if hash.length > 30
|
|
83
|
+
"{".white + pairs.join(", ".white) + "}".white
|
|
84
|
+
rescue
|
|
85
|
+
hash.inspect
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -50,8 +50,15 @@ module PrettyIRB
|
|
|
50
50
|
|
|
51
51
|
# Export history to file
|
|
52
52
|
def export(filename)
|
|
53
|
-
File.
|
|
54
|
-
|
|
53
|
+
path = File.expand_path(filename.to_s)
|
|
54
|
+
# Refuse paths that point at system-sensitive locations
|
|
55
|
+
if path.start_with?("/etc", "/sys", "/proc", "/dev")
|
|
56
|
+
return "❌ Refusing to write to system path: #{path}"
|
|
57
|
+
end
|
|
58
|
+
File.write(path, format_for_export)
|
|
59
|
+
"✓ History exported to #{path}"
|
|
60
|
+
rescue => e
|
|
61
|
+
"❌ Export failed: #{e.message}"
|
|
55
62
|
end
|
|
56
63
|
|
|
57
64
|
private
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
module PrettyIRB
|
|
2
|
+
# Methods mixed into IRB's main object at startup.
|
|
3
|
+
# Names intentionally avoid IRB's own built-in commands
|
|
4
|
+
# (history, hist, help, ls, exit, quit, …) because IRB intercepts
|
|
5
|
+
# those at the statement level before method dispatch.
|
|
6
|
+
module IRBCommands
|
|
7
|
+
|
|
8
|
+
# ── commands ──────────────────────────────────────────────────────────
|
|
9
|
+
# Show all pretty_irb extras (use IRB's own `help` for core IRB commands)
|
|
10
|
+
|
|
11
|
+
def commands
|
|
12
|
+
section = ->(t) { puts "\n #{t}".cyan }
|
|
13
|
+
row = ->(n, d) { puts " #{"%-40s" % n}#{d.light_black}" }
|
|
14
|
+
|
|
15
|
+
puts "\n pretty_irb v#{PrettyIRB::VERSION}".cyan + \
|
|
16
|
+
" · type help for core IRB commands".light_black
|
|
17
|
+
puts " " + ("─" * 60).light_black
|
|
18
|
+
|
|
19
|
+
section.("Snippets")
|
|
20
|
+
row.("snippet", "list all saved snippets")
|
|
21
|
+
row.("snippet(:save, 'n', 'code')", "save a snippet")
|
|
22
|
+
row.("snippet(:load, 'name')", "load and run snippet")
|
|
23
|
+
row.("snippet(:show, 'name')", "show snippet code")
|
|
24
|
+
row.("snippet(:delete, 'name')", "delete snippet")
|
|
25
|
+
row.("snippet(:search, 'keyword')", "search snippets")
|
|
26
|
+
|
|
27
|
+
section.("Variables")
|
|
28
|
+
row.("vars", "list all local variables")
|
|
29
|
+
row.("vars('name')", "inspect one variable")
|
|
30
|
+
row.("vars(type: 'String')", "filter by type")
|
|
31
|
+
row.("vars(search: 'kw')", "search by name")
|
|
32
|
+
row.("vars(memory: true)", "estimate memory usage")
|
|
33
|
+
|
|
34
|
+
section.("Benchmark")
|
|
35
|
+
row.("bench('code')", "time code (1 000 iterations)")
|
|
36
|
+
row.("bench('a', vs: 'b')", "compare two snippets")
|
|
37
|
+
row.("bench('code', memory: true)", "GC / object allocation profile")
|
|
38
|
+
row.("bench('code', iterations: 5000)", "custom iteration count")
|
|
39
|
+
|
|
40
|
+
section.("Cheatsheet")
|
|
41
|
+
row.("cheat", "general Ruby reference")
|
|
42
|
+
row.("cheat('array')", "topic: array hash string enumerable file regex date")
|
|
43
|
+
|
|
44
|
+
section.("Docs")
|
|
45
|
+
row.("explain('method')", "explain a Ruby method")
|
|
46
|
+
row.("example('topic')", "code examples for a topic")
|
|
47
|
+
row.("practices('topic')", "best practices for a topic")
|
|
48
|
+
row.("ref('keyword')", "quick reference lookup")
|
|
49
|
+
|
|
50
|
+
puts "\n tip: IRB built-ins — ".light_black +
|
|
51
|
+
"history ls show_source show_doc help exit".white
|
|
52
|
+
puts
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# ── snippets ──────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
def snippet(action = :list, name = nil, code = nil, description: "")
|
|
59
|
+
mgr = PrettyIRB::Shell.snippet_manager
|
|
60
|
+
case action.to_sym
|
|
61
|
+
when :list
|
|
62
|
+
puts mgr.list
|
|
63
|
+
when :save
|
|
64
|
+
puts mgr.save(name.to_s, code.to_s, description)
|
|
65
|
+
when :load
|
|
66
|
+
src = mgr.load(name.to_s)
|
|
67
|
+
if src.is_a?(String) && !src.start_with?("❌")
|
|
68
|
+
puts " loaded: #{name}".cyan
|
|
69
|
+
puts PrettyIRB::Formatter.highlight_code(src)
|
|
70
|
+
eval(src, IRB.CurrentContext.workspace.binding)
|
|
71
|
+
else
|
|
72
|
+
puts src
|
|
73
|
+
end
|
|
74
|
+
when :show
|
|
75
|
+
puts PrettyIRB::Formatter.highlight_code(mgr.show(name.to_s))
|
|
76
|
+
when :delete
|
|
77
|
+
puts mgr.delete(name.to_s)
|
|
78
|
+
when :search
|
|
79
|
+
puts mgr.search(name.to_s)
|
|
80
|
+
else
|
|
81
|
+
puts " actions: :list :save :load :show :delete :search".yellow
|
|
82
|
+
end
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# ── variables ─────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
def vars(name = nil, type: nil, search: nil, memory: false)
|
|
89
|
+
inspector = PrettyIRB::VariableInspector.new(IRB.CurrentContext.workspace.binding)
|
|
90
|
+
if memory then puts inspector.memory_usage
|
|
91
|
+
elsif type then puts inspector.by_type(type.to_s)
|
|
92
|
+
elsif search then puts inspector.search(search.to_s)
|
|
93
|
+
elsif name then puts inspector.inspect_var(name.to_s)
|
|
94
|
+
else puts inspector.list_variables
|
|
95
|
+
end
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# ── benchmark ─────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
def bench(code, vs: nil, memory: false, iterations: 1000)
|
|
102
|
+
b = IRB.CurrentContext.workspace.binding
|
|
103
|
+
if memory then puts PrettyIRB::Benchmarker.profile_memory(code.to_s, iterations, b)
|
|
104
|
+
elsif vs then puts PrettyIRB::Benchmarker.compare(code.to_s, vs.to_s, iterations, b)
|
|
105
|
+
else puts PrettyIRB::Benchmarker.benchmark(code.to_s, iterations, b)
|
|
106
|
+
end
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# ── cheatsheet ────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
def cheat(topic = nil)
|
|
113
|
+
puts PrettyIRB::CheatSheet.show(topic)
|
|
114
|
+
nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# ── docs ──────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
def explain(method_name)
|
|
120
|
+
puts PrettyIRB::AIHelper.explain_method(method_name.to_s)
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def example(topic)
|
|
125
|
+
puts PrettyIRB::AIHelper.get_example(topic.to_s)
|
|
126
|
+
nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def practices(topic)
|
|
130
|
+
puts PrettyIRB::AIHelper.best_practices(topic.to_s)
|
|
131
|
+
nil
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def ref(keyword)
|
|
135
|
+
puts PrettyIRB::AIHelper.quick_reference(keyword.to_s)
|
|
136
|
+
nil
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
data/lib/pretty_irb/shell.rb
CHANGED
|
@@ -1,387 +1,154 @@
|
|
|
1
|
-
require "irb"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
|
1
|
+
require "irb"
|
|
2
|
+
|
|
3
|
+
module PrettyIRB
|
|
4
|
+
class Shell
|
|
5
|
+
BANNER = <<~BANNER
|
|
6
|
+
╭─────────────────────────────────────────────────────────────╮
|
|
7
|
+
│ pretty_irb v#{PrettyIRB::VERSION} · Enhanced Interactive Ruby Shell │
|
|
8
|
+
│ type commands to list extras · exit / Ctrl+D to quit │
|
|
9
|
+
╰─────────────────────────────────────────────────────────────╯
|
|
10
|
+
BANNER
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
attr_accessor :history_manager, :snippet_manager
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(config = {})
|
|
17
|
+
@config = config
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def run
|
|
21
|
+
STDOUT.sync = true
|
|
22
|
+
puts BANNER.cyan
|
|
23
|
+
|
|
24
|
+
Shell.history_manager = HistoryManager.new
|
|
25
|
+
Shell.snippet_manager = SnippetManager.new
|
|
26
|
+
|
|
27
|
+
# Step 1: patch IRB classes before anything else
|
|
28
|
+
install_output_hook
|
|
29
|
+
install_eval_hook
|
|
30
|
+
|
|
31
|
+
# Step 2: IRB.setup populates IRB.conf (and resets IRB_RC to nil).
|
|
32
|
+
# Must happen before we configure anything.
|
|
33
|
+
IRB.setup(nil)
|
|
34
|
+
|
|
35
|
+
# Step 3: detect the terminal environment and tune input mode.
|
|
36
|
+
# Must happen AFTER setup (so USE_MULTILINE can be overridden)
|
|
37
|
+
# but BEFORE IRB::Irb.new reads those values.
|
|
38
|
+
tune_input_mode
|
|
39
|
+
|
|
40
|
+
# Step 4: prompt — IRB.conf[:PROMPT] now exists.
|
|
41
|
+
configure_prompt
|
|
42
|
+
|
|
43
|
+
# Step 5: IRB_RC — fires inside irb.run, after IRB::Irb.new but before
|
|
44
|
+
# the first prompt. Safe place for ctx-level settings.
|
|
45
|
+
prev_rc = IRB.conf[:IRB_RC]
|
|
46
|
+
IRB.conf[:IRB_RC] = proc do |ctx|
|
|
47
|
+
prev_rc.call(ctx) if prev_rc.respond_to?(:call)
|
|
48
|
+
ctx.prompt_mode = :PRETTY_IRB
|
|
49
|
+
ctx.auto_indent_mode = true
|
|
50
|
+
ctx.workspace.main.extend(PrettyIRB::IRBCommands)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Step 6: start the REPL directly to avoid a second IRB.setup call.
|
|
54
|
+
irb = IRB::Irb.new
|
|
55
|
+
IRB.conf[:MAIN_CONTEXT] = irb.context
|
|
56
|
+
irb.run(IRB.conf)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# ── Terminal / input detection ────────────────────────────────────────────
|
|
62
|
+
# On Windows, Reline uses a Win32 API console handler. In "legacy
|
|
63
|
+
# console" mode (old conhost.exe — PowerShell or cmd.exe without
|
|
64
|
+
# Windows Terminal) Alt+key sequences are read in a way that blocks the
|
|
65
|
+
# input loop, freezing the entire REPL.
|
|
66
|
+
#
|
|
67
|
+
# We detect this and switch to single-line (non-Reline) input, which
|
|
68
|
+
# avoids the freeze while keeping colour, our custom prompt, and all
|
|
69
|
+
# pretty_irb commands. Full features (auto-indent, │ continuation, tab
|
|
70
|
+
# completion) work when the user opens the same shell inside Windows
|
|
71
|
+
# Terminal.
|
|
72
|
+
|
|
73
|
+
def tune_input_mode
|
|
74
|
+
return unless Gem.win_platform?
|
|
75
|
+
|
|
76
|
+
legacy = begin
|
|
77
|
+
Reline::IOGate.instance_variable_get(:@legacy_console)
|
|
78
|
+
rescue
|
|
79
|
+
false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if legacy
|
|
83
|
+
IRB.conf[:USE_MULTILINE] = false # avoid Reline Windows hang
|
|
84
|
+
IRB.conf[:USE_AUTOCOMPLETE] = false
|
|
85
|
+
puts (
|
|
86
|
+
" legacy console detected — Alt+key sequences freeze Reline.\n" \
|
|
87
|
+
" Running in single-line mode. Open Windows Terminal for\n" \
|
|
88
|
+
" full auto-indent and tab-complete support."
|
|
89
|
+
).light_black
|
|
90
|
+
end
|
|
91
|
+
rescue
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# ── Prompt ────────────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
def configure_prompt
|
|
98
|
+
IRB.conf[:PROMPT][:PRETTY_IRB] = {
|
|
99
|
+
PROMPT_I: "[%03n]".light_black + "❯ ".cyan,
|
|
100
|
+
PROMPT_N: "[%03n]".light_black + "│ ".light_cyan,
|
|
101
|
+
PROMPT_S: "[%03n]".light_black + "│ ".light_cyan,
|
|
102
|
+
PROMPT_C: "[%03n]".light_black + "│ ".light_cyan,
|
|
103
|
+
RETURN: "=> %s\n"
|
|
104
|
+
}
|
|
105
|
+
IRB.conf[:PROMPT_MODE] = :PRETTY_IRB
|
|
106
|
+
IRB.conf[:SAVE_HISTORY] = 1000
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# ── Output hook ───────────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
def install_output_hook
|
|
112
|
+
IRB::Irb.prepend(Module.new do
|
|
113
|
+
def output_value(omit = false)
|
|
114
|
+
puts PrettyIRB::Formatter.format_result(@context.last_value)
|
|
115
|
+
end
|
|
116
|
+
end)
|
|
117
|
+
rescue => e
|
|
118
|
+
warn " pretty_irb: output hook skipped (#{e.message})".light_black
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# ── Eval hook ─────────────────────────────────────────────────────────────
|
|
122
|
+
# IRB >= 1.4 passes IRB::Statement objects, not plain Strings.
|
|
123
|
+
# Extract the raw code string for our features; always forward the
|
|
124
|
+
# original object unchanged so IRB's own command dispatch still works.
|
|
125
|
+
|
|
126
|
+
def install_eval_hook
|
|
127
|
+
IRB::Context.prepend(Module.new do
|
|
128
|
+
def evaluate(code, line_no, *args, **kwargs, &block)
|
|
129
|
+
raw = code.is_a?(String) ? code
|
|
130
|
+
: code.respond_to?(:code) ? code.code.to_s
|
|
131
|
+
: nil
|
|
132
|
+
|
|
133
|
+
if raw
|
|
134
|
+
corrected = PrettyIRB::AutoCorrector.auto_correct_code(raw)
|
|
135
|
+
if corrected.strip != raw.strip
|
|
136
|
+
warn " ↻ auto-corrected: #{corrected.strip}".yellow
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
result = begin
|
|
141
|
+
super(code, line_no, *args, **kwargs, &block)
|
|
142
|
+
rescue ArgumentError
|
|
143
|
+
super(code, line_no)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
PrettyIRB::Shell.history_manager&.add(raw.strip) if raw && !raw.strip.empty?
|
|
147
|
+
result
|
|
148
|
+
end
|
|
149
|
+
end)
|
|
150
|
+
rescue => e
|
|
151
|
+
warn " pretty_irb: eval hook skipped (#{e.message})".light_black
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -11,6 +11,7 @@ module PrettyIRB
|
|
|
11
11
|
|
|
12
12
|
# Save a code snippet
|
|
13
13
|
def save(name, code, description = "")
|
|
14
|
+
return "❌ Invalid snippet name. Use only letters, numbers, underscores, and hyphens." unless valid_name?(name)
|
|
14
15
|
filename = snippet_path(name)
|
|
15
16
|
|
|
16
17
|
snippet = {
|
|
@@ -27,6 +28,7 @@ module PrettyIRB
|
|
|
27
28
|
|
|
28
29
|
# Load a snippet
|
|
29
30
|
def load(name)
|
|
31
|
+
return "❌ Invalid snippet name." unless valid_name?(name)
|
|
30
32
|
filename = snippet_path(name)
|
|
31
33
|
return "❌ Snippet '#{name}' not found" unless File.exist?(filename)
|
|
32
34
|
|
|
@@ -73,6 +75,7 @@ module PrettyIRB
|
|
|
73
75
|
|
|
74
76
|
# Delete a snippet
|
|
75
77
|
def delete(name)
|
|
78
|
+
return "❌ Invalid snippet name." unless valid_name?(name)
|
|
76
79
|
filename = snippet_path(name)
|
|
77
80
|
return "❌ Snippet '#{name}' not found" unless File.exist?(filename)
|
|
78
81
|
|
|
@@ -82,6 +85,7 @@ module PrettyIRB
|
|
|
82
85
|
|
|
83
86
|
# Show snippet details
|
|
84
87
|
def show(name)
|
|
88
|
+
return "❌ Invalid snippet name." unless valid_name?(name)
|
|
85
89
|
filename = snippet_path(name)
|
|
86
90
|
return "❌ Snippet '#{name}' not found" unless File.exist?(filename)
|
|
87
91
|
|
|
@@ -100,6 +104,10 @@ module PrettyIRB
|
|
|
100
104
|
FileUtils.mkdir_p(SNIPPET_DIR) unless Dir.exist?(SNIPPET_DIR)
|
|
101
105
|
end
|
|
102
106
|
|
|
107
|
+
def valid_name?(name)
|
|
108
|
+
name.to_s.match?(/\A[a-zA-Z0-9_-]+\z/)
|
|
109
|
+
end
|
|
110
|
+
|
|
103
111
|
def snippet_path(name)
|
|
104
112
|
File.join(SNIPPET_DIR, "#{name}.json")
|
|
105
113
|
end
|
data/lib/pretty_irb/version.rb
CHANGED
data/lib/pretty_irb.rb
CHANGED
|
@@ -13,6 +13,7 @@ require_relative "pretty_irb/snippet_manager"
|
|
|
13
13
|
require_relative "pretty_irb/variable_inspector"
|
|
14
14
|
require_relative "pretty_irb/benchmarker"
|
|
15
15
|
require_relative "pretty_irb/cheat_sheet"
|
|
16
|
+
require_relative "pretty_irb/irb_commands"
|
|
16
17
|
require_relative "pretty_irb/shell"
|
|
17
18
|
|
|
18
19
|
module PrettyIRB
|
data/pretty_irb.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Gem::Specification.new do |spec|
|
|
2
2
|
spec.name = "pretty_irb"
|
|
3
|
-
spec.version = "0.1.
|
|
3
|
+
spec.version = "0.1.3"
|
|
4
4
|
spec.authors = ["Jayesh"]
|
|
5
5
|
spec.email = ["jayesh@example.com"]
|
|
6
6
|
spec.summary = "A pretty IRB replacement with auto-correct and beautiful fonts"
|
|
@@ -37,6 +37,7 @@ Gem::Specification.new do |spec|
|
|
|
37
37
|
# Allow any version >= 1.5 but < 3.0 to avoid conflicts with older constraints.
|
|
38
38
|
spec.add_dependency "did_you_mean", ">= 1.5", "< 3.0" # Auto-correct suggestions
|
|
39
39
|
spec.add_dependency "reline", "~> 0.3" # Enhanced readline
|
|
40
|
+
spec.add_dependency "benchmark" # Performance measurement
|
|
40
41
|
|
|
41
42
|
# Development dependencies
|
|
42
43
|
spec.add_development_dependency "bundler", "~> 2.0"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pretty_irb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jayesh
|
|
@@ -85,6 +85,20 @@ dependencies:
|
|
|
85
85
|
- - "~>"
|
|
86
86
|
- !ruby/object:Gem::Version
|
|
87
87
|
version: '0.3'
|
|
88
|
+
- !ruby/object:Gem::Dependency
|
|
89
|
+
name: benchmark
|
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - ">="
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '0'
|
|
95
|
+
type: :runtime
|
|
96
|
+
prerelease: false
|
|
97
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
88
102
|
- !ruby/object:Gem::Dependency
|
|
89
103
|
name: bundler
|
|
90
104
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -173,6 +187,7 @@ files:
|
|
|
173
187
|
- lib/pretty_irb/cheat_sheet.rb
|
|
174
188
|
- lib/pretty_irb/formatter.rb
|
|
175
189
|
- lib/pretty_irb/history_manager.rb
|
|
190
|
+
- lib/pretty_irb/irb_commands.rb
|
|
176
191
|
- lib/pretty_irb/shell.rb
|
|
177
192
|
- lib/pretty_irb/snippet_manager.rb
|
|
178
193
|
- lib/pretty_irb/variable_inspector.rb
|