rfix 2.0.4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,84 @@
1
+ require 'cli/ui'
2
+
3
+ module CLI
4
+ module UI
5
+ class Color
6
+ attr_reader :sgr, :name, :code
7
+
8
+ # Creates a new color mapping
9
+ # Signatures can be found here:
10
+ # https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
11
+ #
12
+ # ==== Attributes
13
+ #
14
+ # * +sgr+ - The color signature
15
+ # * +name+ - The name of the color
16
+ #
17
+ def initialize(sgr, name)
18
+ @sgr = sgr
19
+ @code = CLI::UI::ANSI.sgr(sgr)
20
+ @name = name
21
+ end
22
+
23
+ RED = new('31', :red)
24
+ GREEN = new('32', :green)
25
+ YELLOW = new('33', :yellow)
26
+ # default blue is low-contrast against black in some default terminal color scheme
27
+ BLUE = new('94', :blue) # 9x = high-intensity fg color x
28
+ MAGENTA = new('35', :magenta)
29
+ CYAN = new('36', :cyan)
30
+ RESET = new('0', :reset)
31
+ BOLD = new('1', :bold)
32
+ WHITE = new('97', :white)
33
+
34
+ # 240 is very dark gray; 255 is very light gray. 244 is somewhat dark.
35
+ GRAY = new('38;5;244', :grey)
36
+
37
+ MAP = {
38
+ red: RED,
39
+ green: GREEN,
40
+ yellow: YELLOW,
41
+ blue: BLUE,
42
+ magenta: MAGENTA,
43
+ cyan: CYAN,
44
+ reset: RESET,
45
+ bold: BOLD,
46
+ gray: GRAY,
47
+ }.freeze
48
+
49
+ class InvalidColorName < ArgumentError
50
+ def initialize(name)
51
+ super
52
+ @name = name
53
+ end
54
+
55
+ def message
56
+ keys = Color.available.map(&:inspect).join(',')
57
+ "invalid color: #{@name.inspect} " \
58
+ "-- must be one of CLI::UI::Color.available (#{keys})"
59
+ end
60
+ end
61
+
62
+ # Looks up a color code by name
63
+ #
64
+ # ==== Raises
65
+ # Raises a InvalidColorName if the color is not available
66
+ # You likely need to add it to the +MAP+ or you made a typo
67
+ #
68
+ # ==== Returns
69
+ # Returns a color code
70
+ #
71
+ def self.lookup(name)
72
+ MAP.fetch(name)
73
+ rescue KeyError
74
+ raise InvalidColorName, name
75
+ end
76
+
77
+ # All available colors by name
78
+ #
79
+ def self.available
80
+ MAP.keys
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+ require('cli/ui')
3
+ require('strscan')
4
+
5
+ module CLI
6
+ module UI
7
+ class Formatter
8
+ # Available mappings of formattings
9
+ # To use any of them, you can use {{<key>:<string>}}
10
+ # There are presentational (colours and formatters)
11
+ # and semantic (error, info, command) formatters available
12
+ #
13
+ SGR_MAP = {
14
+ # presentational
15
+ 'red' => '31',
16
+ 'green' => '32',
17
+ 'yellow' => '33',
18
+ # default blue is low-contrast against black in some default terminal color scheme
19
+ 'blue' => '94', # 9x = high-intensity fg color x
20
+ 'magenta' => '35',
21
+ 'cyan' => '36',
22
+ 'bold' => '1',
23
+ 'italic' => '3',
24
+ 'underline' => '4',
25
+ 'reset' => '0',
26
+
27
+ # semantic
28
+ 'error' => '31', # red
29
+ 'success' => '32', # success
30
+ 'warning' => '33', # yellow
31
+ 'info' => '94', # bright blue
32
+ 'command' => '36', # cyan
33
+ }.freeze
34
+
35
+ BEGIN_EXPR = '{{'
36
+ END_EXPR = '}}'
37
+
38
+ SCAN_WIDGET = %r[@widget/(?<handle>\w+):(?<args>.*?)}}]
39
+ SCAN_FUNCNAME = /\w+:/
40
+ SCAN_GLYPH = /.}}/
41
+ SCAN_BODY = %r{
42
+ .*?
43
+ (
44
+ #{BEGIN_EXPR} |
45
+ #{END_EXPR} |
46
+ \z
47
+ )
48
+ }mx
49
+
50
+ DISCARD_BRACES = 0..-3
51
+
52
+ LITERAL_BRACES = :__literal_braces__
53
+
54
+ class FormatError < StandardError
55
+ attr_accessor :input, :index
56
+
57
+ def initialize(message = nil, input = nil, index = nil)
58
+ super(message)
59
+ @input = input
60
+ @index = index
61
+ end
62
+ end
63
+
64
+ # Initialize a formatter with text.
65
+ #
66
+ # ===== Attributes
67
+ #
68
+ # * +text+ - the text to format
69
+ #
70
+ def initialize(text)
71
+ @text = text
72
+ end
73
+
74
+ # Format the text using a map.
75
+ #
76
+ # ===== Attributes
77
+ #
78
+ # * +sgr_map+ - the mapping of the formattings. Defaults to +SGR_MAP+
79
+ #
80
+ # ===== Options
81
+ #
82
+ # * +:enable_color+ - enable color output? Default is true unless output is redirected
83
+ #
84
+ def format(sgr_map = SGR_MAP, enable_color: CLI::UI.enable_color?)
85
+ @nodes = []
86
+ stack = parse_body(StringScanner.new(@text))
87
+ prev_fmt = nil
88
+ content = @nodes.each_with_object(+'') do |(text, fmt), str|
89
+ if prev_fmt != fmt && enable_color
90
+ text = apply_format(text, fmt, sgr_map)
91
+ end
92
+ str << text
93
+ prev_fmt = fmt
94
+ end
95
+
96
+ stack.reject! { |e| e == LITERAL_BRACES }
97
+
98
+ return content unless enable_color
99
+ return content if stack == prev_fmt
100
+
101
+ unless stack.empty? && (@nodes.size.zero? || @nodes.last[1].empty?)
102
+ content << apply_format('', stack, sgr_map)
103
+ end
104
+ content
105
+ end
106
+
107
+ private
108
+
109
+ def apply_format(text, fmt, sgr_map)
110
+ sgr = fmt.each_with_object(+'0') do |name, str|
111
+ next if name == LITERAL_BRACES
112
+ begin
113
+ str << ';' << sgr_map.fetch(name)
114
+ rescue KeyError
115
+ raise FormatError.new(
116
+ "invalid format specifier: #{name}",
117
+ @text,
118
+ -1
119
+ )
120
+ end
121
+ end
122
+ CLI::UI::ANSI.sgr(sgr) + text
123
+ end
124
+
125
+ def parse_expr(sc, stack)
126
+ if (match = sc.scan(SCAN_GLYPH))
127
+ glyph_handle = match[0]
128
+ begin
129
+ glyph = Glyph.lookup(glyph_handle)
130
+ emit(glyph.char, [glyph.color.name.to_s])
131
+ rescue Glyph::InvalidGlyphHandle
132
+ index = sc.pos - 2 # rewind past '}}'
133
+ raise FormatError.new(
134
+ "invalid glyph handle at index #{index}: '#{glyph_handle}'",
135
+ @text,
136
+ index
137
+ )
138
+ end
139
+ elsif (match = sc.scan(SCAN_WIDGET))
140
+ match_data = SCAN_WIDGET.match(match) # Regexp.last_match doesn't work here
141
+ widget_handle = match_data['handle']
142
+ begin
143
+ widget = Widgets.lookup(widget_handle)
144
+ emit(widget.call(match_data['args']), stack)
145
+ rescue Widgets::InvalidWidgetHandle
146
+ index = sc.pos - 2 # rewind past '}}'
147
+ raise(FormatError.new(
148
+ "invalid widget handle at index #{index}: '#{widget_handle}'",
149
+ @text, index,
150
+ ))
151
+ end
152
+ elsif (match = sc.scan(SCAN_FUNCNAME))
153
+ funcname = match.chop
154
+ stack.push(funcname)
155
+ else
156
+ # We read a {{ but it's not apparently Formatter syntax.
157
+ # We could error, but it's nicer to just pass through as text.
158
+ # We do kind of assume that the text will probably have balanced
159
+ # pairs of {{ }} at least.
160
+ emit('{{', stack)
161
+ stack.push(LITERAL_BRACES)
162
+ end
163
+ parse_body(sc, stack)
164
+ stack
165
+ end
166
+
167
+ def parse_body(sc, stack = [])
168
+ match = sc.scan(SCAN_BODY)
169
+ if match&.end_with?(BEGIN_EXPR)
170
+ emit(match[DISCARD_BRACES], stack)
171
+ parse_expr(sc, stack)
172
+ elsif match&.end_with?(END_EXPR)
173
+ emit(match[DISCARD_BRACES], stack)
174
+ if stack.pop == LITERAL_BRACES
175
+ emit('}}', stack)
176
+ end
177
+ parse_body(sc, stack)
178
+ elsif match
179
+ emit(match, stack)
180
+ else
181
+ emit(sc.rest, stack)
182
+ end
183
+ stack
184
+ end
185
+
186
+ def emit(text, stack)
187
+ return if text.nil? || text.empty?
188
+ @nodes << [text, stack.reject { |n| n == LITERAL_BRACES }]
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,269 @@
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
+ module Frame
9
+ class UnnestedFrameException < StandardError; end
10
+ class << self
11
+ DEFAULT_FRAME_COLOR = CLI::UI.resolve_color(:cyan)
12
+
13
+ def frame_style
14
+ @frame_style ||= FrameStyle::Box
15
+ end
16
+
17
+ # Set the default frame style.
18
+ #
19
+ # Raises ArgumentError if +frame_style+ is not valid
20
+ #
21
+ # ==== Attributes
22
+ #
23
+ # * +symbol+ or +FrameStyle+ - the default frame style to use for frames
24
+ #
25
+ def frame_style=(frame_style)
26
+ @frame_style = CLI::UI.resolve_style(frame_style)
27
+ end
28
+
29
+ # Opens a new frame. Can be nested
30
+ # Can be invoked in two ways: block and blockless
31
+ # * In block form, the frame is closed automatically when the block returns
32
+ # * In blockless form, caller MUST call +Frame.close+ when the frame is logically done
33
+ # * Blockless form is strongly discouraged in cases where block form can be made to work
34
+ #
35
+ # https://user-images.githubusercontent.com/3074765/33799861-cb5dcb5c-dd01-11e7-977e-6fad38cee08c.png
36
+ #
37
+ # The return value of the block determines if the block is a "success" or a "failure"
38
+ #
39
+ # ==== Attributes
40
+ #
41
+ # * +text+ - (required) the text/title to output in the frame
42
+ #
43
+ # ==== Options
44
+ #
45
+ # * +:color+ - The color of the frame. Defaults to +DEFAULT_FRAME_COLOR+
46
+ # * +:failure_text+ - If the block failed, what do we output? Defaults to nil
47
+ # * +:success_text+ - If the block succeeds, what do we output? Defaults to nil
48
+ # * +:timing+ - How long did the frame content take? Invalid for blockless. Defaults to true for the block form
49
+ # * +frame_style+ - The frame style to use for this frame
50
+ #
51
+ # ==== Example
52
+ #
53
+ # ===== Block Form (Assumes +CLI::UI::StdoutRouter.enable+ has been called)
54
+ #
55
+ # CLI::UI::Frame.open('Open') { puts 'hi' }
56
+ #
57
+ # Default Output:
58
+ # ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
59
+ # ┃ hi
60
+ # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ (0.0s) ━━
61
+ #
62
+ # ===== Blockless Form
63
+ #
64
+ # CLI::UI::Frame.open('Open')
65
+ #
66
+ # Default Output:
67
+ # ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
68
+ #
69
+ #
70
+ def open(
71
+ text,
72
+ color: DEFAULT_FRAME_COLOR,
73
+ failure_text: nil,
74
+ success_text: nil,
75
+ timing: nil,
76
+ frame_style: self.frame_style
77
+ )
78
+ frame_style = CLI::UI.resolve_style(frame_style)
79
+ color = CLI::UI.resolve_color(color)
80
+
81
+ unless block_given?
82
+ if failure_text
83
+ raise ArgumentError, 'failure_text is not compatible with blockless invocation'
84
+ elsif success_text
85
+ raise ArgumentError, 'success_text is not compatible with blockless invocation'
86
+ elsif timing
87
+ raise ArgumentError, 'timing is not compatible with blockless invocation'
88
+ end
89
+ end
90
+
91
+ t_start = Time.now
92
+ CLI::UI.raw do
93
+ print(prefix.chop)
94
+ puts frame_style.open(text, color: color)
95
+ end
96
+ FrameStack.push(color: color, style: frame_style)
97
+
98
+ return unless block_given?
99
+
100
+ closed = false
101
+ begin
102
+ success = false
103
+ success = yield
104
+ rescue
105
+ closed = true
106
+ t_diff = elasped(t_start, timing)
107
+ close(failure_text, color: :red, elapsed: t_diff)
108
+ raise
109
+ else
110
+ success
111
+ ensure
112
+ unless closed
113
+ t_diff = elasped(t_start, timing)
114
+ if success != false
115
+ close(success_text, color: color, elapsed: t_diff)
116
+ else
117
+ close(failure_text, color: :red, elapsed: t_diff)
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ # Adds a divider in a frame
124
+ # Used to separate information within a single frame
125
+ #
126
+ # ==== Attributes
127
+ #
128
+ # * +text+ - (required) the text/title to output in the frame
129
+ #
130
+ # ==== Options
131
+ #
132
+ # * +:color+ - The color of the frame. Defaults to +DEFAULT_FRAME_COLOR+
133
+ # * +frame_style+ - The frame style to use for this frame
134
+ #
135
+ # ==== Example
136
+ #
137
+ # CLI::UI::Frame.open('Open') { CLI::UI::Frame.divider('Divider') }
138
+ #
139
+ # Default Output:
140
+ # ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
141
+ # ┣━━ Divider ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
142
+ # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
143
+ #
144
+ # ==== Raises
145
+ #
146
+ # MUST be inside an open frame or it raises a +UnnestedFrameException+
147
+ #
148
+ def divider(text, color: nil, frame_style: nil)
149
+ fs_item = FrameStack.pop
150
+ raise UnnestedFrameException, 'No frame nesting to unnest' unless fs_item
151
+
152
+ color = CLI::UI.resolve_color(color) || fs_item.color
153
+ frame_style = CLI::UI.resolve_style(frame_style) || fs_item.frame_style
154
+
155
+ CLI::UI.raw do
156
+ print(prefix.chop)
157
+ puts frame_style.divider(text, color: color)
158
+ end
159
+
160
+ FrameStack.push(fs_item)
161
+ end
162
+
163
+ # Closes a frame
164
+ # Automatically called for a block-form +open+
165
+ #
166
+ # ==== Attributes
167
+ #
168
+ # * +text+ - (required) the text/title to output in the frame
169
+ #
170
+ # ==== Options
171
+ #
172
+ # * +:color+ - The color of the frame. Defaults to nil
173
+ # * +:elapsed+ - How long did the frame take? Defaults to nil
174
+ # * +frame_style+ - The frame style to use for this frame. Defaults to nil
175
+ #
176
+ # ==== Example
177
+ #
178
+ # CLI::UI::Frame.close('Close')
179
+ #
180
+ # Default Output:
181
+ # ┗━━ Close ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
182
+ #
183
+ # ==== Raises
184
+ #
185
+ # MUST be inside an open frame or it raises a +UnnestedFrameException+
186
+ #
187
+ def close(text, color: nil, elapsed: nil, frame_style: nil)
188
+ fs_item = FrameStack.pop
189
+ raise UnnestedFrameException, 'No frame nesting to unnest' unless fs_item
190
+
191
+ color = CLI::UI.resolve_color(color) || fs_item.color
192
+ frame_style = CLI::UI.resolve_style(frame_style) || fs_item.frame_style
193
+
194
+ kwargs = {}
195
+ if elapsed
196
+ kwargs[:right_text] = "(#{elapsed.round(2)}s)"
197
+ end
198
+
199
+ CLI::UI.raw do
200
+ print(prefix.chop)
201
+ puts frame_style.close(text, color: color, **kwargs)
202
+ end
203
+ end
204
+
205
+ # Determines the prefix of a frame entry taking multi-nested frames into account
206
+ #
207
+ # ==== Options
208
+ #
209
+ # * +:color+ - The color of the prefix. Defaults to +Thread.current[:cliui_frame_color_override]+
210
+ #
211
+ def prefix(color: Thread.current[:cliui_frame_color_override])
212
+ +''.tap do |output|
213
+ items = FrameStack.items
214
+
215
+ items[0..-2].each do |item|
216
+ output << item.color.code << item.frame_style.prefix
217
+ end
218
+
219
+ if (item = items.last)
220
+ final_color = color || item.color
221
+ output << CLI::UI.resolve_color(final_color).code \
222
+ << item.frame_style.prefix \
223
+ << ' ' \
224
+ << CLI::UI::Color::RESET.code
225
+ end
226
+ end
227
+ end
228
+
229
+ # The width of a prefix given the number of Frames in the stack
230
+ def prefix_width
231
+ w = FrameStack.items.reduce(0) do |width, item|
232
+ width + item.frame_style.prefix_width
233
+ end
234
+
235
+ w.zero? ? w : w + 1
236
+ end
237
+
238
+ # Override a color for a given thread.
239
+ #
240
+ # ==== Attributes
241
+ #
242
+ # * +color+ - The color to override to
243
+ #
244
+ def with_frame_color_override(color)
245
+ prev = Thread.current[:cliui_frame_color_override]
246
+ Thread.current[:cliui_frame_color_override] = color
247
+ yield
248
+ ensure
249
+ Thread.current[:cliui_frame_color_override] = prev
250
+ end
251
+
252
+ private
253
+
254
+ # If timing is:
255
+ # Numeric: return it
256
+ # false: return nil
257
+ # true or nil: defaults to Time.new
258
+ # Time: return the difference with start
259
+ def elasped(start, timing)
260
+ return timing if timing.is_a?(Numeric)
261
+ return if timing.is_a?(FalseClass)
262
+
263
+ timing = Time.new if timing.is_a?(TrueClass) || timing.nil?
264
+ timing - start
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end