prependers 0.3.0 → 1.0.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/.rubocop-https---relaxed-ruby-style-rubocop-yml +3 -24
- data/.rubocop.yml +6 -0
- data/CHANGELOG.md +2 -2
- data/README.md +103 -38
- data/lib/prependers.rb +24 -12
- data/lib/prependers/annotate/namespace.rb +25 -0
- data/lib/prependers/annotate/verify.rb +60 -0
- data/lib/prependers/errors.rb +2 -0
- data/lib/prependers/loader.rb +15 -11
- data/lib/prependers/prepender.rb +25 -11
- data/lib/prependers/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30c53a5f64db0e3ee8454289403fbf6aa00a7d90fdee76c504a5cc878324ef88
|
4
|
+
data.tar.gz: 14db78d2c2160984cd891f35f9b1794e55ec011068db5c50834538df430eebf2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0660258db42ece336874d2868b93652a89128076a612057144861a8961d61c1330d8d65d0b4369a81ad2361fdd3d33eeb634b4dab72921a5a1a027a308ba5ecc'
|
7
|
+
data.tar.gz: ab60f96d45f3fecb4abb1689260e6f5aa8b47d52700035ab6a33aebd0e111bf56fb1053d33b380474224a56dab9b1fa11e32bb5e047393a5d2f4e6eacfc21d12
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# Relaxed.Ruby.Style
|
2
|
-
## Version 2.
|
2
|
+
## Version 2.5
|
3
3
|
|
4
4
|
Style/Alias:
|
5
5
|
Enabled: false
|
@@ -145,30 +145,9 @@ Lint/AssignmentInCondition:
|
|
145
145
|
Enabled: false
|
146
146
|
StyleGuide: https://relaxed.ruby.style/#lintassignmentincondition
|
147
147
|
|
148
|
-
|
148
|
+
Layout/LineLength:
|
149
149
|
Enabled: false
|
150
150
|
|
151
|
-
Metrics
|
152
|
-
Enabled: false
|
153
|
-
|
154
|
-
Metrics/ClassLength:
|
155
|
-
Enabled: false
|
156
|
-
|
157
|
-
Metrics/ModuleLength:
|
158
|
-
Enabled: false
|
159
|
-
|
160
|
-
Metrics/CyclomaticComplexity:
|
161
|
-
Enabled: false
|
162
|
-
|
163
|
-
Metrics/LineLength:
|
164
|
-
Enabled: false
|
165
|
-
|
166
|
-
Metrics/MethodLength:
|
167
|
-
Enabled: false
|
168
|
-
|
169
|
-
Metrics/ParameterLists:
|
170
|
-
Enabled: false
|
171
|
-
|
172
|
-
Metrics/PerceivedComplexity:
|
151
|
+
Metrics:
|
173
152
|
Enabled: false
|
174
153
|
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## [
|
3
|
+
## [v0.3.0](https://github.com/nebulab/prependers/tree/v0.3.0) (2020-04-24)
|
4
4
|
|
5
|
-
[Full Changelog](https://github.com/nebulab/prependers/compare/v0.2.0...
|
5
|
+
[Full Changelog](https://github.com/nebulab/prependers/compare/v0.2.0...v0.3.0)
|
6
6
|
|
7
7
|
**Merged pull requests:**
|
8
8
|
|
data/README.md
CHANGED
@@ -18,7 +18,6 @@ And then execute:
|
|
18
18
|
$ bundle
|
19
19
|
```
|
20
20
|
|
21
|
-
|
22
21
|
Or install it yourself as:
|
23
22
|
|
24
23
|
```console
|
@@ -27,13 +26,13 @@ $ gem install prependers
|
|
27
26
|
|
28
27
|
## Usage
|
29
28
|
|
30
|
-
To define a prepender manually, simply include the `Prependers::Prepender
|
29
|
+
To define a prepender manually, simply include the `Prependers::Prepender[]` module. For instance,
|
31
30
|
if you have installed an `animals` gem and you want to extend the `Animals::Dog` class, you can
|
32
31
|
define a module like the following:
|
33
32
|
|
34
33
|
```ruby
|
35
34
|
module Animals::Dog::AddBarking
|
36
|
-
include Prependers::Prepender
|
35
|
+
include Prependers::Prepender[]
|
37
36
|
|
38
37
|
def bark
|
39
38
|
puts 'Woof!'
|
@@ -50,7 +49,7 @@ prepender:
|
|
50
49
|
|
51
50
|
```ruby
|
52
51
|
module Animals::Dog::AddFamily
|
53
|
-
include Prependers::Prepender
|
52
|
+
include Prependers::Prepender[]
|
54
53
|
|
55
54
|
module ClassMethods
|
56
55
|
def family
|
@@ -62,13 +61,100 @@ end
|
|
62
61
|
Animals::Dog.family # => 'Canids'
|
63
62
|
```
|
64
63
|
|
65
|
-
As you can see, the `ClassMethods` module has automagically been `prepend`ed to
|
64
|
+
As you can see, the `ClassMethods` module has automagically been `prepend`ed to `Animals::Dog`'s
|
66
65
|
singleton class.
|
67
66
|
|
67
|
+
### Using a namespace
|
68
|
+
|
69
|
+
It can be useful to have a prefix namespace for your prependers. That way, you don't have to worry
|
70
|
+
about accidentally overriding any vendor modules. This is actually the recommended way to define
|
71
|
+
your prependers.
|
72
|
+
|
73
|
+
You can accomplish this by passing the `:namespace` option when including `Prependers::Prepender`:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
module MyApp
|
77
|
+
module Animals
|
78
|
+
module Dog
|
79
|
+
module AddBarking
|
80
|
+
include Prependers::Prepender[namespace: MyApp]
|
81
|
+
|
82
|
+
def bark
|
83
|
+
puts 'Woof!'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
### Verifying original sources
|
92
|
+
|
93
|
+
One issue you may run into when extending third-party code is that, when the original implementation
|
94
|
+
is updated, it's not always obvious whether you have to update any of your extensions.
|
95
|
+
|
96
|
+
Prependers make this a bit easier with the concept of original source verification: you can compute
|
97
|
+
a SHA1 hash of the original implementation, store it along with your prepender, and then verify it
|
98
|
+
against the current hash when your application loads. If the original source changes, you get an
|
99
|
+
error asking you to ensure your prepender is still relevant.
|
100
|
+
|
101
|
+
To use original source verification in your prependers, pass the `:verify` option:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
module Animals::Dog::AddBarking
|
105
|
+
include Prependers::Prepender[verify: nil]
|
106
|
+
|
107
|
+
# ...
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
When you load your application now, you will get an error with instructions on how to set the proper
|
112
|
+
hash:
|
113
|
+
|
114
|
+
```
|
115
|
+
Prependers::OutdatedPrependerError:
|
116
|
+
You have not defined an original hash for Animals::Dog in Animals::Dog::AddBarking.
|
117
|
+
|
118
|
+
You can define the hash by updating your include statement as follows:
|
119
|
+
|
120
|
+
include Prependers::Prepender[verify: 'f7175533215c39f3f3328aa5829ac6b1bb168218']
|
121
|
+
```
|
122
|
+
|
123
|
+
At this point, you should update your prepender with the correct hash:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
module Animals::Dog::AddBarking
|
127
|
+
include Prependers::Prepender[verify: 'f7175533215c39f3f3328aa5829ac6b1bb168218']
|
128
|
+
|
129
|
+
# ...
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
Now, when the underlying implementation of `Animals::Dog` changes because of a dependency update or
|
134
|
+
other reasons, Prependers will raise an error such as the following:
|
135
|
+
|
136
|
+
```
|
137
|
+
Prependers::OutdatedPrependerError:
|
138
|
+
The stored hash for Animals::Dog in Animals::Dog::AddBarking is
|
139
|
+
f7175533215c39f3f3328aa5829ac6b1bb168218, but the current hash is
|
140
|
+
2f05682e4f46b509c23a8418d9427a9eeaa8a79e instead.
|
141
|
+
|
142
|
+
This most likely means that the original source has changed.
|
143
|
+
|
144
|
+
Check that your prepender is still valid, then update the stored hash:
|
145
|
+
|
146
|
+
include Prependers::Prepender[verify: '2f05682e4f46b509c23a8418d9427a9eeaa8a79e']
|
147
|
+
```
|
148
|
+
|
149
|
+
Original source verification also works when a module is defined in multiple locations.
|
150
|
+
|
151
|
+
*NOTE: Due to limitations in Ruby's API, it is not possible to use source verification with modules
|
152
|
+
that don't define any methods. Prependers will raise an error if you try to do this.*
|
153
|
+
|
68
154
|
### Autoloading prependers
|
69
155
|
|
70
|
-
If you don't want to include `Prependers::Prepender`, you can also autoload prependers from a
|
71
|
-
they will be loaded in alphabetical order.
|
156
|
+
If you don't want to include `Prependers::Prepender[]`, you can also autoload prependers from a
|
157
|
+
path, they will be loaded in alphabetical order.
|
72
158
|
|
73
159
|
Here's the previous example, but with autoloading:
|
74
160
|
|
@@ -84,6 +170,9 @@ end
|
|
84
170
|
Prependers.load_paths(File.expand_path('app/prependers'))
|
85
171
|
```
|
86
172
|
|
173
|
+
Note that, in order for autoprepending to work, the paths of your prependers must match the names
|
174
|
+
of the prependers you defined.
|
175
|
+
|
87
176
|
You can pass multiple arguments to `#load_paths`, which is useful if you have subdirectories in
|
88
177
|
`app/prependers`:
|
89
178
|
|
@@ -95,37 +184,14 @@ Prependers.load_paths(
|
|
95
184
|
)
|
96
185
|
```
|
97
186
|
|
98
|
-
|
99
|
-
of the prependers you defined.
|
100
|
-
|
101
|
-
### Using a namespace
|
102
|
-
|
103
|
-
It can be useful to have a prefix namespace for your prependers. That way, you don't have to worry
|
104
|
-
about accidentally overriding any vendor modules. This is actually the recommended way to define
|
105
|
-
your prependers.
|
106
|
-
|
107
|
-
You can accomplish this by passing an argument when including the `Prependers::Prepender` module:
|
108
|
-
|
109
|
-
```ruby
|
110
|
-
module MyApp
|
111
|
-
module Animals
|
112
|
-
module Dog
|
113
|
-
module AddBarking
|
114
|
-
include Prependers::Prepender.new(MyApp)
|
115
|
-
|
116
|
-
def bark
|
117
|
-
puts 'Woof!'
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
```
|
124
|
-
|
125
|
-
If you use autoloading, you can pass the base namespace to `#load_paths`:
|
187
|
+
You can pass the `:namespace` option to `#load_paths` to have it forwarded to all prependers:
|
126
188
|
|
127
189
|
```ruby
|
128
|
-
Prependers.load_paths(
|
190
|
+
Prependers.load_paths(
|
191
|
+
File.expand_path('app/prependers/controllers'),
|
192
|
+
File.expand_path('app/prependers/models'),
|
193
|
+
namespace: Acme,
|
194
|
+
)
|
129
195
|
```
|
130
196
|
|
131
197
|
### Integrating with Rails
|
@@ -137,8 +203,7 @@ To use prependers in your Rails app, simply create them under `app/prependers/mo
|
|
137
203
|
Prependers.setup_for_rails
|
138
204
|
```
|
139
205
|
|
140
|
-
|
141
|
-
your files and modules accordingly.
|
206
|
+
`#setup_for_rails` accepts the same options as `#load_paths`.
|
142
207
|
|
143
208
|
## Development
|
144
209
|
|
data/lib/prependers.rb
CHANGED
@@ -1,28 +1,40 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "digest"
|
4
|
+
|
3
5
|
require "prependers/version"
|
4
6
|
require "prependers/errors"
|
7
|
+
require "prependers/annotate/namespace"
|
8
|
+
require "prependers/annotate/verify"
|
5
9
|
require "prependers/prepender"
|
6
10
|
require "prependers/loader"
|
7
11
|
|
8
12
|
module Prependers
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
Loader.new(path, options).load
|
13
|
-
end
|
13
|
+
def self.load_paths(*paths, **options)
|
14
|
+
paths.flatten.each do |path|
|
15
|
+
Loader.new(path, options).load
|
14
16
|
end
|
17
|
+
end
|
15
18
|
|
16
|
-
|
17
|
-
|
19
|
+
def self.setup_for_rails(load_options = {})
|
20
|
+
prependers_directories = Rails.root.join('app', 'prependers').glob('*')
|
18
21
|
|
19
|
-
|
20
|
-
|
22
|
+
Rails.application.config.tap do |config|
|
23
|
+
config.autoload_paths += prependers_directories
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
+
config.to_prepare do
|
26
|
+
Prependers.load_paths(prependers_directories, load_options)
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
30
|
+
|
31
|
+
def self.prependable_for(prepender)
|
32
|
+
prependable = prepender.name.split('::')[0..-2].join('::')
|
33
|
+
|
34
|
+
if prepender.respond_to?(:__prependers_namespace__)
|
35
|
+
prependable = (prependable[(prepender.__prependers_namespace__.name.length + 2)..-1]).to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
Object.const_get(prependable)
|
39
|
+
end
|
28
40
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prependers
|
4
|
+
module Annotate
|
5
|
+
class Namespace < Module
|
6
|
+
attr_reader :namespace
|
7
|
+
|
8
|
+
def self.[](namespace)
|
9
|
+
new(namespace)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(namespace)
|
13
|
+
@namespace = namespace
|
14
|
+
end
|
15
|
+
|
16
|
+
def included(base)
|
17
|
+
base.singleton_class.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
18
|
+
def __prependers_namespace__
|
19
|
+
#{@namespace}
|
20
|
+
end
|
21
|
+
RUBY
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prependers
|
4
|
+
module Annotate
|
5
|
+
class Verify < Module
|
6
|
+
WRONG_HASH_ERROR = \
|
7
|
+
"The stored hash for %{prepended_module} in %{prepender} is %{stored_hash}, but the " \
|
8
|
+
"current hash is %{current_hash} instead.\n\n" \
|
9
|
+
"This most likely means that the original source has changed.\n\n" \
|
10
|
+
"Check that your prepender is still valid, then update the stored hash:\n\n" \
|
11
|
+
" include Prependers::Prepender[verify: '%{current_hash}']"
|
12
|
+
|
13
|
+
UNSET_HASH_ERROR = \
|
14
|
+
"You have not defined an original hash for %{prepended_module} in %{prepender}.\n\n" \
|
15
|
+
"You can define the hash by updating your include statement as follows:\n\n" \
|
16
|
+
" include Prependers::Prepender[verify: '%{current_hash}']"
|
17
|
+
|
18
|
+
attr_reader :original_hash
|
19
|
+
|
20
|
+
def self.[](original_hash)
|
21
|
+
new(original_hash)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(original_hash)
|
25
|
+
@original_hash = original_hash
|
26
|
+
end
|
27
|
+
|
28
|
+
def included(prepender)
|
29
|
+
prependable = Prependers.prependable_for(prepender)
|
30
|
+
current_hash = compute_hash(prependable)
|
31
|
+
|
32
|
+
return if current_hash == original_hash
|
33
|
+
|
34
|
+
error = (original_hash ? WRONG_HASH_ERROR : UNSET_HASH_ERROR) % {
|
35
|
+
prepended_module: prependable,
|
36
|
+
prepender: prepender.name,
|
37
|
+
stored_hash: original_hash,
|
38
|
+
current_hash: current_hash,
|
39
|
+
}
|
40
|
+
|
41
|
+
raise OutdatedPrependerError, error
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def compute_hash(constant)
|
47
|
+
methods = constant.methods(false).map(&constant.method(:method)) +
|
48
|
+
constant.instance_methods(false).map(&constant.method(:instance_method))
|
49
|
+
|
50
|
+
raise NameError, "#{constant} has no methods" if methods.empty?
|
51
|
+
|
52
|
+
source_locations = methods.map(&:source_location).compact.map(&:first).uniq.sort
|
53
|
+
|
54
|
+
contents = source_locations.map { |path| File.read(path) }.join
|
55
|
+
|
56
|
+
Digest::SHA1.hexdigest(contents)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/prependers/errors.rb
CHANGED
data/lib/prependers/loader.rb
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
module Prependers
|
4
4
|
class Loader
|
5
|
+
UNDEFINED_PREPENDER_ERROR = \
|
6
|
+
"Expected `%{path}` to define `%{prepender}`, but it is not defined.\n\n" \
|
7
|
+
"This is most likely because the file has not been required.\n\n" \
|
8
|
+
"Require the file yourself before calling `Prependers.load_paths`."
|
9
|
+
|
5
10
|
attr_reader :base_path, :options
|
6
11
|
|
7
12
|
def initialize(base_path, options = {})
|
@@ -14,21 +19,20 @@ module Prependers
|
|
14
19
|
absolute_path = Pathname.new(File.expand_path(path))
|
15
20
|
relative_path = absolute_path.relative_path_from(base_path)
|
16
21
|
|
17
|
-
|
22
|
+
prepender_name = expected_module_for(relative_path)
|
18
23
|
|
19
|
-
unless Object.const_defined?(
|
20
|
-
|
21
|
-
|
24
|
+
unless Object.const_defined?(prepender_name)
|
25
|
+
raise NoPrependerError, UNDEFINED_PREPENDER_ERROR % {
|
26
|
+
path: absolute_path,
|
27
|
+
prepender: prepender_name,
|
28
|
+
}
|
29
|
+
end
|
22
30
|
|
23
|
-
|
24
|
-
yourself before calling `#load_paths`.
|
25
|
-
ERROR
|
31
|
+
prepender = Object.const_get(prepender_name)
|
26
32
|
|
27
|
-
|
33
|
+
if prepender.ancestors.none? { |ancestor| ancestor.is_a?(Prependers::Prepender) }
|
34
|
+
prepender.include(Prepender.new(options))
|
28
35
|
end
|
29
|
-
|
30
|
-
prepender_module = Object.const_get(prepender_module_name)
|
31
|
-
prepender_module.include Prepender.new(options[:namespace])
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
data/lib/prependers/prepender.rb
CHANGED
@@ -2,26 +2,40 @@
|
|
2
2
|
|
3
3
|
module Prependers
|
4
4
|
class Prepender < Module
|
5
|
-
|
5
|
+
def self.[](options = {})
|
6
|
+
new(options)
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :options
|
10
|
+
|
11
|
+
NAMESPACE_DEPRECATION = \
|
12
|
+
"[DEPRECATION] Passing a namespace to `Prependers::Prepender#[]` is deprecated. Use the " \
|
13
|
+
"`:namespace` option instead."
|
6
14
|
|
7
|
-
|
15
|
+
def initialize(options_or_namespace = {})
|
16
|
+
if options_or_namespace.is_a?(Module)
|
17
|
+
warn NAMESPACE_DEPRECATION % { namespace: options_or_namespace }
|
18
|
+
options_or_namespace = { namespace: options_or_namespace }
|
19
|
+
end
|
8
20
|
|
9
|
-
|
10
|
-
@namespace = namespace
|
21
|
+
@options = options_or_namespace
|
11
22
|
end
|
12
23
|
|
13
24
|
def included(base)
|
14
|
-
|
25
|
+
if options.key?(:namespace)
|
26
|
+
base.include Prependers::Annotate::Namespace.new(options[:namespace])
|
27
|
+
end
|
15
28
|
|
16
|
-
if
|
17
|
-
|
29
|
+
if options.key?(:verify)
|
30
|
+
base.include Prependers::Annotate::Verify.new(options[:verify])
|
18
31
|
end
|
19
32
|
|
20
|
-
|
21
|
-
|
33
|
+
prependable = Prependers.prependable_for(base)
|
34
|
+
|
35
|
+
prependable.prepend(base)
|
22
36
|
|
23
|
-
if base.const_defined?(
|
24
|
-
|
37
|
+
if base.const_defined?('ClassMethods')
|
38
|
+
prependable.singleton_class.prepend(base.const_get('ClassMethods'))
|
25
39
|
end
|
26
40
|
end
|
27
41
|
end
|
data/lib/prependers/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prependers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alessandro Desantis
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-05-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -157,6 +157,8 @@ files:
|
|
157
157
|
- bin/console
|
158
158
|
- bin/setup
|
159
159
|
- lib/prependers.rb
|
160
|
+
- lib/prependers/annotate/namespace.rb
|
161
|
+
- lib/prependers/annotate/verify.rb
|
160
162
|
- lib/prependers/errors.rb
|
161
163
|
- lib/prependers/loader.rb
|
162
164
|
- lib/prependers/prepender.rb
|