maximus 0.1.0

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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +66 -0
  6. data/Rakefile +2 -0
  7. data/bin/maximus +15 -0
  8. data/lib/maximus.rb +25 -0
  9. data/lib/maximus/cli.rb +106 -0
  10. data/lib/maximus/config/.jshintignore +2 -0
  11. data/lib/maximus/config/jshint.json +9 -0
  12. data/lib/maximus/config/phantomas.json +4 -0
  13. data/lib/maximus/config/phantomas_urls.yaml +1 -0
  14. data/lib/maximus/config/rubocop.yml +1007 -0
  15. data/lib/maximus/config/scsslint.yml +58 -0
  16. data/lib/maximus/config/stylestats.json +30 -0
  17. data/lib/maximus/config/wraith.yaml +56 -0
  18. data/lib/maximus/config/wraith/casper.js +20 -0
  19. data/lib/maximus/config/wraith/nojs.js +85 -0
  20. data/lib/maximus/config/wraith/snap.js +85 -0
  21. data/lib/maximus/constants.rb +3 -0
  22. data/lib/maximus/git_control.rb +255 -0
  23. data/lib/maximus/helper.rb +137 -0
  24. data/lib/maximus/lint.rb +201 -0
  25. data/lib/maximus/lints/brakeman.rb +61 -0
  26. data/lib/maximus/lints/jshint.rb +20 -0
  27. data/lib/maximus/lints/railsbp.rb +51 -0
  28. data/lib/maximus/lints/rubocop.rb +18 -0
  29. data/lib/maximus/lints/scsslint.rb +17 -0
  30. data/lib/maximus/rake_tasks.rb +13 -0
  31. data/lib/maximus/reporter/git-lines.sh +57 -0
  32. data/lib/maximus/reporter/jshint.js +28 -0
  33. data/lib/maximus/reporter/rubocop.rb +49 -0
  34. data/lib/maximus/statistic.rb +65 -0
  35. data/lib/maximus/statistics/phantomas.rb +32 -0
  36. data/lib/maximus/statistics/stylestats.rb +111 -0
  37. data/lib/maximus/statistics/wraith.rb +88 -0
  38. data/lib/maximus/tasks/be.rake +35 -0
  39. data/lib/maximus/tasks/fe.rake +30 -0
  40. data/lib/maximus/tasks/maximus.rake +39 -0
  41. data/lib/maximus/tasks/statistic.rake +26 -0
  42. data/lib/maximus/version.rb +3 -0
  43. data/maximus.gemspec +32 -0
  44. data/roadmap.md +17 -0
  45. metadata +243 -0
@@ -0,0 +1,18 @@
1
+ module Maximus
2
+ class Rubocop < Maximus::Lint
3
+
4
+ # RuboCop
5
+ def result
6
+ @task = 'rubocop'
7
+ @path ||= @@is_rails ? "#{@opts[:root_dir]}/app" : "#{@opts[:root_dir]}/*.rb"
8
+
9
+ return unless path_exists(@path)
10
+
11
+ rubo = `rubocop #{@path} --require #{reporter_path('rubocop')} --config #{check_default('rubocop.yml')} --format RuboCop::Formatter::MaximusRuboFormatter #{'-R' if @@is_rails}`
12
+
13
+ @output[:files_inspected] ||= files_inspected('rb', ' ')
14
+ refine rubo
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ module Maximus
2
+ class Scsslint < Maximus::Lint
3
+
4
+ # SCSS-Lint
5
+ def result
6
+ @task = 'scsslint'
7
+ @path ||= @@is_rails ? "#{@opts[:root_dir]}/app/assets/stylesheets" : "#{@opts[:root_dir]}/source/assets/stylesheets"
8
+
9
+ return unless path_exists(@path)
10
+
11
+ scss = `scss-lint #{@path} -c #{check_default('scsslint.yml')} --format=JSON`
12
+ @output[:files_inspected] ||= files_inspected('scss')
13
+ refine scss
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ require 'rake'
2
+
3
+ if defined?(Rails)
4
+ module Maximus
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ Dir[File.join(File.dirname(__FILE__),'tasks/*.rake')].each { |f| load f }
8
+ end
9
+ end
10
+ end
11
+ else
12
+ Dir[File.join(File.dirname(__FILE__),'tasks/*.rake')].each { |f| load f }
13
+ end
@@ -0,0 +1,57 @@
1
+ #!/bin/bash
2
+
3
+ # http://stackoverflow.com/questions/8259851/using-git-diff-how-can-i-get-added-and-modified-lines-numbers
4
+ # TODO - this function is tricky because I don't fully understand regex in BASH. Also, getting line hunks from git is tricky. This will likely need to be refactored
5
+ function lines-added(){
6
+ local path=
7
+ local line=
8
+ local start=
9
+ local finish=
10
+ while read; do
11
+ esc=$'\033'
12
+ if [[ $REPLY =~ ---\ (a/)?.* ]]; then
13
+ continue
14
+ elif [[ $REPLY =~ \+\+\+\ (b/)?([^[:blank:]$esc]+).* ]]; then
15
+ path=${BASH_REMATCH[2]}
16
+ elif [[ $REPLY =~ @@\ -[0-9]+,([0-9]+)?\ \+([0-9]+)?,([0-9]+)?\ @@.* ]]; then
17
+ if [[ ${BASH_REMATCH[2]} = ${BASH_REMATCH[3]} ]]; then
18
+ line="0..0"
19
+ elif [[ ${BASH_REMATCH[2]} = $((${BASH_REMATCH[2]} + ${BASH_REMATCH[3]})) ]]; then
20
+ line="$((${BASH_REMATCH[2]} + ${BASH_REMATCH[3]} - ${BASH_REMATCH[1]}))..$((${BASH_REMATCH[2]} + ${BASH_REMATCH[3]} - ${BASH_REMATCH[1]}))"
21
+ else
22
+ line="${BASH_REMATCH[2]}..$((${BASH_REMATCH[2]} + ${BASH_REMATCH[3]}))"
23
+ fi
24
+ elif [[ $REPLY =~ @@\ -([0-9])?,([0-9]+)?\ \+([0-9]+)?\ @@.* ]]; then
25
+ line="${BASH_REMATCH[3]}..${BASH_REMATCH[3]}"
26
+ elif [[ $REPLY =~ @@\ -([0-9]+)?,\ \+([0-9]+)?\ @@.* ]]; then
27
+ line="0..${BASH_REMATCH[2]}"
28
+ elif [[ $REPLY =~ ^($esc\[[0-9;]+m)*([\ +-]) ]]; then
29
+ echo "$path:$line"
30
+ fi
31
+ done
32
+ }
33
+ function diff-lines(){
34
+ local path=
35
+ local line=
36
+ while read; do
37
+ esc=$'\033'
38
+ if [[ $REPLY =~ ---\ (a/)?.* ]]; then
39
+ continue
40
+ elif [[ $REPLY =~ \+\+\+\ (b/)?([^[:blank:]$esc]+).* ]]; then
41
+ path=${BASH_REMATCH[2]}
42
+ elif [[ $REPLY =~ @@\ -[0-9]+(,[0-9]+)?\ \+([0-9]+)(,[0-9]+)?\ @@.* ]]; then
43
+ line=${BASH_REMATCH[2]}
44
+ elif [[ $REPLY =~ ^($esc\[[0-9;]+m)*([\ +-]) ]]; then
45
+ echo "$path:$line:$REPLY"
46
+ if [[ ${BASH_REMATCH[2]} != - ]]; then
47
+ ((line++))
48
+ fi
49
+ fi
50
+ done
51
+ }
52
+
53
+ if [ -z "$2" ]; then
54
+ git diff $1^ $1 --unified=0 | lines-added
55
+ else
56
+ git diff --unified=0 | lines-added
57
+ fi
@@ -0,0 +1,28 @@
1
+ /* jshint node: true */
2
+
3
+ "use strict";
4
+
5
+ module.exports = {
6
+ reporter: function (res) {
7
+ var str = {};
8
+ var files = {};
9
+ res.forEach(function (r) {
10
+ var err = r.error;
11
+ var reform = {};
12
+ if (!files[r.file]) {
13
+ files[r.file] = [];
14
+ }
15
+ reform.linter = err.code;
16
+ reform.severity = err.code.indexOf('W') > - 1 ? 'warning' : 'error';
17
+ reform.reason = err.reason;
18
+ reform.line = err.line;
19
+ reform.column = err.character;
20
+ files[r.file].push(reform);
21
+ });
22
+
23
+ if (res.length) {
24
+ str = files;
25
+ process.stdout.write(JSON.stringify(str));
26
+ }
27
+ }
28
+ };
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'json'
4
+ require 'pathname'
5
+ require 'rubocop'
6
+
7
+ RuboCop = Rubocop if defined?(Rubocop) && ! defined?(RuboCop)
8
+
9
+ module RuboCop
10
+ module Formatter
11
+ # This formatter formats the report data in JSON
12
+ # Makes it consistent with output of other Maximus linters
13
+ class MaximusRuboFormatter < BaseFormatter
14
+ include PathUtil
15
+
16
+ attr_reader :output_hash
17
+
18
+ def initialize(output)
19
+ super
20
+ @output_hash = {}
21
+ end
22
+
23
+ def started(target_files)
24
+ end
25
+
26
+ def file_finished(file, offenses)
27
+ unless offenses.empty?
28
+ @output_hash[relative_path(file).to_sym] = {}
29
+ @output_hash[relative_path(file).to_sym] = offenses.map { |o| hash_for_offense(o) }
30
+ end
31
+ end
32
+
33
+ def finished(inspected_files)
34
+ output.write @output_hash.to_json
35
+ end
36
+
37
+ def hash_for_offense(offense)
38
+ {
39
+ severity: offense.severity.name,
40
+ reason: offense.message,
41
+ linter: offense.cop_name,
42
+ line: offense.line,
43
+ column: offense.real_column
44
+ }
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,65 @@
1
+
2
+ module Maximus
3
+ class Statistic
4
+ attr_accessor :output
5
+
6
+ include Helper
7
+
8
+ # opts - Lint options (default: {})
9
+ # :is_dev - wether or not this is being called by a rake task
10
+ # :root_dir - absolute path to working directory (optional)
11
+ # :port - 4-digit port number (optional)
12
+ # :base_url - standard domain with http:// (default: http://localhost:3000) (optional)
13
+ # :path - default set in methods (optional)
14
+ # All statistics should have the following:
15
+ # def result method to handle the actual parsing
16
+ # TODO - should this be def output to be more consistent?
17
+ # Didn't want to trip over the instance variable @output
18
+ def initialize(opts = {})
19
+ opts[:is_dev] ||= false
20
+ opts[:root_dir] ||= root_dir
21
+ opts[:port] ||= ''
22
+ opts[:base_url] ||= 'http://localhost:3000'
23
+
24
+ @@log ||= mlog
25
+ @@is_rails ||= is_rails?
26
+ @@is_dev = opts[:is_dev]
27
+ @path = opts[:path]
28
+ @opts = opts
29
+
30
+ @output = {}
31
+ # This is different from lints
32
+ # A new stat is run per file or URL, so they should be stored in a child
33
+ # A lint just has one execution, so it's data can be stored directly in @output
34
+ @output[:statistics] = {}
35
+ end
36
+
37
+
38
+ protected
39
+
40
+ # Organize stat output on the @output variable
41
+ # Adds @output[:statistics][:filepath] with all statistic data
42
+ def refine_stats(stats_cli, file_path)
43
+
44
+ # Stop right there unless you mean business
45
+ return puts stats_cli if @@is_dev
46
+
47
+ # JSON.parse will throw an abortive error if it's given an empty string
48
+ return false if stats_cli.blank?
49
+
50
+ stats = JSON.parse(stats_cli)
51
+ @output[:statistics][file_path.to_sym] ||= {}
52
+
53
+ # TODO - is there a better way to do this?
54
+ fp = @output[:statistics][file_path.to_s.to_sym]
55
+
56
+ # TODO - Can I do like a self << thing here?
57
+ stats.each do |stat, value|
58
+ fp[stat.to_sym] = value
59
+ end
60
+
61
+ @output
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,32 @@
1
+ module Maximus
2
+ class Phantomas < Maximus::Statistic
3
+
4
+ # path can be array or string of URLS. Include http://
5
+ # By default, checks homepage
6
+ def result
7
+
8
+ node_module_exists('phantomjs', 'brew install')
9
+ node_module_exists('phantomas')
10
+
11
+ @path ||= YAML.load_file(check_default('phantomas_urls.yaml'))
12
+ # Phantomas doesn't actually skip the skip-modules defined in the config BUT here's to hoping for future support
13
+ phantomas_cli = "phantomas --config=#{check_default('phantomas.json')} "
14
+ phantomas_cli += @@is_dev ? '--colors' : '--reporter=json:no-skip'
15
+ phantomas_cli += " --proxy=#{@opts[:base_url]}:#{@opts[:port]}" unless @opts[:port].blank?
16
+ @path.is_a?(Hash) ? @path.each { |label, url| phantomas_by_url(url, phantomas_cli) } : phantomas_by_url(@path, phantomas_cli)
17
+ @output
18
+ end
19
+
20
+
21
+ private
22
+
23
+ # Organize stat output on the @output variable
24
+ # Adds @output[:statistics][:filepath] with all statistic data
25
+ def phantomas_by_url(url, phantomas_cli)
26
+ puts "Phantomas on #{@opts[:base_url] + url}".color(:green)
27
+ phantomas = `#{phantomas_cli} #{@opts[:base_url] + url}`
28
+ refine_stats(phantomas, url)
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,111 @@
1
+ module Maximus
2
+ class Stylestats < Maximus::Statistic
3
+
4
+ # @path array preferrably absolute paths, but relative should work
5
+ # If stylestatting one file, pass that as an array, i.e. ['/absolute/to/public/assets/application.css']
6
+ def result
7
+
8
+ node_module_exists('stylestats')
9
+ @path ||= @@is_rails ? "#{@opts[:root_dir]}/public/assets/**/*.css" : "#{@opts[:root_dir]}/**/*.css"
10
+
11
+ css_files = @path.is_a?(Array) ? @path : find_css_files
12
+
13
+ css_files.each do |file|
14
+
15
+ # For Rails, we only want the name of the compiled asset, because we know it'll live in public/assets.
16
+ # If this isn't Rails, sure, give me the full path because the directory structure is likely unique
17
+ pretty_name = @@is_rails ? file.split('/').pop.gsub(/(-{1}[a-z0-9]{32}*\.{1}){1}/, '.') : file
18
+
19
+ puts "#{'stylestats'.color(:green)}: #{pretty_name}\n\n"
20
+
21
+ # include JSON formatter unless we're in dev
22
+ stylestats = `stylestats #{file} --config=#{check_default('stylestats.json')} #{'--type=json' unless @@is_dev}`
23
+
24
+ refine_stats(stylestats, pretty_name)
25
+
26
+ File.delete(file)
27
+ end
28
+
29
+ if @@is_rails
30
+ # TODO - I'd rather Rake::Task but it's not working in different directories
31
+ Dir.chdir(@opts[:root_dir]) do
32
+ if @@is_dev
33
+ # TODO - review that this may not be best practice, but it's really noisy in the console
34
+ quietly { `rake assets:clobber` }
35
+ else
36
+ `rake assets:clobber`
37
+ end
38
+ end
39
+ end
40
+
41
+ @output
42
+
43
+ end
44
+
45
+
46
+ private
47
+
48
+ # Find all CSS files
49
+ # Will compile using sprockets if Rails
50
+ # Will compile using built-in Sass engine otherwise
51
+ # Compass friendly
52
+ # Returns Array of CSS files
53
+ def find_css_files
54
+ searched_files = []
55
+
56
+ if @@is_rails
57
+ # Only load tasks if we're not running a rake task
58
+ Rails.application.load_tasks unless @@is_dev
59
+
60
+ puts "\n"
61
+ puts 'Compiling assets for stylestats...'.color(:blue)
62
+
63
+ # TODO - I'd rather Rake::Task but it's not working in different directories
64
+ Dir.chdir(@opts[:root_dir]) do
65
+ if @@is_dev
66
+ # TODO - review that this may not be best practice, but it's really noisy in the console
67
+ quietly { `rake assets:precompile` }
68
+ else
69
+ `rake assets:precompile`
70
+ end
71
+ end
72
+
73
+ Dir.glob(@path).select { |f| File.file? f }.each do |file|
74
+ searched_files << file
75
+ end
76
+
77
+ else
78
+
79
+ # Load Compass paths if it exists
80
+ if Gem::Specification::find_all_by_name('compass').any?
81
+ require 'compass'
82
+ Compass.sass_engine_options[:load_paths].each do |path|
83
+ Sass.load_paths << path
84
+ end
85
+ end
86
+
87
+ # Shouldn't need to load paths anymore, but in case this doesn't work
88
+ # as it should
89
+ # Dir.glob(@path).select { |d| File.directory? d}.each do |directory|
90
+ # Sass.load_paths << directory
91
+ # end
92
+
93
+ @path += ".scss"
94
+
95
+ Dir[@path].select { |f| File.file? f }.each do |file|
96
+ # TODO - don't compile file if it starts with an underscore
97
+ scss_file = File.open(file, 'rb') { |f| f.read }
98
+
99
+ output_file = File.open( file.split('.').reverse.drop(1).reverse.join('.'), "w" )
100
+ output_file << Sass::Engine.new(scss_file, { syntax: :scss, quiet: true, style: :compressed }).render
101
+ output_file.close
102
+
103
+ searched_files << output_file.path
104
+
105
+ end
106
+ end
107
+ searched_files
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,88 @@
1
+ module Maximus
2
+ class Wraith < Maximus::Statistic
3
+
4
+ # By default checks homepage
5
+ # Requires config to be in config/wraith/history.yaml
6
+ # Adds a new config/wraith/history.yaml if not present
7
+ # Path should be an Array defined as [{ label: url }]
8
+ # Returns Hash as defined in the wraith_parse method
9
+ def result
10
+
11
+ node_module_exists('phantomjs', 'brew install')
12
+ @root_config = "#{@opts[:root_dir]}/config/wraith"
13
+ wraith_exists = File.directory?(@root_config)
14
+ @wraith_config_file = "#{@opts[:root_dir]}/config/wraith.yaml"
15
+
16
+ puts 'Starting visual regression tests with wraith...'.color(:blue)
17
+
18
+ # Copy wraith config and run the initial baseline
19
+ # Or, if the config is already there, just run wraith latest
20
+ unless wraith_exists
21
+
22
+ FileUtils.copy_entry(File.join(File.dirname(__FILE__), "../config/wraith"), @root_config)
23
+ FileUtils.cp(File.join(File.dirname(__FILE__), "../config/wraith.yaml"), @wraith_config_file)
24
+ wraith_yaml_reset
25
+ puts `wraith history #{@wraith_config_file}`
26
+
27
+ else
28
+
29
+ wraith_yaml_reset
30
+
31
+ # If the paths have been updated, call a timeout and run history again
32
+ # TODO - this doesn't work very well. It puts the new shots in the history folder,
33
+ # even with absolute paths. Could be a bug in wraith
34
+ YAML.load_file(@wraith_config_file)['paths'].each do |label, url|
35
+ edit_yaml(@wraith_config_file) do |file|
36
+ unless File.directory?("#{@opts[:root_dir]}/maximus_wraith_history/#{label}")
37
+ puts `wraith history #{@wraith_config_file}`
38
+ break
39
+ end
40
+ end
41
+ end
42
+
43
+ # Look for changes if it's not the first time
44
+ puts `wraith latest #{@wraith_config_file}`
45
+
46
+ end
47
+ wraith_parse
48
+ end
49
+
50
+
51
+ private
52
+
53
+ # Get a diff percentage of all changes by label and screensize
54
+ # { path: { percent_changed: [{ size: percent_diff }] } }
55
+ # Example {:statistics=>{:/=>{:percent_changed=>[{1024=>0.0}, {767=>0.0}, {1024=>0.0}, {767=>0.0}, {1024=>0.0}, {767=>0.0}, {1024=>0.0}, {767=>0.0}] } }}
56
+ # Returns Hash
57
+ def wraith_parse(wraith_config_file = @wraith_config_file)
58
+ paths = YAML.load_file(wraith_config_file)['paths']
59
+ Dir.glob("#{@opts[:root_dir]}/maximus_wraith/**/*.txt").select { |f| File.file? f }.each do |file|
60
+ file_object = File.open(file, 'rb')
61
+ orig_label = File.dirname(file).split('/').last
62
+ label = paths[orig_label]
63
+ @output[:statistics][label.to_sym] ||= {}
64
+ @output[:statistics][label.to_sym][:name] = orig_label
65
+ @output[:statistics][label.to_sym][:percent_changed] ||= []
66
+ @output[:statistics][label.to_sym][:percent_changed] << { File.basename(file).split('_')[0].to_i => file_object.read.to_f }
67
+ file_object.close
68
+ end
69
+ @output
70
+ end
71
+
72
+ # Update the root domain (docker ports and addresses may change) and set paths as defined in @path
73
+ def wraith_yaml_reset(wraith_config_file = @wraith_config_file)
74
+ edit_yaml(wraith_config_file) do |file|
75
+ unless @@is_dev
76
+ file['snap_file'] = "#{@root_config}/snap.js"
77
+ file['directory'] = "#{@opts[:root_dir]}/maximus_wraith"
78
+ file['history_dir'] = "#{@opts[:root_dir]}/maximus_wraith_history"
79
+ end
80
+ # .to_s is for consistency in the yaml, but could likely be removed without causing an error
81
+ fresh_domain = @opts[:port].blank? ? @opts[:base_url].to_s : "#{@opts[:base_url]}:#{@opts[:port]}"
82
+ file['domains']['main'] = fresh_domain
83
+ @path.each { |label, url| file['paths'][label] = url } if @path.is_a?(Hash)
84
+ end
85
+ end
86
+
87
+ end
88
+ end