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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f55c43ce02431ffc69bd9284ec5875678d743e3c14544f3ebb0710e440921b8
4
- data.tar.gz: 10b1baa79118d897442fd87e49ee7da2b9259172802246f22e0ea5a3b3855d72
3
+ metadata.gz: 2e80933d8c317438627b041b30bd905ae33ed8d993db4ef149e69f3f71fd30d7
4
+ data.tar.gz: 7eeb22fc466b8b50da7f6d3e887dfc02c04d0de462abf870f36bf0beda6b3a68
5
5
  SHA512:
6
- metadata.gz: ab28505f7eb7784a4a90d7613960263251b43f9d8dcbca34570c360828f7c651e3b60d5e518b0d98e6722e0fc214a53e27f8280dfe3713962474c5914074fb5c
7
- data.tar.gz: c38bd4b3c85a1a6b02f7fe57ab61e69ea729f031d9f6a04fd40a85e71e978c820d7577d63a060079f3309a704452b779fa994d7fca6cc09d547dbcbdbb8622af
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.2
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
- TODO: Write usage instructions here
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/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).
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/tag-ripper/blob/master/CODE_OF_CONDUCT.md).
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>[\w_-]+):\s(?<tag_value>.+)/
7
- END_TOKEN = "end".freeze
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 == END_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
- case token
76
- when "const" then :class
77
- when "module" then :module
78
- when "def" then :instance_method
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
- def initialize(name: nil, parent: nil)
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 = nil
23
- @status = :pending
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
- puts "send_event: #{event_name} - #{lex} #(#{@status})"
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 named?
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
- def pending? = @status == :pending
55
-
56
- def tagged? = @status == :tagged
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
- @status = :tagged
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
- @status = :awaiting_name
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
- @status = :named
121
+ self.status = :named
91
122
  end
92
123
 
93
124
  def close!
94
125
  @open = false
95
- @status = :closed
126
+ self.status = :closed
96
127
  freeze
97
128
  end
98
129
 
99
- def closed?
100
- @status == :closed
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
- "<id=#{id},@name=#{@name},tags=#{@tags},parent=#{@parent}>"
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 type=(type)
132
- @type = type.to_sym
174
+ def parent
175
+ @parent
176
+ end
177
+
178
+ def status
179
+ @status
133
180
  end
134
181
 
135
182
  protected
136
183
 
137
- alias id object_id
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
- self.type = lex.on_kw_type
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 define_method].freeze
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
- return self if named?
200
- return self unless may_name?
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TagRipper
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
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.0
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-04-03 00:00:00.000000000 Z
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/tag-ripper/
34
+ homepage: https://github.com/Bodacious/tag_ripper/
34
35
  licenses:
35
36
  - MIT
36
37
  metadata:
37
- homepage_uri: https://github.com/Bodacious/tag-ripper/
38
- source_code_uri: https://github.com/Bodacious/tag-ripper/
39
- changelog_uri: https://github.com/Bodacious/tag-ripper/
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: