infopark-user_io 0.0.6
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 +7 -0
- data/.gitignore +2 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/Rakefile +1 -0
- data/lib/infopark/user_io/version.rb +5 -0
- data/lib/infopark/user_io.rb +283 -0
- data/lib/infopark-user_io.rb +1 -0
- data/spec/spec_helper.rb +64 -0
- data/spec/user_io_spec.rb +115 -0
- data/user_io.gemspec +16 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 09b42d53dccdd52424b6ae8eb12a6a2241199db2
|
4
|
+
data.tar.gz: 19de857e3bf959eef4c2c0ccd908c7c6060f0c15
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 445017a230548c5f55439e5082ce7519a8385e8fae5c21786431844a6b5b887ba037422ac8c8ed10449c1e5e787ae10898829e8cf24f8a06333a822b93792af5
|
7
|
+
data.tar.gz: f4b865b51d53d6f212ab0982636976f0a3d4a7687f8155a0147c3b489d40ab8bb9160aeedf44d24dd60b4c1310c54f27781c65c38cff9e2a611e3626c7feca20
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,283 @@
|
|
1
|
+
module Infopark
|
2
|
+
|
3
|
+
# TODO extract into infopark base gem
|
4
|
+
class ImplementationError < StandardError; end
|
5
|
+
|
6
|
+
# TODO
|
7
|
+
# - beep (\a) on #acknowledge, #ask or #confirm (and maybe on #listen, too)
|
8
|
+
class UserIO
|
9
|
+
class Aborted < RuntimeError
|
10
|
+
end
|
11
|
+
|
12
|
+
class MissingEnv < RuntimeError
|
13
|
+
end
|
14
|
+
|
15
|
+
class Progress
|
16
|
+
def initialize(label, user_io)
|
17
|
+
@label = label
|
18
|
+
@user_io = user_io
|
19
|
+
end
|
20
|
+
|
21
|
+
def start
|
22
|
+
unless @started
|
23
|
+
user_io.tell("#{label} ", newline: false)
|
24
|
+
@started = true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def increment
|
29
|
+
raise ImplementationError, "progress not started yet" unless @started
|
30
|
+
user_io.tell(".", newline: false)
|
31
|
+
end
|
32
|
+
|
33
|
+
def finish
|
34
|
+
if @started
|
35
|
+
user_io.tell("… ", newline: false)
|
36
|
+
user_io.tell("OK", color: :green, bright: true)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :label, :user_io
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(output_prefix: nil)
|
46
|
+
case output_prefix
|
47
|
+
when String
|
48
|
+
@output_prefix = "[#{output_prefix}] "
|
49
|
+
when Proc, Method
|
50
|
+
@output_prefix_proc = ->() { "[#{output_prefix.call}] " }
|
51
|
+
when :timestamp
|
52
|
+
@output_prefix_proc = ->() { "[#{Time.now.strftime("%T.%L")}] " }
|
53
|
+
end
|
54
|
+
@line_pending = {}
|
55
|
+
end
|
56
|
+
|
57
|
+
def tell(*texts, newline: true, **line_options)
|
58
|
+
lines = texts.flatten.map {|text| text.to_s.split("\n", -1) }.flatten
|
59
|
+
|
60
|
+
lines[0...-1].each {|line| tell_line(line, **line_options) }
|
61
|
+
tell_line(lines.last, newline: newline, **line_options)
|
62
|
+
end
|
63
|
+
|
64
|
+
def warn(*text)
|
65
|
+
tell(*text, color: :yellow, bright: true)
|
66
|
+
end
|
67
|
+
|
68
|
+
def tell_error(e, **options)
|
69
|
+
tell(e, **options, color: :red, bright: true)
|
70
|
+
end
|
71
|
+
|
72
|
+
def acknowledge(*text)
|
73
|
+
tell("-" * 80)
|
74
|
+
tell(*text, color: :cyan, bright: true)
|
75
|
+
tell("-" * 80)
|
76
|
+
tell("Please press ENTER to continue.")
|
77
|
+
read_line
|
78
|
+
end
|
79
|
+
|
80
|
+
def ask(*text, default: nil)
|
81
|
+
# TODO implementation error if default not boolean or nil
|
82
|
+
tell("-" * 80)
|
83
|
+
tell(*text, color: :cyan, bright: true)
|
84
|
+
tell("-" * 80)
|
85
|
+
default_answer = default ? "yes" : "no" unless default.nil?
|
86
|
+
tell("(yes/no) #{default_answer && "[#{default_answer}] "}> ", newline: false)
|
87
|
+
until %w(yes no).include?(answer = read_line.strip.downcase)
|
88
|
+
if answer.empty?
|
89
|
+
answer = default_answer
|
90
|
+
break
|
91
|
+
end
|
92
|
+
tell("I couldn't understand “#{answer}”.", newline: false, color: :red, bright: true)
|
93
|
+
tell(" > ", newline: false)
|
94
|
+
end
|
95
|
+
answer == "yes"
|
96
|
+
end
|
97
|
+
|
98
|
+
def listen(prompt = nil, **options)
|
99
|
+
prompt << " " if prompt
|
100
|
+
tell("#{prompt}> ", **options, newline: false)
|
101
|
+
read_line.strip
|
102
|
+
end
|
103
|
+
|
104
|
+
def confirm(*text)
|
105
|
+
ask(*text) or raise Aborted
|
106
|
+
end
|
107
|
+
|
108
|
+
def new_progress(label)
|
109
|
+
Progress.new(label, self)
|
110
|
+
end
|
111
|
+
|
112
|
+
def start_progress(label)
|
113
|
+
new_progress(label).tap(&:start)
|
114
|
+
end
|
115
|
+
|
116
|
+
def background_other_threads
|
117
|
+
unless @foreground_thread
|
118
|
+
@background_lines = []
|
119
|
+
@foreground_thread = Thread.current
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def foreground
|
124
|
+
if @foreground_thread
|
125
|
+
@background_lines.each(&STDOUT.method(:write))
|
126
|
+
@foreground_thread = nil
|
127
|
+
# take over line_pending from background
|
128
|
+
@line_pending[false] = @line_pending[true]
|
129
|
+
@line_pending[true] = false
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def <<(msg)
|
134
|
+
tell(msg.chomp, newline: msg.end_with?("\n"))
|
135
|
+
end
|
136
|
+
|
137
|
+
def tty?
|
138
|
+
STDOUT.tty?
|
139
|
+
end
|
140
|
+
|
141
|
+
def edit_file(kind_of_data, filename = nil)
|
142
|
+
wait_for_foreground if background?
|
143
|
+
|
144
|
+
editor = ENV['EDITOR'] or raise MissingEnv, "No EDITOR specified."
|
145
|
+
|
146
|
+
filename ||= Tempfile.new("").path
|
147
|
+
tell("Start editing #{kind_of_data} using #{editor}…")
|
148
|
+
sleep(1.7)
|
149
|
+
system(editor, filename)
|
150
|
+
|
151
|
+
File.read(filename)
|
152
|
+
end
|
153
|
+
|
154
|
+
def select(description, items, item_describer: :to_s, default: nil)
|
155
|
+
return if items.empty?
|
156
|
+
|
157
|
+
describer =
|
158
|
+
case item_describer
|
159
|
+
when Method, Proc
|
160
|
+
item_describer
|
161
|
+
else
|
162
|
+
->(item) { item.send(item_describer) }
|
163
|
+
end
|
164
|
+
|
165
|
+
choice = nil
|
166
|
+
if items.size == 1
|
167
|
+
choice = items.first
|
168
|
+
tell("Selected #{describer.call(choice)}.", color: :yellow)
|
169
|
+
return choice
|
170
|
+
end
|
171
|
+
|
172
|
+
items = items.sort_by(&describer)
|
173
|
+
|
174
|
+
tell("-" * 80)
|
175
|
+
tell("Please select #{description}:", color: :cyan, bright: true)
|
176
|
+
items.each_with_index do |item, i|
|
177
|
+
tell("#{i + 1}: #{describer.call(item)}", color: :cyan, bright: true)
|
178
|
+
end
|
179
|
+
tell("-" * 80)
|
180
|
+
default_index = items.index(default)
|
181
|
+
default_selection = "[#{default_index + 1}] " if default_index
|
182
|
+
until choice
|
183
|
+
tell("Your choice #{default_selection}> ", newline: false)
|
184
|
+
answer = read_line.strip
|
185
|
+
if answer.empty?
|
186
|
+
choice = default
|
187
|
+
else
|
188
|
+
int_answer = answer.to_i
|
189
|
+
if int_answer.to_s != answer
|
190
|
+
tell("Please enter a valid integer.")
|
191
|
+
elsif int_answer < 1 || int_answer > items.size
|
192
|
+
tell("Please enter a number from 1 through #{items.size}.")
|
193
|
+
else
|
194
|
+
choice = items[int_answer - 1]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
choice
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
def background?
|
204
|
+
!!@foreground_thread && @foreground_thread != Thread.current
|
205
|
+
end
|
206
|
+
|
207
|
+
def wait_for_foreground
|
208
|
+
sleep 0.1 while background?
|
209
|
+
end
|
210
|
+
|
211
|
+
def output_prefix
|
212
|
+
@output_prefix || @output_prefix_proc && @output_prefix_proc.call
|
213
|
+
end
|
214
|
+
|
215
|
+
def read_line
|
216
|
+
wait_for_foreground if background?
|
217
|
+
@line_pending[false] = false
|
218
|
+
STDIN.gets.chomp
|
219
|
+
end
|
220
|
+
|
221
|
+
def tell_line(line, newline: true, prefix: true, **color_options)
|
222
|
+
if tty?
|
223
|
+
if line_prefix = text_color(**color_options)
|
224
|
+
line_postfix = text_color(color: :none, bright: false)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
prefix = false if @line_pending[background?]
|
229
|
+
|
230
|
+
out_line = "#{output_prefix if prefix}#{line_prefix}#{line}#{line_postfix}#{"\n" if newline}"
|
231
|
+
if background?
|
232
|
+
@background_lines << out_line
|
233
|
+
else
|
234
|
+
STDOUT.write(out_line)
|
235
|
+
end
|
236
|
+
|
237
|
+
@line_pending[background?] = !newline
|
238
|
+
end
|
239
|
+
|
240
|
+
def control_sequence(*parameters, function)
|
241
|
+
"\033[#{parameters.join(";")}#{function}"
|
242
|
+
end
|
243
|
+
|
244
|
+
# SGR: Select Graphic Rendition … far too long for a function name ;)
|
245
|
+
def sgr_sequence(*parameters)
|
246
|
+
control_sequence(*parameters, :m)
|
247
|
+
end
|
248
|
+
|
249
|
+
def text_color(color: nil, bright: nil, faint: nil, italic: nil, underline: nil)
|
250
|
+
return if color.nil? && bright.nil?
|
251
|
+
sequence = []
|
252
|
+
unless bright.nil? && faint.nil?
|
253
|
+
sequence << (bright ? 1 : (faint ? 2 : 22))
|
254
|
+
end
|
255
|
+
unless italic.nil?
|
256
|
+
sequence << (italic ? 3 : 23)
|
257
|
+
end
|
258
|
+
unless underline.nil?
|
259
|
+
sequence << (underline ? 4 : 24)
|
260
|
+
end
|
261
|
+
case color
|
262
|
+
when :red
|
263
|
+
sequence << 31
|
264
|
+
when :green
|
265
|
+
sequence << 32
|
266
|
+
when :yellow
|
267
|
+
sequence << 33
|
268
|
+
when :blue
|
269
|
+
sequence << 34
|
270
|
+
when :purple
|
271
|
+
sequence << 35
|
272
|
+
when :cyan
|
273
|
+
sequence << 36
|
274
|
+
when :white
|
275
|
+
sequence << 37
|
276
|
+
when :none
|
277
|
+
sequence << 39
|
278
|
+
end
|
279
|
+
sgr_sequence(*sequence)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'infopark/user_io'
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative '../lib/infopark-user_io'
|
2
|
+
|
3
|
+
RSpec.configure do |config|
|
4
|
+
config.expect_with :rspec do |expectations|
|
5
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
6
|
+
end
|
7
|
+
|
8
|
+
config.mock_with :rspec do |mocks|
|
9
|
+
mocks.verify_partial_doubles = true
|
10
|
+
end
|
11
|
+
|
12
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
13
|
+
|
14
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
15
|
+
|
16
|
+
# The settings below are suggested to provide a good initial experience
|
17
|
+
# with RSpec, but feel free to customize to your heart's content.
|
18
|
+
=begin
|
19
|
+
# This allows you to limit a spec run to individual examples or groups
|
20
|
+
# you care about by tagging them with `:focus` metadata. When nothing
|
21
|
+
# is tagged with `:focus`, all examples get run. RSpec also provides
|
22
|
+
# aliases for `it`, `describe`, and `context` that include `:focus`
|
23
|
+
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
24
|
+
config.filter_run_when_matching :focus
|
25
|
+
|
26
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
27
|
+
# recommended. For more details, see:
|
28
|
+
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
29
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
30
|
+
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
31
|
+
config.disable_monkey_patching!
|
32
|
+
|
33
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
34
|
+
# be too noisy due to issues in dependencies.
|
35
|
+
config.warnings = true
|
36
|
+
|
37
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
38
|
+
# file, and it's useful to allow more verbose output when running an
|
39
|
+
# individual spec file.
|
40
|
+
if config.files_to_run.one?
|
41
|
+
# Use the documentation formatter for detailed output,
|
42
|
+
# unless a formatter has already been configured
|
43
|
+
# (e.g. via a command-line flag).
|
44
|
+
config.default_formatter = "doc"
|
45
|
+
end
|
46
|
+
|
47
|
+
# Print the 10 slowest examples and example groups at the
|
48
|
+
# end of the spec run, to help surface which specs are running
|
49
|
+
# particularly slow.
|
50
|
+
config.profile_examples = 10
|
51
|
+
|
52
|
+
# Run specs in random order to surface order dependencies. If you find an
|
53
|
+
# order dependency and want to debug it, you can fix the order by providing
|
54
|
+
# the seed, which is printed after each run.
|
55
|
+
# --seed 1234
|
56
|
+
config.order = :random
|
57
|
+
|
58
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
59
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
60
|
+
# test failures related to randomization by passing the same `--seed` value
|
61
|
+
# as the one that triggered the failure.
|
62
|
+
Kernel.srand config.seed
|
63
|
+
=end
|
64
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
RSpec.describe ::Infopark::UserIO do
|
2
|
+
let(:options) { {} }
|
3
|
+
|
4
|
+
subject(:user_io) { ::Infopark::UserIO.new(**options) }
|
5
|
+
|
6
|
+
before do
|
7
|
+
allow($stdout).to receive(:puts)
|
8
|
+
# for debugging: .and_call_original
|
9
|
+
allow($stdout).to receive(:write)
|
10
|
+
# for debugging: .and_call_original
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#acknowledge" do
|
14
|
+
before { allow($stdin).to receive(:gets).and_return("\n") }
|
15
|
+
|
16
|
+
let(:message) { "Some important statement." }
|
17
|
+
|
18
|
+
subject(:acknowledge) { user_io.acknowledge(message) }
|
19
|
+
|
20
|
+
it "presents the message (colorized)" do
|
21
|
+
expect($stdout).to receive(:write).with("\e[1;36m""Some important statement.""\e[22;39m\n")
|
22
|
+
acknowledge
|
23
|
+
end
|
24
|
+
|
25
|
+
it "asks for pressing “Enter”" do
|
26
|
+
expect($stdout).to receive(:write).with("Please press ENTER to continue.\n")
|
27
|
+
acknowledge
|
28
|
+
end
|
29
|
+
|
30
|
+
it "requests input" do
|
31
|
+
expect($stdin).to receive(:gets).and_return("\n")
|
32
|
+
acknowledge
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#ask" do
|
37
|
+
before { allow($stdin).to receive(:gets).and_return("yes\n") }
|
38
|
+
|
39
|
+
let(:ask_options) { {} }
|
40
|
+
let(:question) { "do you want to?" }
|
41
|
+
|
42
|
+
subject(:ask) { user_io.ask(*Array(question), **ask_options) }
|
43
|
+
|
44
|
+
shared_examples_for "any question" do
|
45
|
+
# TODO
|
46
|
+
#it_behaves_like "handling valid answer"
|
47
|
+
#it_behaves_like "handling invalid input"
|
48
|
+
#it_behaves_like "printing prefix on every line"
|
49
|
+
end
|
50
|
+
|
51
|
+
context "with default" do
|
52
|
+
let(:ask_options) { {default: default_value} }
|
53
|
+
|
54
|
+
context "“true”" do
|
55
|
+
let(:default_value) { true }
|
56
|
+
|
57
|
+
it "presents default answer “yes”" do
|
58
|
+
expect($stdout).to receive(:write).with("(yes/no) [yes] > ")
|
59
|
+
ask
|
60
|
+
end
|
61
|
+
|
62
|
+
it "returns “true” on empty input" do
|
63
|
+
expect($stdin).to receive(:gets).and_return("\n")
|
64
|
+
expect(ask).to be true
|
65
|
+
end
|
66
|
+
|
67
|
+
it_behaves_like "any question"
|
68
|
+
end
|
69
|
+
|
70
|
+
context "“false”" do
|
71
|
+
let(:default_value) { false }
|
72
|
+
|
73
|
+
it "presents default answer “no”" do
|
74
|
+
expect($stdout).to receive(:write).with("(yes/no) [no] > ")
|
75
|
+
ask
|
76
|
+
end
|
77
|
+
|
78
|
+
it "returns “false” on empty input" do
|
79
|
+
expect($stdin).to receive(:gets).and_return("\n")
|
80
|
+
expect(ask).to be false
|
81
|
+
end
|
82
|
+
|
83
|
+
it_behaves_like "any question"
|
84
|
+
end
|
85
|
+
|
86
|
+
context "non boolean" do
|
87
|
+
# TODO
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#select" do
|
93
|
+
before { allow($stdin).to receive(:gets).and_return("1\n") }
|
94
|
+
|
95
|
+
let(:description) { "a thing" }
|
96
|
+
let(:items) { [:a, :b, :c] }
|
97
|
+
let(:select_options) { {} }
|
98
|
+
|
99
|
+
subject(:select) { user_io.select(description, items, **select_options) }
|
100
|
+
|
101
|
+
context "with default" do
|
102
|
+
let(:select_options) { {default: :b} }
|
103
|
+
|
104
|
+
it "presents the default's index as default answer" do
|
105
|
+
expect($stdout).to receive(:write).with("Your choice [2] > ")
|
106
|
+
select
|
107
|
+
end
|
108
|
+
|
109
|
+
it "returns the default on empty input" do
|
110
|
+
expect($stdin).to receive(:gets).and_return("\n")
|
111
|
+
expect(select).to eq(:b)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/user_io.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative 'lib/infopark/user_io/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'infopark-user_io'
|
5
|
+
s.version = Infopark::UserIO::VERSION
|
6
|
+
s.summary = 'A utility lib to interact with the user on the command line.'
|
7
|
+
s.description = s.summary
|
8
|
+
s.authors = ['Tilo Prütz']
|
9
|
+
s.email = 'tilo@infopark.de'
|
10
|
+
s.files = `git ls-files -z`.split("\0")
|
11
|
+
s.license = 'UNLICENSED'
|
12
|
+
|
13
|
+
s.add_development_dependency "bundler"
|
14
|
+
s.add_development_dependency "rake"
|
15
|
+
s.add_development_dependency "rspec"
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: infopark-user_io
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.6
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tilo Prütz
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-06-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: A utility lib to interact with the user on the command line.
|
56
|
+
email: tilo@infopark.de
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- ".gitignore"
|
62
|
+
- ".rspec"
|
63
|
+
- Gemfile
|
64
|
+
- Rakefile
|
65
|
+
- lib/infopark-user_io.rb
|
66
|
+
- lib/infopark/user_io.rb
|
67
|
+
- lib/infopark/user_io/version.rb
|
68
|
+
- spec/spec_helper.rb
|
69
|
+
- spec/user_io_spec.rb
|
70
|
+
- user_io.gemspec
|
71
|
+
homepage:
|
72
|
+
licenses:
|
73
|
+
- UNLICENSED
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 2.4.5.1
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: A utility lib to interact with the user on the command line.
|
95
|
+
test_files: []
|