solargraph 0.9.0 → 0.9.1
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 +4 -4
- data/lib/solargraph/api_map.rb +72 -58
- data/lib/solargraph/code_map.rb +25 -4
- data/lib/solargraph/server.rb +8 -1
- data/lib/solargraph/shell.rb +1 -1
- data/lib/solargraph/suggestion.rb +38 -18
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/yard_map.rb +30 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3993f33469901ac1b7d51659d9452a48adcd0e3f
|
4
|
+
data.tar.gz: de8f13ed8c39b332b7837fca4bcdfd8516ca8c1f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0fdcec41d5a9b6920596d30f16a719948e74b3432b53584a0621b05f7c6d815e5bc5e69748737ef4c415db67061682babefeac0cf28c4cc73e69b8927b86b768
|
7
|
+
data.tar.gz: f6daf69d9d06dba1b7fea6e29ed5a2d8c336cfccbe3c33ab29cbb2fa05c79eca886f1e1474ed92ba5cc11a7eeb71ed6fbdedc5de96fc8ecd52c634fe91417209
|
data/lib/solargraph/api_map.rb
CHANGED
@@ -24,6 +24,7 @@ module Solargraph
|
|
24
24
|
attr_reader :workspace
|
25
25
|
attr_reader :required
|
26
26
|
|
27
|
+
# @param workspace [String]
|
27
28
|
def initialize workspace = nil
|
28
29
|
@workspace = workspace.gsub(/\\/, '/') unless workspace.nil?
|
29
30
|
clear
|
@@ -38,6 +39,7 @@ module Solargraph
|
|
38
39
|
end
|
39
40
|
|
40
41
|
def clear
|
42
|
+
@file_source = {}
|
41
43
|
@file_nodes = {}
|
42
44
|
@file_comments = {}
|
43
45
|
@parent_stack = {}
|
@@ -46,15 +48,20 @@ module Solargraph
|
|
46
48
|
@required = []
|
47
49
|
end
|
48
50
|
|
51
|
+
# @return [Solargraph::YardMap]
|
49
52
|
def yard_map
|
50
53
|
@yard_map ||= YardMap.new(required: required, workspace: workspace)
|
51
54
|
end
|
52
55
|
|
56
|
+
# @param filename [String]
|
53
57
|
def append_file filename
|
54
58
|
append_source File.read(filename), filename
|
55
59
|
end
|
56
60
|
|
61
|
+
# @param text [String]
|
62
|
+
# @param filename [String]
|
57
63
|
def append_source text, filename = nil
|
64
|
+
@file_source[filename] = text
|
58
65
|
begin
|
59
66
|
node, comments = Parser::CurrentRuby.parse_with_comments(text)
|
60
67
|
append_node(node, comments, filename)
|
@@ -107,7 +114,6 @@ module Solargraph
|
|
107
114
|
@file_comments[filename][node.loc]
|
108
115
|
end
|
109
116
|
|
110
|
-
|
111
117
|
def self.get_keywords
|
112
118
|
result = []
|
113
119
|
keywords = KEYWORDS + ['attr_reader', 'attr_writer', 'attr_accessor', 'private', 'public', 'protected']
|
@@ -149,45 +155,6 @@ module Solargraph
|
|
149
155
|
result
|
150
156
|
end
|
151
157
|
|
152
|
-
def inner_namespaces_in name, root, skip
|
153
|
-
result = []
|
154
|
-
fqns = find_fully_qualified_namespace(name, root)
|
155
|
-
unless fqns.nil? or skip.include?(fqns)
|
156
|
-
skip.push fqns
|
157
|
-
nodes = get_namespace_nodes(fqns)
|
158
|
-
nodes.delete_if { |n| yardoc_has_file?(get_filename_for(n))}
|
159
|
-
unless nodes.empty?
|
160
|
-
cursor = @namespace_tree
|
161
|
-
parts = fqns.split('::')
|
162
|
-
parts.each { |p|
|
163
|
-
cursor = cursor[p]
|
164
|
-
}
|
165
|
-
unless cursor.nil?
|
166
|
-
cursor.keys.each { |k|
|
167
|
-
type = get_namespace_type(k, fqns)
|
168
|
-
kind = nil
|
169
|
-
detail = nil
|
170
|
-
if type == :class
|
171
|
-
kind = Suggestion::CLASS
|
172
|
-
detail = 'Class'
|
173
|
-
elsif type == :module
|
174
|
-
kind = Suggestion::MODULE
|
175
|
-
detail = 'Module'
|
176
|
-
end
|
177
|
-
result.push Suggestion.new(k, kind: kind, detail: detail)
|
178
|
-
}
|
179
|
-
nodes = get_namespace_nodes(fqns)
|
180
|
-
nodes.each { |n|
|
181
|
-
get_include_strings_from(n).each { |i|
|
182
|
-
result += inner_namespaces_in(i, fqns, skip)
|
183
|
-
}
|
184
|
-
}
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
188
|
-
result
|
189
|
-
end
|
190
|
-
|
191
158
|
def find_fully_qualified_namespace name, root = '', skip = []
|
192
159
|
return nil if skip.include?(root)
|
193
160
|
skip.push root
|
@@ -269,22 +236,6 @@ module Solargraph
|
|
269
236
|
@yardoc_files.include?(file)
|
270
237
|
end
|
271
238
|
|
272
|
-
def inner_get_instance_variables(node, scope)
|
273
|
-
arr = []
|
274
|
-
if node.kind_of?(AST::Node)
|
275
|
-
node.children.each { |c|
|
276
|
-
if c.kind_of?(AST::Node)
|
277
|
-
is_inst = !find_parent(c, :def).nil?
|
278
|
-
if c.type == :ivasgn and c.children[0] and ( (scope == :instance and is_inst) or (scope != :instance and !is_inst) )
|
279
|
-
arr.push Suggestion.new(c.children[0], kind: Suggestion::VARIABLE, documentation: get_comment_for(c))
|
280
|
-
end
|
281
|
-
arr += inner_get_instance_variables(c, scope) unless [:class, :module].include?(c.type)
|
282
|
-
end
|
283
|
-
}
|
284
|
-
end
|
285
|
-
arr
|
286
|
-
end
|
287
|
-
|
288
239
|
def infer_instance_variable(var, namespace, scope = :instance)
|
289
240
|
result = nil
|
290
241
|
vn = nil
|
@@ -418,11 +369,11 @@ module Solargraph
|
|
418
369
|
if c.type == :arg
|
419
370
|
args.push c.children[0]
|
420
371
|
elsif c.type == :optarg
|
421
|
-
args.push "#{c.children[0]} =
|
372
|
+
args.push "#{c.children[0]} = #{code_for(c.children[1])}"
|
422
373
|
elsif c.type == :kwarg
|
423
374
|
args.push "#{c.children[0]}:"
|
424
375
|
elsif c.type == :kwoptarg
|
425
|
-
args.push "#{c.children[0]}:
|
376
|
+
args.push "#{c.children[0]}: #{code_for(c.children[1])}"
|
426
377
|
end
|
427
378
|
}
|
428
379
|
args
|
@@ -607,6 +558,61 @@ module Solargraph
|
|
607
558
|
meths.uniq
|
608
559
|
end
|
609
560
|
|
561
|
+
def inner_namespaces_in name, root, skip
|
562
|
+
result = []
|
563
|
+
fqns = find_fully_qualified_namespace(name, root)
|
564
|
+
unless fqns.nil? or skip.include?(fqns)
|
565
|
+
skip.push fqns
|
566
|
+
nodes = get_namespace_nodes(fqns)
|
567
|
+
nodes.delete_if { |n| yardoc_has_file?(get_filename_for(n))}
|
568
|
+
unless nodes.empty?
|
569
|
+
cursor = @namespace_tree
|
570
|
+
parts = fqns.split('::')
|
571
|
+
parts.each { |p|
|
572
|
+
cursor = cursor[p]
|
573
|
+
}
|
574
|
+
unless cursor.nil?
|
575
|
+
cursor.keys.each { |k|
|
576
|
+
type = get_namespace_type(k, fqns)
|
577
|
+
kind = nil
|
578
|
+
detail = nil
|
579
|
+
if type == :class
|
580
|
+
kind = Suggestion::CLASS
|
581
|
+
detail = 'Class'
|
582
|
+
elsif type == :module
|
583
|
+
kind = Suggestion::MODULE
|
584
|
+
detail = 'Module'
|
585
|
+
end
|
586
|
+
result.push Suggestion.new(k, kind: kind, detail: detail)
|
587
|
+
}
|
588
|
+
nodes = get_namespace_nodes(fqns)
|
589
|
+
nodes.each { |n|
|
590
|
+
get_include_strings_from(n).each { |i|
|
591
|
+
result += inner_namespaces_in(i, fqns, skip)
|
592
|
+
}
|
593
|
+
}
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
597
|
+
result
|
598
|
+
end
|
599
|
+
|
600
|
+
def inner_get_instance_variables(node, scope)
|
601
|
+
arr = []
|
602
|
+
if node.kind_of?(AST::Node)
|
603
|
+
node.children.each { |c|
|
604
|
+
if c.kind_of?(AST::Node)
|
605
|
+
is_inst = !find_parent(c, :def).nil?
|
606
|
+
if c.type == :ivasgn and c.children[0] and ( (scope == :instance and is_inst) or (scope != :instance and !is_inst) )
|
607
|
+
arr.push Suggestion.new(c.children[0], kind: Suggestion::VARIABLE, documentation: get_comment_for(c))
|
608
|
+
end
|
609
|
+
arr += inner_get_instance_variables(c, scope) unless [:class, :module].include?(c.type)
|
610
|
+
end
|
611
|
+
}
|
612
|
+
end
|
613
|
+
arr
|
614
|
+
end
|
615
|
+
|
610
616
|
def mappable?(node)
|
611
617
|
return true if node.kind_of?(AST::Node) and [:array, :hash, :str, :int, :float].include?(node.type)
|
612
618
|
# TODO Add node.type :casgn (constant assignment)
|
@@ -728,5 +734,13 @@ module Solargraph
|
|
728
734
|
}
|
729
735
|
end
|
730
736
|
end
|
737
|
+
|
738
|
+
def code_for node
|
739
|
+
src = @file_source[get_filename_for(node)]
|
740
|
+
return nil if src.nil?
|
741
|
+
b = node.location.expression.begin.begin_pos
|
742
|
+
e = node.location.expression.end.end_pos
|
743
|
+
src[b..e].strip.gsub(/,$/, '')
|
744
|
+
end
|
731
745
|
end
|
732
746
|
end
|
data/lib/solargraph/code_map.rb
CHANGED
@@ -23,8 +23,8 @@ module Solargraph
|
|
23
23
|
# HACK: The current file is parsed with a trailing underscore to fix
|
24
24
|
# incomplete trees resulting from short scripts (e.g., a lone variable
|
25
25
|
# assignment).
|
26
|
-
node, comments = Parser::CurrentRuby.parse_with_comments(tmp + "\n_")
|
27
|
-
@node = self.api_map.append_node(node, comments, filename)
|
26
|
+
node, @comments = Parser::CurrentRuby.parse_with_comments(tmp + "\n_")
|
27
|
+
@node = self.api_map.append_node(node, @comments, filename)
|
28
28
|
@parsed = tmp
|
29
29
|
@code.freeze
|
30
30
|
@parsed.freeze
|
@@ -99,6 +99,16 @@ module Solargraph
|
|
99
99
|
n.kind_of?(AST::Node) and n.type == :str
|
100
100
|
end
|
101
101
|
|
102
|
+
# Determine if the specified index is inside a comment.
|
103
|
+
#
|
104
|
+
# @return [Boolean]
|
105
|
+
def comment_at?(index)
|
106
|
+
@comments.each do |c|
|
107
|
+
return true if index > c.location.expression.begin_pos and index <= c.location.expression.end_pos
|
108
|
+
end
|
109
|
+
false
|
110
|
+
end
|
111
|
+
|
102
112
|
def parent_node_from(index, *types)
|
103
113
|
arr = tree_at(index)
|
104
114
|
arr.each { |a|
|
@@ -178,7 +188,7 @@ module Solargraph
|
|
178
188
|
#
|
179
189
|
# @return [Array<Suggestions>] The completion suggestions
|
180
190
|
def suggest_at index, filtered: false, with_snippets: false
|
181
|
-
return [] if string_at?(index) or string_at?(index - 1)
|
191
|
+
return [] if string_at?(index) or string_at?(index - 1) or comment_at?(index)
|
182
192
|
result = []
|
183
193
|
phrase = phrase_at(index)
|
184
194
|
signature = get_signature_at(index)
|
@@ -387,6 +397,7 @@ module Solargraph
|
|
387
397
|
# @x.bar @x.bar
|
388
398
|
# y.split(', ').length y.split.length
|
389
399
|
#
|
400
|
+
# @param index [Integer]
|
390
401
|
# @return [String]
|
391
402
|
def get_signature_at index
|
392
403
|
brackets = 0
|
@@ -478,15 +489,25 @@ module Solargraph
|
|
478
489
|
private
|
479
490
|
|
480
491
|
def get_method_arguments_from node
|
492
|
+
param_hash = {}
|
493
|
+
cmnt = api_map.get_comment_for(node)
|
494
|
+
unless cmnt.nil?
|
495
|
+
tags = cmnt.tags(:param)
|
496
|
+
tags.each do |tag|
|
497
|
+
param_hash[tag.name] = tag.types[0]
|
498
|
+
end
|
499
|
+
end
|
481
500
|
result = []
|
482
501
|
args = node.children[1]
|
483
502
|
args.children.each do |arg|
|
484
503
|
name = arg.children[0].to_s
|
485
|
-
result.push Suggestion.new(name, kind: Suggestion::PROPERTY, insert: name)
|
504
|
+
result.push Suggestion.new(name, kind: Suggestion::PROPERTY, insert: name, return_type: param_hash[name])
|
486
505
|
end
|
487
506
|
result
|
488
507
|
end
|
489
508
|
|
509
|
+
# @param suggestions [Array<Solargraph::Suggestion>]
|
510
|
+
# @param word [String]
|
490
511
|
def reduce_starting_with(suggestions, word)
|
491
512
|
suggestions.reject { |s|
|
492
513
|
!s.label.start_with?(word)
|
data/lib/solargraph/server.rb
CHANGED
@@ -132,12 +132,19 @@ module Solargraph
|
|
132
132
|
end
|
133
133
|
|
134
134
|
class Helpers
|
135
|
+
include YARD::Templates::Helpers::HtmlHelper
|
136
|
+
|
135
137
|
attr_accessor :object
|
136
138
|
attr_accessor :serializer
|
137
|
-
|
139
|
+
|
140
|
+
def url_for(object)
|
141
|
+
'.'
|
142
|
+
end
|
143
|
+
|
138
144
|
def options
|
139
145
|
@options ||= YARD::Templates::TemplateOptions.new
|
140
146
|
end
|
147
|
+
|
141
148
|
# HACK: The linkify method just returns the arguments as plain text
|
142
149
|
def linkify *args
|
143
150
|
args.join(', ')
|
data/lib/solargraph/shell.rb
CHANGED
@@ -82,7 +82,7 @@ module Solargraph
|
|
82
82
|
|
83
83
|
desc 'config [DIRECTORY]', 'Create or overwrite a default configuration file'
|
84
84
|
def config(directory = '.')
|
85
|
-
File.open(File.join(directory, '.solargraph'), 'w') do |file|
|
85
|
+
File.open(File.join(directory, '.solargraph.yml'), 'w') do |file|
|
86
86
|
file.puts "include:",
|
87
87
|
" - ./**/*.rb",
|
88
88
|
"exclude:",
|
@@ -14,7 +14,7 @@ module Solargraph
|
|
14
14
|
|
15
15
|
attr_reader :label, :kind, :insert, :detail, :documentation, :code_object, :location, :arguments
|
16
16
|
|
17
|
-
def initialize label, kind: KEYWORD, insert: nil, detail: nil, documentation: nil, code_object: nil, location: nil, arguments: []
|
17
|
+
def initialize label, kind: KEYWORD, insert: nil, detail: nil, documentation: nil, code_object: nil, location: nil, arguments: [], return_type: nil
|
18
18
|
@helper = Server::Helpers.new
|
19
19
|
@label = label.to_s
|
20
20
|
@kind = kind
|
@@ -24,6 +24,7 @@ module Solargraph
|
|
24
24
|
@documentation = documentation
|
25
25
|
@location = location
|
26
26
|
@arguments = arguments
|
27
|
+
@return_type = return_type
|
27
28
|
end
|
28
29
|
|
29
30
|
def path
|
@@ -35,27 +36,28 @@ module Solargraph
|
|
35
36
|
end
|
36
37
|
|
37
38
|
def return_type
|
38
|
-
if
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
39
|
+
if @return_type.nil?
|
40
|
+
if code_object.nil?
|
41
|
+
unless documentation.nil?
|
42
|
+
if documentation.kind_of?(YARD::Docstring)
|
43
|
+
t = documentation.tag(:return)
|
44
|
+
@return_type = t.types[0] unless t.nil? or t.types.nil?
|
45
|
+
else
|
46
|
+
match = documentation.match(/@return \[([a-z0-9:_]*)/i)
|
47
|
+
@return_type = match[1] unless match.nil?
|
48
|
+
end
|
47
49
|
end
|
48
|
-
end
|
49
|
-
else
|
50
|
-
o = code_object.tag(:overload)
|
51
|
-
if o.nil?
|
52
|
-
r = code_object.tag(:return)
|
53
50
|
else
|
54
|
-
|
51
|
+
o = code_object.tag(:overload)
|
52
|
+
if o.nil?
|
53
|
+
r = code_object.tag(:return)
|
54
|
+
else
|
55
|
+
r = o.tag(:return)
|
56
|
+
end
|
57
|
+
@return_type = r.types[0] unless r.nil? or r.types.nil?
|
55
58
|
end
|
56
|
-
return r.types[0] unless r.nil?
|
57
59
|
end
|
58
|
-
|
60
|
+
@return_type
|
59
61
|
end
|
60
62
|
|
61
63
|
def documentation
|
@@ -67,6 +69,23 @@ module Solargraph
|
|
67
69
|
@documentation
|
68
70
|
end
|
69
71
|
|
72
|
+
def params
|
73
|
+
if @params.nil?
|
74
|
+
@params = []
|
75
|
+
return @params if documentation.nil?
|
76
|
+
param_tags = documentation.tags(:param)
|
77
|
+
unless param_tags.empty?
|
78
|
+
param_tags.each do |t|
|
79
|
+
txt = t.name
|
80
|
+
txt += " [#{t.types.join(',')}]" unless t.types.nil? or t.types.empty?
|
81
|
+
txt += " #{t.text}" unless t.text.nil? or t.text.empty?
|
82
|
+
@params.push txt
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
@params
|
87
|
+
end
|
88
|
+
|
70
89
|
def to_json args={}
|
71
90
|
obj = {
|
72
91
|
label: @label,
|
@@ -76,6 +95,7 @@ module Solargraph
|
|
76
95
|
path: path,
|
77
96
|
location: (@location.nil? ? nil : @location.to_s),
|
78
97
|
arguments: @arguments,
|
98
|
+
params: params,
|
79
99
|
return_type: return_type,
|
80
100
|
documentation: @helper.html_markup_rdoc(documentation.to_s)
|
81
101
|
}
|
data/lib/solargraph/version.rb
CHANGED
data/lib/solargraph/yard_map.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'parser/current'
|
3
3
|
require 'yard'
|
4
|
+
require 'bundler'
|
4
5
|
|
5
6
|
module Solargraph
|
6
7
|
|
7
8
|
class YardMap
|
8
9
|
autoload :Cache, 'solargraph/yard_map/cache'
|
9
10
|
|
10
|
-
|
11
|
+
attr_reader :workspace
|
12
|
+
|
13
|
+
def initialize required: [], workspace: nil, with_bundled: false
|
14
|
+
@workspace = workspace
|
11
15
|
unless workspace.nil?
|
12
16
|
wsy = File.join(workspace, '.yardoc')
|
13
17
|
yardocs.push wsy if File.exist?(wsy)
|
@@ -22,16 +26,35 @@ module Solargraph
|
|
22
26
|
if gy.nil?
|
23
27
|
STDERR.puts "Required path not found: #{r}"
|
24
28
|
else
|
25
|
-
|
29
|
+
STDERR.puts "Adding #{gy}"
|
30
|
+
yardocs.unshift gy
|
26
31
|
end
|
27
32
|
end
|
28
33
|
end
|
29
34
|
}
|
30
35
|
yardocs.push File.join(Dir.home, '.solargraph', 'cache', '2.0.0', 'yardoc')
|
31
36
|
yardocs.uniq!
|
37
|
+
include_bundled_gems if with_bundled
|
32
38
|
cache_core
|
33
39
|
end
|
34
40
|
|
41
|
+
def include_bundled_gems
|
42
|
+
return if workspace.nil?
|
43
|
+
lockfile = File.join(workspace, 'Gemfile.lock')
|
44
|
+
return unless File.file?(lockfile)
|
45
|
+
parser = Bundler::LockfileParser.new(Bundler.read_file(lockfile))
|
46
|
+
parser.specs.each do |s|
|
47
|
+
STDERR.puts "Specs include #{s.name}"
|
48
|
+
gy = YARD::Registry.yardoc_file_for_gem(s.name)
|
49
|
+
if gy.nil?
|
50
|
+
STDERR.puts "Bundled gem not found: #{s.name}"
|
51
|
+
else
|
52
|
+
yardocs.unshift gy unless yardocs.include?(gy)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Array<String>]
|
35
58
|
def yardocs
|
36
59
|
@yardocs ||= []
|
37
60
|
end
|
@@ -50,6 +73,7 @@ module Solargraph
|
|
50
73
|
end
|
51
74
|
end
|
52
75
|
|
76
|
+
# @param query [String]
|
53
77
|
def search query
|
54
78
|
found = []
|
55
79
|
yardocs.each { |y|
|
@@ -63,6 +87,7 @@ module Solargraph
|
|
63
87
|
found.sort
|
64
88
|
end
|
65
89
|
|
90
|
+
# @param query [String]
|
66
91
|
def document query
|
67
92
|
found = []
|
68
93
|
yardocs.each { |y|
|
@@ -109,6 +134,7 @@ module Solargraph
|
|
109
134
|
result
|
110
135
|
end
|
111
136
|
|
137
|
+
# @param signature [String]
|
112
138
|
def at signature
|
113
139
|
yardocs.each { |y|
|
114
140
|
yard = load_yardoc(y)
|
@@ -120,6 +146,8 @@ module Solargraph
|
|
120
146
|
nil
|
121
147
|
end
|
122
148
|
|
149
|
+
# @param signature [String]
|
150
|
+
# @param scope [String]
|
123
151
|
def resolve signature, scope
|
124
152
|
yardocs.each { |y|
|
125
153
|
yard = load_yardoc(y)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solargraph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fred Snyder
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parser
|