shopify-cli 0.9.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +3 -0
  3. data/CHANGELOG.md +24 -2
  4. data/RELEASING.md +4 -4
  5. data/docs/_config.yml +3 -0
  6. data/docs/_data/nav.yml +9 -0
  7. data/docs/getting-started/index.md +5 -40
  8. data/docs/getting-started/install/index.md +75 -0
  9. data/docs/getting-started/migrate/index.md +96 -0
  10. data/docs/getting-started/uninstall/index.md +37 -0
  11. data/docs/getting-started/upgrade/index.md +37 -0
  12. data/docs/index.md +5 -6
  13. data/lib/project_types/extension/cli.rb +2 -1
  14. data/lib/project_types/extension/commands/tunnel.rb +1 -1
  15. data/lib/project_types/extension/forms/register.rb +2 -3
  16. data/lib/project_types/extension/graphql/get_app_by_api_key.graphql +9 -0
  17. data/lib/project_types/extension/tasks/converters/app_converter.rb +27 -0
  18. data/lib/project_types/extension/tasks/get_app.rb +22 -0
  19. data/lib/project_types/extension/tasks/get_apps.rb +1 -6
  20. data/lib/project_types/node/forms/create.rb +3 -54
  21. data/lib/project_types/node/messages/messages.rb +3 -14
  22. data/lib/project_types/rails/forms/create.rb +3 -52
  23. data/lib/project_types/rails/messages/messages.rb +2 -13
  24. data/lib/project_types/script/cli.rb +5 -5
  25. data/lib/project_types/script/commands/create.rb +5 -4
  26. data/lib/project_types/script/commands/push.rb +1 -0
  27. data/lib/project_types/script/config/extension_points.yml +3 -3
  28. data/lib/project_types/script/errors.rb +1 -0
  29. data/lib/project_types/script/forms/create.rb +8 -4
  30. data/lib/project_types/script/layers/application/build_script.rb +7 -10
  31. data/lib/project_types/script/layers/application/create_script.rb +16 -14
  32. data/lib/project_types/script/layers/application/project_dependencies.rb +3 -9
  33. data/lib/project_types/script/layers/application/push_script.rb +13 -11
  34. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +106 -0
  35. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +64 -0
  36. data/lib/project_types/script/layers/infrastructure/errors.rb +2 -2
  37. data/lib/project_types/script/layers/infrastructure/project_creator.rb +23 -0
  38. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +6 -3
  39. data/lib/project_types/script/layers/infrastructure/script_repository.rb +8 -38
  40. data/lib/project_types/script/layers/infrastructure/task_runner.rb +18 -0
  41. data/lib/project_types/script/messages/messages.rb +5 -6
  42. data/lib/project_types/script/script_project.rb +22 -9
  43. data/lib/project_types/script/templates/ts/as-pect.d.ts +1 -0
  44. data/lib/project_types/script/ui/error_handler.rb +5 -0
  45. data/lib/shopify-cli/admin_api.rb +1 -2
  46. data/lib/shopify-cli/admin_api/populate_resource_command.rb +10 -1
  47. data/lib/shopify-cli/admin_api/schema.rb +11 -1
  48. data/lib/shopify-cli/api.rb +2 -0
  49. data/lib/shopify-cli/context.rb +60 -0
  50. data/lib/shopify-cli/core/entry_point.rb +6 -0
  51. data/lib/shopify-cli/core/finalize.rb +13 -0
  52. data/lib/shopify-cli/git.rb +14 -10
  53. data/lib/shopify-cli/messages/messages.rb +22 -2
  54. data/lib/shopify-cli/tasks.rb +1 -0
  55. data/lib/shopify-cli/tasks/select_org_and_shop.rb +77 -0
  56. data/lib/shopify-cli/tunnel.rb +33 -1
  57. data/lib/shopify-cli/version.rb +1 -1
  58. data/vendor/deps/cli-ui/REVISION +1 -1
  59. data/vendor/deps/cli-ui/lib/cli/ui.rb +52 -11
  60. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +11 -7
  61. data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +34 -21
  62. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +107 -149
  63. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +99 -0
  64. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +119 -0
  65. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +158 -0
  66. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +112 -0
  67. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +9 -15
  68. data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +47 -0
  69. data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +9 -7
  70. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +39 -14
  71. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +62 -44
  72. data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +7 -2
  73. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +23 -3
  74. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +34 -10
  75. data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +12 -7
  76. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +26 -16
  77. data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +3 -3
  78. data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +75 -0
  79. data/vendor/deps/cli-ui/lib/cli/ui/widgets/base.rb +27 -0
  80. data/vendor/deps/cli-ui/lib/cli/ui/widgets/status.rb +61 -0
  81. metadata +24 -9
  82. data/lib/project_types/extension/features/tunnel_url.rb +0 -20
  83. data/lib/project_types/script/layers/infrastructure/assemblyscript_dependency_manager.rb +0 -73
  84. data/lib/project_types/script/layers/infrastructure/assemblyscript_wasm_builder.rb +0 -39
  85. data/lib/project_types/script/layers/infrastructure/dependency_manager.rb +0 -36
  86. data/lib/project_types/script/layers/infrastructure/script_builder.rb +0 -34
  87. data/lib/project_types/script/layers/infrastructure/test_suite_repository.rb +0 -59
  88. data/vendor/deps/cli-ui/lib/cli/ui/box.rb +0 -15
@@ -11,7 +11,7 @@ module ShopifyCli
11
11
  class Tunnel
12
12
  extend SingleForwardable
13
13
 
14
- def_delegators :new, :start, :stop, :auth
14
+ def_delegators :new, :start, :stop, :auth, :stats, :urls
15
15
 
16
16
  class FetchUrlError < RuntimeError; end
17
17
  class NgrokError < RuntimeError; end
@@ -23,6 +23,10 @@ module ShopifyCli
23
23
  linux: 'https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip',
24
24
  }
25
25
 
26
+ NGROK_TUNNELS_URI = URI.parse('http://localhost:4040/api/tunnels')
27
+ TUNNELS_FIELD = 'tunnels'
28
+ PUBLIC_URL_FIELD = 'public_url'
29
+
26
30
  ##
27
31
  # will find and stop a running tunnel process. It will also output if the
28
32
  # operation was successful or not
@@ -82,6 +86,34 @@ module ShopifyCli
82
86
  ctx.system(File.join(ShopifyCli::CACHE_DIR, 'ngrok'), 'authtoken', token)
83
87
  end
84
88
 
89
+ ##
90
+ # will return the statistics of the current running tunnels
91
+ #
92
+ # #### Returns
93
+ #
94
+ # * `stats` - the hash of running statistics returning from the ngrok api
95
+ #
96
+ def stats
97
+ response = Net::HTTP.get_response(NGROK_TUNNELS_URI)
98
+ JSON.parse(response.body)
99
+ rescue
100
+ {}
101
+ end
102
+
103
+ ##
104
+ # will return the urls of the current running tunnels
105
+ #
106
+ # #### Returns
107
+ #
108
+ # * `stats` - the array of urls
109
+ #
110
+ def urls
111
+ tunnels = stats.dig(TUNNELS_FIELD)
112
+ tunnels.map { |tunnel| tunnel.dig(PUBLIC_URL_FIELD) }
113
+ rescue
114
+ []
115
+ end
116
+
85
117
  private
86
118
 
87
119
  def install(ctx)
@@ -1,3 +1,3 @@
1
1
  module ShopifyCli
2
- VERSION = '0.9.1'
2
+ VERSION = '1.0.2'
3
3
  end
@@ -1 +1 @@
1
- 35e1b188f19b2ba0ab85bbf48ab983b1f72a361b
1
+ c59f601fe271432dfe304f30f08a48b2a343606e
@@ -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