lintron 1.0.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.
- checksums.yaml +7 -0
- data/app/models/file_like.rb +15 -0
- data/app/models/local_pr_alike.rb +84 -0
- data/app/models/patch.rb +51 -0
- data/app/models/stub_file.rb +40 -0
- data/bin/lintron +8 -0
- data/lib/lintron.rb +7 -0
- data/lib/lintron/api.rb +55 -0
- data/lib/lintron/cli.rb +101 -0
- data/lib/lintron/terminal_reporter.rb +74 -0
- metadata +138 -0
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
|
data/app/models/patch.rb
ADDED
@@ -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
data/lib/lintron.rb
ADDED
data/lib/lintron/api.rb
ADDED
@@ -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
|
data/lib/lintron/cli.rb
ADDED
@@ -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: []
|