Skip to content

Commit 59fc76d

Browse files
authored
Merge pull request #190 from glennsarti/spike-extract-bolt-info
(GH-94) Extract Bolt module metadata and use within Plans
2 parents 053b76d + e784a13 commit 59fc76d

File tree

20 files changed

+428
-32
lines changed

20 files changed

+428
-32
lines changed

lib/puppet-languageserver/manifest/completion_provider.rb

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def self.complete(content, line_num, char_num, options = {})
2727
# Add resources
2828
all_resources { |x| items << x }
2929

30-
all_functions { |x| items << x }
30+
all_functions(options[:tasks_mode]) { |x| items << x }
3131

3232
response = LSP::CompletionList.new
3333
response.items = items
@@ -53,6 +53,14 @@ def self.complete(content, line_num, char_num, options = {})
5353
# Add resources
5454
all_resources { |x| items << x }
5555

56+
when 'Puppet::Pops::Model::PlanDefinition'
57+
# We are in the root of a `plan` statement
58+
59+
# Add resources
60+
all_resources { |x| items << x }
61+
62+
all_functions(options[:tasks_mode]) { |x| items << x }
63+
5664
when 'Puppet::Pops::Model::ResourceExpression'
5765
# We are inside a resource definition. Should display all available
5866
# properities and parameters.
@@ -176,8 +184,8 @@ def self.all_resources(&block)
176184
end
177185
end
178186

179-
def self.all_functions(&block)
180-
PuppetLanguageServer::PuppetHelper.function_names.each do |name|
187+
def self.all_functions(tasks_mode, &block)
188+
PuppetLanguageServer::PuppetHelper.function_names(tasks_mode).each do |name|
181189
item = LSP::CompletionItem.new(
182190
'label' => name.to_s,
183191
'kind' => LSP::CompletionItemKind::FUNCTION,
@@ -234,7 +242,8 @@ def self.resolve(completion_item)
234242
end
235243

236244
when 'function'
237-
item_type = PuppetLanguageServer::PuppetHelper.function(data['name'])
245+
# We don't know if this resolution is coming from a plan or not, so just assume it is
246+
item_type = PuppetLanguageServer::PuppetHelper.function(data['name'], true)
238247
return result if item_type.nil?
239248
result.documentation = item_type.doc unless item_type.doc.nil?
240249
unless item_type.nil? || item_type.signatures.count.zero?

lib/puppet-languageserver/manifest/hover_provider.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def self.resolve(content, line_num, char_num, options = {})
3333

3434
content = get_hover_content_for_access_expression(path, expr)
3535
when 'Puppet::Pops::Model::CallNamedFunctionExpression'
36-
content = get_call_named_function_expression_content(item)
36+
content = get_call_named_function_expression_content(item, options[:tasks_mode])
3737
when 'Puppet::Pops::Model::AttributeOperation'
3838
# Get the parent resource class
3939
distance_up_ast = -1
@@ -66,7 +66,7 @@ def self.resolve(content, line_num, char_num, options = {})
6666
# https://github.com/puppetlabs/puppet-specifications/blob/master/language/names.md#names
6767
# Datatypes have to start with uppercase and can be fully qualified
6868
if item.cased_value =~ /^[A-Z][a-zA-Z:0-9]*$/ # rubocop:disable Style/GuardClause
69-
content = get_puppet_datatype_content(item)
69+
content = get_puppet_datatype_content(item, options[:tasks_mode])
7070
else
7171
raise "#{item.cased_value} is an unknown QualifiedReference"
7272
end
@@ -143,10 +143,10 @@ def self.get_attribute_class_parameter_content(item_class, param)
143143
content
144144
end
145145

146-
def self.get_call_named_function_expression_content(item)
146+
def self.get_call_named_function_expression_content(item, tasks_mode)
147147
func_name = item.functor_expr.value
148148

149-
func_info = PuppetLanguageServer::PuppetHelper.function(func_name)
149+
func_info = PuppetLanguageServer::PuppetHelper.function(func_name, tasks_mode)
150150
raise "Function #{func_name} does not exist" if func_info.nil?
151151

152152
content = "**#{func_name}** Function"
@@ -189,8 +189,8 @@ def self.get_puppet_class_content(item_class)
189189
end
190190
private_class_method :get_puppet_class_content
191191

192-
def self.get_puppet_datatype_content(item)
193-
dt_info = PuppetLanguageServer::PuppetHelper.datatype(item.cased_value)
192+
def self.get_puppet_datatype_content(item, tasks_mode)
193+
dt_info = PuppetLanguageServer::PuppetHelper.datatype(item.cased_value, tasks_mode)
194194
raise "DataType #{item.cased_value} does not exist" if dt_info.nil?
195195

196196
content = "**#{item.cased_value}** Data Type"

lib/puppet-languageserver/puppet_helper.rb

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,48 @@ def self.get_puppet_resource(typename, title, local_workspace)
6565
sidecar_queue.execute_sync('resource_list', args)
6666
end
6767

68+
# Static data
69+
def self.static_data_loaded?
70+
@static_data_loaded.nil? ? false : @static_data_loaded
71+
end
72+
73+
def self.load_static_data
74+
raise('Puppet Helper Cache has not been configured') if @inmemory_cache.nil?
75+
@static_data_loaded = false
76+
77+
bolt_static_data = PuppetLanguageServer::Sidecar::Protocol::AggregateMetadata.new
78+
Dir.glob(File.join(PuppetLanguageServer.static_data_dir, 'bolt-*.json')) do |path|
79+
PuppetLanguageServer.log_message(:debug, "Importing static data file #{path}...")
80+
# No need to catch errors here. As this is static data and is tested in rspec
81+
# Sure, we could have corrupt/missing files on disk, but then we have bigger issues
82+
data = PuppetLanguageServer::Sidecar::Protocol::AggregateMetadata.new.from_json!(File.open(path, 'rb:UTF-8') { |f| f.read })
83+
data.each_list { |_, list| bolt_static_data.concat!(list) }
84+
end
85+
86+
@inmemory_cache.import_sidecar_list!(bolt_static_data.classes, :class, :bolt)
87+
@inmemory_cache.import_sidecar_list!(bolt_static_data.datatypes, :datatype, :bolt)
88+
@inmemory_cache.import_sidecar_list!(bolt_static_data.functions, :function, :bolt)
89+
@inmemory_cache.import_sidecar_list!(bolt_static_data.types, :type, :bolt)
90+
91+
bolt_static_data.each_list do |k, v|
92+
if v.nil?
93+
PuppetLanguageServer.log_message(:debug, "Static bolt data returned no #{k}")
94+
else
95+
PuppetLanguageServer.log_message(:debug, "Static bolt data returned #{v.count} #{k}")
96+
end
97+
end
98+
99+
@static_data_loaded = true
100+
end
101+
102+
def self.load_static_data_async
103+
raise('Puppet Helper Cache has not been configured') if @inmemory_cache.nil?
104+
@static_data_loaded = false
105+
Thread.new do
106+
load_static_data
107+
end
108+
end
109+
68110
# Types
69111
def self.default_types_loaded?
70112
@default_types_loaded.nil? ? false : @default_types_loaded
@@ -131,18 +173,25 @@ def self.filtered_function_names(&block)
131173
result
132174
end
133175

134-
def self.function(name)
176+
def self.function(name, tasks_mode = false)
135177
return nil if @default_functions_loaded == false
136178
raise('Puppet Helper Cache has not been configured') if @inmemory_cache.nil?
137179
load_default_functions unless @default_functions_loaded
138-
@inmemory_cache.object_by_name(:function, name)
180+
exclude_origins = tasks_mode ? [] : [:bolt]
181+
@inmemory_cache.object_by_name(
182+
:function,
183+
name,
184+
:fuzzy_match => true,
185+
:exclude_origins => exclude_origins
186+
)
139187
end
140188

141-
def self.function_names
189+
def self.function_names(tasks_mode = false)
142190
return [] if @default_functions_loaded == false
143191
raise('Puppet Helper Cache has not been configured') if @inmemory_cache.nil?
144192
load_default_functions if @default_functions_loaded.nil?
145-
@inmemory_cache.object_names_by_section(:function).map(&:to_s)
193+
exclude_origins = tasks_mode ? [] : [:bolt]
194+
@inmemory_cache.object_names_by_section(:function, :exclude_origins => exclude_origins).map(&:to_s)
146195
end
147196

148197
# Classes and Defined Types
@@ -200,11 +249,18 @@ def self.load_default_datatypes_async
200249
sidecar_queue.enqueue('default_datatypes', [])
201250
end
202251

203-
def self.datatype(name)
204-
return nil if @default_functions_loaded == false
252+
def self.datatype(name, tasks_mode = false)
253+
return nil if @default_datatypes_loaded == false
205254
raise('Puppet Helper Cache has not been configured') if @inmemory_cache.nil?
206-
load_default_datatypes unless @default_functions_loaded
207-
@inmemory_cache.object_by_name(:datatype, name)
255+
load_default_datatypes unless @default_datatypes_loaded
256+
load_static_data if tasks_mode && !static_data_loaded?
257+
exclude_origins = tasks_mode ? [] : [:bolt]
258+
@inmemory_cache.object_by_name(
259+
:datatype,
260+
name,
261+
:fuzzy_match => true,
262+
:exclude_origins => exclude_origins
263+
)
208264
end
209265

210266
def self.cache

lib/puppet-languageserver/puppet_helper/cache.rb

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module PuppetLanguageServer
44
module PuppetHelper
55
class Cache
66
SECTIONS = %i[class type function datatype].freeze
7-
ORIGINS = %i[default workspace].freeze
7+
ORIGINS = %i[default workspace bolt].freeze
88

99
def initialize(_options = {})
1010
@cache_lock = Mutex.new
@@ -36,27 +36,60 @@ def remove_section!(section, origin = nil)
3636
end
3737

3838
# section => <Type of object in the file :function, :type, :class, :datatype>
39-
def object_by_name(section, name)
39+
def object_by_name(section, name, options = {})
40+
# options[:exclude_origins]
41+
# options[:fuzzy_match]
42+
options = {
43+
:exclude_origins => [],
44+
:fuzzy_match => false
45+
}.merge(options)
46+
4047
name = name.intern if name.is_a?(String)
4148
return nil if section.nil?
4249
@cache_lock.synchronize do
43-
@inmemory_cache.each do |_, sections|
50+
@inmemory_cache.each do |origin, sections|
4451
next if sections[section].nil? || sections[section].empty?
52+
next if options[:exclude_origins].include?(origin)
4553
sections[section].each do |item|
46-
return item if item.key == name
54+
match = options[:fuzzy_match] ? fuzzy_match?(item.key, name) : item.key == name
55+
return item if match
4756
end
4857
end
4958
end
5059
nil
5160
end
5261

62+
# Performs fuzzy text matching of Puppet Language Type names
63+
# e.g 'TargetSpec' in 'Boltlib::TargetSpec'
64+
# @api private
65+
def fuzzy_match?(obj, test_obj)
66+
value = obj.is_a?(String) ? obj.dup : obj.to_s
67+
test_string = test_obj.is_a?(String) ? test_obj.dup : test_obj.to_s
68+
69+
# Test for equality
70+
return true if value == test_string
71+
72+
# Test for a shortname
73+
unless test_string.start_with?('::')
74+
# e.g 'TargetSpec' in 'Boltlib::TargetSpec'
75+
return true if value.end_with?('::' + test_string)
76+
end
77+
78+
false
79+
end
80+
5381
# section => <Type of object in the file :function, :type, :class, :datatype>
54-
def object_names_by_section(section)
82+
# options[:exclude_origins]
83+
def object_names_by_section(section, options = {})
84+
options = {
85+
:exclude_origins => []
86+
}.merge(options)
5587
result = []
5688
return result if section.nil?
5789
@cache_lock.synchronize do
58-
@inmemory_cache.each do |_, sections|
90+
@inmemory_cache.each do |origin, sections|
5991
next if sections[section].nil? || sections[section].empty?
92+
next if options[:exclude_origins].include?(origin)
6093
result.concat(sections[section].map { |i| i.key })
6194
end
6295
end

lib/puppet-languageserver/sidecar_protocol.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ class ActionParams < Hash
1717
include Base
1818

1919
def to_json(*options)
20-
JSON.generate(to_h, options)
20+
::JSON.generate(to_h, options)
2121
end
2222

2323
def from_json!(json_string)
24-
obj = JSON.parse(json_string)
24+
obj = ::JSON.parse(json_string)
2525
obj.each do |key, value|
2626
self[key] = value
2727
end
@@ -37,7 +37,7 @@ def to_json(*options)
3737
end
3838

3939
def from_json!(json_string)
40-
from_h!(JSON.parse(json_string))
40+
from_h!(::JSON.parse(json_string))
4141
end
4242

4343
def ==(other)
@@ -128,7 +128,7 @@ def to_json(*options)
128128
end
129129

130130
def from_json!(json_string)
131-
obj = JSON.parse(json_string)
131+
obj = ::JSON.parse(json_string)
132132
obj.each do |child_hash|
133133
child = child_type.new
134134
self << child.from_h!(child_hash)
@@ -153,7 +153,7 @@ def to_json(*options)
153153
end
154154

155155
def from_json!(json_string)
156-
obj = JSON.parse(json_string)
156+
obj = ::JSON.parse(json_string)
157157
self.dot_content = obj['dot_content']
158158
self.error_content = obj['error_content']
159159
self
@@ -421,7 +421,7 @@ def to_json(*options)
421421
end
422422

423423
def from_json!(json_string)
424-
from_h!(JSON.parse(json_string))
424+
from_h!(::JSON.parse(json_string))
425425
end
426426
end
427427

@@ -470,7 +470,7 @@ def to_json(*options)
470470
end
471471

472472
def from_json!(json_string)
473-
obj = JSON.parse(json_string)
473+
obj = ::JSON.parse(json_string)
474474
obj.each do |key, value|
475475
info = METADATA_LIST[key.intern]
476476
next if info.nil?
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"functions":[{"key":"aggregate::count","calling_source":null,"source":null,"line":null,"char":null,"length":null,"doc":"Aggregates the key/value pairs in the results of a ResultSet into a hash\nmapping the keys to a hash of each distinct value and how many nodes returned\nthat value for the key.","function_version":4,"signatures":[{"key":"aggregate::count(ResultSet $resultset)","doc":"Aggregates the key/value pairs in the results of a ResultSet into a hash\nmapping the keys to a hash of each distinct value and how many nodes returned\nthat value for the key.","return_types":["Any"],"parameters":[{"name":"resultset","doc":"","types":["ResultSet"],"signature_key_offset":27,"signature_key_length":10}]}]},{"key":"aggregate::nodes","calling_source":null,"source":null,"line":null,"char":null,"length":null,"doc":"Aggregates the key/value pairs in the results of a ResultSet into a hash\nmapping the keys to a hash of each distinct value and the list of nodes\nreturning that value for the key.","function_version":4,"signatures":[{"key":"aggregate::nodes(ResultSet $resultset)","doc":"Aggregates the key/value pairs in the results of a ResultSet into a hash\nmapping the keys to a hash of each distinct value and the list of nodes\nreturning that value for the key.","return_types":["Any"],"parameters":[{"name":"resultset","doc":"","types":["ResultSet"],"signature_key_offset":27,"signature_key_length":10}]}]}]}

lib/puppet-languageserver/static_data/bolt-boltlib.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"functions":[{"key":"canary::merge","calling_source":null,"source":null,"line":null,"char":null,"length":null,"doc":"Merges two ResultSets into a new ResultSet","function_version":4,"signatures":[{"key":"canary::merge(ResultSet $merger, ResultSet $mergee)","doc":"Merges two ResultSets into a new ResultSet","return_types":["Any"],"parameters":[{"name":"merger","doc":"","types":["ResultSet"],"signature_key_offset":24,"signature_key_length":7},{"name":"mergee","doc":"","types":["ResultSet"],"signature_key_offset":43,"signature_key_length":7}]}]},{"key":"canary::random_split","calling_source":null,"source":null,"line":null,"char":null,"length":null,"doc":"Splits an array into two groups, where the 1st group is a randomly selected\nsample of the input (of the specified size) and the 2nd group is the remainder.\n\nThis function takes 2 parameters:\n* The array to split (Array)\n* The number of items to sample from the array (Integer)\n\nReturns an array of [<sample>, <remainder>].","function_version":4,"signatures":[{"key":"canary::random_split(Array $arr, Integer $size)","doc":"Splits an array into two groups, where the 1st group is a randomly selected\nsample of the input (of the specified size) and the 2nd group is the remainder.\n\nThis function takes 2 parameters:\n* The array to split (Array)\n* The number of items to sample from the array (Integer)\n\nReturns an array of [<sample>, <remainder>].","return_types":["Any"],"parameters":[{"name":"arr","doc":"","types":["Array"],"signature_key_offset":27,"signature_key_length":4},{"name":"size","doc":"","types":["Integer"],"signature_key_offset":41,"signature_key_length":5}]}]},{"key":"canary::skip","calling_source":null,"source":null,"line":null,"char":null,"length":null,"doc":"Returns a ResultSet with canary/skipped-node errors for each Target provided.\n\nThis function takes a single parameter:\n* List of nodes (Array[Variant[Target,String]])\n\nReturns a ResultSet.","function_version":4,"signatures":[{"key":"canary::skip(Array[Variant[Target,String]] $nodes)","doc":"Returns a ResultSet with canary/skipped-node errors for each Target provided.\n\nThis function takes a single parameter:\n* List of nodes (Array[Variant[Target,String]])\n\nReturns a ResultSet.","return_types":["Any"],"parameters":[{"name":"nodes","doc":"","types":["Array[Variant[Target,String]]"],"signature_key_offset":43,"signature_key_length":6}]}]}]}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"functions":[{"key":"ctrl::do_until","calling_source":null,"source":null,"line":null,"char":null,"length":null,"doc":"Repeat the block until it returns a truthy value. Returns the value.","function_version":4,"signatures":[{"key":"ctrl::do_until(Optional[Hash[String[1], Any]] $options, Callable &$block)","doc":"Repeat the block until it returns a truthy value. Returns the value.","return_types":["Any"],"parameters":[{"name":"options","doc":"Additional options: 'until'","types":["Optional[Hash[String[1], Any]]"],"signature_key_offset":46,"signature_key_length":8},{"name":"&block","doc":"","types":["Callable"],"signature_key_offset":65,"signature_key_length":7}]}]},{"key":"ctrl::sleep","calling_source":null,"source":null,"line":null,"char":null,"length":null,"doc":"Sleeps for specified number of seconds.","function_version":4,"signatures":[{"key":"ctrl::sleep(Numeric $period)","doc":"Sleeps for specified number of seconds.","return_types":["Undef"],"parameters":[{"name":"period","doc":"Time to sleep (in seconds)","types":["Numeric"],"signature_key_offset":20,"signature_key_length":7}]}]}]}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"functions":[{"key":"file::exists","calling_source":null,"source":null,"line":null,"char":null,"length":null,"doc":"check if a file exists","function_version":4,"signatures":[{"key":"file::exists(String $filename)","doc":"check if a file exists","return_types":["Boolean"],"parameters":[{"name":"filename","doc":"Absolute path or Puppet file path.","types":["String"],"signature_key_offset":20,"signature_key_length":9}]}]},{"key":"file::read","calling_source":null,"source":null,"line":null,"char":null,"length":null,"doc":"Read a file and return its contents.","function_version":4,"signatures":[{"key":"file::read(String $filename)","doc":"Read a file and return its contents.","return_types":["String"],"parameters":[{"name":"filename","doc":"Absolute path or Puppet file path.","types":["String"],"signature_key_offset":18,"signature_key_length":9}]}]},{"key":"file::readable","calling_source":null,"source":null,"line":null,"char":null,"length":null,"doc":"check if a file is readable","function_version":4,"signatures":[{"key":"file::readable(String $filename)","doc":"check if a file is readable","return_types":["Boolean"],"parameters":[{"name":"filename","doc":"Absolute path or Puppet file path.","types":["String"],"signature_key_offset":22,"signature_key_length":9}]}]},{"key":"file::write","calling_source":null,"source":null,"line":null,"char":null,"length":null,"doc":"Write a string to a file.","function_version":4,"signatures":[{"key":"file::write(String $filename, String $content)","doc":"Write a string to a file.","return_types":["Undef"],"parameters":[{"name":"filename","doc":"Absolute path.","types":["String"],"signature_key_offset":19,"signature_key_length":9},{"name":"content","doc":"File content to write.","types":["String"],"signature_key_offset":37,"signature_key_length":8}]}]}]}

0 commit comments

Comments
 (0)