banalize 0.0.1

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.
@@ -0,0 +1,19 @@
1
+ module Banalize
2
+ class BanalizeError < StandardError; end
3
+
4
+ class Runner
5
+ class Error < BanalizeError; end
6
+ end
7
+
8
+ class Registry < Parser
9
+ class Error < BanalizeError; end
10
+ class RuntimeError < BanalizeError; end
11
+ class ArgumentError < BanalizeError; end
12
+ end
13
+
14
+ class Policy
15
+ class Error < BanalizeError; end
16
+ class RuntimeError < BanalizeError; end
17
+ class ArgumentError < BanalizeError; end
18
+ end
19
+ end
@@ -0,0 +1,74 @@
1
+ module Banalize
2
+
3
+ require 'active_support/inflector'
4
+ require 'singleton'
5
+
6
+ ##
7
+ # Class for handling policies files: reading and loading.
8
+ #
9
+ class Files
10
+
11
+
12
+ include ::Singleton
13
+
14
+ ##
15
+ # Get list of all policy files installed in banalize.
16
+ #
17
+ # Policies can come from Banalize distribution in
18
+ # `./lib/policies` directory or suppplied by user from
19
+ # `~/.banalize/policies` directory. This allows exteding Banalize
20
+ # by creating own policies without need to repackage gem.
21
+ #
22
+ # @return [Hash] Sets class level variable `@@files` with list of
23
+ # policies. All policies are groupped in 3 arrays:
24
+ # `@@files[:all]`, `@@files[:ruby]`, `@@files[:other]`
25
+ #
26
+ def self.files
27
+ all = Dir.glob("#{File.dirname(File.dirname(__FILE__))}/policies/*")
28
+ all += Dir.glob("#{Banalize::USER[:policies]}/*") if Dir.exists? Banalize::USER[:policies]
29
+
30
+ all = all.grep(%r{/[^\.]})
31
+
32
+ ruby = all.dup.keep_if { |x| x=~ /\.rb$/ }
33
+
34
+ @@files ||= {
35
+ all: all,
36
+ ruby: ruby,
37
+ other: (all - ruby)
38
+ }
39
+ end
40
+
41
+ ##
42
+ # Load and populate policies list with configuration of each
43
+ # policy.
44
+ #
45
+ # For Ruby policies it requries each file and then calls #config
46
+ # method for it.
47
+ def self.policies
48
+
49
+ files[:ruby].each { |f| require f }
50
+
51
+ @policies ||= (files[:other].map { |f| shell_config f }) +
52
+ Banalize.policies.map(&:config)
53
+ end
54
+
55
+ ##
56
+ # Read configuration from Bash shell policy and return it as Hash
57
+ #
58
+ # @param [String] bash PATH to bash policy file
59
+ #
60
+ def self.shell_config bash
61
+ yaml = %x{ #{bash} config 2>&1 }
62
+ abort "Can not execute policy file #{bash}: \n ERROR #{yaml} " unless $?.exitstatus == 0
63
+ hash = YAML.load(yaml) rescue "Can not load YAML #{yaml}"
64
+
65
+ abort "Loaded policy metdata is not Hash: #{hash.to_s}" unless hash.is_a? Hash
66
+ hash.merge!({
67
+ path: bash,
68
+ policy: File.basename(bash).to_sym
69
+ })
70
+
71
+ Policy::DEFAULT.merge Hash[hash.map { |k, v| [k.to_sym, v] }]
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,46 @@
1
+ module Banalize
2
+
3
+ class Parser
4
+
5
+ def initialize path
6
+ @shebang = Numbered.new
7
+ @comments = Numbered.new
8
+ @code = Numbered.new
9
+
10
+ @shebang.add lines.shift if lines.first =~ /^#!/
11
+
12
+ lines.each_index do |idx|
13
+
14
+ next if lines[idx] =~ /^\s*$/
15
+
16
+ lineno = idx + 1 + (@shebang ? 1 : 0) # Compensate for base-0 and shebang line
17
+
18
+ if lines[idx] =~ /^\s*\#/
19
+ @comments.add lines[idx], lineno
20
+ else
21
+ @code.add lines[idx], lineno
22
+ end
23
+ end
24
+ end
25
+
26
+ ##
27
+ # Shebang contains first line of the script if it's in `#!`
28
+ # format. Otherwise it is nil.
29
+ attr_accessor :shebang
30
+
31
+ ##
32
+ # Contains all block comments of the file, excluding shebang line
33
+ #
34
+ # Only hash-comments are suported at this time. Comments in the
35
+ # style of here-documents are not.
36
+ attr_accessor :comments
37
+
38
+ ##
39
+ # Contains all non-comments of the script, excluding shebang line.
40
+ #
41
+ # Same as with comments, excluded from code blocks are only
42
+ # hash-comments, but here-documents even if they are not executing
43
+ # any action are included here.
44
+ attr_accessor :code
45
+ end
46
+ end
@@ -0,0 +1,102 @@
1
+ module Banalize
2
+
3
+ ##
4
+ # Class numbered implements simple model of numbered lines data
5
+ # structure. It's Mash (think Hash).
6
+ #
7
+ # Each pair is line_number => row. Line numbers are *not*
8
+ # necessarily sequential.
9
+ class Numbered < ::Mash
10
+
11
+ def initialize *p
12
+ @search = nil
13
+ super *p
14
+ end
15
+
16
+ ##
17
+ # Return true if values of instance have specifid pattern,
18
+ # returned by {#grep}.
19
+ #
20
+ # @see #grep
21
+ #
22
+ def has? *params
23
+ !grep(*params).empty?
24
+ end
25
+ alias :have? :has?
26
+
27
+ ##
28
+ # Opposite of {#has?}
29
+ # @see #has?
30
+ def does_not_have? *p
31
+ ! has? *p
32
+ end
33
+ alias :dont_have? :does_not_have?
34
+
35
+
36
+ ##
37
+ # Helper method to display only line numbers of the search result.
38
+ #
39
+ # Comma separated string with line numbers of {#search}
40
+ #
41
+ def lines
42
+ line = search.keys.join ', '
43
+ if Banalize::TRUNCATE
44
+ line.truncate(
45
+ Banalize::TRUNCATE,
46
+ separator: ' ',
47
+ omission: "... (total #{search.keys.length})"
48
+ )
49
+ end
50
+ end
51
+
52
+ ##
53
+ # Search attribute always contains last result of search (grep)
54
+ # operation.
55
+ #
56
+ attr_accessor :search
57
+
58
+ ##
59
+ # Human readable form. Only lines without numbers.
60
+ #
61
+ def to_s
62
+ if self.count == 1
63
+ self.values.first.to_s
64
+ else
65
+ self.values.to_s
66
+ end
67
+ end
68
+
69
+ alias :inspect :to_s
70
+
71
+ ##
72
+ # Grep lines of the Numbered object (i.e. values of the hash) and
73
+ # return all lines together with numbers that match
74
+ #
75
+ # @param [Regexp] pattern Search Regexp pattern for lines. It
76
+ # should be regexp, not string, since regular Array grep is
77
+ # not used here.
78
+ #
79
+ # @return [Numbered] Returns new instance of the same class
80
+ # {Numbered} containing only lines matching the search.
81
+ #
82
+ def grep pattern
83
+ @search = self.class.new(self.select { |idx,line| line =~ pattern })
84
+ end
85
+
86
+ ##
87
+ # Add new line to the collection of lines.
88
+ #
89
+ # *Attention*: since {Numbered} is a hash, adding line with the
90
+ # number that already exists will overwrite existing one.
91
+ #
92
+ # ## Example
93
+ #
94
+ # ```
95
+ # @shebang.add lines.shift if lines.first =~ /^#!/
96
+ # ```
97
+ def add line, number=0
98
+ self[number] = line.chomp
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,95 @@
1
+ module Banalize
2
+ ##
3
+ # Class defining use of Banalize policies. Mainly for accessing
4
+ # attributes created by Banalize::Files class methods.
5
+ #
6
+ class Policy
7
+
8
+ ##
9
+ # Default settings for policies
10
+ #
11
+ DEFAULT = {
12
+ style: :core,
13
+ severity: Severity.default,
14
+ description: 'No description'
15
+ }
16
+
17
+ def initialize policy
18
+
19
+ @config =
20
+ Files.policies.find { |x| x[:policy] == policy.to_sym } ||
21
+ raise(RuntimeError, "Policy ''#{policy}' not found among policy files")
22
+
23
+ end
24
+
25
+ attr_reader :config
26
+
27
+ # Find policy or list of policies by search criteria.
28
+ #
29
+ # Search can be policy name (Symbol or String), Hash with
30
+ # :policy and/or :severity keys or nil.
31
+ #
32
+ # - `nil` - no filtering. Simply list of all policies returned.
33
+ #
34
+ # - `:severity` searched for policies with severity value same as
35
+ # search or higher.
36
+ #
37
+ # - `:style` - can be Symbol or Array of symbols. If it's :core
38
+ # or includes :core, all policies returned.
39
+ #
40
+ #
41
+ # @param [String, Symbol, Hash] search Name of a policy to check
42
+ # against or hash having :severity and/or :policy keys.
43
+ #
44
+ # @return [Hash]
45
+ #
46
+ def self.search search=nil
47
+ case search
48
+
49
+ when nil # If nothing given return all
50
+ Files.policies
51
+
52
+ when Symbol, String
53
+ [Files.policies.find { |x| x[:policy] == search.to_sym }]
54
+
55
+ when Hash
56
+ res = Files.policies
57
+ #
58
+ if search.has_key? :style
59
+
60
+ search[:style] = [search[:style]].flatten
61
+
62
+ res = if search[:style].include?(:core)
63
+ res # `core` - includes everything
64
+ else
65
+ res.select { |x| search[:style].include? x[:style] }
66
+ end
67
+ end
68
+ #
69
+ # Find policies with severity this or higher
70
+ #
71
+ res = res.select { |x| x[:severity] >= search[:severity] } if
72
+ search.has_key? :severity
73
+
74
+ res
75
+
76
+ else
77
+ raise ArgumentError, "Unknown search criteria: #{search.inspect}"
78
+
79
+ end.compact
80
+ end
81
+
82
+ ##
83
+ # Return values of config hash
84
+ #
85
+ def method_missing symbol, *args, &block
86
+ return config[symbol] if config.has_key? symbol
87
+ super symbol, *args, &block
88
+ end
89
+
90
+ def self.method_missing symbol, *args, &block
91
+ self.new(*args).send symbol
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,35 @@
1
+ module Banalize
2
+ class Policy
3
+ class Severity
4
+
5
+ LIST = { gentle: 5, stern: 4, harsh: 3, cruel: 2, brutal: 1 }
6
+
7
+ def initialize severity
8
+ @severity = self.to_i severity
9
+ end
10
+
11
+ def self.default
12
+ :gentle
13
+ end
14
+
15
+ def self.to_i severity
16
+ case severity
17
+ when Symbol, String
18
+ LIST[severity.to_sym]
19
+ when Fixnum
20
+ severity
21
+ end
22
+ end
23
+ ##
24
+ # Describe available severities in a taxt format
25
+ #
26
+ def self.to_s
27
+ format = "%-20s%s"
28
+ LIST.map do |k,v|
29
+ sprintf format , k.to_s.humanize, v
30
+ end.join "\n"
31
+
32
+ end
33
+ end #severity
34
+ end
35
+ end
@@ -0,0 +1,219 @@
1
+ module Banalize
2
+
3
+ require 'active_support/inflector'
4
+ require 'singleton'
5
+ ##
6
+ # Class defining use of Banalize policies DSL. Sets some sane
7
+ # default values for each of the DSL methods and registers new
8
+ # policy in the list of policies.
9
+ #
10
+ # Instance attributes
11
+ # -----------
12
+ # Class sets following attribute accessor methods:
13
+ #
14
+ # - {#lines}
15
+ # - {#path}
16
+ # - {#errors}
17
+ #
18
+ # Other attributes are inherited from parent Parser class.
19
+ #
20
+ # Class methods
21
+ # ----------------------
22
+ # Class methods define DSL for Banalizer. These are:
23
+ #
24
+ # - {register}
25
+ # - {policy}
26
+ # - {synopsis}
27
+ # - {description}
28
+ # - {default}
29
+ # - {style}
30
+ # - {config}
31
+ class Registry < Parser
32
+
33
+ # Define new policy from loading Ruby file with policy.
34
+ #
35
+ # ## Example
36
+ #
37
+ # # First argument to the `banalizer` method call defines
38
+ # # policy name and short description (AKA synopsis).
39
+ # # Both `description` and `policy_name` methods are
40
+ # # optional in the block.
41
+ #
42
+ # banalizer "Check that format of shebang is #!/usr/bin/env bash" do
43
+ #
44
+ # severity 5 # Default is 1
45
+ #
46
+ # synopsis "Can provide alternative description here"
47
+ #
48
+ # style 'bug'
49
+ #
50
+ # policy_name "Don't need to specify policy name,it is defined from argument to `describe` method"
51
+ #
52
+ # # All method calls are optional. Only required things
53
+ # # are policy description and definition of method
54
+ # # `run`. Method `run` must be overwritten, otherwise
55
+ # # it simply raises execption.
56
+ # #
57
+ # # Run method must return something that evaluates into
58
+ # # `true` (policy check successful) or `false`.
59
+ # #
60
+ # def run
61
+ # lines.first =~ %r{^\#!/usr/bin/env\s+bash}
62
+ # end
63
+ # end
64
+ #
65
+ # @param [String] myname name for the policy
66
+ # @param [Block] block
67
+ def self.register myname, &block
68
+
69
+ klass = myname.to_s.gsub(/\W/, '_').camelize
70
+
71
+ c = Object.const_set klass, Class.new(self , &block)
72
+
73
+ c.synopsis myname
74
+ c.default({})
75
+ c.severity Policy::DEFAULT[:severity] unless c.severity # Set default severity if it's not defined in block
76
+
77
+ # Override these with Styles file
78
+ begin
79
+ c.description $styles[myname][:description]
80
+ c.severity $styles[myname][:severity]
81
+ rescue NoMethodError => e
82
+ end
83
+
84
+ return c
85
+ end
86
+
87
+ # TODO: how to load parsers ????
88
+
89
+ @@parsers = []
90
+ def self.parser name
91
+ @@parsers << name
92
+ end
93
+
94
+ ##
95
+ # Creates new instance of policy check.
96
+ #
97
+ # @param [String] path UNIX PATH to Bash script
98
+ #
99
+ def initialize path
100
+ raise RuntimeError, "File does not exist: #{path}" unless File.exists? path
101
+ @lines = IO.read(path).force_encoding("utf-8").split($/)
102
+ @path = path
103
+ @errors = Errors.new self
104
+
105
+ # Make class level default variable accessible as instance level
106
+ # variable and accessor
107
+
108
+ @default = self.class.default.merge($styles[self.class.config[:policy]] || {})
109
+
110
+ super path
111
+ end
112
+
113
+ attr_accessor :default
114
+
115
+ # Instance of Errors class to hold all error messages from tests
116
+ attr_accessor :errors
117
+
118
+ ##
119
+ # This method must be overwritten in children classes.
120
+ def run
121
+ raise ArgumentError, "You must override #run method in class ''#{self.class.policy_name}'"
122
+ end
123
+
124
+ # Lines of the tested bash file, split by \n's
125
+ attr_accessor :lines
126
+
127
+ # UNIX path to the tested file
128
+ attr_accessor :path
129
+
130
+ ##
131
+ # Name of this policy.
132
+ #
133
+ def self.policy name=nil
134
+ if name
135
+ @policy = name.to_sym
136
+ else
137
+ @policy ||= self.name.underscore.to_sym
138
+ end
139
+ end
140
+
141
+ ##
142
+ # Set defaults for the policy. Defaults are hash with values used
143
+ # in {#run} method. When defining defaults, define them as:
144
+ #
145
+ # ```
146
+ # default :max => 20
147
+ # ```
148
+ #
149
+ # During run defaults accessible as default[:max]
150
+ #
151
+ # Defaults can be overwriten by personal style configuration file. See {file:CONFIGURATION.md} for details.
152
+ #
153
+ def self.default hash=nil
154
+ @default ||= hash
155
+ end
156
+
157
+
158
+ ##
159
+ # Short summary of the policy. Synopsis comes from the name of the
160
+ # policy which is first parameter to the `banalizer` method, but
161
+ # can be overwwritten by calling systemu DSL method in the block
162
+ # for the `banalizer` method.
163
+ #
164
+ def self.synopsis desc=nil
165
+ @synopsis ||= desc.to_s.humanize
166
+ end
167
+
168
+ def self.description hlp=nil
169
+ if hlp
170
+ @description = hlp
171
+ else
172
+ @description ||= "No description available for #{self.name}"
173
+ end
174
+ end
175
+
176
+ ##
177
+ # Default style is 'bug'
178
+ #
179
+ def self.style p=Policy::DEFAULT[:style]
180
+ @style ||= p
181
+ end
182
+
183
+ ##
184
+ # Use lowest severity by default
185
+ #
186
+ def self.severity sev=nil
187
+ if sev
188
+ @severity = Banalize::Policy::Severity.to_i(sev)
189
+ else
190
+ @severity ||= Banalize::Policy::Severity.to_i(sev)
191
+ end
192
+ end
193
+
194
+
195
+ ##
196
+ #
197
+ # Return configuration for the policy.
198
+ #
199
+ # Same as config parameter of 'other' checks.
200
+ #
201
+ # @return [Hash] Hash containing all configured attributes of the
202
+ # policy check.
203
+ #
204
+ def self.config
205
+ {
206
+ policy: policy,
207
+ synopsis: synopsis,
208
+ style: style,
209
+ severity: severity,
210
+ description: description,
211
+ klass: name,
212
+ default: default
213
+ }
214
+ end
215
+
216
+
217
+
218
+ end
219
+ end