Skip to content

Instantly share code, notes, and snippets.

@masterzen
Created February 24, 2012 16:27
Show Gist options
  • Save masterzen/1901871 to your computer and use it in GitHub Desktop.
Save masterzen/1901871 to your computer and use it in GitHub Desktop.
Parse Puppet AST for global node variables
require 'puppet'
class Parser
def scan(input_file_name)
@input_file_name = input_file_name
environment = Puppet::Node::Environment.new
@known_resource_types = environment.known_resource_types
unless environment.known_resource_types.watching_file?(@input_file_name)
Puppet.info "rdoc: scanning #{@input_file_name}"
if @input_file_name =~ /\.pp$/
@parser = Puppet::Parser::Parser.new(environment)
@parser.file = input_file_name
@parser.parse.instantiate('').each do |type|
@known_resource_types.add type
end
end
end
scan_top_level
end
# split_module tries to find if +path+ belongs to the module path
# if it does, it returns the module name, otherwise if we are sure
# it is part of the global manifest path, "__site__" is returned.
# And finally if this path couldn't be mapped anywhere, nil is returned.
def split_module(path)
# find a module
fullpath = File.expand_path(path)
puts "rdoc: testing #{fullpath}"
if fullpath =~ /(.*)\/([^\/]+)\/(?:manifests|plugins|lib)\/.+\.(pp|rb)$/
modpath = $1
name = $2
puts "rdoc: module #{name} into #{modpath} ?"
Puppet::Node::Environment.new.modulepath.each do |mp|
if File.identical?(modpath,mp)
puts "rdoc: found module #{name}"
return name
end
end
end
if fullpath =~ /\.(pp|rb)$/
# there can be paths we don't want to scan under modules
# imagine a ruby or manifest that would be distributed as part as a module
# but we don't want those to be hosted under <site>
Puppet::Node::Environment.new.modulepath.each do |mp|
# check that fullpath is a descendant of mp
dirname = fullpath
previous = dirname
while (dirname = File.dirname(previous)) != previous
previous = dirname
return nil if File.identical?(dirname,mp)
end
end
end
# we are under a global manifests
puts "rdoc: global manifests"
"__site__"
end
# create documentation for the top level +container+
def scan_top_level
# infer module name from directory
name = split_module(@input_file_name)
if name.nil?
return
end
puts "rdoc: scanning for #{name}"
parse_elements
end
# create documentation for global variables assignements we can find in +code+
# and associate it with +container+
def scan_for_vardef(code)
code = [code] unless code.is_a?(Array)
code.each do |stmt|
scan_for_vardef(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray)
if stmt.is_a?(Puppet::Parser::AST::VarDef)
#puts "rdoc: found constant: #{stmt.name} = #{stmt.value}"
### EMIT AS YAML
case stmt.value
when Puppet::Parser::AST::String
puts "#{stmt.name}: #{stmt.value}"
when Puppet::Parser::AST::Boolean
puts "#{stmt.name}: #{stmt.value}"
when Puppet::Parser::AST::ASTArray
puts "#{stmt.name}:"
stmt.value.children.each do |m|
puts "\t- #{m}"
end
end
end
end
end
# create documentation for a node
def document_node(name, node)
puts "rdoc: found new node #{name}"
superclass = node.parent
code = node.code.children if node.code.is_a?(Puppet::Parser::AST::ASTArray)
code ||= node.code
unless code.nil?
scan_for_vardef(code)
end
rescue => detail
puts detail.backtrace
raise Puppet::ParseError, "impossible to parse node '#{name}' in #{node.file} at line #{node.line}: #{detail}"
end
# Traverse the AST tree and produce code-objects node
# that contains the documentation
def parse_elements
puts "rdoc: scanning manifest"
@known_resource_types.nodes.each do |name, node|
if node.file == @input_file_name
document_node(name.to_s,node)
end
end
end
end
Puppet[:modulepath] = "/home/brice/cvs/puppet/modules"
parser = Parser.new
parser.scan("/home/brice/cvs/puppet/manifests/site.pp")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment