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,58 @@
1
+ scss_files: 'app/assets/stylesheets/'
2
+
3
+ # exclude: 'app/assets/stylesheets/admin/'
4
+
5
+ format: 'JSON'
6
+
7
+ linters:
8
+ Compass::*:
9
+ enabled: true
10
+ Compass::PropertyWithMixin:
11
+ ignore:
12
+ - 'inline-block'
13
+ - 'border-radius'
14
+ - 'box-shadow'
15
+ - 'opacity'
16
+ - 'text-shadow'
17
+ Indentation:
18
+ enabled: false
19
+ character: tab
20
+ severity: warning
21
+ width: 1
22
+ NameFormat:
23
+ convention: BEM
24
+ enabled: false
25
+ SelectorFormat:
26
+ enabled: false
27
+ DeclarationOrder:
28
+ enabled: false
29
+ EmptyLineBetweenBlocks:
30
+ enabled: false
31
+ NestingDepth:
32
+ max_depth: 6
33
+ severity: error
34
+ SelectorDepth:
35
+ max_depth: 5
36
+ severity: error
37
+ LeadingZero:
38
+ enabled: false
39
+ PlaceholderInExtend:
40
+ enabled: false
41
+ PropertySortOrder:
42
+ enabled: false
43
+ QualifyingElement:
44
+ enabled: true
45
+ allow_element_with_attribute: true
46
+ SpaceAfterPropertyColon:
47
+ enabled: false
48
+ UnnecessaryParentReference:
49
+ severity: error
50
+ VendorPrefixes:
51
+ exclude:
52
+ - '-webkit-tap-highlight-color'
53
+ - '-webkit-font-smoothing'
54
+ - '-moz-osx-font-smoothing'
55
+ - '-webkit-appearance'
56
+ - '-moz-placeholder'
57
+ - '-ms-input-placeholder'
58
+ - '-moz-pre-wrap'
@@ -0,0 +1,30 @@
1
+ {
2
+ "published": false,
3
+ "paths": false,
4
+ "stylesheets": false,
5
+ "styleElements": true,
6
+ "size": true,
7
+ "dataUriSize": true,
8
+ "ratioOfDataUriSize": true,
9
+ "gzippedSize": false,
10
+ "simplicity": true,
11
+ "rules": true,
12
+ "selectors": true,
13
+ "mostIdentifier": true,
14
+ "mostIdentifierSelector": true,
15
+ "lowestCohesion": true,
16
+ "lowestCohesionSelector": true,
17
+ "totalUniqueFontSizes": true,
18
+ "uniqueFontSize": true,
19
+ "totalUniqueColors": true,
20
+ "uniqueColor": true,
21
+ "idSelectors": true,
22
+ "universalSelectors": true,
23
+ "unqualifiedAttributeSelectors": true,
24
+ "javascriptSpecificSelectors": "[#\\.]js\\-",
25
+ "importantKeywords": true,
26
+ "floatProperties": true,
27
+ "mediaQueries": true,
28
+ "propertiesCount": 10,
29
+ "requestOptions": {}
30
+ }
@@ -0,0 +1,56 @@
1
+ #Headless browser option
2
+ browser:
3
+ phantomjs: "phantomjs"
4
+ # slimerjs: "slimerjs"
5
+ # phantomjs: "casperjs"
6
+
7
+ #If you want to have multiple snapping files, set the file name here
8
+ snap_file: "config/wraith/snap.js"
9
+ # snap_file: "config/wraith/nojs.js"
10
+
11
+ # Type the name of the directory that shots will be stored in
12
+ directory: 'maximus_wraith'
13
+ history_dir: 'maximus_wraith_history'
14
+
15
+ domains:
16
+ main: "http://localhost:3000"
17
+
18
+ #Type screen widths below, here are a couple of examples
19
+ screen_widths:
20
+ - 767
21
+ - 1024
22
+ - 1280
23
+
24
+ #Type page URL paths below
25
+ paths:
26
+ home: /
27
+
28
+ # Casper/component example config
29
+ # paths:
30
+ # home:
31
+ # path: /
32
+ # selector: '.site-brand'
33
+
34
+ #Amount of fuzz ImageMagick will use
35
+ fuzz: '20%'
36
+
37
+ #Set the filename of the spider file to use, if not specified it will fallback to spider.txt
38
+ # spider_file: bbc_co_uk_spider.txt
39
+
40
+ #Set the number of days to keep the site spider file
41
+ spider_days:
42
+ - 10
43
+
44
+ #A list of URLs to skip when spidering. Ruby regular expressions can be
45
+ #used, if prefixed with !ruby/regexp as defined in the YAML Cookbook
46
+ #http://www.yaml.org/YAML_for_ruby.html#regexps
47
+ #
48
+ # spider_skips:
49
+ # - /foo/bar.html # Matches /foo/bar.html explcitly
50
+ # - !ruby/regexp /^\/baz\// # Matches any URLs that start with /baz
51
+
52
+ #Choose how results are displayed, by default alphanumeric. Different screen widths are always grouped.
53
+ #alphanumeric - all paths (with, and without, a difference) are shown, sorted by path
54
+ #diffs_first - all paths (with, and without, a difference) are shown, sorted by difference size (largest first)
55
+ #diffs_only - only paths with a difference are shown, sorted by difference size (largest first)
56
+ #mode: diffs_first
@@ -0,0 +1,20 @@
1
+ var system = require('system');
2
+ var page = require('webpage').create();
3
+ var fs = require('fs');
4
+ var casper = require("casper").create();
5
+
6
+ var url = casper.cli.get(0);
7
+ var view_port_width = casper.cli.get(1);
8
+ var image_name = casper.cli.get(2);
9
+ var selector = casper.cli.get(3);
10
+
11
+ casper.start(url, function() {
12
+ this.viewport(view_port_width, 1500).then(function(){
13
+ this.wait(2000, function() {
14
+ this.captureSelector(image_name, selector);
15
+ console.log('Snapping ' + url + ' at width ' + view_port_width);
16
+ });
17
+ });
18
+ });
19
+
20
+ casper.run();
@@ -0,0 +1,85 @@
1
+ var system = require('system');
2
+ var page = require('webpage').create();
3
+ var fs = require('fs');
4
+
5
+ if (system.args.length === 3) {
6
+ console.log('Usage: snap.js <some URL> <view port width> <target image name>');
7
+ phantom.exit();
8
+ }
9
+
10
+ var url = system.args[1];
11
+ var image_name = system.args[3];
12
+ var view_port_width = system.args[2];
13
+ var current_requests = 0;
14
+ var last_request_timeout;
15
+ var final_timeout;
16
+
17
+
18
+ page.viewportSize = { width: view_port_width, height: 1500};
19
+ page.settings = { loadImages: true, javascriptEnabled: false };
20
+
21
+ // If you want to use additional phantomjs commands, place them here
22
+ page.settings.userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.17';
23
+
24
+ // You can place custom headers here, example below.
25
+ // page.customHeaders = {
26
+
27
+ // 'X-Candy-OVERRIDE': 'https://api.live.bbc.co.uk/'
28
+
29
+ // };
30
+
31
+ // If you want to set a cookie, just add your details below in the following way.
32
+
33
+ // phantom.addCookie({
34
+ // 'name': 'ckns_policy',
35
+ // 'value': '111',
36
+ // 'domain': '.bbc.co.uk'
37
+ // });
38
+ // phantom.addCookie({
39
+ // 'name': 'locserv',
40
+ // 'value': '1#l1#i=6691484:n=Oxford+Circus:h=e@w1#i=8:p=London@d1#1=l:2=e:3=e:4=2@n1#r=40',
41
+ // 'domain': '.bbc.co.uk'
42
+ // });
43
+
44
+ page.onResourceRequested = function(req) {
45
+ current_requests += 1;
46
+ };
47
+
48
+ page.onResourceReceived = function(res) {
49
+ if (res.stage === 'end') {
50
+ current_requests -= 1;
51
+ debounced_render();
52
+ }
53
+ };
54
+
55
+ page.open(url, function(status) {
56
+ if (status !== 'success') {
57
+ console.log('Error with page ' + url);
58
+ phantom.exit();
59
+ }
60
+ });
61
+
62
+
63
+ function debounced_render() {
64
+ clearTimeout(last_request_timeout);
65
+ clearTimeout(final_timeout);
66
+
67
+ // If there's no more ongoing resource requests, wait for 1 second before
68
+ // rendering, just in case the page kicks off another request
69
+ if (current_requests < 1) {
70
+ clearTimeout(final_timeout);
71
+ last_request_timeout = setTimeout(function() {
72
+ console.log('Snapping ' + url + ' at width ' + view_port_width);
73
+ page.render(image_name);
74
+ phantom.exit();
75
+ }, 1000);
76
+ }
77
+
78
+ // Sometimes, straggling requests never make it back, in which
79
+ // case, timeout after 5 seconds and render the page anyway
80
+ final_timeout = setTimeout(function() {
81
+ console.log('Snapping ' + url + ' at width ' + view_port_width);
82
+ page.render(image_name);
83
+ phantom.exit();
84
+ }, 5000);
85
+ }
@@ -0,0 +1,85 @@
1
+ var system = require('system');
2
+ var page = require('webpage').create();
3
+ var fs = require('fs');
4
+
5
+ if (system.args.length === 3) {
6
+ console.log('Usage: snap.js <some URL> <view port width> <target image name>');
7
+ phantom.exit();
8
+ }
9
+
10
+ var url = system.args[1];
11
+ var image_name = system.args[3];
12
+ var view_port_width = system.args[2];
13
+ var current_requests = 0;
14
+ var last_request_timeout;
15
+ var final_timeout;
16
+
17
+
18
+ page.viewportSize = { width: view_port_width, height: 1500};
19
+ page.settings = { loadImages: true, javascriptEnabled: true };
20
+
21
+ // If you want to use additional phantomjs commands, place them here
22
+ page.settings.userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.17';
23
+
24
+ // You can place custom headers here, example below.
25
+ // page.customHeaders = {
26
+
27
+ // 'X-Candy-OVERRIDE': 'https://api.live.bbc.co.uk/'
28
+
29
+ // };
30
+
31
+ // If you want to set a cookie, just add your details below in the following way.
32
+
33
+ // phantom.addCookie({
34
+ // 'name': 'ckns_policy',
35
+ // 'value': '111',
36
+ // 'domain': '.bbc.co.uk'
37
+ // });
38
+ // phantom.addCookie({
39
+ // 'name': 'locserv',
40
+ // 'value': '1#l1#i=6691484:n=Oxford+Circus:h=e@w1#i=8:p=London@d1#1=l:2=e:3=e:4=2@n1#r=40',
41
+ // 'domain': '.bbc.co.uk'
42
+ // });
43
+
44
+ page.onResourceRequested = function(req) {
45
+ current_requests += 1;
46
+ };
47
+
48
+ page.onResourceReceived = function(res) {
49
+ if (res.stage === 'end') {
50
+ current_requests -= 1;
51
+ debounced_render();
52
+ }
53
+ };
54
+
55
+ page.open(url, function(status) {
56
+ if (status !== 'success') {
57
+ console.log('Error with page ' + url);
58
+ phantom.exit();
59
+ }
60
+ });
61
+
62
+
63
+ function debounced_render() {
64
+ clearTimeout(last_request_timeout);
65
+ clearTimeout(final_timeout);
66
+
67
+ // If there's no more ongoing resource requests, wait for 1 second before
68
+ // rendering, just in case the page kicks off another request
69
+ if (current_requests < 1) {
70
+ clearTimeout(final_timeout);
71
+ last_request_timeout = setTimeout(function() {
72
+ console.log('Snapping ' + url + ' at width ' + view_port_width);
73
+ page.render(image_name);
74
+ phantom.exit();
75
+ }, 1000);
76
+ }
77
+
78
+ // Sometimes, straggling requests never make it back, in which
79
+ // case, timeout after 5 seconds and render the page anyway
80
+ final_timeout = setTimeout(function() {
81
+ console.log('Snapping ' + url + ' at width ' + view_port_width);
82
+ page.render(image_name);
83
+ phantom.exit();
84
+ }, 5000);
85
+ }
@@ -0,0 +1,3 @@
1
+ # Global application constants.
2
+ module Maximus
3
+ end
@@ -0,0 +1,255 @@
1
+ require 'git'
2
+ require 'active_support'
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'rainbow'
5
+ require 'rainbow/ext/string'
6
+
7
+ module Maximus
8
+ class GitControl
9
+
10
+ include Helper
11
+
12
+ def initialize(opts = {})
13
+ opts[:is_dev] ||= false
14
+ opts[:log] = Logger.new('log/maximus_git.log') if opts[:log].nil?
15
+ opts[:base_url] ||= 'http://localhost:3000'
16
+ opts[:port] ||= ''
17
+ opts[:root_dir] ||= root_dir
18
+ log = opts[:log] ? log : nil
19
+ @@log = mlog
20
+ @@is_dev = opts[:is_dev]
21
+ @opts = opts
22
+
23
+ @psuedo_commit = (!@opts[:commit].blank? && @opts[:commit] == 'working')
24
+ @g = Git.open(@opts[:root_dir], :log => log)
25
+ end
26
+
27
+ # Returns Hash of commit data
28
+ def commit_export(commitsha = sha)
29
+ ce_commit = vccommit(commitsha)
30
+ ce_diff = diff(ce_commit, @g.object('HEAD^'))
31
+ {
32
+ commitsha: commitsha,
33
+ branch: branch,
34
+ message: ce_commit.message,
35
+ remote_repo: remote,
36
+ git_author: ce_commit.author.name,
37
+ git_author_email: ce_commit.author.email,
38
+ diff: ce_diff
39
+ }
40
+ end
41
+
42
+ # Compare two commits and get line number ranges of changed patches
43
+ # Returns Hash grouped by file extension (defined in assoc) => { filename, changes: (changed line ranges) }
44
+ # Example: 'sha' => { rb: {filename: 'file.rb', changes: { ['0..4'], ['10..20'] } }}
45
+ def compare(sha1 = master_commit.sha, sha2 = sha)
46
+ diff_return = {}
47
+
48
+ if @opts[:commit]
49
+ sha1 = case @opts[:commit]
50
+ when 'master' then master_commit.sha
51
+ when 'last' then @g.object('HEAD^').sha
52
+ when 'working' then 'working'
53
+ else @opts[:commit]
54
+ end
55
+ end
56
+
57
+ # if working directory, just have a single item array
58
+ # the space here is important because git-lines checks for a second arg,
59
+ # and if one is present, it runs git diff without a commit
60
+ # or a comparison to a commit
61
+ git_diff = @psuedo_commit ? ['working directory'] : `git rev-list #{sha1}..#{sha2} --no-merges`.split("\n")
62
+
63
+ # Include the first sha because rev-list is doing a traversal
64
+ # So sha1 is never included
65
+ git_diff << sha1 unless @psuedo_commit
66
+
67
+ # Reverse so that we go in chronological order
68
+ git_diff.reverse.each do |git_sha|
69
+ new_lines = lines_added(git_sha)
70
+
71
+ # Grab all files in that commit and group them by extension
72
+ # If working copy, just give the diff names of the files changed
73
+ files = @psuedo_commit ? `git diff --name-only` : `git show --pretty="format:" --name-only #{git_sha}`
74
+ files = files.split("\n").group_by { |f| f.split('.').pop }
75
+
76
+ # Don't worry about files that we don't have a lint or a statistic for
77
+ flat_associations = associations.clone.flatten(2)
78
+ files.delete_if { |k,v| !flat_associations.include?(k) || k.nil? }
79
+
80
+ associations.each do |ext, related|
81
+ files[ext] ||= []
82
+ related.each do |child|
83
+ unless files[child].blank?
84
+ files[child].each do |c|
85
+ # hack to ignore deleted files
86
+ files[child] = new_lines[c].blank? ? [] : [ filename: "#{@opts[:root_dir]}/#{c}", changes: new_lines[c] ]
87
+ end
88
+ files[ext].concat(files[child])
89
+ files.delete(child)
90
+ end
91
+ end
92
+ end
93
+ files.delete_if { |k,v| v.blank? }
94
+ diff_return[git_sha.to_sym] = files
95
+ end
96
+ diff_return
97
+ end
98
+
99
+ # Run appropriate lint for every sha in commit history
100
+ # Creates new branch based on each sha, then deletes it
101
+ # Different from above method as it returns the entire lint, not just the lines relevant to commit
102
+ # Returns Hash with all data grouped by task
103
+ # Example: { 'sha': { lints: { scsslint: { files_inspec... }, statisti... } }, 'sha...' }
104
+ def lints_and_stats(lint_by_path = false, git_shas = compare)
105
+ return false if git_shas.blank?
106
+ base_branch = branch
107
+ git_output = {}
108
+ git_shas.each do |sha, exts|
109
+ # TODO - better way to silence git, in case there's a real error?
110
+ quietly { `git checkout #{sha} -b maximus_#{sha}` } unless @psuedo_commit
111
+ puts sha.to_s.color(:blue) if @@is_dev
112
+ git_output[sha.to_sym] = {
113
+ lints: {},
114
+ statistics: {}
115
+ }
116
+ lints = git_output[sha.to_sym][:lints]
117
+ statistics = git_output[sha.to_sym][:statistics]
118
+ lint_opts = {
119
+ is_dev: @@is_dev,
120
+ root_dir: @opts[:root_dir],
121
+ commit: !@opts[:commit].blank?
122
+ }
123
+ stat_opts = {
124
+ is_dev: @@is_dev,
125
+ base_url: @opts[:base_url],
126
+ port: @opts[:port],
127
+ root_dir: @opts[:root_dir]
128
+ }
129
+ # This is where everything goes down
130
+ exts.each do |ext, files|
131
+ # For relevant_lines data
132
+ lint_opts[:git_files] = files
133
+ lint_opts[:path] = lint_file_paths(files, ext) if lint_by_path
134
+ case ext
135
+ when :scss
136
+ lints[:scsslint] = Maximus::Scsslint.new(lint_opts).result
137
+
138
+ # Do not run statistics if called by rake task :compare
139
+ if lint_opts[:commit].blank?
140
+
141
+ # stylestat is singular here because model name in Rails is singular.
142
+ # But adding a .classify when it's converted to a model chops off the end s on 'phantomas',
143
+ # which breaks the model name. This could be a TODO
144
+ statistics[:stylestat] = Maximus::Stylestats.new(stat_opts).result
145
+
146
+ # TODO - double pipe here is best way to say, if it's already run, don't run again, right?
147
+ statistics[:phantomas] ||= Maximus::Phantomas.new(stat_opts).result
148
+ statistics[:wraith] = Maximus::Wraith.new(stat_opts).result
149
+ end
150
+ when :js
151
+ lints[:jshint] = Maximus::Jshint.new(lint_opts).result
152
+
153
+ # Do not run statistics if called by rake task :compare
154
+ if lint_opts[:commit].blank?
155
+
156
+ statistics[:phantomas] = Maximus::Phantomas.new(stat_opts).result
157
+
158
+ # TODO - double pipe here is best way to say, if it's already run, don't run again, right?
159
+ statistics[:wraith] ||= Maximus::Wraith.new(stat_opts).result
160
+ end
161
+ when :ruby
162
+ lints[:rubocop] = Maximus::Rubocop.new(lint_opts).result
163
+ lints[:railsbp] = Maximus::Railsbp.new(lint_opts).result
164
+ lints[:brakeman] = Maximus::Brakeman.new(lint_opts).result
165
+ when :rails
166
+ lints[:railsbp] ||= Maximus::Railsbp.new(lint_opts).result
167
+ end
168
+ end
169
+ # TODO - better way to silence git, in case there's a real error?
170
+ quietly {
171
+ @g.branch(base_branch).checkout
172
+ @g.branch("maximus_#{sha}").delete
173
+ } unless @psuedo_commit
174
+ end
175
+ git_output
176
+ end
177
+
178
+
179
+ protected
180
+
181
+ # Get list of file paths
182
+ # Returns String delimited by comma or space
183
+ def lint_file_paths(files, ext)
184
+ file_list = files.map { |f| f[:filename] }.compact
185
+ # Lints accept files differently
186
+ ext == :ruby ? file_list.join(' ') : file_list.join(',')
187
+ end
188
+
189
+ # Returns Array of ranges by lines added in a commit by file name
190
+ # {'filename' => ['0..10', '11..14']}
191
+ def lines_added(git_sha)
192
+ new_lines = {}
193
+ lines_added = `#{File.join(File.dirname(__FILE__), 'reporter/git-lines.sh')} #{git_sha}`.split("\n")
194
+ lines_added.each do |filename|
195
+ fsplit = filename.split(':')
196
+ # if file isn't already part of the array
197
+ new_lines[fsplit[0]] ||= []
198
+ new_lines[fsplit[0]] << fsplit[1] unless fsplit[1].nil?
199
+ # no repeats
200
+ new_lines[fsplit[0]].uniq!
201
+ end
202
+ new_lines.delete("/dev/null")
203
+ new_lines
204
+ end
205
+
206
+ # Get last commit on current branch
207
+ # Returns String
208
+ def sha
209
+ @g.object('HEAD').sha
210
+ end
211
+
212
+ # Get branch name
213
+ # Returns String
214
+ def branch
215
+ `env -i git rev-parse --abbrev-ref HEAD`.strip!
216
+ end
217
+
218
+ # Get last commit on the master branch
219
+ # Returns Git::Object
220
+ def master_commit
221
+ @g.branches[:master].gcommit
222
+ end
223
+
224
+ # Store last commit as Ruby Git::Object
225
+ # Returns Git::Object
226
+ def vccommit(commitsha = sha)
227
+ @g.gcommit(commitsha)
228
+ end
229
+
230
+ # Get general stats of commit on HEAD versus last commit on master branch
231
+ # Roadmap - include lines_added in this method's output
232
+ # Returns Git::Diff
233
+ def diff(new_commit = vccommit, old_commit = master_commit)
234
+ @g.diff(new_commit, old_commit).stats
235
+ end
236
+
237
+ # Get remote URL
238
+ # Returns String or nil if remotes is blank
239
+ def remote
240
+ @g.remotes.first.url unless @g.remotes.blank?
241
+ end
242
+
243
+ # Define associations to linters based on file extension
244
+ # Returns Hash of linters and extension arrays
245
+ def associations
246
+ {
247
+ scss: ['scss', 'sass'],
248
+ js: ['js'],
249
+ ruby: ['rb', 'Gemfile', 'lock', 'yml', 'Rakefile', 'ru', 'rdoc'],
250
+ rails: ['slim', 'haml']
251
+ }
252
+ end
253
+
254
+ end
255
+ end