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