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,589 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "English"
|
4
|
+
|
5
|
+
require_relative "choices"
|
6
|
+
require_relative "paginator"
|
7
|
+
require_relative "block_paginator"
|
8
|
+
|
9
|
+
module TTY
|
10
|
+
class Prompt
|
11
|
+
# A class responsible for rendering select list menu
|
12
|
+
# Used by {Prompt} to display interactive menu.
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
class List
|
16
|
+
# Allowed keys for filter, along with backspace and canc.
|
17
|
+
FILTER_KEYS_MATCHER = /\A([[:alnum:]]|[[:punct:]])\Z/.freeze
|
18
|
+
|
19
|
+
# Checks type of default parameter to be integer
|
20
|
+
INTEGER_MATCHER = /\A\d+\Z/.freeze
|
21
|
+
|
22
|
+
# Create instance of TTY::Prompt::List menu.
|
23
|
+
#
|
24
|
+
# @param Hash options
|
25
|
+
# the configuration options
|
26
|
+
# @option options [Symbol] :default
|
27
|
+
# the default active choice, defaults to 1
|
28
|
+
# @option options [Symbol] :color
|
29
|
+
# the color for the selected item, defualts to :green
|
30
|
+
# @option options [Symbol] :marker
|
31
|
+
# the marker for the selected item
|
32
|
+
# @option options [String] :enum
|
33
|
+
# the delimiter for the item index
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
def initialize(prompt, **options)
|
37
|
+
check_options_consistency(options)
|
38
|
+
|
39
|
+
@prompt = prompt
|
40
|
+
@prefix = options.fetch(:prefix) { @prompt.prefix }
|
41
|
+
@enum = options.fetch(:enum) { nil }
|
42
|
+
@default = Array(options[:default])
|
43
|
+
@choices = Choices.new
|
44
|
+
@active_color = options.fetch(:active_color) { @prompt.active_color }
|
45
|
+
@help_color = options.fetch(:help_color) { @prompt.help_color }
|
46
|
+
@cycle = options.fetch(:cycle) { false }
|
47
|
+
@filterable = options.fetch(:filter) { false }
|
48
|
+
@symbols = @prompt.symbols.merge(options.fetch(:symbols, {}))
|
49
|
+
@quiet = options.fetch(:quiet) { @prompt.quiet }
|
50
|
+
@filter = []
|
51
|
+
@filter_cache = {}
|
52
|
+
@help = options[:help]
|
53
|
+
@show_help = options.fetch(:show_help) { :start }
|
54
|
+
@first_render = true
|
55
|
+
@done = false
|
56
|
+
@per_page = options[:per_page]
|
57
|
+
@paginator = Paginator.new
|
58
|
+
@block_paginator = BlockPaginator.new
|
59
|
+
@by_page = false
|
60
|
+
@paging_changed = false
|
61
|
+
end
|
62
|
+
|
63
|
+
# Change symbols used by this prompt
|
64
|
+
#
|
65
|
+
# @param [Hash] new_symbols
|
66
|
+
# the new symbols to use
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def symbols(new_symbols = (not_set = true))
|
70
|
+
return @symbols if not_set
|
71
|
+
|
72
|
+
@symbols.merge!(new_symbols)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Set default option selected
|
76
|
+
#
|
77
|
+
# @api public
|
78
|
+
def default(*default_values)
|
79
|
+
@default = default_values
|
80
|
+
end
|
81
|
+
|
82
|
+
# Select paginator based on the current navigation key
|
83
|
+
#
|
84
|
+
# @return [Paginator]
|
85
|
+
#
|
86
|
+
# @api private
|
87
|
+
def paginator
|
88
|
+
@by_page ? @block_paginator : @paginator
|
89
|
+
end
|
90
|
+
|
91
|
+
# Synchronize paginators start positions
|
92
|
+
#
|
93
|
+
# @api private
|
94
|
+
def sync_paginators
|
95
|
+
if @by_page
|
96
|
+
if @paginator.start_index
|
97
|
+
@block_paginator.reset!
|
98
|
+
@block_paginator.start_index = @paginator.start_index
|
99
|
+
end
|
100
|
+
else
|
101
|
+
if @block_paginator.start_index
|
102
|
+
@paginator.reset!
|
103
|
+
@paginator.start_index = @block_paginator.start_index
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Set number of items per page
|
109
|
+
#
|
110
|
+
# @api public
|
111
|
+
def per_page(value)
|
112
|
+
@per_page = value
|
113
|
+
end
|
114
|
+
|
115
|
+
def page_size
|
116
|
+
(@per_page || Paginator::DEFAULT_PAGE_SIZE)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Check if list is paginated
|
120
|
+
#
|
121
|
+
# @return [Boolean]
|
122
|
+
#
|
123
|
+
# @api private
|
124
|
+
def paginated?
|
125
|
+
choices.size > page_size
|
126
|
+
end
|
127
|
+
|
128
|
+
# Provide help information
|
129
|
+
#
|
130
|
+
# @param [String] value
|
131
|
+
# the new help text
|
132
|
+
#
|
133
|
+
# @return [String]
|
134
|
+
#
|
135
|
+
# @api public
|
136
|
+
def help(value = (not_set = true))
|
137
|
+
return @help if !@help.nil? && not_set
|
138
|
+
|
139
|
+
@help = (@help.nil? && !not_set) ? value : default_help
|
140
|
+
end
|
141
|
+
|
142
|
+
# Change when help is displayed
|
143
|
+
#
|
144
|
+
# @api public
|
145
|
+
def show_help(value = (not_set = true))
|
146
|
+
return @show_ehlp if not_set
|
147
|
+
|
148
|
+
@show_help = value
|
149
|
+
end
|
150
|
+
|
151
|
+
# Information about arrow keys
|
152
|
+
#
|
153
|
+
# @return [String]
|
154
|
+
#
|
155
|
+
# @api private
|
156
|
+
def arrows_help
|
157
|
+
up_down = @symbols[:arrow_up] + "/" + @symbols[:arrow_down]
|
158
|
+
left_right = @symbols[:arrow_left] + "/" + @symbols[:arrow_right]
|
159
|
+
|
160
|
+
arrows = [up_down]
|
161
|
+
arrows << "/" if paginated?
|
162
|
+
arrows << left_right if paginated?
|
163
|
+
arrows.join
|
164
|
+
end
|
165
|
+
|
166
|
+
# Default help text
|
167
|
+
#
|
168
|
+
# Note that enumeration and filter are mutually exclusive
|
169
|
+
#
|
170
|
+
# @a public
|
171
|
+
def default_help
|
172
|
+
str = []
|
173
|
+
str << "(Press "
|
174
|
+
str << "#{arrows_help} arrow"
|
175
|
+
str << " or 1-#{choices.size} number" if enumerate?
|
176
|
+
str << " to move"
|
177
|
+
str << (filterable? ? "," : " and")
|
178
|
+
str << " Enter to select"
|
179
|
+
str << " and letters to filter" if filterable?
|
180
|
+
str << ")"
|
181
|
+
str.join
|
182
|
+
end
|
183
|
+
|
184
|
+
# Set selecting active index using number pad
|
185
|
+
#
|
186
|
+
# @api public
|
187
|
+
def enum(value)
|
188
|
+
@enum = value
|
189
|
+
end
|
190
|
+
|
191
|
+
# Set whether selected answers are echoed
|
192
|
+
#
|
193
|
+
# @api public
|
194
|
+
def quiet(value)
|
195
|
+
@quiet = value
|
196
|
+
end
|
197
|
+
|
198
|
+
# Add a single choice
|
199
|
+
#
|
200
|
+
# @api public
|
201
|
+
def choice(*value, &block)
|
202
|
+
@filter_cache = {}
|
203
|
+
if block
|
204
|
+
@choices << (value << block)
|
205
|
+
else
|
206
|
+
@choices << value
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Add multiple choices, or return them.
|
211
|
+
#
|
212
|
+
# @param [Array[Object]] values
|
213
|
+
# the values to add as choices; if not passed, the current
|
214
|
+
# choices are displayed.
|
215
|
+
#
|
216
|
+
# @api public
|
217
|
+
def choices(values = (not_set = true))
|
218
|
+
if not_set
|
219
|
+
if !filterable? || @filter.empty?
|
220
|
+
@choices
|
221
|
+
else
|
222
|
+
filter_value = @filter.join.downcase
|
223
|
+
@filter_cache[filter_value] ||= @choices.enabled.select do |choice|
|
224
|
+
choice.name.to_s.downcase.include?(filter_value)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
else
|
228
|
+
@filter_cache = {}
|
229
|
+
values.each { |val| @choices << val }
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Call the list menu by passing question and choices
|
234
|
+
#
|
235
|
+
# @param [String] question
|
236
|
+
#
|
237
|
+
# @param
|
238
|
+
# @api public
|
239
|
+
def call(question, possibilities, &block)
|
240
|
+
choices(possibilities)
|
241
|
+
@question = question
|
242
|
+
block.call(self) if block
|
243
|
+
setup_defaults
|
244
|
+
@prompt.subscribe(self) do
|
245
|
+
render
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Check if list is enumerated
|
250
|
+
#
|
251
|
+
# @return [Boolean]
|
252
|
+
def enumerate?
|
253
|
+
!@enum.nil?
|
254
|
+
end
|
255
|
+
|
256
|
+
def keynum(event)
|
257
|
+
return unless enumerate?
|
258
|
+
|
259
|
+
value = event.value.to_i
|
260
|
+
return unless (1..choices.count).cover?(value)
|
261
|
+
return if choices[value - 1].disabled?
|
262
|
+
|
263
|
+
@active = value
|
264
|
+
end
|
265
|
+
|
266
|
+
def keyenter(*)
|
267
|
+
@done = true unless choices.empty?
|
268
|
+
end
|
269
|
+
alias keyreturn keyenter
|
270
|
+
alias keyspace keyenter
|
271
|
+
|
272
|
+
def search_choice_in(searchable)
|
273
|
+
searchable.find { |i| !choices[i - 1].disabled? }
|
274
|
+
end
|
275
|
+
|
276
|
+
def keyup(*)
|
277
|
+
searchable = (@active - 1).downto(1).to_a
|
278
|
+
prev_active = search_choice_in(searchable)
|
279
|
+
|
280
|
+
if prev_active
|
281
|
+
@active = prev_active
|
282
|
+
elsif @cycle
|
283
|
+
searchable = choices.length.downto(1).to_a
|
284
|
+
prev_active = search_choice_in(searchable)
|
285
|
+
|
286
|
+
@active = prev_active if prev_active
|
287
|
+
end
|
288
|
+
|
289
|
+
@paging_changed = @by_page
|
290
|
+
@by_page = false
|
291
|
+
end
|
292
|
+
|
293
|
+
def keydown(*)
|
294
|
+
searchable = ((@active + 1)..choices.length)
|
295
|
+
next_active = search_choice_in(searchable)
|
296
|
+
|
297
|
+
if next_active
|
298
|
+
@active = next_active
|
299
|
+
elsif @cycle
|
300
|
+
searchable = (1..choices.length)
|
301
|
+
next_active = search_choice_in(searchable)
|
302
|
+
|
303
|
+
@active = next_active if next_active
|
304
|
+
end
|
305
|
+
@paging_changed = @by_page
|
306
|
+
@by_page = false
|
307
|
+
end
|
308
|
+
alias keytab keydown
|
309
|
+
|
310
|
+
# Moves all choices page by page keeping the current selected item
|
311
|
+
# at the same level on each page.
|
312
|
+
#
|
313
|
+
# When the choice on a page is outside of next page range then
|
314
|
+
# adjust it to the last item, otherwise leave unchanged.
|
315
|
+
def keyright(*)
|
316
|
+
choices_size = choices.size
|
317
|
+
if (@active + page_size) <= choices_size
|
318
|
+
searchable = ((@active + page_size)..choices_size)
|
319
|
+
@active = search_choice_in(searchable)
|
320
|
+
elsif @active <= choices_size # last page shorter
|
321
|
+
current = @active % page_size
|
322
|
+
remaining = choices_size % page_size
|
323
|
+
|
324
|
+
if current.zero? || (remaining > 0 && current > remaining)
|
325
|
+
searchable = choices_size.downto(0).to_a
|
326
|
+
@active = search_choice_in(searchable)
|
327
|
+
elsif @cycle
|
328
|
+
searchable = ((current.zero? ? page_size : current)..choices_size)
|
329
|
+
@active = search_choice_in(searchable)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
@paging_changed = !@by_page
|
334
|
+
@by_page = true
|
335
|
+
end
|
336
|
+
alias keypage_down keyright
|
337
|
+
|
338
|
+
def keyleft(*)
|
339
|
+
if (@active - page_size) > 0
|
340
|
+
searchable = ((@active - page_size)..choices.size)
|
341
|
+
@active = search_choice_in(searchable)
|
342
|
+
elsif @cycle
|
343
|
+
searchable = choices.size.downto(1).to_a
|
344
|
+
@active = search_choice_in(searchable)
|
345
|
+
end
|
346
|
+
@paging_changed = !@by_page
|
347
|
+
@by_page = true
|
348
|
+
end
|
349
|
+
alias keypage_up keyleft
|
350
|
+
|
351
|
+
def keypress(event)
|
352
|
+
return unless filterable?
|
353
|
+
|
354
|
+
if event.value =~ FILTER_KEYS_MATCHER
|
355
|
+
@filter << event.value
|
356
|
+
@active = 1
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def keydelete(*)
|
361
|
+
return unless filterable?
|
362
|
+
|
363
|
+
@filter.clear
|
364
|
+
@active = 1
|
365
|
+
end
|
366
|
+
|
367
|
+
def keybackspace(*)
|
368
|
+
return unless filterable?
|
369
|
+
|
370
|
+
@filter.pop
|
371
|
+
@active = 1
|
372
|
+
end
|
373
|
+
|
374
|
+
private
|
375
|
+
|
376
|
+
def check_options_consistency(options)
|
377
|
+
if options.key?(:enum) && options.key?(:filter)
|
378
|
+
raise ConfigurationError,
|
379
|
+
"Enumeration can't be used with filter"
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Setup default option and active selection
|
384
|
+
#
|
385
|
+
# @return [Integer]
|
386
|
+
#
|
387
|
+
# @api private
|
388
|
+
def setup_defaults
|
389
|
+
validate_defaults
|
390
|
+
|
391
|
+
if @default.empty?
|
392
|
+
# no default, pick the first non-disabled choice
|
393
|
+
@active = choices.index { |choice| !choice.disabled? } + 1
|
394
|
+
elsif @default.first.to_s =~ INTEGER_MATCHER
|
395
|
+
@active = @default.first
|
396
|
+
elsif default_choice = choices.find_by(:name, @default.first)
|
397
|
+
@active = choices.index(default_choice) + 1
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# Validate default indexes to be within range
|
402
|
+
#
|
403
|
+
# @raise [ConfigurationError]
|
404
|
+
# raised when the default index is either non-integer,
|
405
|
+
# out of range or clashes with disabled choice item.
|
406
|
+
#
|
407
|
+
# @api private
|
408
|
+
def validate_defaults
|
409
|
+
@default.each do |d|
|
410
|
+
msg = if d.nil? || d.to_s.empty?
|
411
|
+
"default index must be an integer in range (1 - #{choices.size})"
|
412
|
+
elsif d.to_s !~ INTEGER_MATCHER
|
413
|
+
validate_default_name(d)
|
414
|
+
elsif d < 1 || d > choices.size
|
415
|
+
"default index `#{d}` out of range (1 - #{choices.size})"
|
416
|
+
elsif (dflt_choice = choices[d - 1]) && dflt_choice.disabled?
|
417
|
+
"default index `#{d}` matches disabled choice"
|
418
|
+
end
|
419
|
+
|
420
|
+
raise(ConfigurationError, msg) if msg
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
# Validate default choice name
|
425
|
+
#
|
426
|
+
# @param [String] name
|
427
|
+
# the name to verify
|
428
|
+
#
|
429
|
+
# @return [String]
|
430
|
+
#
|
431
|
+
# @api private
|
432
|
+
def validate_default_name(name)
|
433
|
+
default_choice = choices.find_by(:name, name.to_s)
|
434
|
+
if default_choice.nil?
|
435
|
+
"no choice found for the default name: #{name.inspect}"
|
436
|
+
elsif default_choice.disabled?
|
437
|
+
"default name #{name.inspect} matches disabled choice"
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
# Render a selection list.
|
442
|
+
#
|
443
|
+
# By default the result is printed out.
|
444
|
+
#
|
445
|
+
# @return [Object] value
|
446
|
+
# return the selected value
|
447
|
+
#
|
448
|
+
# @api private
|
449
|
+
def render
|
450
|
+
@prompt.print(@prompt.hide)
|
451
|
+
until @done
|
452
|
+
question = render_question
|
453
|
+
@prompt.print(question)
|
454
|
+
@prompt.read_keypress
|
455
|
+
|
456
|
+
# Split manually; if the second line is blank (when there are no
|
457
|
+
# matching lines), it won't be included by using String#lines.
|
458
|
+
question_lines = question.split($INPUT_RECORD_SEPARATOR, -1)
|
459
|
+
|
460
|
+
@prompt.print(refresh(question_lines_count(question_lines)))
|
461
|
+
end
|
462
|
+
@prompt.print(render_question) unless @quiet
|
463
|
+
answer
|
464
|
+
ensure
|
465
|
+
@prompt.print(@prompt.show)
|
466
|
+
end
|
467
|
+
|
468
|
+
# Count how many screen lines the question spans
|
469
|
+
#
|
470
|
+
# @return [Integer]
|
471
|
+
#
|
472
|
+
# @api private
|
473
|
+
def question_lines_count(question_lines)
|
474
|
+
question_lines.reduce(0) do |acc, line|
|
475
|
+
acc + @prompt.count_screen_lines(line)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# Find value for the choice selected
|
480
|
+
#
|
481
|
+
# @return [nil, Object]
|
482
|
+
#
|
483
|
+
# @api private
|
484
|
+
def answer
|
485
|
+
choices[@active - 1].value
|
486
|
+
end
|
487
|
+
|
488
|
+
# Clear screen lines
|
489
|
+
#
|
490
|
+
# @param [String]
|
491
|
+
#
|
492
|
+
# @api private
|
493
|
+
def refresh(lines)
|
494
|
+
@prompt.clear_lines(lines)
|
495
|
+
end
|
496
|
+
|
497
|
+
# Render question with instructions and menu
|
498
|
+
#
|
499
|
+
# @return [String]
|
500
|
+
#
|
501
|
+
# @api private
|
502
|
+
def render_question
|
503
|
+
header = ["#{@prefix}#{@question} #{render_header}\n"]
|
504
|
+
@first_render = false
|
505
|
+
unless @done
|
506
|
+
header << render_menu
|
507
|
+
end
|
508
|
+
header.join
|
509
|
+
end
|
510
|
+
|
511
|
+
# Is filtering enabled?
|
512
|
+
#
|
513
|
+
# @return [Boolean]
|
514
|
+
#
|
515
|
+
# @api private
|
516
|
+
def filterable?
|
517
|
+
@filterable
|
518
|
+
end
|
519
|
+
|
520
|
+
# Header part showing the current filter
|
521
|
+
#
|
522
|
+
# @return String
|
523
|
+
#
|
524
|
+
# @api private
|
525
|
+
def filter_help
|
526
|
+
"(Filter: #{@filter.join.inspect})"
|
527
|
+
end
|
528
|
+
|
529
|
+
# Check if help is shown only on start
|
530
|
+
#
|
531
|
+
# @api private
|
532
|
+
def help_start?
|
533
|
+
@show_help =~ /start/i
|
534
|
+
end
|
535
|
+
|
536
|
+
# Check if help is always displayed
|
537
|
+
#
|
538
|
+
# @api private
|
539
|
+
def help_always?
|
540
|
+
@show_help =~ /always/i
|
541
|
+
end
|
542
|
+
|
543
|
+
# Render initial help and selected choice
|
544
|
+
#
|
545
|
+
# @return [String]
|
546
|
+
#
|
547
|
+
# @api private
|
548
|
+
def render_header
|
549
|
+
if @done
|
550
|
+
selected_item = choices[@active - 1].name
|
551
|
+
@prompt.decorate(selected_item.to_s, @active_color)
|
552
|
+
elsif (@first_render && (help_start? || help_always?)) ||
|
553
|
+
(help_always? && !@filter.any?)
|
554
|
+
@prompt.decorate(help, @help_color)
|
555
|
+
elsif filterable? && @filter.any?
|
556
|
+
@prompt.decorate(filter_help, @help_color)
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
# Render menu with choices to select from
|
561
|
+
#
|
562
|
+
# @return [String]
|
563
|
+
#
|
564
|
+
# @api private
|
565
|
+
def render_menu
|
566
|
+
output = []
|
567
|
+
|
568
|
+
sync_paginators if @paging_changed
|
569
|
+
paginator.paginate(choices, @active, @per_page) do |choice, index|
|
570
|
+
num = enumerate? ? (index + 1).to_s + @enum + " " : ""
|
571
|
+
message = if index + 1 == @active && !choice.disabled?
|
572
|
+
selected = "#{@symbols[:marker]} #{num}#{choice.name}"
|
573
|
+
@prompt.decorate(selected.to_s, @active_color)
|
574
|
+
elsif choice.disabled?
|
575
|
+
@prompt.decorate(@symbols[:cross], :red) +
|
576
|
+
" #{num}#{choice.name} #{choice.disabled}"
|
577
|
+
else
|
578
|
+
" #{num}#{choice.name}"
|
579
|
+
end
|
580
|
+
end_index = paginated? ? paginator.end_index : choices.size - 1
|
581
|
+
newline = (index == end_index) ? "" : "\n"
|
582
|
+
output << (message + newline)
|
583
|
+
end
|
584
|
+
|
585
|
+
output.join
|
586
|
+
end
|
587
|
+
end # List
|
588
|
+
end # Prompt
|
589
|
+
end # TTY
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "question"
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
class Prompt
|
7
|
+
class MaskQuestion < Question
|
8
|
+
# Names for delete keys
|
9
|
+
DELETE_KEYS = %i[backspace delete].freeze
|
10
|
+
|
11
|
+
# Create masked question
|
12
|
+
#
|
13
|
+
# @param [Hash] options
|
14
|
+
# @option options [String] :mask
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
def initialize(prompt, **options)
|
18
|
+
super
|
19
|
+
@mask = options.fetch(:mask) { @prompt.symbols[:dot] }
|
20
|
+
@done_masked = false
|
21
|
+
@failure = false
|
22
|
+
end
|
23
|
+
|
24
|
+
# Set character for masking the STDIN input
|
25
|
+
#
|
26
|
+
# @param [String] char
|
27
|
+
#
|
28
|
+
# @return [self]
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
def mask(char = (not_set = true))
|
32
|
+
return @mask if not_set
|
33
|
+
|
34
|
+
@mask = char
|
35
|
+
end
|
36
|
+
|
37
|
+
def keyreturn(_event)
|
38
|
+
@done_masked = true
|
39
|
+
end
|
40
|
+
|
41
|
+
def keyenter(_event)
|
42
|
+
@done_masked = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def keypress(event)
|
46
|
+
if DELETE_KEYS.include?(event.key.name)
|
47
|
+
@input.chop! unless @input.empty?
|
48
|
+
elsif event.value =~ /^[^\e\n\r]/
|
49
|
+
@input += event.value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Render question and input replaced with masked character
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
def render_question
|
57
|
+
header = ["#{@prefix}#{message} "]
|
58
|
+
if echo?
|
59
|
+
masked = @mask.to_s * @input.to_s.length
|
60
|
+
if @done_masked && !@failure
|
61
|
+
masked = @prompt.decorate(masked, @active_color)
|
62
|
+
elsif @done_masked && @failure
|
63
|
+
masked = @prompt.decorate(masked, @error_color)
|
64
|
+
end
|
65
|
+
header << masked
|
66
|
+
end
|
67
|
+
header << "\n" if @done
|
68
|
+
header.join
|
69
|
+
end
|
70
|
+
|
71
|
+
def render_error(errors)
|
72
|
+
@failure = !errors.empty?
|
73
|
+
super
|
74
|
+
end
|
75
|
+
|
76
|
+
# Read input from user masked by character
|
77
|
+
#
|
78
|
+
# @private
|
79
|
+
def read_input(question)
|
80
|
+
@done_masked = false
|
81
|
+
@failure = false
|
82
|
+
@input = ""
|
83
|
+
@prompt.print(question)
|
84
|
+
until @done_masked
|
85
|
+
@prompt.read_keypress
|
86
|
+
question = render_question
|
87
|
+
total_lines = @prompt.count_screen_lines(question)
|
88
|
+
@prompt.print(@prompt.clear_lines(total_lines))
|
89
|
+
@prompt.print(render_question)
|
90
|
+
end
|
91
|
+
@prompt.puts
|
92
|
+
@input
|
93
|
+
end
|
94
|
+
end # MaskQuestion
|
95
|
+
end # Prompt
|
96
|
+
end # TTY
|