rfix 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.
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+ require "rouge"
5
+ require "rainbow"
6
+ require "shellwords"
7
+
8
+ module Rfix
9
+ class Formatter < RuboCop::Formatter::SimpleTextFormatter
10
+ def started(files)
11
+ theme = Rouge::Themes::Gruvbox.new
12
+ @formatter = Rouge::Formatters::TerminalTruecolor.new(theme)
13
+ @lexer = Rouge::Lexers::Ruby.new
14
+ out "{{v}} Loading {{yellow:#{files.count}}} files"
15
+ out("\n")
16
+ @pg = CLI::UI::Progress.new
17
+ @total = files.count
18
+ @current = 0
19
+ @files = {}
20
+ end
21
+
22
+ def finished(files)
23
+ files.each do |file|
24
+ render_file(file, @files.fetch(file))
25
+ end
26
+
27
+ offenses = @files.values.flatten
28
+ corrected = offenses.select(&:corrected?)
29
+ out("\n")
30
+ report_summary(files.size, offenses.count, corrected.count)
31
+ end
32
+
33
+ def to_clickable(url, title)
34
+ esc = CLI::UI::ANSI::ESC
35
+ cmd = esc + "]8;;"
36
+ slash = "\x07"
37
+ cmd + "#{escape(url)}#{slash}#{escape(title)}" + cmd + slash
38
+ end
39
+
40
+ def to_path(path, title)
41
+ to_clickable("file://#{path}", title)
42
+ end
43
+
44
+ def to_url(url, title)
45
+ to_clickable(url, title)
46
+ end
47
+
48
+ def escape(str)
49
+ Shellwords.escape(str)
50
+ end
51
+
52
+ def render_file(file, offenses)
53
+ return if offenses.empty?
54
+
55
+ path = Rfix.to_relative(path: file)
56
+ url = to_url(file, path)
57
+ offenses.each do |offense|
58
+ out("\n\n")
59
+ clickable_path = "{{italic:#{path}:#{offense.where}}}"
60
+ clickable_code = to_url("https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/#{offense.code}", offense.code)
61
+ CLI::UI::Frame.open("#{offense.icon} #{offense.msg}", color: :reset)
62
+ report_line(file, offense, offense.location, offense.highlighted_area)
63
+ CLI::UI::Frame.close("#{clickable_path} » {{italic:#{clickable_code}}}", color: :reset)
64
+ end
65
+ end
66
+
67
+ def mark
68
+ CLI::UI::ANSI::ESC + "]1337;SetMark" + "\x07"
69
+ end
70
+
71
+ def file_finished(file, offenses)
72
+ @current += 1.0
73
+ @pg.tick(set_percent: (@current / @total))
74
+ @files[file] = offenses
75
+ end
76
+
77
+ def out(msg, format: true)
78
+ CLI::UI.puts(msg, to: output, format: format)
79
+ end
80
+
81
+ def fmt(msg)
82
+ CLI::UI.fmt(msg, enable_color: true)
83
+ end
84
+
85
+ def dim(value)
86
+ Rainbow(value).lightgray
87
+ end
88
+
89
+ def highlighted_source_line(offense)
90
+ source_before_highlight(offense) +
91
+ hightlight_source_tag(offense) +
92
+ source_after_highlight(offense)
93
+ end
94
+
95
+ def hightlight_source_tag(offense)
96
+ offense.highlighted_area.source
97
+ end
98
+
99
+ def source_before_highlight(offense)
100
+ source_line = offense.location.source_line
101
+ source_line[0...offense.highlighted_area.begin_pos]
102
+ end
103
+
104
+ def source_after_highlight(offense)
105
+ source_line = offense.location.source_line
106
+ source_line[offense.highlighted_area.end_pos..-1]
107
+ end
108
+
109
+ def report_line(_file, offense, _location, highlighted_area)
110
+ extra = " "
111
+ src = highlighted_source_line(offense).lines.map { |line| extra + line }.join("\n")
112
+ lines = @formatter.format(@lexer.lex(src)).gsub('\\e', CLI::UI::ANSI::ESC).lines.map(&:chomp)
113
+ out("\n\n")
114
+ out(lines.join("\n"), format: false)
115
+ b_pos = highlighted_area.begin_pos + extra.length
116
+ e_pos = highlighted_area.end_pos + extra.length
117
+ size = e_pos - b_pos
118
+ out((" " * b_pos) + Rainbow((" " * size)).underline.bold)
119
+ out("\n\n")
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "rfix/git_helper"
5
+
6
+ class Rfix::GitFile
7
+ include Rfix::GitHelper
8
+ attr_reader :path, :ref
9
+
10
+ def initialize(path, ref, root_dir)
11
+ @path = File.join(root_dir, path)
12
+ @root_dir = Pathname.new(root_dir)
13
+ @ref = ref
14
+ @ranges = []
15
+ end
16
+
17
+ def file?
18
+ File.file?(path)
19
+ end
20
+
21
+ def ==(other)
22
+ path == other.path
23
+ end
24
+
25
+ def eql?(other)
26
+ path == other.path
27
+ end
28
+
29
+ def hash
30
+ path.hash
31
+ end
32
+
33
+ def relative_path
34
+ Pathname.new(path).relative_path_from(@root_dir).to_s
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "rfix"
5
+ require "rfix/log"
6
+ require "rfix/cmd"
7
+
8
+ module Rfix::GitHelper
9
+ include Rfix::Log
10
+ include Rfix::Cmd
11
+
12
+ def git(*args)
13
+ cmd("git", *args)
14
+ end
15
+
16
+ def params
17
+ [
18
+ "--word-diff-regex=[^[:space:]]",
19
+ "--no-renames",
20
+ "--no-merges",
21
+ "--first-parent",
22
+ "--diff-filter=AM",
23
+ "-U0",
24
+ "--no-color",
25
+ "-p"
26
+ ]
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rfix"
4
+ require "cli/ui"
5
+
6
+ module Rfix::Log
7
+ def say(message)
8
+ CLI::UI.puts("{{v}} #{message}")
9
+ end
10
+
11
+ def say_error(message)
12
+ CLI::UI.puts("{{x}} #{message}")
13
+ end
14
+
15
+ def say_error_sub(message)
16
+ CLI::UI.puts("#{message}")
17
+ end
18
+
19
+ def say_debug(message)
20
+ CLI::UI.puts("{{*}} #{message}")
21
+ end
22
+
23
+ def say_abort(message)
24
+ CLI::UI.puts("{{x}} #{message}")
25
+ exit 1
26
+ end
27
+
28
+ def say_exit(message)
29
+ CLI::UI.puts("{{v}} #{message}")
30
+ exit 0
31
+ end
32
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+ require "optparse"
5
+ require "rbconfig"
6
+ require "rfix/git_file"
7
+ require "rfix/git_helper"
8
+ require "rfix/log"
9
+ require "rfix/tracked_file"
10
+ require "rfix/untracked_file"
11
+
12
+ module Rfix
13
+ include GitHelper
14
+ include Log
15
+
16
+ def git_version
17
+ cmd("git --version").last.split(/\s+/, 3).last
18
+ end
19
+
20
+ def ruby_version
21
+ RbConfig::CONFIG["ruby_version"] || "<unknown>"
22
+ end
23
+
24
+ def current_os
25
+ RbConfig::CONFIG["host_os"] || "<unknown>"
26
+ end
27
+
28
+ def global_enable!
29
+ @global_enable = true
30
+ end
31
+
32
+ def global_enable?
33
+ @global_enable
34
+ end
35
+
36
+ def init!
37
+ @files ||= {}
38
+ @global_enable = false
39
+ end
40
+
41
+ def files
42
+ @files.values
43
+ end
44
+
45
+ def spin
46
+ @spin ||= CLI::UI::SpinGroup.new
47
+ end
48
+
49
+ def paths
50
+ @files.keys
51
+ end
52
+
53
+ def root_dir
54
+ @root_dir ||= git("rev-parse", "--show-toplevel").first
55
+ end
56
+
57
+ def refresh!(source)
58
+ @files[source.file_path]&.refresh!
59
+ end
60
+
61
+ def enabled?(path, line)
62
+ return true if global_enable?
63
+
64
+ @files[path]&.include?(line)
65
+ end
66
+
67
+ def to_relative(path:)
68
+ Pathname.new(path).relative_path_from(Pathname.new(root_dir)).to_s
69
+ rescue ArgumentError
70
+ path
71
+ end
72
+
73
+ def load_untracked!
74
+ cached(list_untrack_files.map do |path|
75
+ UntrackedFile.new(path, nil, root_dir)
76
+ end.select(&:file?).to_set)
77
+ end
78
+
79
+ def load_tracked!(reference)
80
+ cached(git("log", "--name-only", "--pretty=format:", *params, "#{reference}..HEAD").map do |path|
81
+ TrackedFile.new(path, reference, root_dir)
82
+ end.select(&:file?).to_set)
83
+ end
84
+
85
+ def has_branch?(name)
86
+ Open3.capture2e("git", "cat-file", "-t", name).last.success?
87
+ end
88
+
89
+ # Ref since last push
90
+ def ref_since_push
91
+ git("rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}").first
92
+ end
93
+
94
+ # Original branch, usually master
95
+ def ref_since_origin
96
+ git("show-branch", "--merge-base").first
97
+ end
98
+
99
+ private
100
+
101
+ def get_file(path, &block)
102
+ if file = @files[path]
103
+ block.call(file)
104
+ end
105
+ end
106
+
107
+ def list_untrack_files
108
+ git("status", "-u", "--porcelain", "--no-column").map do |line|
109
+ line.split(" ", 2).map(&:strip)
110
+ end.select { |el| el.first == "??" }.map(&:last)
111
+ end
112
+
113
+ def cached(files)
114
+ @files ||= {}
115
+ files.each do |file|
116
+ @files[file.path] = file
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rfix/git_file"
4
+
5
+ class Rfix::TrackedFile < Rfix::GitFile
6
+ def refresh!
7
+ @ranges = git("--no-pager", "diff", *params, ref, path)
8
+ .grep(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/) do
9
+ Regexp.last_match(1).to_i...(Regexp.last_match(1).to_i + (Regexp.last_match(2) || 1).to_i)
10
+ end
11
+ end
12
+
13
+ def include?(line)
14
+ @ranges.any? { |range| range.include?(line) }
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rfix/git_file"
4
+
5
+ class Rfix::UntrackedFile < Rfix::GitFile
6
+ def refresh!
7
+ # nothing
8
+ end
9
+
10
+ def include?(_line)
11
+ true
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rfix
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require_relative "lib/rfix/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rfix"
8
+ spec.version = Rfix::VERSION
9
+ spec.authors = ["Linus Oleander"]
10
+ spec.email = ["linus@oleander.nu"]
11
+
12
+ spec.summary = "RuboCop wrapper that only complains about your latest changes"
13
+ spec.description = spec.summary + "."
14
+ spec.homepage = "https://github.com/oleander/rfix"
15
+ spec.license = "MIT"
16
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ validate_file = ->(f) { f.match(%r{^(test|spec|features)/}) }
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject(&validate_file)
24
+ end
25
+
26
+ spec.files += Dir.glob("vendor/cli-ui/lib/**/*").reject(&validate_file)
27
+
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib", "vendor/cli-ui/lib"]
31
+
32
+ spec.add_runtime_dependency "rainbow", "~> 3.0"
33
+ spec.add_runtime_dependency "rouge", "~> 3.20"
34
+ spec.add_runtime_dependency "rubocop", "~> 0.80"
35
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rfix
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Linus Oleander
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-06-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rainbow
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rouge
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.20'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.20'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.80'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.80'
55
+ description: RuboCop wrapper that only complains about your latest changes.
56
+ email:
57
+ - linus@oleander.nu
58
+ executables:
59
+ - rfix
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".rubocop.yml"
66
+ - ".travis.yml"
67
+ - Gemfile
68
+ - Gemfile.lock
69
+ - Guardfile
70
+ - LICENSE.txt
71
+ - README.md
72
+ - Rakefile
73
+ - bin/console
74
+ - bin/setup
75
+ - ci/Gemfile.rubocop-0.80
76
+ - ci/Gemfile.rubocop-0.80.lock
77
+ - ci/Gemfile.rubocop-0.81
78
+ - ci/Gemfile.rubocop-0.81.lock
79
+ - ci/Gemfile.rubocop-0.82
80
+ - ci/Gemfile.rubocop-0.82.lock
81
+ - ci/Gemfile.rubocop-0.83
82
+ - ci/Gemfile.rubocop-0.83.lock
83
+ - ci/Gemfile.rubocop-0.84
84
+ - ci/Gemfile.rubocop-0.84.lock
85
+ - ci/Gemfile.rubocop-0.85
86
+ - ci/Gemfile.rubocop-0.85.1
87
+ - ci/Gemfile.rubocop-0.85.1.lock
88
+ - ci/Gemfile.rubocop-0.85.lock
89
+ - exe/rfix
90
+ - lib/rfix.rb
91
+ - lib/rfix/cmd.rb
92
+ - lib/rfix/extensions.rb
93
+ - lib/rfix/formatter.rb
94
+ - lib/rfix/git_file.rb
95
+ - lib/rfix/git_helper.rb
96
+ - lib/rfix/log.rb
97
+ - lib/rfix/rfix.rb
98
+ - lib/rfix/tracked_file.rb
99
+ - lib/rfix/untracked_file.rb
100
+ - lib/rfix/version.rb
101
+ - rfix.gemspec
102
+ - vendor/cli-ui/lib/cli/ui.rb
103
+ - vendor/cli-ui/lib/cli/ui/ansi.rb
104
+ - vendor/cli-ui/lib/cli/ui/color.rb
105
+ - vendor/cli-ui/lib/cli/ui/formatter.rb
106
+ - vendor/cli-ui/lib/cli/ui/frame.rb
107
+ - vendor/cli-ui/lib/cli/ui/frame/frame_stack.rb
108
+ - vendor/cli-ui/lib/cli/ui/frame/frame_style.rb
109
+ - vendor/cli-ui/lib/cli/ui/frame/frame_style/box.rb
110
+ - vendor/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb
111
+ - vendor/cli-ui/lib/cli/ui/glyph.rb
112
+ - vendor/cli-ui/lib/cli/ui/printer.rb
113
+ - vendor/cli-ui/lib/cli/ui/progress.rb
114
+ - vendor/cli-ui/lib/cli/ui/prompt.rb
115
+ - vendor/cli-ui/lib/cli/ui/prompt/interactive_options.rb
116
+ - vendor/cli-ui/lib/cli/ui/prompt/options_handler.rb
117
+ - vendor/cli-ui/lib/cli/ui/spinner.rb
118
+ - vendor/cli-ui/lib/cli/ui/spinner/async.rb
119
+ - vendor/cli-ui/lib/cli/ui/spinner/spin_group.rb
120
+ - vendor/cli-ui/lib/cli/ui/stdout_router.rb
121
+ - vendor/cli-ui/lib/cli/ui/terminal.rb
122
+ - vendor/cli-ui/lib/cli/ui/truncater.rb
123
+ - vendor/cli-ui/lib/cli/ui/version.rb
124
+ - vendor/cli-ui/lib/cli/ui/widgets.rb
125
+ - vendor/cli-ui/lib/cli/ui/widgets/base.rb
126
+ - vendor/cli-ui/lib/cli/ui/widgets/status.rb
127
+ homepage: https://github.com/oleander/rfix
128
+ licenses:
129
+ - MIT
130
+ metadata:
131
+ homepage_uri: https://github.com/oleander/rfix
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ - vendor/cli-ui/lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: 2.5.0
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubygems_version: 3.1.4
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: RuboCop wrapper that only complains about your latest changes
152
+ test_files: []