docrb 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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