tag_ripper 0.1.0 → 0.2.1
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.yml +4 -2
- data/.ruby-version +1 -1
- data/Guardfile +12 -0
- data/README.md +30 -21
- data/lib/tag_ripper/lexical_token.rb +29 -10
- data/lib/tag_ripper/taggable_entity.rb +132 -38
- data/lib/tag_ripper/version.rb +1 -1
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e80933d8c317438627b041b30bd905ae33ed8d993db4ef149e69f3f71fd30d7
|
4
|
+
data.tar.gz: 7eeb22fc466b8b50da7f6d3e887dfc02c04d0de462abf870f36bf0beda6b3a68
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 131915acf06d838e8d5aa62ee92b62eab06743071298aa57fb887975cd9414b825d9929a40349bd6e4607ee5b7475eb41dc205939dc8a86d8c51856880317692
|
7
|
+
data.tar.gz: e7e86cc4d7984e3f05d1743d42892ee235152467a16f64edbc64cfe79d9683a5130aa013ce193084adb3348d509cc06938d78bbf5d2823efcb7a33ef61fbcf89
|
data/.rubocop.yml
CHANGED
@@ -8,7 +8,10 @@ AllCops:
|
|
8
8
|
TargetRubyVersion: 3.1
|
9
9
|
NewCops: enable
|
10
10
|
SuggestExtensions: true
|
11
|
-
|
11
|
+
Exclude:
|
12
|
+
- bin/*
|
13
|
+
- test/fixtures/**/* # These files have particular styles that should be preserved
|
14
|
+
- samples/**/* # These files have particular styles that should be preserved
|
12
15
|
Metrics/AbcSize:
|
13
16
|
Exclude:
|
14
17
|
- test/**/*
|
@@ -30,4 +33,3 @@ Style/TrivialAccessors:
|
|
30
33
|
|
31
34
|
Style/StringLiteralsInInterpolation:
|
32
35
|
EnforcedStyle: double_quotes
|
33
|
-
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.4.
|
1
|
+
3.4.3
|
data/Guardfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
guard :minitest, cli: "--verbose" do
|
2
|
+
watch(%r{^test/(.+)_test\.rb$})
|
3
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
|
4
|
+
watch("test/test_helper.rb") { "test" }
|
5
|
+
end
|
6
|
+
|
7
|
+
guard :rubocop, all_on_start: false do
|
8
|
+
watch(/^.+\.rb$/)
|
9
|
+
watch(%r{^(config|test)/.+\.rb$})
|
10
|
+
watch("Gemfile")
|
11
|
+
watch(".rubocop.yml")
|
12
|
+
end
|
data/README.md
CHANGED
@@ -5,27 +5,9 @@
|
|
5
5
|
|
6
6
|
Lets you annotate Ruby code with tags that can be parsed and collected in code.
|
7
7
|
|
8
|
-
Example:
|
9
|
-
|
10
|
-
```ruby
|
11
|
-
# @domain: Auth
|
12
|
-
class User
|
13
|
-
# @warning: untested
|
14
|
-
def some_method_that_isnt_tested
|
15
|
-
# ...
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
TagRipper.new(File.read('user.rb')).taggables
|
20
|
-
# => [
|
21
|
-
# <id=22040, @name=some_method_that_isnt_tested, tags={"warning" => #<Set: {"untested"}>},parent=#<TagRipper::TaggableEntity:0x00000001203bf468>>,
|
22
|
-
# <id=22224, @name=User, tags={"domain" => #<Set: {"Auth"}>},parent=>nil
|
23
|
-
# ]
|
24
|
-
```
|
25
8
|
|
26
9
|
## Installation
|
27
10
|
|
28
|
-
TODO: Replace `tag_ripper` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
29
11
|
|
30
12
|
Install the gem and add to the application's Gemfile by executing:
|
31
13
|
|
@@ -41,7 +23,34 @@ gem install tag_ripper
|
|
41
23
|
|
42
24
|
## Usage
|
43
25
|
|
44
|
-
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# @domain: Auth
|
29
|
+
class User
|
30
|
+
# @warning: untested
|
31
|
+
def some_method_that_isnt_tested
|
32
|
+
# ...
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
TagRipper.new(File.read('user.rb')).taggables
|
37
|
+
# (Beautified output)
|
38
|
+
# ---
|
39
|
+
# -
|
40
|
+
# id: 2221
|
41
|
+
# name: some_method_that_isnt_tested
|
42
|
+
# tags:
|
43
|
+
# warning:
|
44
|
+
# - "untested
|
45
|
+
# parent: 22224,
|
46
|
+
# -
|
47
|
+
# id: 22224
|
48
|
+
# name: User
|
49
|
+
# tags:
|
50
|
+
# domain:
|
51
|
+
# - "Auth"
|
52
|
+
# parent: nil
|
53
|
+
```
|
45
54
|
|
46
55
|
## Development
|
47
56
|
|
@@ -51,7 +60,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
51
60
|
|
52
61
|
## Contributing
|
53
62
|
|
54
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/bodacious/
|
63
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/bodacious/tag_ripper. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/bodacious/tag_ripper/blob/master/CODE_OF_CONDUCT.md).
|
55
64
|
|
56
65
|
## License
|
57
66
|
|
@@ -59,4 +68,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
59
68
|
|
60
69
|
## Code of Conduct
|
61
70
|
|
62
|
-
Everyone interacting in the Tag::Ripper project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bodacious/
|
71
|
+
Everyone interacting in the Tag::Ripper project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bodacious/tag_ripper/blob/master/CODE_OF_CONDUCT.md).
|
@@ -3,8 +3,22 @@ module TagRipper
|
|
3
3
|
require "forwardable"
|
4
4
|
extend Forwardable
|
5
5
|
|
6
|
-
TAG_REGEX = /#\s@(?<tag_name>[\
|
7
|
-
|
6
|
+
TAG_REGEX = /#\s@(?<tag_name>[\w-]+):\s(?<tag_value>.+)/
|
7
|
+
|
8
|
+
##
|
9
|
+
# = Operators =
|
10
|
+
|
11
|
+
DOUBLE_COLON = "::".freeze
|
12
|
+
SINGLETON_CLASS = "<<".freeze
|
13
|
+
|
14
|
+
##
|
15
|
+
# = Keywords =
|
16
|
+
|
17
|
+
KEYWORD_CONST = "const".freeze
|
18
|
+
KEYWORD_CLASS = "class".freeze
|
19
|
+
KEYWORD_DEF = "def".freeze
|
20
|
+
KEYWORD_END = "end".freeze
|
21
|
+
KEYWORD_MODULE = "module".freeze
|
8
22
|
|
9
23
|
class Location
|
10
24
|
attr_reader :col
|
@@ -49,12 +63,20 @@ module TagRipper
|
|
49
63
|
comment? && token.match?(TAG_REGEX)
|
50
64
|
end
|
51
65
|
|
66
|
+
def double_colon?
|
67
|
+
token == DOUBLE_COLON
|
68
|
+
end
|
69
|
+
|
70
|
+
def singleton_class?
|
71
|
+
token == SINGLETON_CLASS
|
72
|
+
end
|
73
|
+
|
52
74
|
def keyword?
|
53
75
|
type == :on_kw
|
54
76
|
end
|
55
77
|
|
56
78
|
def end?
|
57
|
-
keyword? && token ==
|
79
|
+
keyword? && token == KEYWORD_END
|
58
80
|
end
|
59
81
|
|
60
82
|
def tag_name
|
@@ -72,13 +94,10 @@ module TagRipper
|
|
72
94
|
def on_kw_type
|
73
95
|
return nil unless keyword?
|
74
96
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
else
|
80
|
-
:unknown
|
81
|
-
end
|
97
|
+
const_lookup = "KEYWORD_#{token.upcase}"
|
98
|
+
return :unknown unless self.class.const_defined?(const_lookup)
|
99
|
+
|
100
|
+
self.class.const_get(const_lookup).to_sym
|
82
101
|
end
|
83
102
|
end
|
84
103
|
end
|
@@ -9,22 +9,43 @@ module TagRipper
|
|
9
9
|
# entity is spawned. This creates a sort of recursion that allows a taggable
|
10
10
|
# entity to be flexible to any amount of code nesting.
|
11
11
|
class TaggableEntity
|
12
|
+
# Unable to move transition from one state to another
|
12
13
|
class IllegalStateTransitionError < StandardError
|
13
14
|
def initialize(from:, to:)
|
14
15
|
super("Cannot transition from #{from} to #{to}")
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
+
# Attempting to set status to an unknown value
|
20
|
+
class InvalidStatusError < ArgumentError; end
|
21
|
+
|
22
|
+
# TODO: define naming state, to represent a partial name token
|
23
|
+
# The valid statuses that a TaggableEntity can move through.
|
24
|
+
# @return [Array<Symbol>]
|
25
|
+
VALID_STATUSES = %i[
|
26
|
+
pending
|
27
|
+
tagged
|
28
|
+
awaiting_name
|
29
|
+
named
|
30
|
+
closed
|
31
|
+
].freeze
|
32
|
+
|
33
|
+
# Statuses that represent an open lexical scope.
|
34
|
+
# @return [Array<Symbol>]
|
35
|
+
OPENED_STATUSES = %i[tagged awaiting_name named].freeze
|
36
|
+
|
37
|
+
def initialize(name: nil, parent: nil, type: nil)
|
19
38
|
@name = name
|
20
39
|
@tags = Hash.new { |hash, key| hash[key] = Set.new }
|
21
40
|
@parent = parent
|
22
|
-
@type =
|
23
|
-
|
41
|
+
@type = type
|
42
|
+
self.status = :pending
|
24
43
|
end
|
25
44
|
|
45
|
+
alias id object_id
|
46
|
+
|
26
47
|
def send_event(event_name, lex)
|
27
|
-
|
48
|
+
debug_event(event_name, lex) if ENV["DEBUG_TAG_RIPPER"]
|
28
49
|
if respond_to?(event_name, true)
|
29
50
|
send(event_name, lex)
|
30
51
|
else
|
@@ -32,6 +53,13 @@ module TagRipper
|
|
32
53
|
end
|
33
54
|
end
|
34
55
|
|
56
|
+
def debug_event(event_name, lex)
|
57
|
+
puts <<~OUTPUT.chomp
|
58
|
+
Sending #{event_name} to #{self} with #{lex.token.inspect}
|
59
|
+
#{inspect}"
|
60
|
+
OUTPUT
|
61
|
+
end
|
62
|
+
|
35
63
|
def module?
|
36
64
|
(type == :module) | (type == :class)
|
37
65
|
end
|
@@ -39,8 +67,11 @@ module TagRipper
|
|
39
67
|
def type
|
40
68
|
@type
|
41
69
|
end
|
70
|
+
|
71
|
+
# The fully-qualified name of this entity (e.g. +"Foo::Bar::MyClass"+)
|
72
|
+
# @return [String]
|
42
73
|
def fqn
|
43
|
-
return nil unless
|
74
|
+
return nil unless name?
|
44
75
|
return name if fqn_names.size == 1
|
45
76
|
|
46
77
|
if type == :instance_method
|
@@ -51,14 +82,10 @@ module TagRipper
|
|
51
82
|
end
|
52
83
|
alias fully_qualified_name fqn
|
53
84
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
def awaiting_name? = @status == :awaiting_name
|
59
|
-
|
60
|
-
OPENED_STATUSES = %i[tagged awaiting_name named].freeze
|
61
|
-
|
85
|
+
# Have we opened a new lexical scope? (e.g. evaluating within the body
|
86
|
+
# of a class, rather than comments before the class)
|
87
|
+
#
|
88
|
+
# @return [Boolean]
|
62
89
|
def open?
|
63
90
|
OPENED_STATUSES.include?(@status)
|
64
91
|
end
|
@@ -68,7 +95,7 @@ module TagRipper
|
|
68
95
|
raise IllegalStateTransitionError.new(from: @status, to: :tagged)
|
69
96
|
end
|
70
97
|
|
71
|
-
|
98
|
+
self.status = :tagged
|
72
99
|
|
73
100
|
add_tag(tag_name, tag_value)
|
74
101
|
end
|
@@ -78,7 +105,11 @@ module TagRipper
|
|
78
105
|
raise IllegalStateTransitionError.new(from: @status, to: :awaiting_name)
|
79
106
|
end
|
80
107
|
|
81
|
-
|
108
|
+
self.status = :awaiting_name
|
109
|
+
end
|
110
|
+
|
111
|
+
def name?
|
112
|
+
!!@name
|
82
113
|
end
|
83
114
|
|
84
115
|
def name=(name)
|
@@ -87,17 +118,19 @@ module TagRipper
|
|
87
118
|
end
|
88
119
|
|
89
120
|
@name = name.to_s
|
90
|
-
|
121
|
+
self.status = :named
|
91
122
|
end
|
92
123
|
|
93
124
|
def close!
|
94
125
|
@open = false
|
95
|
-
|
126
|
+
self.status = :closed
|
96
127
|
freeze
|
97
128
|
end
|
98
129
|
|
99
|
-
|
100
|
-
|
130
|
+
VALID_STATUSES.each do |status|
|
131
|
+
define_method(:"#{status}?") do
|
132
|
+
@status == status
|
133
|
+
end
|
101
134
|
end
|
102
135
|
|
103
136
|
def may_tag?
|
@@ -116,10 +149,20 @@ module TagRipper
|
|
116
149
|
named?
|
117
150
|
end
|
118
151
|
|
152
|
+
def parent_id
|
153
|
+
parent&.id
|
154
|
+
end
|
155
|
+
|
119
156
|
def inspect
|
120
|
-
|
157
|
+
exposed_properties = %i[object_id name fqn type parent_id status tags]
|
158
|
+
inner_string = exposed_properties.map do |property|
|
159
|
+
"#{property}=#{public_send(property)}"
|
160
|
+
end.join(", ")
|
161
|
+
"<#{inner_string}>"
|
121
162
|
end
|
122
163
|
|
164
|
+
alias to_s inspect
|
165
|
+
|
123
166
|
def tags
|
124
167
|
@tags.dup
|
125
168
|
end
|
@@ -128,13 +171,32 @@ module TagRipper
|
|
128
171
|
@name.to_s.dup
|
129
172
|
end
|
130
173
|
|
131
|
-
def
|
132
|
-
@
|
174
|
+
def parent
|
175
|
+
@parent
|
176
|
+
end
|
177
|
+
|
178
|
+
def status
|
179
|
+
@status
|
133
180
|
end
|
134
181
|
|
135
182
|
protected
|
136
183
|
|
137
|
-
|
184
|
+
def append_name!(string)
|
185
|
+
raise StandardError, "Cannot append #{string} to nil" unless @name
|
186
|
+
|
187
|
+
@name.concat(string.to_s)
|
188
|
+
end
|
189
|
+
|
190
|
+
def status=(status)
|
191
|
+
status = status.to_sym
|
192
|
+
raise InvalidStatusError unless VALID_STATUSES.include?(status)
|
193
|
+
|
194
|
+
@status = status
|
195
|
+
end
|
196
|
+
|
197
|
+
def type=(type)
|
198
|
+
@type = type.to_sym
|
199
|
+
end
|
138
200
|
|
139
201
|
def fqn_names
|
140
202
|
return [name] if parent.nil?
|
@@ -142,18 +204,10 @@ module TagRipper
|
|
142
204
|
[*parent.fqn_names, name]
|
143
205
|
end
|
144
206
|
|
145
|
-
def parent
|
146
|
-
@parent
|
147
|
-
end
|
148
|
-
|
149
207
|
def add_tag(name, value)
|
150
208
|
@tags[name].add(value)
|
151
209
|
end
|
152
210
|
|
153
|
-
def named?
|
154
|
-
!!@name
|
155
|
-
end
|
156
|
-
|
157
211
|
def build_child
|
158
212
|
self.class.new(parent: self)
|
159
213
|
end
|
@@ -178,11 +232,13 @@ module TagRipper
|
|
178
232
|
send(event_token_method_name, lex)
|
179
233
|
end
|
180
234
|
|
235
|
+
##
|
236
|
+
# Lex is a keyword (e.g. def, class, module)
|
181
237
|
def on_new_taggable_context_kw(lex)
|
182
238
|
returnable_entity = named? ? build_child : self
|
183
239
|
|
184
240
|
returnable_entity.await_name!
|
185
|
-
|
241
|
+
returnable_entity.type = lex.on_kw_type
|
186
242
|
|
187
243
|
returnable_entity
|
188
244
|
end
|
@@ -191,21 +247,59 @@ module TagRipper
|
|
191
247
|
alias on_kw_module on_new_taggable_context_kw
|
192
248
|
alias on_kw_class on_new_taggable_context_kw
|
193
249
|
|
194
|
-
IGNORED_IDENT_KEYWORDS = %w[require private class_eval instance_eval
|
250
|
+
IGNORED_IDENT_KEYWORDS = %w[require private class_eval instance_eval
|
251
|
+
define_method].freeze
|
195
252
|
private_constant :IGNORED_IDENT_KEYWORDS
|
196
253
|
|
197
|
-
def name_from_lex(lex)
|
254
|
+
def name_from_lex(lex) # rubocop:disable Metrics
|
198
255
|
return self if IGNORED_IDENT_KEYWORDS.include?(lex.token)
|
199
|
-
|
200
|
-
return self
|
256
|
+
# TODO: Simplify this logic
|
257
|
+
return self if named? && !@name.end_with?("::")
|
258
|
+
return self unless may_name? || (name? && @name.end_with?("::"))
|
259
|
+
|
260
|
+
# self.status = :awaiting_name # TODO: Fix this with a proper state
|
261
|
+
if named? && @name.to_s.end_with?("::")
|
262
|
+
append_name!(lex.token)
|
263
|
+
else
|
264
|
+
self.name = "#{name}#{lex.token}"
|
265
|
+
end
|
201
266
|
|
202
|
-
self.name = lex.token
|
203
267
|
self
|
204
|
-
end
|
268
|
+
end # rubocop:enable Metrics
|
205
269
|
|
270
|
+
##
|
271
|
+
# Matches names of constants: module names, const names, etc.
|
206
272
|
alias on_const name_from_lex
|
273
|
+
|
274
|
+
##
|
275
|
+
# Matches tokens like: private, method names, argument names
|
207
276
|
alias on_ident name_from_lex
|
208
277
|
|
278
|
+
def on_op(lex)
|
279
|
+
if lex.double_colon?
|
280
|
+
append_name!(lex.token)
|
281
|
+
end
|
282
|
+
|
283
|
+
# return name_from_op(lex) if lex.singleton_class?
|
284
|
+
self
|
285
|
+
end
|
286
|
+
|
287
|
+
def on_kw_self(lex)
|
288
|
+
if module? && awaiting_name?
|
289
|
+
self.name = lex.token
|
290
|
+
return self
|
291
|
+
end
|
292
|
+
|
293
|
+
self
|
294
|
+
end
|
295
|
+
|
296
|
+
##
|
297
|
+
# Name the current entity 'self' based on an operator (e.g. +class << self+)
|
298
|
+
def name_from_kw(lex)
|
299
|
+
self.name = lex.token
|
300
|
+
self
|
301
|
+
end
|
302
|
+
|
209
303
|
def on_kw_end(_lex)
|
210
304
|
close!
|
211
305
|
parent
|
data/lib/tag_ripper/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tag_ripper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gavin Morrice
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-07-02 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
12
|
description: Add tags to your Ruby code comments and then Rip the as lexical tokens
|
13
13
|
email:
|
@@ -21,6 +21,7 @@ files:
|
|
21
21
|
- ".ruby-version"
|
22
22
|
- CHANGELOG.md
|
23
23
|
- CODE_OF_CONDUCT.md
|
24
|
+
- Guardfile
|
24
25
|
- LICENSE.txt
|
25
26
|
- README.md
|
26
27
|
- Rakefile
|
@@ -30,13 +31,13 @@ files:
|
|
30
31
|
- lib/tag_ripper/ripper.rb
|
31
32
|
- lib/tag_ripper/taggable_entity.rb
|
32
33
|
- lib/tag_ripper/version.rb
|
33
|
-
homepage: https://github.com/Bodacious/
|
34
|
+
homepage: https://github.com/Bodacious/tag_ripper/
|
34
35
|
licenses:
|
35
36
|
- MIT
|
36
37
|
metadata:
|
37
|
-
homepage_uri: https://github.com/Bodacious/
|
38
|
-
source_code_uri: https://github.com/Bodacious/
|
39
|
-
changelog_uri: https://github.com/Bodacious/
|
38
|
+
homepage_uri: https://github.com/Bodacious/tag_ripper/
|
39
|
+
source_code_uri: https://github.com/Bodacious/tag_ripper/
|
40
|
+
changelog_uri: https://github.com/Bodacious/tag_ripper/
|
40
41
|
rubygems_mfa_required: 'true'
|
41
42
|
rdoc_options: []
|
42
43
|
require_paths:
|