pry-byetypo 1.0.2 → 1.2.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 +4 -4
- data/Gemfile.lock +3 -3
- data/README.md +66 -25
- data/lib/pry-byetypo/constants/errors.rb +7 -0
- data/lib/pry-byetypo/exceptions/active_record/base.rb +2 -6
- data/lib/pry-byetypo/exceptions/active_record/uninitialized_constant.rb +23 -0
- data/lib/pry-byetypo/exceptions/exceptions_base.rb +15 -6
- data/lib/pry-byetypo/exceptions/name_error/handler.rb +34 -0
- data/lib/pry-byetypo/exceptions/name_error/undefined_variable.rb +32 -0
- data/lib/pry-byetypo/exceptions_handler.rb +6 -13
- data/lib/pry-byetypo/session/clear_history.rb +21 -0
- data/lib/pry-byetypo/session/populate_history.rb +57 -0
- data/lib/pry-byetypo/setup/application_dictionary.rb +15 -53
- data/lib/pry-byetypo/setup/dictionary/active_record.rb +81 -0
- data/lib/pry-byetypo/setup/store.rb +9 -0
- data/lib/pry-byetypo/version.rb +1 -1
- data/lib/pry-byetypo.rb +13 -4
- data/pry-byetypo.gemspec +4 -4
- metadata +27 -21
- data/lib/pry-byetypo/exceptions/name_error.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd76efc826578597fe9cecb0ec6357a275d8cb8c37aaa1e238681f0235287027
|
4
|
+
data.tar.gz: 11eeb72895ab4150815e61f932a7aa8d13dc2ae6d6b3564c81d3ff9624c8ec89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f55811aaaeeeb06521f50108d8fab819b00c461b2fd635519ad3559baf409ee256e241d5315a39ec2e238aaae6e330f2d84bcd0cfbc1b3b9f04e9b26d8b9d39
|
7
|
+
data.tar.gz: 954ecd0a20d6fb34df1592cf36f8c2dd5d893d21f0060a7ccba7a4769ead0800917d6ee08aa8c294b8937e8eaa450c77fe7ac3704f4da3e2ea4466257e306552
|
data/Gemfile.lock
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pry-byetypo (1.0
|
4
|
+
pry-byetypo (1.2.0)
|
5
5
|
colorize (~> 1.1.0)
|
6
6
|
pry (>= 0.13, < 0.15)
|
7
|
-
rails (~> 7.0)
|
8
7
|
|
9
8
|
GEM
|
10
9
|
remote: https://rubygems.org/
|
@@ -146,7 +145,7 @@ GEM
|
|
146
145
|
psych (5.1.2)
|
147
146
|
stringio
|
148
147
|
racc (1.7.3)
|
149
|
-
rack (3.0.
|
148
|
+
rack (3.0.9)
|
150
149
|
rack-session (2.0.0)
|
151
150
|
rack (>= 3.0.0)
|
152
151
|
rack-test (2.1.0)
|
@@ -253,6 +252,7 @@ PLATFORMS
|
|
253
252
|
DEPENDENCIES
|
254
253
|
byebug
|
255
254
|
pry-byetypo!
|
255
|
+
rails (~> 7.0)
|
256
256
|
rake (~> 13.0)
|
257
257
|
rspec (~> 3.0)
|
258
258
|
standard (~> 1.3)
|
data/README.md
CHANGED
@@ -1,29 +1,32 @@
|
|
1
|
-
# PRY-BYETYPO
|
1
|
+
# PRY-BYETYPO 👋
|
2
2
|
|
3
3
|
Autocorrects typos in your Pry console.
|
4
4
|
|
5
|
-
This small Pry plugin captures exceptions that
|
5
|
+
This small Pry plugin captures exceptions that may arise from typos and deduces the correct command based on your database information and session history.
|
6
6
|
|
7
7
|
#### Before
|
8
8
|
|
9
9
|
```ruby
|
10
|
-
[1] pry(main)>
|
11
|
-
|
12
|
-
|
10
|
+
[1] pry(main)> result = 1
|
11
|
+
=> 1
|
12
|
+
[2] pry(main)> resilt
|
13
|
+
NameError: undefined local variable or method `resilt' for main:Object
|
14
|
+
from (pry):2:in `__pry__'
|
13
15
|
```
|
14
16
|
|
15
17
|
#### After
|
16
18
|
|
17
19
|
```ruby
|
18
|
-
[1] pry(main)>
|
19
|
-
|
20
|
-
|
21
|
-
2024-01-
|
22
|
-
|
20
|
+
[1] pry(main)> result = 1
|
21
|
+
=> 1
|
22
|
+
[2] pry(main)> resilt
|
23
|
+
E, [2024-01-31T17:11:16.344161 #3739] ERROR -- : undefined local variable or method `resilt' for main:Object
|
24
|
+
I, [2024-01-31T17:11:16.344503 #3739] INFO -- : Running: result
|
25
|
+
=> 1
|
23
26
|
```
|
24
27
|
|
25
28
|
> [!NOTE]
|
26
|
-
>
|
29
|
+
> Currently, this plugin is not truly ORM-agnostic; to fully benefit from it, ActiveRecord is required.
|
27
30
|
|
28
31
|
## Installation
|
29
32
|
|
@@ -46,24 +49,59 @@ gem install pry-byetypo
|
|
46
49
|
2. Start your daily work.
|
47
50
|
3. Let `pry-byetypo` remove frictions. 🚀
|
48
51
|
|
49
|
-
##
|
50
|
-
|
51
|
-
### 1. Byetypo dictionary
|
52
|
+
## Byetypo dictionary
|
52
53
|
|
53
|
-
When you open a new Pry console, the gem will generate a `byetypo_dictionary.pstore` file containing
|
54
|
+
When you open a new Pry console, the gem will generate a `byetypo_dictionary.pstore` file containing four pieces of information:
|
54
55
|
|
55
56
|
- A list of the ActiveRecord models in your application (e.g. `User`, `Account`).
|
56
57
|
- A list of the ActiveRecord associations in your application (e.g. `user`, `users`, `account`, `accounts`).
|
58
|
+
- Unique identifiers for each active Pry instance, populated with the variable history of the current session. (e.g. `result = 1` will store `result`)
|
57
59
|
- A timestamp representing the last time the `byetypo_dictionary` was updated. (by default updated every week).
|
58
60
|
|
59
61
|
This file is generated at the root of your application by default. If you want to update its location, you can configure the path by adding a `BYETYPO_STORE_PATH` entry in your `.env` file.
|
60
62
|
|
61
|
-
|
63
|
+
## Captured exceptions
|
64
|
+
|
65
|
+
### NameError - undefined local variable or method
|
66
|
+
|
67
|
+
This error occurs when you mispelled a variable in your REPL. The gem will catch that exception and will try find the closest matches. If so, it will run the command with the (potential) corrected variable.
|
68
|
+
|
69
|
+
##### Before
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
[1] pry(main)> result = 1
|
73
|
+
=> 1
|
74
|
+
[2] pry(main)> resilt
|
75
|
+
NameError: undefined local variable or method `resilt' for main:Object
|
76
|
+
from (pry):2:in `__pry__'
|
77
|
+
```
|
78
|
+
|
79
|
+
##### After
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
[1] pry(main)> result = 1
|
83
|
+
=> 1
|
84
|
+
[2] pry(main)> resilt
|
85
|
+
E, [2024-01-31T17:11:16.344161 #3739] ERROR -- : undefined local variable or method `resilt' for main:Object
|
86
|
+
I, [2024-01-31T17:11:16.344503 #3739] INFO -- : Running: result
|
87
|
+
=> 1
|
88
|
+
```
|
62
89
|
|
63
|
-
|
90
|
+
### NameError - uninitialized constant
|
64
91
|
|
65
92
|
This error occurs when you mispelled a model in your REPL. The gem will catch that exception and will try find the closest matches. If so, it will run the command with the (potential) corrected model.
|
66
93
|
|
94
|
+
##### Before
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
[2] pry(main)> Usert.last
|
98
|
+
NameError: uninitialized constant Usert
|
99
|
+
from (pry):2:in `__pry__'
|
100
|
+
[3] pry(main)>
|
101
|
+
```
|
102
|
+
|
103
|
+
##### After
|
104
|
+
|
67
105
|
```ruby
|
68
106
|
[1] pry(main)> Usert.last
|
69
107
|
I, [2024-01-13T20:00:16.280710 #694] ERROR -- : uninitialized constant Usert
|
@@ -71,19 +109,19 @@ I, [2024-01-13T20:00:16.281237 #694] INFO -- : Running: User.last
|
|
71
109
|
=> #<User id: 1, email: "yo@email.com">
|
72
110
|
```
|
73
111
|
|
74
|
-
|
112
|
+
### ActiveRecord::ConfigurationError
|
75
113
|
|
76
114
|
Raised when association is being configured improperly or user tries to use offset and limit together with `ActiveRecord::Base.has_many` or `ActiveRecord::Base.has_and_belongs_to_many` associations.
|
115
|
+
This plugin will look into the `byetypo_dictionary` file to find the closest match and run the correct query.
|
77
116
|
|
78
|
-
|
117
|
+
##### Before
|
79
118
|
|
80
119
|
```ruby
|
81
120
|
[6] pry(main)> User.joins(:group).where(groups: { name: "Landlord" }).last
|
82
121
|
ActiveRecord::ConfigurationError: Can't join 'User' to association named 'group'; perhaps you misspelled it?
|
83
122
|
```
|
84
123
|
|
85
|
-
|
86
|
-
|
124
|
+
##### After
|
87
125
|
|
88
126
|
```ruby
|
89
127
|
[1] pry(main)> User.joins(:group).where(groups: { name: "Landlord" })
|
@@ -93,9 +131,12 @@ I, [2024-01-13T22:45:16.297972 #1079] INFO -- : Running: User.joins(:groups).wh
|
|
93
131
|
=> []
|
94
132
|
```
|
95
133
|
|
96
|
-
|
134
|
+
### ActiveRecord::StatementInvalid
|
97
135
|
|
98
136
|
The query attempts to reference columns or conditions related to a table, but the table is not properly included in the FROM clause.
|
137
|
+
This plugin will look into the `byetypo_dictionary` file to find the closest match and run the correct query.
|
138
|
+
|
139
|
+
##### Before
|
99
140
|
|
100
141
|
```ruby
|
101
142
|
[1] pry(main)> User.joins(:groups).where(grous: { name: "Landlord" }).last
|
@@ -103,7 +144,7 @@ ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause
|
|
103
144
|
LINE 1: ..."group_id" WHERE "users"."deleted_at" IS NULL AND "grous"."n...
|
104
145
|
```
|
105
146
|
|
106
|
-
|
147
|
+
##### After
|
107
148
|
|
108
149
|
```ruby
|
109
150
|
1] pry(main)> User.joins(:groups).where(grous: { name: "Landlord" }).last
|
@@ -116,11 +157,11 @@ I, [2024-01-14T23:50:49.273177 #1248] INFO -- : Running: User.joins(:groups).wh
|
|
116
157
|
|
117
158
|
Pry-byetypo is linked to your development database. During initialization, it will attempt to establish a connection to retrieve the tables available in your project. It will fetch the information for the development environment from the `database.yml` file.
|
118
159
|
|
119
|
-
|
160
|
+
#### Unreadable database URL (URI::InvalidURIError)
|
120
161
|
|
121
162
|
If the database connection string is not readable, the gem will be unable to establish a connection. If you encounter such an issue, make sure to add a `DATABASE_URL` variable to your `.env` file with the easily readable URL of your database.
|
122
163
|
|
123
|
-
|
164
|
+
#### Unreadable connection pool (ActiveRecord::ConnectionTimeoutError)
|
124
165
|
|
125
166
|
If the number of connections in your pool is not readable, you may encounter an `ActiveRecord::ConnectionTimeoutError`. If you experience this issue, make sure to add a `DATABASE_POOL` variable to your `.env` file.
|
126
167
|
|
@@ -7,10 +7,6 @@ module Exceptions
|
|
7
7
|
class Base < ExceptionsBase
|
8
8
|
private
|
9
9
|
|
10
|
-
def corrected_word
|
11
|
-
@corrected_word ||= spell_checker(associations_dictionary).correct(unknown_from_exception).first
|
12
|
-
end
|
13
|
-
|
14
10
|
def corrected_cmd
|
15
11
|
@corrected_cmd ||= last_cmd.gsub(/\b#{unknown_from_exception}\b/, corrected_word)
|
16
12
|
end
|
@@ -19,8 +15,8 @@ module Exceptions
|
|
19
15
|
exception.to_s.match(exception_regexp)[1]
|
20
16
|
end
|
21
17
|
|
22
|
-
def
|
23
|
-
|
18
|
+
def dictionary
|
19
|
+
store.transaction { |s| s["associations"] }
|
24
20
|
end
|
25
21
|
end
|
26
22
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Exceptions
|
6
|
+
module ActiveRecord
|
7
|
+
class UninitializedConstant < Base
|
8
|
+
private
|
9
|
+
|
10
|
+
def unknown_from_exception
|
11
|
+
exception.to_s.split.last
|
12
|
+
end
|
13
|
+
|
14
|
+
def corrected_cmd
|
15
|
+
@corrected_cmd ||= last_cmd.gsub(unknown_from_exception, corrected_word)
|
16
|
+
end
|
17
|
+
|
18
|
+
def dictionary
|
19
|
+
@ar_models_dictionary ||= store.transaction { |s| s["active_record_models"] }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,17 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "logger"
|
4
|
+
require "colorize"
|
3
5
|
require_relative "../base"
|
4
6
|
require_relative "../setup/store"
|
5
|
-
require "colorize"
|
6
7
|
|
7
8
|
class ExceptionsBase < Base
|
8
9
|
include Setup::Store
|
9
10
|
|
10
11
|
def call
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
if corrected_word
|
13
|
+
logger.error(exception.to_s.colorize(color: :light_red, mode: :bold))
|
14
|
+
logger.info("Running: #{corrected_cmd}".colorize(color: :green, mode: :bold))
|
15
|
+
|
16
|
+
pry.eval(corrected_cmd)
|
17
|
+
else
|
18
|
+
Pry::ExceptionHandler.handle_exception(output, exception, pry)
|
19
|
+
end
|
15
20
|
end
|
16
21
|
|
17
22
|
private
|
@@ -24,7 +29,11 @@ class ExceptionsBase < Base
|
|
24
29
|
@pry = pry
|
25
30
|
end
|
26
31
|
|
27
|
-
def
|
32
|
+
def corrected_word
|
33
|
+
@corrected_word ||= spell_checker.correct(unknown_from_exception).first
|
34
|
+
end
|
35
|
+
|
36
|
+
def spell_checker
|
28
37
|
DidYouMean::SpellChecker.new(dictionary: dictionary)
|
29
38
|
end
|
30
39
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../exceptions_base"
|
4
|
+
require_relative "../../constants/errors"
|
5
|
+
require_relative "../active_record/uninitialized_constant"
|
6
|
+
require_relative "undefined_variable"
|
7
|
+
|
8
|
+
module Exceptions
|
9
|
+
module NameError
|
10
|
+
class Handler < ExceptionsBase
|
11
|
+
attr_reader :exception, :output, :pry
|
12
|
+
|
13
|
+
def initialize(output, exception, pry)
|
14
|
+
@output = output
|
15
|
+
@exception = exception
|
16
|
+
@pry = pry
|
17
|
+
end
|
18
|
+
|
19
|
+
# FIXME: https://github.com/rubocop/rubocop-performance/issues/438
|
20
|
+
# rubocop:disable Performance/ConstantRegexp
|
21
|
+
def call
|
22
|
+
case exception.message
|
23
|
+
in /#{Constants::Errors::UNINITIALIZED_CONSTANT}/
|
24
|
+
ActiveRecord::UninitializedConstant.call(output, exception, pry)
|
25
|
+
in /#{Constants::Errors::UNDEFINED_VARIABLE}/
|
26
|
+
UndefinedVariable.call(output, exception, pry)
|
27
|
+
else
|
28
|
+
Pry::ExceptionHandler.handle_exception(output, exception, pry)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# rubocop:enable Performance/ConstantRegexp
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../exceptions_base"
|
4
|
+
|
5
|
+
module Exceptions
|
6
|
+
module NameError
|
7
|
+
class UndefinedVariable < ExceptionsBase
|
8
|
+
private
|
9
|
+
|
10
|
+
def corrected_cmd
|
11
|
+
@corrected_cmd ||= last_cmd.gsub(/\b#{unknown_from_exception}\b/, corrected_word)
|
12
|
+
end
|
13
|
+
|
14
|
+
def unknown_from_exception
|
15
|
+
exception.to_s.match(exception_regexp)[1]
|
16
|
+
end
|
17
|
+
|
18
|
+
def dictionary
|
19
|
+
store.transaction { |s| s[pry_instance_uid] }
|
20
|
+
end
|
21
|
+
|
22
|
+
def exception_regexp
|
23
|
+
/`(\w+)'/
|
24
|
+
end
|
25
|
+
|
26
|
+
# Use the current binding identifier as pry instance uid.
|
27
|
+
def pry_instance_uid
|
28
|
+
pry.current_binding.to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -3,14 +3,12 @@
|
|
3
3
|
require_relative "base"
|
4
4
|
require_relative "exceptions/active_record/statement_invalid"
|
5
5
|
require_relative "exceptions/active_record/configuration_error"
|
6
|
-
require_relative "exceptions/name_error"
|
6
|
+
require_relative "exceptions/name_error/handler"
|
7
|
+
require_relative "constants/errors"
|
7
8
|
|
8
9
|
class ExceptionsHandler < Base
|
9
10
|
attr_reader :exception, :output, :pry
|
10
11
|
|
11
|
-
UNINITIALIZED_CONSTANT = "uninitialized constant"
|
12
|
-
UNDEFINED_TABLE = "UndefinedTable"
|
13
|
-
|
14
12
|
def initialize(output, exception, pry)
|
15
13
|
@output = output
|
16
14
|
@exception = exception
|
@@ -19,20 +17,15 @@ class ExceptionsHandler < Base
|
|
19
17
|
|
20
18
|
def call
|
21
19
|
case exception
|
22
|
-
in NameError
|
23
|
-
# eg: Usert.last
|
20
|
+
in NameError
|
24
21
|
# NameError is a Superclass for all undefined statement.
|
25
|
-
|
26
|
-
|
27
|
-
Exceptions::NameError.call(output, exception, pry)
|
28
|
-
in ActiveRecord::StatementInvalid => error
|
29
|
-
# eg: User.joins(:groups).where(grous: { name: "Landlord" }).last
|
22
|
+
Exceptions::NameError::Handler.call(output, exception, pry)
|
23
|
+
in ActiveRecord::StatementInvalid
|
30
24
|
# ActiveRecord::StatementInvalid is a Superclass for all database execution errors.
|
31
25
|
# We only need to one including an `UndefinedTable` error.
|
32
|
-
return pry_exception_handler unless
|
26
|
+
return pry_exception_handler unless exception.message.include?(Constants::Errors::UNDEFINED_TABLE)
|
33
27
|
Exceptions::ActiveRecord::StatementInvalid.call(output, exception, pry)
|
34
28
|
in ActiveRecord::ConfigurationError
|
35
|
-
# eg: User.joins(:group).where(groups: { name: "Landlord" }).last
|
36
29
|
Exceptions::ActiveRecord::ConfigurationError.call(output, exception, pry)
|
37
30
|
else
|
38
31
|
pry_exception_handler
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../base"
|
4
|
+
require_relative "../setup/store"
|
5
|
+
|
6
|
+
module Session
|
7
|
+
class ClearHistory < Base
|
8
|
+
include Setup::Store
|
9
|
+
|
10
|
+
attr_reader :pry
|
11
|
+
|
12
|
+
def initialize(pry)
|
13
|
+
@pry = pry
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
pry_instance_to_remove = pry.push_initial_binding.join
|
18
|
+
store.transaction { store.delete(pry_instance_to_remove) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../base"
|
4
|
+
require_relative "../setup/store"
|
5
|
+
|
6
|
+
module Session
|
7
|
+
class PopulateHistory < Base
|
8
|
+
include Setup::Store
|
9
|
+
|
10
|
+
attr_reader :binding
|
11
|
+
|
12
|
+
def initialize(binding)
|
13
|
+
@binding = binding
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
return unless is_assignement_variables?
|
18
|
+
|
19
|
+
store.transaction do
|
20
|
+
store.abort unless variables_to_store
|
21
|
+
|
22
|
+
store[pry_instance_uid].push(*variables_to_store)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Unique instance identifier (e.g., a Pry opened tab)
|
29
|
+
def pry_instance_uid
|
30
|
+
binding.binding_stack.join
|
31
|
+
end
|
32
|
+
|
33
|
+
def variables_to_store
|
34
|
+
@variables_to_store ||= last_cmd.split("=").first.strip.split(",")
|
35
|
+
end
|
36
|
+
|
37
|
+
def last_cmd
|
38
|
+
binding.eval_string.strip
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns true if the last command seems to be an assignment of variables, false otherwise.
|
42
|
+
#
|
43
|
+
# Examples
|
44
|
+
#
|
45
|
+
# is_assignment_variables?("user_last, user_first = User.last, User.first")
|
46
|
+
# # => true
|
47
|
+
#
|
48
|
+
# is_assignment_variables?("user_last = User.last")
|
49
|
+
# # => true
|
50
|
+
#
|
51
|
+
# is_assignment_variables?("user_last")
|
52
|
+
# # => false
|
53
|
+
def is_assignement_variables?
|
54
|
+
last_cmd.include?("=")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -1,73 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
4
|
-
require_relative "checks/database_pool"
|
3
|
+
require_relative "../base"
|
5
4
|
require_relative "store"
|
6
|
-
|
5
|
+
require_relative "dictionary/active_record"
|
7
6
|
require "pstore"
|
8
7
|
|
9
8
|
module Setup
|
10
|
-
class ApplicationDictionary
|
9
|
+
class ApplicationDictionary < Base
|
11
10
|
include Store
|
12
11
|
|
13
|
-
def initialize
|
14
|
-
|
12
|
+
def initialize(binding)
|
13
|
+
@binding = binding
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
Setup::Dictionary::ActiveRecord.initialize! if defined?(ActiveRecord)
|
15
18
|
populate_store
|
16
19
|
end
|
17
20
|
|
18
21
|
private
|
19
22
|
|
20
|
-
|
23
|
+
attr_reader :binding
|
21
24
|
|
22
25
|
def populate_store
|
23
|
-
store.
|
24
|
-
|
25
|
-
|
26
|
-
store["active_record_models"] = populate_active_record_models_dictionary
|
27
|
-
store["associations"] = populate_associations
|
28
|
-
store["synced_at"] = Time.now
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def establish_db_connection
|
33
|
-
configuration_checks
|
34
|
-
ActiveRecord::Base.establish_connection(development_database_config)
|
35
|
-
end
|
36
|
-
|
37
|
-
def populate_active_record_models_dictionary
|
38
|
-
Zeitwerk::Loader.eager_load_all
|
39
|
-
ActiveRecord::Base.descendants.map { |model| model.name }
|
40
|
-
end
|
41
|
-
|
42
|
-
def populate_associations
|
43
|
-
table_names = ActiveRecord::Base.connection.tables
|
44
|
-
singularize_table_names = table_names.map { |a| a.chop }
|
45
|
-
table_names.zip(singularize_table_names).flatten
|
46
|
-
end
|
47
|
-
|
48
|
-
def configuration_checks
|
49
|
-
Setup::Checks::DatabaseUrl.check(development_database_config, logger)
|
50
|
-
Setup::Checks::DatabasePool.check(development_database_config, logger)
|
51
|
-
end
|
52
|
-
|
53
|
-
def database_config
|
54
|
-
YAML.safe_load(File.read("./config/database.yml"), aliases: true)
|
55
|
-
end
|
56
|
-
|
57
|
-
def development_database_config
|
58
|
-
@development_database_config ||= database_config["development"]
|
59
|
-
end
|
60
|
-
|
61
|
-
# By default we update the store every week.
|
62
|
-
# TODO: Make it configurable
|
63
|
-
def staled_store?
|
64
|
-
return true if store["synced_at"].nil?
|
65
|
-
|
66
|
-
(store["synced_at"] + SEVEN_DAYS) <= Time.now
|
26
|
+
# Create a table with unique instance identifier information to store variables history.
|
27
|
+
store.transaction { store[pry_instance_uid] = [] }
|
67
28
|
end
|
68
29
|
|
69
|
-
|
70
|
-
|
30
|
+
# Use the binding identifier as pry instance uid.
|
31
|
+
def pry_instance_uid
|
32
|
+
binding.to_s
|
71
33
|
end
|
72
34
|
end
|
73
35
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../checks/database_url"
|
4
|
+
require_relative "../checks/database_pool"
|
5
|
+
require_relative "../store"
|
6
|
+
|
7
|
+
require "pstore"
|
8
|
+
|
9
|
+
module Setup
|
10
|
+
module Dictionary
|
11
|
+
class ActiveRecord
|
12
|
+
class << self
|
13
|
+
include Store
|
14
|
+
|
15
|
+
def initialize!
|
16
|
+
establish_db_connection
|
17
|
+
populate_store
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def populate_store
|
23
|
+
store.transaction do
|
24
|
+
store.commit unless staled_store?
|
25
|
+
|
26
|
+
store["active_record_models"] = populate_active_record_models_dictionary
|
27
|
+
store["associations"] = populate_associations
|
28
|
+
store["synced_at"] = Time.now
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def establish_db_connection
|
33
|
+
configuration_checks
|
34
|
+
::ActiveRecord::Base.establish_connection(development_database_config)
|
35
|
+
end
|
36
|
+
|
37
|
+
def populate_active_record_models_dictionary
|
38
|
+
Zeitwerk::Loader.eager_load_all
|
39
|
+
::ActiveRecord::Base.descendants.map { |model| format_active_record_model(model) }.flatten
|
40
|
+
end
|
41
|
+
|
42
|
+
def populate_associations
|
43
|
+
table_names = ::ActiveRecord::Base.connection.tables
|
44
|
+
singularize_table_names = table_names.map { |a| a.chop }
|
45
|
+
table_names.zip(singularize_table_names).flatten
|
46
|
+
end
|
47
|
+
|
48
|
+
# This method takes an ActiveRecord model as an argument and formats its name and module information.
|
49
|
+
# If the model is within a module namespace, it returns an array containing the model's name and an array of its modules.
|
50
|
+
# If the model is not within a module, it returns just the model's name as a string.
|
51
|
+
def format_active_record_model(model)
|
52
|
+
modules = model.name.split("::")
|
53
|
+
return model.name, modules if modules.count > 1
|
54
|
+
|
55
|
+
model.name
|
56
|
+
end
|
57
|
+
|
58
|
+
def development_database_config
|
59
|
+
@development_database_config ||= database_config["development"]
|
60
|
+
end
|
61
|
+
|
62
|
+
def configuration_checks
|
63
|
+
Setup::Checks::DatabaseUrl.check(development_database_config, logger)
|
64
|
+
Setup::Checks::DatabasePool.check(development_database_config, logger)
|
65
|
+
end
|
66
|
+
|
67
|
+
def database_config
|
68
|
+
YAML.safe_load(File.read(database_config_path), aliases: true)
|
69
|
+
end
|
70
|
+
|
71
|
+
def logger
|
72
|
+
@logger = Logger.new($stdout)
|
73
|
+
end
|
74
|
+
|
75
|
+
def database_config_path
|
76
|
+
ENV["DB_CONFIG_PATH"] || "./config/database.yml"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Setup
|
2
2
|
module Store
|
3
3
|
DEFAULT_STORE_PATH = "byetypo_dictionary.pstore"
|
4
|
+
SEVEN_DAYS = 604800
|
4
5
|
|
5
6
|
def store
|
6
7
|
@store ||= PStore.new(store_path)
|
@@ -9,5 +10,13 @@ module Setup
|
|
9
10
|
def store_path
|
10
11
|
ENV["BYETYPO_STORE_PATH"] || DEFAULT_STORE_PATH
|
11
12
|
end
|
13
|
+
|
14
|
+
# By default we update the store every week.
|
15
|
+
# TODO: Make it configurable
|
16
|
+
def staled_store?
|
17
|
+
return true if store["synced_at"].nil?
|
18
|
+
|
19
|
+
(store["synced_at"] + SEVEN_DAYS) <= Time.now
|
20
|
+
end
|
12
21
|
end
|
13
22
|
end
|
data/lib/pry-byetypo/version.rb
CHANGED
data/lib/pry-byetypo.rb
CHANGED
@@ -1,15 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "pry"
|
4
|
-
require "zeitwerk"
|
5
4
|
|
6
|
-
require_relative "pry-byetypo/version"
|
7
5
|
require_relative "pry-byetypo/setup/application_dictionary"
|
6
|
+
require_relative "pry-byetypo/session/clear_history"
|
8
7
|
require_relative "pry-byetypo/exceptions_handler"
|
8
|
+
require_relative "pry-byetypo/session/populate_history"
|
9
|
+
require_relative "pry-byetypo/version"
|
9
10
|
|
10
11
|
module Pry::Byetypo
|
11
|
-
Pry.config.hooks.add_hook(:before_session, :
|
12
|
-
Setup::ApplicationDictionary.
|
12
|
+
Pry.config.hooks.add_hook(:before_session, :create_dictionary) do |_output, binding, _pry|
|
13
|
+
Setup::ApplicationDictionary.call(binding)
|
14
|
+
end
|
15
|
+
|
16
|
+
Pry.config.hooks.add_hook(:after_read, :populate_session_history) do |_output, binding, _pry|
|
17
|
+
Session::PopulateHistory.call(binding)
|
18
|
+
end
|
19
|
+
|
20
|
+
Pry.config.hooks.add_hook(:after_session, :clear_session_history) do |_output, _binding, pry|
|
21
|
+
Session::ClearHistory.call(pry)
|
13
22
|
end
|
14
23
|
|
15
24
|
# TODO: Adds max_attempts
|
data/pry-byetypo.gemspec
CHANGED
@@ -5,14 +5,14 @@ require_relative "lib/pry-byetypo/version"
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
6
|
gem.name = "pry-byetypo"
|
7
7
|
gem.version = Pry::Byetypo::VERSION
|
8
|
-
gem.authors = ["
|
8
|
+
gem.authors = ["Clément Morisset"]
|
9
9
|
gem.email = ["morissetcl87@gmail.com"]
|
10
10
|
|
11
11
|
gem.summary = "Autocorrects typos in your Pry console"
|
12
|
-
gem.description = "This
|
12
|
+
gem.description = "This Pry plugin captures exceptions that could be due to typos and deduces the correct command based on your database information."
|
13
13
|
gem.homepage = "https://github.com/morissetcl/pry-byetypo"
|
14
14
|
gem.license = "MIT"
|
15
|
-
gem.required_ruby_version = ">= 2.
|
15
|
+
gem.required_ruby_version = ">= 2.7.0"
|
16
16
|
|
17
17
|
gem.metadata["homepage_uri"] = gem.homepage
|
18
18
|
gem.metadata["source_code_uri"] = "https://github.com/morissetcl/pry-byetypo"
|
@@ -28,7 +28,7 @@ Gem::Specification.new do |gem|
|
|
28
28
|
gem.bindir = "exe"
|
29
29
|
gem.executables = gem.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
30
30
|
gem.require_paths = ["lib"]
|
31
|
+
gem.add_development_dependency "rails", "~> 7.0"
|
31
32
|
gem.add_runtime_dependency "colorize", "~> 1.1.0"
|
32
33
|
gem.add_runtime_dependency "pry", ">= 0.13", "< 0.15"
|
33
|
-
gem.add_runtime_dependency "rails", "~> 7.0"
|
34
34
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pry-byetypo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Clément Morisset
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-02-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: colorize
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,22 +58,8 @@ dependencies:
|
|
44
58
|
- - "<"
|
45
59
|
- !ruby/object:Gem::Version
|
46
60
|
version: '0.15'
|
47
|
-
|
48
|
-
|
49
|
-
requirement: !ruby/object:Gem::Requirement
|
50
|
-
requirements:
|
51
|
-
- - "~>"
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: '7.0'
|
54
|
-
type: :runtime
|
55
|
-
prerelease: false
|
56
|
-
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
requirements:
|
58
|
-
- - "~>"
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
version: '7.0'
|
61
|
-
description: This small Pry plugin captures exceptions that could be due to typos
|
62
|
-
and deduces the correct command based on your database information.
|
61
|
+
description: This Pry plugin captures exceptions that could be due to typos and deduces
|
62
|
+
the correct command based on your database information.
|
63
63
|
email:
|
64
64
|
- morissetcl87@gmail.com
|
65
65
|
executables: []
|
@@ -77,16 +77,22 @@ files:
|
|
77
77
|
- Rakefile
|
78
78
|
- lib/pry-byetypo.rb
|
79
79
|
- lib/pry-byetypo/base.rb
|
80
|
+
- lib/pry-byetypo/constants/errors.rb
|
80
81
|
- lib/pry-byetypo/exceptions/active_record/base.rb
|
81
82
|
- lib/pry-byetypo/exceptions/active_record/configuration_error.rb
|
82
83
|
- lib/pry-byetypo/exceptions/active_record/statement_invalid.rb
|
84
|
+
- lib/pry-byetypo/exceptions/active_record/uninitialized_constant.rb
|
83
85
|
- lib/pry-byetypo/exceptions/exceptions_base.rb
|
84
|
-
- lib/pry-byetypo/exceptions/name_error.rb
|
86
|
+
- lib/pry-byetypo/exceptions/name_error/handler.rb
|
87
|
+
- lib/pry-byetypo/exceptions/name_error/undefined_variable.rb
|
85
88
|
- lib/pry-byetypo/exceptions_handler.rb
|
89
|
+
- lib/pry-byetypo/session/clear_history.rb
|
90
|
+
- lib/pry-byetypo/session/populate_history.rb
|
86
91
|
- lib/pry-byetypo/setup/application_dictionary.rb
|
87
92
|
- lib/pry-byetypo/setup/checks/base.rb
|
88
93
|
- lib/pry-byetypo/setup/checks/database_pool.rb
|
89
94
|
- lib/pry-byetypo/setup/checks/database_url.rb
|
95
|
+
- lib/pry-byetypo/setup/dictionary/active_record.rb
|
90
96
|
- lib/pry-byetypo/setup/store.rb
|
91
97
|
- lib/pry-byetypo/version.rb
|
92
98
|
- pry-byetypo.gemspec
|
@@ -106,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
106
112
|
requirements:
|
107
113
|
- - ">="
|
108
114
|
- !ruby/object:Gem::Version
|
109
|
-
version: 2.
|
115
|
+
version: 2.7.0
|
110
116
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
117
|
requirements:
|
112
118
|
- - ">="
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "exceptions_base"
|
4
|
-
|
5
|
-
module Exceptions
|
6
|
-
class NameError < ExceptionsBase
|
7
|
-
private
|
8
|
-
|
9
|
-
def unknown_from_exception
|
10
|
-
exception.to_s.split.last
|
11
|
-
end
|
12
|
-
|
13
|
-
def corrected_word
|
14
|
-
@corrected_word ||= spell_checker(ar_models_dictionary).correct(unknown_from_exception).first
|
15
|
-
end
|
16
|
-
|
17
|
-
def corrected_cmd
|
18
|
-
@corrected_cmd ||= last_cmd.gsub(unknown_from_exception, corrected_word)
|
19
|
-
end
|
20
|
-
|
21
|
-
def ar_models_dictionary
|
22
|
-
@ar_models_dictionary ||= store.transaction { |s| s["active_record_models"] }
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|