maximus 0.1.2 → 0.1.3

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,329 @@
1
+ module Maximus
2
+ # @since 0.1.3
3
+ # @attr_reader settings [Hash] all the options
4
+ # @attr_reader temp_files [Hash] Filename without extension => path to temp file
5
+ class Config
6
+
7
+ include Helper
8
+
9
+ attr_reader :settings, :temp_files
10
+
11
+ # Global options for all of maximus
12
+ #
13
+ # @param opts [Hash] options passed directly to config
14
+ # @option opts [Boolean] :is_dev (false) whether or not the class was initialized from the command line
15
+ # @option opts [String, Boolean, nil] :log ('log/maximus_git.log') path to log file
16
+ # If not set, logger outputs to STDOUT
17
+ # @option opts [String, Boolean] :git_log (false) path to log file or don't log
18
+ # The git gem is very noisey
19
+ # @option opts [String] :root_dir base directory
20
+ # @option opts [String] :domain ('http://localhost:3000') the host - used for Statistics
21
+ # @option opts [String, Integer] :port ('') port number - used for Statistics
22
+ # and appended to domain. If blank (false, empty string, etc.), will not
23
+ # append to domain
24
+ # @option opts [String, Array] :file_paths ('') path to files. Accepts glob notation
25
+ # @option opts [Hash] :paths ({home: '/'}) labeled relative path to URLs. Statistics only
26
+ # @option opts [String] :commit accepts sha, "working", "last", or "master".
27
+ # @option opts [String] :config_file ('maximus.yml') path to config file
28
+ # @return [void] this method is used to set up instance variables
29
+ def initialize(opts = {})
30
+ opts[:is_dev] ||= false
31
+
32
+ # Only set log file if it's set to true.
33
+ # Otherwise, allow it to be nil or a path
34
+ opts[:log] = 'log/maximus.log' if opts[:log].is_a?(TrueClass)
35
+
36
+ opts[:git_log] = false if opts[:git_log].nil?
37
+ opts[:git_log] = 'log/maximus_git.log' if opts[:git_log].is_a?(TrueClass)
38
+
39
+ # @see Helper#root_dir
40
+ opts[:root_dir] ||= root_dir
41
+ opts[:domain] ||= 'http://localhost'
42
+ opts[:port] ||= is_rails? ? 3000 : ''
43
+ opts[:paths] ||= { 'home' => '/' }
44
+
45
+ # Accounting for space-separated command line arrays
46
+ if opts[:paths].is_a?(Array)
47
+ new_paths = {}
48
+ opts[:paths].each do |p|
49
+ if p.split('/').length > 1
50
+ new_paths[p.split('/').last.to_s] = p
51
+ else
52
+ new_paths['home'] = '/'
53
+ end
54
+ end
55
+ opts[:paths] = new_paths
56
+ end
57
+
58
+ # What we're really interested in
59
+ @settings = opts
60
+
61
+ # Instance variables for Config class only
62
+ @temp_files = {}
63
+
64
+ conf_location = (opts[:config_file] && File.exist?(opts[:config_file])) ? opts[:config] : find_config
65
+
66
+ @yaml = YAML.load_file(conf_location)
67
+
68
+ # Match defaults
69
+ @yaml['domain'] ||= @settings[:domain]
70
+ @yaml['paths'] ||= @settings[:paths]
71
+ @yaml['port'] ||= @settings[:port]
72
+ set_families('lints', ['jshint', 'scsslint', 'rubocop', 'brakeman', 'railsbp'])
73
+ set_families('frontend', ['jshint', 'scsslint', 'phantomas', 'stylestats', 'wraith'])
74
+ set_families('backend', ['rubocop', 'brakeman', 'railsbp'])
75
+ set_families('ruby', ['rubocop', 'brakeman', 'railsbp'])
76
+ set_families('statistics', ['phantomas', 'stylestats', 'wraith'])
77
+ set_families('all', ['lints', 'statistics'])
78
+
79
+ # Override options with any defined in a discovered config file
80
+ evaluate_yaml
81
+ end
82
+
83
+ # Set global options or generate appropriate config files for lints or statistics
84
+ #
85
+ # @param yaml_data [Hash] (@yaml) loaded data from the discovered maximus config file
86
+ # @return [Hash] paths to temp config files and static options
87
+ # These should be deleted with destroy_temp after read and loaded
88
+ def evaluate_yaml(yaml_data = @yaml)
89
+ yaml_data.each do |key, value|
90
+ unless value.is_a?(FalseClass)
91
+ value = {} if value.is_a?(TrueClass)
92
+
93
+ case key
94
+
95
+ when 'jshint', 'JSHint', 'JShint'
96
+
97
+ # @todo DRY this up, but can't call it at the start because of the
98
+ # global config variables (last when statement in this switch)
99
+ value = load_config(value)
100
+
101
+ if yaml_data[key].is_a?(Hash) && yaml_data[key].has_key?['ignore']
102
+ jshintignore_file = []
103
+ yaml_data[key]['ignore'].each { |i| jshintignore_file << "#{i}\n" }
104
+ @settings[:jshintignore] = temp_it('jshintignore.json', jshintignore_file)
105
+ end
106
+ @settings[:jshint] = temp_it('jshint.json', value.to_json)
107
+
108
+ when 'scsslint', 'scss-lint', 'SCSSlint'
109
+ value = load_config(value)
110
+
111
+ @settings[:scsslint] = temp_it('scsslint.yml', value.to_yaml)
112
+
113
+ when 'rubocop', 'Rubocop', 'RuboCop'
114
+ value = load_config(value)
115
+
116
+ @settings[:rubocop] = temp_it('rubocop.yml', value.to_yaml)
117
+
118
+ when 'brakeman'
119
+ @settings[:brakeman] = yaml_data[key]
120
+
121
+ when 'rails_best_practice', 'railsbp'
122
+ @settings[:railsbp] = yaml_data[key]
123
+
124
+ when 'stylestats', 'Stylestats'
125
+ value = load_config(value)
126
+ @settings[:stylestats] = temp_it('stylestats.json', value.to_json)
127
+
128
+ when 'phantomas', 'Phantomas'
129
+ value = load_config(value)
130
+ @settings[:phantomas] = temp_it('phantomas.json', value.to_json)
131
+
132
+ when 'wraith', 'Wraith'
133
+ value = load_config(value)
134
+
135
+ @settings[:wraith] = {}
136
+ if value.include?('browser')
137
+ value['browser'].each do |browser, browser_value|
138
+ unless browser_value.is_a?(FalseClass)
139
+ new_data = {}
140
+ new_data['browser'] = []
141
+ new_data['browser'] << { browser.to_s => browser.to_s }
142
+
143
+ # Regardless of what's in the config, override with maximus,
144
+ # predictable namespacing
145
+ new_data['directory'] = "maximus_wraith_#{browser}"
146
+ new_data['history_dir'] = "maximus_wraith_history_#{browser}"
147
+
148
+ # @todo a snap file cannot be set in the config
149
+ snap_file = case browser
150
+ when 'casperjs' then 'casper'
151
+ when 'nojs' then 'nojs'
152
+ else 'snap'
153
+ end
154
+ new_data['snap_file'] = File.join(File.dirname(__FILE__), "config/wraith/#{snap_file}.js")
155
+
156
+ @settings[:wraith][browser.to_sym] = wraith_setup(new_data, "wraith_#{browser}")
157
+ end
158
+ end
159
+ else
160
+ value['browser'] = { 'phantomjs' => 'phantomjs' }
161
+ value['directory'] = 'maximus_wraith_phantomjs'
162
+ value['history_dir'] = 'maximus_wraith_history_phantomjs'
163
+ value['snap_file'] = File.join(File.dirname(__FILE__), "config/wraith/snap.js")
164
+ @settings[:wraith][:phantomjs] = wraith_setup(value)
165
+ end
166
+
167
+ # Configuration important to all of maximus
168
+ when 'is_dev', 'log', 'root_dir', 'domain', 'port', 'paths', 'commit'
169
+ @settings[key.to_sym] = yaml_data[key]
170
+ end
171
+ end
172
+ end
173
+
174
+ # Finally, we're done
175
+ @settings
176
+ end
177
+
178
+ # @return [Boolean]
179
+ def is_dev?
180
+ @settings[:is_dev]
181
+ end
182
+
183
+ # Defines base logger
184
+ #
185
+ # @param out [String, STDOUT] location for logging
186
+ # Accepts file path
187
+ # @return [Logger] self.log
188
+ def log
189
+ out = @settings[:log] || STDOUT
190
+ @log ||= Logger.new(out)
191
+ @log.level ||= Logger::INFO
192
+ @log
193
+ end
194
+
195
+ # Remove all or one created temporary config file
196
+ #
197
+ # @see #temp_it
198
+ # @see #yaml_evaluate
199
+ #
200
+ # @param filename [String] (nil) file to destroy
201
+ # If nil, destroy all temp files
202
+ # @return [void]
203
+ def destroy_temp(filename = nil)
204
+ return if @temp_files[filename.to_sym].blank?
205
+ if filename.nil?
206
+ @temp_files.each { |filename, file| file.unlink }
207
+ @temp_files = {}
208
+ else
209
+ @temp_files[filename.to_sym].unlink
210
+ @temp_files.delete(filename.to_sym)
211
+ end
212
+ end
213
+
214
+ # Combine domain with port if necessary
215
+ #
216
+ # @return [String] complete domain/host address
217
+ def domain
218
+ (!@settings[:port].blank? || @settings[:domain].include?(':')) ? "#{@settings[:domain]}:#{@settings[:port]}" : @settings[:domain]
219
+ end
220
+
221
+
222
+ private
223
+
224
+ # Allow shorthand to be declared for groups Maximus executions
225
+ #
226
+ # @example disable statistics
227
+ # @yaml['statistics'] = false
228
+ # set_families('statistics', ['phantomas', 'stylestats', 'wraith'])
229
+ #
230
+ # Sets as Boolean based on whether or not the queried label is `true`
231
+ # @param head_of_house [String] @yaml key and group label
232
+ # @param family [Array] group of other @yaml keys to be disabled
233
+ # @return [void] modified @yaml
234
+ def set_families(head_of_house, family)
235
+ if @yaml.has_key?(head_of_house)
236
+ family.each { |f| @yaml[f] = @yaml[head_of_house].is_a?(TrueClass) }
237
+ end
238
+ end
239
+
240
+ # Load config files if filename supplied
241
+ #
242
+ # @param value [Mixed] value from base config file
243
+ # @param [Hash] return blank hash if file not found so
244
+ # the reset of the process doesn't break
245
+ def load_config(value)
246
+ return value unless value.is_a?(String)
247
+ if File.exist?(value)
248
+ return YAML.load_file(value)
249
+ else
250
+ puts "#{value} not found"
251
+ return {}
252
+ end
253
+ end
254
+
255
+ # Create a temp file with config data
256
+ #
257
+ # Stores all temp files in @temp_files or self.temp_files
258
+ # In Hash with filename minus extension as the key.
259
+ #
260
+ # @param filename [String] the preferred name/identifier of the file
261
+ # @param data [Mixed] config data important to each lint or statistic
262
+ # @return [String] absolute path to new config file
263
+ def temp_it(filename, data)
264
+ ext = filename.split('.')
265
+ file = Tempfile.new([filename, ".#{ext[1]}"])
266
+ file.write(data)
267
+ file.close
268
+ @temp_files[ext[0].to_sym] = file
269
+ file.path
270
+ end
271
+
272
+ # Look for a maximus config file
273
+ #
274
+ # Checks ./maximus.yml, ./maximus.yaml, ./config/maximus.yaml in order.
275
+ # If there hasn't been a file discovered yet, checks ./config/maximus.yml
276
+ # and if there still isn't a file, load the default one included with the
277
+ # maximus gem.
278
+ #
279
+ # @return [String] absolute path to config file
280
+ def find_config
281
+ config_exists('maximus.yml') || config_exists('maximus.yaml') || config_exists('config/maximus.yaml') || check_default_config_path('maximus.yml')
282
+ end
283
+
284
+ # See if a config file exists
285
+ #
286
+ # @see #find_config
287
+ #
288
+ # This is used exclusively for the find_config method
289
+ # @param file [String] file name
290
+ # @return [String, FalseClass] if file is found return the absolute path
291
+ # otherwise return false so we can keep checking
292
+ def config_exists(file)
293
+ File.exist?(File.join(File.dirname(__FILE__), file)) ? File.join(File.dirname(__FILE__), file) : false
294
+ end
295
+
296
+ # Wraith is a complicated gem with significant configuration
297
+ #
298
+ # @see #yaml_evaluate
299
+ # @see #temp_it
300
+ #
301
+ # @param value [Hash] modified data from a wraith config or injected data
302
+ # @param name [String] ('wraith') config file name to write and eventually load
303
+ # @return [String] temp file path
304
+ def wraith_setup(value, name = 'phantomjs')
305
+
306
+ if @yaml.include?('urls')
307
+ value['domains'] = yaml_data['urls']
308
+ else
309
+ value['domains'] = {}
310
+ # @see #domain
311
+ value['domains']['main'] = domain
312
+ end
313
+
314
+ # Wraith requires this screen_width config to be present
315
+ value['screen_widths'] ||= [767, 1024, 1280]
316
+
317
+ value['paths'] = @yaml['paths']
318
+ value['threshold'] ||= 0
319
+
320
+ # Wraith requires config files have .yaml extensions
321
+ # https://github.com/BBC-News/wraith/blob/2aff771eba01b76e61600cccb2113869bfe16479/lib/wraith/wraith.rb
322
+ file = Tempfile.new([name, '.yaml'])
323
+ file.write(value.to_yaml)
324
+ file.close
325
+ file.path
326
+ end
327
+
328
+ end
329
+ end
@@ -1,8 +1,4 @@
1
1
  require 'git'
2
- require 'active_support'
3
- require 'active_support/core_ext/object/blank'
4
- require 'rainbow'
5
- require 'rainbow/ext/string'
6
2
 
7
3
  module Maximus
8
4
  # @since 0.1.0
@@ -12,29 +8,21 @@ module Maximus
12
8
 
13
9
  # Git management
14
10
  #
15
- # @param opts [Hash] the options to initialize and pass to other classes
16
- # @option opts [Boolean] :is_dev whether or not the class was initialized from the command line
17
- # @option opts [String] :log ('log/maximus_git.log') path to log file
18
- # @option opts [String] :root_dir base directory
19
- # @option opts [String] :base_url ('http://localhost:3000') the host - used for Statistics
20
- # @option opts [String, Integer] :port port number - used for Statistics
21
- # @option opts [String, Array] :path ('') path to files. Accepts glob notation
11
+ # Inherits settings from {Config#initialize}
12
+ # @param opts [Hash] options passed directly to config
13
+ # @option opts [Boolean] :is_dev (false) whether or not the class was initialized from the command line
14
+ # This is set here again in case only GitControl needs to be directly called (outside of command line)
15
+ # @option opts [Config object] :config custom Maximus::Config object
22
16
  # @option opts [String] :commit accepts sha, "working", "last", or "master".
23
- # Used in the command line
24
17
  # @return [void] this method is used to set up instance variables
25
18
  def initialize(opts = {})
26
19
  opts[:is_dev] ||= false
27
- opts[:log] = Logger.new('log/maximus_git.log') if opts[:log].nil?
28
- opts[:base_url] ||= 'http://localhost:3000'
29
- opts[:port] ||= ''
30
- opts[:root_dir] ||= root_dir
31
- log = opts[:log] ? log : nil
32
- @@log = mlog
33
- @@is_dev = opts[:is_dev]
34
- @opts = opts
35
-
36
- @psuedo_commit = (!@opts[:commit].blank? && @opts[:commit] == 'working')
37
- @g = Git.open(@opts[:root_dir], :log => log)
20
+
21
+ opts[:config] ||= Maximus::Config.new({commit: opts[:commit], is_dev: opts[:is_dev] })
22
+ @config ||= opts[:config]
23
+ @settings ||= @config.settings
24
+ @psuedo_commit = (!@settings[:commit].blank? && (@settings[:commit] == 'working' || @settings[:commit] == 'last' || @settings[:commit] == 'master') )
25
+ @g = Git.open(@settings[:root_dir], :log => @settings[:git_log])
38
26
  end
39
27
 
40
28
  # 30,000 foot view of a commit
@@ -74,12 +62,12 @@ module Maximus
74
62
  def compare(sha1 = master_commit.sha, sha2 = sha)
75
63
  diff_return = {}
76
64
 
77
- if @opts[:commit]
78
- sha1 = case @opts[:commit]
65
+ if @settings[:commit]
66
+ sha1 = case @settings[:commit]
79
67
  when 'master' then master_commit.sha
80
68
  when 'last' then @g.object('HEAD^').sha
81
69
  when 'working' then 'working'
82
- else @opts[:commit]
70
+ else @settings[:commit]
83
71
  end
84
72
  end
85
73
 
@@ -112,7 +100,7 @@ module Maximus
112
100
  unless files[child].blank?
113
101
  files[child].each do |c|
114
102
  # hack to ignore deleted files
115
- files[child] = new_lines[c].blank? ? [] : [ filename: "#{@opts[:root_dir]}/#{c}", changes: new_lines[c] ]
103
+ files[child] = new_lines[c].blank? ? [] : [ filename: "#{@settings[:root_dir]}/#{c}", changes: new_lines[c] ]
116
104
  end
117
105
  files[ext].concat(files[child])
118
106
  files.delete(child)
@@ -149,30 +137,23 @@ module Maximus
149
137
  git_shas.each do |sha, exts|
150
138
  # @todo better way to silence git, in case there's a real error?
151
139
  quietly { `git checkout #{sha} -b maximus_#{sha}` } unless @psuedo_commit
152
- puts sha.to_s.color(:blue) if @@is_dev
140
+ puts sha.to_s.color(:blue) if @config.is_dev?
153
141
  git_output[sha.to_sym] = {
154
142
  lints: {},
155
143
  statistics: {}
156
144
  }
157
145
  lints = git_output[sha.to_sym][:lints]
158
146
  statistics = git_output[sha.to_sym][:statistics]
159
- lint_opts = {
160
- is_dev: @@is_dev,
161
- root_dir: @opts[:root_dir],
162
- commit: !@opts[:commit].blank?
163
- }
164
- stat_opts = {
165
- is_dev: @@is_dev,
166
- base_url: @opts[:base_url],
167
- port: @opts[:port],
168
- root_dir: @opts[:root_dir]
169
- }
147
+ lint_opts = {}
170
148
 
171
149
  # This is where everything goes down
172
150
  exts.each do |ext, files|
173
151
  # For relevant_lines data
174
- lint_opts[:git_files] = files
175
- lint_opts[:path] = lint_file_paths(files, ext) if lint_by_path
152
+ lint_opts = {
153
+ git_files: files,
154
+ config: @config
155
+ }
156
+ lint_opts[:file_paths] = lint_file_paths(files, ext) if lint_by_path
176
157
  case ext
177
158
  when :scss
178
159
  lints[:scsslint] = Maximus::Scsslint.new(lint_opts).result
@@ -183,11 +164,11 @@ module Maximus
183
164
  # @todo stylestat is singular here because model name in Rails is singular.
184
165
  # But adding a .classify when it's converted to a model chops off the end s on 'phantomas',
185
166
  # which breaks the model name.
186
- statistics[:stylestat] = Maximus::Stylestats.new(stat_opts).result
167
+ statistics[:stylestat] = Maximus::Stylestats.new({config: @config}).result
187
168
 
188
169
  # @todo double pipe here is best way to say, if it's already run, don't run again, right?
189
- statistics[:phantomas] ||= Maximus::Phantomas.new(stat_opts).result
190
- statistics[:wraith] = Maximus::Wraith.new(stat_opts).result
170
+ statistics[:phantomas] ||= Maximus::Phantomas.new({config: @config}).result
171
+ statistics[:wraith] ||= Maximus::Wraith.new({config: @config}).result
191
172
  end
192
173
  when :js
193
174
  lints[:jshint] = Maximus::Jshint.new(lint_opts).result
@@ -195,14 +176,14 @@ module Maximus
195
176
  # Do not run statistics if called from command line
196
177
  if lint_opts[:commit].blank?
197
178
 
198
- statistics[:phantomas] = Maximus::Phantomas.new(stat_opts).result
179
+ statistics[:phantomas] ||= Maximus::Phantomas.new({config: @config}).result
199
180
 
200
181
  # @todo double pipe here is best way to say, if it's already run, don't run again, right?
201
- statistics[:wraith] ||= Maximus::Wraith.new(stat_opts).result
182
+ statistics[:wraith] ||= Maximus::Wraith.new({config: @config}).result
202
183
  end
203
184
  when :ruby
204
185
  lints[:rubocop] = Maximus::Rubocop.new(lint_opts).result
205
- lints[:railsbp] = Maximus::Railsbp.new(lint_opts).result
186
+ lints[:railsbp] ||= Maximus::Railsbp.new(lint_opts).result
206
187
  lints[:brakeman] = Maximus::Brakeman.new(lint_opts).result
207
188
  when :rails
208
189
  lints[:railsbp] ||= Maximus::Railsbp.new(lint_opts).result
@@ -280,6 +261,7 @@ module Maximus
280
261
  end
281
262
 
282
263
  # Store last commit as Ruby Git::Object
264
+ #
283
265
  # @param commitsha [String]
284
266
  # @return [Git::Object]
285
267
  def vccommit(commitsha = sha)
@@ -3,18 +3,15 @@ require 'rainbow/ext/string'
3
3
  require 'active_support'
4
4
  require 'active_support/core_ext/object/blank'
5
5
  require 'yaml'
6
+ require 'tempfile'
6
7
 
7
8
  module Maximus
8
9
  # @since 0.1.0
9
10
  module Helper
10
11
 
11
12
  # See if project linted is a Rails app
12
- # This will usually be stored as a class variable in the inherited class
13
- # @example class variable
14
- # @@is_rails = is_rails?
15
- #
16
- # @see Lint#initialize
17
13
  #
14
+ # This will usually be stored as a class variable in the inherited class
18
15
  # @return [Boolean]
19
16
  def is_rails?
20
17
  defined?(Rails)
@@ -45,15 +42,13 @@ module Maximus
45
42
  end
46
43
  end
47
44
 
48
- # Look for a custom config in the app's config/ directory;
49
- # otherwise, use the built-in one.
50
- # @todo best practice that this inherits the @opts from the model it's being included in?
45
+ # Look for a file in the config directory
51
46
  #
52
- # @param filename [String]
53
- # @return [String] absolute path to the reporter file
54
- def check_default(filename)
55
- user_file = "#{@opts[:root_dir]}/config/#{filename}"
56
- File.exist?(user_file) ? user_file : File.join(File.dirname(__FILE__), "config/#{filename}")
47
+ # @since 0.1.0
48
+ # @param file [String] filename with extension to search for
49
+ # @return [String] path to default config file or file in user's directory
50
+ def check_default_config_path(file)
51
+ File.exist?(file) ? file : File.join(File.dirname(__FILE__), "config/#{file}")
57
52
  end
58
53
 
59
54
  # Grab the absolute path of the reporter file
@@ -61,7 +56,7 @@ module Maximus
61
56
  # @param filename [String]
62
57
  # @return [String] absolute path to the reporter file
63
58
  def reporter_path(filename)
64
- File.join(File.dirname(__FILE__),"reporter/#{filename}")
59
+ File.join(File.dirname(__FILE__), "reporter/#{filename}")
65
60
  end
66
61
 
67
62
  # Find all files that were linted by extension
@@ -96,6 +91,7 @@ module Maximus
96
91
 
97
92
  # Edit and save a YAML file
98
93
  #
94
+ # @param yaml_location [String] YAML absolute file path
99
95
  # @return [void]
100
96
  def edit_yaml(yaml_location, &block)
101
97
  d = YAML.load_file(yaml_location)
@@ -112,15 +108,6 @@ module Maximus
112
108
  STDIN.gets
113
109
  end
114
110
 
115
- # Defines base logger
116
- #
117
- # @return [Logger] @@log for logging use
118
- def mlog
119
- @@log ||= Logger.new(STDOUT)
120
- @@log.level ||= Logger::INFO
121
- @@log
122
- end
123
-
124
111
  # Convert the array from lines_added into spelled-out ranges
125
112
  # This is a GitControl helper but it's used in Lint
126
113
  # @see GitControl#lines_added