memosa 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f32b1b800bfdaf62015183e444f5ad4ecdf49584bb04ee31f85b5c7747a8aaa1
4
+ data.tar.gz: 3b4107c3d7c801b0fc9015ec41bb43743e8a5ddf424b6c4710b180c0e0b78356
5
+ SHA512:
6
+ metadata.gz: 62d00474986ac1d1a691454b07e97d33879ac7846af0edb39b77847c6e5928beee2788b45a3555acb3c2bd89980f14ff26477074d2cf7db8a5fe333b8ee03ccb
7
+ data.tar.gz: 967d8646d2fff68b257e7058570690d3e2df89c3d806982483a4bebb6e9524cfde5971bf0d55321408796cae56cb56d51aa57ead071e99f24e6d22f642741776
@@ -0,0 +1,10 @@
1
+ require:
2
+ - rubocop/cop/memosa
3
+
4
+ Memosa/MethodDefinition:
5
+ Description: "Disallows the use of `memoize` without a method definition"
6
+ Enabled: true
7
+
8
+ Memosa/MethodSignature:
9
+ Description: "Disallows the use of `memoize` for methods that accept arguments"
10
+ Enabled: true
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Memosa
4
+ # This class is used to interact with the memoization cache.
5
+ # @api public
6
+ class Cache
7
+ # Create a new instance of {Cache}
8
+ #
9
+ # @api private
10
+ # @param cache [Hash<Symbol, BasicObject>] the memoization cache
11
+ def initialize(cache)
12
+ @cache = cache
13
+ end
14
+
15
+ # Add values to the cache
16
+ #
17
+ # @api public
18
+ # @param values [Hash<Symbol, BasicObject>] the values to add to the cache.
19
+ # @return [self]
20
+ # @example
21
+ # memosa.merge!(foo: "bar")
22
+ def merge!(values)
23
+ @cache.merge!(values)
24
+ self
25
+ end
26
+
27
+ # Reset the cache
28
+ #
29
+ # @api public
30
+ # @return [self]
31
+ # @example
32
+ # memosa.clear
33
+ def clear
34
+ @cache.clear
35
+ self
36
+ end
37
+
38
+ # Delete a key from the cache
39
+ #
40
+ # @api public
41
+ # @param key [Symbol]
42
+ # @return [self]
43
+ # @example
44
+ # memosa.delete(:foo)
45
+ def delete(key)
46
+ @cache.delete(key)
47
+ self
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Memosa
4
+ # These methods are use internally by Memosa and are not intended for public use.
5
+ # @!visibility private
6
+ module Internal
7
+ # Determine the visibility of a method
8
+ #
9
+ # @param owner [Module]
10
+ # @param method_name [Symbol]
11
+ # @return [Symbol]
12
+ def self.visibility(owner, method_name)
13
+ if owner.private_method_defined?(method_name)
14
+ :private
15
+ elsif owner.protected_method_defined?(method_name)
16
+ :protected
17
+ elsif owner.public_method_defined?(method_name)
18
+ :public
19
+ else
20
+ raise ArgumentError, "`#{method_name.inspect}` is not defined"
21
+ end
22
+ end
23
+
24
+ # Determine if a method is defined (including private methods)
25
+ #
26
+ # @param owner [Module]
27
+ # @param method_name [Symbol]
28
+ # @return [Boolean]
29
+ def self.method_defined?(owner, method_name)
30
+ owner.method_defined?(method_name) || owner.private_method_defined?(method_name)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Memosa
4
+ # The current Memosa version
5
+ # @return [String]
6
+ VERSION = "0.8.0"
7
+ end
data/lib/memosa.rb ADDED
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "memosa/cache"
4
+ require_relative "memosa/internal"
5
+ require_relative "memosa/version"
6
+
7
+ # Memosa provides a simple way to memoize methods in Ruby.
8
+ module Memosa
9
+ # Convert a method to a memoized method
10
+ #
11
+ # Memosa does not support memoizing methods that accept arguments.
12
+ #
13
+ # @api public
14
+ # @param method_name [Symbol] the name of the method to memoize
15
+ # @return [Symbol] the name of the memoized method
16
+ # @raise [ArgumentError] when the method is is not defined
17
+ # @raise [ArgumentError] when the method is already memoized
18
+ # @example
19
+ # class State
20
+ # extend Memosa
21
+ #
22
+ # memoize def id
23
+ # SecureRandom.uuid
24
+ # end
25
+ # end
26
+ #
27
+ def memoize(method_name)
28
+ methods = prepend_memosa
29
+ visibility = Internal.visibility(self, method_name)
30
+
31
+ if Internal.method_defined?(methods, method_name)
32
+ raise ArgumentError, "`#{method_name.inspect}` is already memoized"
33
+ end
34
+
35
+ methods.module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
36
+ #{visibility} def #{method_name}
37
+ raise ArgumentError, "unsupported block argument" if block_given?
38
+
39
+ cache = (@_memosa_cache ||= {})
40
+ cache.fetch(:#{method_name}) do
41
+ cache[:#{method_name}] = super()
42
+ end
43
+ end
44
+ RUBY
45
+
46
+ method_name
47
+ end
48
+
49
+ # Define and prepend MemosaMethods
50
+ #
51
+ # When {memoize} is called, Memosa will define a module named MemosaMethods. Memoized
52
+ # methods will be defined on this module and then prepended to the class.
53
+ #
54
+ # This method allows you to force that module to be defined and prepended, which is
55
+ # useful when order matters.
56
+ #
57
+ # @api public
58
+ # @return [Module]
59
+ # @example
60
+ # class Foo
61
+ # extend Memosa
62
+ # prepend_memosa
63
+ # end
64
+ def prepend_memosa
65
+ prepend Initializer
66
+
67
+ if const_defined?(:MemosaMethods, false)
68
+ const_get(:MemosaMethods, false)
69
+ else
70
+ const_set(:MemosaMethods, Module.new).tap { |mod| prepend(mod) }
71
+ end
72
+ end
73
+
74
+ # Reset an object's memoization cache
75
+ #
76
+ # @api public
77
+ # @param object [Object]
78
+ # @return [void]
79
+ # @example
80
+ # Memosa.reset(user)
81
+ def self.reset(object)
82
+ cache = object.instance_variable_get(:@_memosa_cache)
83
+ cache&.clear
84
+ nil
85
+ end
86
+
87
+ # This module is responsible for initializing the cache
88
+ #
89
+ # @!visibility private
90
+ module Initializer
91
+ # Eagerly initialize the cache before the object is frozen
92
+ #
93
+ # This ensures that frozen objects can still have memoized methods.
94
+ #
95
+ # @return [self]
96
+ def freeze
97
+ @_memosa_cache ||= {}
98
+ super
99
+ end
100
+ end
101
+
102
+ # This module provides a {#memosa} helper that can be used to manipulate the
103
+ # memoization cache directly.
104
+ module API
105
+ # Used to manipulate the memoized cache directly
106
+ #
107
+ # @api public
108
+ # @return [Memosa::Cache]
109
+ # @example
110
+ # class State
111
+ # extend Memosa
112
+ # include Memosa::API
113
+ #
114
+ # memoize def id
115
+ # SecureRandom.uuid
116
+ # end
117
+ #
118
+ # def reset!
119
+ # memosa.clear
120
+ # end
121
+ # end
122
+ def memosa
123
+ ::Memosa::Cache.new(@_memosa_cache ||= {})
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+
5
+ module RuboCop
6
+ module Cop
7
+ # Memosa includes a set of RuboCop rules that encourage best practices when using
8
+ # memosa.
9
+ #
10
+ # Add the following configuration to your `.rubocop.yml`:
11
+ #
12
+ # @example
13
+ # inherit_gem:
14
+ # memosa: config/rubocop.yml
15
+ #
16
+ module Memosa
17
+ # This cop checks for memoized methods that accept arguments or yield.
18
+ #
19
+ # @example
20
+ # # bad
21
+ # def foo; end
22
+ # memoize :foo
23
+ #
24
+ # # good
25
+ # memoize def foo; end
26
+ class MethodDefinition < RuboCop::Cop::Base
27
+ # @return [String]
28
+ MSG = "`memoize` should precede a method definition (e.g. `memoize def`)"
29
+
30
+ def_node_matcher :invalid_usage?, <<~PATTERN
31
+ (send nil? :memoize (sym _))
32
+ PATTERN
33
+
34
+ # @!visibility private
35
+ def on_send(node)
36
+ add_offense(node.selector) if invalid_usage?(node)
37
+ end
38
+ end
39
+
40
+ # This cop checks for memoized methods that accept arguments or yield.
41
+ #
42
+ # @example
43
+ # # bad
44
+ # memoize def foo(a); end
45
+ #
46
+ # # good
47
+ # memoize def foo; end
48
+ class MethodSignature < RuboCop::Cop::Base
49
+ # @return [String]
50
+ MSG = "Memoized methods should not accept arguments or yield"
51
+
52
+ def_node_matcher :invalid_signature?, <<~PATTERN
53
+ (send nil? :memoize {(def _name (args _+) _body) | (def _name _args `yield)})
54
+ PATTERN
55
+
56
+ # @!visibility private
57
+ def on_send(node)
58
+ add_offense(node.selector) if invalid_signature?(node)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
data/rbi/memosa.rbi ADDED
@@ -0,0 +1,152 @@
1
+ # typed: strong
2
+ # Memosa provides a simple way to memoize methods in Ruby.
3
+ module Memosa
4
+ VERSION = T.let("0.8.0", T.untyped)
5
+
6
+ # Convert a method to a memoized method
7
+ #
8
+ # Memosa does not support memoizing methods that accept arguments.
9
+ #
10
+ # _@param_ `method_name` — the name of the method to memoize
11
+ #
12
+ # _@return_ — the name of the memoized method
13
+ #
14
+ # ```ruby
15
+ # class State
16
+ # extend Memosa
17
+ #
18
+ # memoize def id
19
+ # SecureRandom.uuid
20
+ # end
21
+ # end
22
+ # ```
23
+ sig { params(method_name: Symbol).returns(Symbol) }
24
+ def memoize(method_name); end
25
+
26
+ # Define and prepend MemosaMethods
27
+ #
28
+ # When {memoize} is called, Memosa will define a module named MemosaMethods. Memoized
29
+ # methods will be defined on this module and then prepended to the class.
30
+ #
31
+ # This method allows you to force that module to be defined and prepended, which is
32
+ # useful when order matters.
33
+ #
34
+ # ```ruby
35
+ # class Foo
36
+ # extend Memosa
37
+ # prepend_memosa
38
+ # end
39
+ # ```
40
+ sig { returns(Module) }
41
+ def prepend_memosa; end
42
+
43
+ # Reset an object's memoization cache
44
+ #
45
+ # _@param_ `object`
46
+ #
47
+ # ```ruby
48
+ # Memosa.reset(user)
49
+ # ```
50
+ sig { params(object: Object).void }
51
+ def self.reset(object); end
52
+
53
+ # This module provides a {#memosa} helper that can be used to manipulate the
54
+ # memoization cache directly.
55
+ module API
56
+ # Used to manipulate the memoized cache directly
57
+ #
58
+ # ```ruby
59
+ # class State
60
+ # extend Memosa
61
+ # include Memosa::API
62
+ #
63
+ # memoize def id
64
+ # SecureRandom.uuid
65
+ # end
66
+ #
67
+ # def reset!
68
+ # memosa.clear
69
+ # end
70
+ # end
71
+ # ```
72
+ sig { returns(Memosa::Cache) }
73
+ def memosa; end
74
+ end
75
+
76
+ # This class is used to interact with the memoization cache.
77
+ # @api public
78
+ class Cache
79
+ # Create a new instance of {Cache}
80
+ #
81
+ # _@param_ `cache` — the memoization cache
82
+ sig { params(cache: T::Hash[Symbol, BasicObject]).void }
83
+ def initialize(cache); end
84
+
85
+ # Add values to the cache
86
+ #
87
+ # _@param_ `values` — the values to add to the cache.
88
+ #
89
+ # ```ruby
90
+ # memosa.merge!(foo: "bar")
91
+ # ```
92
+ sig { params(values: T::Hash[Symbol, BasicObject]).returns(T.self_type) }
93
+ def merge!(values); end
94
+
95
+ # Reset the cache
96
+ #
97
+ # ```ruby
98
+ # memosa.clear
99
+ # ```
100
+ sig { returns(T.self_type) }
101
+ def clear; end
102
+
103
+ # Delete a key from the cache
104
+ #
105
+ # _@param_ `key`
106
+ #
107
+ # ```ruby
108
+ # memosa.delete(:foo)
109
+ # ```
110
+ sig { params(key: Symbol).returns(T.self_type) }
111
+ def delete(key); end
112
+ end
113
+ end
114
+
115
+ module RuboCop
116
+ module Cop
117
+ # Memosa includes a set of RuboCop rules that encourage best practices when using
118
+ # memosa.
119
+ #
120
+ # Add the following configuration to your `.rubocop.yml`:
121
+ #
122
+ # @example
123
+ # inherit_gem:
124
+ # memosa: config/rubocop.yml
125
+ module Memosa
126
+ # This cop checks for memoized methods that accept arguments or yield.
127
+ #
128
+ # @example
129
+ # # bad
130
+ # def foo; end
131
+ # memoize :foo
132
+ #
133
+ # # good
134
+ # memoize def foo; end
135
+ class MethodDefinition < RuboCop::Cop::Base
136
+ MSG = T.let("`memoize` should precede a method definition (e.g. `memoize def`)", T.untyped)
137
+ end
138
+
139
+ # This cop checks for memoized methods that accept arguments or yield.
140
+ #
141
+ # @example
142
+ # # bad
143
+ # memoize def foo(a); end
144
+ #
145
+ # # good
146
+ # memoize def foo; end
147
+ class MethodSignature < RuboCop::Cop::Base
148
+ MSG = T.let("Memoized methods should not accept arguments or yield", T.untyped)
149
+ end
150
+ end
151
+ end
152
+ end
data/sig/memosa.rbs ADDED
@@ -0,0 +1,143 @@
1
+ # Memosa provides a simple way to memoize methods in Ruby.
2
+ module Memosa
3
+ VERSION: String
4
+
5
+ # Convert a method to a memoized method
6
+ #
7
+ # Memosa does not support memoizing methods that accept arguments.
8
+ #
9
+ # _@param_ `method_name` — the name of the method to memoize
10
+ #
11
+ # _@return_ — the name of the memoized method
12
+ #
13
+ # ```ruby
14
+ # class State
15
+ # extend Memosa
16
+ #
17
+ # memoize def id
18
+ # SecureRandom.uuid
19
+ # end
20
+ # end
21
+ # ```
22
+ def memoize: (Symbol method_name) -> Symbol
23
+
24
+ # Define and prepend MemosaMethods
25
+ #
26
+ # When {memoize} is called, Memosa will define a module named MemosaMethods. Memoized
27
+ # methods will be defined on this module and then prepended to the class.
28
+ #
29
+ # This method allows you to force that module to be defined and prepended, which is
30
+ # useful when order matters.
31
+ #
32
+ # ```ruby
33
+ # class Foo
34
+ # extend Memosa
35
+ # prepend_memosa
36
+ # end
37
+ # ```
38
+ def prepend_memosa: () -> Module
39
+
40
+ # Reset an object's memoization cache
41
+ #
42
+ # _@param_ `object`
43
+ #
44
+ # ```ruby
45
+ # Memosa.reset(user)
46
+ # ```
47
+ def self.reset: (Object object) -> void
48
+
49
+ # This module provides a {#memosa} helper that can be used to manipulate the
50
+ # memoization cache directly.
51
+ module API
52
+ # Used to manipulate the memoized cache directly
53
+ #
54
+ # ```ruby
55
+ # class State
56
+ # extend Memosa
57
+ # include Memosa::API
58
+ #
59
+ # memoize def id
60
+ # SecureRandom.uuid
61
+ # end
62
+ #
63
+ # def reset!
64
+ # memosa.clear
65
+ # end
66
+ # end
67
+ # ```
68
+ def memosa: () -> Memosa::Cache
69
+ end
70
+
71
+ # This class is used to interact with the memoization cache.
72
+ # @api public
73
+ class Cache
74
+ # Create a new instance of {Cache}
75
+ #
76
+ # _@param_ `cache` — the memoization cache
77
+ def initialize: (::Hash[Symbol, BasicObject] cache) -> void
78
+
79
+ # Add values to the cache
80
+ #
81
+ # _@param_ `values` — the values to add to the cache.
82
+ #
83
+ # ```ruby
84
+ # memosa.merge!(foo: "bar")
85
+ # ```
86
+ def merge!: (::Hash[Symbol, BasicObject] values) -> self
87
+
88
+ # Reset the cache
89
+ #
90
+ # ```ruby
91
+ # memosa.clear
92
+ # ```
93
+ def clear: () -> self
94
+
95
+ # Delete a key from the cache
96
+ #
97
+ # _@param_ `key`
98
+ #
99
+ # ```ruby
100
+ # memosa.delete(:foo)
101
+ # ```
102
+ def delete: (Symbol key) -> self
103
+ end
104
+ end
105
+
106
+ module RuboCop
107
+ module Cop
108
+ # Memosa includes a set of RuboCop rules that encourage best practices when using
109
+ # memosa.
110
+ #
111
+ # Add the following configuration to your `.rubocop.yml`:
112
+ #
113
+ # @example
114
+ # inherit_gem:
115
+ # memosa: config/rubocop.yml
116
+ module Memosa
117
+ # This cop checks for memoized methods that accept arguments or yield.
118
+ #
119
+ # @example
120
+ # # bad
121
+ # def foo; end
122
+ # memoize :foo
123
+ #
124
+ # # good
125
+ # memoize def foo; end
126
+ class MethodDefinition < RuboCop::Cop::Base
127
+ MSG: String
128
+ end
129
+
130
+ # This cop checks for memoized methods that accept arguments or yield.
131
+ #
132
+ # @example
133
+ # # bad
134
+ # memoize def foo(a); end
135
+ #
136
+ # # good
137
+ # memoize def foo; end
138
+ class MethodSignature < RuboCop::Cop::Base
139
+ MSG: String
140
+ end
141
+ end
142
+ end
143
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: memosa
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.0
5
+ platform: ruby
6
+ authors:
7
+ - Ray Zane
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-02-14 00:00:00.000000000 Z
11
+ dependencies: []
12
+ email:
13
+ - raymondzane@gmail.com
14
+ executables: []
15
+ extensions: []
16
+ extra_rdoc_files: []
17
+ files:
18
+ - config/rubocop.yml
19
+ - lib/memosa.rb
20
+ - lib/memosa/cache.rb
21
+ - lib/memosa/internal.rb
22
+ - lib/memosa/version.rb
23
+ - lib/rubocop/cop/memosa.rb
24
+ - rbi/memosa.rbi
25
+ - sig/memosa.rbs
26
+ homepage: https://github.com/rzane/memosa
27
+ licenses: []
28
+ metadata:
29
+ allowed_push_host: https://rubygems.org
30
+ homepage_uri: https://github.com/rzane/memosa
31
+ source_code_uri: https://github.com/rzane/memosa
32
+ changelog_uri: https://github.com/rzane/memosa/releases
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 3.2.0
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubygems_version: 3.6.2
48
+ specification_version: 4
49
+ summary: A brutally simple macro for memoizing methods.
50
+ test_files: []