class PuppetLint
Public: The public interface to puppet-lint.
Public Class Methods
Public: Access PuppetLint's configuration from outside the class.
Returns a PuppetLint::Configuration object.
# File lib/puppet-lint.rb, line 58 def self.configuration @configuration ||= PuppetLint::Configuration.new end
Public: Initialise a new PuppetLint object.
# File lib/puppet-lint.rb, line 49 def initialize @code = nil @statistics = { :error => 0, :warning => 0, :fixed => 0, :ignored => 0 } @manifest = '' end
Public: Define a new check.
name - A unique name for the check as a Symbol. block - The check logic. This must contain a `check` method and optionally
a `fix` method.
Returns nothing.
Examples
PuppetLint.new_check(:foo) do def check end end
# File lib/puppet-lint.rb, line 222 def self.new_check(name, &block) class_name = name.to_s.split('_').map(&:capitalize).join klass = PuppetLint.const_set("Check#{class_name}", Class.new(PuppetLint::CheckPlugin)) klass.const_set('NAME', name) klass.class_exec(&block) PuppetLint.configuration.add_check(name, klass) PuppetLint::Data.ignore_overrides[name] ||= {} end
Public Instance Methods
Public: Access PuppetLint's configuration from inside the class.
Returns a PuppetLint::Configuration object.
# File lib/puppet-lint.rb, line 65 def configuration self.class.configuration end
Public: Determine if PuppetLint found any errors in the manifest.
Returns true if errors were found, otherwise returns false.
# File lib/puppet-lint.rb, line 169 def errors? @statistics[:error] != 0 end
Public: Set the path of the manifest file to be tested and read the contents of the file.
Returns nothing.
# File lib/puppet-lint.rb, line 73 def file=(path) return unless File.exist?(path) @path = path File.open(path, 'r:UTF-8') do |f| @code = f.read end # Check if the input is an SE Linux policy package file (which also use # the .pp extension), which all have the first 4 bytes 0xf97cff8f. @code = '' if @code[0..3].unpack('V').first == 0xf97cff8f end
Internal: Format a problem message and print it to STDOUT.
message - A Hash containing all the information about a problem.
Returns nothing.
# File lib/puppet-lint.rb, line 107 def format_message(message) format = log_format puts format % message puts " #{message[:reason]}" if message[:kind] == :ignored && !message[:reason].nil? end
Internal: Get the line of the manifest on which the problem was found
message - A Hash containing all the information about a problem.
Returns the problematic line as a string.
# File lib/puppet-lint.rb, line 119 def get_context(message) PuppetLint::Data.manifest_lines[message[:line] - 1].strip end
Internal: Splits a heredoc String into segments if it is to be interpolated.
string - The String heredoc. eos_text - The String endtext for the heredoc. interpolate - A Boolean that specifies whether this heredoc can contain
interpolated values (defaults to True).
Returns an Array consisting of two Strings, the String up to the first terminator and the terminator that was found.
# File lib/puppet-lint/lexer.rb, line 550 def get_heredoc_segment(string, eos_text, interpolate = true) regexp = if interpolate %r{(([^\]|^|[^\])([\]{2})*[$]+|\|?\s*-?#{Regexp.escape(eos_text)})} else %r{\|?\s*-?#{Regexp.escape(eos_text)}} end str = string.scan_until(regexp) begin str =~ %r{\A(.*?)([$]+|\|?\s*-?#{Regexp.escape(eos_text)})\Z}m [Regexp.last_match(1), Regexp.last_match(2)] rescue [nil, nil] end end
Internal: Split a string on multiple terminators, excluding escaped terminators.
string - The String to be split. terminators - The String of terminators that the String should be split
on.
Returns an Array consisting of two Strings, the String up to the first terminator and the terminator that was found.
# File lib/puppet-lint/lexer.rb, line 412 def get_string_segment(string, terminators) str = string.scan_until(%r{([^\]|^|[^\])([\]{2})*[#{terminators}]+}) begin [str[0..-2], str[-1, 1]] rescue [nil, nil] end end
Internal: Tokenise the contents of a heredoc.
string - The String to be tokenised. name - The String name/endtext of the heredoc.
Returns nothing.
# File lib/puppet-lint/lexer.rb, line 491 def interpolate_heredoc(string, name) ss = StringScanner.new(string) eos_text = name[%r{\A"?(.+?)"?(:.+?)?(/.*)?\Z}, 1] first = true interpolate = name.start_with?('"') value, terminator = get_heredoc_segment(ss, eos_text, interpolate) until value.nil? if terminator =~ %r{\A\|?\s*-?\s*#{Regexp.escape(eos_text)}} if first tokens << new_token(:HEREDOC, value, :raw => "#{value}#{terminator}") first = false else tokens << new_token(:HEREDOC_POST, value, :raw => "#{value}#{terminator}") end else if first tokens << new_token(:HEREDOC_PRE, value) first = false else tokens << new_token(:HEREDOC_MID, value) end if ss.scan(%r{\{}).nil? var_name = ss.scan(%r{(::)?(\w+(-\w+)*::)*\w+(-\w+)*}) tokens << if var_name.nil? new_token(:HEREDOC_MID, '$') else new_token(:UNENC_VARIABLE, var_name) end else contents = ss.scan_until(%r{\}})[0..-2] raw = contents.dup if contents.match(%r{\A(::)?([\w-]+::)*[\w-]|(\[.+?\])*}) && !contents.match(%r{\A\w+\(}) contents = "$#{contents}" unless contents.start_with?('$') end lexer = PuppetLint::Lexer.new lexer.tokenise(contents) lexer.tokens.each do |token| tokens << new_token(token.type, token.value) end if lexer.tokens.length == 1 && lexer.tokens[0].type == :VARIABLE tokens.last.raw = raw end end end value, terminator = get_heredoc_segment(ss, eos_text, interpolate) end end
Internal: Tokenise the contents of a double quoted string.
string - The String to be tokenised. line - The Integer line number of the start of the passed string. column - The Integer column number of the start of the passed string.
Returns nothing.
# File lib/puppet-lint/lexer.rb, line 428 def interpolate_string(string, line, column) ss = StringScanner.new(string) first = true value, terminator = get_string_segment(ss, '"$') until value.nil? if terminator == '"' if first tokens << new_token(:STRING, value, :line => line, :column => column) first = false else token_column = column + (ss.pos - value.size) tokens << new_token(:DQPOST, value, :line => line, :column => token_column) line += value.scan(%r{(\r\n|\r|\n)}).size @column = column + ss.pos + 1 @line_no = line end else if first tokens << new_token(:DQPRE, value, :line => line, :column => column) first = false else token_column = column + (ss.pos - value.size) tokens << new_token(:DQMID, value, :line => line, :column => token_column) line += value.scan(%r{(\r\n|\r|\n)}).size end if ss.scan(%r{\{}).nil? var_name = ss.scan(%r{(::)?(\w+(-\w+)*::)*\w+(-\w+)*}) if var_name.nil? token_column = column + ss.pos - 1 tokens << new_token(:DQMID, '$', :line => line, :column => token_column) else token_column = column + (ss.pos - var_name.size) tokens << new_token(:UNENC_VARIABLE, var_name, :line => line, :column => token_column) end else line += value.scan(%r{(\r\n|\r|\n)}).size contents = ss.scan_until(%r{\}})[0..-2] raw = contents.dup if contents.match(%r{\A(::)?([\w-]+::)*[\w-]+(\[.+?\])*}) && !contents.match(%r{\A\w+\(}) contents = "$#{contents}" end lexer = PuppetLint::Lexer.new lexer.tokenise(contents) lexer.tokens.each do |token| tok_col = column + token.column + (ss.pos - contents.size - 1) tok_line = token.line + line - 1 tokens << new_token(token.type, token.value, :line => tok_line, :column => tok_col) end if lexer.tokens.length == 1 && lexer.tokens[0].type == :VARIABLE tokens.last.raw = raw end end end value, terminator = get_string_segment(ss, '"$') end end
Internal: Retrieve the format string to be used when writing problems to STDOUT. If the user has not specified a custom log format, build one for them.
Returns a format String to be used with String#%.
# File lib/puppet-lint.rb, line 91 def log_format if configuration.log_format.nil? || configuration.log_format.empty? ## recreate previous old log format as far as thats possible. format = '%{KIND}: %{message} on line %{line}' format.prepend('%{path} - ') if configuration.with_filename configuration.log_format = format end configuration.log_format end
Internal: Create a new PuppetLint::Lexer::Token object, calculate its line number and column and then add it to the Linked List of tokens.
type - The Symbol token type. value - The token value. opts - A Hash of additional values required to determine line number and
column: :line - The Integer line number if calculated externally. :column - The Integer column number if calculated externally. :raw - The String raw value of the token (if necessary).
Returns the instantiated PuppetLint::Lexer::Token object.
# File lib/puppet-lint/lexer.rb, line 350 def new_token(type, value, *args) # This bit of magic is used instead of an "opts = {}" argument so that we # can safely deprecate the old "length" parameter that might still be # passed by 3rd party plugins that haven't updated yet. opts = args.last.is_a?(Hash) ? args.last : {} # column number is calculated at the end of this method by calling # to_manifest on the token. Because the string tokens (DQPRE, DQMID etc) # are parsed before the variable token, they default to assuming that # they are followed by an enclosed variable and we need to remove 2 from # the column number if we encounter an unenclosed variable because of the # missing ${ at the end of the token value. @column -= 2 if type == :UNENC_VARIABLE column = opts[:column] || @column line_no = opts[:line] || @line_no token = Token.new(type, value, line_no, column) unless tokens.last.nil? token.prev_token = tokens.last tokens.last.next_token = token unless FORMATTING_TOKENS.include?(token.type) prev_nf_idx = tokens.rindex { |r| !FORMATTING_TOKENS.include?(r.type) } unless prev_nf_idx.nil? prev_nf_token = tokens[prev_nf_idx] prev_nf_token.next_code_token = token token.prev_code_token = prev_nf_token end end end token.raw = opts[:raw] if opts[:raw] if type == :NEWLINE @line_no += 1 @column = 1 else lines = token.to_manifest.split(%r{(?:\r\n|\r|\n)}, -1) @line_no += lines.length - 1 if lines.length > 1 # if the token renders to multiple lines, set the column state to the # length of the last line plus 1 (because column numbers are # 1 indexed) @column = lines.last.size + 1 else @column += (lines.last || '').size end end token end
Internal: Given the tokens already processed, determine if the next token could be a regular expression.
Returns true if the next token could be a regex, otherwise return false.
# File lib/puppet-lint/lexer.rb, line 328 def possible_regex? prev_token = tokens.reject { |r| FORMATTING_TOKENS.include?(r.type) }.last return true if prev_token.nil? REGEX_PREV_TOKENS.include?(prev_token.type) end
Internal: Print out the line of the manifest on which the problem was found as well as a marker pointing to the location on the line.
message - A Hash containing all the information about a problem.
Returns nothing.
# File lib/puppet-lint.rb, line 129 def print_context(message) return if message[:check] == 'documentation' return if message[:kind] == :fixed line = get_context(message) offset = line.index(%r{\S}) || 1 puts "\n #{line.strip}" printf("%#{message[:column] + 2 - offset}s\n\n", '^') end
Public: Print any problems that were found out to stdout.
Returns nothing.
# File lib/puppet-lint.rb, line 204 def print_problems report(@problems) end
Internal: Print the reported problems with a manifest to stdout.
problems - An Array of problem Hashes as returned by
PuppetLint::Checks#run.
Returns nothing.
# File lib/puppet-lint.rb, line 144 def report(problems) json = [] problems.each do |message| next if message[:kind] == :ignored && !PuppetLint.configuration.show_ignored message[:KIND] = message[:kind].to_s.upcase if message[:kind] == :fixed || [message[:kind], :all].include?(configuration.error_level) if configuration.json message['context'] = get_context(message) if configuration.with_context json << message else format_message(message) print_context(message) if configuration.with_context end end end puts JSON.pretty_generate(json) if configuration.json $stderr.puts 'Try running `puppet parser validate <file>`' if problems.any? { |p| p[:check] == :syntax } end
Public: Run the loaded manifest code through the lint checks and print the results of the checks to stdout.
Returns nothing. Raises PuppetLint::NoCodeError if no manifest code has been loaded.
# File lib/puppet-lint.rb, line 185 def run raise PuppetLint::NoCodeError if @code.nil? if @code.empty? @problems = [] @manifest = '' return end linter = PuppetLint::Checks.new @problems = linter.run(@path, @code) @problems.each { |problem| @statistics[problem[:kind]] += 1 } @manifest = linter.manifest if PuppetLint.configuration.fix end
Public: Determine if PuppetLint found any warnings in the manifest.
Returns true if warnings were found, otherwise returns false.
# File lib/puppet-lint.rb, line 176 def warnings? @statistics[:warning] != 0 end