wibrfake-brk 0.0.2
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/.dockerignore +48 -0
- data/.gitattributes +19 -0
- data/.github/dependabot.yml +12 -0
- data/.github/workflows/ci.yml +90 -0
- data/.rubocop.yml +8 -0
- data/.ruby-version +1 -0
- data/Dockerfile +69 -0
- data/Gemfile +68 -0
- data/README.md +188 -0
- data/Rakefile +6 -0
- data/app/assets/config/manifest.js +5 -0
- data/app/assets/images/.keep +0 -0
- data/app/assets/images/2024-12-30-07:58:34-screenshot.png +0 -0
- data/app/assets/images/facebook/facebook-logo.svg +3 -0
- data/app/assets/images/google/googlelogo_color_272x92dp.png +0 -0
- data/app/assets/images/instagram/app-store.png +0 -0
- data/app/assets/images/instagram/google-play.png +0 -0
- data/app/assets/images/instagram/instagram-logo.png +0 -0
- data/app/assets/images/instagram/screenshot1.png +0 -0
- data/app/assets/images/instagram/screenshot2.png +0 -0
- data/app/assets/images/instagram/screenshot3.png +0 -0
- data/app/assets/images/instagram/screenshot4.png +0 -0
- data/app/assets/images/nauta_etecsa/CUBADEBATE.jpg +0 -0
- data/app/assets/images/nauta_etecsa/CUBAEDUCA.jpg +0 -0
- data/app/assets/images/nauta_etecsa/CUBARTE.jpg +0 -0
- data/app/assets/images/nauta_etecsa/CUBASI.jpg +0 -0
- data/app/assets/images/nauta_etecsa/ECURED.jpg +0 -0
- data/app/assets/images/nauta_etecsa/OFERTAS.jpg +0 -0
- data/app/assets/images/nauta_etecsa/PAPELETA.jpg +0 -0
- data/app/assets/images/nauta_etecsa/contenedor_bg.jpg +0 -0
- data/app/assets/images/nauta_etecsa/etecsa.jpg +0 -0
- data/app/assets/images/nauta_etecsa/nauta_wifi.jpg +0 -0
- data/app/assets/images/nauta_hogar/CUBADEBATE.jpg +0 -0
- data/app/assets/images/nauta_hogar/CUBAEDUCA.jpg +0 -0
- data/app/assets/images/nauta_hogar/CUBARTE.jpg +0 -0
- data/app/assets/images/nauta_hogar/CUBASI.jpg +0 -0
- data/app/assets/images/nauta_hogar/ECURED.jpg +0 -0
- data/app/assets/images/nauta_hogar/OFERTAS.jpg +0 -0
- data/app/assets/images/nauta_hogar/PAPELETA.jpg +0 -0
- data/app/assets/images/nauta_hogar/banner_hogar.png +0 -0
- data/app/assets/images/wibrfake/Breaker-Wallpaper1.jpg +0 -0
- data/app/assets/images/wibrfake/mac-list-parameter.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-100x100.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-32x32.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-48x48.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-apfake-on.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-apfake-set-basic.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-apfake-show.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-help.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-hostpot.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-init.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-init2.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-login-basic.jpg +0 -0
- data/app/assets/images/wibrfake/wibrfake-mac-list.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-mac-ramset-oui.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-mac-ramset.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-mac-set.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-mac-show.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-need-signin.jpg +0 -0
- data/app/assets/images/wibrfake/wibrfake-session-active.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-session-init.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-session-list.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-session-new.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-session-rename.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-session-save.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-web-server-apfake.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-web-server-on.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-web-server-show.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-wkdump-loaded.png +0 -0
- data/app/assets/images/wibrfake/wibrfake-wkdump.png +0 -0
- data/app/assets/images/wibrfake/wibrfake.png +0 -0
- data/app/assets/stylesheets/application.css +15 -0
- data/app/assets/stylesheets/basic/page_basic.css +0 -0
- data/app/assets/stylesheets/facebook/page_facebook.css +93 -0
- data/app/assets/stylesheets/google/page_google.css +137 -0
- data/app/assets/stylesheets/instagram/page_instagram.css +94 -0
- data/app/assets/stylesheets/nauta_etecsa/page_nauta.css +325 -0
- data/app/assets/stylesheets/nauta_hogar/page_nauta.css +469 -0
- data/app/assets/stylesheets/wibrfake/page_wibrfake.css +1181 -0
- data/app/assets/stylesheets/wibrfake/page_wibrfake_about.css +486 -0
- data/app/channels/application_cable/channel.rb +4 -0
- data/app/channels/application_cable/connection.rb +4 -0
- data/app/controllers/application_controller.rb +15 -0
- data/app/controllers/concerns/.keep +0 -0
- data/app/controllers/sessions_controller.rb +111 -0
- data/app/helpers/application_helper.rb +2 -0
- data/app/helpers/sessions_helper.rb +2 -0
- data/app/javascript/application.js +3 -0
- data/app/javascript/bootstrap.bundle.min.js +7 -0
- data/app/javascript/controllers/application.js +9 -0
- data/app/javascript/controllers/hello_controller.js +7 -0
- data/app/javascript/controllers/index.js +4 -0
- data/app/javascript/google/page_google.js +50 -0
- data/app/javascript/incorrect3.js +29 -0
- data/app/javascript/page_wibrfake.js +50 -0
- data/app/javascript/page_wibrfake_about.js +147 -0
- data/app/jobs/application_job.rb +7 -0
- data/app/mailers/application_mailer.rb +4 -0
- data/app/models/application_record.rb +3 -0
- data/app/models/concerns/.keep +0 -0
- data/app/models/user.rb +6 -0
- data/app/views/layouts/application.html.erb +33 -0
- data/app/views/layouts/mailer.html.erb +13 -0
- data/app/views/layouts/mailer.text.erb +1 -0
- data/app/views/pwa/manifest.json.erb +22 -0
- data/app/views/pwa/service-worker.js +26 -0
- data/app/views/sessions/basic/basic.html.erb +21 -0
- data/app/views/sessions/facebook/facebook.html.erb +132 -0
- data/app/views/sessions/google/google.html.erb +72 -0
- data/app/views/sessions/index.html.erb +11 -0
- data/app/views/sessions/instagram/instagram.html.erb +69 -0
- data/app/views/sessions/nauta_etecsa/nauta_etecsa.html.erb +97 -0
- data/app/views/sessions/nauta_hogar/nauta_hogar.html.erb +135 -0
- data/app/views/sessions/wibrfake/about/about.html.erb +164 -0
- data/app/views/sessions/wibrfake/wibrfake.html.erb +417 -0
- data/bin/.names.rb.swp +0 -0
- data/bin/brakeman +7 -0
- data/bin/bundle +114 -0
- data/bin/docker-entrypoint +13 -0
- data/bin/google.wkdump +3 -0
- data/bin/importmap +4 -0
- data/bin/rails +4 -0
- data/bin/rake +4 -0
- data/bin/rubocop +8 -0
- data/bin/setup +37 -0
- data/bin/wibrfake +20 -0
- data/config/application.rb +26 -0
- data/config/boot.rb +4 -0
- data/config/cable.yml +10 -0
- data/config/credentials.yml.enc +1 -0
- data/config/database.yml +16 -0
- data/config/environment.rb +5 -0
- data/config/environments/development.rb +85 -0
- data/config/environments/production.rb +105 -0
- data/config/environments/test.rb +67 -0
- data/config/icons/application-x-wkdump.xml +13 -0
- data/config/icons/hicolor/16x16/mimetypes/application-x-wkdump.png +0 -0
- data/config/icons/hicolor/32x32/mimetypes/application-x-wkdump.png +0 -0
- data/config/icons/hicolor/48x48/mimetypes/application-x-wkdump.png +0 -0
- data/config/icons/hicolor/icon-theme.cache +0 -0
- data/config/icons/hicolor/index.theme +20 -0
- data/config/icons/wkdump.desktop +5 -0
- data/config/importmap.rb +7 -0
- data/config/initializers/assets.rb +12 -0
- data/config/initializers/content_security_policy.rb +25 -0
- data/config/initializers/devise.rb +313 -0
- data/config/initializers/filter_parameter_logging.rb +8 -0
- data/config/initializers/inflections.rb +16 -0
- data/config/initializers/permissions_policy.rb +13 -0
- data/config/locales/devise.en.yml +65 -0
- data/config/locales/en.yml +31 -0
- data/config/puma.rb +35 -0
- data/config/routes.rb +12 -0
- data/config/storage.yml +34 -0
- data/config.ru +6 -0
- data/db/development.sqlite3 +0 -0
- data/db/schema.rb +14 -0
- data/ext/wibrfake/extconf.rb +3 -0
- data/ext/wibrfake/wibrfake.h +0 -0
- data/images/ouilist.png +0 -0
- data/images/wibrfakehelp.png +0 -0
- data/images/wibrfakeinit.png +0 -0
- data/lib/assets/.keep +0 -0
- data/lib/tasks/.keep +0 -0
- data/lib/wibrfake/Config/ipaddr.rb +71 -0
- data/lib/wibrfake/Config/options.rb +98 -0
- data/lib/wibrfake/Config/security_wpa.rb +43 -0
- data/lib/wibrfake/DataBase/howuis.db +0 -0
- data/lib/wibrfake/DataBase/ouis.db +0 -0
- data/lib/wibrfake/DataBase/ouis.rb +24 -0
- data/lib/wibrfake/DataBase/rename.rb +15 -0
- data/lib/wibrfake/DataBase/show.rb +18 -0
- data/lib/wibrfake/Dnsmasq/dhcp.rb +149 -0
- data/lib/wibrfake/Dnsmasq/dhcp_prueba.rb +135 -0
- data/lib/wibrfake/Dnsmasq/dns.rb +34 -0
- data/lib/wibrfake/Dnsmasq/dns_prueba.rb +36 -0
- data/lib/wibrfake/Dnsmasq/dnsmasq.rb +33 -0
- data/lib/wibrfake/Listing/apfake.rb +28 -0
- data/lib/wibrfake/Listing/clients.rb +76 -0
- data/lib/wibrfake/Listing/ouis.rb +53 -0
- data/lib/wibrfake/Listing/process.rb +14 -0
- data/lib/wibrfake/Listing/sessions.rb +47 -0
- data/lib/wibrfake/Listing/web_server.rb +17 -0
- data/lib/wibrfake/Listing/wireless.rb +30 -0
- data/lib/wibrfake/Logs/credentials.log +59 -0
- data/lib/wibrfake/NetworkInterface/ip-routing.rb +99 -0
- data/lib/wibrfake/NetworkInterface/mac.rb +114 -0
- data/lib/wibrfake/NetworkInterface/monitor.rb +44 -0
- data/lib/wibrfake/NetworkInterface/ouis.db +0 -0
- data/lib/wibrfake/Process/id.rb +11 -0
- data/lib/wibrfake/Process/pids.log +5 -0
- data/lib/wibrfake/Process/pkill.rb +124 -0
- data/lib/wibrfake/Process/processes.rb +76 -0
- data/lib/wibrfake/Rails/login.rb +20 -0
- data/lib/wibrfake/Rails/routes.rb +76 -0
- data/lib/wibrfake/Rails/service.rb +58 -0
- data/lib/wibrfake/Rails/service_usage.rb +46 -0
- data/lib/wibrfake/Sessions/session.rb +188 -0
- data/lib/wibrfake/String/string.rb +109 -0
- data/lib/wibrfake/Tmp/config.yml +1 -0
- data/lib/wibrfake/config.rb +24 -0
- data/lib/wibrfake/help.rb +107 -0
- data/lib/wibrfake/optionpr.rb +68 -0
- data/lib/wibrfake/run.rb +9 -0
- data/lib/wibrfake/ty/apld.rb +110 -0
- data/lib/wibrfake/ty/prompt/answers_collector.rb +78 -0
- data/lib/wibrfake/ty/prompt/block_paginator.rb +59 -0
- data/lib/wibrfake/ty/prompt/choice.rb +147 -0
- data/lib/wibrfake/ty/prompt/choices.rb +129 -0
- data/lib/wibrfake/ty/prompt/confirm_question.rb +158 -0
- data/lib/wibrfake/ty/prompt/const.rb +17 -0
- data/lib/wibrfake/ty/prompt/converter_dsl.rb +21 -0
- data/lib/wibrfake/ty/prompt/converter_registry.rb +69 -0
- data/lib/wibrfake/ty/prompt/converters.rb +182 -0
- data/lib/wibrfake/ty/prompt/distance.rb +49 -0
- data/lib/wibrfake/ty/prompt/enum_list.rb +433 -0
- data/lib/wibrfake/ty/prompt/errors.rb +31 -0
- data/lib/wibrfake/ty/prompt/evaluator.rb +29 -0
- data/lib/wibrfake/ty/prompt/expander.rb +321 -0
- data/lib/wibrfake/ty/prompt/keypress.rb +98 -0
- data/lib/wibrfake/ty/prompt/list.rb +589 -0
- data/lib/wibrfake/ty/prompt/mask_question.rb +96 -0
- data/lib/wibrfake/ty/prompt/multi_list.rb +224 -0
- data/lib/wibrfake/ty/prompt/multiline.rb +72 -0
- data/lib/wibrfake/ty/prompt/paginator.rb +111 -0
- data/lib/wibrfake/ty/prompt/question/checks.rb +105 -0
- data/lib/wibrfake/ty/prompt/question/modifier.rb +96 -0
- data/lib/wibrfake/ty/prompt/question/validation.rb +72 -0
- data/lib/wibrfake/ty/prompt/question.rb +391 -0
- data/lib/wibrfake/ty/prompt/result.rb +42 -0
- data/lib/wibrfake/ty/prompt/selected_choices.rb +77 -0
- data/lib/wibrfake/ty/prompt/slider.rb +286 -0
- data/lib/wibrfake/ty/prompt/statement.rb +55 -0
- data/lib/wibrfake/ty/prompt/suggestion.rb +113 -0
- data/lib/wibrfake/ty/prompt/symbols.rb +89 -0
- data/lib/wibrfake/ty/prompt/test.rb +36 -0
- data/lib/wibrfake/ty/prompt/timer.rb +75 -0
- data/lib/wibrfake/ty/prompt/utils.rb +42 -0
- data/lib/wibrfake/ty/prompt/version.rb +7 -0
- data/lib/wibrfake/ty/prompt.rb +589 -0
- data/lib/wibrfake/version.rb +5 -0
- data/lib/wibrfake/wibrfake_cli.rb +683 -0
- data/log/.keep +0 -0
- data/pruebas/daemon1.pid +1 -0
- data/pruebas/daemon2.pid +1 -0
- data/pruebas/daemons.rb +44 -0
- data/public/404.html +67 -0
- data/public/406-unsupported-browser.html +66 -0
- data/public/422.html +67 -0
- data/public/500.html +66 -0
- data/public/icon.png +0 -0
- data/public/icon.svg +3 -0
- data/public/robots.txt +1 -0
- data/storage/.keep +0 -0
- data/test/application_system_test_case.rb +5 -0
- data/test/channels/application_cable/connection_test.rb +13 -0
- data/test/controllers/.keep +0 -0
- data/test/controllers/sessions_controller_test.rb +7 -0
- data/test/fixtures/files/.keep +0 -0
- data/test/fixtures/users.yml +11 -0
- data/test/helpers/.keep +0 -0
- data/test/integration/.keep +0 -0
- data/test/mailers/.keep +0 -0
- data/test/models/.keep +0 -0
- data/test/models/user_test.rb +7 -0
- data/test/system/.keep +0 -0
- data/test/test_helper.rb +15 -0
- data/tmp/.keep +0 -0
- data/tmp/pids/.keep +0 -0
- data/tmp/storage/.keep +0 -0
- data/vendor/.keep +0 -0
- data/vendor/javascript/.keep +0 -0
- data/wibrfake.gemspec +49 -0
- metadata +743 -0
@@ -0,0 +1,182 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "const"
|
4
|
+
require_relative "converter_dsl"
|
5
|
+
|
6
|
+
module TTY
|
7
|
+
class Prompt
|
8
|
+
module Converters
|
9
|
+
extend ConverterDSL
|
10
|
+
|
11
|
+
TRUE_VALUES = /^(t(rue)?|y(es)?|on|1)$/i.freeze
|
12
|
+
FALSE_VALUES = /^(f(alse)?|n(o)?|off|0)$/i.freeze
|
13
|
+
|
14
|
+
SINGLE_DIGIT_MATCHER = /^(?<digit>\-?\d+(\.\d+)?)$/.freeze
|
15
|
+
DIGIT_MATCHER = /^(?<open>-?\d+(\.\d+)?)
|
16
|
+
\s*(?<sep>(\.\s*){2,3}|-|,)\s*
|
17
|
+
(?<close>-?\d+(\.\d+)?)$
|
18
|
+
/x.freeze
|
19
|
+
LETTER_MATCHER = /^(?<open>\w)
|
20
|
+
\s*(?<sep>(\.\s*){2,3}|-|,)\s*
|
21
|
+
(?<close>\w)$
|
22
|
+
/x.freeze
|
23
|
+
|
24
|
+
converter(:boolean, :bool) do |input|
|
25
|
+
case input.to_s
|
26
|
+
when TRUE_VALUES then true
|
27
|
+
when FALSE_VALUES then false
|
28
|
+
else Const::Undefined
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
converter(:string, :str) do |input|
|
33
|
+
String(input).chomp
|
34
|
+
end
|
35
|
+
|
36
|
+
converter(:symbol, :sym) do |input|
|
37
|
+
input.to_sym
|
38
|
+
end
|
39
|
+
|
40
|
+
converter(:char) do |input|
|
41
|
+
String(input).chars.to_a[0]
|
42
|
+
end
|
43
|
+
|
44
|
+
converter(:date) do |input|
|
45
|
+
begin
|
46
|
+
require "date" unless defined?(::Date)
|
47
|
+
::Date.parse(input)
|
48
|
+
rescue ArgumentError
|
49
|
+
Const::Undefined
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
converter(:datetime) do |input|
|
54
|
+
begin
|
55
|
+
require "date" unless defined?(::Date)
|
56
|
+
::DateTime.parse(input.to_s)
|
57
|
+
rescue ArgumentError
|
58
|
+
Const::Undefined
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
converter(:time) do |input|
|
63
|
+
begin
|
64
|
+
require "time"
|
65
|
+
::Time.parse(input.to_s)
|
66
|
+
rescue ArgumentError
|
67
|
+
Const::Undefined
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
converter(:integer, :int) do |input|
|
72
|
+
begin
|
73
|
+
Integer(input)
|
74
|
+
rescue ArgumentError
|
75
|
+
Const::Undefined
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
converter(:float) do |input|
|
80
|
+
begin
|
81
|
+
Float(input)
|
82
|
+
rescue TypeError, ArgumentError
|
83
|
+
Const::Undefined
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Convert string number to integer or float
|
88
|
+
#
|
89
|
+
# @return [Integer,Float,Const::Undefined]
|
90
|
+
#
|
91
|
+
# @api private
|
92
|
+
def cast_to_num(num)
|
93
|
+
([convert(:int, num), convert(:float, num)] - [Const::Undefined]).first ||
|
94
|
+
Const::Undefined
|
95
|
+
end
|
96
|
+
module_function :cast_to_num
|
97
|
+
|
98
|
+
converter(:range) do |input|
|
99
|
+
if input.is_a?(::Range)
|
100
|
+
input
|
101
|
+
elsif match = input.to_s.match(SINGLE_DIGIT_MATCHER)
|
102
|
+
digit = cast_to_num(match[:digit])
|
103
|
+
::Range.new(digit, digit)
|
104
|
+
elsif match = input.to_s.match(DIGIT_MATCHER)
|
105
|
+
open = cast_to_num(match[:open])
|
106
|
+
close = cast_to_num(match[:close])
|
107
|
+
::Range.new(open, close, match[:sep].gsub(/\s*/, "") == "...")
|
108
|
+
elsif match = input.to_s.match(LETTER_MATCHER)
|
109
|
+
::Range.new(match[:open], match[:close],
|
110
|
+
match[:sep].gsub(/\s*/, "") == "...")
|
111
|
+
else Const::Undefined
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
converter(:regexp) do |input|
|
116
|
+
Regexp.new(input)
|
117
|
+
end
|
118
|
+
|
119
|
+
converter(:filepath, :file) do |input|
|
120
|
+
::File.expand_path(input)
|
121
|
+
end
|
122
|
+
|
123
|
+
converter(:pathname, :path) do |input|
|
124
|
+
require "pathname" unless defined?(::Pathname)
|
125
|
+
::Pathname.new(input)
|
126
|
+
end
|
127
|
+
|
128
|
+
converter(:uri) do |input|
|
129
|
+
require "uri" unless defined?(::URI)
|
130
|
+
::URI.parse(input)
|
131
|
+
end
|
132
|
+
|
133
|
+
converter(:list, :array) do |val|
|
134
|
+
(val.respond_to?(:to_a) ? val : val.split(/(?<!\\),/))
|
135
|
+
.map { |v| v.strip.gsub(/\\,/, ",") }
|
136
|
+
.reject(&:empty?)
|
137
|
+
end
|
138
|
+
|
139
|
+
converter(:hash, :map) do |val|
|
140
|
+
values = val.respond_to?(:to_a) ? val : val.split(/[& ]/)
|
141
|
+
values.each_with_object({}) do |pair, pairs|
|
142
|
+
key, value = pair.split(/[=:]/, 2)
|
143
|
+
if (current = pairs[key.to_sym])
|
144
|
+
pairs[key.to_sym] = Array(current) << value
|
145
|
+
else
|
146
|
+
pairs[key.to_sym] = value
|
147
|
+
end
|
148
|
+
pairs
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
converter_registry.keys.each do |type|
|
153
|
+
next if type =~ /list|array|map|hash/
|
154
|
+
|
155
|
+
[:"#{type}_list", :"#{type}_array", :"#{type}s"].each do |new_type|
|
156
|
+
converter(new_type) do |val|
|
157
|
+
converter_registry[:array].(val).map do |obj|
|
158
|
+
converter_registry[type].(obj)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
[:"#{type}_map", :"#{type}_hash"].each do |new_type|
|
164
|
+
converter(new_type) do |val|
|
165
|
+
converter_registry[:hash].(val).each_with_object({}) do |(k, v), h|
|
166
|
+
h[k] = converter_registry[type].(v)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
[:"string_#{type}_map", :"str_#{type}_map",
|
172
|
+
:"string_#{type}_hash", :"str_#{type}_hash"].each do |new_type|
|
173
|
+
converter(new_type) do |val|
|
174
|
+
converter_registry[:hash].(val).each_with_object({}) do |(k, v), h|
|
175
|
+
h[converter_registry[:string].(k)] = converter_registry[type].(v)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end # Converters
|
181
|
+
end # Prompt
|
182
|
+
end # TTY
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
# A class responsible for string comparison
|
6
|
+
class Distance
|
7
|
+
# Calculate the optimal string alignment distance
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
def distance(first, second)
|
11
|
+
distances = []
|
12
|
+
rows = first.to_s.length
|
13
|
+
cols = second.to_s.length
|
14
|
+
|
15
|
+
0.upto(rows) do |index|
|
16
|
+
distances << [index] + [0] * cols
|
17
|
+
end
|
18
|
+
distances[0] = 0.upto(cols).to_a
|
19
|
+
|
20
|
+
1.upto(rows) do |first_index|
|
21
|
+
1.upto(cols) do |second_index|
|
22
|
+
first_char = first[first_index - 1]
|
23
|
+
second_char = second[second_index - 1]
|
24
|
+
cost = first_char == second_char ? 0 : 1
|
25
|
+
|
26
|
+
distances[first_index][second_index] = [
|
27
|
+
distances[first_index - 1][second_index], # deletion
|
28
|
+
distances[first_index][second_index - 1], # insertion
|
29
|
+
distances[first_index - 1][second_index - 1] # substitution
|
30
|
+
].min + cost
|
31
|
+
|
32
|
+
if first_index > 1 && second_index > 1
|
33
|
+
first_previous_char = first[first_index - 2]
|
34
|
+
second_previous_char = second[second_index - 2]
|
35
|
+
if first_char == second_previous_char && second_char == first_previous_char
|
36
|
+
distances[first_index][second_index] = [
|
37
|
+
distances[first_index][second_index],
|
38
|
+
distances[first_index - 2][second_index - 2] + 1 # transposition
|
39
|
+
].min
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
distances[rows][cols]
|
46
|
+
end
|
47
|
+
end # Distance
|
48
|
+
end # Prompt
|
49
|
+
end # TTY
|
@@ -0,0 +1,433 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "English"
|
4
|
+
|
5
|
+
require_relative "choices"
|
6
|
+
require_relative "block_paginator"
|
7
|
+
require_relative "paginator"
|
8
|
+
|
9
|
+
module TTY
|
10
|
+
class Prompt
|
11
|
+
# A class reponsible for rendering enumerated list menu.
|
12
|
+
# Used by {Prompt} to display static choice menu.
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
class EnumList
|
16
|
+
PAGE_HELP = "(Press tab/right or left to reveal more choices)"
|
17
|
+
|
18
|
+
# Checks type of default parameter to be integer
|
19
|
+
INTEGER_MATCHER = /\A[-+]?\d+\Z/.freeze
|
20
|
+
|
21
|
+
# Create instance of EnumList menu.
|
22
|
+
#
|
23
|
+
# @api public
|
24
|
+
def initialize(prompt, **options)
|
25
|
+
@prompt = prompt
|
26
|
+
@prefix = options.fetch(:prefix) { @prompt.prefix }
|
27
|
+
@enum = options.fetch(:enum) { ")" }
|
28
|
+
@default = options.fetch(:default, nil)
|
29
|
+
@active_color = options.fetch(:active_color) { @prompt.active_color }
|
30
|
+
@help_color = options.fetch(:help_color) { @prompt.help_color }
|
31
|
+
@error_color = options.fetch(:error_color) { @prompt.error_color }
|
32
|
+
@cycle = options.fetch(:cycle, false)
|
33
|
+
@quiet = options.fetch(:quiet) { @prompt.quiet }
|
34
|
+
@symbols = @prompt.symbols.merge(options.fetch(:symbols, {}))
|
35
|
+
@input = nil
|
36
|
+
@done = false
|
37
|
+
@first_render = true
|
38
|
+
@failure = false
|
39
|
+
@active = @default
|
40
|
+
@choices = Choices.new
|
41
|
+
@per_page = options[:per_page]
|
42
|
+
@page_help = options[:page_help] || PAGE_HELP
|
43
|
+
@paginator = BlockPaginator.new
|
44
|
+
@page_active = @default
|
45
|
+
end
|
46
|
+
|
47
|
+
# Change symbols used by this prompt
|
48
|
+
#
|
49
|
+
# @param [Hash] new_symbols
|
50
|
+
# the new symbols to use
|
51
|
+
#
|
52
|
+
# @api public
|
53
|
+
def symbols(new_symbols = (not_set = true))
|
54
|
+
return @symbols if not_set
|
55
|
+
|
56
|
+
@symbols.merge!(new_symbols)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Set default option selected
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def default(default)
|
63
|
+
@default = default
|
64
|
+
end
|
65
|
+
|
66
|
+
# Check if default value is set
|
67
|
+
#
|
68
|
+
# @return [Boolean]
|
69
|
+
#
|
70
|
+
# @api public
|
71
|
+
def default?
|
72
|
+
!@default.to_s.empty?
|
73
|
+
end
|
74
|
+
|
75
|
+
# Set number of items per page
|
76
|
+
#
|
77
|
+
# @api public
|
78
|
+
def per_page(value)
|
79
|
+
@per_page = value
|
80
|
+
end
|
81
|
+
|
82
|
+
def page_size
|
83
|
+
(@per_page || Paginator::DEFAULT_PAGE_SIZE)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Check if list is paginated
|
87
|
+
#
|
88
|
+
# @return [Boolean]
|
89
|
+
#
|
90
|
+
# @api private
|
91
|
+
def paginated?
|
92
|
+
@choices.size > page_size
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param [String] text
|
96
|
+
# the help text to display per page
|
97
|
+
# @api pbulic
|
98
|
+
def page_help(text)
|
99
|
+
@page_help = text
|
100
|
+
end
|
101
|
+
|
102
|
+
# Set selecting active index using number pad
|
103
|
+
#
|
104
|
+
# @api public
|
105
|
+
def enum(value)
|
106
|
+
@enum = value
|
107
|
+
end
|
108
|
+
|
109
|
+
# Set quiet mode
|
110
|
+
#
|
111
|
+
# @api public
|
112
|
+
def quiet(value)
|
113
|
+
@quiet = value
|
114
|
+
end
|
115
|
+
|
116
|
+
# Add a single choice
|
117
|
+
#
|
118
|
+
# @api public
|
119
|
+
def choice(*value, &block)
|
120
|
+
if block
|
121
|
+
@choices << (value << block)
|
122
|
+
else
|
123
|
+
@choices << value
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Add multiple choices
|
128
|
+
#
|
129
|
+
# @param [Array[Object]] values
|
130
|
+
# the values to add as choices
|
131
|
+
#
|
132
|
+
# @api public
|
133
|
+
def choices(values = (not_set = true))
|
134
|
+
if not_set
|
135
|
+
@choices
|
136
|
+
else
|
137
|
+
values.each { |val| @choices << val }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Call the list menu by passing question and choices
|
142
|
+
#
|
143
|
+
# @param [String] question
|
144
|
+
#
|
145
|
+
# @param
|
146
|
+
# @api public
|
147
|
+
def call(question, possibilities, &block)
|
148
|
+
choices(possibilities)
|
149
|
+
@question = question
|
150
|
+
block[self] if block
|
151
|
+
setup_defaults
|
152
|
+
@prompt.subscribe(self) do
|
153
|
+
render
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def keypress(event)
|
158
|
+
if %i[backspace delete].include?(event.key.name)
|
159
|
+
return if @input.empty?
|
160
|
+
|
161
|
+
@input.chop!
|
162
|
+
mark_choice_as_active
|
163
|
+
elsif event.value =~ /^\d+$/
|
164
|
+
@input += event.value
|
165
|
+
mark_choice_as_active
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def keyreturn(*)
|
170
|
+
@failure = false
|
171
|
+
num = @input.to_i
|
172
|
+
choice_disabled = choices[num - 1] && choices[num - 1].disabled?
|
173
|
+
choice_in_range = num > 0 && num <= @choices.size
|
174
|
+
|
175
|
+
if choice_in_range && !choice_disabled || @input.empty?
|
176
|
+
@done = true
|
177
|
+
else
|
178
|
+
@input = ""
|
179
|
+
@failure = true
|
180
|
+
end
|
181
|
+
end
|
182
|
+
alias keyenter keyreturn
|
183
|
+
|
184
|
+
def keyright(*)
|
185
|
+
if (@page_active + page_size) <= @choices.size
|
186
|
+
@page_active += page_size
|
187
|
+
elsif @cycle
|
188
|
+
@page_active = 1
|
189
|
+
end
|
190
|
+
end
|
191
|
+
alias keytab keyright
|
192
|
+
|
193
|
+
def keyleft(*)
|
194
|
+
if (@page_active - page_size) >= 0
|
195
|
+
@page_active -= page_size
|
196
|
+
elsif @cycle
|
197
|
+
@page_active = @choices.size - 1
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
# Find active choice or set to default
|
204
|
+
#
|
205
|
+
# @return [nil]
|
206
|
+
#
|
207
|
+
# @api private
|
208
|
+
def mark_choice_as_active
|
209
|
+
next_active = @choices[@input.to_i - 1]
|
210
|
+
|
211
|
+
if next_active && next_active.disabled?
|
212
|
+
# noop
|
213
|
+
elsif (@input.to_i > 0) && next_active
|
214
|
+
@active = @input.to_i
|
215
|
+
else
|
216
|
+
@active = @default
|
217
|
+
end
|
218
|
+
@page_active = @active
|
219
|
+
end
|
220
|
+
|
221
|
+
# Validate default indexes to be within range
|
222
|
+
#
|
223
|
+
# @api private
|
224
|
+
def validate_defaults
|
225
|
+
msg = if @default.nil? || @default.to_s.empty?
|
226
|
+
"default index must be an integer in range (1 - #{choices.size})"
|
227
|
+
elsif @default.to_s !~ INTEGER_MATCHER
|
228
|
+
validate_default_name
|
229
|
+
elsif @default < 1 || @default > @choices.size
|
230
|
+
"default index #{@default} out of range (1 - #{@choices.size})"
|
231
|
+
elsif choices[@default - 1] && choices[@default - 1].disabled?
|
232
|
+
"default index #{@default} matches disabled choice item"
|
233
|
+
end
|
234
|
+
|
235
|
+
raise(ConfigurationError, msg) if msg
|
236
|
+
end
|
237
|
+
|
238
|
+
# Validate default choice name
|
239
|
+
#
|
240
|
+
# @return [String]
|
241
|
+
#
|
242
|
+
# @api private
|
243
|
+
def validate_default_name
|
244
|
+
default_choice = choices.find_by(:name, @default.to_s)
|
245
|
+
if default_choice.nil?
|
246
|
+
"no choice found for the default name: #{@default.inspect}"
|
247
|
+
elsif default_choice.disabled?
|
248
|
+
"default name #{@default.inspect} matches disabled choice"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Setup default option and active selection
|
253
|
+
#
|
254
|
+
# @api private
|
255
|
+
def setup_defaults
|
256
|
+
if @default.to_s.empty?
|
257
|
+
@default = (0..choices.length).find { |i| !choices[i].disabled? } + 1
|
258
|
+
end
|
259
|
+
validate_defaults
|
260
|
+
if default_choice = choices.find_by(:name, @default)
|
261
|
+
@default = choices.index(default_choice) + 1
|
262
|
+
end
|
263
|
+
mark_choice_as_active
|
264
|
+
end
|
265
|
+
|
266
|
+
# Render a selection list.
|
267
|
+
#
|
268
|
+
# By default the result is printed out.
|
269
|
+
#
|
270
|
+
# @return [Object] value
|
271
|
+
# return the selected value
|
272
|
+
#
|
273
|
+
# @api private
|
274
|
+
def render
|
275
|
+
@input = ""
|
276
|
+
until @done
|
277
|
+
question = render_question
|
278
|
+
@prompt.print(question)
|
279
|
+
@prompt.print(render_error) if @failure
|
280
|
+
if paginated? && !@done
|
281
|
+
@prompt.print(render_page_help)
|
282
|
+
end
|
283
|
+
@prompt.read_keypress
|
284
|
+
question_lines = question.split($INPUT_RECORD_SEPARATOR, -1)
|
285
|
+
@prompt.print(refresh(question_lines_count(question_lines)))
|
286
|
+
end
|
287
|
+
@prompt.print(render_question) unless @quiet
|
288
|
+
answer
|
289
|
+
end
|
290
|
+
|
291
|
+
# Count how many screen lines the question spans
|
292
|
+
#
|
293
|
+
# @return [Integer]
|
294
|
+
#
|
295
|
+
# @api private
|
296
|
+
def question_lines_count(question_lines)
|
297
|
+
question_lines.reduce(0) do |acc, line|
|
298
|
+
acc + @prompt.count_screen_lines(line)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# Find value for the choice selected
|
303
|
+
#
|
304
|
+
# @return [nil, Object]
|
305
|
+
#
|
306
|
+
# @api private
|
307
|
+
def answer
|
308
|
+
@choices[@active - 1].value
|
309
|
+
end
|
310
|
+
|
311
|
+
# Determine area of the screen to clear
|
312
|
+
#
|
313
|
+
# @param [Integer] lines
|
314
|
+
# the lines to clear
|
315
|
+
#
|
316
|
+
# @return [String]
|
317
|
+
#
|
318
|
+
# @api private
|
319
|
+
def refresh(lines)
|
320
|
+
@prompt.clear_lines(lines) +
|
321
|
+
@prompt.cursor.clear_screen_down
|
322
|
+
end
|
323
|
+
|
324
|
+
# Render question with the menu options
|
325
|
+
#
|
326
|
+
# @return [String]
|
327
|
+
#
|
328
|
+
# @api private
|
329
|
+
def render_question
|
330
|
+
header = ["#{@prefix}#{@question} #{render_header}\n"]
|
331
|
+
unless @done
|
332
|
+
header << render_menu
|
333
|
+
header << render_footer
|
334
|
+
end
|
335
|
+
header.join
|
336
|
+
end
|
337
|
+
|
338
|
+
# Error message when incorrect index chosen
|
339
|
+
#
|
340
|
+
# @api private
|
341
|
+
def error_message
|
342
|
+
error = "Please enter a valid number"
|
343
|
+
"\n" + @prompt.decorate(">>", @error_color) + " " + error
|
344
|
+
end
|
345
|
+
|
346
|
+
# Render error message and return cursor to position of input
|
347
|
+
#
|
348
|
+
# @return [String]
|
349
|
+
#
|
350
|
+
# @api private
|
351
|
+
def render_error
|
352
|
+
error = error_message.dup
|
353
|
+
if !paginated?
|
354
|
+
error << @prompt.cursor.prev_line
|
355
|
+
error << @prompt.cursor.forward(render_footer.size)
|
356
|
+
end
|
357
|
+
error
|
358
|
+
end
|
359
|
+
|
360
|
+
# Render chosen option
|
361
|
+
#
|
362
|
+
# @return [String]
|
363
|
+
#
|
364
|
+
# @api private
|
365
|
+
def render_header
|
366
|
+
return "" unless @done
|
367
|
+
return "" unless @active
|
368
|
+
|
369
|
+
selected_item = @choices[@active - 1].name.to_s
|
370
|
+
@prompt.decorate(selected_item, @active_color)
|
371
|
+
end
|
372
|
+
|
373
|
+
# Render footer for the indexed menu
|
374
|
+
#
|
375
|
+
# @return [String]
|
376
|
+
#
|
377
|
+
# @api private
|
378
|
+
def render_footer
|
379
|
+
" Choose 1-#{@choices.size} [#{@default}]: #{@input}"
|
380
|
+
end
|
381
|
+
|
382
|
+
# Pagination help message
|
383
|
+
#
|
384
|
+
# @return [String]
|
385
|
+
#
|
386
|
+
# @api private
|
387
|
+
def page_help_message
|
388
|
+
return "" unless paginated?
|
389
|
+
|
390
|
+
"\n" + @prompt.decorate(@page_help, @help_color)
|
391
|
+
end
|
392
|
+
|
393
|
+
# Render page help
|
394
|
+
#
|
395
|
+
# @return [String]
|
396
|
+
#
|
397
|
+
# @api private
|
398
|
+
def render_page_help
|
399
|
+
help = page_help_message.dup
|
400
|
+
if @failure
|
401
|
+
help << @prompt.cursor.prev_line
|
402
|
+
end
|
403
|
+
help << @prompt.cursor.prev_line
|
404
|
+
help << @prompt.cursor.forward(render_footer.size)
|
405
|
+
end
|
406
|
+
|
407
|
+
# Render menu with indexed choices to select from
|
408
|
+
#
|
409
|
+
# @return [String]
|
410
|
+
#
|
411
|
+
# @api private
|
412
|
+
def render_menu
|
413
|
+
output = []
|
414
|
+
|
415
|
+
@paginator.paginate(@choices, @page_active, @per_page) do |choice, index|
|
416
|
+
num = (index + 1).to_s + @enum + " "
|
417
|
+
selected = num.to_s + choice.name.to_s
|
418
|
+
output << if index + 1 == @active && !choice.disabled?
|
419
|
+
(" " * 2) + @prompt.decorate(selected, @active_color)
|
420
|
+
elsif choice.disabled?
|
421
|
+
@prompt.decorate(@symbols[:cross], :red) + " " +
|
422
|
+
selected + " " + choice.disabled.to_s
|
423
|
+
else
|
424
|
+
(" " * 2) + selected
|
425
|
+
end
|
426
|
+
output << "\n"
|
427
|
+
end
|
428
|
+
|
429
|
+
output.join
|
430
|
+
end
|
431
|
+
end # EnumList
|
432
|
+
end # Prompt
|
433
|
+
end # TTY
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
Error = Class.new(StandardError)
|
6
|
+
|
7
|
+
# Raised when wrong parameter is used to configure prompt
|
8
|
+
ConfigurationError = Class.new(Error)
|
9
|
+
|
10
|
+
# Raised when type conversion cannot be performed
|
11
|
+
ConversionError = Class.new(Error)
|
12
|
+
|
13
|
+
# Raised when the passed in validation argument is of wrong type
|
14
|
+
ValidationCoercion = Class.new(Error)
|
15
|
+
|
16
|
+
# Raised when the required argument is not supplied
|
17
|
+
ArgumentRequired = Class.new(Error)
|
18
|
+
|
19
|
+
# Raised when the argument validation fails
|
20
|
+
ArgumentValidation = Class.new(Error)
|
21
|
+
|
22
|
+
# Raised when the argument is not expected
|
23
|
+
InvalidArgument = Class.new(Error)
|
24
|
+
|
25
|
+
# Raised when overriding already defined conversion
|
26
|
+
ConversionAlreadyDefined = Class.new(Error)
|
27
|
+
|
28
|
+
# Raised when conversion type isn't registered
|
29
|
+
UnsupportedConversion = Class.new(Error)
|
30
|
+
end # Prompt
|
31
|
+
end # TTY
|