puppet-lint 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -2,5 +2,24 @@ require 'rake'
2
2
  require 'rspec/core/rake_task'
3
3
 
4
4
  task :default => :test
5
+ #task :default => [:test, :rdoc]
5
6
 
6
7
  RSpec::Core::RakeTask.new(:test)
8
+
9
+ ### RDOC Tasks ###
10
+ require 'rdoc'
11
+ if (RDoc::VERSION.split('.') <=> ['2','4','2']) >= 0
12
+ require 'rdoc/task'
13
+ RDoc::Task.new(:rdoc) do |rdoc|
14
+ rdoc.main = "README.md"
15
+ rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
16
+ rdoc.options << "--all"
17
+ end
18
+ else
19
+ require 'rake/rdoctask'
20
+ Rake::RDocTask.new(:rdoc) do |rdoc|
21
+ rdoc.main = "README.md"
22
+ rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
23
+ rdoc.options << "--all"
24
+ end
25
+ end
data/bin/puppet-lint CHANGED
@@ -17,10 +17,6 @@ require 'optparse'
17
17
  require 'rubygems'
18
18
  require 'puppet-lint'
19
19
 
20
- options = {
21
- :error_level => :all,
22
- :with_filename => false,
23
- }
24
20
  opts = OptionParser.new do |opts|
25
21
  opts.banner = help
26
22
 
@@ -30,19 +26,45 @@ opts = OptionParser.new do |opts|
30
26
  end
31
27
 
32
28
  opts.on("--with-filename", "Display the filename before the warning") do
33
- options[:with_filename] = true
29
+ PuppetLint.configuration.with_filename = true
34
30
  end
35
31
 
36
- opts.on("--error-level LEVEL", [:all, :warning, :error], "The level of error to return (warning, error, all).") do |el|
37
- options[:error_level] = el
32
+ opts.on("--error-level LEVEL", [:all, :warning, :error], "The level of error to return.", "(warning, error, all)") do |el|
33
+ PuppetLint.configuration.error_level = el
38
34
  end
39
35
 
40
36
  opts.on("--fail-on-warnings", "Return a non-zero exit status for warnings.") do
41
- options[:fail_on_warnings] = true
37
+ PuppetLint.configuration.fail_on_warnings = true
42
38
  end
39
+
40
+ opts.on("--log-format FORMAT",
41
+ "Change the log format.", "Overrides --with-filename.",
42
+ "The following placeholders can be used:",
43
+ "%{filename} - Filename without path.",
44
+ "%{path} - Path as provided.",
45
+ "%{fullpath} - Full path.",
46
+ "%{linenumber} - Line number.",
47
+ "%{kind} - The kind of message.",
48
+ " - (warning, error)",
49
+ "%{KIND} - Uppercase version of %{kind}",
50
+ "%{check} - Name of the check.",
51
+ "%{message} - The message."
52
+ ) do |format|
53
+ PuppetLint.configuration.log_format = format
54
+ end
55
+
56
+ opts.separator ""
57
+ opts.separator " Disable checks:"
58
+
59
+ PuppetLint.configuration.checks.each do |check|
60
+ opts.on("--no-#{check}-check", "Skip the #{check} check") do
61
+ PuppetLint.configuration.send("disable_#{check}")
62
+ end
63
+ end
64
+
65
+ opts.load(File.expand_path("~/.puppet-lintrc")) unless opts.load(".puppet-lintrc")
43
66
  end
44
67
 
45
- # Read command line options into `options` hash
46
68
  begin
47
69
  opts.parse!
48
70
  rescue OptionParser::InvalidOption
@@ -58,11 +80,11 @@ if ARGV[0].nil?
58
80
  end
59
81
 
60
82
  begin
61
- l = PuppetLint.new(options)
83
+ l = PuppetLint.new
62
84
  l.file = ARGV[0]
63
85
  l.run
64
86
 
65
- if l.errors? or (l.warnings? and options[:fail_on_warnings])
87
+ if l.errors? or (l.warnings? and PuppetLint.configuration.fail_on_warnings)
66
88
  exit 1
67
89
  end
68
90
  rescue PuppetLint::NoCodeError
data/lib/puppet-lint.rb CHANGED
@@ -7,8 +7,8 @@ rescue LoadError
7
7
  exit 1
8
8
  end
9
9
 
10
+ require 'puppet-lint/configuration'
10
11
  require 'puppet-lint/plugin'
11
- require 'puppet-lint/plugins'
12
12
 
13
13
  unless String.respond_to?('prepend')
14
14
  class String
@@ -18,25 +18,85 @@ unless String.respond_to?('prepend')
18
18
  end
19
19
  end
20
20
 
21
+ # If we are using an older ruby version, we back-port the basic functionality
22
+ # we need for formatting output: 'somestring' % <hash>
23
+ begin
24
+ if ('%{test}' % {:test => 'replaced'} == 'replaced')
25
+ # If this works, we are all good to go.
26
+ end
27
+ rescue
28
+ # If the test failed (threw a error), monkeypatch String.
29
+ # Most of this code came from http://www.ruby-forum.com/topic/144310 but was
30
+ # simplified for our use.
31
+
32
+ # Basic implementation of 'string' % { } like we need it. needs work.
33
+ class String
34
+ Percent = instance_method '%' unless defined? Percent
35
+ def % *a, &b
36
+ a.flatten!
37
+
38
+ string = case a.last
39
+ when Hash
40
+ expand a.pop
41
+ else
42
+ self
43
+ end
44
+
45
+ if a.empty?
46
+ string
47
+ else
48
+ Percent.bind(string).call(*a, &b)
49
+ end
50
+
51
+ end
52
+ def expand! vars = {}
53
+ loop do
54
+ changed = false
55
+ vars.each do |var, value|
56
+ var = var.to_s
57
+ var.gsub! %r/[^a-zA-Z0-9_]/, ''
58
+ [
59
+ %r/\%\{#{ var }\}/,
60
+ ].each do |pat|
61
+ changed = gsub! pat, "#{ value }"
62
+ end
63
+ end
64
+ break unless changed
65
+ end
66
+ self
67
+ end
68
+ def expand opts = {}
69
+ dup.expand! opts
70
+ end
71
+ end
72
+ end
73
+
21
74
  class PuppetLint::NoCodeError < StandardError; end
22
75
 
23
76
  class PuppetLint
24
- VERSION = '0.1.9'
77
+ VERSION = '0.1.10'
25
78
 
26
79
  attr_reader :code, :file
27
80
 
28
- def initialize(options)
81
+ def initialize
29
82
  @data = nil
30
- @errors = 0
31
- @warnings = 0
32
- @with_filename = options[:with_filename]
33
- @path = ''
34
- @error_level = options[:error_level]
83
+ @statistics = {:error => 0, :warning => 0}
84
+ @fileinfo = {:path => ''}
85
+ end
86
+
87
+ def self.configuration
88
+ @configuration ||= PuppetLint::Configuration.new
89
+ end
90
+
91
+ def configuration
92
+ self.class.configuration
35
93
  end
36
94
 
37
95
  def file=(path)
38
96
  if File.exist? path
39
- @path = File.expand_path(path)
97
+ @fileinfo[:path] = path
98
+ @fileinfo[:fullpath] = File.expand_path(path)
99
+ @fileinfo[:filename] = File.basename(path)
40
100
  @data = File.read(path)
41
101
  end
42
102
  end
@@ -45,27 +105,48 @@ class PuppetLint
45
105
  @data = value
46
106
  end
47
107
 
48
- def report(kind, message)
49
- #msg = message
50
- if kind == :warnings
51
- @warnings += 1
52
- message.prepend('WARNING: ')
53
- else
54
- @errors += 1
55
- message.prepend('ERROR: ')
56
- end
57
- if @with_filename
58
- message.prepend("#{@path} - ")
108
+ def log_format
109
+ if configuration.log_format == ''
110
+ ## recreate previous old log format as far as thats possible.
111
+ format = '%{KIND}: %{message} on line %{linenumber}'
112
+ if configuration.with_filename
113
+ format.prepend '%{path} - '
114
+ end
115
+ configuration.log_format = format
59
116
  end
60
- puts message
117
+ return configuration.log_format
118
+ end
119
+
120
+ def format_message(message)
121
+ format = log_format
122
+ puts format % message
61
123
  end
62
124
 
125
+ def report(problems)
126
+ problems.each do |message|
127
+ @statistics[message[:kind]] += 1
128
+ ## Add some default attributes.
129
+ message.merge!(@fileinfo) {|key, v1, v2| v1 }
130
+ message[:KIND] = message[:kind].to_s.upcase
131
+
132
+ if configuration.error_level == message[:kind] or configuration.error_level == :all
133
+ format_message message
134
+ end
135
+ end
136
+ end
137
+
63
138
  def errors?
64
- @errors != 0
139
+ @statistics[:error] != 0
65
140
  end
66
141
 
67
142
  def warnings?
68
- @warnings != 0
143
+ @statistics[:warning] != 0
144
+ end
145
+
146
+ def checks
147
+ PuppetLint::CheckPlugin.repository.map do |plugin|
148
+ plugin.new.checks
149
+ end.flatten
69
150
  end
70
151
 
71
152
  def run
@@ -74,17 +155,15 @@ class PuppetLint
74
155
  end
75
156
 
76
157
  PuppetLint::CheckPlugin.repository.each do |plugin|
77
- problems = plugin.new.run(@path, @data)
78
- case @error_level
79
- when :warning
80
- problems[:warnings].each { |warning| report :warnings, warning }
81
- when :error
82
- problems[:errors].each { |error| report :errors, error }
83
- else
84
- problems[:warnings].each { |warning| report :warnings, warning }
85
- problems[:errors].each { |error| report :errors, error }
86
- end
158
+ report plugin.new.run(@fileinfo[:path], @data)
87
159
  end
88
160
  end
89
161
  end
90
162
 
163
+ # Default configuration options
164
+ PuppetLint.configuration.fail_on_warnings = false
165
+ PuppetLint.configuration.error_level = :all
166
+ PuppetLint.configuration.with_filename = false
167
+ PuppetLint.configuration.log_format = ''
168
+
169
+ require 'puppet-lint/plugins'
@@ -0,0 +1,57 @@
1
+ class PuppetLint
2
+ class Configuration
3
+ def self.add_check(check)
4
+ define_method("#{check}_enabled?") do
5
+ settings["#{check}_disabled"] == true ? false : true
6
+ end
7
+
8
+ define_method("disable_#{check}") do
9
+ settings["#{check}_disabled"] = true
10
+ end
11
+
12
+ define_method("enable_#{check}") do
13
+ settings["#{check}_disabled"] = false
14
+ end
15
+ end
16
+
17
+ def method_missing(method, *args, &block)
18
+ if method.to_s =~ /^(\w+)=$/
19
+ option = $1
20
+ add_option(option.to_s) if settings[option].nil?
21
+ settings[option] = args[0]
22
+ else
23
+ nil
24
+ end
25
+ end
26
+
27
+ def add_option(option)
28
+ self.class.add_option(option)
29
+ end
30
+
31
+ def self.add_option(option)
32
+ define_method("#{option}=") do |value|
33
+ settings[option] = value
34
+ end
35
+
36
+ define_method(option) do
37
+ settings[option]
38
+ end
39
+ end
40
+
41
+ def add_check(check)
42
+ self.class.add_check(check)
43
+ end
44
+
45
+ def settings
46
+ @settings ||= {}
47
+ end
48
+
49
+ def checks
50
+ self.public_methods.select { |method|
51
+ method =~ /^.+_enabled\?$/
52
+ }.map { |method|
53
+ method[0..-10]
54
+ }
55
+ end
56
+ end
57
+ end
@@ -1,4 +1,5 @@
1
1
  class PuppetLint
2
+
2
3
  module Plugin
3
4
  module ClassMethods
4
5
  def repository
@@ -18,29 +19,139 @@ end
18
19
 
19
20
  class PuppetLint::CheckPlugin
20
21
  include PuppetLint::Plugin
21
- attr_reader :warnings, :errors
22
+ attr_reader :problems, :checks
22
23
 
23
24
  def initialize
24
- @warnings = []
25
- @errors = []
25
+ @problems = []
26
+ @checks = []
27
+ @default_info = {:check => 'unknown', :linenumber => 0}
26
28
  end
27
29
 
28
- def warn(message)
29
- @warnings << message
30
+ def register_check(check)
31
+ @checks << check
30
32
  end
31
33
 
32
- def error(message)
33
- @errors << message
34
+ # notify(kind, message_hash) #=> nil
35
+ #
36
+ # Adds the message to the problems array.
37
+ # The _kind_ gets added to the _message_hash_ by setting the key :_kind_.
38
+ # Typically, the _message_hash_ should contain following keys:
39
+ # <i>message</i>:: which contains a string value describing the problem
40
+ # <i>linenumber</i>:: which contains the line number on which the problem occurs.
41
+ # Besides the :_kind_ value that is being set, some other key/values are also
42
+ # added. Typically, this is
43
+ # <i>check</i>:: which contains the name of the check that is being executed.
44
+ # <i>linenumber</i>:: which defaults to 0 if the message does not already contain one.
45
+ #
46
+ # notify :warning, :message => "Something happened", :linenumber => 4
47
+ # => {:kind=>:warning, :message=>"Something happened", :linenumber=>4, :check=>'unknown'}
48
+ #
49
+ def notify(kind, message_hash)
50
+ message_hash[:kind] = kind
51
+ message_hash.merge!(@default_info) {|key, v1, v2| v1 }
52
+ @problems << message_hash
53
+ message_hash
34
54
  end
35
55
 
36
56
  def run(path, data)
37
- test(path, data)
57
+ lexer = Puppet::Parser::Lexer.new
58
+ lexer.string = data
59
+ @tokens = lexer.fullscan
60
+ @path = path
61
+ @data = data
62
+
63
+ test(path, data) if self.respond_to? :test
64
+ self.public_methods.select { |method|
65
+ method.start_with? 'lint_check_'
66
+ }.each { |method|
67
+ name = method[11..-1]
68
+ @default_info[:check] = name
69
+ self.send(method) if PuppetLint.configuration.send("#{name}_enabled?")
70
+ }
71
+
72
+ @problems
73
+ end
74
+
75
+ def filter_tokens
76
+ @title_tokens = []
77
+ @resource_indexes = []
78
+ @class_indexes = []
79
+ @defined_type_indexes = []
80
+
81
+ @tokens.each_index do |token_idx|
82
+ if @tokens[token_idx].first == :COLON
83
+ # gather a list of tokens that are resource titles
84
+ if @tokens[token_idx-1].first == :RBRACK
85
+ title_array_tokens = @tokens[@tokens.rindex { |r| r.first == :LBRACK }+1..token_idx-2]
86
+ @title_tokens += title_array_tokens.select { |token| [:STRING, :NAME].include? token.first }
87
+ else
88
+ if @tokens[token_idx + 1].first != :LBRACE
89
+ @title_tokens << @tokens[token_idx-1]
90
+ end
91
+ end
92
+
93
+ # gather a list of start and end indexes for resource attribute blocks
94
+ if @tokens[token_idx+1].first != :LBRACE
95
+ @resource_indexes << {:start => token_idx+1, :end => @tokens[token_idx+1..-1].index { |r| [:SEMIC, :RBRACE].include? r.first }+token_idx}
96
+ end
97
+ elsif [:CLASS, :DEFINE].include? @tokens[token_idx].first
98
+ lbrace_count = 0
99
+ @tokens[token_idx+1..-1].each_index do |class_token_idx|
100
+ idx = class_token_idx + token_idx
101
+ if @tokens[idx].first == :LBRACE
102
+ lbrace_count += 1
103
+ elsif @tokens[idx].first == :RBRACE
104
+ lbrace_count -= 1
105
+ if lbrace_count == 0
106
+ class_indexes << {:start => token_idx, :end => idx} if @tokens[token_idx].first == :CLASS
107
+ defined_type_indexes << {:start => token_idx, :end => idx} if @tokens[token_idx].first == :DEFINE
108
+ break
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ def tokens
117
+ @tokens
118
+ end
119
+
120
+ def path
121
+ @path
122
+ end
123
+
124
+ def data
125
+ @data
126
+ end
127
+
128
+ def title_tokens
129
+ filter_tokens if @title_tokens.nil?
130
+ @title_tokens
131
+ end
132
+
133
+ def resource_indexes
134
+ filter_tokens if @resource_indexes.nil?
135
+ @resource_indexes
136
+ end
137
+
138
+ def class_indexes
139
+ filter_tokens if @class_indexes.nil?
140
+ @class_indexes
141
+ end
142
+
143
+ def defined_type_indexes
144
+ filter_tokens if @defined_type_indexes.nil?
145
+ @defined_type_indexes
146
+ end
38
147
 
39
- {:warnings => @warnings, :errors => @errors}
148
+ def manifest_lines
149
+ @manifest_lines ||= @data.split("\n")
40
150
  end
41
151
 
42
- def test(data)
43
- raise NotImplementedError.new "Oh no"
152
+ def self.check(name, &b)
153
+ PuppetLint.configuration.add_check name
154
+ define_method("lint_check_#{name}", b)
44
155
  end
45
156
  end
46
157