rfix 2.0.4 → 3.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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/exe/rfix +11 -90
  3. data/lib/rfix.rb +10 -9
  4. data/lib/rfix/branch/reference.rb +2 -2
  5. data/lib/rfix/branch/upstream.rb +2 -4
  6. data/lib/rfix/cli/command.rb +14 -1
  7. data/lib/rfix/cli/command/all.rb +21 -0
  8. data/lib/rfix/cli/command/base.rb +30 -19
  9. data/lib/rfix/cli/command/branch.rb +2 -0
  10. data/lib/rfix/cli/command/help.rb +2 -0
  11. data/lib/rfix/cli/command/info.rb +6 -1
  12. data/lib/rfix/cli/command/local.rb +2 -0
  13. data/lib/rfix/cli/command/origin.rb +2 -0
  14. data/lib/rfix/cli/command/setup.rb +2 -0
  15. data/lib/rfix/cli/command/status.rb +39 -0
  16. data/lib/rfix/collector.rb +69 -0
  17. data/lib/rfix/diff.rb +69 -0
  18. data/lib/rfix/extension/comment_config.rb +15 -0
  19. data/lib/rfix/extension/offense.rb +17 -14
  20. data/lib/rfix/extension/pastel.rb +7 -4
  21. data/lib/rfix/extension/progresbar.rb +15 -0
  22. data/lib/rfix/extension/strings.rb +10 -2
  23. data/lib/rfix/file.rb +5 -3
  24. data/lib/rfix/file/base.rb +21 -14
  25. data/lib/rfix/file/deleted.rb +2 -0
  26. data/lib/rfix/file/ignored.rb +2 -0
  27. data/lib/rfix/file/null.rb +17 -0
  28. data/lib/rfix/file/tracked.rb +39 -23
  29. data/lib/rfix/file/undefined.rb +17 -0
  30. data/lib/rfix/file/untracked.rb +3 -1
  31. data/lib/rfix/formatter.rb +67 -71
  32. data/lib/rfix/highlighter.rb +1 -3
  33. data/lib/rfix/rake/gemfile.rb +26 -23
  34. data/lib/rfix/repository.rb +59 -96
  35. data/lib/rfix/types.rb +24 -14
  36. data/lib/rfix/version.rb +1 -1
  37. data/rfix.gemspec +11 -3
  38. data/vendor/cli-ui/Gemfile +17 -0
  39. data/vendor/cli-ui/Gemfile.lock +60 -0
  40. data/vendor/cli-ui/LICENSE.txt +21 -0
  41. data/vendor/cli-ui/README.md +224 -0
  42. data/vendor/cli-ui/Rakefile +20 -0
  43. data/vendor/cli-ui/bin/console +14 -0
  44. data/vendor/cli-ui/cli-ui.gemspec +25 -0
  45. data/vendor/cli-ui/dev.yml +14 -0
  46. data/vendor/cli-ui/lib/cli/ui.rb +233 -0
  47. data/vendor/cli-ui/lib/cli/ui/ansi.rb +157 -0
  48. data/vendor/cli-ui/lib/cli/ui/color.rb +84 -0
  49. data/vendor/cli-ui/lib/cli/ui/formatter.rb +192 -0
  50. data/vendor/cli-ui/lib/cli/ui/frame.rb +269 -0
  51. data/vendor/cli-ui/lib/cli/ui/frame/frame_stack.rb +98 -0
  52. data/vendor/cli-ui/lib/cli/ui/frame/frame_style.rb +120 -0
  53. data/vendor/cli-ui/lib/cli/ui/frame/frame_style/box.rb +166 -0
  54. data/vendor/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +139 -0
  55. data/vendor/cli-ui/lib/cli/ui/glyph.rb +84 -0
  56. data/vendor/cli-ui/lib/cli/ui/os.rb +67 -0
  57. data/vendor/cli-ui/lib/cli/ui/printer.rb +59 -0
  58. data/vendor/cli-ui/lib/cli/ui/progress.rb +90 -0
  59. data/vendor/cli-ui/lib/cli/ui/prompt.rb +297 -0
  60. data/vendor/cli-ui/lib/cli/ui/prompt/interactive_options.rb +484 -0
  61. data/vendor/cli-ui/lib/cli/ui/prompt/options_handler.rb +29 -0
  62. data/vendor/cli-ui/lib/cli/ui/spinner.rb +66 -0
  63. data/vendor/cli-ui/lib/cli/ui/spinner/async.rb +40 -0
  64. data/vendor/cli-ui/lib/cli/ui/spinner/spin_group.rb +263 -0
  65. data/vendor/cli-ui/lib/cli/ui/stdout_router.rb +232 -0
  66. data/vendor/cli-ui/lib/cli/ui/terminal.rb +46 -0
  67. data/vendor/cli-ui/lib/cli/ui/truncater.rb +102 -0
  68. data/vendor/cli-ui/lib/cli/ui/version.rb +5 -0
  69. data/vendor/cli-ui/lib/cli/ui/widgets.rb +77 -0
  70. data/vendor/cli-ui/lib/cli/ui/widgets/base.rb +27 -0
  71. data/vendor/cli-ui/lib/cli/ui/widgets/status.rb +61 -0
  72. data/vendor/cli-ui/lib/cli/ui/wrap.rb +56 -0
  73. data/vendor/cli-ui/test/cli/ui/ansi_test.rb +32 -0
  74. data/vendor/cli-ui/test/cli/ui/cli_ui_test.rb +23 -0
  75. data/vendor/cli-ui/test/cli/ui/color_test.rb +40 -0
  76. data/vendor/cli-ui/test/cli/ui/formatter_test.rb +79 -0
  77. data/vendor/cli-ui/test/cli/ui/glyph_test.rb +68 -0
  78. data/vendor/cli-ui/test/cli/ui/printer_test.rb +103 -0
  79. data/vendor/cli-ui/test/cli/ui/progress_test.rb +46 -0
  80. data/vendor/cli-ui/test/cli/ui/prompt/options_handler_test.rb +39 -0
  81. data/vendor/cli-ui/test/cli/ui/prompt_test.rb +348 -0
  82. data/vendor/cli-ui/test/cli/ui/spinner/spin_group_test.rb +39 -0
  83. data/vendor/cli-ui/test/cli/ui/spinner_test.rb +141 -0
  84. data/vendor/cli-ui/test/cli/ui/stdout_router_test.rb +32 -0
  85. data/vendor/cli-ui/test/cli/ui/terminal_test.rb +26 -0
  86. data/vendor/cli-ui/test/cli/ui/truncater_test.rb +31 -0
  87. data/vendor/cli-ui/test/cli/ui/widgets/status_test.rb +49 -0
  88. data/vendor/cli-ui/test/cli/ui/widgets_test.rb +15 -0
  89. data/vendor/cli-ui/test/test_helper.rb +53 -0
  90. data/vendor/cli-ui/tmp/cache/bootsnap/compile-cache/d9/c036af0f3dc494 +0 -0
  91. data/vendor/cli-ui/tmp/cache/bootsnap/load-path-cache +0 -0
  92. data/vendor/dry-cli/lib/dry/cli/command.rb +2 -1
  93. data/vendor/dry-cli/tmp/cache/bootsnap/compile-cache/ff/a22a5daafbd74c +0 -0
  94. data/vendor/dry-cli/tmp/cache/bootsnap/load-path-cache +0 -0
  95. data/vendor/strings-ansi/tmp/cache/bootsnap/compile-cache/79/49cf49407b370e +0 -0
  96. data/vendor/strings-ansi/tmp/cache/bootsnap/load-path-cache +0 -0
  97. metadata +170 -9
  98. data/lib/rfix/extension/string.rb +0 -12
  99. data/lib/rfix/indicator.rb +0 -19
@@ -0,0 +1,46 @@
1
+ require 'cli/ui'
2
+ require 'io/console'
3
+
4
+ module CLI
5
+ module UI
6
+ module Terminal
7
+ DEFAULT_WIDTH = 80
8
+ DEFAULT_HEIGHT = 24
9
+
10
+ # Returns the width of the terminal, if possible
11
+ # Otherwise will return DEFAULT_WIDTH
12
+ #
13
+ def self.width
14
+ winsize[1]
15
+ end
16
+
17
+ # Returns the width of the terminal, if possible
18
+ # Otherwise, will return DEFAULT_HEIGHT
19
+ #
20
+ def self.height
21
+ winsize[0]
22
+ end
23
+
24
+ def self.winsize
25
+ @winsize ||= begin
26
+ winsize = IO.console.winsize
27
+ setup_winsize_trap
28
+
29
+ if winsize.any?(&:zero?)
30
+ [DEFAULT_HEIGHT, DEFAULT_WIDTH]
31
+ else
32
+ winsize
33
+ end
34
+ rescue
35
+ [DEFAULT_HEIGHT, DEFAULT_WIDTH]
36
+ end
37
+ end
38
+
39
+ def self.setup_winsize_trap
40
+ @winsize_trap ||= Signal.trap('WINCH') do
41
+ @winsize = nil
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cli/ui'
4
+
5
+ module CLI
6
+ module UI
7
+ # Truncater truncates a string to a provided printable width.
8
+ module Truncater
9
+ PARSE_ROOT = :root
10
+ PARSE_ANSI = :ansi
11
+ PARSE_ESC = :esc
12
+ PARSE_ZWJ = :zwj
13
+
14
+ ESC = 0x1b
15
+ LEFT_SQUARE_BRACKET = 0x5b
16
+ ZWJ = 0x200d # emojipedia.org/emoji-zwj-sequences
17
+ SEMICOLON = 0x3b
18
+
19
+ # EMOJI_RANGE in particular is super inaccurate. This is best-effort.
20
+ # If you need this to be more accurate, we'll almost certainly accept a
21
+ # PR improving it.
22
+ EMOJI_RANGE = 0x1f300..0x1f5ff
23
+ NUMERIC_RANGE = 0x30..0x39
24
+ LC_ALPHA_RANGE = 0x40..0x5a
25
+ UC_ALPHA_RANGE = 0x60..0x71
26
+
27
+ TRUNCATED = "\x1b[0m…"
28
+
29
+ class << self
30
+ def call(text, printing_width)
31
+ return text if text.size <= printing_width
32
+
33
+ width = 0
34
+ mode = PARSE_ROOT
35
+ truncation_index = nil
36
+
37
+ codepoints = text.codepoints
38
+ codepoints.each.with_index do |cp, index|
39
+ case mode
40
+ when PARSE_ROOT
41
+ case cp
42
+ when ESC # non-printable, followed by some more non-printables.
43
+ mode = PARSE_ESC
44
+ when ZWJ # non-printable, followed by another non-printable.
45
+ mode = PARSE_ZWJ
46
+ else
47
+ width += width(cp)
48
+ if width >= printing_width
49
+ truncation_index ||= index
50
+ # it looks like we could break here but we still want the
51
+ # width calculation for the rest of the characters.
52
+ end
53
+ end
54
+ when PARSE_ESC
55
+ mode = case cp
56
+ when LEFT_SQUARE_BRACKET
57
+ PARSE_ANSI
58
+ else
59
+ PARSE_ROOT
60
+ end
61
+ when PARSE_ANSI
62
+ # ANSI escape codes preeeetty much have the format of:
63
+ # \x1b[0-9;]+[A-Za-z]
64
+ case cp
65
+ when NUMERIC_RANGE, SEMICOLON
66
+ when LC_ALPHA_RANGE, UC_ALPHA_RANGE
67
+ mode = PARSE_ROOT
68
+ else
69
+ # unexpected. let's just go back to the root state I guess?
70
+ mode = PARSE_ROOT
71
+ end
72
+ when PARSE_ZWJ
73
+ # consume any character and consider it as having no width
74
+ # width(x+ZWJ+y) = width(x).
75
+ mode = PARSE_ROOT
76
+ end
77
+ end
78
+
79
+ # Without the `width <= printing_width` check, we truncate
80
+ # "foo\x1b[0m" for a width of 3, but it should not be truncated.
81
+ # It's specifically for the case where we decided "Yes, this is the
82
+ # point at which we'd have to add a truncation!" but it's actually
83
+ # the end of the string.
84
+ return text if !truncation_index || width <= printing_width
85
+
86
+ codepoints[0...truncation_index].pack('U*') + TRUNCATED
87
+ end
88
+
89
+ private
90
+
91
+ def width(printable_codepoint)
92
+ case printable_codepoint
93
+ when EMOJI_RANGE
94
+ 2
95
+ else
96
+ 1
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,5 @@
1
+ module CLI
2
+ module UI
3
+ VERSION = '1.5.1'
4
+ end
5
+ end
@@ -0,0 +1,77 @@
1
+ require('cli/ui')
2
+
3
+ module CLI
4
+ module UI
5
+ # Widgets are formatter objects with more custom implementations than the
6
+ # other features, which all center around formatting text with colours,
7
+ # etc.
8
+ #
9
+ # If you want to extend CLI::UI with your own widgets, you may want to do
10
+ # something like this:
11
+ #
12
+ # require('cli/ui')
13
+ # class MyWidget < CLI::UI::Widgets::Base
14
+ # # ...
15
+ # end
16
+ # CLI::UI::Widgets.register('my-widget') { MyWidget }
17
+ # puts(CLI::UI.fmt("{{@widget/my-widget:args}}"))
18
+ module Widgets
19
+ MAP = {}
20
+
21
+ autoload(:Base, 'cli/ui/widgets/base')
22
+
23
+ def self.register(name, &cb)
24
+ MAP[name] = cb
25
+ end
26
+
27
+ autoload(:Status, 'cli/ui/widgets/status')
28
+ register('status') { Widgets::Status }
29
+
30
+ # Looks up a widget by handle
31
+ #
32
+ # ==== Raises
33
+ # Raises InvalidWidgetHandle if the widget is not available.
34
+ #
35
+ # ==== Returns
36
+ # A callable widget, to be invoked like `.call(argstring)`
37
+ #
38
+ def self.lookup(handle)
39
+ MAP.fetch(handle.to_s).call
40
+ rescue KeyError, NameError
41
+ raise(InvalidWidgetHandle, handle)
42
+ end
43
+
44
+ # All available widgets by name
45
+ #
46
+ def self.available
47
+ MAP.keys
48
+ end
49
+
50
+ class InvalidWidgetHandle < ArgumentError
51
+ def initialize(handle)
52
+ super
53
+ @handle = handle
54
+ end
55
+
56
+ def message
57
+ keys = Widget.available.join(',')
58
+ "invalid widget handle: #{@handle} " \
59
+ "-- must be one of CLI::UI::Widgets.available (#{keys})"
60
+ end
61
+ end
62
+
63
+ class InvalidWidgetArguments < ArgumentError
64
+ def initialize(argstring, pattern)
65
+ super
66
+ @argstring = argstring
67
+ @pattern = pattern
68
+ end
69
+
70
+ def message
71
+ "invalid widget arguments: #{@argstring} " \
72
+ "-- must match pattern: #{@pattern.inspect}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,27 @@
1
+ require('cli/ui')
2
+
3
+ module CLI
4
+ module UI
5
+ module Widgets
6
+ class Base
7
+ def self.call(argstring)
8
+ new(argstring).render
9
+ end
10
+
11
+ def initialize(argstring)
12
+ pat = self.class.argparse_pattern
13
+ unless (@match_data = pat.match(argstring))
14
+ raise(Widgets::InvalidWidgetArguments.new(argstring, pat))
15
+ end
16
+ @match_data.names.each do |name|
17
+ instance_variable_set(:"@#{name}", @match_data[name])
18
+ end
19
+ end
20
+
21
+ def self.argparse_pattern
22
+ const_get(:ARGPARSE_PATTERN)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,61 @@
1
+ # frozen-string-literal: true
2
+ require('cli/ui')
3
+
4
+ module CLI
5
+ module UI
6
+ module Widgets
7
+ class Status < Widgets::Base
8
+ ARGPARSE_PATTERN = %r{
9
+ \A (?<succeeded> \d+)
10
+ : (?<failed> \d+)
11
+ : (?<working> \d+)
12
+ : (?<pending> \d+) \z
13
+ }x # e.g. "1:23:3:404"
14
+ OPEN = Color::RESET.code + Color::BOLD.code + '[' + Color::RESET.code
15
+ CLOSE = Color::RESET.code + Color::BOLD.code + ']' + Color::RESET.code
16
+ ARROW = Color::RESET.code + Color::GRAY.code + '◂' + Color::RESET.code
17
+ COMMA = Color::RESET.code + Color::GRAY.code + ',' + Color::RESET.code
18
+
19
+ SPINNER_STOPPED = '⠿'
20
+ EMPTY_SET = '∅'
21
+
22
+ def render
23
+ if zero?(@succeeded) && zero?(@failed) && zero?(@working) && zero?(@pending)
24
+ Color::RESET.code + Color::BOLD.code + EMPTY_SET + Color::RESET.code
25
+ else
26
+ # [ 0✓ , 2✗ ◂ 3⠼ ◂ 4⌛︎ ]
27
+ "#{OPEN}#{succeeded_part}#{COMMA}#{failed_part}#{ARROW}#{working_part}#{ARROW}#{pending_part}#{CLOSE}"
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def zero?(num_str)
34
+ num_str == '0'
35
+ end
36
+
37
+ def colorize_if_nonzero(num_str, rune, color)
38
+ color = Color::GRAY if zero?(num_str)
39
+ color.code + num_str + rune
40
+ end
41
+
42
+ def succeeded_part
43
+ colorize_if_nonzero(@succeeded, Glyph::CHECK.char, Color::GREEN)
44
+ end
45
+
46
+ def failed_part
47
+ colorize_if_nonzero(@failed, Glyph::X.char, Color::RED)
48
+ end
49
+
50
+ def working_part
51
+ rune = zero?(@working) ? SPINNER_STOPPED : Spinner.current_rune
52
+ colorize_if_nonzero(@working, rune, Color::BLUE)
53
+ end
54
+
55
+ def pending_part
56
+ colorize_if_nonzero(@pending, Glyph::HOURGLASS.char, Color::WHITE)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,56 @@
1
+ # coding: utf-8
2
+ require 'cli/ui'
3
+ require 'cli/ui/frame/frame_stack'
4
+ require 'cli/ui/frame/frame_style'
5
+
6
+ module CLI
7
+ module UI
8
+ class Wrap
9
+ def initialize(input)
10
+ @input = input
11
+ end
12
+
13
+ def wrap
14
+ max_width = Terminal.width - Frame.prefix_width
15
+ width = 0
16
+ final = []
17
+ # Create an alternation of format codes of parameter lengths 1-20, since + and {1,n} not allowed in lookbehind
18
+ format_codes = (1..20).map { |n| /\x1b\[[\d;]{#{n}}m/ }.join('|')
19
+ codes = ''
20
+ @input.split(/(?=\s|\x1b\[[\d;]+m|\r)|(?<=\s|#{format_codes})/).each do |token|
21
+ case token
22
+ when '\x1B[0?m'
23
+ codes = ''
24
+ final << token
25
+ when /\x1b\[[\d;]+m/
26
+ codes += token # Track in use format codes so that they are resent after frame coloring
27
+ final << token
28
+ when "\n"
29
+ final << "\n#{codes}"
30
+ width = 0
31
+ when /\s/
32
+ token_width = ANSI.printing_width(token)
33
+ if width + token_width <= max_width
34
+ final << token
35
+ width += token_width
36
+ else
37
+ final << "\n#{codes}"
38
+ width = 0
39
+ end
40
+ else
41
+ token_width = ANSI.printing_width(token)
42
+ if width + token_width <= max_width
43
+ final << token
44
+ width += token_width
45
+ else
46
+ final << "\n#{codes}"
47
+ final << token
48
+ width = token_width
49
+ end
50
+ end
51
+ end
52
+ final.join
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ require 'test_helper'
2
+
3
+ module CLI
4
+ module UI
5
+ class ANSITest < MiniTest::Test
6
+ def test_sgr
7
+ assert_equal("\x1b[1;34m", ANSI.sgr('1;34'))
8
+ end
9
+
10
+ def test_printing_width
11
+ assert_equal(4, ANSI.printing_width("\x1b[38;2;100;100;100mtest\x1b[0m"))
12
+ assert_equal(0, ANSI.printing_width(''))
13
+
14
+ assert_equal(3, ANSI.printing_width('>🔧<'))
15
+ assert_equal(1, ANSI.printing_width('👩‍💻'))
16
+ end
17
+
18
+ def test_line_skip_with_shift
19
+ next_line_expected = "\e[1B\e[1G"
20
+ previous_line_expected = "\e[1A\e[1G"
21
+
22
+ assert_equal(next_line_expected, ANSI.next_line)
23
+ assert_equal(previous_line_expected, ANSI.previous_line)
24
+
25
+ CLI::UI::OS.stubs(:current).returns(CLI::UI::OS::Windows)
26
+
27
+ assert_equal("#{next_line_expected}\e[1D", ANSI.next_line)
28
+ assert_equal("#{previous_line_expected}\e[1D", ANSI.previous_line)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ require 'test_helper'
2
+
3
+ module CLI
4
+ class UITest < MiniTest::Test
5
+ def test_resolve_test
6
+ input = 'a{{blue:b {{*}}{{bold:c {{red:d}}}}{{bold: e}}}} f'
7
+ expected = "\e[0ma\e[0;94mb \e[0;33m⭑\e[0;94;1mc \e[0;94;1;31md\e[0;94;1m e\e[0m f"
8
+ actual = CLI::UI.resolve_text(input)
9
+ assert_equal(expected, actual)
10
+ end
11
+
12
+ def test_color
13
+ prev = CLI::UI.enable_color?
14
+
15
+ CLI::UI.enable_color = true
16
+ assert_equal("\e[0;31ma\e[0m", CLI::UI.fmt('{{red:a}}'))
17
+ CLI::UI.enable_color = false
18
+ assert_equal('a', CLI::UI.fmt('{{red:a}}'))
19
+ ensure
20
+ CLI::UI.enable_color = prev
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+
3
+ module CLI
4
+ module UI
5
+ class ColorTest < MiniTest::Test
6
+ def test_colors
7
+ assert_equal("\x1b[31m", Color::RED.code)
8
+ assert_equal("\x1b[32m", Color::GREEN.code)
9
+ assert_equal("\x1b[33m", Color::YELLOW.code)
10
+ assert_equal("\x1b[94m", Color::BLUE.code)
11
+ assert_equal("\x1b[35m", Color::MAGENTA.code)
12
+ assert_equal("\x1b[36m", Color::CYAN.code)
13
+ assert_equal("\x1b[0m", Color::RESET.code)
14
+ assert_equal("\x1b[1m", Color::BOLD.code)
15
+ assert_equal("\x1b[97m", Color::WHITE.code)
16
+
17
+ assert_equal('36', Color::CYAN.sgr)
18
+ assert_equal(:bold, Color::BOLD.name)
19
+
20
+ assert_equal(Color::BLUE, Color.lookup(:blue))
21
+ assert_equal(Color::RESET, Color.lookup(:reset))
22
+
23
+ assert_raises(Color::InvalidColorName) do
24
+ Color.lookup(:foobar)
25
+ end
26
+ end
27
+
28
+ def test_useful_exception
29
+ e = begin
30
+ Color.lookup(:foobar)
31
+ rescue => e
32
+ e
33
+ end
34
+ assert_match(/invalid color: :foobar/, e.message) # error
35
+ assert_match(/Color\.available/, e.message) # where to find colors
36
+ assert_match(/:green/, e.message) # list of valid colors
37
+ end
38
+ end
39
+ end
40
+ end