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.
@@ -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.write(filename, format_for_export)
54
- "✓ History exported to #{filename}"
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