pretty_irb 0.1.3 ā 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/configuration.rb +40 -0
- data/lib/pretty_irb/formatter.rb +143 -31
- data/lib/pretty_irb/history_manager.rb +14 -2
- data/lib/pretty_irb/irb_commands.rb +80 -39
- data/lib/pretty_irb/railtie.rb +35 -0
- data/lib/pretty_irb/shell.rb +355 -74
- data/lib/pretty_irb/snippet_manager.rb +1 -0
- data/lib/pretty_irb/ui.rb +88 -0
- data/lib/pretty_irb/version.rb +1 -1
- data/lib/pretty_irb.rb +16 -0
- data/pretty_irb.gemspec +3 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 95ec3de4041e5f89096a2fe2f3a2c34506bde577008b8539d6d5459067d0b360
|
|
4
|
+
data.tar.gz: 2277edc3c5e7913d3537b219e77bc0f0fd85d33c47a259bcc50a57f84f542296
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7257181d6bf2c0a01212c13cc1ddee99ddda9f2b41131e9ae40b93b1979039b090921494dcd566bfc4d01583e831f37f75812729b008d1f88363f4bc213bb40
|
|
7
|
+
data.tar.gz: b3795b0bcd2e5d7a8ddb5a6bed472b92fd43dbad7f34342bc72709b90ebe95852d986341e7726ad99d3ced9a61a73b5bd8021c5a5de05f6b9732781fa37b6d1f
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.4] - 2026-11-11
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Rails 7+ Console Integration (`PrettyIRB::Railtie`)** - Automatically wires `config.console` in `rails c` across Rails 7.0, 7.1, 7.2, and 8.x while preserving `reload!`, `app`, `helper`, `controller`, `Rails.backtrace_cleaner`, and `--sandbox`, plus `DISABLE_PRETTY_IRB=1` opt-out
|
|
12
|
+
- **Configuration API (`PrettyIRB::Configuration`)** - Supports `PrettyIRB.configure` and Rails `config.pretty_irb` toggles (`colorize`, `syntax_highlight`, `auto_correct`, `banner`, `rails_env_prompt`)
|
|
13
|
+
- **Terminal UI Toolkit (`PrettyIRB::UI`)** - ANSI-aware alignment, rounded banner/command cards, environment badges (`dev`, `test`, bold red `PROD`, `SANDBOX`), and column-aligned multiline prompt rails
|
|
14
|
+
- **ActiveRecord & Struct Formatting** - Multi-line attribute formatting for `ActiveRecord::Base` and bounded `.limit(31)` formatting for `ActiveRecord::Relation`
|
|
15
|
+
- **Exception Hints & Error History** - `ExceptionHook` on `IRB::Irb#handle_exception` records runtime errors in `HistoryManager` and provides non-duplicate typo / nil-receiver / syntax suggestions
|
|
16
|
+
|
|
8
17
|
## [0.1.0] - 2025-11-27
|
|
9
18
|
|
|
10
19
|
### Added
|
data/lib/pretty_irb/ai_helper.rb
CHANGED
|
@@ -286,13 +286,13 @@ module PrettyIRB
|
|
|
286
286
|
|
|
287
287
|
def example_loops
|
|
288
288
|
<<~EXAMPLES
|
|
289
|
-
š
|
|
289
|
+
š Loops and Iteration Examples:
|
|
290
290
|
|
|
291
291
|
Using each:
|
|
292
292
|
[1, 2, 3].each { |x| puts x }
|
|
293
293
|
|
|
294
294
|
Using times:
|
|
295
|
-
5.times { |i| puts "Number
|
|
295
|
+
5.times { |i| puts "Number \#{i}" }
|
|
296
296
|
|
|
297
297
|
Using while:
|
|
298
298
|
i = 0
|
|
@@ -707,7 +707,7 @@ module PrettyIRB
|
|
|
707
707
|
|
|
708
708
|
def general_best_practices
|
|
709
709
|
<<~PRACTICES
|
|
710
|
-
ā
General Best Practices:
|
|
710
|
+
ā
General Ruby Best Practices:
|
|
711
711
|
|
|
712
712
|
Code Quality:
|
|
713
713
|
⢠DRY: Don't Repeat Yourself
|
|
@@ -1,76 +1,87 @@
|
|
|
1
|
-
module PrettyIRB
|
|
2
|
-
class AutoCorrector
|
|
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
|
-
|
|
1
|
+
module PrettyIRB
|
|
2
|
+
class AutoCorrector
|
|
3
|
+
COMMON_METHOD_TYPOS = {
|
|
4
|
+
"lenght" => "length",
|
|
5
|
+
"sizze" => "size",
|
|
6
|
+
"empyt?" => "empty?",
|
|
7
|
+
"nil" => "nil?",
|
|
8
|
+
"to_str" => "to_s",
|
|
9
|
+
"to_int" => "to_i"
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
# Suggest corrections for common Ruby mistakes
|
|
14
|
+
def suggest_corrections(error)
|
|
15
|
+
case error
|
|
16
|
+
when NameError
|
|
17
|
+
suggest_name_or_method_error(error)
|
|
18
|
+
when SyntaxError
|
|
19
|
+
suggest_syntax_error(error)
|
|
20
|
+
else
|
|
21
|
+
""
|
|
22
|
+
end
|
|
23
|
+
rescue
|
|
24
|
+
""
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Auto-correct common typos in code
|
|
28
|
+
def auto_correct_code(code)
|
|
29
|
+
fix_method_names(code)
|
|
30
|
+
rescue
|
|
31
|
+
code
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def suggest_name_or_method_error(error)
|
|
37
|
+
hints = []
|
|
38
|
+
|
|
39
|
+
if error.respond_to?(:corrections)
|
|
40
|
+
corrections = begin
|
|
41
|
+
error.corrections
|
|
42
|
+
rescue ArgumentError
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
if corrections.is_a?(Array) && !corrections.empty?
|
|
46
|
+
hints << "Did you mean?: #{corrections.join(', ')}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if error.is_a?(NoMethodError)
|
|
51
|
+
method_name = error.name.to_s
|
|
52
|
+
if (replacement = COMMON_METHOD_TYPOS[method_name])
|
|
53
|
+
hints << "Did you mean `.#{replacement}`?"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
nil_receiver = begin
|
|
57
|
+
error.respond_to?(:receiver) && error.receiver.nil?
|
|
58
|
+
rescue ArgumentError
|
|
59
|
+
false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if nil_receiver && !method_name.empty?
|
|
63
|
+
hints << "Receiver is nil ā check initialization or use safe navigation (`&.#{method_name}`)"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
hints.empty? ? "" : "\nš” #{hints.uniq.join(' Ā· ')}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def suggest_syntax_error(error)
|
|
71
|
+
msg = error.message.to_s
|
|
72
|
+
if msg.include?("unexpected end-of-input") || msg.include?("unterminated")
|
|
73
|
+
"\nš” Check for a missing `end`, closing bracket, or closing quote."
|
|
74
|
+
elsif msg.include?("unexpected `end'") || msg.include?("unexpected end")
|
|
75
|
+
"\nš” Extra `end` detected ā check block/method nesting."
|
|
76
|
+
else
|
|
77
|
+
""
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def fix_method_names(code)
|
|
82
|
+
code.gsub(/\.lenght\b/, ".length")
|
|
83
|
+
.gsub(/\.sizze\b/, ".size")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module PrettyIRB
|
|
2
|
+
# Global and per-session configuration options for PrettyIRB.
|
|
3
|
+
class Configuration
|
|
4
|
+
attr_accessor :colorize, :syntax_highlight, :auto_correct, :banner, :rails_env_prompt
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@colorize = true
|
|
8
|
+
@syntax_highlight = true
|
|
9
|
+
@auto_correct = true
|
|
10
|
+
@banner = true
|
|
11
|
+
@rails_env_prompt = true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Return a duplicate configuration with any non-nil overrides applied.
|
|
15
|
+
def merge(overrides = {})
|
|
16
|
+
copy = dup
|
|
17
|
+
return copy if overrides.nil?
|
|
18
|
+
|
|
19
|
+
hash = overrides.respond_to?(:to_h) ? overrides.to_h : {}
|
|
20
|
+
hash.each do |key, value|
|
|
21
|
+
next if value.nil?
|
|
22
|
+
|
|
23
|
+
setter = "#{key}="
|
|
24
|
+
copy.public_send(setter, value) if copy.respond_to?(setter)
|
|
25
|
+
end
|
|
26
|
+
copy
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_h
|
|
30
|
+
{
|
|
31
|
+
colorize: @colorize,
|
|
32
|
+
syntax_highlight: @syntax_highlight,
|
|
33
|
+
auto_correct: @auto_correct,
|
|
34
|
+
banner: @banner,
|
|
35
|
+
rails_env_prompt: @rails_env_prompt
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
data/lib/pretty_irb/formatter.rb
CHANGED
|
@@ -3,27 +3,33 @@ require "rouge"
|
|
|
3
3
|
|
|
4
4
|
module PrettyIRB
|
|
5
5
|
class Formatter
|
|
6
|
+
INLINE_MAX_WIDTH = 72
|
|
7
|
+
MAX_COLLECTION_ITEMS = 30
|
|
8
|
+
|
|
6
9
|
class << self
|
|
7
|
-
# "=>
|
|
10
|
+
# Muted "=> " indicator so the returned value stands out clearly.
|
|
8
11
|
def format_result(output)
|
|
9
|
-
"=> "
|
|
12
|
+
color("=> ", :light_black) + format_output(output)
|
|
10
13
|
rescue
|
|
11
14
|
"=> #{output}"
|
|
12
15
|
end
|
|
13
16
|
|
|
14
|
-
def format_output(value)
|
|
17
|
+
def format_output(value, indent: 0)
|
|
15
18
|
case value
|
|
16
|
-
when String
|
|
17
|
-
when Integer
|
|
18
|
-
when
|
|
19
|
-
when
|
|
20
|
-
when
|
|
21
|
-
when
|
|
22
|
-
when
|
|
23
|
-
when
|
|
24
|
-
when
|
|
25
|
-
when
|
|
26
|
-
|
|
19
|
+
when String then color(value.inspect, :light_yellow)
|
|
20
|
+
when Integer, Float then color(value.to_s, :light_green)
|
|
21
|
+
when Numeric then color(value.to_s, :light_green)
|
|
22
|
+
when TrueClass then color("true", :cyan)
|
|
23
|
+
when FalseClass then color("false", :cyan)
|
|
24
|
+
when NilClass then color("nil", :light_black)
|
|
25
|
+
when Symbol then color(value.inspect, :light_magenta)
|
|
26
|
+
when Regexp then color(value.inspect, :light_red)
|
|
27
|
+
when Range then color(value.inspect, :light_cyan)
|
|
28
|
+
when Class, Module then color(value.inspect, :light_blue)
|
|
29
|
+
when Time then color(value.inspect, :light_cyan)
|
|
30
|
+
when Array then format_array(value, indent: indent)
|
|
31
|
+
when Hash then format_hash(value, indent: indent)
|
|
32
|
+
else format_object(value, indent: indent)
|
|
27
33
|
end
|
|
28
34
|
rescue
|
|
29
35
|
value.to_s
|
|
@@ -31,6 +37,8 @@ module PrettyIRB
|
|
|
31
37
|
|
|
32
38
|
# Rouge Monokai syntax highlight for Ruby code (used in history / snippets)
|
|
33
39
|
def highlight_code(code)
|
|
40
|
+
return code unless syntax_highlight? && colorize?
|
|
41
|
+
|
|
34
42
|
formatter = Rouge::Formatters::Terminal256.new(Rouge::Themes::Monokai.new)
|
|
35
43
|
lexer = Rouge::Lexers::Ruby.new
|
|
36
44
|
formatter.format(lexer.lex(code))
|
|
@@ -38,52 +46,156 @@ module PrettyIRB
|
|
|
38
46
|
code
|
|
39
47
|
end
|
|
40
48
|
|
|
41
|
-
# "ErrorType: message" ā red type,
|
|
49
|
+
# "ErrorType: message" ā bold red type, crisp light-red detail
|
|
42
50
|
def format_error(type, message = nil)
|
|
43
|
-
if
|
|
44
|
-
type.red.bold + ": ".red + message.light_red
|
|
51
|
+
if colorize?
|
|
52
|
+
message ? type.red.bold + ": ".red + message.light_red : type.red.bold
|
|
45
53
|
else
|
|
46
|
-
type.
|
|
54
|
+
message ? "#{type}: #{message}" : type.to_s
|
|
47
55
|
end
|
|
48
56
|
rescue
|
|
49
57
|
"#{type}: #{message}"
|
|
50
58
|
end
|
|
51
59
|
|
|
52
60
|
def format_success(message)
|
|
53
|
-
message
|
|
61
|
+
color(message, :green)
|
|
54
62
|
rescue
|
|
55
63
|
message
|
|
56
64
|
end
|
|
57
65
|
|
|
58
66
|
def format_warning(message)
|
|
59
|
-
message
|
|
67
|
+
color(message, :yellow)
|
|
60
68
|
rescue
|
|
61
69
|
message
|
|
62
70
|
end
|
|
63
71
|
|
|
64
72
|
private
|
|
65
73
|
|
|
66
|
-
def
|
|
67
|
-
return
|
|
74
|
+
def colorize?
|
|
75
|
+
return true unless defined?(PrettyIRB) && PrettyIRB.respond_to?(:configuration)
|
|
76
|
+
|
|
77
|
+
PrettyIRB.configuration.colorize
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def syntax_highlight?
|
|
81
|
+
return true unless defined?(PrettyIRB) && PrettyIRB.respond_to?(:configuration)
|
|
82
|
+
|
|
83
|
+
PrettyIRB.configuration.syntax_highlight
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def color(text, style)
|
|
87
|
+
colorize? ? text.public_send(style) : text
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def visible_length(text)
|
|
91
|
+
defined?(PrettyIRB::UI) ? PrettyIRB::UI.visible_length(text) : text.gsub(/\e\[[0-9;]*m/, "").length
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def format_object(value, indent: 0)
|
|
95
|
+
return color(value.inspect, :light_cyan) if defined?(Date) && value.is_a?(Date)
|
|
96
|
+
return format_active_record_relation(value, indent: indent) if active_record_relation?(value)
|
|
97
|
+
return format_active_record_model(value, indent: indent) if active_record_model?(value)
|
|
98
|
+
return format_struct(value, indent: indent) if value.is_a?(Struct)
|
|
99
|
+
|
|
100
|
+
color(value.inspect, :white)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def active_record_relation?(value)
|
|
104
|
+
defined?(ActiveRecord::Relation) && value.is_a?(ActiveRecord::Relation)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def active_record_model?(value)
|
|
108
|
+
defined?(ActiveRecord::Base) && value.is_a?(ActiveRecord::Base) && value.respond_to?(:attributes)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def format_active_record_model(record, indent: 0)
|
|
112
|
+
attrs = record.attributes
|
|
113
|
+
klass = record.class.name || "ActiveRecord"
|
|
114
|
+
new_tag = record.respond_to?(:new_record?) && record.new_record? ? color(" (new)", :yellow) : ""
|
|
115
|
+
header = color("#<#{klass}", :light_cyan) + new_tag
|
|
116
|
+
closing = color(">", :light_cyan)
|
|
117
|
+
|
|
118
|
+
return "#{header} #{color('(no attributes)', :light_black)}#{closing}" if attrs.nil? || attrs.empty?
|
|
119
|
+
|
|
120
|
+
pairs = attrs.map { |k, v| color("#{k}: ", :light_black) + format_output(v, indent: indent + 1) }
|
|
121
|
+
join_collection(pairs, "#{header} ", closing, indent: indent, inline_limit: 3, inline_separator: ", ")
|
|
122
|
+
rescue
|
|
123
|
+
color(record.inspect, :white)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def format_active_record_relation(relation, indent: 0)
|
|
127
|
+
loaded = relation.respond_to?(:loaded?) && relation.loaded?
|
|
128
|
+
if loaded
|
|
129
|
+
all_records = relation.to_a
|
|
130
|
+
truncated = all_records.length > MAX_COLLECTION_ITEMS
|
|
131
|
+
shown = all_records.first(MAX_COLLECTION_ITEMS)
|
|
132
|
+
trunc_label = "⦠(#{all_records.length} total)"
|
|
133
|
+
else
|
|
134
|
+
fetched = relation.limit(MAX_COLLECTION_ITEMS + 1).to_a
|
|
135
|
+
truncated = fetched.length > MAX_COLLECTION_ITEMS
|
|
136
|
+
shown = fetched.first(MAX_COLLECTION_ITEMS)
|
|
137
|
+
trunc_label = "⦠(showing first #{MAX_COLLECTION_ITEMS})"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
header = color("#<ActiveRecord::Relation [", :light_cyan)
|
|
141
|
+
closing = color("]>", :light_cyan)
|
|
142
|
+
return "#{header}#{closing}" if shown.empty?
|
|
68
143
|
|
|
69
|
-
items =
|
|
70
|
-
items <<
|
|
71
|
-
|
|
144
|
+
items = shown.map { |rec| format_output(rec, indent: indent + 1) }
|
|
145
|
+
items << color(trunc_label, :light_black) if truncated
|
|
146
|
+
|
|
147
|
+
join_collection(items, header, closing, indent: indent)
|
|
148
|
+
rescue
|
|
149
|
+
color(relation.inspect, :white)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def format_struct(struct_obj, indent: 0)
|
|
153
|
+
name = struct_obj.class.name || "Struct"
|
|
154
|
+
pairs = struct_obj.to_h.map do |k, v|
|
|
155
|
+
color("#{k}: ", :light_black) + format_output(v, indent: indent + 1)
|
|
156
|
+
end
|
|
157
|
+
header = color("#<struct #{name}", :light_cyan)
|
|
158
|
+
closing = color(">", :light_cyan)
|
|
159
|
+
join_collection(pairs, "#{header} ", closing, indent: indent, inline_limit: 4)
|
|
160
|
+
rescue
|
|
161
|
+
color(struct_obj.inspect, :white)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def format_array(arr, indent: 0)
|
|
165
|
+
return color("[]", :light_black) if arr.empty?
|
|
166
|
+
|
|
167
|
+
items = arr.first(MAX_COLLECTION_ITEMS).map { |v| format_output(v, indent: indent + 1) }
|
|
168
|
+
items << color("⦠(#{arr.length} total)", :light_black) if arr.length > MAX_COLLECTION_ITEMS
|
|
169
|
+
|
|
170
|
+
join_collection(items, color("[", :light_black), color("]", :light_black), indent: indent, inline_limit: 6)
|
|
72
171
|
rescue
|
|
73
172
|
arr.inspect
|
|
74
173
|
end
|
|
75
174
|
|
|
76
|
-
def format_hash(hash)
|
|
77
|
-
return "{}"
|
|
175
|
+
def format_hash(hash, indent: 0)
|
|
176
|
+
return color("{}", :light_black) if hash.empty?
|
|
78
177
|
|
|
79
|
-
pairs = hash.first(
|
|
80
|
-
format_output(k) + " => "
|
|
178
|
+
pairs = hash.first(MAX_COLLECTION_ITEMS).map do |k, v|
|
|
179
|
+
format_output(k, indent: indent + 1) + color(" => ", :light_black) + format_output(v, indent: indent + 1)
|
|
81
180
|
end
|
|
82
|
-
pairs << "ā¦
|
|
83
|
-
|
|
181
|
+
pairs << color("⦠(#{hash.length} total)", :light_black) if hash.length > MAX_COLLECTION_ITEMS
|
|
182
|
+
|
|
183
|
+
join_collection(pairs, color("{", :light_black), color("}", :light_black), indent: indent, inline_limit: 4)
|
|
84
184
|
rescue
|
|
85
185
|
hash.inspect
|
|
86
186
|
end
|
|
187
|
+
|
|
188
|
+
def join_collection(entries, open_delim, close_delim, indent:, inline_limit: nil, inline_separator: ", ")
|
|
189
|
+
inline = "#{open_delim}#{entries.join(color(inline_separator, :light_black))}#{close_delim}"
|
|
190
|
+
if inline_limit && entries.length <= inline_limit && !inline.include?("\n") && visible_length(inline) <= INLINE_MAX_WIDTH
|
|
191
|
+
return inline
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
pad_inner = " " * (indent + 1)
|
|
195
|
+
pad_outer = " " * indent
|
|
196
|
+
body = entries.map { |entry| "#{pad_inner}#{entry}" }.join(color(",\n", :light_black))
|
|
197
|
+
"#{open_delim.rstrip}\n#{body}\n#{pad_outer}#{close_delim}"
|
|
198
|
+
end
|
|
87
199
|
end
|
|
88
200
|
end
|
|
89
201
|
end
|
|
@@ -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) }
|
|
@@ -51,8 +58,13 @@ module PrettyIRB
|
|
|
51
58
|
# Export history to file
|
|
52
59
|
def export(filename)
|
|
53
60
|
path = File.expand_path(filename.to_s)
|
|
54
|
-
# Refuse paths that point at system-sensitive locations
|
|
55
|
-
|
|
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
|
|
56
68
|
return "ā Refusing to write to system path: #{path}"
|
|
57
69
|
end
|
|
58
70
|
File.write(path, format_for_export)
|