puppet-lint 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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