tty-prompt 0.13.1 → 0.13.2

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
  SHA1:
3
- metadata.gz: 6826fe5bd7ea9efb4e7e901d1bf2aaa34671bd0d
4
- data.tar.gz: 440b8acadbc509ffe2260562c1cf9d8e2b192ce0
3
+ metadata.gz: 774726386ea09cc8c3fc2abd877e377eb3db3c3d
4
+ data.tar.gz: 43ac05278ae482148a133e7b4ca1ad304e304189
5
5
  SHA512:
6
- metadata.gz: b4ba64116ae4982a55d032e60a6cee47d28a165e4ab1286c294e3e3109e602f4e6c174febfbe81507d0676eadbd0771565e73fc65c79bda5b914e1fd02405e67
7
- data.tar.gz: 3213c42f59d3c3df70a826ed6d2e69f8328725a7483a9ce57437b1464f1c4ae8a2c99a70cd1c43451c8b55d14bcb3ee02e1b98421145397ee08f9892424669bf
6
+ metadata.gz: d78bc4524d6e8183692e636d783d4c40c4666af82364084e67d3dae701dd9a2bd0695eeb4eac9925616bd054cf1edcca701307f016c3f9ea7a0756c6a0eedeac
7
+ data.tar.gz: 6965d2d8df2dcffdfe33539d71da7426a185fca2a1649d665417cca03d4961edb20c45e2cd65c2f74784201b86ec3a4e7135b21de547f73db1e7da410b459f2f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Change log
2
2
 
3
+ ## [v0.13.2] - 2017-08-30
4
+
5
+ ### Changed
6
+ * Change to extract TTY::Prompt::Reader to its own dependency
7
+
3
8
  ## [v0.13.1] - 2017-08-16
4
9
 
5
10
  ### Added
@@ -213,6 +218,8 @@
213
218
 
214
219
  * Initial implementation and release
215
220
 
221
+ [v0.13.2]: https://github.com/piotrmurach/tty-prompt/compare/v0.13.1...v0.13.2
222
+ [v0.13.1]: https://github.com/piotrmurach/tty-prompt/compare/v0.13.0...v0.13.1
216
223
  [v0.13.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.12.0...v0.13.0
217
224
  [v0.12.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.11.0...v0.12.0
218
225
  [v0.11.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.10.1...v0.11.0
data/Gemfile CHANGED
@@ -2,6 +2,9 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ # gem 'tty-reader', path: '../tty-reader'
6
+ # gem 'tty-reader', git: 'https://github.com/piotrmurach/tty-reader'
7
+
5
8
  group :test do
6
9
  gem 'benchmark-ips', '~> 2.0.0'
7
10
  gem 'simplecov', '~> 0.10.0'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module TTY
4
4
  class Prompt
5
- VERSION = "0.13.1"
5
+ VERSION = "0.13.2"
6
6
  end # Prompt
7
7
  end # TTY
data/lib/tty/prompt.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'forwardable'
4
4
  require 'pastel'
5
5
  require 'tty-cursor'
6
+ require 'tty-reader'
6
7
 
7
8
  require_relative 'prompt/answers_collector'
8
9
  require_relative 'prompt/confirm_question'
@@ -14,7 +15,6 @@ require_relative 'prompt/multi_list'
14
15
  require_relative 'prompt/multiline'
15
16
  require_relative 'prompt/mask_question'
16
17
  require_relative 'prompt/question'
17
- require_relative 'prompt/reader'
18
18
  require_relative 'prompt/slider'
19
19
  require_relative 'prompt/statement'
20
20
  require_relative 'prompt/suggestion'
@@ -127,8 +127,8 @@ module TTY
127
127
 
128
128
  @cursor = TTY::Cursor
129
129
  @pastel = Pastel.new(@enabled_color.nil? ? {} : { enabled: @enabled_color })
130
- @reader = Reader.new(@input, @output, interrupt: @interrupt,
131
- track_history: @track_history, env: @env)
130
+ @reader = TTY::Reader.new(@input, @output, interrupt: @interrupt,
131
+ track_history: @track_history, env: @env)
132
132
  end
133
133
 
134
134
  # Invoke a question type of prompt
data/tty-prompt.gemspec CHANGED
@@ -22,9 +22,9 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_dependency 'necromancer', '~> 0.4.0'
24
24
  spec.add_dependency 'pastel', '~> 0.7.0'
25
- spec.add_dependency 'tty-cursor', '~> 0.5.0'
26
- spec.add_dependency 'wisper', '~> 2.0.0'
27
25
  spec.add_dependency 'timers', '~> 4.1.2'
26
+ spec.add_dependency 'tty-cursor', '~> 0.5.0'
27
+ spec.add_dependency 'tty-reader', '~> 0.1.0'
28
28
 
29
29
  spec.add_development_dependency 'bundler', '>= 1.5.0', '< 2.0'
30
30
  spec.add_development_dependency 'rake'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tty-prompt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.1
4
+ version: 0.13.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Murach
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-15 00:00:00.000000000 Z
11
+ date: 2017-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: necromancer
@@ -39,47 +39,47 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.7.0
41
41
  - !ruby/object:Gem::Dependency
42
- name: tty-cursor
42
+ name: timers
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.5.0
47
+ version: 4.1.2
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.5.0
54
+ version: 4.1.2
55
55
  - !ruby/object:Gem::Dependency
56
- name: wisper
56
+ name: tty-cursor
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 2.0.0
61
+ version: 0.5.0
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 2.0.0
68
+ version: 0.5.0
69
69
  - !ruby/object:Gem::Dependency
70
- name: timers
70
+ name: tty-reader
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 4.1.2
75
+ version: 0.1.0
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 4.1.2
82
+ version: 0.1.0
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: bundler
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -192,15 +192,6 @@ files:
192
192
  - lib/tty/prompt/question/checks.rb
193
193
  - lib/tty/prompt/question/modifier.rb
194
194
  - lib/tty/prompt/question/validation.rb
195
- - lib/tty/prompt/reader.rb
196
- - lib/tty/prompt/reader/codes.rb
197
- - lib/tty/prompt/reader/console.rb
198
- - lib/tty/prompt/reader/history.rb
199
- - lib/tty/prompt/reader/key_event.rb
200
- - lib/tty/prompt/reader/line.rb
201
- - lib/tty/prompt/reader/mode.rb
202
- - lib/tty/prompt/reader/win_api.rb
203
- - lib/tty/prompt/reader/win_console.rb
204
195
  - lib/tty/prompt/result.rb
205
196
  - lib/tty/prompt/slider.rb
206
197
  - lib/tty/prompt/statement.rb
@@ -1,121 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module TTY
4
- class Prompt
5
- class Reader
6
- module Codes
7
- def ctrl_keys
8
- {
9
- ctrl_a: ?\C-a,
10
- ctrl_b: ?\C-b,
11
- ctrl_c: ?\C-c,
12
- ctrl_d: ?\C-d,
13
- ctrl_e: ?\C-e,
14
- ctrl_f: ?\C-f,
15
- ctrl_g: ?\C-g,
16
- ctrl_h: ?\C-h,
17
- ctrl_i: ?\C-i,
18
- ctrl_j: ?\C-j,
19
- ctrl_k: ?\C-k,
20
- ctrl_l: ?\C-l,
21
- ctrl_m: ?\C-m,
22
- ctrl_n: ?\C-n,
23
- ctrl_o: ?\C-o,
24
- ctrl_p: ?\C-p,
25
- ctrl_q: ?\C-q,
26
- ctrl_r: ?\C-r,
27
- ctrl_s: ?\C-s,
28
- ctrl_t: ?\C-t,
29
- ctrl_u: ?\C-u,
30
- ctrl_v: ?\C-v,
31
- ctrl_w: ?\C-w,
32
- ctrl_x: ?\C-x,
33
- ctrl_y: ?\C-y,
34
- ctrl_z: ?\C-z
35
- }
36
- end
37
- module_function :ctrl_keys
38
-
39
- def keys
40
- {
41
- tab: "\t",
42
- enter: "\n",
43
- return: "\r",
44
- escape: "\e",
45
- space: " ",
46
- backspace: ?\C-?,
47
- home: "\e[1~",
48
- insert: "\e[2~",
49
- delete: "\e[3~",
50
- end: "\e[4~",
51
- page_up: "\e[5~",
52
- page_down: "\e[6~",
53
-
54
- up: "\e[A",
55
- down: "\e[B",
56
- right: "\e[C",
57
- left: "\e[D",
58
- clear: "\e[E",
59
-
60
- f1_xterm: "\eOP",
61
- f2_xterm: "\eOQ",
62
- f3_xterm: "\eOR",
63
- f4_xterm: "\eOS",
64
-
65
- f1: "\e[11~",
66
- f2: "\e[12~",
67
- f3: "\e[13~",
68
- f4: "\e[14~",
69
- f5: "\e[15~",
70
- f6: "\e[17~",
71
- f7: "\e[18~",
72
- f8: "\e[19~",
73
- f9: "\e[20~",
74
- f10: "\e[21~",
75
- f11: "\e[23~",
76
- f12: "\e[24~"
77
- }.merge(ctrl_keys)
78
- end
79
- module_function :keys
80
-
81
- def win_keys
82
- {
83
- tab: "\t",
84
- enter: "\r",
85
- return: "\r",
86
- escape: "\e",
87
- space: " ",
88
- backspace: "\b",
89
- home: [224, 71].pack('U*'),
90
- end: [224, 79].pack('U*'),
91
- insert: [224, 82].pack('U*'),
92
- delete: [224, 83].pack('U*'),
93
- page_up: [224, 73].pack('U*'),
94
- page_down: [224, 81].pack('U*'),
95
-
96
- up: [224, 72].pack('U*'),
97
- down: [224, 80].pack('U*'),
98
- right: [224, 77].pack('U*'),
99
- left: [224, 75].pack('U*'),
100
- clear: [224, 83].pack('U*'),
101
-
102
- f1: "\x00;",
103
- f2: "\x00<",
104
- f3: "\x00",
105
- f4: "\x00=",
106
- f5: "\x00?",
107
- f6: "\x00@",
108
- f7: "\x00A",
109
- f8: "\x00B",
110
- f9: "\x00C",
111
- f10: "\x00D",
112
- f11: "\x00\x85",
113
- f12: "\x00\x86"
114
- }.merge(ctrl_keys)
115
- end
116
- module_function :win_keys
117
-
118
- end # Codes
119
- end # Reader
120
- end # Prompt
121
- end # TTY
@@ -1,57 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require_relative 'codes'
4
- require_relative 'mode'
5
-
6
- module TTY
7
- class Prompt
8
- class Reader
9
- class Console
10
- ESC = "\e".freeze
11
- CSI = "\e[".freeze
12
-
13
- # Key codes
14
- #
15
- # @return [Hash[Symbol]]
16
- #
17
- # @api public
18
- attr_reader :keys
19
-
20
- # Escape codes
21
- #
22
- # @return [Array[Integer]]
23
- #
24
- # @api public
25
- attr_reader :escape_codes
26
-
27
- def initialize(input)
28
- @input = input
29
- @mode = Mode.new(input)
30
- @keys = Codes.keys
31
- @escape_codes = [[ESC.ord], CSI.bytes.to_a]
32
- end
33
-
34
- # Get a character from console with echo
35
- #
36
- # @param [Hash[Symbol]] options
37
- # @option options [Symbol] :echo
38
- # the echo toggle
39
- #
40
- # @return [String]
41
- #
42
- # @api private
43
- def get_char(options)
44
- mode.raw(options[:raw]) do
45
- mode.echo(options[:echo]) { input.getc }
46
- end
47
- end
48
-
49
- protected
50
-
51
- attr_reader :mode
52
-
53
- attr_reader :input
54
- end # Console
55
- end # Reader
56
- end # Prompt
57
- end # TTY
@@ -1,145 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'forwardable'
4
-
5
- module TTY
6
- class Prompt
7
- class Reader
8
- # A class responsible for storing a history of all lines entered by
9
- # user when interacting with shell prompt.
10
- #
11
- # @api private
12
- class History
13
- include Enumerable
14
- extend Forwardable
15
-
16
- # Default maximum size
17
- DEFAULT_SIZE = 32 << 4
18
-
19
- def_delegators :@history, :size, :length, :to_s, :inspect
20
-
21
- # Set and retrieve the maximum size of the buffer
22
- attr_accessor :max_size
23
-
24
- attr_reader :index
25
-
26
- attr_accessor :cycle
27
-
28
- attr_accessor :duplicates
29
-
30
- attr_accessor :exclude
31
-
32
- # Create a History buffer
33
- #
34
- # param [Integer] max_size
35
- # the maximum size for history buffer
36
- #
37
- # param [Hash[Symbol]] options
38
- # @option options [Boolean] :duplicates
39
- # whether or not to store duplicates, true by default
40
- #
41
- # @api public
42
- def initialize(max_size = DEFAULT_SIZE, options = {})
43
- @max_size = max_size
44
- @index = 0
45
- @history = []
46
- @duplicates = options.fetch(:duplicates) { true }
47
- @exclude = options.fetch(:exclude) { proc {} }
48
- @cycle = options.fetch(:cycle) { false }
49
- yield self if block_given?
50
- end
51
-
52
- # Iterates over history lines
53
- #
54
- # @api public
55
- def each
56
- if block_given?
57
- @history.each { |line| yield line }
58
- else
59
- @history.to_enum
60
- end
61
- end
62
-
63
- # Add the last typed line to history buffer
64
- #
65
- # @param [String] line
66
- #
67
- # @api public
68
- def push(line)
69
- @history.delete(line) unless @duplicates
70
- return if line.to_s.empty? || @exclude[line]
71
-
72
- @history.shift if size >= max_size
73
- @history << line
74
- @index = @history.size - 1
75
-
76
- self
77
- end
78
- alias << push
79
-
80
- # Move the pointer to the next line in the history
81
- #
82
- # @api public
83
- def next
84
- return if size.zero?
85
- if @index == size - 1
86
- @index = 0 if @cycle
87
- else
88
- @index += 1
89
- end
90
- end
91
-
92
- def next?
93
- size > 0 && !(@index == size - 1 && !@cycle)
94
- end
95
-
96
- # Move the pointer to the previous line in the history
97
- def previous
98
- return if size.zero?
99
- if @index.zero?
100
- @index = size - 1 if @cycle
101
- else
102
- @index -= 1
103
- end
104
- end
105
-
106
- def previous?
107
- size > 0 && !(@index < 0 && !@cycle)
108
- end
109
-
110
- # Return line at the specified index
111
- #
112
- # @raise [IndexError] index out of range
113
- #
114
- # @api public
115
- def [](index)
116
- if index < 0
117
- index += @history.size if index < 0
118
- end
119
- line = @history[index]
120
- if line.nil?
121
- raise IndexError, 'invalid index'
122
- end
123
- line.dup
124
- end
125
-
126
- # Get current line
127
- #
128
- # @api public
129
- def get
130
- return if size.zero?
131
-
132
- self[@index]
133
- end
134
-
135
- # Empty all history lines
136
- #
137
- # @api public
138
- def clear
139
- @history.clear
140
- @index = 0
141
- end
142
- end # History
143
- end # Reader
144
- end # Prompt
145
- end # TTY
@@ -1,91 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module TTY
4
- class Prompt
5
- class Reader
6
- # Responsible for meta-data information about key pressed
7
- #
8
- # @api private
9
- class Key < Struct.new(:name, :ctrl, :meta, :shift)
10
- def initialize(*)
11
- super(nil, false, false, false)
12
- end
13
- end
14
-
15
- # Represents key event emitted during keyboard press
16
- #
17
- # @api public
18
- class KeyEvent < Struct.new(:value, :key)
19
- # Create key event from read input codes
20
- #
21
- # @param [Hash[Symbol]] keys
22
- # the keys and codes mapping
23
- # @param [Array[Integer]] codes
24
- #
25
- # @return [KeyEvent]
26
- #
27
- # @api public
28
- def self.from(keys, char)
29
- key = Key.new
30
- ctrls = keys.keys.grep(/ctrl/)
31
-
32
- case char
33
- when keys[:return] then key.name = :return
34
- when keys[:enter] then key.name = :enter
35
- when keys[:tab] then key.name = :tab
36
- when keys[:backspace] then key.name = :backspace
37
- when keys[:delete] then key.name = :delete
38
- when keys[:space] then key.name = :space
39
- when keys[:escape] then key.name = :escape
40
- when proc { |c| c =~ /^[a-z]{1}$/ }
41
- key.name = :alpha
42
- when proc { |c| c =~ /^[A-Z]{1}$/ }
43
- key.name = :alpha
44
- key.shift = true
45
- when proc { |c| c =~ /^\d+$/ }
46
- key.name = :num
47
- # arrows
48
- when keys[:up] then key.name = :up
49
- when keys[:down] then key.name = :down
50
- when keys[:left] then key.name = :left
51
- when keys[:right] then key.name = :right
52
- # editing
53
- when keys[:clear] then key.name = :clear
54
- when keys[:end] then key.name = :end
55
- when keys[:home] then key.name = :home
56
- when keys[:insert] then key.name = :insert
57
- when keys[:page_up] then key.name = :page_up
58
- when keys[:page_down] then key.name = :page_down
59
- when proc { |cs| ctrls.any? { |name| keys[name] == cs } }
60
- key.name = keys.key(char)
61
- key.ctrl = true
62
- # f1 - f12
63
- when keys[:f1], keys[:f1_xterm] then key.name = :f1
64
- when keys[:f2], keys[:f2_xterm] then key.name = :f2
65
- when keys[:f3], keys[:f3_xterm] then key.name = :f3
66
- when keys[:f4], keys[:f4_xterm] then key.name = :f4
67
- when keys[:f5] then key.name = :f5
68
- when keys[:f6] then key.name = :f6
69
- when keys[:f7] then key.name = :f7
70
- when keys[:f8] then key.name = :f8
71
- when keys[:f9] then key.name = :f9
72
- when keys[:f10] then key.name = :f10
73
- when keys[:f11] then key.name = :f11
74
- when keys[:f12] then key.name = :f12
75
- end
76
-
77
- new(char, key)
78
- end
79
-
80
- # Check if key event can be triggered
81
- #
82
- # @return [Boolean]
83
- #
84
- # @api public
85
- def trigger?
86
- !key.nil? && !key.name.nil?
87
- end
88
- end # KeyEvent
89
- end # Reader
90
- end # Prompt
91
- end # TTY
@@ -1,162 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'forwardable'
4
-
5
- module TTY
6
- class Prompt
7
- class Reader
8
- class Line
9
- extend Forwardable
10
-
11
- def_delegators :@text, :size, :length, :to_s, :inspect,
12
- :slice!, :empty?
13
-
14
- attr_accessor :text
15
-
16
- attr_accessor :cursor
17
-
18
- def initialize(text = "")
19
- @text = text
20
- @cursor = [0, @text.length].max
21
- yield self if block_given?
22
- end
23
-
24
- # Check if cursor reached beginning of the line
25
- #
26
- # @return [Boolean]
27
- #
28
- # @api public
29
- def start?
30
- @cursor == 0
31
- end
32
-
33
- # Check if cursor reached end of the line
34
- #
35
- # @return [Boolean]
36
- #
37
- # @api public
38
- def end?
39
- @cursor == @text.length
40
- end
41
-
42
- # Move line position to the left by n chars
43
- #
44
- # @api public
45
- def left(n = 1)
46
- @cursor = [0, @cursor - n].max
47
- end
48
-
49
- # Move line position to the right by n chars
50
- #
51
- # @api public
52
- def right(n = 1)
53
- @cursor = [@text.length, @cursor + n].min
54
- end
55
-
56
- # Move cursor to beginning position
57
- #
58
- # @api public
59
- def move_to_start
60
- @cursor = 0
61
- end
62
-
63
- # Move cursor to end position
64
- #
65
- # @api public
66
- def move_to_end
67
- @cursor = @text.length # put cursor outside of text
68
- end
69
-
70
- # Insert characters inside a line. When the lines exceeds
71
- # maximum length, an extra space is added to accomodate index.
72
- #
73
- # @param [Integer] i
74
- # the index to insert at
75
- #
76
- # @example
77
- # text = 'aaa'
78
- # line[5]= 'b'
79
- # => 'aaa b'
80
- #
81
- # @api public
82
- def []=(i, chars)
83
- if i.is_a?(Range)
84
- @text[i] = chars
85
- @cursor += chars.length
86
- return
87
- end
88
-
89
- if i <= 0
90
- before_text = ''
91
- after_text = @text.dup
92
- elsif i == @text.length - 1
93
- before_text = @text.dup
94
- after_text = ''
95
- elsif i > @text.length - 1
96
- before_text = @text.dup
97
- after_text = ?\s * (i - @text.length)
98
- @cursor += after_text.length
99
- else
100
- before_text = @text[0..i-1].dup
101
- after_text = @text[i..-1].dup
102
- end
103
-
104
- if i > @text.length - 1
105
- @text = before_text << after_text << chars
106
- else
107
- @text = before_text << chars << after_text
108
- end
109
-
110
- @cursor = i + chars.length
111
- end
112
-
113
- # Read character
114
- #
115
- # @api public
116
- def [](i)
117
- @text[i]
118
- end
119
-
120
- # Replace current line with new text
121
- #
122
- # @param [String] text
123
- #
124
- # @api public
125
- def replace(text)
126
- @text = text
127
- @cursor = @text.length # put cursor outside of text
128
- end
129
-
130
- # Insert char(s) at cursor position
131
- #
132
- # @api public
133
- def insert(chars)
134
- self[@cursor] = chars
135
- end
136
-
137
- # Add char and move cursor
138
- #
139
- # @api public
140
- def <<(char)
141
- @text << char
142
- @cursor += 1
143
- end
144
-
145
- # Remove char from the line at current position
146
- #
147
- # @api public
148
- def delete
149
- @text.slice!(@cursor, 1)
150
- end
151
-
152
- # Remove char from the line in front of the cursor
153
- #
154
- # @api public
155
- def remove
156
- left
157
- @text.slice!(@cursor, 1)
158
- end
159
- end # Line
160
- end # Reader
161
- end # Prompt
162
- end # TTY
@@ -1,44 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'io/console'
4
-
5
- module TTY
6
- class Prompt
7
- class Reader
8
- class Mode
9
- # Initialize a Terminal
10
- #
11
- # @api public
12
- def initialize(input = $stdin)
13
- @input = input
14
- end
15
-
16
- # Echo given block
17
- #
18
- # @param [Boolean] is_on
19
- #
20
- # @api public
21
- def echo(is_on = true, &block)
22
- if is_on || !@input.tty?
23
- yield
24
- else
25
- @input.noecho(&block)
26
- end
27
- end
28
-
29
- # Use raw mode in the given block
30
- #
31
- # @param [Boolean] is_on
32
- #
33
- # @api public
34
- def raw(is_on = true, &block)
35
- if is_on && @input.tty?
36
- @input.raw(&block)
37
- else
38
- yield
39
- end
40
- end
41
- end # Mode
42
- end # Reader
43
- end # Prompt
44
- end # TTY
@@ -1,55 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'fiddle'
4
-
5
- module TTY
6
- class Prompt
7
- class Reader
8
- module WinAPI
9
- include Fiddle
10
-
11
- Handle = RUBY_VERSION >= "2.0.0" ? Fiddle::Handle : DL::Handle
12
-
13
- CRT_HANDLE = Handle.new("msvcrt") rescue Handle.new("crtdll")
14
-
15
- # Get a character from the console without echo.
16
- #
17
- # @return [String]
18
- # return the character read
19
- #
20
- # @api public
21
- def getch
22
- @@getch ||= Fiddle::Function.new(CRT_HANDLE["_getch"], [], TYPE_INT)
23
- @@getch.call
24
- end
25
- module_function :getch
26
-
27
- # Gets a character from the console with echo.
28
- #
29
- # @return [String]
30
- # return the character read
31
- #
32
- # @api public
33
- def getche
34
- @@getche ||= Fiddle::Function.new(CRT_HANDLE["_getche"], [], TYPE_INT)
35
- @@getche.call
36
- end
37
- module_function :getche
38
-
39
- # Check the console for recent keystroke. If the function
40
- # returns a nonzero value, a keystroke is waiting in the buffer.
41
- #
42
- # @return [Integer]
43
- # return a nonzero value if a key has been pressed. Otherwirse,
44
- # it returns 0.
45
- #
46
- # @api public
47
- def kbhit
48
- @@kbhit ||= Fiddle::Function.new(CRT_HANDLE["_kbhit"], [], TYPE_INT)
49
- @@kbhit.call
50
- end
51
- module_function :kbhit
52
- end # WinAPI
53
- end # Reader
54
- end # Prompt
55
- end # TTY
@@ -1,91 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require_relative 'codes'
4
-
5
- module TTY
6
- class Prompt
7
- class Reader
8
- class WinConsole
9
- ESC = "\e".freeze
10
- NUL_HEX = "\x00".freeze
11
- EXT_HEX = "\xE0".freeze
12
-
13
- # Key codes
14
- #
15
- # @return [Hash[Symbol]]
16
- #
17
- # @api public
18
- attr_reader :keys
19
-
20
- # Escape codes
21
- #
22
- # @return [Array[Integer]]
23
- #
24
- # @api public
25
- attr_reader :escape_codes
26
-
27
- def initialize(input)
28
- require_relative 'win_api'
29
- @input = input
30
- @keys = Codes.win_keys
31
- @escape_codes = [[NUL_HEX.ord], [ESC.ord], EXT_HEX.bytes.to_a]
32
- end
33
-
34
- # Get a character from console blocking for input
35
- #
36
- # @param [Hash[Symbol]] options
37
- # @option options [Symbol] :echo
38
- # the echo mode toggle
39
- # @option options [Symbol] :raw
40
- # the raw mode toggle
41
- #
42
- # @return [String]
43
- #
44
- # @api private
45
- def get_char(options)
46
- if options[:raw] && options[:echo]
47
- if options[:nonblock]
48
- get_char_echo_non_blocking
49
- else
50
- get_char_echo_blocking
51
- end
52
- elsif options[:raw] && !options[:echo]
53
- options[:nonblock] ? get_char_non_blocking : get_char_blocking
54
- elsif !options[:raw] && !options[:echo]
55
- options[:nonblock] ? get_char_non_blocking : get_char_blocking
56
- else
57
- @input.getc
58
- end
59
- end
60
-
61
- # Get the char for last key pressed, or if no keypress return nil
62
- #
63
- # @api private
64
- def get_char_non_blocking
65
- input_ready? ? get_char_blocking : nil
66
- end
67
-
68
- def get_char_echo_non_blocking
69
- input_ready? ? get_char_echo_blocking : nil
70
- end
71
-
72
- def get_char_blocking
73
- WinAPI.getch.chr
74
- end
75
-
76
- def get_char_echo_blocking
77
- WinAPI.getche.chr
78
- end
79
-
80
- # Check if IO has user input
81
- #
82
- # @return [Boolean]
83
- #
84
- # @api private
85
- def input_ready?
86
- !WinAPI.kbhit.zero?
87
- end
88
- end # Console
89
- end # Reader
90
- end # Prompt
91
- end # TTY
@@ -1,350 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'wisper'
4
- require 'rbconfig'
5
-
6
- require_relative 'reader/history'
7
- require_relative 'reader/line'
8
- require_relative 'reader/key_event'
9
- require_relative 'reader/console'
10
- require_relative 'reader/win_console'
11
-
12
- module TTY
13
- # A class responsible for shell prompt interactions.
14
- class Prompt
15
- # A class responsible for reading character input from STDIN
16
- #
17
- # Used internally to provide key and line reading functionality
18
- #
19
- # @api private
20
- class Reader
21
- include Wisper::Publisher
22
-
23
- # Raised when the user hits the interrupt key(Control-C)
24
- #
25
- # @api public
26
- InputInterrupt = Class.new(StandardError)
27
-
28
- attr_reader :input
29
-
30
- attr_reader :output
31
-
32
- attr_reader :env
33
-
34
- attr_reader :track_history
35
- alias track_history? track_history
36
-
37
- attr_reader :console
38
-
39
- # Key codes
40
- CARRIAGE_RETURN = 13
41
- NEWLINE = 10
42
- BACKSPACE = 127
43
- DELETE = 8
44
-
45
- # Initialize a Reader
46
- #
47
- # @param [IO] input
48
- # the input stream
49
- # @param [IO] output
50
- # the output stream
51
- # @param [Hash] options
52
- # @option options [Symbol] :interrupt
53
- # handling of Ctrl+C key out of :signal, :exit, :noop
54
- # @option options [Boolean] :track_history
55
- # disable line history tracking, true by default
56
- #
57
- # @api public
58
- def initialize(input = $stdin, output = $stdout, options = {})
59
- @input = input
60
- @output = output
61
- @interrupt = options.fetch(:interrupt) { :error }
62
- @env = options.fetch(:env) { ENV }
63
- @track_history = options.fetch(:track_history) { true }
64
- @console = select_console(input)
65
- @history = History.new do |h|
66
- h.duplicates = false
67
- h.exclude = proc { |line| line.strip == '' }
68
- end
69
- @stop = false # gathering input
70
-
71
- subscribe(self)
72
- end
73
-
74
- # Select appropriate console
75
- #
76
- # @api private
77
- def select_console(input)
78
- if windows? && !env['TTY_TEST']
79
- WinConsole.new(input)
80
- else
81
- Console.new(input)
82
- end
83
- end
84
-
85
- # Get input in unbuffered mode.
86
- #
87
- # @example
88
- # unbufferred do
89
- # ...
90
- # end
91
- #
92
- # @api public
93
- def unbufferred(&block)
94
- bufferring = output.sync
95
- # Immediately flush output
96
- output.sync = true
97
- block[] if block_given?
98
- ensure
99
- output.sync = bufferring
100
- end
101
-
102
- # Read a keypress including invisible multibyte codes
103
- # and return a character as a string.
104
- # Nothing is echoed to the console. This call will block for a
105
- # single keypress, but will not wait for Enter to be pressed.
106
- #
107
- # @param [Hash[Symbol]] options
108
- # @option options [Boolean] echo
109
- # whether to echo chars back or not, defaults to false
110
- # @option options [Boolean] raw
111
- # whenther raw mode enabled, defaults to true
112
- #
113
- # @return [String]
114
- #
115
- # @api public
116
- def read_keypress(options = {})
117
- opts = { echo: false, raw: true }.merge(options)
118
- codes = unbufferred { get_codes(opts) }
119
- char = codes ? codes.pack('U*') : nil
120
-
121
- trigger_key_event(char) if char
122
- char
123
- end
124
- alias read_char read_keypress
125
-
126
- # Get input code points
127
- #
128
- # @param [Hash[Symbol]] options
129
- # @param [Array[Integer]] codes
130
- #
131
- # @return [Array[Integer]]
132
- #
133
- # @api private
134
- def get_codes(options = {}, codes = [])
135
- opts = { echo: true, raw: false }.merge(options)
136
- char = console.get_char(opts)
137
- handle_interrupt if char == console.keys[:ctrl_c]
138
- return if char.nil?
139
- codes << char.ord
140
-
141
- condition = proc { |escape|
142
- (codes - escape).empty? ||
143
- (escape - codes).empty? &&
144
- !(64..126).include?(codes.last)
145
- }
146
-
147
- while console.escape_codes.any?(&condition)
148
- get_codes(options, codes)
149
- end
150
- codes
151
- end
152
-
153
- # Get a single line from STDIN. Each key pressed is echoed
154
- # back to the shell. The input terminates when enter or
155
- # return key is pressed.
156
- #
157
- # @param [String] prompt
158
- # the prompt to display before input
159
- #
160
- # @param [Boolean] echo
161
- # if true echo back characters, output nothing otherwise
162
- #
163
- # @return [String]
164
- #
165
- # @api public
166
- def read_line(*args)
167
- options = args.last.respond_to?(:to_hash) ? args.pop : {}
168
- prompt = args.empty? ? '' : args.pop
169
- opts = { echo: true, raw: true }.merge(options)
170
- line = Line.new('')
171
- ctrls = console.keys.keys.grep(/ctrl/)
172
- clear_line = "\e[2K\e[1G"
173
-
174
- while (codes = unbufferred { get_codes(opts) }) && (code = codes[0])
175
- char = codes.pack('U*')
176
- trigger_key_event(char)
177
-
178
- if console.keys[:backspace] == char || BACKSPACE == code
179
- next if line.start?
180
- line.left
181
- line.delete
182
- elsif console.keys[:delete] == char || DELETE == code
183
- line.delete
184
- elsif [console.keys[:ctrl_d],
185
- console.keys[:ctrl_z]].include?(char)
186
- break
187
- elsif ctrls.include?(console.keys.key(char))
188
- # skip
189
- elsif console.keys[:up] == char
190
- next unless history_previous?
191
- line.replace(history_previous)
192
- elsif console.keys[:down] == char
193
- line.replace(history_next? ? history_next : '')
194
- elsif console.keys[:left] == char
195
- line.left
196
- elsif console.keys[:right] == char
197
- line.right
198
- else
199
- if opts[:raw] && code == CARRIAGE_RETURN
200
- char = "\n"
201
- line.move_to_end
202
- end
203
- line.insert(char)
204
- end
205
-
206
- if opts[:raw] && opts[:echo]
207
- output.print(clear_line)
208
- output.print(prompt + line.to_s)
209
- if char == "\n"
210
- line.move_to_start
211
- elsif !line.end?
212
- output.print("\e[#{line.size - line.cursor}D")
213
- end
214
- end
215
-
216
- break if (code == CARRIAGE_RETURN || code == NEWLINE)
217
-
218
- if (console.keys[:backspace] == char || BACKSPACE == code) && opts[:echo]
219
- if opts[:raw]
220
- output.print("\e[1X") unless line.start?
221
- else
222
- output.print(?\s + (line.start? ? '' : ?\b))
223
- end
224
- end
225
- end
226
- add_to_history(line.to_s.rstrip) if track_history?
227
- line.to_s
228
- end
229
-
230
- # Read multiple lines and return them in an array.
231
- # Skip empty lines in the returned lines array.
232
- # The input gathering is terminated by Ctrl+d or Ctrl+z.
233
- #
234
- # @param [String] prompt
235
- # the prompt displayed before the input
236
- #
237
- # @yield [String] line
238
- #
239
- # @return [Array[String]]
240
- #
241
- # @api public
242
- def read_multiline(prompt = '')
243
- @stop = false
244
- lines = []
245
- loop do
246
- line = read_line(prompt)
247
- break if !line || line == ''
248
- next if line !~ /\S/ && !@stop
249
- if block_given?
250
- yield(line) unless line.to_s.empty?
251
- else
252
- lines << line unless line.to_s.empty?
253
- end
254
- break if @stop
255
- end
256
- lines
257
- end
258
- alias read_lines read_multiline
259
-
260
- # Expose event broadcasting
261
- #
262
- # @api public
263
- def trigger(event, *args)
264
- publish(event, *args)
265
- end
266
-
267
- # Capture Ctrl+d and Ctrl+z key events
268
- #
269
- # @api private
270
- def keyctrl_d(*)
271
- @stop = true
272
- end
273
- alias keyctrl_z keyctrl_d
274
-
275
- def add_to_history(line)
276
- @history.push(line)
277
- end
278
-
279
- def history_next?
280
- @history.next?
281
- end
282
-
283
- def history_next
284
- @history.next
285
- @history.get
286
- end
287
-
288
- def history_previous?
289
- @history.previous?
290
- end
291
-
292
- def history_previous
293
- line = @history.get
294
- @history.previous
295
- line
296
- end
297
-
298
- # Inspect class name and public attributes
299
- # @return [String]
300
- #
301
- # @api public
302
- def inspect
303
- "#<#{self.class}: @input=#{input}, @output=#{output}>"
304
- end
305
-
306
- private
307
-
308
- # Publish event
309
- #
310
- # @param [String] char
311
- # the key pressed
312
- #
313
- # @return [nil]
314
- #
315
- # @api private
316
- def trigger_key_event(char)
317
- event = KeyEvent.from(console.keys, char)
318
- trigger(:"key#{event.key.name}", event) if event.trigger?
319
- trigger(:keypress, event)
320
- end
321
-
322
- # Handle input interrupt based on provided value
323
- #
324
- # @api private
325
- def handle_interrupt
326
- case @interrupt
327
- when :signal
328
- Process.kill('SIGINT', Process.pid)
329
- when :exit
330
- exit(130)
331
- when Proc
332
- @interrupt.call
333
- when :noop
334
- return
335
- else
336
- raise InputInterrupt
337
- end
338
- end
339
-
340
- # Check if Windowz mode
341
- #
342
- # @return [Boolean]
343
- #
344
- # @api public
345
- def windows?
346
- ::File::ALT_SEPARATOR == '\\'
347
- end
348
- end # Reader
349
- end # Prompt
350
- end # TTY