memosa 0.8.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 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: []