docrb 0.2.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.
- checksums.yaml +7 -0
- data/.editorconfig +21 -0
- data/.rspec +3 -0
- data/.rubocop.yml +70 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +79 -0
- data/Rakefile +12 -0
- data/bin/console +16 -0
- data/bin/json +16 -0
- data/bin/md +8 -0
- data/bin/setup +8 -0
- data/docrb.gemspec +33 -0
- data/exe/docrb +205 -0
- data/lib/docrb/comment_parser/code_example_block.rb +36 -0
- data/lib/docrb/comment_parser/code_example_parser.rb +29 -0
- data/lib/docrb/comment_parser/field_block.rb +18 -0
- data/lib/docrb/comment_parser/field_list_parser.rb +90 -0
- data/lib/docrb/comment_parser/text_block.rb +43 -0
- data/lib/docrb/comment_parser.rb +272 -0
- data/lib/docrb/doc_compiler/base_container/computations.rb +178 -0
- data/lib/docrb/doc_compiler/base_container.rb +123 -0
- data/lib/docrb/doc_compiler/doc_attribute.rb +58 -0
- data/lib/docrb/doc_compiler/doc_blocks.rb +111 -0
- data/lib/docrb/doc_compiler/doc_class.rb +43 -0
- data/lib/docrb/doc_compiler/doc_method.rb +66 -0
- data/lib/docrb/doc_compiler/doc_module.rb +9 -0
- data/lib/docrb/doc_compiler/file_ref.rb +41 -0
- data/lib/docrb/doc_compiler/object_container.rb +68 -0
- data/lib/docrb/doc_compiler.rb +55 -0
- data/lib/docrb/markdown.rb +62 -0
- data/lib/docrb/module_extensions.rb +13 -0
- data/lib/docrb/resolvable.rb +178 -0
- data/lib/docrb/ruby_parser.rb +630 -0
- data/lib/docrb/spec.rb +31 -0
- data/lib/docrb/version.rb +5 -0
- data/lib/docrb.rb +71 -0
- metadata +139 -0
@@ -0,0 +1,43 @@
|
|
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
|
@@ -0,0 +1,272 @@
|
|
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
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docrb
|
4
|
+
class DocCompiler
|
5
|
+
class BaseContainer
|
6
|
+
# Module Computations implements utility methods to handle hierarchy and
|
7
|
+
# nesting.
|
8
|
+
module Computations
|
9
|
+
# Recursively computes all dependants of the receiver.
|
10
|
+
# This method expands all references into concrete representations.
|
11
|
+
def compute_dependants
|
12
|
+
return if @compute_dependants
|
13
|
+
|
14
|
+
@compute_dependants = true
|
15
|
+
|
16
|
+
classes.each(&:compute_dependants)
|
17
|
+
modules.each(&:compute_dependants)
|
18
|
+
|
19
|
+
extends.each do |ref|
|
20
|
+
resolve_ref(ref)&.compute_dependants
|
21
|
+
end
|
22
|
+
includes.each do |ref|
|
23
|
+
resolve_ref(ref)&.compute_dependants
|
24
|
+
end
|
25
|
+
|
26
|
+
return unless (parent_name = @inherits)
|
27
|
+
|
28
|
+
@parent_class = resolve(parent_name)
|
29
|
+
return unless @parent_class
|
30
|
+
|
31
|
+
@parent_class.compute_dependants
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns a Hash containing all methods for the container, along with
|
35
|
+
# inherited and included ones.
|
36
|
+
def merged_instance_methods
|
37
|
+
return @merged_instance_methods if @merged_instance_methods
|
38
|
+
|
39
|
+
compute_dependants
|
40
|
+
|
41
|
+
methods = {}
|
42
|
+
@parent_class&.merged_instance_methods&.each do |k, v|
|
43
|
+
methods[k] = { source: :inheritance, definition: v }
|
44
|
+
end
|
45
|
+
|
46
|
+
includes.each do |ref|
|
47
|
+
next unless (container = resolve_container(ref))
|
48
|
+
|
49
|
+
container.merged_instance_methods.each do |k, v|
|
50
|
+
methods[k] = { source: :inclusion, definition: v, overriding: methods[k] }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
defs.each do |m|
|
55
|
+
methods[m.name] = { source: :source, definition: m, overriding: methods[m.name] }
|
56
|
+
end
|
57
|
+
|
58
|
+
methods
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns a hash containing all class methods for the container, along
|
62
|
+
# with inherited and extended ones.
|
63
|
+
def merged_class_methods
|
64
|
+
return @merged_class_methods if @merged_class_methods
|
65
|
+
|
66
|
+
compute_dependants
|
67
|
+
|
68
|
+
methods = {}
|
69
|
+
@parent_class&.merged_class_methods&.each do |k, v|
|
70
|
+
methods[k] = { source: :inheritance, definition: v }
|
71
|
+
end
|
72
|
+
|
73
|
+
includes.each do |ref|
|
74
|
+
next unless (container = resolve_container(ref))
|
75
|
+
|
76
|
+
container.merged_class_methods.each do |k, v|
|
77
|
+
methods[k] = { source: :extension, definition: v, overriding: methods[k] }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
sdefs.each do |m|
|
82
|
+
methods[m.name] = { source: :source, definition: m, overriding: methods[m.name] }
|
83
|
+
end
|
84
|
+
|
85
|
+
methods
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns a hash containing all attributes for the container, along with
|
89
|
+
# inherited ones.
|
90
|
+
def merged_attributes
|
91
|
+
return @merged_attributes if @merged_attributes
|
92
|
+
|
93
|
+
compute_dependants
|
94
|
+
|
95
|
+
attrs = {}
|
96
|
+
@parent_class&.merged_attributes&.each do |k, v|
|
97
|
+
attrs[k] = { source: :inheritance, definition: v }
|
98
|
+
end
|
99
|
+
|
100
|
+
attributes.each do |m|
|
101
|
+
attrs[m.name] = { source: :source, definition: m, overriding: attrs[m.name] }
|
102
|
+
end
|
103
|
+
|
104
|
+
attrs
|
105
|
+
end
|
106
|
+
|
107
|
+
# Deprecated: Use #merged_instance_methods.
|
108
|
+
def all_defs
|
109
|
+
return @all_defs if @all_defs
|
110
|
+
|
111
|
+
compute_dependants
|
112
|
+
|
113
|
+
methods = {}
|
114
|
+
@parent_class&.all_defs&.each do |k, v|
|
115
|
+
methods[k] = v
|
116
|
+
end
|
117
|
+
|
118
|
+
includes.each do |ref|
|
119
|
+
resolve_ref(ref)&.all_defs&.each do |met|
|
120
|
+
if methods.key? met.name
|
121
|
+
methods[key].override! met
|
122
|
+
next
|
123
|
+
end
|
124
|
+
|
125
|
+
methods[met.name] = met
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
defs.each do |met|
|
130
|
+
if methods.key? met.name
|
131
|
+
methods[met.name].override! met
|
132
|
+
next
|
133
|
+
end
|
134
|
+
|
135
|
+
methods[met.name] = met
|
136
|
+
end
|
137
|
+
|
138
|
+
@all_defs = methods
|
139
|
+
end
|
140
|
+
|
141
|
+
# Deprecated: Use #merged_class_methods.
|
142
|
+
def all_sdefs
|
143
|
+
return @all_sdefs if @all_sdefs
|
144
|
+
|
145
|
+
compute_dependants
|
146
|
+
|
147
|
+
methods = {}
|
148
|
+
|
149
|
+
@parent_class&.all_sdefs&.each do |k, v|
|
150
|
+
methods[k] = v
|
151
|
+
end
|
152
|
+
|
153
|
+
extends.each do |ref|
|
154
|
+
resolve_ref(ref)&.all_defs&.each do |met|
|
155
|
+
if methods.key? met.name
|
156
|
+
methods[met.name].override! met
|
157
|
+
next
|
158
|
+
end
|
159
|
+
|
160
|
+
methods[met.name] = met
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
@sdefs.each do |met|
|
165
|
+
if methods.key? met.name
|
166
|
+
methods[met.name].override! met
|
167
|
+
next
|
168
|
+
end
|
169
|
+
|
170
|
+
methods[met.name] = met
|
171
|
+
end
|
172
|
+
|
173
|
+
@all_sdefs = methods
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docrb
|
4
|
+
class DocCompiler
|
5
|
+
# BaseContainer represents a container for methods, classes, modules and
|
6
|
+
# attributes.
|
7
|
+
class BaseContainer < Resolvable
|
8
|
+
require_relative "./base_container/computations"
|
9
|
+
include Computations
|
10
|
+
|
11
|
+
attr_accessor :extends, :includes, :classes, :modules, :defs, :sdefs,
|
12
|
+
:type, :name, :defined_by, :parent, :doc
|
13
|
+
|
14
|
+
# Initialises a new BaseContainer instance with a provided parent,
|
15
|
+
# filename and represented object.
|
16
|
+
def initialize(parent, filename, obj)
|
17
|
+
super()
|
18
|
+
@parent = parent
|
19
|
+
@type = obj[:type]
|
20
|
+
@name = obj[:name]
|
21
|
+
@extends = []
|
22
|
+
@includes = []
|
23
|
+
@classes = ObjectContainer.new(self, DocClass)
|
24
|
+
@modules = ObjectContainer.new(self, DocModule)
|
25
|
+
@defs = ObjectContainer.new(self, DocMethod)
|
26
|
+
@sdefs = ObjectContainer.new(self, DocMethod)
|
27
|
+
@defined_by = ObjectContainer.new(nil, FileRef)
|
28
|
+
append(filename, obj)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Appends a new object defined by the provided file to this container
|
32
|
+
def <<(filename, obj)
|
33
|
+
append(filename, obj)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Appends a new class defined by the provided file to this container
|
37
|
+
def append_class(filename, cls)
|
38
|
+
@classes.push filename, cls
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
ext = "#{extends.length} extend#{extends.length == 1 ? "" : "s"}"
|
43
|
+
inc = "#{includes.length} include#{includes.length == 1 ? "" : "s"}"
|
44
|
+
cls = "#{classes.length} class#{classes.length == 1 ? "" : "es"}"
|
45
|
+
mod = "#{modules.length} module#{modules.length == 1 ? "" : "s"}"
|
46
|
+
de = "#{defs.length} def#{defs.length == 1 ? "" : "s"}"
|
47
|
+
sde = "#{sdefs.length} sdef#{sdefs.length == 1 ? "" : "s"}"
|
48
|
+
|
49
|
+
"#<#{self.class.name}:#{format("0x%08x",
|
50
|
+
object_id * 2)} #{@type} #{@name} #{ext}, #{inc}, #{cls}, #{mod}, #{de}, #{sde}>"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Attempts to append a given object defined in a provided filename.
|
54
|
+
# Raises ArgumentError in case the object can't be added to the container
|
55
|
+
# due to type contraints.
|
56
|
+
#
|
57
|
+
# filename - Filename defining the object being appended
|
58
|
+
# obj - The object definition to be appended to this container.
|
59
|
+
def append(filename, obj)
|
60
|
+
raise ArgumentError, "cannot append obj of type #{obj[:type]} into #{@name} (#{@type})" if obj[:type] != @type
|
61
|
+
|
62
|
+
raise ArgumentError, "cannot append obj named #{obj[:name]} into #{@name} (#{@type})" if obj[:name] != @name
|
63
|
+
|
64
|
+
unless (doc = obj[:doc]).nil?
|
65
|
+
@defined_by.push(filename, obj)
|
66
|
+
@doc = doc
|
67
|
+
end
|
68
|
+
|
69
|
+
obj.fetch(:classes, []).each { |c| @classes.push(filename, c) }
|
70
|
+
obj.fetch(:modules, []).each { |m| @modules.push(filename, m) }
|
71
|
+
obj.fetch(:extend, []).each { |e| @extends << e }
|
72
|
+
obj.fetch(:include, []).each { |i| @includes << i }
|
73
|
+
obj.fetch(:methods, []).each do |met|
|
74
|
+
if met[:type] == :def
|
75
|
+
@defs.push(filename, met)
|
76
|
+
else
|
77
|
+
@sdefs.push(filename, met)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@inherits = obj.fetch(:inherits, nil) if obj[:type] == :class
|
82
|
+
|
83
|
+
appended(filename, obj)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Courtesy method. This method is called whenever a new object is
|
87
|
+
# appended to the container. Inheriting classes can override it to perform
|
88
|
+
# further operations on the appended object.
|
89
|
+
def appended(filename, obj); end
|
90
|
+
|
91
|
+
# Intenral: Recursively unpacks a given element by checking its
|
92
|
+
# :definition and :overriding keys.
|
93
|
+
#
|
94
|
+
# Returns the updated element.
|
95
|
+
def unpack(elem)
|
96
|
+
return unless elem
|
97
|
+
|
98
|
+
elem.tap do |e|
|
99
|
+
inner = e[:definition]
|
100
|
+
e[:definition] = inner.is_a?(Hash) ? unpack(inner) : inner.to_h
|
101
|
+
e[:overriding] = unpack(e[:overriding])
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns a Hash representation of the current container along with its
|
106
|
+
# children.
|
107
|
+
def to_h
|
108
|
+
{
|
109
|
+
type:,
|
110
|
+
name:,
|
111
|
+
doc: DocBlocks.prepare(doc, parent: self),
|
112
|
+
defined_by: defined_by.map(&:to_h),
|
113
|
+
defs: merged_instance_methods.transform_values { |v| unpack(v) },
|
114
|
+
sdefs: merged_class_methods.transform_values { |v| unpack(v) },
|
115
|
+
classes: classes.map(&:to_h),
|
116
|
+
modules: modules.map(&:to_h),
|
117
|
+
includes: includes.map(&:to_h),
|
118
|
+
extends: extends.map(&:to_h)
|
119
|
+
}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docrb
|
4
|
+
class DocCompiler
|
5
|
+
# DocAttribute represents a single attribute defined on a class.
|
6
|
+
class DocAttribute < Resolvable
|
7
|
+
attr_reader :parent, :defined_by, :name, :docs, :type, :writer_visibility,
|
8
|
+
:reader_visibility
|
9
|
+
|
10
|
+
def initialize(parent, filename, obj)
|
11
|
+
super()
|
12
|
+
@parent = parent
|
13
|
+
@defined_by = [filename]
|
14
|
+
@name = obj[:name]
|
15
|
+
@docs = obj[:docs]
|
16
|
+
@type = nil
|
17
|
+
@writer_visibility = obj[:writer_visibility]
|
18
|
+
@reader_visibility = obj[:reader_visibility]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Marks the current attribute as an accessor.
|
22
|
+
#
|
23
|
+
# Returns the attribute's instance for chaining.
|
24
|
+
def accessor!
|
25
|
+
@type = :accessor
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Marks the current attribute as an reader.
|
30
|
+
#
|
31
|
+
# Returns the attribute's instance for chaining.
|
32
|
+
def reader!
|
33
|
+
@type = :reader
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# Marks the current attribute as an writer.
|
38
|
+
#
|
39
|
+
# Returns the attribute's instance for chaining.
|
40
|
+
def writer!
|
41
|
+
@type = :writer
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a Hash representing this attribute
|
46
|
+
def to_h
|
47
|
+
{
|
48
|
+
defined_by:,
|
49
|
+
name:,
|
50
|
+
docs:,
|
51
|
+
type:,
|
52
|
+
writer_visibility:,
|
53
|
+
reader_visibility:
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|