cli-ui 1.2.2 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
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)