docrb 0.2.0 → 0.3.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.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Victor Gama
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-02-21 00:00:00.000000000 Z
11
+ date: 2023-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: docrb-html
@@ -16,56 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.2'
19
+ version: '0.3'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.2'
26
+ version: '0.3'
27
27
  - !ruby/object:Gem::Dependency
28
- name: parser
28
+ name: docrb-parser
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '3.2'
33
+ version: '0.1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '3.2'
41
- - !ruby/object:Gem::Dependency
42
- name: redcarpet
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '3.6'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '3.6'
55
- - !ruby/object:Gem::Dependency
56
- name: rouge
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '4.1'
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '4.1'
40
+ version: '0.1'
69
41
  description: An opinionated documentation parser
70
42
  email:
71
43
  - hey@vito.io
@@ -87,26 +59,7 @@ files:
87
59
  - docrb.gemspec
88
60
  - exe/docrb
89
61
  - lib/docrb.rb
90
- - lib/docrb/comment_parser.rb
91
- - lib/docrb/comment_parser/code_example_block.rb
92
- - lib/docrb/comment_parser/code_example_parser.rb
93
- - lib/docrb/comment_parser/field_block.rb
94
- - lib/docrb/comment_parser/field_list_parser.rb
95
- - lib/docrb/comment_parser/text_block.rb
96
62
  - lib/docrb/doc_compiler.rb
97
- - lib/docrb/doc_compiler/base_container.rb
98
- - lib/docrb/doc_compiler/base_container/computations.rb
99
- - lib/docrb/doc_compiler/doc_attribute.rb
100
- - lib/docrb/doc_compiler/doc_blocks.rb
101
- - lib/docrb/doc_compiler/doc_class.rb
102
- - lib/docrb/doc_compiler/doc_method.rb
103
- - lib/docrb/doc_compiler/doc_module.rb
104
- - lib/docrb/doc_compiler/file_ref.rb
105
- - lib/docrb/doc_compiler/object_container.rb
106
- - lib/docrb/markdown.rb
107
- - lib/docrb/module_extensions.rb
108
- - lib/docrb/resolvable.rb
109
- - lib/docrb/ruby_parser.rb
110
63
  - lib/docrb/spec.rb
111
64
  - lib/docrb/version.rb
112
65
  homepage: https://github.com/heyvito/docrb
@@ -132,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
85
  - !ruby/object:Gem::Version
133
86
  version: '0'
134
87
  requirements: []
135
- rubygems_version: 3.4.2
88
+ rubygems_version: 3.4.10
136
89
  signing_key:
137
90
  specification_version: 4
138
91
  summary: An opinionated documentation parser
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docrb
4
- class CommentParser
5
- # CodeExampleBlock represents a list of characters of a code example
6
- class CodeExampleBlock
7
- attr_reader :code
8
-
9
- def initialize
10
- @code = []
11
- end
12
-
13
- def <<(text_block)
14
- @code << "\n" unless empty?
15
- @code << text_block.text
16
- end
17
-
18
- def empty?
19
- @code.empty?
20
- end
21
-
22
- def normalize
23
- @code
24
- .map { |txt| txt.split("\n") }
25
- .map { |item| item.empty? ? "" : item }
26
- .flatten
27
- .map { |line| line.gsub(/^\s{2}/, "") }
28
- .join("\n")
29
- end
30
-
31
- def to_h
32
- { type: :code_example, contents: normalize }
33
- end
34
- end
35
- end
36
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docrb
4
- class CommentParser
5
- # CodeExampleParser attempts to extract code examples from a documentation
6
- # block.
7
- class CodeExampleParser
8
- def self.process(components)
9
- new_components = []
10
- code_example_group = CodeExampleBlock.new
11
- components.each do |c|
12
- if !c.is_a?(TextBlock) || !c.text.start_with?(" ")
13
- unless code_example_group.empty?
14
- new_components << code_example_group
15
- code_example_group = CodeExampleBlock.new
16
- end
17
- new_components << c
18
- next
19
- end
20
-
21
- code_example_group << c
22
- end
23
-
24
- new_components << code_example_group unless code_example_group.empty?
25
- new_components
26
- end
27
- end
28
- end
29
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docrb
4
- class CommentParser
5
- # FieldBlock represents a list of fields obtained by FieldListParser
6
- class FieldBlock
7
- attr_reader :fields
8
-
9
- def initialize(fields)
10
- @fields = fields
11
- end
12
-
13
- def to_h
14
- { type: :field_block, contents: fields }
15
- end
16
- end
17
- end
18
- end
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docrb
4
- class CommentParser
5
- # FieldListParser parses a field block (representing arguments of an method,
6
- # for instance) into a specialised structure.
7
- class FieldListParser
8
- FIELD_FORMAT_REGEXP = /^([a-z_][0-9a-z_]*:?)\s+- /i
9
-
10
- def initialize(text)
11
- @text = text
12
- @data = []
13
- @current = []
14
- @result = {}
15
- @dash_index = nil
16
- end
17
-
18
- def detect
19
- @text.each_char do |c|
20
- next @current << c unless c == LINE_BREAK
21
- return false unless handle_linebreak
22
- end
23
- return false unless handle_linebreak
24
-
25
- flush_current_field!
26
- true
27
- end
28
-
29
- def handle_linebreak
30
- return true if @current.empty?
31
-
32
- # Here's a linebreak. Handle it as needed.
33
- @current = @current.join
34
- if infer_field_alignment(@current)
35
- flush_current_field!
36
- @data << @current
37
- else
38
- # This is not a field. May be a continuation.
39
- # Can it be a continuation?
40
- return false if @data.empty?
41
-
42
- # Yep. It may be. Is it?
43
- return false unless continuation?(@current)
44
-
45
- # It is. Append to data.
46
- @data << @current.strip
47
- end
48
- @current = []
49
- true
50
- end
51
-
52
- def flush_current_field!
53
- return if @data.empty?
54
-
55
- data = @data.join(" ")
56
- @data = []
57
- field_name = FIELD_FORMAT_REGEXP.match(data)[1]
58
- text = data.slice(@dash_index + 1...).strip
59
- @result[field_name] = text
60
- end
61
-
62
- def continuation?(line)
63
- return false unless @dash_index
64
-
65
- # until dash_index, we should have spaces
66
- return false if line.length < @dash_index
67
-
68
- return false unless line.slice(0..@dash_index + 1).strip.empty?
69
-
70
- true
71
- end
72
-
73
- def infer_field_alignment(line)
74
- # is the line a field?
75
- return false unless FIELD_FORMAT_REGEXP.match?(line)
76
-
77
- if @dash_index
78
- # does it match the same alignment?
79
- return false if line[@dash_index] != DASH
80
- else
81
- @dash_index = line.index(DASH)
82
- end
83
-
84
- true
85
- end
86
-
87
- attr_reader :result
88
- end
89
- end
90
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docrb
4
- class CommentParser
5
- # TextBlock represents an array of characters to be built into a single
6
- # text block.
7
- class TextBlock
8
- def initialize(text = nil)
9
- @buffer = [text].compact
10
- end
11
-
12
- def <<(obj)
13
- @buffer << obj
14
- @text = nil
15
- end
16
-
17
- def empty?
18
- @buffer.empty?
19
- end
20
-
21
- def match?(regexp)
22
- regexp.match? text
23
- end
24
-
25
- def last
26
- @buffer.last
27
- end
28
-
29
- def text
30
- @text ||= @buffer.join
31
- end
32
-
33
- def subs!(index)
34
- @text = nil
35
- @buffer = @buffer[index...]
36
- end
37
-
38
- def to_h
39
- { type: :text_block, contents: text.gsub(/\n /, " ") }
40
- end
41
- end
42
- end
43
- end
@@ -1,272 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docrb
4
- # CommentParser implements a small parser for matching comment's contents to
5
- # relevant references and annotations.
6
- class CommentParser
7
- COMMENT_METHOD_REF_REGEXP = /(?:([A-Z][a-zA-Z0-9_]*::)*([A-Z][a-zA-Z0-9_]*))?(::|\.|#)([A-Za-z_][a-zA-Z0-9_@]*[!?]?)(?:\([a-zA-Z0-9=_,\s*]+\))?/
8
- COMMENT_SYMBOL_REGEXP = /:(!|[@$][a-z_][a-z0-9_]*|[a-z_][a-z0-9_]*|[a-z_][a-z0-9_]*[?!]?)/i
9
- CAMELCASE_IDENTIFIER_REGEXP = /[A-Z][a-z]+(?:[A-Z][a-z]+)+/
10
- INTERNAL_ANNOTATION_REGEXP = /^internal:/i
11
- PUBLIC_ANNOTATION_REGEXP = /^public:/i
12
- PRIVATE_ANNOTATION_REGEXP = /^private:/i
13
- DEPRECATED_ANNOTATION_REGEXP = /^deprecated:/i
14
- VISIBILITY_ANNOTATIONS = {
15
- internal: INTERNAL_ANNOTATION_REGEXP,
16
- public: PUBLIC_ANNOTATION_REGEXP,
17
- private: PRIVATE_ANNOTATION_REGEXP,
18
- depreacated: DEPRECATED_ANNOTATION_REGEXP
19
- }.freeze
20
- CARRIAGE = "\r"
21
- LINE_BREAK = "\n"
22
- SPACE = " "
23
- DASH = "-"
24
-
25
- autoload :TextBlock, "docrb/comment_parser/text_block"
26
- autoload :FieldListParser, "docrb/comment_parser/field_list_parser"
27
- autoload :CodeExampleParser, "docrb/comment_parser/code_example_parser"
28
- autoload :FieldBlock, "docrb/comment_parser/field_block"
29
- autoload :CodeExampleBlock, "docrb/comment_parser/code_example_block"
30
-
31
- # Parses a given comment for a given object type.
32
- #
33
- # type: - Type of the object's to which the comment data belongs to
34
- # comment: - A string containing the object's documentation.
35
- #
36
- # Returns a Hash containing the parsed content for the comment.
37
- def self.parse(type:, comment:)
38
- new(type).parse(comment)
39
- end
40
-
41
- # Internal: Initializes a new parser with a provided type
42
- #
43
- # type - Type of the object being documented
44
- def initialize(type)
45
- @type = type
46
- @components = []
47
- @current = TextBlock.new
48
- @last_char = nil
49
- @current_char = nil
50
- @meta = {}
51
- end
52
-
53
- # Intenral: Loads the provided data into the parser and executes all steps
54
- # required to extract relevant information and annotations.
55
- #
56
- # data - String containing the comment being parsed
57
- #
58
- # Returns a Hash containing the parsed content for the comment.
59
- def parse(data)
60
- load(data)
61
- coalesce_field_list
62
- detect_code_example
63
-
64
- infer_visibility_from_doc if %i[def defs].include? @type
65
-
66
- data = to_h
67
- data[:contents] = detect_references(data[:contents])
68
- data
69
- end
70
-
71
- # Internal: Attempts to infer an object visiblity based on the comment's
72
- # prefix. Supported visibility options are `public:`, `private:`, and
73
- # `internal:`. Annotations are case-insensitive.
74
- # This method also removes the detected annotation from the comment block,
75
- # to reduce clutter in the emitted documentation.
76
- def infer_visibility_from_doc
77
- return if @components.empty?
78
-
79
- item = @components.first
80
- return unless item.is_a? TextBlock
81
- return unless (annotation = VISIBILITY_ANNOTATIONS.find { |_k, v| v.match?(item.text) })
82
-
83
- item.subs! item.text.index(":") + 1
84
- @meta[:doc_visibility_annotation] = annotation.first
85
- end
86
-
87
- # Internal: Commits the current text block being processed by #load
88
- def commit_current!
89
- @components << @current if @current && !@current.empty?
90
- @current = TextBlock.new
91
- end
92
-
93
- # Internal: Loads a given stirng into the parser, by splitting it into
94
- # text blocks.
95
- def load(text)
96
- text.each_char do |c|
97
- @last_char = @current_char
98
- @current_char = c
99
- next if c == CARRIAGE
100
-
101
- if @last_char == LINE_BREAK && (c == LINE_BREAK)
102
- commit_current!
103
- next
104
- end
105
- @current << c
106
- end
107
- commit_current!
108
- end
109
-
110
- # Internal: Attempts to find and split field lists from the loaded comment.
111
- def coalesce_field_list
112
- return if @field_list_coalesced
113
-
114
- @field_list_coalesced = true
115
-
116
- new_contents = []
117
- has_field_list = false
118
-
119
- @components.each do |block|
120
- unless has_field_list
121
- parser = FieldListParser.new(block.text)
122
- if parser.detect
123
- has_field_list = true
124
- new_contents << FieldBlock.new(parser.result)
125
- next
126
- end
127
- end
128
- new_contents << block
129
- end
130
-
131
- @components = new_contents
132
- end
133
-
134
- # Internal: Attempts to find and extract code examples contained within the
135
- # comment
136
- def detect_code_example
137
- return if @code_examples_detected
138
-
139
- @code_examples_detected = true
140
-
141
- @components = CodeExampleParser.process(@components)
142
- end
143
-
144
- # Internal: Attempts to detect references on a provided list of blocks
145
- #
146
- # on - List of blocks to process
147
- #
148
- # Returns an updated list of blocks with references transformed into
149
- # specialised structures.
150
- def detect_references(on)
151
- new_contents = []
152
-
153
- on.each do |block|
154
- case block[:type]
155
- when :text_block
156
- block[:contents] = detect_text_references(block[:contents])
157
- when :field_block
158
- block[:contents] = detect_field_references(block[:contents])
159
- end
160
- new_contents << block
161
- end
162
-
163
- new_contents
164
- end
165
-
166
- # Internal: Attempts to detect references on a text object.
167
- #
168
- # text - Text data to have references transformed into specialised objects
169
- #
170
- # Returns an array containing the updated text data
171
- def detect_text_references(text)
172
- changed = true
173
- text, changed = update_next_reference(text) while changed
174
- text
175
- end
176
-
177
- # Internal: Attempts to detect field references on a given field object.
178
- #
179
- # Returns a new hash containing the field's data post-processed with
180
- # reference annotations
181
- def detect_field_references(field)
182
- field
183
- .transform_values do |v|
184
- { type: :text_block, contents: detect_text_references(v) }
185
- end
186
- end
187
-
188
- def process_comment_method_reference(contents, match)
189
- class_path, target, invocation, name = match.to_a.slice(1...)
190
- class_path&.gsub!(/::$/, "")
191
- reference_type = invocation == "#" ? :method : :ambiguous
192
- begin_at = match.begin(0)
193
- end_at = match.end(0)
194
- left_slice = contents[0...begin_at]
195
- right_slice = contents[end_at...]
196
- item = {
197
- type: :ref,
198
- ref_type: reference_type,
199
- name:,
200
- target:,
201
- class_path:,
202
- contents: match[0]
203
- }
204
- [
205
- { type: :span, contents: left_slice },
206
- item,
207
- { type: :span, contents: right_slice }
208
- ].reject { |i| i[:contents].empty? }
209
- end
210
-
211
- def process_simple_symbol(type, contents, match)
212
- begin_at = match.begin(0)
213
- end_at = match.end(0)
214
- left_slice = contents[0...begin_at]
215
- right_slice = contents[end_at...]
216
- [
217
- { type: :span, contents: left_slice },
218
- { type:, contents: match[0] },
219
- { type: :span, contents: right_slice }
220
- ].reject { |i| i[:contents].empty? }
221
- end
222
-
223
- def update_string_reference(contents)
224
- updated = false
225
- if (match = COMMENT_METHOD_REF_REGEXP.match(contents))
226
- contents = process_comment_method_reference(contents, match)
227
- updated = true
228
- elsif (match = COMMENT_SYMBOL_REGEXP.match(contents))
229
- contents = process_simple_symbol(:sym_ref, contents, match)
230
- updated = true
231
- elsif (match = CAMELCASE_IDENTIFIER_REGEXP.match(contents))
232
- contents = process_simple_symbol(:camelcase_identifier, contents, match)
233
- updated = true
234
- end
235
- [contents, updated]
236
- end
237
-
238
- # Internal: Recursivelly updates a given value until all references are
239
- # processed.
240
- #
241
- # contents - Contents to be processed
242
- #
243
- # Returns a new array containing the processed contents
244
- def update_next_reference(contents)
245
- return update_string_reference(contents) if contents.is_a? String
246
-
247
- new_contents = []
248
- changed = false
249
- contents.each do |block|
250
- if block[:type] == :span
251
- updated, ok = update_next_reference(block[:contents])
252
- if ok
253
- changed = true
254
- next new_contents.append(*updated)
255
- end
256
- end
257
- new_contents << block
258
- end
259
-
260
- [new_contents, changed]
261
- end
262
-
263
- # Public: Returns a hash by merging contents and metadata obtained through
264
- # the parsing process.
265
- def to_h
266
- @meta.merge({
267
- type: @type,
268
- contents: @components.map(&:to_h)
269
- })
270
- end
271
- end
272
- end