tty-reader 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4450cd173fbc5dece09c32d0ad799bb5fcccff3a
4
+ data.tar.gz: e744eaefca8e5b03f79b41d502fe71a2ba8b3166
5
+ SHA512:
6
+ metadata.gz: b113279156eb75f4f2c7079abe5c225993c4f0b24bd33fb07dd45366e316bf9a9356ea23b0f96cf122e36502fa85161d8ccc33f7a4e8ef2dae57faf184f4f77a
7
+ data.tar.gz: 21fd68ce3809c21a88dd24d18ec25348e4111fc41a7828c6c2bb3ccb6279f7fed3477c1261d9d65e3911f8d6c5656f92f908fee5ac9d1ab44609100feb9c9a5c
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --warnings
@@ -0,0 +1,25 @@
1
+ ---
2
+ language: ruby
3
+ sudo: false
4
+ cache: bundler
5
+ bundler_args: --without tools
6
+ script: "bundle exec rake ci"
7
+ rvm:
8
+ - 1.9.3
9
+ - 2.0.0
10
+ - 2.1.10
11
+ - 2.2.6
12
+ - 2.3.3
13
+ - 2.4.1
14
+ - ruby-head
15
+ - jruby-9000
16
+ - jruby-head
17
+ matrix:
18
+ allow_failures:
19
+ - rvm: ruby-head
20
+ - rvm: jruby-head
21
+ fast_finish: true
22
+ branches:
23
+ only: master
24
+ notifications:
25
+ email: false
@@ -0,0 +1,7 @@
1
+ # Change log
2
+
3
+ ## [v0.1.0] - 2017-08-30
4
+
5
+ * Initial implementation and release
6
+
7
+ [v0.1.0]: https://github.com/peter-murach/tty-reader/compare/v0.1.0
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at [email]. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ gemspec
6
+
7
+ group :test do
8
+ gem 'benchmark-ips', '~> 2.0.0'
9
+ gem 'simplecov', '~> 0.10.0'
10
+ gem 'coveralls', '~> 0.8.2'
11
+ gem 'term-ansicolor', '=1.3.2'
12
+ end
13
+
14
+ group :tools do
15
+ gem 'byebug', platform: :mri
16
+ end
17
+
18
+ group :metrics do
19
+ gem 'yard', '~> 0.8.7'
20
+ gem 'yardstick', '~> 0.9.9'
21
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Piotr Murach
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,187 @@
1
+ # TTY::Reader [![Gitter](https://badges.gitter.im/Join%20Chat.svg)][gitter]
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/tty-reader.svg)][gem]
4
+ [![Build Status](https://secure.travis-ci.org/piotrmurach/tty-reader.svg?branch=master)][travis]
5
+ [![Build status](https://ci.appveyor.com/api/projects/status/cj4owy2vlty2q1ko?svg=true)][appveyor]
6
+ [![Code Climate](https://codeclimate.com/github/piotrmurach/tty-reader/badges/gpa.svg)][codeclimate]
7
+ [![Coverage Status](https://coveralls.io/repos/github/piotrmurach/tty-reader/badge.svg)][coverage]
8
+ [![Inline docs](http://inch-ci.org/github/piotrmurach/tty-reader.svg?branch=master)][inchpages]
9
+
10
+ [gitter]: https://gitter.im/piotrmurach/tty
11
+ [gem]: http://badge.fury.io/rb/tty-reader
12
+ [travis]: http://travis-ci.org/piotrmurach/tty-reader
13
+ [appveyor]: https://ci.appveyor.com/project/piotrmurach/tty-reader
14
+ [codeclimate]: https://codeclimate.com/github/piotrmurach/tty-reader
15
+ [coverage]: https://coveralls.io/github/piotrmurach/tty-reader
16
+ [inchpages]: http://inch-ci.org/github/piotrmurach/tty-reader
17
+
18
+ > A pure Ruby library that provides a set of methods for processing keyboard input in character, line and multiline modes. In addition it maintains history of entered input with an ability to recall and re-edit those inputs and register to listen for keystroke events.
19
+
20
+ **TTY::Reader** provides independent reader component for [TTY](https://github.com/piotrmurach/tty) toolkit.
21
+
22
+ ## Features
23
+
24
+ * Reading single keypress
25
+ * Line editing
26
+ * Multiline input
27
+ * History management
28
+ * Ability to register for key events
29
+
30
+ ## Installation
31
+
32
+ Add this line to your application's Gemfile:
33
+
34
+ ```ruby
35
+ gem 'tty-reader'
36
+ ```
37
+
38
+ And then execute:
39
+
40
+ $ bundle
41
+
42
+ Or install it yourself as:
43
+
44
+ $ gem install tty-reader
45
+
46
+ * [1. Usage](#1-usage)
47
+ * [2. API](#2-api)
48
+ * [2.1 read_keypress](#21-read_keypress)
49
+ * [2.2 read_line](#22-read_line)
50
+ * [2.3 read_multiline](#23-read_multiline)
51
+ * [2.4 events](#24-events)
52
+ * [3. Configuration](#3-configuration)
53
+ * [3.1 :interrupt](#31-interrupt)
54
+ * [3.2 :track_history](#31-track_history)
55
+
56
+ ## Usage
57
+
58
+ ```ruby
59
+ reader = TTY::Reader.new
60
+ ```
61
+
62
+ ## API
63
+
64
+ ### 2.1 read_keypress
65
+
66
+ To read a single key stroke from the user use `read_char` or `read_keypress`:
67
+
68
+ ```ruby
69
+ reader.read_char
70
+ reader.read_keypress
71
+ ```
72
+
73
+ ## 2.2 read_line
74
+
75
+ To read a single line terminated by new line character use `read_line` like so:
76
+
77
+ ```ruby
78
+ reader.read_line
79
+ ```
80
+
81
+ ## 2.3 read_multiline
82
+
83
+ To read more than one line terminated by `Ctrl+d` or `Ctrl+z` use `read_multiline`:
84
+
85
+ ```ruby
86
+ reader.read_multiline
87
+ # => [ "line1", "line2", ... ]
88
+ ```
89
+
90
+ ## 2.4 events
91
+
92
+ You can register to listen on a key pressed events. This can be done by calling `on` with a event name:
93
+
94
+ ```ruby
95
+ reader.on(:keypress) { |event| .... }
96
+ ```
97
+
98
+ The event object is yielded to a block whenever particular key event fires. The event has `key` and `value` methods. Further, the `key` responds to following messages:
99
+
100
+ * `name` - the name of the event such as :up, :down, letter or digit
101
+ * `meta` - true if event is non-standard key associated
102
+ * `shift` - true if shift has been pressed with the key
103
+ * `ctrl` - true if ctrl has been pressed with the key
104
+
105
+ For example, to add listen to vim like navigation keys, one would do the following:
106
+
107
+ ```ruby
108
+ reader.on(:keypress) do |event|
109
+ if event.value == 'j'
110
+ ...
111
+ end
112
+
113
+ if event.value == 'k'
114
+ ...
115
+ end
116
+ end
117
+ ```
118
+
119
+ You can subscribe to more than one event:
120
+
121
+ ```ruby
122
+ prompt.on(:keypress) { |key| ... }
123
+ .on(:keydown) { |key| ... }
124
+ ```
125
+
126
+ The available events are:
127
+
128
+ * `:keypress`
129
+ * `:keydown`
130
+ * `:keyup`
131
+ * `:keyleft`
132
+ * `:keyright`
133
+ * `:keynum`
134
+ * `:keytab`
135
+ * `:keyenter`
136
+ * `:keyreturn`
137
+ * `:keyspace`
138
+ * `:keyescape`
139
+ * `:keydelete`
140
+ * `:keybackspace`
141
+
142
+ ## 3. Configuration
143
+
144
+ ### 3.1. :interrupt
145
+
146
+ By default `InputInterrupt` error will be raised when the user hits the interrupt key(Control-C). However, you can customise this behaviour by passing the `:interrupt` option. The available options are:
147
+
148
+ * `:signal` - sends interrupt signal
149
+ * `:exit` - exists with status code
150
+ * `:noop` - skips handler
151
+ * custom proc
152
+
153
+ For example, to send interrupt signal do:
154
+
155
+ ```ruby
156
+ reader = TTY::Reader.new(interrupt: :signal)
157
+ ```
158
+
159
+ ### 3.2. :track_history
160
+
161
+ The `read_line` and `read_multiline` provide history buffer that tracks all the lines entered during `TTY::Reader.new` interactions. The history buffer provides previoius or next lines when user presses up/down arrows respectively. However, if you wish to disable this behaviour use `:track_history` option like so:
162
+
163
+ ```ruby
164
+ reader = TTY::Reader.new(track_history: false)
165
+ ```
166
+
167
+ ## Development
168
+
169
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
170
+
171
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
172
+
173
+ ## Contributing
174
+
175
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/tty-reader. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
176
+
177
+ ## License
178
+
179
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
180
+
181
+ ## Code of Conduct
182
+
183
+ Everyone interacting in the Tty::Reader project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/piotrmurach/tty-reader/blob/master/CODE_OF_CONDUCT.md).
184
+
185
+ ## Copyright
186
+
187
+ Copyright (c) 2017 Piotr Murach. See LICENSE for further details.
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ FileList['tasks/**/*.rake'].each(&method(:import))
6
+
7
+ desc 'Run all specs'
8
+ task ci: %w[ spec ]
9
+
10
+ task default: :spec
@@ -0,0 +1,25 @@
1
+ ---
2
+ install:
3
+ - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
4
+ - ruby --version
5
+ - gem --version
6
+ - bundle install
7
+ build: off
8
+ test_script:
9
+ - bundle exec rake ci
10
+ environment:
11
+ matrix:
12
+ - ruby_version: "193"
13
+ - ruby_version: "200"
14
+ - ruby_version: "200-x64"
15
+ - ruby_version: "21"
16
+ - ruby_version: "21-x64"
17
+ - ruby_version: "22"
18
+ - ruby_version: "22-x64"
19
+ - ruby_version: "23"
20
+ - ruby_version: "23-x64"
21
+ - ruby_version: "24"
22
+ - ruby_version: "24-x64"
23
+ matrix:
24
+ allow_failures:
25
+ - ruby_version: "193"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "tty/reader"
5
+ require "irb"
6
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'tty/reader'
@@ -0,0 +1,348 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'wisper'
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
+ require_relative 'reader/version'
12
+
13
+ module TTY
14
+ # A class responsible for reading character input from STDIN
15
+ #
16
+ # Used internally to provide key and line reading functionality
17
+ #
18
+ # @api private
19
+ class Reader
20
+ include Wisper::Publisher
21
+
22
+ # Raised when the user hits the interrupt key(Control-C)
23
+ #
24
+ # @api public
25
+ InputInterrupt = Class.new(StandardError)
26
+
27
+ attr_reader :input
28
+
29
+ attr_reader :output
30
+
31
+ attr_reader :env
32
+
33
+ attr_reader :track_history
34
+ alias track_history? track_history
35
+
36
+ attr_reader :console
37
+
38
+ # Key codes
39
+ CARRIAGE_RETURN = 13
40
+ NEWLINE = 10
41
+ BACKSPACE = 127
42
+ DELETE = 8
43
+
44
+ # Initialize a Reader
45
+ #
46
+ # @param [IO] input
47
+ # the input stream
48
+ # @param [IO] output
49
+ # the output stream
50
+ # @param [Hash] options
51
+ # @option options [Symbol] :interrupt
52
+ # handling of Ctrl+C key out of :signal, :exit, :noop
53
+ # @option options [Boolean] :track_history
54
+ # disable line history tracking, true by default
55
+ #
56
+ # @api public
57
+ def initialize(input = $stdin, output = $stdout, options = {})
58
+ @input = input
59
+ @output = output
60
+ @interrupt = options.fetch(:interrupt) { :error }
61
+ @env = options.fetch(:env) { ENV }
62
+ @track_history = options.fetch(:track_history) { true }
63
+ @console = select_console(input)
64
+ @history = History.new do |h|
65
+ h.duplicates = false
66
+ h.exclude = proc { |line| line.strip == '' }
67
+ end
68
+ @stop = false # gathering input
69
+
70
+ subscribe(self)
71
+ end
72
+
73
+ # Select appropriate console
74
+ #
75
+ # @api private
76
+ def select_console(input)
77
+ if windows? && !env['TTY_TEST']
78
+ WinConsole.new(input)
79
+ else
80
+ Console.new(input)
81
+ end
82
+ end
83
+
84
+ # Get input in unbuffered mode.
85
+ #
86
+ # @example
87
+ # unbufferred do
88
+ # ...
89
+ # end
90
+ #
91
+ # @api public
92
+ def unbufferred(&block)
93
+ bufferring = output.sync
94
+ # Immediately flush output
95
+ output.sync = true
96
+ block[] if block_given?
97
+ ensure
98
+ output.sync = bufferring
99
+ end
100
+
101
+ # Read a keypress including invisible multibyte codes
102
+ # and return a character as a string.
103
+ # Nothing is echoed to the console. This call will block for a
104
+ # single keypress, but will not wait for Enter to be pressed.
105
+ #
106
+ # @param [Hash[Symbol]] options
107
+ # @option options [Boolean] echo
108
+ # whether to echo chars back or not, defaults to false
109
+ # @option options [Boolean] raw
110
+ # whenther raw mode enabled, defaults to true
111
+ #
112
+ # @return [String]
113
+ #
114
+ # @api public
115
+ def read_keypress(options = {})
116
+ opts = { echo: false, raw: true }.merge(options)
117
+ codes = unbufferred { get_codes(opts) }
118
+ char = codes ? codes.pack('U*') : nil
119
+
120
+ trigger_key_event(char) if char
121
+ char
122
+ end
123
+ alias read_char read_keypress
124
+
125
+ # Get input code points
126
+ #
127
+ # @param [Hash[Symbol]] options
128
+ # @param [Array[Integer]] codes
129
+ #
130
+ # @return [Array[Integer]]
131
+ #
132
+ # @api private
133
+ def get_codes(options = {}, codes = [])
134
+ opts = { echo: true, raw: false }.merge(options)
135
+ char = console.get_char(opts)
136
+ handle_interrupt if char == console.keys[:ctrl_c]
137
+ return if char.nil?
138
+ codes << char.ord
139
+
140
+ condition = proc { |escape|
141
+ (codes - escape).empty? ||
142
+ (escape - codes).empty? &&
143
+ !(64..126).include?(codes.last)
144
+ }
145
+
146
+ while console.escape_codes.any?(&condition)
147
+ get_codes(options, codes)
148
+ end
149
+ codes
150
+ end
151
+
152
+ # Get a single line from STDIN. Each key pressed is echoed
153
+ # back to the shell. The input terminates when enter or
154
+ # return key is pressed.
155
+ #
156
+ # @param [String] prompt
157
+ # the prompt to display before input
158
+ #
159
+ # @param [Boolean] echo
160
+ # if true echo back characters, output nothing otherwise
161
+ #
162
+ # @return [String]
163
+ #
164
+ # @api public
165
+ def read_line(*args)
166
+ options = args.last.respond_to?(:to_hash) ? args.pop : {}
167
+ prompt = args.empty? ? '' : args.pop
168
+ opts = { echo: true, raw: true }.merge(options)
169
+ line = Line.new('')
170
+ ctrls = console.keys.keys.grep(/ctrl/)
171
+ clear_line = "\e[2K\e[1G"
172
+
173
+ while (codes = unbufferred { get_codes(opts) }) && (code = codes[0])
174
+ char = codes.pack('U*')
175
+ trigger_key_event(char)
176
+
177
+ if console.keys[:backspace] == char || BACKSPACE == code
178
+ next if line.start?
179
+ line.left
180
+ line.delete
181
+ elsif console.keys[:delete] == char || DELETE == code
182
+ line.delete
183
+ elsif [console.keys[:ctrl_d],
184
+ console.keys[:ctrl_z]].include?(char)
185
+ break
186
+ elsif ctrls.include?(console.keys.key(char))
187
+ # skip
188
+ elsif console.keys[:up] == char
189
+ next unless history_previous?
190
+ line.replace(history_previous)
191
+ elsif console.keys[:down] == char
192
+ line.replace(history_next? ? history_next : '')
193
+ elsif console.keys[:left] == char
194
+ line.left
195
+ elsif console.keys[:right] == char
196
+ line.right
197
+ else
198
+ if opts[:raw] && code == CARRIAGE_RETURN
199
+ char = "\n"
200
+ line.move_to_end
201
+ end
202
+ line.insert(char)
203
+ end
204
+
205
+ if opts[:raw] && opts[:echo]
206
+ output.print(clear_line)
207
+ output.print(prompt + line.to_s)
208
+ if char == "\n"
209
+ line.move_to_start
210
+ elsif !line.end?
211
+ output.print("\e[#{line.size - line.cursor}D")
212
+ end
213
+ end
214
+
215
+ break if (code == CARRIAGE_RETURN || code == NEWLINE)
216
+
217
+ if (console.keys[:backspace] == char || BACKSPACE == code) && opts[:echo]
218
+ if opts[:raw]
219
+ output.print("\e[1X") unless line.start?
220
+ else
221
+ output.print(?\s + (line.start? ? '' : ?\b))
222
+ end
223
+ end
224
+ end
225
+ add_to_history(line.to_s.rstrip) if track_history?
226
+ line.to_s
227
+ end
228
+
229
+ # Read multiple lines and return them in an array.
230
+ # Skip empty lines in the returned lines array.
231
+ # The input gathering is terminated by Ctrl+d or Ctrl+z.
232
+ #
233
+ # @param [String] prompt
234
+ # the prompt displayed before the input
235
+ #
236
+ # @yield [String] line
237
+ #
238
+ # @return [Array[String]]
239
+ #
240
+ # @api public
241
+ def read_multiline(prompt = '')
242
+ @stop = false
243
+ lines = []
244
+ loop do
245
+ line = read_line(prompt)
246
+ break if !line || line == ''
247
+ next if line !~ /\S/ && !@stop
248
+ if block_given?
249
+ yield(line) unless line.to_s.empty?
250
+ else
251
+ lines << line unless line.to_s.empty?
252
+ end
253
+ break if @stop
254
+ end
255
+ lines
256
+ end
257
+ alias read_lines read_multiline
258
+
259
+ # Expose event broadcasting
260
+ #
261
+ # @api public
262
+ def trigger(event, *args)
263
+ publish(event, *args)
264
+ end
265
+
266
+ # Capture Ctrl+d and Ctrl+z key events
267
+ #
268
+ # @api private
269
+ def keyctrl_d(*)
270
+ @stop = true
271
+ end
272
+ alias keyctrl_z keyctrl_d
273
+
274
+ def add_to_history(line)
275
+ @history.push(line)
276
+ end
277
+
278
+ def history_next?
279
+ @history.next?
280
+ end
281
+
282
+ def history_next
283
+ @history.next
284
+ @history.get
285
+ end
286
+
287
+ def history_previous?
288
+ @history.previous?
289
+ end
290
+
291
+ def history_previous
292
+ line = @history.get
293
+ @history.previous
294
+ line
295
+ end
296
+
297
+ # Inspect class name and public attributes
298
+ # @return [String]
299
+ #
300
+ # @api public
301
+ def inspect
302
+ "#<#{self.class}: @input=#{input}, @output=#{output}>"
303
+ end
304
+
305
+ private
306
+
307
+ # Publish event
308
+ #
309
+ # @param [String] char
310
+ # the key pressed
311
+ #
312
+ # @return [nil]
313
+ #
314
+ # @api private
315
+ def trigger_key_event(char)
316
+ event = KeyEvent.from(console.keys, char)
317
+ trigger(:"key#{event.key.name}", event) if event.trigger?
318
+ trigger(:keypress, event)
319
+ end
320
+
321
+ # Handle input interrupt based on provided value
322
+ #
323
+ # @api private
324
+ def handle_interrupt
325
+ case @interrupt
326
+ when :signal
327
+ Process.kill('SIGINT', Process.pid)
328
+ when :exit
329
+ exit(130)
330
+ when Proc
331
+ @interrupt.call
332
+ when :noop
333
+ return
334
+ else
335
+ raise InputInterrupt
336
+ end
337
+ end
338
+
339
+ # Check if Windowz mode
340
+ #
341
+ # @return [Boolean]
342
+ #
343
+ # @api public
344
+ def windows?
345
+ ::File::ALT_SEPARATOR == '\\'
346
+ end
347
+ end # Reader
348
+ end # TTY