11require 'puppet/indirector/face'
2-
2+ require 'pathname'
33module PuppetLanguageServer
44 module PuppetHelper
55 # Reference - https://github.com/puppetlabs/puppet/blob/master/lib/puppet/reference/type.rb
66
77 @ops_lock_types = Mutex . new
88 @ops_lock_funcs = Mutex . new
9+ @ops_lock_classes = Mutex . new
910 @types_hash = nil
1011 @function_module = nil
1112 @types_loaded = nil
1213 @functions_loaded = nil
14+ @classes_loaded = nil
15+ @class_load_info = { }
16+ @function_load_info = { }
17+ @type_load_info = { }
1318
1419 def self . reset
1520 @ops_lock_types . synchronize do
@@ -114,13 +119,60 @@ def self.function_names
114119 result
115120 end
116121
122+ # Classes and Defined Types
123+ def self . classes_loaded?
124+ @classes_loaded . nil? ? false : @classes_loaded
125+ end
126+
127+ def self . load_classes
128+ @ops_lock_classes . synchronize do
129+ _load_classes if @classes_loaded . nil?
130+ end
131+ end
132+
133+ def self . load_classes_async
134+ Thread . new do
135+ load_classes
136+ end
137+ end
138+
139+ # Loading information
140+ def self . add_function_load_info ( name , options )
141+ @function_load_info [ name . to_s ] = options
142+ end
143+
144+ def self . function_load_info ( name )
145+ options = @function_load_info [ name . to_s ]
146+ options . nil? ? nil : options . dup
147+ end
148+
149+ def self . add_type_load_info ( name , options )
150+ @type_load_info [ name . to_s ] = options
151+ end
152+
153+ def self . type_load_info ( name )
154+ options = @type_load_info [ name . to_s ]
155+ options . nil? ? nil : options . dup
156+ end
157+
158+ def self . class_load_info ( name )
159+ # This is the only entrypoint to class loading
160+ load_classes
161+ options = @class_load_info [ name . to_s ]
162+ options . nil? ? nil : options . dup
163+ end
164+
117165 # DO NOT ops_lock on any of these methods
118166 # deadlocks will ensue!
119167 def self . _reset
120168 @types_hash = nil
121169 @function_module = nil
122170 @types_loaded = nil
123171 @functions_loaded = nil
172+ @classes_loaded = nil
173+ @function_load_info = { }
174+ @type_load_info = { }
175+ @class_load_info = { }
124176 end
125177 private_class_method :_reset
126178
@@ -134,6 +186,126 @@ def self.prune_resource_parameters(resources)
134186 end
135187 private_class_method :prune_resource_parameters
136188
189+ # Class and Defined Type loading
190+ def self . _load_classes
191+ @classes_loaded = false
192+ module_path_list = [ ]
193+ # Add the base modulepath
194+ module_path_list . concat ( Puppet ::Node ::Environment . split_path ( Puppet . settings [ :basemodulepath ] ) )
195+ # Add the modulepath
196+ module_path_list . concat ( Puppet ::Node ::Environment . split_path ( Puppet . settings [ :modulepath ] ) )
197+
198+ # Add the environment specified in puppet conf - This can be overridden by the master but there's no way to know.
199+ unless Puppet . settings [ :environmentpath ] . nil?
200+ module_path_list << File . join ( Puppet . settings [ :environmentpath ] , Puppet . settings [ :environment ] , 'modules' ) unless Puppet . settings [ :environment ] . nil?
201+
202+ module_path_list . concat ( Pathname . new ( Puppet . settings [ :environmentpath ] )
203+ . children
204+ . select { |c | c . directory? }
205+ . collect { |c | File . join ( c , 'modules' ) }
206+ )
207+ end
208+ module_path_list . uniq!
209+ PuppetLanguageServer . log_message ( :debug , "[PuppetHelper::_load_classes] Loading classes from #{ module_path_list } " )
210+
211+ # Find all of the manifest paths for all of the modules...
212+ manifest_path_list = [ ]
213+ module_path_list . each do |module_path |
214+ next unless File . exists? ( module_path )
215+ Pathname . new ( module_path )
216+ . children
217+ . select { |c | c . directory? }
218+ . each do |module_filepath |
219+ manifest_path = File . join ( module_filepath , 'manifests' )
220+ manifest_path_list << manifest_path if File . exists? ( manifest_path )
221+ end
222+ end
223+
224+ # Find and parse all manifests in the manifest paths
225+ @class_load_info = { }
226+ manifest_path_list . each do |manifest_path |
227+ Dir . glob ( "#{ manifest_path } /**/*.pp" ) . each do |manifest_file |
228+ classes = load_classes_from_manifest ( manifest_file )
229+ next if classes . nil?
230+ classes . each do |key , data |
231+ @class_load_info [ key ] = data unless @class_load_info . has_key? ( name )
232+ end
233+ end
234+ end
235+ @classes_loaded = true
236+
237+ PuppetLanguageServer . log_message ( :debug , "[PuppetHelper::_load_classes] Finished loading #{ @class_load_info . count } classes" )
238+ nil
239+ end
240+ private_class_method :_load_classes
241+
242+ def self . load_classes_from_manifest ( manifest_file )
243+ file_content = File . open ( manifest_file , "r:UTF-8" ) { |f | f . read }
244+
245+ parser = Puppet ::Pops ::Parser ::Parser . new
246+ result = nil
247+ begin
248+ result = parser . parse_string ( file_content , '' )
249+ rescue Puppet ::ParseErrorWithIssue => _exception
250+ # Any parsing errors means we can't inspect the document
251+ return nil
252+ end
253+
254+ class_info = { }
255+ # Enumerate the entire AST looking for classes and defined types
256+ # TODO - Need to learn how to read the help/docs for hover support
257+ if result . model . respond_to? :eAllContents
258+ # TODO Puppet 4 language stuff
259+ result . model . eAllContents . select do |item |
260+ case item . class . to_s
261+ when 'Puppet::Pops::Model::HostClassDefinition'
262+ class_info [ item . name ] = {
263+ 'name' => item . name ,
264+ 'type' => 'class' ,
265+ 'parameters' => item . parameters ,
266+ 'source' => manifest_file ,
267+ 'line' => result . locator . line_for_offset ( item . offset ) - 1 ,
268+ 'char' => result . locator . offset_on_line ( item . offset )
269+ }
270+ when 'Puppet::Pops::Model::ResourceTypeDefinition'
271+ class_info [ item . name ] = {
272+ 'name' => item . name ,
273+ 'type' => 'typedefinition' ,
274+ 'parameters' => item . parameters ,
275+ 'source' => manifest_file ,
276+ 'line' => result . locator . line_for_offset ( item . offset ) - 1 ,
277+ 'char' => result . locator . offset_on_line ( item . offset )
278+ }
279+ end
280+ end
281+ else
282+ result . model . _pcore_all_contents ( [ ] ) do |item |
283+ case item . class . to_s
284+ when 'Puppet::Pops::Model::HostClassDefinition'
285+ class_info [ item . name ] = {
286+ 'name' => item . name ,
287+ 'type' => 'class' ,
288+ 'parameters' => item . parameters ,
289+ 'source' => manifest_file ,
290+ 'line' => item . line ,
291+ 'char' => item . pos
292+ }
293+ when 'Puppet::Pops::Model::ResourceTypeDefinition'
294+ class_info [ item . name ] = {
295+ 'name' => item . name ,
296+ 'type' => 'typedefinition' ,
297+ 'parameters' => item . parameters ,
298+ 'source' => manifest_file ,
299+ 'line' => item . line ,
300+ 'char' => item . pos
301+ }
302+ end
303+ end
304+ end
305+
306+ class_info
307+ end
308+
137309 def self . _load_types
138310 @types_loaded = false
139311 @types_hash = { }
0 commit comments