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,224 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "list"
|
4
|
+
require_relative "selected_choices"
|
5
|
+
|
6
|
+
module TTY
|
7
|
+
class Prompt
|
8
|
+
# A class responsible for rendering multi select list menu.
|
9
|
+
# Used by {Prompt} to display interactive choice menu.
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
class MultiList < List
|
13
|
+
# Create instance of TTY::Prompt::MultiList menu.
|
14
|
+
#
|
15
|
+
# @param [Prompt] :prompt
|
16
|
+
# @param [Hash] options
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def initialize(prompt, **options)
|
20
|
+
super
|
21
|
+
@selected = SelectedChoices.new
|
22
|
+
@help = options[:help]
|
23
|
+
@echo = options.fetch(:echo, true)
|
24
|
+
@min = options[:min]
|
25
|
+
@max = options[:max]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Set a minimum number of choices
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
def min(value)
|
32
|
+
@min = value
|
33
|
+
end
|
34
|
+
|
35
|
+
# Set a maximum number of choices
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def max(value)
|
39
|
+
@max = value
|
40
|
+
end
|
41
|
+
|
42
|
+
# Callback fired when enter/return key is pressed
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
def keyenter(*)
|
46
|
+
valid = true
|
47
|
+
valid = @min <= @selected.size if @min
|
48
|
+
valid = @selected.size <= @max if @max
|
49
|
+
|
50
|
+
super if valid
|
51
|
+
end
|
52
|
+
alias keyreturn keyenter
|
53
|
+
|
54
|
+
# Callback fired when space key is pressed
|
55
|
+
#
|
56
|
+
# @api private
|
57
|
+
def keyspace(*)
|
58
|
+
active_choice = choices[@active - 1]
|
59
|
+
if @selected.include?(active_choice)
|
60
|
+
@selected.delete_at(@active - 1)
|
61
|
+
else
|
62
|
+
return if @max && @selected.size >= @max
|
63
|
+
|
64
|
+
@selected.insert(@active - 1, active_choice)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Selects all choices when Ctrl+A is pressed
|
69
|
+
#
|
70
|
+
# @api private
|
71
|
+
def keyctrl_a(*)
|
72
|
+
return if @max && @max < choices.size
|
73
|
+
|
74
|
+
@selected = SelectedChoices.new(choices.enabled, choices.enabled_indexes)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Revert currently selected choices when Ctrl+I is pressed
|
78
|
+
#
|
79
|
+
# @api private
|
80
|
+
def keyctrl_r(*)
|
81
|
+
return if @max && @max < choices.size
|
82
|
+
|
83
|
+
indexes = choices.each_with_index.reduce([]) do |acc, (choice, idx)|
|
84
|
+
acc << idx if !choice.disabled? && !@selected.include?(choice)
|
85
|
+
acc
|
86
|
+
end
|
87
|
+
@selected = SelectedChoices.new(choices.enabled - @selected.to_a, indexes)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Setup default options and active selection
|
93
|
+
#
|
94
|
+
# @api private
|
95
|
+
def setup_defaults
|
96
|
+
validate_defaults
|
97
|
+
# At this stage, @choices matches all the visible choices.
|
98
|
+
default_indexes = @default.map do |d|
|
99
|
+
if d.to_s =~ INTEGER_MATCHER
|
100
|
+
d - 1
|
101
|
+
else
|
102
|
+
choices.index(choices.find_by(:name, d.to_s))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
@selected = SelectedChoices.new(@choices.values_at(*default_indexes),
|
106
|
+
default_indexes)
|
107
|
+
|
108
|
+
if @default.empty?
|
109
|
+
# no default, pick the first non-disabled choice
|
110
|
+
@active = choices.index { |choice| !choice.disabled? } + 1
|
111
|
+
elsif @default.last.to_s =~ INTEGER_MATCHER
|
112
|
+
@active = @default.last
|
113
|
+
elsif default_choice = choices.find_by(:name, @default.last.to_s)
|
114
|
+
@active = choices.index(default_choice) + 1
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Generate selected items names
|
119
|
+
#
|
120
|
+
# @return [String]
|
121
|
+
#
|
122
|
+
# @api private
|
123
|
+
def selected_names
|
124
|
+
@selected.map(&:name).join(", ")
|
125
|
+
end
|
126
|
+
|
127
|
+
# Header part showing the minimum/maximum number of choices
|
128
|
+
#
|
129
|
+
# @return [String]
|
130
|
+
#
|
131
|
+
# @api private
|
132
|
+
def minmax_help
|
133
|
+
help = []
|
134
|
+
help << "min. #{@min}" if @min
|
135
|
+
help << "max. #{@max}" if @max
|
136
|
+
"(%s) " % [help.join(", ")]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Build a default help text
|
140
|
+
#
|
141
|
+
# @return [String]
|
142
|
+
#
|
143
|
+
# @api private
|
144
|
+
def default_help
|
145
|
+
str = []
|
146
|
+
str << "(Press "
|
147
|
+
str << "#{arrows_help} arrow"
|
148
|
+
str << " or 1-#{choices.size} number" if enumerate?
|
149
|
+
str << " to move, Space"
|
150
|
+
str << "/Ctrl+A|R" if @max.nil?
|
151
|
+
str << " to select"
|
152
|
+
str << " (all|rev)" if @max.nil?
|
153
|
+
str << (filterable? ? "," : " and")
|
154
|
+
str << " Enter to finish"
|
155
|
+
str << " and letters to filter" if filterable?
|
156
|
+
str << ")"
|
157
|
+
str.join
|
158
|
+
end
|
159
|
+
|
160
|
+
# Render initial help text and then currently selected choices
|
161
|
+
#
|
162
|
+
# @api private
|
163
|
+
def render_header
|
164
|
+
instructions = @prompt.decorate(help, @help_color)
|
165
|
+
minmax_suffix = @min || @max ? minmax_help : ""
|
166
|
+
print_selected = @selected.size.nonzero? && @echo
|
167
|
+
|
168
|
+
if @done && @echo
|
169
|
+
@prompt.decorate(selected_names, @active_color)
|
170
|
+
elsif (@first_render && (help_start? || help_always?)) ||
|
171
|
+
(help_always? && !@filter.any? && !@done)
|
172
|
+
minmax_suffix +
|
173
|
+
(print_selected ? "#{selected_names} " : "") +
|
174
|
+
instructions
|
175
|
+
elsif filterable? && @filter.any?
|
176
|
+
minmax_suffix +
|
177
|
+
(print_selected ? "#{selected_names} " : "") +
|
178
|
+
@prompt.decorate(filter_help, @help_color)
|
179
|
+
else
|
180
|
+
minmax_suffix + (print_selected ? selected_names : "")
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# All values for the choices selected
|
185
|
+
#
|
186
|
+
# @return [Array[nil,Object]]
|
187
|
+
#
|
188
|
+
# @api private
|
189
|
+
def answer
|
190
|
+
@selected.map(&:value)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Render menu with choices to select from
|
194
|
+
#
|
195
|
+
# @return [String]
|
196
|
+
#
|
197
|
+
# @api private
|
198
|
+
def render_menu
|
199
|
+
output = []
|
200
|
+
|
201
|
+
sync_paginators if @paging_changed
|
202
|
+
paginator.paginate(choices, @active, @per_page) do |choice, index|
|
203
|
+
num = enumerate? ? (index + 1).to_s + @enum + " " : ""
|
204
|
+
indicator = (index + 1 == @active) ? @symbols[:marker] : " "
|
205
|
+
indicator += " "
|
206
|
+
message = if @selected.include?(choice) && !choice.disabled?
|
207
|
+
selected = @prompt.decorate(@symbols[:radio_on], @active_color)
|
208
|
+
"#{selected} #{num}#{choice.name}"
|
209
|
+
elsif choice.disabled?
|
210
|
+
@prompt.decorate(@symbols[:cross], :red) +
|
211
|
+
" #{num}#{choice.name} #{choice.disabled}"
|
212
|
+
else
|
213
|
+
"#{@symbols[:radio_off]} #{num}#{choice.name}"
|
214
|
+
end
|
215
|
+
end_index = paginated? ? paginator.end_index : choices.size - 1
|
216
|
+
newline = (index == end_index) ? "" : "\n"
|
217
|
+
output << indicator + message + newline
|
218
|
+
end
|
219
|
+
|
220
|
+
output.join
|
221
|
+
end
|
222
|
+
end # MultiList
|
223
|
+
end # Prompt
|
224
|
+
end # TTY
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "question"
|
4
|
+
require_relative "symbols"
|
5
|
+
|
6
|
+
module TTY
|
7
|
+
class Prompt
|
8
|
+
# A prompt responsible for multi line user input
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Multiline < Question
|
12
|
+
HELP = "(Press Ctrl+D or Ctrl+Z to finish)".freeze
|
13
|
+
|
14
|
+
def initialize(prompt, **options)
|
15
|
+
super
|
16
|
+
@help = options[:help] || self.class::HELP
|
17
|
+
@first_render = true
|
18
|
+
@lines_count = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
# Provide help information
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def help(value = (not_set = true))
|
27
|
+
return @help if not_set
|
28
|
+
|
29
|
+
@help = value
|
30
|
+
end
|
31
|
+
|
32
|
+
def read_input
|
33
|
+
@prompt.read_multiline
|
34
|
+
end
|
35
|
+
|
36
|
+
def keyreturn(*)
|
37
|
+
@lines_count += 1
|
38
|
+
end
|
39
|
+
alias keyenter keyreturn
|
40
|
+
|
41
|
+
def render_question
|
42
|
+
header = ["#{@prefix}#{message} "]
|
43
|
+
if !echo?
|
44
|
+
header
|
45
|
+
elsif @done
|
46
|
+
header << @prompt.decorate(@input.to_s, @active_color)
|
47
|
+
elsif @first_render
|
48
|
+
header << @prompt.decorate(help, @help_color)
|
49
|
+
@first_render = false
|
50
|
+
end
|
51
|
+
header << "\n"
|
52
|
+
header.join
|
53
|
+
end
|
54
|
+
|
55
|
+
def process_input(question)
|
56
|
+
@prompt.print(question)
|
57
|
+
@lines = read_input
|
58
|
+
@input = "#{@lines.first.strip} ..." unless @lines.first.to_s.empty?
|
59
|
+
if Utils.blank?(@input) && default?
|
60
|
+
@input = default
|
61
|
+
@lines = default
|
62
|
+
end
|
63
|
+
@evaluator.(@lines)
|
64
|
+
end
|
65
|
+
|
66
|
+
def refresh(lines, lines_to_clear)
|
67
|
+
size = @lines_count + lines_to_clear + 1
|
68
|
+
@prompt.clear_lines(size)
|
69
|
+
end
|
70
|
+
end # Multiline
|
71
|
+
end # Prompt
|
72
|
+
end # TTY
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
class Paginator
|
6
|
+
DEFAULT_PAGE_SIZE = 6
|
7
|
+
|
8
|
+
# The 0-based index of the first item on this page
|
9
|
+
attr_accessor :start_index
|
10
|
+
|
11
|
+
# The 0-based index of the last item on this page
|
12
|
+
attr_reader :end_index
|
13
|
+
|
14
|
+
# The 0-based index of the active item on this page
|
15
|
+
attr_reader :current_index
|
16
|
+
|
17
|
+
# The 0-based index of the previously active item on this page
|
18
|
+
attr_reader :last_index
|
19
|
+
|
20
|
+
# Create a Paginator
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
def initialize(**options)
|
24
|
+
@last_index = Array(options[:default]).flatten.first || 0
|
25
|
+
@per_page = options[:per_page]
|
26
|
+
@start_index = Array(options[:default]).flatten.first
|
27
|
+
end
|
28
|
+
|
29
|
+
# Reset current page indexes
|
30
|
+
#
|
31
|
+
# @api private
|
32
|
+
def reset!
|
33
|
+
@start_index = nil
|
34
|
+
@end_index = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
# Check if page size is valid
|
38
|
+
#
|
39
|
+
# @raise [InvalidArgument]
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
def check_page_size!
|
43
|
+
raise InvalidArgument, "per_page must be > 0" if @per_page < 1
|
44
|
+
end
|
45
|
+
|
46
|
+
# Paginate collection given an active index
|
47
|
+
#
|
48
|
+
# @param [Array[Choice]] list
|
49
|
+
# a collection of choice items
|
50
|
+
# @param [Integer] active
|
51
|
+
# current choice active index
|
52
|
+
# @param [Integer] per_page
|
53
|
+
# number of choice items per page
|
54
|
+
#
|
55
|
+
# @return [Enumerable]
|
56
|
+
# the list between start and end index
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
def paginate(list, active, per_page = nil, &block)
|
60
|
+
current_index = active - 1
|
61
|
+
default_size = (list.size <= DEFAULT_PAGE_SIZE ? list.size : DEFAULT_PAGE_SIZE)
|
62
|
+
@per_page = @per_page || per_page || default_size
|
63
|
+
check_page_size!
|
64
|
+
@start_index ||= (current_index / @per_page) * @per_page
|
65
|
+
@end_index ||= @start_index + @per_page - 1
|
66
|
+
|
67
|
+
# Don't paginate short lists
|
68
|
+
if list.size <= @per_page
|
69
|
+
@start_index = 0
|
70
|
+
@end_index = list.size - 1
|
71
|
+
if block
|
72
|
+
return list.each_with_index(&block)
|
73
|
+
else
|
74
|
+
return list.each_with_index.to_enum
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
step = (current_index - @last_index).abs
|
79
|
+
if current_index > @last_index # going up
|
80
|
+
if current_index >= @end_index && current_index < list.size - 1
|
81
|
+
last_page = list.size - @per_page
|
82
|
+
@start_index = [@start_index + step, last_page].min
|
83
|
+
end
|
84
|
+
elsif current_index < @last_index # going down
|
85
|
+
if current_index <= @start_index && current_index > 0
|
86
|
+
@start_index = [@start_index - step, 0].max
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Cycle list
|
91
|
+
if current_index.zero?
|
92
|
+
@start_index = 0
|
93
|
+
elsif current_index == list.size - 1
|
94
|
+
@start_index = list.size - 1 - (@per_page - 1)
|
95
|
+
end
|
96
|
+
|
97
|
+
@end_index = @start_index + (@per_page - 1)
|
98
|
+
@last_index = current_index
|
99
|
+
|
100
|
+
sliced_list = list[@start_index..@end_index]
|
101
|
+
page_range = (@start_index..@end_index)
|
102
|
+
|
103
|
+
return sliced_list.zip(page_range).to_enum unless block_given?
|
104
|
+
|
105
|
+
sliced_list.each_with_index do |item, index|
|
106
|
+
block[item, @start_index + index]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end # Paginator
|
110
|
+
end # Prompt
|
111
|
+
end # TTY
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../const"
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
class Prompt
|
7
|
+
class Question
|
8
|
+
module Checks
|
9
|
+
# Check if modifications are applicable
|
10
|
+
class CheckModifier
|
11
|
+
def self.call(question, value)
|
12
|
+
if !question.modifier.nil? || question.modifier
|
13
|
+
[Modifier.new(question.modifier).apply_to(value)]
|
14
|
+
else
|
15
|
+
[value]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Check if value is within range
|
21
|
+
class CheckRange
|
22
|
+
def self.float?(value)
|
23
|
+
!/[-+]?(\d*[.])?\d+/.match(value.to_s).nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.int?(value)
|
27
|
+
!/^[-+]?\d+$/.match(value.to_s).nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.cast(value)
|
31
|
+
if float?(value)
|
32
|
+
value.to_f
|
33
|
+
elsif int?(value)
|
34
|
+
value.to_i
|
35
|
+
else
|
36
|
+
value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.call(question, value)
|
41
|
+
if !question.in? ||
|
42
|
+
(question.in? && question.in.include?(cast(value)))
|
43
|
+
[value]
|
44
|
+
else
|
45
|
+
tokens = { value: value, in: question.in }
|
46
|
+
[value, question.message_for(:range?, tokens)]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Check if input requires validation
|
52
|
+
class CheckValidation
|
53
|
+
def self.call(question, value)
|
54
|
+
if !question.validation? || (question.required? && value.nil?) ||
|
55
|
+
(question.validation? &&
|
56
|
+
Validation.new(question.validation).call(value))
|
57
|
+
[value]
|
58
|
+
else
|
59
|
+
tokens = { valid: question.validation.inspect, value: value }
|
60
|
+
[value, question.message_for(:valid?, tokens)]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Check if default value provided
|
66
|
+
class CheckDefault
|
67
|
+
def self.call(question, value)
|
68
|
+
if value.nil? && question.default?
|
69
|
+
[question.default]
|
70
|
+
else
|
71
|
+
[value]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Check if input is required
|
77
|
+
class CheckRequired
|
78
|
+
def self.call(question, value)
|
79
|
+
if question.required? && !question.default? && value.nil?
|
80
|
+
[value, question.message_for(:required?)]
|
81
|
+
else
|
82
|
+
[value]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class CheckConversion
|
88
|
+
def self.call(question, value)
|
89
|
+
if question.convert? && !Utils.blank?(value)
|
90
|
+
result = question.convert_result(value)
|
91
|
+
if result == Const::Undefined
|
92
|
+
tokens = { value: value, type: question.convert }
|
93
|
+
[value, question.message_for(:convert?, tokens)]
|
94
|
+
else
|
95
|
+
[result]
|
96
|
+
end
|
97
|
+
else
|
98
|
+
[value]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end # Checks
|
103
|
+
end # Question
|
104
|
+
end # Prompt
|
105
|
+
end # TTY
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
class Question
|
6
|
+
# A class representing String modifications.
|
7
|
+
class Modifier
|
8
|
+
attr_reader :modifiers
|
9
|
+
|
10
|
+
# Initialize a Modifier
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
def initialize(modifiers)
|
14
|
+
@modifiers = modifiers
|
15
|
+
end
|
16
|
+
|
17
|
+
# Change supplied value according to the given string transformation.
|
18
|
+
# Valid settings are:
|
19
|
+
#
|
20
|
+
# @param [String] value
|
21
|
+
# the string to be modified
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
def apply_to(value)
|
27
|
+
modifiers.reduce(value) do |result, mod|
|
28
|
+
result = Modifier.letter_case(mod, result)
|
29
|
+
Modifier.whitespace(mod, result)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Changes letter casing in a string according to valid modifications.
|
34
|
+
# For invalid modification option the string is preserved.
|
35
|
+
#
|
36
|
+
# @param [Symbol] mod
|
37
|
+
# the modification to change the string
|
38
|
+
#
|
39
|
+
# @option mod [Symbol] :up change to upper case
|
40
|
+
# @option mod [Symbol] :upcase change to upper case
|
41
|
+
# @option mod [Symbol] :uppercase change to upper case
|
42
|
+
# @option mod [Symbol] :down change to lower case
|
43
|
+
# @option mod [Symbol] :downcase change to lower case
|
44
|
+
# @option mod [Symbol] :capitalize change all words to start
|
45
|
+
# with uppercase case letter
|
46
|
+
#
|
47
|
+
# @return [String]
|
48
|
+
#
|
49
|
+
# @api public
|
50
|
+
def self.letter_case(mod, value)
|
51
|
+
return value unless value.is_a?(String)
|
52
|
+
|
53
|
+
case mod
|
54
|
+
when :up, :upcase, :uppercase
|
55
|
+
value.upcase
|
56
|
+
when :down, :downcase, :lowercase
|
57
|
+
value.downcase
|
58
|
+
when :capitalize
|
59
|
+
value.capitalize
|
60
|
+
else
|
61
|
+
value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Changes whitespace in a string according to valid modifications.
|
66
|
+
#
|
67
|
+
# @param [Symbol] mod
|
68
|
+
# the modification to change the string
|
69
|
+
#
|
70
|
+
# @option mod [String] :trim, :strip
|
71
|
+
# remove whitespace for the start and end
|
72
|
+
# @option mod [String] :chomp remove record separator from the end
|
73
|
+
# @option mod [String] :collapse remove any duplicate whitespace
|
74
|
+
# @option mod [String] :remove remove all whitespace
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def self.whitespace(mod, value)
|
78
|
+
return value unless value.is_a?(String)
|
79
|
+
|
80
|
+
case mod
|
81
|
+
when :trim, :strip
|
82
|
+
value.strip
|
83
|
+
when :chomp
|
84
|
+
value.chomp
|
85
|
+
when :collapse
|
86
|
+
value.gsub(/\s+/, " ")
|
87
|
+
when :remove
|
88
|
+
value.gsub(/\s+/, "")
|
89
|
+
else
|
90
|
+
value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end # Modifier
|
94
|
+
end # Question
|
95
|
+
end # Prompt
|
96
|
+
end # TTY
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
class Question
|
6
|
+
# A class representing question validation.
|
7
|
+
class Validation
|
8
|
+
# Available validator names
|
9
|
+
VALIDATORS = {
|
10
|
+
email: /^[a-z0-9._%+-]+@([a-z0-9-]+\.)+[a-z]{2,6}$/i
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
attr_reader :pattern
|
14
|
+
|
15
|
+
# Initialize a Validation
|
16
|
+
#
|
17
|
+
# @param [Object] pattern
|
18
|
+
#
|
19
|
+
# @return [undefined]
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
def initialize(pattern)
|
23
|
+
@pattern = coerce(pattern)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Convert validation into known type.
|
27
|
+
#
|
28
|
+
# @param [Object] pattern
|
29
|
+
#
|
30
|
+
# @raise [TTY::ValidationCoercion]
|
31
|
+
# raised when failed to convert validation
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
def coerce(pattern)
|
35
|
+
case pattern
|
36
|
+
when String, Symbol, Proc
|
37
|
+
pattern
|
38
|
+
when Regexp
|
39
|
+
Regexp.new(pattern.to_s)
|
40
|
+
else
|
41
|
+
raise ValidationCoercion, "Wrong type, got #{pattern.class}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Test if the input passes the validation
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# Validation.new(/pattern/)
|
49
|
+
# validation.call(input) # => true
|
50
|
+
#
|
51
|
+
# @param [Object] input
|
52
|
+
# the input to validate
|
53
|
+
#
|
54
|
+
# @return [Boolean]
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
def call(input)
|
58
|
+
if pattern.is_a?(String) || pattern.is_a?(Symbol)
|
59
|
+
VALIDATORS.key?(pattern.to_sym)
|
60
|
+
!VALIDATORS[pattern.to_sym].match(input.to_s).nil?
|
61
|
+
elsif pattern.is_a?(Regexp)
|
62
|
+
!pattern.match(input.to_s).nil?
|
63
|
+
elsif pattern.is_a?(Proc)
|
64
|
+
result = pattern.call(input.to_s)
|
65
|
+
result.nil? ? false : result
|
66
|
+
else false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end # Validation
|
70
|
+
end # Question
|
71
|
+
end # Prompt
|
72
|
+
end # TTY
|