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 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: []