lintron 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 395602bdec689110f8bdb9e08643197014a7db36
4
+ data.tar.gz: 9fb2bc909a2d0f52545538894f578a9ebf7a3863
5
+ SHA512:
6
+ metadata.gz: f853f0c7d1a2e9e0ceda044944dc49ee538e4ef95c3dfacf47ac8122bd3d471edccdfa45d4e25b45d71e3ccd0615e97ff2ff6a28f267489bdc7c83d0dd1b9ced
7
+ data.tar.gz: 1d4ebf5fa5e6617be217fe13f3072bd8eceed553ba944de6073afe5c1010ee7f18572ad71169d4d5ca6a0411b453d81088d2642ca6fa67cbcd4eccf3408ac8bf
@@ -0,0 +1,15 @@
1
+ # Base class for Github API files and mock files that perform basic operations
2
+ # on the blob and path
3
+ class FileLike
4
+ def extname
5
+ File.extname(path).gsub(/^\./, '') # without leading .
6
+ end
7
+
8
+ def basename(ext = nil)
9
+ File.basename path, ext
10
+ end
11
+
12
+ def first_line_of_patch
13
+ patch.changed_lines.first.try(:number)
14
+ end
15
+ end
@@ -0,0 +1,84 @@
1
+ require 'git_diff_parser'
2
+ require_relative './stub_file'
3
+ require_relative './patch'
4
+
5
+ # An object that is similar enough to PullRequest to be linted. It can be
6
+ # constructed from the CLI tool (compares local working tree to base_branch) or
7
+ # from the JSON payload that the CLI tool sends to the API
8
+ class LocalPrAlike
9
+ attr_accessor :files
10
+
11
+ def self.from_json(json)
12
+ LocalPrAlike.new.tap do |pr|
13
+ pr.files = json.map do |file_json|
14
+ StubFile.from_json(file_json)
15
+ end
16
+ end
17
+ end
18
+
19
+ def self.from_branch(base_branch)
20
+ LocalPrAlike.new.tap do |pr|
21
+ pr.files = pr.stubs_for_existing(base_branch) + pr.stubs_for_new
22
+ end
23
+ end
24
+
25
+ def stubs_for_existing(base_branch)
26
+ patches = GitDiffParser.parse(raw_diff(base_branch))
27
+
28
+ patches.map do |patch|
29
+ StubFile.new(
30
+ path: patch.file,
31
+ blob: File.read(patch.file),
32
+ patch: Patch.new(patch.body),
33
+ )
34
+ end
35
+ end
36
+
37
+ def raw_diff(base_branch)
38
+ diff = `git diff --no-ext-diff #{base_branch} .`
39
+ unless $CHILD_STATUS.success?
40
+ raise(
41
+ 'git diff failed. You may need to set a default branch in .linty_rc.',
42
+ )
43
+ end
44
+ diff
45
+ end
46
+
47
+ def stubs_for_new
48
+ untracked_names = `git ls-files --others --exclude-standard`.split("\n")
49
+ untracked_names.map do |name|
50
+ body = File.read(name)
51
+ StubFile.new(
52
+ path: name,
53
+ blob: body,
54
+ patch: Patch.from_file_body(body),
55
+ )
56
+ end
57
+ end
58
+
59
+ def changed_files
60
+ files
61
+ end
62
+
63
+ def expected_url_from_path(path)
64
+ path
65
+ end
66
+
67
+ def as_json(_opts = {})
68
+ @files.map(&:as_json)
69
+ end
70
+
71
+ def to_json(_opts = {})
72
+ {
73
+ files: as_json.select do |file_json|
74
+ begin
75
+ JSON.dump(file_json)
76
+ file_json
77
+ rescue JSON::GeneratorError
78
+ puts "Ignoring #{file_json[:path]}, possible binary"
79
+ nil
80
+ end
81
+ end,
82
+ }.to_json
83
+ end
84
+ end
@@ -0,0 +1,51 @@
1
+ class Patch
2
+ RANGE_INFORMATION_LINE = /^@@ .+\+(?<line_number>\d+),/
3
+ MODIFIED_LINE = /^\+(?!\+|\+)/
4
+ NOT_REMOVED_LINE = /^[^-]/
5
+
6
+ attr_reader :body
7
+
8
+ def self.from_file_body(source)
9
+ lines = source.lines
10
+ header = "@@ -0,0 +1,#{lines.count} @@\n"
11
+ body = header + lines.map { |line| "+#{line}"}.join
12
+ new(body)
13
+ end
14
+
15
+ def initialize(body)
16
+ @body = body || ''
17
+ end
18
+
19
+ def changed_lines
20
+ line_number = 0
21
+
22
+ lines.each_with_index.inject([]) do |lines, (content, patch_position)|
23
+ case content
24
+ when RANGE_INFORMATION_LINE
25
+ line_number = Regexp.last_match[:line_number].to_i
26
+ when MODIFIED_LINE
27
+ line = Line.new(
28
+ content: content,
29
+ number: line_number,
30
+ patch_position: patch_position
31
+ )
32
+ lines << line
33
+ line_number += 1
34
+ when NOT_REMOVED_LINE
35
+ line_number += 1
36
+ end
37
+
38
+ lines
39
+ end
40
+ end
41
+
42
+ def position_for_line_number(line)
43
+ changed_lines.find { |patch_line| patch_line.number == line }.patch_position
44
+ end
45
+
46
+ private
47
+
48
+ def lines
49
+ @body.lines
50
+ end
51
+ end
@@ -0,0 +1,40 @@
1
+ require_relative './file_like'
2
+
3
+ # A simple object we can use in place of a GithubFile as a test mock or for
4
+ # debugging
5
+ class StubFile < FileLike
6
+ attr_accessor :path, :blob
7
+
8
+ def self.from_json(json)
9
+ json = json.symbolize_keys
10
+ StubFile.new(
11
+ path: json[:path],
12
+ blob: json[:blob],
13
+ patch: Patch.new(json[:patch]),
14
+ )
15
+ end
16
+
17
+ def initialize(path:, blob:, patch: nil)
18
+ @path = path
19
+ @blob = blob
20
+ @patch = patch
21
+ end
22
+
23
+ def patch_from_blob
24
+ lines = @blob.lines
25
+ "@@ -0,0 +1,#{lines.length}\n" +
26
+ lines.map { |l| "+#{l}" }.join('')
27
+ end
28
+
29
+ def patch
30
+ @patch || Patch.new(patch_from_blob)
31
+ end
32
+
33
+ def as_json(_opts = {})
34
+ {
35
+ path: @path,
36
+ blob: @blob,
37
+ patch: patch.body,
38
+ }
39
+ end
40
+ end
data/bin/lintron ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require_relative '../lib/lintron'
4
+
5
+ begin
6
+ cli = Lintron::CLI.new
7
+ cli.go
8
+ end
data/lib/lintron.rb ADDED
@@ -0,0 +1,7 @@
1
+ require_relative 'lintron/api'
2
+ require_relative 'lintron/cli'
3
+ require_relative 'lintron/terminal_reporter'
4
+
5
+ # Module for the local lintron gem
6
+ module Lintron
7
+ end
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'httparty'
3
+ require 'byebug'
4
+
5
+ module Lintron
6
+ # Makes requests to the lintron local lint API
7
+ class API
8
+ def initialize(base_url)
9
+ @base_url = base_url
10
+ end
11
+
12
+ def violations(pr)
13
+ response = post_lint_request(pr)
14
+ violations =
15
+ JSON
16
+ .parse(response.body)
17
+ .map { |json| Lintron::ViolationLine.new(json) }
18
+
19
+ violations.sort_by(&:file_and_line)
20
+ rescue JSON::ParserError
21
+ puts 'Error occurred while parsing response from Lintron'.colorize(:red)
22
+ puts 'Raw response body: '
23
+ puts response.body
24
+ end
25
+
26
+ def post_lint_request(pr)
27
+ HTTParty.post(URI.join(@base_url, 'local_lints'), request_params(pr))
28
+ end
29
+
30
+ def request_params(pr)
31
+ {
32
+ body: pr.to_json,
33
+ headers: {
34
+ 'Content-Type' => 'application/json',
35
+ 'Accept' => 'application/json',
36
+ },
37
+ }
38
+ end
39
+ end
40
+
41
+ # Represents one line of the lint results. Exists so that
42
+ # (1) We can use dot syntax to access members instead of []
43
+ # (2) I can define file_and_line which is used for display and sorting
44
+ class ViolationLine < OpenStruct
45
+ def file_and_line(padTo = 0)
46
+ file_and_line = "#{path}:#{format '%03i', line} "
47
+
48
+ if padTo > file_and_line.length
49
+ file_and_line += ' ' * (padTo - file_and_line.length)
50
+ end
51
+
52
+ file_and_line
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,101 @@
1
+ require 'optparse'
2
+ require 'rubygems'
3
+ require 'active_support/all'
4
+ require 'filewatcher'
5
+ require_relative '../../app/models/local_pr_alike'
6
+
7
+ module Lintron
8
+ # Handles setting up flags for CLI runs based on defaults, linty_rc, and
9
+ # command line arguments
10
+ class CLI
11
+ def initialize
12
+ @options = {}
13
+ OptionParser.new do |opts|
14
+ opts.banner = 'Usage: linty [options]'
15
+
16
+ opts.on('--watch', 'Watch for changes') do |v|
17
+ @options[:watch] = v
18
+ end
19
+ end.parse!
20
+ end
21
+
22
+ def go
23
+ if config[:watch]
24
+ go_watch
25
+ else
26
+ go_once
27
+ end
28
+ end
29
+
30
+ def go_watch
31
+ system('clear')
32
+ go_once
33
+
34
+ watcher.watch do |filename|
35
+ system('clear')
36
+ puts "Re-linting because of #{filename}\n\n"
37
+ go_once
38
+ end
39
+ end
40
+
41
+ def watcher
42
+ FileWatcher.new(['*', '**/*'], exclude: config[:watch_exclude])
43
+ end
44
+
45
+ def go_once
46
+ violations = Lintron::API.new(config[:base_url]).violations(pr)
47
+ puts Lintron::TerminalReporter.new.format_violations(violations)
48
+ end
49
+
50
+ def pr
51
+ LocalPrAlike.from_branch(base_branch)
52
+ end
53
+
54
+ def base_branch
55
+ config[:base_branch]
56
+ end
57
+
58
+ def config
59
+ defaults
60
+ .merge(validated_config_from_file)
61
+ .merge(@options)
62
+ .merge(
63
+ {
64
+ base_branch: ARGV[0],
65
+ }.compact,
66
+ )
67
+ end
68
+
69
+ def defaults
70
+ {
71
+ base_branch: 'origin/develop',
72
+ watch_exclude: [
73
+ '**/*.log',
74
+ 'tmp/**/*',
75
+ ],
76
+ }
77
+ end
78
+
79
+ def validated_config_from_file
80
+ config = config_from_file
81
+
82
+ unless config.key?(:base_url)
83
+ raise('.linty_rc missing required key: base_url')
84
+ end
85
+
86
+ config
87
+ end
88
+
89
+ def config_from_file
90
+ file_path = File.join(`git rev-parse --show-toplevel`.strip, '.linty_rc')
91
+
92
+ raise('.linty_rc is missing.') unless File.exist?(file_path)
93
+
94
+ begin
95
+ JSON.parse(File.read(file_path)).symbolize_keys
96
+ rescue JSON::ParserError
97
+ raise('Malformed .linty_rc')
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,74 @@
1
+ require 'rubygems'
2
+ require 'colorize'
3
+ require 'terminfo'
4
+
5
+ module Lintron
6
+ # Outputs lint results on the command line
7
+ class TerminalReporter
8
+ def format_violations(violations)
9
+ row_header_width = violations.map { |v| v.file_and_line.length }.max
10
+ return no_violations if violations.empty?
11
+ last_file = violations.first.path
12
+ buffer = ''
13
+
14
+ violations.each do |violation|
15
+ buffer += do_line(violation, last_file, row_header_width)
16
+ last_file = violation.path
17
+ end
18
+ buffer += "\n\n"
19
+ buffer
20
+ end
21
+
22
+ def no_violations
23
+ 'No violations found!'.colorize(:green)
24
+ end
25
+
26
+ def do_line(violation, last_file, row_header_width)
27
+ rule = ''
28
+ if last_file != violation.path
29
+ colors.rewind
30
+ rule += hr
31
+ end
32
+
33
+ file_and_line = violation.file_and_line(row_header_width)
34
+ rule + wrap_pretty(
35
+ "#{file_and_line}#{violation['message']}".colorize(colors.next),
36
+ file_and_line.length,
37
+ )
38
+ end
39
+
40
+ def hr
41
+ '-' * TermInfo.screen_size[1] + "\n\n\n"
42
+ end
43
+
44
+ def colors
45
+ @_colors ||= [:magenta, :cyan].cycle.each
46
+ end
47
+
48
+ def wrap_pretty(string, indent_level)
49
+ width = TermInfo.screen_size[1] # Get the width of the term
50
+ buffer = ''
51
+ words = string.split(/\s/)
52
+
53
+ current_word = 0
54
+ line_length = 0
55
+ last_line_word = 0
56
+ while current_word < words.length
57
+ line_length += words[current_word].length
58
+ if line_length > width - 5 - indent_level &&
59
+ current_word > last_line_word
60
+
61
+ buffer += "\n"
62
+ buffer += ' ' * indent_level
63
+ line_length = indent_level
64
+ last_line_word = current_word
65
+ else
66
+ buffer += words[current_word]
67
+ buffer += ' '
68
+ current_word += 1
69
+ end
70
+ end
71
+ buffer + "\n"
72
+ end
73
+ end
74
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lintron
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Robert Prehn
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 4.2.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 4.2.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: git_diff_parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.3.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.3.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: httparty
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.14.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.14.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: colorize
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.8.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.8.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: ruby-terminfo
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.1.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.1.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: filewatcher
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.5.3
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.5.3
97
+ description: A command line tool for using the lintron service against local changes
98
+ (requires a lintron server).
99
+ email: robert@revelry.co
100
+ executables:
101
+ - lintron
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - app/models/file_like.rb
106
+ - app/models/local_pr_alike.rb
107
+ - app/models/patch.rb
108
+ - app/models/stub_file.rb
109
+ - bin/lintron
110
+ - lib/lintron.rb
111
+ - lib/lintron/api.rb
112
+ - lib/lintron/cli.rb
113
+ - lib/lintron/terminal_reporter.rb
114
+ homepage: https://github.com/prehnra/lintron
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 2.5.1
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: A CLI for running lintron against your changes.
138
+ test_files: []