pretty_irb 0.1.2 → 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/CHANGELOG.md +9 -0
- data/lib/pretty_irb/ai_helper.rb +3 -3
- data/lib/pretty_irb/auto_corrector.rb +87 -76
- data/lib/pretty_irb/benchmarker.rb +48 -45
- data/lib/pretty_irb/configuration.rb +40 -0
- data/lib/pretty_irb/formatter.rb +201 -66
- data/lib/pretty_irb/history_manager.rb +21 -2
- data/lib/pretty_irb/irb_commands.rb +180 -0
- data/lib/pretty_irb/railtie.rb +35 -0
- data/lib/pretty_irb/shell.rb +435 -387
- data/lib/pretty_irb/snippet_manager.rb +9 -0
- data/lib/pretty_irb/ui.rb +88 -0
- data/lib/pretty_irb/version.rb +1 -1
- data/lib/pretty_irb.rb +17 -0
- data/pretty_irb.gemspec +4 -1
- metadata +19 -1
|
@@ -19,6 +19,13 @@ module PrettyIRB
|
|
|
19
19
|
}
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# Attach an error message to the most recent history entry
|
|
23
|
+
def record_error(error_message)
|
|
24
|
+
return if @entries.empty?
|
|
25
|
+
|
|
26
|
+
@entries.last[:error] = error_message
|
|
27
|
+
end
|
|
28
|
+
|
|
22
29
|
# Search history by keyword
|
|
23
30
|
def search(keyword)
|
|
24
31
|
results = @entries.select { |entry| entry[:code].include?(keyword) }
|
|
@@ -50,8 +57,20 @@ module PrettyIRB
|
|
|
50
57
|
|
|
51
58
|
# Export history to file
|
|
52
59
|
def export(filename)
|
|
53
|
-
File.
|
|
54
|
-
|
|
60
|
+
path = File.expand_path(filename.to_s)
|
|
61
|
+
# Refuse paths that point at system-sensitive locations (Unix and Windows)
|
|
62
|
+
normalized = path.tr('\\', '/')
|
|
63
|
+
unix_sensitive = %w[/etc /sys /proc /dev]
|
|
64
|
+
windows_sensitive = %w[C:/Windows C:/Program]
|
|
65
|
+
blocked = unix_sensitive.any? { |p| normalized.start_with?(p) } ||
|
|
66
|
+
windows_sensitive.any? { |p| normalized.start_with?(p) }
|
|
67
|
+
if blocked
|
|
68
|
+
return "❌ Refusing to write to system path: #{path}"
|
|
69
|
+
end
|
|
70
|
+
File.write(path, format_for_export)
|
|
71
|
+
"✓ History exported to #{path}"
|
|
72
|
+
rescue => e
|
|
73
|
+
"❌ Export failed: #{e.message}"
|
|
55
74
|
end
|
|
56
75
|
|
|
57
76
|
private
|
|
@@ -0,0 +1,180 @@
|
|
|
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
|
+
ui = PrettyIRB::UI
|
|
13
|
+
section = ->(title) { puts "\n #{ui.paint(title, :white, :bold)}" }
|
|
14
|
+
row = ->(name, desc) { puts ui.table_row(ui.paint(name, :cyan), ui.paint(desc, :light_black), left_width: 40, indent: 4) }
|
|
15
|
+
|
|
16
|
+
title = "#{ui.paint('pretty_irb', :white, :bold)} #{ui.paint("v#{PrettyIRB::VERSION}", :cyan)}"
|
|
17
|
+
puts
|
|
18
|
+
puts ui.box(
|
|
19
|
+
["#{ui.paint('Command Reference', :white)} #{ui.paint('· type help for built-in IRB commands', :light_black)}"],
|
|
20
|
+
title: title,
|
|
21
|
+
min_width: 64
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
section.("Snippets")
|
|
25
|
+
row.("snippet", "list all saved snippets")
|
|
26
|
+
row.("snippet(:save, 'n', 'code')", "save a snippet")
|
|
27
|
+
row.("snippet(:load, 'name')", "load and run snippet")
|
|
28
|
+
row.("snippet(:show, 'name')", "show snippet code")
|
|
29
|
+
row.("snippet(:delete, 'name')", "delete snippet")
|
|
30
|
+
row.("snippet(:search, 'keyword')", "search snippets")
|
|
31
|
+
|
|
32
|
+
section.("Variables")
|
|
33
|
+
row.("vars", "list all local variables")
|
|
34
|
+
row.("vars('name')", "inspect one variable")
|
|
35
|
+
row.("vars(type: 'String')", "filter by type")
|
|
36
|
+
row.("vars(search: 'kw')", "search by name")
|
|
37
|
+
row.("vars(memory: true)", "estimate memory usage")
|
|
38
|
+
|
|
39
|
+
section.("Benchmark")
|
|
40
|
+
row.("bench('code')", "time code (1,000 iterations)")
|
|
41
|
+
row.("bench('a', vs: 'b')", "compare two snippets")
|
|
42
|
+
row.("bench('code', memory: true)", "GC / object allocation profile")
|
|
43
|
+
row.("bench('code', iterations: 5000)", "custom iteration count")
|
|
44
|
+
|
|
45
|
+
section.("Cheatsheet & Docs")
|
|
46
|
+
row.("cheat / cheat('array')", "Ruby reference (array hash string enumerable file regex date)")
|
|
47
|
+
row.("explain('method')", "explain a Ruby method")
|
|
48
|
+
row.("example('topic')", "code examples for a topic")
|
|
49
|
+
row.("practices('topic')", "best practices for a topic")
|
|
50
|
+
row.("ref('keyword')", "quick reference lookup")
|
|
51
|
+
|
|
52
|
+
puts
|
|
53
|
+
puts " #{ui.rule(64)}"
|
|
54
|
+
built_ins = "history ls show_source show_doc help exit"
|
|
55
|
+
built_ins += " reload! app helper" if defined?(Rails)
|
|
56
|
+
puts " #{ui.paint('IRB built-ins:', :light_black)} #{ui.paint(built_ins, :white)}"
|
|
57
|
+
puts
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# ── snippets ──────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
def snippet(action = :list, name = nil, code = nil, description: "")
|
|
64
|
+
ui = PrettyIRB::UI
|
|
65
|
+
mgr = PrettyIRB::Shell.snippet_manager
|
|
66
|
+
case action.to_sym
|
|
67
|
+
when :list
|
|
68
|
+
puts format_panel(mgr.list)
|
|
69
|
+
when :save
|
|
70
|
+
puts " #{ui.paint(mgr.save(name.to_s, code.to_s, description), :green)}"
|
|
71
|
+
when :load
|
|
72
|
+
src = mgr.load(name.to_s)
|
|
73
|
+
if src.is_a?(String) && !src.start_with?("❌")
|
|
74
|
+
puts " #{ui.paint("loaded:", :light_black)} #{ui.paint(name.to_s, :cyan, :bold)}"
|
|
75
|
+
puts PrettyIRB::Formatter.highlight_code(src)
|
|
76
|
+
eval(src, IRB.CurrentContext.workspace.binding)
|
|
77
|
+
else
|
|
78
|
+
puts " #{ui.paint(src, :light_red)}"
|
|
79
|
+
end
|
|
80
|
+
when :show
|
|
81
|
+
puts PrettyIRB::Formatter.highlight_code(mgr.show(name.to_s))
|
|
82
|
+
when :delete
|
|
83
|
+
puts " #{ui.paint(mgr.delete(name.to_s), :yellow)}"
|
|
84
|
+
when :search
|
|
85
|
+
puts format_panel(mgr.search(name.to_s))
|
|
86
|
+
else
|
|
87
|
+
puts " #{ui.paint('actions: :list :save :load :show :delete :search', :yellow)}"
|
|
88
|
+
end
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# ── variables ─────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
def vars(name = nil, type: nil, search: nil, memory: false)
|
|
95
|
+
inspector = PrettyIRB::VariableInspector.new(IRB.CurrentContext.workspace.binding)
|
|
96
|
+
output = if memory then inspector.memory_usage
|
|
97
|
+
elsif type then inspector.by_type(type.to_s)
|
|
98
|
+
elsif search then inspector.search(search.to_s)
|
|
99
|
+
elsif name then inspector.inspect_var(name.to_s)
|
|
100
|
+
else inspector.list_variables
|
|
101
|
+
end
|
|
102
|
+
puts format_panel(output)
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# ── benchmark ─────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
def bench(code, vs: nil, memory: false, iterations: 1000)
|
|
109
|
+
b = IRB.CurrentContext.workspace.binding
|
|
110
|
+
output = if memory then PrettyIRB::Benchmarker.profile_memory(code.to_s, iterations, b)
|
|
111
|
+
elsif vs then PrettyIRB::Benchmarker.compare(code.to_s, vs.to_s, iterations, b)
|
|
112
|
+
else PrettyIRB::Benchmarker.benchmark(code.to_s, iterations, b)
|
|
113
|
+
end
|
|
114
|
+
puts format_panel(output)
|
|
115
|
+
nil
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# ── cheatsheet ────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
def cheat(topic = nil)
|
|
121
|
+
puts format_panel(PrettyIRB::CheatSheet.show(topic))
|
|
122
|
+
nil
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# ── docs ──────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
def explain(method_name)
|
|
128
|
+
puts format_panel(PrettyIRB::AIHelper.explain_method(method_name.to_s))
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def example(topic)
|
|
133
|
+
puts format_panel(PrettyIRB::AIHelper.get_example(topic.to_s))
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def practices(topic)
|
|
138
|
+
puts format_panel(PrettyIRB::AIHelper.best_practices(topic.to_s))
|
|
139
|
+
nil
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def ref(keyword)
|
|
143
|
+
puts format_panel(PrettyIRB::AIHelper.quick_reference(keyword.to_s))
|
|
144
|
+
nil
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def format_panel(text)
|
|
150
|
+
lines = text.to_s.lines.map(&:rstrip)
|
|
151
|
+
return "" if lines.empty?
|
|
152
|
+
|
|
153
|
+
ui = PrettyIRB::UI
|
|
154
|
+
header = ui.paint(lines.first, :cyan, :bold)
|
|
155
|
+
rest = lines.drop(1).map { |line| format_panel_line(line, ui) }
|
|
156
|
+
(["\n #{header}", " #{ui.rule(56)}"] + rest + [""]).join("\n")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def format_panel_line(line, ui)
|
|
160
|
+
return "" if line.strip.empty?
|
|
161
|
+
return " #{ui.paint(line, :light_red)}" if line.lstrip.start_with?("❌", "✗")
|
|
162
|
+
return " #{ui.paint(line, :green)}" if line.lstrip.start_with?("✓")
|
|
163
|
+
|
|
164
|
+
if (m = line.match(/\A(\s*)(= )(.+)\z/))
|
|
165
|
+
" #{m[1]}#{ui.paint(m[2], :light_black)}#{ui.paint(m[3], :cyan)}"
|
|
166
|
+
elsif (m = line.match(/\A(\s*[•🔸📌]\s+)(.+)\z/))
|
|
167
|
+
" #{ui.paint(m[1], :cyan)}#{m[2]}"
|
|
168
|
+
elsif (m = line.match(/\A([A-Za-z][A-Za-z0-9 &\/-]{1,34}:)\s*\z/))
|
|
169
|
+
" #{ui.paint(m[1], :white, :bold)}"
|
|
170
|
+
elsif (m = line.match(/\A(\s*)([A-Z][A-Za-z0-9 _?\/-]{1,22}:)(\s+)(.+)\z/))
|
|
171
|
+
value_color = m[2] == "Winner:" ? :green : :white
|
|
172
|
+
" #{m[1]}#{ui.paint(m[2], :light_black)}#{m[3]}#{ui.paint(m[4], value_color)}"
|
|
173
|
+
elsif (m = line.match(/\A(\s+.+?)(\s{2,}# .+)\z/))
|
|
174
|
+
" #{m[1]}#{ui.paint(m[2], :light_black)}"
|
|
175
|
+
else
|
|
176
|
+
" #{line}"
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require_relative "../pretty_irb" unless defined?(PrettyIRB::Shell)
|
|
2
|
+
|
|
3
|
+
module PrettyIRB
|
|
4
|
+
# Hooks PrettyIRB into Ruby on Rails' `rails console` lifecycle so
|
|
5
|
+
# `bin/rails c` boots PrettyIRB automatically without per-environment config.
|
|
6
|
+
class Railtie < ::Rails::Railtie
|
|
7
|
+
config.pretty_irb = if defined?(ActiveSupport::OrderedOptions)
|
|
8
|
+
ActiveSupport::OrderedOptions.new
|
|
9
|
+
else
|
|
10
|
+
PrettyIRB::Configuration.new
|
|
11
|
+
end
|
|
12
|
+
config.pretty_irb.colorize = true
|
|
13
|
+
config.pretty_irb.syntax_highlight = true
|
|
14
|
+
config.pretty_irb.auto_correct = true
|
|
15
|
+
config.pretty_irb.banner = true
|
|
16
|
+
config.pretty_irb.rails_env_prompt = true
|
|
17
|
+
|
|
18
|
+
console do |app|
|
|
19
|
+
next if ENV["DISABLE_PRETTY_IRB"] == "1"
|
|
20
|
+
|
|
21
|
+
if app.config.respond_to?(:pretty_irb)
|
|
22
|
+
PrettyIRB.configure do |c|
|
|
23
|
+
opts = app.config.pretty_irb
|
|
24
|
+
c.colorize = opts.colorize unless opts.colorize.nil?
|
|
25
|
+
c.syntax_highlight = opts.syntax_highlight unless opts.syntax_highlight.nil?
|
|
26
|
+
c.auto_correct = opts.auto_correct unless opts.auto_correct.nil?
|
|
27
|
+
c.banner = opts.banner unless opts.banner.nil?
|
|
28
|
+
c.rails_env_prompt = opts.rails_env_prompt unless opts.rails_env_prompt.nil?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
app.config.console ||= PrettyIRB
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|