ruumba 0.1.10 → 0.1.12
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 +4 -4
- data/bin/ruumba +5 -0
- data/lib/ruumba/analyzer.rb +69 -11
- data/lib/ruumba/correctors.rb +80 -0
- data/lib/ruumba/parser.rb +53 -6
- data/lib/ruumba/rubocop_runner.rb +18 -21
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be806019ab1e49249c6c93f96fc6c3b0cc64133e
|
4
|
+
data.tar.gz: 23f5f794ec43878ffa895fca296c3635c765affc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98d98ef80a4604c9c4eeb61fc2e950ec029b6cc54f36af699f9051cb6c526bc20f6d6d6ee78e2946db3a358a30552c9928064445399584bf84d468136838958a
|
7
|
+
data.tar.gz: 1029541aa945f8611b1b8da61d7a4f9c53ccab0a6dc7b550e0de31ae4f24dce9f067a0c723ce341ebba77369ffd2e143bcbe1ac323b36fd1a938d92f3d1efbb4
|
data/bin/ruumba
CHANGED
@@ -96,6 +96,11 @@ opts_parser = OptionParser.new do |opts|
|
|
96
96
|
opts.on('-L', '--list-target-files', 'List all files Ruumba will inspect.') do
|
97
97
|
options[:arguments] << '--list-target-files'
|
98
98
|
end
|
99
|
+
|
100
|
+
opts.on('-a', '--auto-correct', 'Auto-correct offenses.') do
|
101
|
+
options[:arguments] << '--auto-correct'
|
102
|
+
options[:auto_correct] = true
|
103
|
+
end
|
99
104
|
end
|
100
105
|
|
101
106
|
begin
|
data/lib/ruumba/analyzer.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# @author Eric Weinstein <eric.q.weinstein@gmail.com>
|
2
2
|
|
3
3
|
require 'securerandom'
|
4
|
+
require 'digest'
|
4
5
|
require 'pathname'
|
5
6
|
require 'tmpdir'
|
6
7
|
require 'open3'
|
7
8
|
require 'English'
|
8
9
|
|
9
10
|
require 'ruumba/iterators'
|
11
|
+
require 'ruumba/correctors'
|
10
12
|
require 'ruumba/parser'
|
11
13
|
require 'ruumba/rubocop_runner'
|
12
14
|
|
@@ -38,24 +40,43 @@ module Ruumba
|
|
38
40
|
def analyze(temp_dir, files_or_dirs)
|
39
41
|
temp_dir_path = Pathname.new(temp_dir)
|
40
42
|
|
41
|
-
iterator =
|
43
|
+
iterator, corrector =
|
42
44
|
if stdin?
|
43
|
-
Iterators::StdinIterator.new(stdin_filename)
|
45
|
+
[Iterators::StdinIterator.new(File.expand_path(stdin_filename)), Correctors::StdinCorrector.new(digestor, parser)]
|
44
46
|
else
|
45
|
-
Iterators::DirectoryIterator.new(files_or_dirs)
|
47
|
+
[Iterators::DirectoryIterator.new(files_or_dirs), Correctors::FileCorrector.new(digestor, parser)]
|
46
48
|
end
|
47
49
|
|
48
50
|
iterator.each do |file, contents|
|
49
|
-
copy_erb_file(file, contents, temp_dir_path)
|
51
|
+
code, new_file_name = copy_erb_file(file, contents, temp_dir_path)
|
52
|
+
|
53
|
+
if stdin?
|
54
|
+
@stdin_contents = code
|
55
|
+
@new_stdin_filename = new_file_name
|
56
|
+
end
|
50
57
|
end
|
51
58
|
|
52
|
-
RubocopRunner.new(arguments, pwd, temp_dir_path, !disable_rb_extension?).execute
|
59
|
+
stdout, stderr, exit_code = RubocopRunner.new(arguments, pwd, temp_dir_path, @stdin_contents, !disable_rb_extension?).execute
|
60
|
+
|
61
|
+
corrector.correct(stdout, stderr, file_mappings) if auto_correct?
|
62
|
+
|
63
|
+
[[STDOUT, stdout], [STDERR, stderr]].each do |output_stream, output|
|
64
|
+
next if output.nil? || output.empty?
|
65
|
+
|
66
|
+
output_stream.puts(output)
|
67
|
+
end
|
68
|
+
|
69
|
+
exit_code
|
53
70
|
end
|
54
71
|
|
55
72
|
def extension
|
56
73
|
'.rb' unless disable_rb_extension?
|
57
74
|
end
|
58
75
|
|
76
|
+
def auto_correct?
|
77
|
+
options[:auto_correct]
|
78
|
+
end
|
79
|
+
|
59
80
|
def stdin?
|
60
81
|
stdin_filename
|
61
82
|
end
|
@@ -65,7 +86,11 @@ module Ruumba
|
|
65
86
|
end
|
66
87
|
|
67
88
|
def arguments
|
68
|
-
|
89
|
+
if stdin?
|
90
|
+
options[:arguments] + ['--stdin', @new_stdin_filename]
|
91
|
+
else
|
92
|
+
options[:arguments]
|
93
|
+
end
|
69
94
|
end
|
70
95
|
|
71
96
|
def disable_rb_extension?
|
@@ -76,19 +101,52 @@ module Ruumba
|
|
76
101
|
@pwd ||= Pathname.new(ENV['PWD'])
|
77
102
|
end
|
78
103
|
|
104
|
+
def auto_correct_marker
|
105
|
+
return @auto_correct_marker if defined?(@auto_correct_marker)
|
106
|
+
|
107
|
+
@auto_correct_marker = auto_correct? ? 'marker_' + SecureRandom.uuid.tr('-', '_') : nil
|
108
|
+
end
|
109
|
+
|
79
110
|
def parser
|
80
|
-
@parser ||= Parser.new
|
111
|
+
@parser ||= Parser.new(auto_correct_marker)
|
112
|
+
end
|
113
|
+
|
114
|
+
def digestor
|
115
|
+
@digestor ||= ->(contents) { Digest::SHA256.base64digest(contents) }
|
116
|
+
end
|
117
|
+
|
118
|
+
def file_mappings
|
119
|
+
@file_mappings ||= {}
|
81
120
|
end
|
82
121
|
|
83
122
|
def copy_erb_file(file, contents, temp_dir)
|
84
123
|
code = parser.extract(contents)
|
124
|
+
new_file = temp_filename_for(file, temp_dir)
|
85
125
|
|
86
|
-
|
87
|
-
|
126
|
+
if auto_correct?
|
127
|
+
properties = []
|
128
|
+
properties << new_file
|
129
|
+
properties << digestor.call(code)
|
88
130
|
|
89
|
-
|
90
|
-
|
131
|
+
properties <<
|
132
|
+
if stdin?
|
133
|
+
contents
|
134
|
+
else
|
135
|
+
-> { File.read(file) }
|
136
|
+
end
|
137
|
+
|
138
|
+
file_mappings[file] = properties
|
139
|
+
end
|
140
|
+
|
141
|
+
unless stdin?
|
142
|
+
FileUtils.mkdir_p(File.dirname(new_file))
|
143
|
+
|
144
|
+
File.open(new_file, 'w+') do |tmp_file|
|
145
|
+
tmp_file.write(code)
|
146
|
+
end
|
91
147
|
end
|
148
|
+
|
149
|
+
[code, new_file]
|
92
150
|
end
|
93
151
|
|
94
152
|
def temp_filename_for(file, temp_dir)
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ruumba
|
4
|
+
# Responsible for extracted auto corrected code and updating the original ERBs files
|
5
|
+
module Correctors
|
6
|
+
# Module to help replace code
|
7
|
+
module Replacer
|
8
|
+
def handle_corrected_output(old_digest, new_contents, original_contents)
|
9
|
+
new_digest = digestor.call(new_contents)
|
10
|
+
|
11
|
+
return if old_digest == new_digest
|
12
|
+
|
13
|
+
original_contents = original_contents.call if original_contents.respond_to?(:call)
|
14
|
+
|
15
|
+
replaced_output = parser.replace(original_contents, new_contents)
|
16
|
+
|
17
|
+
yield(replaced_output) if replaced_output
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Corrector for when the checked file was passed through stdin.
|
22
|
+
class StdinCorrector
|
23
|
+
include Replacer
|
24
|
+
|
25
|
+
def initialize(digestor, parser)
|
26
|
+
@digestor = digestor
|
27
|
+
@parser = parser
|
28
|
+
end
|
29
|
+
|
30
|
+
def correct(stdout, stderr, file_mappings)
|
31
|
+
_, old_ruumba_digest, original_contents = *file_mappings.values.first
|
32
|
+
|
33
|
+
[stdout, stderr].each do |output|
|
34
|
+
next if output.nil? || output.empty?
|
35
|
+
|
36
|
+
matches = output.scan(/\A(.*^====================)?$(.*)\z/m)
|
37
|
+
|
38
|
+
next if matches.empty?
|
39
|
+
|
40
|
+
prefix, new_contents = *matches.first
|
41
|
+
|
42
|
+
handle_corrected_output(old_ruumba_digest, new_contents, original_contents) do |corrected_output|
|
43
|
+
output.clear
|
44
|
+
output.concat("#{prefix}\n#{corrected_output}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :digestor, :parser
|
52
|
+
end
|
53
|
+
|
54
|
+
# Corrector for when normal file checking
|
55
|
+
class FileCorrector
|
56
|
+
include Replacer
|
57
|
+
|
58
|
+
def initialize(digestor, parser)
|
59
|
+
@digestor = digestor
|
60
|
+
@parser = parser
|
61
|
+
end
|
62
|
+
|
63
|
+
def correct(_stdout, _stderr, file_mappings)
|
64
|
+
file_mappings.each do |original_file, (ruumba_file, old_ruumba_digest, original_contents)|
|
65
|
+
new_contents = File.read(ruumba_file)
|
66
|
+
|
67
|
+
handle_corrected_output(old_ruumba_digest, new_contents, original_contents) do |corrected_output|
|
68
|
+
File.open(original_file, 'w+') do |file_handle|
|
69
|
+
file_handle.write(corrected_output)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
attr_reader :digestor, :parser
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/ruumba/parser.rb
CHANGED
@@ -8,6 +8,10 @@ module Ruumba
|
|
8
8
|
# The regular expression to capture interpolated Ruby.
|
9
9
|
ERB_REGEX = /<%[-=]?(.*?)-?%>/m
|
10
10
|
|
11
|
+
def initialize(region_start_marker = nil)
|
12
|
+
@region_start_marker = region_start_marker
|
13
|
+
end
|
14
|
+
|
11
15
|
# Extracts Ruby code from an ERB template.
|
12
16
|
# @return [String] The extracted ruby code
|
13
17
|
def extract(contents)
|
@@ -16,16 +20,16 @@ module Ruumba
|
|
16
20
|
extracted_ruby = +''
|
17
21
|
|
18
22
|
last_match = [0, 0]
|
19
|
-
matches.
|
23
|
+
matches.each_with_index do |(start_index, end_index), index|
|
20
24
|
handle_region_before(start_index, last_match.last, file_text, extracted_ruby)
|
21
25
|
|
22
|
-
|
26
|
+
match_marker = "#{region_start_marker}_#{format('%010d', index + 1)}" if region_start_marker
|
27
|
+
extracted_ruby << extract_match(file_text, start_index, end_index, match_marker)
|
23
28
|
|
24
29
|
last_match = [start_index, end_index]
|
25
30
|
end
|
26
31
|
|
27
32
|
extracted_ruby << file_text[last_match.last..-1].gsub(/./, ' ')
|
28
|
-
extracted_ruby.gsub!(/[^\S\r\n]+$/, '')
|
29
33
|
|
30
34
|
# if we replaced <%== with <%= raw, try to shift the columns back to the
|
31
35
|
# left so they match the original again
|
@@ -34,8 +38,44 @@ module Ruumba
|
|
34
38
|
extracted_ruby
|
35
39
|
end
|
36
40
|
|
41
|
+
def replace(old_contents, new_contents)
|
42
|
+
file_text, matches = parse(old_contents)
|
43
|
+
|
44
|
+
auto_corrected_erb = +''
|
45
|
+
|
46
|
+
last_match = [0, 0]
|
47
|
+
matches.each_with_index do |(start_index, end_index), index|
|
48
|
+
match_start = start_index
|
49
|
+
prev_end_index = last_match.last
|
50
|
+
|
51
|
+
if start_index > prev_end_index
|
52
|
+
region_before = file_text[prev_end_index..match_start - 1]
|
53
|
+
|
54
|
+
auto_corrected_erb << region_before
|
55
|
+
end
|
56
|
+
|
57
|
+
suffix = format('%010d', index + 1)
|
58
|
+
match_marker = "#{region_start_marker}_#{suffix}"
|
59
|
+
|
60
|
+
match_without_markers = new_contents[/\n#{match_marker}$\n(.*)\n#{match_marker}\n/m, 1]
|
61
|
+
|
62
|
+
# auto-correct is still experimental and can cause invalid ruby to be generated when extracting ruby from ERBs
|
63
|
+
return nil unless match_without_markers
|
64
|
+
|
65
|
+
auto_corrected_erb << match_without_markers
|
66
|
+
|
67
|
+
last_match = [start_index, end_index]
|
68
|
+
end
|
69
|
+
|
70
|
+
auto_corrected_erb << file_text[last_match.last..-1]
|
71
|
+
|
72
|
+
auto_corrected_erb
|
73
|
+
end
|
74
|
+
|
37
75
|
private
|
38
76
|
|
77
|
+
attr_reader :region_start_marker
|
78
|
+
|
39
79
|
def parse(contents)
|
40
80
|
# http://edgeguides.rubyonrails.org/active_support_core_extensions.html#output-safety
|
41
81
|
# replace '<%==' with '<%= raw' to avoid generating invalid ruby code
|
@@ -50,20 +90,24 @@ module Ruumba
|
|
50
90
|
def handle_region_before(match_start, prev_end_index, file_text, extracted_ruby)
|
51
91
|
return unless match_start > prev_end_index
|
52
92
|
|
93
|
+
last_position = extracted_ruby.length
|
94
|
+
|
53
95
|
region_before = file_text[prev_end_index..match_start - 1]
|
54
96
|
|
55
|
-
|
97
|
+
region_before.gsub!(/./, ' ')
|
56
98
|
|
57
99
|
# if the last match was on the same line, we need to use a semicolon to
|
58
100
|
# separate statements
|
59
|
-
extracted_ruby[
|
101
|
+
extracted_ruby[last_position] = ';' if needs_stmt_delimiter?(prev_end_index, region_before)
|
102
|
+
|
103
|
+
extracted_ruby << region_before
|
60
104
|
end
|
61
105
|
|
62
106
|
def needs_stmt_delimiter?(last_match, region_before)
|
63
107
|
last_match.positive? && region_before.index("\n").nil?
|
64
108
|
end
|
65
109
|
|
66
|
-
def extract_match(file_text, start_index, end_index)
|
110
|
+
def extract_match(file_text, start_index, end_index, match_marker)
|
67
111
|
file_text[start_index...end_index].tap do |region|
|
68
112
|
# if there is a ruby comment inside, replace the beginning of each line
|
69
113
|
# with the '#' so we end up with valid ruby
|
@@ -71,6 +115,9 @@ module Ruumba
|
|
71
115
|
if region[0] == '#'
|
72
116
|
region.gsub!(/^ /, '#')
|
73
117
|
region.gsub!(/^(?!#)/, '#')
|
118
|
+
elsif match_marker
|
119
|
+
region.prepend("\n", match_marker, "\n")
|
120
|
+
region.concat("\n", match_marker, "\n")
|
74
121
|
end
|
75
122
|
end
|
76
123
|
end
|
@@ -5,11 +5,17 @@ require 'open3'
|
|
5
5
|
module Ruumba
|
6
6
|
# Runs rubocop on the files in the given target_directory
|
7
7
|
class RubocopRunner
|
8
|
-
def initialize(arguments, current_directory, target_directory, rb_extension_enabled)
|
8
|
+
def initialize(arguments, current_directory, target_directory, stdin, rb_extension_enabled)
|
9
9
|
@arguments = Array(arguments)
|
10
10
|
@current_directory = current_directory
|
11
11
|
@rb_extension_enabled = rb_extension_enabled
|
12
|
+
@stdin = stdin
|
12
13
|
@target_directory = target_directory
|
14
|
+
@replacements = []
|
15
|
+
|
16
|
+
# if adding the .rb extension is enabled, remove the extension again from
|
17
|
+
# any output so it matches the actual files names we are linting
|
18
|
+
@replacements << [/\.erb\.rb/, '.erb'] if rb_extension_enabled
|
13
19
|
end
|
14
20
|
|
15
21
|
# Executes rubocop, updating filenames in the output if needed.
|
@@ -18,41 +24,32 @@ module Ruumba
|
|
18
24
|
args = ['rubocop'] + arguments
|
19
25
|
todo = target_directory.join('.rubocop_todo.yml')
|
20
26
|
|
21
|
-
|
22
|
-
|
23
|
-
# if adding the .rb extension is enabled, remove the extension again from
|
24
|
-
# any output so it matches the actual files names we are linting
|
25
|
-
replacements << [/\.erb\.rb/, '.erb'] if rb_extension_enabled
|
26
|
-
|
27
|
-
result = Dir.chdir(target_directory) do
|
27
|
+
results = Dir.chdir(target_directory) do
|
28
28
|
replacements.unshift([/^#{Regexp.quote(Dir.pwd)}/, current_directory.to_s])
|
29
|
-
stdout, stderr, status = Open3.capture3(*args)
|
30
29
|
|
31
|
-
|
30
|
+
stdout, stderr, status = Open3.capture3(*args, stdin_data: stdin)
|
32
31
|
|
33
|
-
status.exitstatus
|
32
|
+
[munge_output(stdout), munge_output(stderr), status.exitstatus]
|
34
33
|
end
|
35
34
|
|
36
35
|
# copy the todo file back for the case where we've used --auto-gen-config
|
37
36
|
FileUtils.cp(todo, current_directory) if todo.exist?
|
38
37
|
|
39
|
-
|
38
|
+
results
|
40
39
|
end
|
41
40
|
|
42
41
|
private
|
43
42
|
|
44
|
-
attr_reader :arguments, :current_directory, :rb_extension_enabled, :target_directory
|
43
|
+
attr_reader :arguments, :current_directory, :rb_extension_enabled, :replacements, :stdin, :target_directory
|
45
44
|
|
46
|
-
def munge_output(
|
47
|
-
|
48
|
-
next if output.nil? || output.empty?
|
45
|
+
def munge_output(output)
|
46
|
+
return output if output.nil? || output.empty?
|
49
47
|
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
output_stream.puts(output)
|
48
|
+
replacements.each do |pattern, replacement|
|
49
|
+
output = output.gsub(pattern, replacement)
|
55
50
|
end
|
51
|
+
|
52
|
+
output
|
56
53
|
end
|
57
54
|
end
|
58
55
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruumba
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Weinstein
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2019-
|
14
|
+
date: 2019-08-30 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rubocop
|
@@ -39,6 +39,7 @@ files:
|
|
39
39
|
- bin/ruumba
|
40
40
|
- lib/ruumba.rb
|
41
41
|
- lib/ruumba/analyzer.rb
|
42
|
+
- lib/ruumba/correctors.rb
|
42
43
|
- lib/ruumba/iterators.rb
|
43
44
|
- lib/ruumba/parser.rb
|
44
45
|
- lib/ruumba/rake_task.rb
|
@@ -63,7 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
64
|
version: '0'
|
64
65
|
requirements: []
|
65
66
|
rubyforge_project:
|
66
|
-
rubygems_version: 2.
|
67
|
+
rubygems_version: 2.6.14.3
|
67
68
|
signing_key:
|
68
69
|
specification_version: 4
|
69
70
|
summary: Allows users to lint Ruby code in ERB templates the same way they lint source
|