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,111 @@
|
|
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
|
@@ -0,0 +1,43 @@
|
|
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
|
@@ -0,0 +1,66 @@
|
|
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
|
@@ -0,0 +1,41 @@
|
|
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
|
@@ -0,0 +1,68 @@
|
|
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
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docrb
|
4
|
+
# DocCompiler implements utilities to post-process documentation data
|
5
|
+
# generated by RubyParser.
|
6
|
+
class DocCompiler < Resolvable
|
7
|
+
autoload :FileRef, "docrb/doc_compiler/file_ref"
|
8
|
+
autoload :BaseContainer, "docrb/doc_compiler/base_container"
|
9
|
+
autoload :DocClass, "docrb/doc_compiler/doc_class"
|
10
|
+
autoload :DocModule, "docrb/doc_compiler/doc_module"
|
11
|
+
autoload :DocMethod, "docrb/doc_compiler/doc_method"
|
12
|
+
autoload :DocAttribute, "docrb/doc_compiler/doc_attribute"
|
13
|
+
autoload :ObjectContainer, "docrb/doc_compiler/object_container"
|
14
|
+
autoload :DocBlocks, "docrb/doc_compiler/doc_blocks"
|
15
|
+
|
16
|
+
attr_reader :classes, :modules, :methods, :parent
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
super
|
20
|
+
@classes = ObjectContainer.new(self, DocClass)
|
21
|
+
@modules = ObjectContainer.new(self, DocModule)
|
22
|
+
@methods = ObjectContainer.new(self, DocMethod)
|
23
|
+
@parent = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# Appends a given object to the compiler collection. Raises ArgumentError in
|
27
|
+
# case the object cannot be appended to the current container.
|
28
|
+
#
|
29
|
+
# obj - Object to be appended
|
30
|
+
def append(obj)
|
31
|
+
filename = obj[:filename]
|
32
|
+
target = case obj[:type]
|
33
|
+
when :module
|
34
|
+
@modules
|
35
|
+
when :class
|
36
|
+
@classes
|
37
|
+
when :def, :defs
|
38
|
+
@methods
|
39
|
+
end
|
40
|
+
|
41
|
+
raise ArgumentError, "cannot append obj of type #{obj[:type]}" if target.nil?
|
42
|
+
|
43
|
+
target.push(filename, obj)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Transforms the content's of the parser into a Hash representation
|
47
|
+
def to_h
|
48
|
+
{
|
49
|
+
modules: @modules.map(&:to_h),
|
50
|
+
classes: @classes.map(&:to_h),
|
51
|
+
methods: @methods.map(&:to_h)
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docrb
|
4
|
+
# Renderer provides a Redcarpet renderer with Rouge extensions
|
5
|
+
class Renderer < Redcarpet::Render::HTML
|
6
|
+
def initialize(extensions = {})
|
7
|
+
super extensions.merge(link_attributes: { target: "_blank" })
|
8
|
+
end
|
9
|
+
include Rouge::Plugins::Redcarpet
|
10
|
+
end
|
11
|
+
|
12
|
+
# InlineRenderer provides a renderer for inline contents. This renderer
|
13
|
+
# does not emit paragraph tags.
|
14
|
+
class InlineRenderer < Renderer
|
15
|
+
def paragraph(text)
|
16
|
+
text
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Markdown provides utilities for generating HTML from markdown contents
|
21
|
+
class Markdown
|
22
|
+
# Internal: Creates a new renderer based on a provided type by setting
|
23
|
+
# sensible defaults.
|
24
|
+
#
|
25
|
+
# type - Type of the renderer to be initialised. Use Docrb::Renderer or
|
26
|
+
# InlineRenderer
|
27
|
+
def self.make_render(type)
|
28
|
+
Redcarpet::Markdown.new(
|
29
|
+
type,
|
30
|
+
fenced_code_blocks: true,
|
31
|
+
autolink: true
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Renders a given input using the default renderer.
|
36
|
+
#
|
37
|
+
# input - Markdown content to be rendered
|
38
|
+
#
|
39
|
+
# Returns an HTML string containing the rendered Markdown content
|
40
|
+
def self.render(input)
|
41
|
+
make_render(Renderer).render(input)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Renders a given input using the inline renderer.
|
45
|
+
#
|
46
|
+
# input - Markdown content to be rendered
|
47
|
+
#
|
48
|
+
# Returns an HTML string containing the rendered Markdown content
|
49
|
+
def self.inline(input)
|
50
|
+
make_render(InlineRenderer).render(input)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Renders a given Ruby source code into HTML
|
54
|
+
#
|
55
|
+
# source - Source code to be rendered
|
56
|
+
#
|
57
|
+
# Returns an HTML string containing the rendered source code
|
58
|
+
def self.render_source(source)
|
59
|
+
render("```ruby\n#{source}\n```")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
unless Class.respond_to? :try?
|
4
|
+
class Object
|
5
|
+
def try?(method, *args, **kwargs, &)
|
6
|
+
send(method, *args, **kwargs, &) if respond_to?(method)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.try?(method, *args, **kwargs, &)
|
10
|
+
send(method, *args, **kwargs, &) if respond_to?(method)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docrb
|
4
|
+
# Resolvable implements utilities for resolving names and class paths on a
|
5
|
+
# container context (module/class)
|
6
|
+
class Resolvable
|
7
|
+
# Returns a matcher for a provided name
|
8
|
+
#
|
9
|
+
# name - Name to match against
|
10
|
+
def by_name(name)
|
11
|
+
->(obj) { obj.name == name }
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns a matcher for a provided name and type
|
15
|
+
#
|
16
|
+
# name - Name to match against
|
17
|
+
# type - Object type to match against (:def/:sdef/:class/:module...)
|
18
|
+
def by_name_and_type(name, type)
|
19
|
+
->(obj) { obj.name == name && obj.type == type }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Attempts to resolve a method with a provided name in the container's
|
23
|
+
# context. This method attempts to match for instance methods, class
|
24
|
+
# methods, and finally by recursively calling this method on the inherited
|
25
|
+
# members, if any.
|
26
|
+
#
|
27
|
+
# name - Name of the method to be resolved.
|
28
|
+
#
|
29
|
+
# Returns a method structure, or nil, in case none is found.
|
30
|
+
def resolve_method(name)
|
31
|
+
if (local = try?(:defs)&.find(&by_name(name)))
|
32
|
+
return local
|
33
|
+
end
|
34
|
+
|
35
|
+
if (local = try?(:sdefs)&.find(&by_name(name)))
|
36
|
+
return local
|
37
|
+
end
|
38
|
+
|
39
|
+
# Inherited?
|
40
|
+
if (inherited = try?(:inherits)&.try?(:resolve_method, name, type))
|
41
|
+
return inherited
|
42
|
+
end
|
43
|
+
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
|
47
|
+
# Resolves a container using a provided reference.
|
48
|
+
#
|
49
|
+
# ref - Either a reference hash (containing :class_path, :target, :name), or
|
50
|
+
# a symbol representing the name of the container being resolved.
|
51
|
+
#
|
52
|
+
# Returns a matched container or nil, in case none is found.
|
53
|
+
def resolve_container(ref)
|
54
|
+
if ref.is_a? Hash
|
55
|
+
path = ref
|
56
|
+
.slice(:class_path, :target, :name)
|
57
|
+
.values
|
58
|
+
.flatten
|
59
|
+
.compact
|
60
|
+
return resolve_container(path)
|
61
|
+
end
|
62
|
+
|
63
|
+
ref = [ref] if ref.is_a? Symbol
|
64
|
+
|
65
|
+
if ref.length == 1
|
66
|
+
name = ref.first
|
67
|
+
# module?
|
68
|
+
if (mod = try?(:modules)&.find(&by_name(name)))
|
69
|
+
return mod
|
70
|
+
end
|
71
|
+
|
72
|
+
# class?
|
73
|
+
if (cls = try?(:classes)&.find(&by_name(name)))
|
74
|
+
return cls
|
75
|
+
end
|
76
|
+
|
77
|
+
# parent?
|
78
|
+
return @parent&.resolve_container(ref)
|
79
|
+
end
|
80
|
+
|
81
|
+
obj = self
|
82
|
+
while ref.length.positive?
|
83
|
+
obj = resolve_container(ref.shift)
|
84
|
+
return nil if obj.nil?
|
85
|
+
end
|
86
|
+
|
87
|
+
obj
|
88
|
+
end
|
89
|
+
|
90
|
+
# Resolves a provided name in the current containter's context. This method
|
91
|
+
# will attempt to return an object matching the provided name by looking for
|
92
|
+
# it in the following subcontainers: modules, classes, methods, attributes,
|
93
|
+
# and recursively performing the resolution on the container's parent, if
|
94
|
+
# any.
|
95
|
+
#
|
96
|
+
# name - Name of the object being resolved
|
97
|
+
#
|
98
|
+
# Returns the first object found by the provided name, or nil, in case none
|
99
|
+
# is found.
|
100
|
+
def resolve(name)
|
101
|
+
return nil if name.nil?
|
102
|
+
|
103
|
+
name = name.to_sym
|
104
|
+
|
105
|
+
# module?
|
106
|
+
if (mod = try?(:modules)&.find(&by_name(name)))
|
107
|
+
return mod
|
108
|
+
end
|
109
|
+
|
110
|
+
# class?
|
111
|
+
if (cls = try?(:classes)&.find(&by_name(name)))
|
112
|
+
return cls
|
113
|
+
end
|
114
|
+
|
115
|
+
# method?
|
116
|
+
if (met = resolve_method(name))
|
117
|
+
return met
|
118
|
+
end
|
119
|
+
|
120
|
+
# attribute?
|
121
|
+
if (att = try?(:attributes)&.find(&by_name(name)))
|
122
|
+
return att
|
123
|
+
end
|
124
|
+
|
125
|
+
if (obj = @parent&.resolve(name))
|
126
|
+
return obj
|
127
|
+
end
|
128
|
+
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
# Resolves a provided ref by creating its path string representation and
|
133
|
+
# calling #resolve_qualified
|
134
|
+
#
|
135
|
+
# ref - Hash representing the reference being resolved.
|
136
|
+
#
|
137
|
+
# Returns the object under the provided reference, or nil.
|
138
|
+
def resolve_ref(ref)
|
139
|
+
path = ref
|
140
|
+
.slice(:class_path, :target, :name)
|
141
|
+
.values
|
142
|
+
.flatten
|
143
|
+
.compact
|
144
|
+
return resolve_qualified(path.join("::")) if path.length > 1
|
145
|
+
|
146
|
+
resolve(path[0])
|
147
|
+
end
|
148
|
+
|
149
|
+
# Resolves a qualified path under the current container's context.
|
150
|
+
#
|
151
|
+
# path - Path of the object being resolved. Must be a string containing the
|
152
|
+
# object name being searched, or a classpath for it (e.g.
|
153
|
+
# `Foo::Bar::Baz`)
|
154
|
+
def resolve_qualified(path)
|
155
|
+
components = path.split("::").map(&:to_sym)
|
156
|
+
obj = root
|
157
|
+
until components.empty?
|
158
|
+
obj = obj.resolve(components.shift)
|
159
|
+
break if obj.nil?
|
160
|
+
end
|
161
|
+
obj
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns the root object for the current container
|
165
|
+
def root
|
166
|
+
obj = self
|
167
|
+
obj = obj.parent while obj.parent
|
168
|
+
obj
|
169
|
+
end
|
170
|
+
|
171
|
+
# Returns the container's full qualified path
|
172
|
+
def path
|
173
|
+
return [] if parent.nil?
|
174
|
+
|
175
|
+
parent.path + [name]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|