shopify-cli 0.9.2 → 0.9.3

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/docs/_config.yml +3 -0
  4. data/docs/_data/nav.yml +9 -0
  5. data/docs/getting-started/index.md +5 -40
  6. data/docs/getting-started/install/index.md +39 -0
  7. data/docs/getting-started/migrate/index.md +63 -0
  8. data/docs/getting-started/uninstall/index.md +37 -0
  9. data/docs/getting-started/upgrade/index.md +37 -0
  10. data/docs/index.md +5 -6
  11. data/lib/project_types/extension/cli.rb +2 -1
  12. data/lib/project_types/extension/commands/tunnel.rb +1 -1
  13. data/lib/project_types/extension/forms/register.rb +2 -3
  14. data/lib/project_types/extension/graphql/get_app_by_api_key.graphql +9 -0
  15. data/lib/project_types/extension/tasks/converters/app_converter.rb +27 -0
  16. data/lib/project_types/extension/tasks/get_app.rb +22 -0
  17. data/lib/project_types/extension/tasks/get_apps.rb +1 -6
  18. data/lib/project_types/script/cli.rb +3 -2
  19. data/lib/project_types/script/commands/create.rb +5 -4
  20. data/lib/project_types/script/errors.rb +1 -0
  21. data/lib/project_types/script/forms/create.rb +8 -4
  22. data/lib/project_types/script/layers/application/create_script.rb +11 -18
  23. data/lib/project_types/script/layers/application/project_dependencies.rb +0 -5
  24. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +106 -0
  25. data/lib/project_types/script/layers/infrastructure/errors.rb +1 -1
  26. data/lib/project_types/script/layers/infrastructure/project_creator.rb +23 -0
  27. data/lib/project_types/script/layers/infrastructure/script_repository.rb +0 -33
  28. data/lib/project_types/script/messages/messages.rb +5 -6
  29. data/lib/project_types/script/templates/ts/as-pect.d.ts +1 -0
  30. data/lib/project_types/script/ui/error_handler.rb +5 -0
  31. data/lib/shopify-cli/tunnel.rb +33 -1
  32. data/lib/shopify-cli/version.rb +1 -1
  33. data/vendor/deps/cli-ui/REVISION +1 -1
  34. data/vendor/deps/cli-ui/lib/cli/ui.rb +52 -11
  35. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +11 -7
  36. data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +34 -21
  37. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +107 -149
  38. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +99 -0
  39. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +119 -0
  40. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +158 -0
  41. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +112 -0
  42. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +9 -15
  43. data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +47 -0
  44. data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +9 -7
  45. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +39 -14
  46. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +62 -44
  47. data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +7 -2
  48. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +23 -3
  49. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +34 -10
  50. data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +12 -7
  51. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +26 -16
  52. data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +3 -3
  53. data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +75 -0
  54. data/vendor/deps/cli-ui/lib/cli/ui/widgets/base.rb +27 -0
  55. data/vendor/deps/cli-ui/lib/cli/ui/widgets/status.rb +61 -0
  56. metadata +20 -7
  57. data/lib/project_types/extension/features/tunnel_url.rb +0 -20
  58. data/lib/project_types/script/layers/infrastructure/assemblyscript_dependency_manager.rb +0 -51
  59. data/lib/project_types/script/layers/infrastructure/dependency_manager.rb +0 -36
  60. data/lib/project_types/script/layers/infrastructure/test_suite_repository.rb +0 -62
  61. data/vendor/deps/cli-ui/lib/cli/ui/box.rb +0 -15
@@ -3,14 +3,15 @@ module CLI
3
3
  autoload :ANSI, 'cli/ui/ansi'
4
4
  autoload :Glyph, 'cli/ui/glyph'
5
5
  autoload :Color, 'cli/ui/color'
6
- autoload :Box, 'cli/ui/box'
7
6
  autoload :Frame, 'cli/ui/frame'
7
+ autoload :Printer, 'cli/ui/printer'
8
8
  autoload :Progress, 'cli/ui/progress'
9
9
  autoload :Prompt, 'cli/ui/prompt'
10
10
  autoload :Terminal, 'cli/ui/terminal'
11
11
  autoload :Truncater, 'cli/ui/truncater'
12
12
  autoload :Formatter, 'cli/ui/formatter'
13
13
  autoload :Spinner, 'cli/ui/spinner'
14
+ autoload :Widgets, 'cli/ui/widgets'
14
15
 
15
16
  # Convenience accessor to +CLI::UI::Spinner::SpinGroup+
16
17
  SpinGroup = Spinner::SpinGroup
@@ -42,7 +43,23 @@ module CLI
42
43
  end
43
44
  end
44
45
 
45
- # Conviencence Method for +CLI::UI::Prompt.confirm+
46
+ # Frame style resolution using +CLI::UI::Frame::FrameStyle.lookup+.
47
+ # Will lookup using +FrameStyle.lookup+ if the input is a symbol. Otherwise,
48
+ # we assume it's a valid FrameStyle
49
+ #
50
+ # ==== Attributes
51
+ #
52
+ # * +input+ - frame style to resolve
53
+ def self.resolve_style(input)
54
+ case input
55
+ when Symbol
56
+ CLI::UI::Frame::FrameStyle.lookup(input)
57
+ else
58
+ input
59
+ end
60
+ end
61
+
62
+ # Convenience Method for +CLI::UI::Prompt.confirm+
46
63
  #
47
64
  # ==== Attributes
48
65
  #
@@ -52,18 +69,18 @@ module CLI
52
69
  CLI::UI::Prompt.confirm(question, **kwargs)
53
70
  end
54
71
 
55
- # Conviencence Method for +CLI::UI::Prompt.ask+
72
+ # Convenience Method for +CLI::UI::Prompt.ask+
56
73
  #
57
74
  # ==== Attributes
58
75
  #
59
76
  # * +question+ - question to ask
60
- # * +kwargs+ - arugments for +Prompt.ask+
77
+ # * +kwargs+ - arguments for +Prompt.ask+
61
78
  #
62
79
  def self.ask(question, **kwargs)
63
80
  CLI::UI::Prompt.ask(question, **kwargs)
64
81
  end
65
82
 
66
- # Conviencence Method to resolve text using +CLI::UI::Formatter.format+
83
+ # Convenience Method to resolve text using +CLI::UI::Formatter.format+
67
84
  # Check +CLI::UI::Formatter::SGR_MAP+ for available formatting options
68
85
  #
69
86
  # ==== Attributes
@@ -75,10 +92,10 @@ module CLI
75
92
  return input if input.nil?
76
93
  formatted = CLI::UI::Formatter.new(input).format
77
94
  return formatted unless truncate_to
78
- return CLI::UI::Truncater.call(formatted, truncate_to)
95
+ CLI::UI::Truncater.call(formatted, truncate_to)
79
96
  end
80
97
 
81
- # Conviencence Method to format text using +CLI::UI::Formatter.format+
98
+ # Convenience Method to format text using +CLI::UI::Formatter.format+
82
99
  # Check +CLI::UI::Formatter::SGR_MAP+ for available formatting options
83
100
  #
84
101
  # https://user-images.githubusercontent.com/3074765/33799827-6d0721a2-dd01-11e7-9ab5-c3d455264afe.png
@@ -96,7 +113,18 @@ module CLI
96
113
  CLI::UI::Formatter.new(input).format(enable_color: enable_color)
97
114
  end
98
115
 
99
- # Conviencence Method for +CLI::UI::Frame.open+
116
+ # Convenience Method for +CLI::UI::Printer.puts+
117
+ #
118
+ # ==== Attributes
119
+ #
120
+ # * +msg+ - Message to print
121
+ # * +kwargs+ - keyword arguments for +Printer.puts+
122
+ #
123
+ def self.puts(msg, **kwargs)
124
+ CLI::UI::Printer.puts(msg, **kwargs)
125
+ end
126
+
127
+ # Convenience Method for +CLI::UI::Frame.open+
100
128
  #
101
129
  # ==== Attributes
102
130
  #
@@ -107,7 +135,7 @@ module CLI
107
135
  CLI::UI::Frame.open(*args, &block)
108
136
  end
109
137
 
110
- # Conviencence Method for +CLI::UI::Spinner.spin+
138
+ # Convenience Method for +CLI::UI::Spinner.spin+
111
139
  #
112
140
  # ==== Attributes
113
141
  #
@@ -118,7 +146,7 @@ module CLI
118
146
  CLI::UI::Spinner.spin(*args, &block)
119
147
  end
120
148
 
121
- # Conviencence Method to override frame color using +CLI::UI::Frame.with_frame_color+
149
+ # Convenience Method to override frame color using +CLI::UI::Frame.with_frame_color+
122
150
  #
123
151
  # ==== Attributes
124
152
  #
@@ -142,7 +170,7 @@ module CLI
142
170
  CLI::UI::StdoutRouter.duplicate_output_to = File.open(path, 'w')
143
171
  yield
144
172
  ensure
145
- if file_descriptor = CLI::UI::StdoutRouter.duplicate_output_to
173
+ if (file_descriptor = CLI::UI::StdoutRouter.duplicate_output_to)
146
174
  file_descriptor.close
147
175
  CLI::UI::StdoutRouter.duplicate_output_to = nil
148
176
  end
@@ -181,6 +209,19 @@ module CLI
181
209
  end
182
210
 
183
211
  self.enable_color = $stdout.tty?
212
+
213
+ # Set the default frame style.
214
+ # Convenience method for setting the default frame style with +CLI::UI::Frame.frame_style=+
215
+ #
216
+ # Raises ArgumentError if +frame_style+ is not valid
217
+ #
218
+ # ==== Attributes
219
+ #
220
+ # * +symbol+ - the default frame style to use for frames
221
+ #
222
+ def self.frame_style=(frame_style)
223
+ Frame.frame_style = frame_style.to_sym
224
+ end
184
225
  end
185
226
  end
186
227
 
@@ -31,15 +31,19 @@ module CLI
31
31
  BOLD = new('1', :bold)
32
32
  WHITE = new('97', :white)
33
33
 
34
+ # 240 is very dark gray; 255 is very light gray. 244 is somewhat dark.
35
+ GRAY = new('38;5;244', :grey)
36
+
34
37
  MAP = {
35
- red: RED,
36
- green: GREEN,
37
- yellow: YELLOW,
38
- blue: BLUE,
38
+ red: RED,
39
+ green: GREEN,
40
+ yellow: YELLOW,
41
+ blue: BLUE,
39
42
  magenta: MAGENTA,
40
- cyan: CYAN,
41
- reset: RESET,
42
- bold: BOLD,
43
+ cyan: CYAN,
44
+ reset: RESET,
45
+ bold: BOLD,
46
+ gray: GRAY,
43
47
  }.freeze
44
48
 
45
49
  class InvalidColorName < ArgumentError
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
-
3
- require 'cli/ui'
4
- require 'strscan'
2
+ require('cli/ui')
3
+ require('strscan')
5
4
 
6
5
  module CLI
7
6
  module UI
@@ -13,39 +12,40 @@ module CLI
13
12
  #
14
13
  SGR_MAP = {
15
14
  # presentational
16
- 'red' => '31',
17
- 'green' => '32',
18
- 'yellow' => '33',
19
- # default blue is low-contrast against black in some default terminal color scheme
20
- 'blue' => '94', # 9x = high-intensity fg color x
21
- 'magenta' => '35',
22
- 'cyan' => '36',
23
- 'bold' => '1',
24
- 'italic' => '3',
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',
25
24
  'underline' => '4',
26
- 'reset' => '0',
25
+ 'reset' => '0',
27
26
 
28
27
  # semantic
29
- 'error' => '31', # red
28
+ 'error' => '31', # red
30
29
  'success' => '32', # success
31
30
  'warning' => '33', # yellow
32
- 'info' => '94', # bright blue
31
+ 'info' => '94', # bright blue
33
32
  'command' => '36', # cyan
34
33
  }.freeze
35
34
 
36
35
  BEGIN_EXPR = '{{'
37
36
  END_EXPR = '}}'
38
37
 
38
+ SCAN_WIDGET = %r[@widget/(?<handle>\w+):(?<args>.*?)}}]
39
39
  SCAN_FUNCNAME = /\w+:/
40
40
  SCAN_GLYPH = /.}}/
41
- SCAN_BODY = /
41
+ SCAN_BODY = %r{
42
42
  .*?
43
43
  (
44
44
  #{BEGIN_EXPR} |
45
45
  #{END_EXPR} |
46
46
  \z
47
47
  )
48
- /mx
48
+ }mx
49
49
 
50
50
  DISCARD_BRACES = 0..-3
51
51
 
@@ -123,7 +123,7 @@ module CLI
123
123
  end
124
124
 
125
125
  def parse_expr(sc, stack)
126
- if match = sc.scan(SCAN_GLYPH)
126
+ if (match = sc.scan(SCAN_GLYPH))
127
127
  glyph_handle = match[0]
128
128
  begin
129
129
  glyph = Glyph.lookup(glyph_handle)
@@ -136,7 +136,20 @@ module CLI
136
136
  index
137
137
  )
138
138
  end
139
- elsif match = sc.scan(SCAN_FUNCNAME)
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))
140
153
  funcname = match.chop
141
154
  stack.push(funcname)
142
155
  else
@@ -153,10 +166,10 @@ module CLI
153
166
 
154
167
  def parse_body(sc, stack = [])
155
168
  match = sc.scan(SCAN_BODY)
156
- if match && match.end_with?(BEGIN_EXPR)
169
+ if match&.end_with?(BEGIN_EXPR)
157
170
  emit(match[DISCARD_BRACES], stack)
158
171
  parse_expr(sc, stack)
159
- elsif match && match.end_with?(END_EXPR)
172
+ elsif match&.end_with?(END_EXPR)
160
173
  emit(match[DISCARD_BRACES], stack)
161
174
  if stack.pop == LITERAL_BRACES
162
175
  emit('}}', stack)
@@ -1,4 +1,7 @@
1
+ # coding: utf-8
1
2
  require 'cli/ui'
3
+ require 'cli/ui/frame/frame_stack'
4
+ require 'cli/ui/frame/frame_style'
2
5
 
3
6
  module CLI
4
7
  module UI
@@ -7,6 +10,22 @@ module CLI
7
10
  class << self
8
11
  DEFAULT_FRAME_COLOR = CLI::UI.resolve_color(:cyan)
9
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
+
10
29
  # Opens a new frame. Can be nested
11
30
  # Can be invoked in two ways: block and blockless
12
31
  # * In block form, the frame is closed automatically when the block returns
@@ -27,6 +46,7 @@ module CLI
27
46
  # * +:failure_text+ - If the block failed, what do we output? Defaults to nil
28
47
  # * +:success_text+ - If the block succeeds, what do we output? Defaults to nil
29
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
30
50
  #
31
51
  # ==== Example
32
52
  #
@@ -34,7 +54,7 @@ module CLI
34
54
  #
35
55
  # CLI::UI::Frame.open('Open') { puts 'hi' }
36
56
  #
37
- # Output:
57
+ # Default Output:
38
58
  # ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
39
59
  # ┃ hi
40
60
  # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ (0.0s) ━━
@@ -43,7 +63,7 @@ module CLI
43
63
  #
44
64
  # CLI::UI::Frame.open('Open')
45
65
  #
46
- # Output:
66
+ # Default Output:
47
67
  # ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
48
68
  #
49
69
  #
@@ -52,8 +72,10 @@ module CLI
52
72
  color: DEFAULT_FRAME_COLOR,
53
73
  failure_text: nil,
54
74
  success_text: nil,
55
- timing: nil
75
+ timing: nil,
76
+ frame_style: self.frame_style
56
77
  )
78
+ frame_style = CLI::UI.resolve_style(frame_style)
57
79
  color = CLI::UI.resolve_color(color)
58
80
 
59
81
  unless block_given?
@@ -61,18 +83,17 @@ module CLI
61
83
  raise ArgumentError, "failure_text is not compatible with blockless invocation"
62
84
  elsif success_text
63
85
  raise ArgumentError, "success_text is not compatible with blockless invocation"
64
- elsif !timing.nil?
86
+ elsif timing
65
87
  raise ArgumentError, "timing is not compatible with blockless invocation"
66
88
  end
67
89
  end
68
90
 
69
- timing = true if timing.nil?
70
-
71
- t_start = Time.now.to_f
91
+ t_start = Time.now
72
92
  CLI::UI.raw do
73
- puts edge(text, color: color, first: CLI::UI::Box::Heavy::TL)
93
+ print prefix.chop
94
+ puts frame_style.open(text, color: color)
74
95
  end
75
- FrameStack.push(color)
96
+ FrameStack.push(color: color, style: frame_style)
76
97
 
77
98
  return unless block_given?
78
99
 
@@ -82,14 +103,14 @@ module CLI
82
103
  success = yield
83
104
  rescue
84
105
  closed = true
85
- t_diff = timing ? (Time.now.to_f - t_start) : nil
106
+ t_diff = elasped(t_start, timing)
86
107
  close(failure_text, color: :red, elapsed: t_diff)
87
108
  raise
88
109
  else
89
110
  success
90
111
  ensure
91
112
  unless closed
92
- t_diff = timing ? (Time.now.to_f - t_start) : nil
113
+ t_diff = elasped(t_start, timing)
93
114
  if success != false
94
115
  close(success_text, color: color, elapsed: t_diff)
95
116
  else
@@ -99,8 +120,8 @@ module CLI
99
120
  end
100
121
  end
101
122
 
102
- # Closes a frame
103
- # Automatically called for a block-form +open+
123
+ # Adds a divider in a frame
124
+ # Used to separate information within a single frame
104
125
  #
105
126
  # ==== Attributes
106
127
  #
@@ -109,31 +130,38 @@ module CLI
109
130
  # ==== Options
110
131
  #
111
132
  # * +:color+ - The color of the frame. Defaults to +DEFAULT_FRAME_COLOR+
112
- # * +:elapsed+ - How long did the frame take? Defaults to nil
133
+ # * +frame_style+ - The frame style to use for this frame
113
134
  #
114
135
  # ==== Example
115
136
  #
116
- # CLI::UI::Frame.close('Close')
137
+ # CLI::UI::Frame.open('Open') { CLI::UI::Frame.divider('Divider') }
117
138
  #
118
- # Output:
119
- # ┗━━ Close ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
139
+ # Default Output:
140
+ # ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
141
+ # ┣━━ Divider ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
142
+ # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
120
143
  #
144
+ # ==== Raises
121
145
  #
122
- def close(text, color: DEFAULT_FRAME_COLOR, elapsed: nil)
123
- color = CLI::UI.resolve_color(color)
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
124
154
 
125
- FrameStack.pop
126
- kwargs = {}
127
- if elapsed
128
- kwargs[:right_text] = "(#{elapsed.round(2)}s)"
129
- end
130
155
  CLI::UI.raw do
131
- puts edge(text, color: color, first: CLI::UI::Box::Heavy::BL, **kwargs)
156
+ print prefix.chop
157
+ puts frame_style.divider(text, color: color)
132
158
  end
159
+
160
+ FrameStack.push(fs_item)
133
161
  end
134
162
 
135
- # Adds a divider in a frame
136
- # Used to separate information within a single frame
163
+ # Closes a frame
164
+ # Automatically called for a block-form +open+
137
165
  #
138
166
  # ==== Attributes
139
167
  #
@@ -141,51 +169,69 @@ module CLI
141
169
  #
142
170
  # ==== Options
143
171
  #
144
- # * +:color+ - The color of the frame. Defaults to +DEFAULT_FRAME_COLOR+
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
145
175
  #
146
176
  # ==== Example
147
177
  #
148
- # CLI::UI::Frame.open('Open') { CLI::UI::Frame.divider('Divider') }
178
+ # CLI::UI::Frame.close('Close')
149
179
  #
150
- # Output:
151
- # ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
152
- # ┣━━ Divider ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
153
- # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
180
+ # Default Output:
181
+ # ┗━━ Close ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
154
182
  #
155
183
  # ==== Raises
156
184
  #
157
185
  # MUST be inside an open frame or it raises a +UnnestedFrameException+
158
186
  #
159
- def divider(text, color: nil)
187
+ def close(text, color: nil, elapsed: nil, frame_style: nil)
160
188
  fs_item = FrameStack.pop
161
- raise UnnestedFrameException, "no frame nesting to unnest" unless fs_item
162
- color = CLI::UI.resolve_color(color)
163
- item = CLI::UI.resolve_color(fs_item)
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
164
198
 
165
199
  CLI::UI.raw do
166
- puts edge(text, color: (color || item), first: CLI::UI::Box::Heavy::DIV)
200
+ print prefix.chop
201
+ puts frame_style.close(text, color: color, **kwargs)
167
202
  end
168
- FrameStack.push(item)
169
203
  end
170
204
 
171
205
  # Determines the prefix of a frame entry taking multi-nested frames into account
172
206
  #
173
207
  # ==== Options
174
208
  #
175
- # * +:color+ - The color of the prefix. Defaults to +Thread.current[:cliui_frame_color_override]+ or nil
209
+ # * +:color+ - The color of the prefix. Defaults to +Thread.current[:cliui_frame_color_override]+
176
210
  #
177
- def prefix(color: nil)
178
- pfx = +''
179
- items = FrameStack.items
180
- items[0..-2].each do |item|
181
- pfx << CLI::UI.resolve_color(item).code << CLI::UI::Box::Heavy::VERT
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
182
226
  end
183
- if item = items.last
184
- c = Thread.current[:cliui_frame_color_override] || color || item
185
- pfx << CLI::UI.resolve_color(c).code \
186
- << CLI::UI::Box::Heavy::VERT << ' ' << CLI::UI::Color::RESET.code
227
+ end
228
+
229
+ # The width of a prefix given the number of Frames in the stack
230
+ # Does _not_ account for the space at the end of the final prefix
231
+ def prefix_width
232
+ FrameStack.items.reduce(0) do |width, item|
233
+ width + item.frame_style.prefix_width
187
234
  end
188
- pfx
189
235
  end
190
236
 
191
237
  # Override a color for a given thread.
@@ -202,107 +248,19 @@ module CLI
202
248
  Thread.current[:cliui_frame_color_override] = prev
203
249
  end
204
250
 
205
- # The width of a prefix given the number of Frames in the stack
206
- #
207
- def prefix_width
208
- w = FrameStack.items.size
209
- w.zero? ? 0 : w + 1
210
- end
211
-
212
251
  private
213
252
 
214
- def edge(text, color: raise, first: raise, right_text: nil)
215
- color = CLI::UI.resolve_color(color)
216
- text = CLI::UI.resolve_text("{{#{color.name}:#{text}}}")
217
-
218
- prefix = +''
219
- FrameStack.items.each do |item|
220
- prefix << CLI::UI.resolve_color(item).code << CLI::UI::Box::Heavy::VERT
221
- end
222
- prefix << color.code << first << (CLI::UI::Box::Heavy::HORZ * 2)
223
- text ||= ''
224
- unless text.empty?
225
- prefix << ' ' << text << ' '
226
- end
227
-
228
- termwidth = CLI::UI::Terminal.width
229
-
230
- suffix = +''
231
- if right_text
232
- suffix << ' ' << right_text << ' '
233
- end
234
-
235
- suffix_width = CLI::UI::ANSI.printing_width(suffix)
236
- suffix_end = termwidth - 2
237
- suffix_start = suffix_end - suffix_width
238
-
239
- prefix_width = CLI::UI::ANSI.printing_width(prefix)
240
- prefix_start = 0
241
- prefix_end = prefix_start + prefix_width
242
-
243
- if prefix_end > suffix_start
244
- suffix = ''
245
- # if prefix_end > termwidth
246
- # we *could* truncate it, but let's just let it overflow to the
247
- # next line and call it poor usage of this API.
248
- end
249
-
250
- o = +''
251
-
252
- is_ci = ![0, '', nil].include?(ENV['CI'])
253
-
254
- # Jumping around the line can cause some unwanted flashes
255
- o << CLI::UI::ANSI.hide_cursor
256
-
257
- o << if is_ci
258
- # In CI, we can't use absolute horizontal positions because of timestamps.
259
- # So we move around the line by offset from this cursor position.
260
- CLI::UI::ANSI.cursor_save
261
- else
262
- # Outside of CI, we reset to column 1 so that things like ^C don't
263
- # cause output misformatting.
264
- "\r"
265
- end
266
-
267
- o << color.code
268
- o << CLI::UI::Box::Heavy::HORZ * termwidth # draw a full line
269
- o << print_at_x(prefix_start, prefix, is_ci)
270
- o << color.code
271
- o << print_at_x(suffix_start, suffix, is_ci)
272
- o << CLI::UI::Color::RESET.code
273
- o << CLI::UI::ANSI.show_cursor
274
- o << "\n"
275
-
276
- o
277
- end
278
-
279
- def print_at_x(x, str, is_ci)
280
- if is_ci
281
- CLI::UI::ANSI.cursor_restore + CLI::UI::ANSI.cursor_forward(x) + str
282
- else
283
- CLI::UI::ANSI.cursor_horizontal_absolute(1 + x) + str
284
- end
285
- end
286
-
287
- module FrameStack
288
- ENVVAR = 'CLI_FRAME_STACK'
289
-
290
- def self.items
291
- ENV.fetch(ENVVAR, '').split(':').map(&:to_sym)
292
- end
293
-
294
- def self.push(item)
295
- curr = items
296
- curr << item.name
297
- ENV[ENVVAR] = curr.join(':')
298
- end
299
-
300
- def self.pop
301
- curr = items
302
- ret = curr.pop
303
- ENV[ENVVAR] = curr.join(':')
304
- ret.nil? ? nil : ret.to_sym
305
- end
253
+ # If timing is:
254
+ # Numeric: return it
255
+ # false: return nil
256
+ # true or nil: defaults to Time.new
257
+ # Time: return the difference with start
258
+ def elasped(start, timing)
259
+ return timing if timing.is_a?(Numeric)
260
+ return if timing.is_a?(FalseClass)
261
+
262
+ timing = Time.new if timing.is_a?(TrueClass) || timing.nil?
263
+ timing - start
306
264
  end
307
265
  end
308
266
  end