maximus 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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