docrb 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,178 +0,0 @@
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
@@ -1,123 +0,0 @@
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
@@ -1,58 +0,0 @@
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
@@ -1,111 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docrb
4
- class DocCompiler
5
- # DocBlocks provides utilities for annotating and formatting documentation
6
- # blocks.
7
- class DocBlocks
8
- # Internal: Transforms a provided reference on a given parent by a
9
- # ttempting to resolve it into a specific object.
10
- #
11
- # ref - Reference to be resolved
12
- # parent - The reference's parent
13
- #
14
- # Returns the reference itself by augmenting it with :ref_type and
15
- # :ref_path information.
16
- def self.process_reference(ref, parent)
17
- return process_method_reference(ref, parent) if ref[:ref_type] == :method
18
- return ref if ref[:ref_type] != :ambiguous
19
-
20
- resolved = parent.resolve_ref(ref)
21
- if resolved.nil?
22
- ref[:ref_type] = :not_found
23
- return ref
24
- end
25
- ref[:ref_type] = resolved.type
26
- ref[:ref_path] = resolved.path
27
- ref
28
- end
29
-
30
- def self.process_method_reference(ref, parent)
31
- if (resolved = parent.resolve_ref(ref))
32
- ref[:ref_path] = resolved.path
33
- end
34
- ref
35
- end
36
-
37
- # Internal: Processes a identifier on a provided parent. Returns an
38
- # augmented reference with extra location information whenever it can
39
- # be resolved.
40
- #
41
- # id - Identifier to be processed
42
- # parent - Parent on which the identifier appeared.
43
- def self.process_identifier(id, parent)
44
- resolved = parent.resolve(id[:contents].to_sym)
45
- if resolved.nil?
46
- puts "Unresolved: #{id[:contents]} on #{parent.path}"
47
- return {
48
- type: :span,
49
- contents: Markdown.inline("`#{id[:contents]}`")
50
- }
51
- end
52
- {
53
- type: :ref,
54
- ref_type: resolved.type,
55
- ref_path: resolved.path,
56
- contents: id[:contents]
57
- }
58
- end
59
-
60
- # Internal: Formats a given TextBlock object by expanding its contents
61
- # into markdown annotations and attempting to resolve references and
62
- # identifiers.
63
- #
64
- # Returns the updated block list.
65
- def self.format_text_block(block, parent)
66
- unless block[:contents].is_a? Array
67
- block[:contents] = Markdown.inline(block[:contents])
68
- return block
69
- end
70
- block[:contents].map! do |c|
71
- case c[:type]
72
- when :span
73
- c[:contents] = Markdown.inline(c[:contents])
74
- when :ref
75
- c = process_reference(c, parent)
76
- when :camelcase_identifier
77
- c = process_identifier(c, parent)
78
- end
79
- c
80
- end
81
- block
82
- end
83
-
84
- # Public: Updates a given documentation block by augmenting markdown
85
- # elements, references, fields and identifiers.
86
- #
87
- # doc - Documentation block to be processed
88
- # parent - The parent container to which the block belongs to.
89
- #
90
- # Returns the updated block.
91
- def self.prepare(doc, parent: nil)
92
- return unless doc
93
-
94
- doc[:contents].map! do |block|
95
- case block[:type]
96
- when :text_block
97
- format_text_block(block, parent)
98
- when :code_example
99
- block[:contents] = Markdown.render_source(block[:contents])
100
- when :field_block
101
- block[:contents] = block[:contents].transform_values { |v| format_text_block(v, parent) }
102
- else
103
- puts "Skipped block with type #{block[:type]}"
104
- end
105
- block
106
- end
107
- doc
108
- end
109
- end
110
- end
111
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docrb
4
- class DocCompiler
5
- # DocClass represents a documented class
6
- class DocClass < BaseContainer
7
- attr_accessor :inherits, :attributes
8
-
9
- # Initializes a new instance with the provided parent, file, and object.
10
- #
11
- # parent - The parent container holding this class definition.
12
- # filename - The filename defining this class.
13
- # obj - The parsed class data.
14
- def initialize(parent, filename, obj)
15
- @inherits = nil
16
- @attributes = ObjectContainer.new(self, DocAttribute, always_append: true)
17
- super
18
- end
19
-
20
- def appended(filename, obj)
21
- obj.fetch(:attr_accessor, []).each do |att|
22
- @attributes.push(filename, att).accessor!
23
- end
24
-
25
- obj.fetch(:attr_reader, []).each do |att|
26
- @attributes.push(filename, att).reader!
27
- end
28
-
29
- obj.fetch(:attr_writer, []).each do |att|
30
- @attributes.push(filename, att).writer!
31
- end
32
- end
33
-
34
- # Returns a Hash representation of this class definition
35
- def to_h
36
- super.to_h.merge({
37
- inherits:,
38
- attributes: merged_attributes.transform_values { |v| unpack(v) }
39
- })
40
- end
41
- end
42
- end
43
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docrb
4
- class DocCompiler
5
- # DocMethod represents a documented method
6
- class DocMethod < Resolvable
7
- attr_reader :parent, :type, :name, :args, :doc, :visibility,
8
- :overriden_by, :defined_by
9
-
10
- # Initializes a new DocMethod instance with a given parent, path, and
11
- # object.
12
- #
13
- # parent - The object holding the represented method
14
- # filename - The filename on which the method is defined on
15
- # obj - The method definition itself
16
- def initialize(parent, filename, obj)
17
- super()
18
- @parent = parent
19
- @type = obj[:type]
20
- @name = obj[:name]
21
- @args = obj[:args]
22
- @doc = obj[:doc]
23
- @visibility = obj[:visibility]
24
- @overriden_by = nil
25
- @defined_by = FileRef.new(nil, filename, obj)
26
- end
27
-
28
- # Public: Marks this method as being overriden by a provided object in
29
- # a given filename. This method initializes a new DocMethod instance using
30
- # this instance's parent, the provided filename and object, and invokes
31
- # #override! on it.
32
- #
33
- # filename - The filename defining the override for this method
34
- # obj - The object representing the override for this method
35
- def override(filename, obj)
36
- override! DocMethod.new(@parent, filename, obj)
37
- end
38
-
39
- # Public: Marks this method as being overriden by the provided method
40
- #
41
- # method - DocMethod object overriding this method's implementation
42
- def override!(method)
43
- @overriden_by = method
44
- end
45
-
46
- def inspect
47
- type = @type == :def ? "method" : "singleton method"
48
- overriden = overriden_by.nil? ? "" : " overriden"
49
- "#<DocMethod:#{format("0x%08x", object_id * 2)} #{visibility} #{type} #{name}#{overriden}>"
50
- end
51
-
52
- # Public: Returns a Hash representation of this method
53
- def to_h
54
- {
55
- type:,
56
- name:,
57
- args:,
58
- doc: DocBlocks.prepare(doc, parent: self),
59
- visibility:,
60
- overriden_by:,
61
- defined_by: defined_by&.to_h
62
- }
63
- end
64
- end
65
- end
66
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docrb
4
- class DocCompiler
5
- # DocModule represents a documented Module object
6
- class DocModule < BaseContainer
7
- end
8
- end
9
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docrb
4
- class DocCompiler
5
- # FileRef represents a file reference. This class is used to represent
6
- # definition metadata of objects in files. Instances of this class
7
- # indicates filenames and boundaries for a represented object.
8
- class FileRef
9
- attr_reader :filename, :start_at, :end_at
10
-
11
- # Initializes a new FileRef instance.
12
- #
13
- # _parent - Unused.
14
- # filename - Path to the file being referenced
15
- # obj - The object being referenced in the provided filename
16
- #
17
- def initialize(_parent, filename, obj)
18
- @filename = filename
19
- @start_at, @end_at = obj.values_at(:start_at, :end_at)
20
- end
21
-
22
- # Public: Returns the source code portion of the represented reference
23
- def ruby_source
24
- f = File.read(filename).split("\n")[start_at - 1..end_at - 1]
25
- f.join("\n")
26
- end
27
-
28
- # Public: Returns the reference's Hash representation
29
- def to_h
30
- source = ruby_source
31
- {
32
- filename:,
33
- start_at:,
34
- end_at:,
35
- source:,
36
- markdown_source: Markdown.render_source(source)
37
- }
38
- end
39
- end
40
- end
41
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docrb
4
- class DocCompiler
5
- # ObjectContainer implements utilities for representing a container of
6
- # objects with a common type.
7
- class ObjectContainer
8
- extend Forwardable
9
-
10
- attr_reader :objects
11
-
12
- # Initializes a new container with a given parent, containing objects with
13
- # a provided class and options.
14
- #
15
- # parent - The container's parent
16
- # cls - The class of represented items
17
- # **opts - Options list. Currently, only `always_append` is supported,
18
- # which indicates that all objects provided to the instance must
19
- # be appended regardless of their type.
20
- def initialize(parent, cls, **opts)
21
- @cls = cls
22
- @objects = []
23
- @opts = opts
24
- @parent = parent
25
- end
26
-
27
- def_delegators :@objects, :length, :each, :map, :find, :filter, :first, :[], :to_json
28
-
29
- # Pushes a new object into the container. May raise ArgumentError in case
30
- # the object being pushed is not compatible with the container's base
31
- # object.
32
- #
33
- # filename - Name of the file which defines the object being pushed
34
- # obj - The object being pushed
35
- #
36
- # Returns the new instance representing the pushed object.
37
- def push(filename, obj)
38
- if @cls.method_defined?(:name) &&
39
- (instance = @objects.find { |o| o.name == obj[:name] }) &&
40
- !@opts.fetch(:always_append, false)
41
-
42
- return instance.merge(filename, obj) if instance.respond_to? :merge
43
- return instance.override(filename, obj) if instance.respond_to? :override
44
- return instance.append(filename, obj) if instance.respond_to? :append
45
-
46
- raise ArgumentError, "cannot handle existing object #{obj[:name]} for container of type #{@cls.name}"
47
- end
48
-
49
- inst = @cls.new(@parent, filename, obj)
50
- @objects << inst
51
- inst
52
- end
53
-
54
- def inspect
55
- opts = []
56
- opts << "always_append" if @opts[:always_append]
57
- count = if @objects.count.zero?
58
- "empty"
59
- else
60
- "#{@objects.length} object#{@objects.length == 1 ? "" : "s"}"
61
- end
62
- obj_id = format("0x%08x", object_id * 2)
63
- opts_repr = opts.empty? ? "" : " #{opts.join(", ")}"
64
- "#<ObjectContainer:#{obj_id} containing #{@cls.name}, #{count}#{opts_repr}>"
65
- end
66
- end
67
- end
68
- end