cli-ui 1.2.2 → 1.5.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c658a2195a92d5e4ccf05fc1359eef09d25b5a01e0110c6ab84190d4a10c922
4
- data.tar.gz: 4745f15513a91abef5cce3e3523d26de38d60336dbdf723601402fe8bd4b2635
3
+ metadata.gz: 1e28f7cb02b8647ee32e91b11f2b25b450c33a245514a520783d50cc6d5f7d84
4
+ data.tar.gz: 894e2a52591d18c5210efa36beab81d761c18734a42f8362d8af0c97e3bd4ae2
5
5
  SHA512:
6
- metadata.gz: 609b7cab1cde15280659813f6b283c419c76b744a9089dcded708c3cf08113b7118150fd5e6642f8e41709f8b481ad0a51927d48ac294432ce108209faefccce
7
- data.tar.gz: feb06db36c85af0d83c2407bf6ebb06ee1c65ea8ec38b7b80b09bbeaac0a25220a7fe2707993bee950916cd203e31d219b6db6adf132b08e9027be8e00df846a
6
+ metadata.gz: af8bd2b36c0d4c65e93aa966062e040109f5a10347404b112331975f04eb9cfe2a8601a9a9d1d4c1787f92f5a306b81f68e21d1e4f6728705fdfc81dd58fc25c
7
+ data.tar.gz: a94fa31ff9991a26202f8c4d4d85f87d9efdd23489ed47c5f719b0389a6106be4eee4ee86075fcddafe367d847b8299f69e6fe014fdffce629b0c9df43e156d1
data/README.md CHANGED
@@ -108,13 +108,26 @@ puts CLI::UI.fmt "{{red:Red}} {{green:Green}}"
108
108
  e.g. `{{*}}` => a yellow ⭑
109
109
 
110
110
  ```ruby
111
- puts CLI::UI.fmt "{{*}} {{x}} {{?}} {{v}}"
111
+ puts CLI::UI.fmt "{{*}} {{v}} {{?}} {{x}}"
112
112
  ```
113
113
 
114
114
  ![Symbol Formatting](https://user-images.githubusercontent.com/3074765/33799847-9ec03fd0-dd01-11e7-93f7-5f5cc540e61e.png)
115
115
 
116
116
  ---
117
117
 
118
+ ### Status Widget
119
+
120
+ ```ruby
121
+ CLI::UI::Spinner.spin("building packages: {{@widget/status:1:2:3:4}}") do |spinner|
122
+ # spinner.update_title(...)
123
+ sleep(3)
124
+ end
125
+ ```
126
+
127
+ ![Status Widget](https://user-images.githubusercontent.com/1284/61405142-11042580-a8a7-11e9-9885-46ba44c46358.gif)
128
+
129
+ ---
130
+
118
131
  ### Progress Bar
119
132
 
120
133
  Show progress of a process or operation.
@@ -131,6 +144,37 @@ end
131
144
 
132
145
  ---
133
146
 
147
+ ### Frame Styles
148
+
149
+ Modify the appearance of CLI::UI both globally and on an individual frame level.
150
+
151
+ To set the default style:
152
+
153
+ ```ruby
154
+ CLI::UI.frame_style = :box
155
+ ```
156
+
157
+ To style an individual frame:
158
+
159
+ ```ruby
160
+ CLI::UI.frame('New Style!', frame_style: :bracket) { puts 'It's pretty cool!' }
161
+ ```
162
+
163
+ The default style - `:box` - is what has been used up until now. The other style - `:bracket` - looks like this:
164
+
165
+ ```ruby
166
+ CLI::UI.frame_style = :bracket
167
+ CLI::UI::StdoutRouter.enable
168
+ CLI::UI::Frame.open('Frame 1') do
169
+ CLI::UI::Frame.open('Frame 2') { puts "inside frame 2" }
170
+ puts "inside frame 1"
171
+ end
172
+ ```
173
+
174
+ ![Frame Style](https://user-images.githubusercontent.com/315948/65287373-9a82de80-db08-11e9-94fb-20f4b7561c07.png)
175
+
176
+ ---
177
+
134
178
  ## Example Usage
135
179
 
136
180
  The following code makes use of nested-framing, multi-threaded spinners, formatted text, and more.
data/lib/cli/ui.rb CHANGED
@@ -1,16 +1,19 @@
1
1
  module CLI
2
2
  module UI
3
- autoload :ANSI, 'cli/ui/ansi'
4
- autoload :Glyph, 'cli/ui/glyph'
5
- autoload :Color, 'cli/ui/color'
6
- autoload :Box, 'cli/ui/box'
7
- autoload :Frame, 'cli/ui/frame'
8
- autoload :Progress, 'cli/ui/progress'
9
- autoload :Prompt, 'cli/ui/prompt'
10
- autoload :Terminal, 'cli/ui/terminal'
11
- autoload :Truncater, 'cli/ui/truncater'
12
- autoload :Formatter, 'cli/ui/formatter'
13
- autoload :Spinner, 'cli/ui/spinner'
3
+ autoload :ANSI, 'cli/ui/ansi'
4
+ autoload :Glyph, 'cli/ui/glyph'
5
+ autoload :Color, 'cli/ui/color'
6
+ autoload :Frame, 'cli/ui/frame'
7
+ autoload :OS, 'cli/ui/os'
8
+ autoload :Printer, 'cli/ui/printer'
9
+ autoload :Progress, 'cli/ui/progress'
10
+ autoload :Prompt, 'cli/ui/prompt'
11
+ autoload :Terminal, 'cli/ui/terminal'
12
+ autoload :Truncater, 'cli/ui/truncater'
13
+ autoload :Formatter, 'cli/ui/formatter'
14
+ autoload :Spinner, 'cli/ui/spinner'
15
+ autoload :Widgets, 'cli/ui/widgets'
16
+ autoload :Wrap, 'cli/ui/wrap'
14
17
 
15
18
  # Convenience accessor to +CLI::UI::Spinner::SpinGroup+
16
19
  SpinGroup = Spinner::SpinGroup
@@ -27,7 +30,7 @@ module CLI
27
30
  end
28
31
 
29
32
  # Color resolution using +CLI::UI::Color.lookup+
30
- # Will lookup using +Color.lookup+ if a symbol, otherwise we assume it is a valid color and return it
33
+ # Will lookup using +Color.lookup+ unless it's already a CLI::UI::Color (or nil)
31
34
  #
32
35
  # ==== Attributes
33
36
  #
@@ -35,14 +38,29 @@ module CLI
35
38
  #
36
39
  def self.resolve_color(input)
37
40
  case input
38
- when Symbol
39
- CLI::UI::Color.lookup(input)
41
+ when CLI::UI::Color, nil
42
+ input
40
43
  else
44
+ CLI::UI::Color.lookup(input)
45
+ end
46
+ end
47
+
48
+ # Frame style resolution using +CLI::UI::Frame::FrameStyle.lookup+.
49
+ # Will lookup using +FrameStyle.lookup+ unless it's already a CLI::UI::Frame::FrameStyle(or nil)
50
+ #
51
+ # ==== Attributes
52
+ #
53
+ # * +input+ - frame style to resolve
54
+ def self.resolve_style(input)
55
+ case input
56
+ when CLI::UI::Frame::FrameStyle, nil
41
57
  input
58
+ else
59
+ CLI::UI::Frame::FrameStyle.lookup(input)
42
60
  end
43
61
  end
44
62
 
45
- # Conviencence Method for +CLI::UI::Prompt.confirm+
63
+ # Convenience Method for +CLI::UI::Prompt.confirm+
46
64
  #
47
65
  # ==== Attributes
48
66
  #
@@ -52,18 +70,18 @@ module CLI
52
70
  CLI::UI::Prompt.confirm(question, **kwargs)
53
71
  end
54
72
 
55
- # Conviencence Method for +CLI::UI::Prompt.ask+
73
+ # Convenience Method for +CLI::UI::Prompt.ask+
56
74
  #
57
75
  # ==== Attributes
58
76
  #
59
77
  # * +question+ - question to ask
60
- # * +kwargs+ - arugments for +Prompt.ask+
78
+ # * +kwargs+ - arguments for +Prompt.ask+
61
79
  #
62
80
  def self.ask(question, **kwargs)
63
81
  CLI::UI::Prompt.ask(question, **kwargs)
64
82
  end
65
83
 
66
- # Conviencence Method to resolve text using +CLI::UI::Formatter.format+
84
+ # Convenience Method to resolve text using +CLI::UI::Formatter.format+
67
85
  # Check +CLI::UI::Formatter::SGR_MAP+ for available formatting options
68
86
  #
69
87
  # ==== Attributes
@@ -75,10 +93,10 @@ module CLI
75
93
  return input if input.nil?
76
94
  formatted = CLI::UI::Formatter.new(input).format
77
95
  return formatted unless truncate_to
78
- return CLI::UI::Truncater.call(formatted, truncate_to)
96
+ CLI::UI::Truncater.call(formatted, truncate_to)
79
97
  end
80
98
 
81
- # Conviencence Method to format text using +CLI::UI::Formatter.format+
99
+ # Convenience Method to format text using +CLI::UI::Formatter.format+
82
100
  # Check +CLI::UI::Formatter::SGR_MAP+ for available formatting options
83
101
  #
84
102
  # https://user-images.githubusercontent.com/3074765/33799827-6d0721a2-dd01-11e7-9ab5-c3d455264afe.png
@@ -96,29 +114,44 @@ module CLI
96
114
  CLI::UI::Formatter.new(input).format(enable_color: enable_color)
97
115
  end
98
116
 
99
- # Conviencence Method for +CLI::UI::Frame.open+
117
+ def self.wrap(input)
118
+ CLI::UI::Wrap.new(input).wrap
119
+ end
120
+
121
+ # Convenience Method for +CLI::UI::Printer.puts+
122
+ #
123
+ # ==== Attributes
124
+ #
125
+ # * +msg+ - Message to print
126
+ # * +kwargs+ - keyword arguments for +Printer.puts+
127
+ #
128
+ def self.puts(msg, **kwargs)
129
+ CLI::UI::Printer.puts(msg, **kwargs)
130
+ end
131
+
132
+ # Convenience Method for +CLI::UI::Frame.open+
100
133
  #
101
134
  # ==== Attributes
102
135
  #
103
136
  # * +args+ - arguments for +Frame.open+
104
137
  # * +block+ - block for +Frame.open+
105
138
  #
106
- def self.frame(*args, &block)
107
- CLI::UI::Frame.open(*args, &block)
139
+ def self.frame(*args, **kwargs, &block)
140
+ CLI::UI::Frame.open(*args, **kwargs, &block)
108
141
  end
109
142
 
110
- # Conviencence Method for +CLI::UI::Spinner.spin+
143
+ # Convenience Method for +CLI::UI::Spinner.spin+
111
144
  #
112
145
  # ==== Attributes
113
146
  #
114
147
  # * +args+ - arguments for +Spinner.open+
115
148
  # * +block+ - block for +Spinner.open+
116
149
  #
117
- def self.spinner(*args, &block)
118
- CLI::UI::Spinner.spin(*args, &block)
150
+ def self.spinner(*args, **kwargs, &block)
151
+ CLI::UI::Spinner.spin(*args, **kwargs, &block)
119
152
  end
120
153
 
121
- # Conviencence Method to override frame color using +CLI::UI::Frame.with_frame_color+
154
+ # Convenience Method to override frame color using +CLI::UI::Frame.with_frame_color+
122
155
  #
123
156
  # ==== Attributes
124
157
  #
@@ -137,12 +170,12 @@ module CLI
137
170
  #
138
171
  def self.log_output_to(path)
139
172
  if CLI::UI::StdoutRouter.duplicate_output_to
140
- raise "multiple logs not allowed"
173
+ raise 'multiple logs not allowed'
141
174
  end
142
175
  CLI::UI::StdoutRouter.duplicate_output_to = File.open(path, 'w')
143
176
  yield
144
177
  ensure
145
- if file_descriptor = CLI::UI::StdoutRouter.duplicate_output_to
178
+ if (file_descriptor = CLI::UI::StdoutRouter.duplicate_output_to)
146
179
  file_descriptor.close
147
180
  CLI::UI::StdoutRouter.duplicate_output_to = nil
148
181
  end
@@ -181,6 +214,19 @@ module CLI
181
214
  end
182
215
 
183
216
  self.enable_color = $stdout.tty?
217
+
218
+ # Set the default frame style.
219
+ # Convenience method for setting the default frame style with +CLI::UI::Frame.frame_style=+
220
+ #
221
+ # Raises ArgumentError if +frame_style+ is not valid
222
+ #
223
+ # ==== Attributes
224
+ #
225
+ # * +symbol+ - the default frame style to use for frames
226
+ #
227
+ def self.frame_style=(frame_style)
228
+ Frame.frame_style = frame_style.to_sym
229
+ end
184
230
  end
185
231
  end
186
232
 
data/lib/cli/ui/ansi.rb CHANGED
@@ -21,6 +21,8 @@ module CLI
21
21
  when 0x200d # zero-width joiner
22
22
  zwj = true
23
23
  acc
24
+ when "\n"
25
+ acc
24
26
  else
25
27
  acc + 1
26
28
  end
@@ -45,7 +47,7 @@ module CLI
45
47
  # - +cmd+ - ANSI control sequence Command
46
48
  #
47
49
  def self.control(args, cmd)
48
- ESC + "[" + args + cmd
50
+ ESC + '[' + args + cmd
49
51
  end
50
52
 
51
53
  # https://en.wikipedia.org/wiki/ANSI_escape_code#graphics
@@ -106,19 +108,21 @@ module CLI
106
108
  # * +n+ - The column to move to
107
109
  #
108
110
  def self.cursor_horizontal_absolute(n = 1)
109
- control(n.to_s, 'G')
111
+ cmd = control(n.to_s, 'G')
112
+ cmd += control('1', 'D') if CLI::UI::OS.current.shift_cursor_on_line_reset?
113
+ cmd
110
114
  end
111
115
 
112
116
  # Show the cursor
113
117
  #
114
118
  def self.show_cursor
115
- control('', "?25h")
119
+ control('', '?25h')
116
120
  end
117
121
 
118
122
  # Hide the cursor
119
123
  #
120
124
  def self.hide_cursor
121
- control('', "?25l")
125
+ control('', '?25l')
122
126
  end
123
127
 
124
128
  # Save the cursor position
@@ -136,13 +140,13 @@ module CLI
136
140
  # Move to the next line
137
141
  #
138
142
  def self.next_line
139
- cursor_down + control('1', 'G')
143
+ cursor_down + cursor_horizontal_absolute
140
144
  end
141
145
 
142
146
  # Move to the previous line
143
147
  #
144
148
  def self.previous_line
145
- cursor_up + control('1', 'G')
149
+ cursor_up + cursor_horizontal_absolute
146
150
  end
147
151
 
148
152
  def self.clear_to_end_of_line
data/lib/cli/ui/color.rb CHANGED
@@ -31,19 +31,24 @@ 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
46
50
  def initialize(name)
51
+ super
47
52
  @name = name
48
53
  end
49
54
 
@@ -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)