annotable 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/annotable.rb ADDED
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "annotable/annotation"
4
+ require_relative "annotable/method"
5
+ require_relative "annotable/version"
6
+
7
+ #
8
+ # Annotable is a module that can extend any class or module to add the ability to annotate its methods.
9
+ #
10
+ # class Needy
11
+ # extend Annotable # Extend your class or module with Annotable
12
+ # annotable :my_annotation, :my_other_annotation # Declare your annotations
13
+ #
14
+ # my_annotation 42, hello: "world!" # Annotate your methods
15
+ # def method_needing_meta_data
16
+ # # ...
17
+ # end
18
+ #
19
+ # def regular_method
20
+ # # ...
21
+ # end
22
+ # end
23
+ #
24
+ # `Annotable` adds several methods to your class or module, providing access to annotations and their metadata:
25
+ #
26
+ # Needy.annotated_method_exist? :method_needing_meta_data # => true
27
+ # Needy.annotated_method_exist? :regular_method # => false
28
+ # Needy.annotated_methods
29
+ # # => [#<Annotable::Method
30
+ # # @annotations=[
31
+ # # #<Annotable::Annotation
32
+ # # @name=:my_annotation,
33
+ # # @options={:hello=>"world!"},
34
+ # # @params=[42]>
35
+ # # ],
36
+ # # @name=:method_needing_meta_data>
37
+ # # ]
38
+ #
39
+ # `Annotable::Method` represents a method name along with its annotations:
40
+ #
41
+ # method = Needy.annotated_methods.first
42
+ # method.name # => :method_needing_meta_data
43
+ # method.annotations
44
+ # # => [
45
+ # # #<Annotable::Annotation
46
+ # # @name=:my_annotation,
47
+ # # @options={:hello=>"world!"},
48
+ # # @params=[42]>
49
+ # # ]
50
+ #
51
+ # `Annotable::Annotation` contains annotation's name and metadata:
52
+ #
53
+ # annotation = method.annotations.first
54
+ # annotation.name # => :my_annotation
55
+ # annotation.params # => [42]
56
+ # annotation.options # => {:hello => "world!"}
57
+ #
58
+ module Annotable
59
+ #
60
+ # Declares annotations usable in the module or class.
61
+ #
62
+ # annotable :my_annotation, :my_other_annotation
63
+ #
64
+ # This will generate two class methods named after the given symbols.
65
+ # These methods, will push a new `Annotation` in the `current_annotation` array.
66
+ #
67
+ # @param [Array<Symbol>] annotation_names The names of annotations to declare
68
+ #
69
+ # @return [void]
70
+ #
71
+ def annotable(*annotation_names)
72
+ raise ArgumentError, "You must provide at least one annotation name" if annotation_names.empty?
73
+
74
+ annotation_names.each do |name|
75
+ define_singleton_method(name) do |*params, **options|
76
+ current_annotations.push Annotable::Annotation.new(name, params, options)
77
+ end
78
+ end
79
+ end
80
+
81
+ #
82
+ # Return all annotated methods or those matching the given annotation names.
83
+ #
84
+ # @param [Array<Symbol>] names The annotation names to find
85
+ #
86
+ # @return [Array<Annotable::Method>] The annotated methods
87
+ #
88
+ def annotated_methods(*names)
89
+ return @annotated_methods if @annotated_methods.empty? || names.empty?
90
+
91
+ @annotated_methods.select do |method|
92
+ annotation_found = false
93
+
94
+ names.each do |name|
95
+ annotation_found = method.annotation_exist?(name)
96
+ break if annotation_found
97
+ end
98
+
99
+ annotation_found
100
+ end
101
+ end
102
+
103
+ #
104
+ # Check if an annotated method exists based in its name.
105
+ #
106
+ # @param [Symbol] name The name to check
107
+ #
108
+ # @return [Boolean] True if the annotated method exists, false otherwise
109
+ #
110
+ def annotated_method_exist?(name)
111
+ !annotated_methods.find { |am| am.name == name }.nil?
112
+ end
113
+
114
+ private
115
+
116
+ #
117
+ # A callback called by Ruby when a method is added into the class or module.
118
+ #
119
+ # @param [Symbol] name The name of the created method
120
+ #
121
+ # @return [void]
122
+ #
123
+ def method_added(name)
124
+ super
125
+ @annotated_methods ||= []
126
+ return if current_annotations.empty?
127
+
128
+ remove_annotated_method(name) if annotated_method_exist?(name)
129
+ @annotated_methods.push Annotable::Method.new(name, *current_annotations)
130
+
131
+ reset_current_annotations
132
+ end
133
+
134
+ #
135
+ # Remove an annotated method based on its name.
136
+ #
137
+ # @param [Symbol] name The name of the method to delete
138
+ #
139
+ # @return [void]
140
+ #
141
+ def remove_annotated_method(name)
142
+ @annotated_methods.reject! do |annotated_method|
143
+ annotated_method.name == name
144
+ end
145
+ end
146
+
147
+ #
148
+ # Annotation found for the current method declaration.
149
+ #
150
+ # @return [Array<Annotable::Annotation>] The annotations for the current method declaration
151
+ #
152
+ def current_annotations
153
+ @current_annotations ||= []
154
+ end
155
+
156
+ #
157
+ # Empty the current annotation array.
158
+ #
159
+ # @return [void]
160
+ #
161
+ def reset_current_annotations
162
+ @current_annotations = []
163
+ end
164
+ end
data/sig/annotable.rbs ADDED
@@ -0,0 +1,168 @@
1
+ #
2
+ # Annotable is a module that can extend any class or module to add the ability to annotate its methods.
3
+ #
4
+ # class Needy
5
+ # extend Annotable # Extend your class or module with Annotable
6
+ # annotable :my_annotation, :my_other_annotation # Declare your annotations
7
+ #
8
+ # my_annotation 42, hello: "world!" # Annotate your methods
9
+ # def method_needing_meta_data
10
+ # # ...
11
+ # end
12
+ #
13
+ # def regular_method
14
+ # # ...
15
+ # end
16
+ # end
17
+ #
18
+ # `Annotable` adds several methods to your class or module, providing access to annotations and their metadata:
19
+ #
20
+ # Needy.annotated_method_exist? :method_needing_meta_data # => true
21
+ # Needy.annotated_method_exist? :regular_method # => false
22
+ # Needy.annotated_methods
23
+ # # => [#<Annotable::Method
24
+ # # @annotations=[
25
+ # # #<Annotable::Annotation
26
+ # # @name=:my_annotation,
27
+ # # @options={:hello=>"world!"},
28
+ # # @params=[42]>
29
+ # # ],
30
+ # # @name=:method_needing_meta_data>
31
+ # # ]
32
+ #
33
+ # `Annotable::Method` represents a method name along with its annotations:
34
+ #
35
+ # method = Needy.annotated_methods.first
36
+ # method.name # => :method_needing_meta_data
37
+ # method.annotations
38
+ # # => [
39
+ # # #<Annotable::Annotation
40
+ # # @name=:my_annotation,
41
+ # # @options={:hello=>"world!"},
42
+ # # @params=[42]>
43
+ # # ]
44
+ #
45
+ # `Annotable::Annotation` contains annotation's name and metadata:
46
+ #
47
+ # annotation = method.annotations.first
48
+ # annotation.name # => :my_annotation
49
+ # annotation.params # => [42]
50
+ # annotation.options # => {:hello => "world!"}
51
+ module Annotable
52
+ VERSION: String
53
+
54
+ # Declares annotations usable in the module or class.
55
+ #
56
+ # annotable :my_annotation, :my_other_annotation
57
+ #
58
+ # This will generate two class methods named after the given symbols.
59
+ # These methods, will push a new `Annotation` in the `current_annotation` array.
60
+ #
61
+ # _@param_ `annotation_names` — The names of annotations to declare
62
+ def annotable: (*::Array[Symbol] annotation_names) -> void
63
+
64
+ # Return all annotated methods or those matching the given annotation names.
65
+ #
66
+ # _@param_ `names` — The annotation names to find
67
+ #
68
+ # _@return_ — The annotated methods
69
+ def annotated_methods: (*::Array[Symbol] names) -> ::Array[Annotable::Method]
70
+
71
+ # Check if an annotated method exists based in its name.
72
+ #
73
+ # _@param_ `name` — The name to check
74
+ #
75
+ # _@return_ — True if the annotated method exists, false otherwise
76
+ def annotated_method_exist?: (Symbol name) -> bool
77
+
78
+ # A callback called by Ruby when a method is added into the class or module.
79
+ #
80
+ # _@param_ `name` — The name of the created method
81
+ def method_added: (Symbol name) -> void
82
+
83
+ # Remove an annotated method based on its name.
84
+ #
85
+ # _@param_ `name` — The name of the method to delete
86
+ def remove_annotated_method: (Symbol name) -> void
87
+
88
+ # Annotation found for the current method declaration.
89
+ #
90
+ # _@return_ — The annotations for the current method declaration
91
+ def current_annotations: () -> ::Array[Annotable::Annotation]
92
+
93
+ # Empty the current annotation array.
94
+ def reset_current_annotations: () -> void
95
+
96
+ #
97
+ # An annotated method with its annotations.
98
+ #
99
+ # one = Annotation.new(:one)
100
+ # two = Annotation.new(:two)
101
+ # some_method = Method.new(:some_method, one, two)
102
+ #
103
+ # some_method.annotation_exist?(:one) # => true
104
+ # some_method.annotation_exist?(:no) # => false
105
+ # some_method.select_annotations(:one) # => [#<Annotable::Annotation @name=:one, @options={}, @params=[]>]
106
+ class Method
107
+ # Creates a new annotated method.
108
+ #
109
+ # _@param_ `name` — The method name
110
+ #
111
+ # _@param_ `annotations` — The annotations linked to this method
112
+ def initialize: (Symbol name, *::Array[Annotation] annotations) -> void
113
+
114
+ # Determines whether annotation exists based on its name.
115
+ #
116
+ # _@param_ `name` — The annotation's name to check for
117
+ #
118
+ # _@return_ — True if the annotation exists, false otherwise
119
+ def annotation_exist?: (Symbol name) -> bool
120
+
121
+ # Returns all annotations matching the given names.
122
+ #
123
+ # _@param_ `names` — Names of the annotations to select
124
+ #
125
+ # _@return_ — The matching annotations
126
+ def select_annotations: (*::Array[Symbol] names) -> ::Array[Annotation]
127
+
128
+ # Finds the first annotation matching one of the given names.
129
+ #
130
+ # _@param_ `*names` — The annotation names to find
131
+ #
132
+ # _@return_ — The matching annotation
133
+ def find_annotation: (*::Array[Symbol] names) -> Annotation
134
+
135
+ # _@return_ — The method name
136
+ attr_reader name: Symbol
137
+
138
+ # _@return_ — The annotations declared for this method
139
+ attr_reader annotations: ::Array[Annotation]
140
+ end
141
+
142
+ #
143
+ # Encapsulates annotation data: name, params & options.
144
+ #
145
+ # my_annotation = Annotation.new(:name, ["some", "params"], {some: "options"})
146
+ # my_annotation.name # => :name
147
+ # my_annotation.params # => ["some", "params"]
148
+ # my_annotation.options # => {some: "options"}
149
+ class Annotation
150
+ # Creates an new annotation.
151
+ #
152
+ # _@param_ `name` — The annotation's name
153
+ #
154
+ # _@param_ `params` — The annotation's params
155
+ #
156
+ # _@param_ `options` — The annotation's options
157
+ def initialize: (Symbol name, ?::Array[Object] params, ?::Hash[Symbol, Object] options) -> void
158
+
159
+ # _@return_ — The annotation's name
160
+ attr_reader name: Symbol
161
+
162
+ # _@return_ — The annotation's params
163
+ attr_reader params: ::Array[Object]
164
+
165
+ # _@return_ — The annotation's options
166
+ attr_reader options: ::Hash[Symbol, Object]
167
+ end
168
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: annotable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Mathieu MOREL
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-03-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '13.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '13.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.11'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.11'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.29'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.29'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop-rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.21'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.21'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sord
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '4.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '4.0'
97
+ description: Provides a simple way to add annotations to your method declarations.
98
+ email:
99
+ - mathieu@lamanufacture.dev
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".rspec"
105
+ - ".rubocop.yml"
106
+ - ".vscode/settings.json"
107
+ - COPYING
108
+ - COPYING.LESSER
109
+ - Gemfile
110
+ - Gemfile.lock
111
+ - README.md
112
+ - Rakefile
113
+ - annotable.gemspec
114
+ - lib/annotable.rb
115
+ - lib/annotable/annotation.rb
116
+ - lib/annotable/method.rb
117
+ - lib/annotable/version.rb
118
+ - sig/annotable.rbs
119
+ homepage: https://github.com/ductr-io/annotable
120
+ licenses:
121
+ - LGPL-3.0-or-later
122
+ metadata:
123
+ allowed_push_host: https://rubygems.org
124
+ rubygems_mfa_required: 'true'
125
+ homepage_uri: https://github.com/ductr-io/annotable
126
+ source_code_uri: https://github.com/ductr-io/annotable
127
+ changelog_uri: https://github.com/ductr-io/annotable/releases
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: 3.1.0
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubygems_version: 3.3.26
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: A simple zero-dependency method annotation gem
147
+ test_files: []