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.
Files changed (275) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +48 -0
  3. data/.gitattributes +19 -0
  4. data/.github/dependabot.yml +12 -0
  5. data/.github/workflows/ci.yml +90 -0
  6. data/.rubocop.yml +8 -0
  7. data/.ruby-version +1 -0
  8. data/Dockerfile +69 -0
  9. data/Gemfile +68 -0
  10. data/README.md +188 -0
  11. data/Rakefile +6 -0
  12. data/app/assets/config/manifest.js +5 -0
  13. data/app/assets/images/.keep +0 -0
  14. data/app/assets/images/2024-12-30-07:58:34-screenshot.png +0 -0
  15. data/app/assets/images/facebook/facebook-logo.svg +3 -0
  16. data/app/assets/images/google/googlelogo_color_272x92dp.png +0 -0
  17. data/app/assets/images/instagram/app-store.png +0 -0
  18. data/app/assets/images/instagram/google-play.png +0 -0
  19. data/app/assets/images/instagram/instagram-logo.png +0 -0
  20. data/app/assets/images/instagram/screenshot1.png +0 -0
  21. data/app/assets/images/instagram/screenshot2.png +0 -0
  22. data/app/assets/images/instagram/screenshot3.png +0 -0
  23. data/app/assets/images/instagram/screenshot4.png +0 -0
  24. data/app/assets/images/nauta_etecsa/CUBADEBATE.jpg +0 -0
  25. data/app/assets/images/nauta_etecsa/CUBAEDUCA.jpg +0 -0
  26. data/app/assets/images/nauta_etecsa/CUBARTE.jpg +0 -0
  27. data/app/assets/images/nauta_etecsa/CUBASI.jpg +0 -0
  28. data/app/assets/images/nauta_etecsa/ECURED.jpg +0 -0
  29. data/app/assets/images/nauta_etecsa/OFERTAS.jpg +0 -0
  30. data/app/assets/images/nauta_etecsa/PAPELETA.jpg +0 -0
  31. data/app/assets/images/nauta_etecsa/contenedor_bg.jpg +0 -0
  32. data/app/assets/images/nauta_etecsa/etecsa.jpg +0 -0
  33. data/app/assets/images/nauta_etecsa/nauta_wifi.jpg +0 -0
  34. data/app/assets/images/nauta_hogar/CUBADEBATE.jpg +0 -0
  35. data/app/assets/images/nauta_hogar/CUBAEDUCA.jpg +0 -0
  36. data/app/assets/images/nauta_hogar/CUBARTE.jpg +0 -0
  37. data/app/assets/images/nauta_hogar/CUBASI.jpg +0 -0
  38. data/app/assets/images/nauta_hogar/ECURED.jpg +0 -0
  39. data/app/assets/images/nauta_hogar/OFERTAS.jpg +0 -0
  40. data/app/assets/images/nauta_hogar/PAPELETA.jpg +0 -0
  41. data/app/assets/images/nauta_hogar/banner_hogar.png +0 -0
  42. data/app/assets/images/wibrfake/Breaker-Wallpaper1.jpg +0 -0
  43. data/app/assets/images/wibrfake/mac-list-parameter.png +0 -0
  44. data/app/assets/images/wibrfake/wibrfake-100x100.png +0 -0
  45. data/app/assets/images/wibrfake/wibrfake-32x32.png +0 -0
  46. data/app/assets/images/wibrfake/wibrfake-48x48.png +0 -0
  47. data/app/assets/images/wibrfake/wibrfake-apfake-on.png +0 -0
  48. data/app/assets/images/wibrfake/wibrfake-apfake-set-basic.png +0 -0
  49. data/app/assets/images/wibrfake/wibrfake-apfake-show.png +0 -0
  50. data/app/assets/images/wibrfake/wibrfake-help.png +0 -0
  51. data/app/assets/images/wibrfake/wibrfake-hostpot.png +0 -0
  52. data/app/assets/images/wibrfake/wibrfake-init.png +0 -0
  53. data/app/assets/images/wibrfake/wibrfake-init2.png +0 -0
  54. data/app/assets/images/wibrfake/wibrfake-login-basic.jpg +0 -0
  55. data/app/assets/images/wibrfake/wibrfake-mac-list.png +0 -0
  56. data/app/assets/images/wibrfake/wibrfake-mac-ramset-oui.png +0 -0
  57. data/app/assets/images/wibrfake/wibrfake-mac-ramset.png +0 -0
  58. data/app/assets/images/wibrfake/wibrfake-mac-set.png +0 -0
  59. data/app/assets/images/wibrfake/wibrfake-mac-show.png +0 -0
  60. data/app/assets/images/wibrfake/wibrfake-need-signin.jpg +0 -0
  61. data/app/assets/images/wibrfake/wibrfake-session-active.png +0 -0
  62. data/app/assets/images/wibrfake/wibrfake-session-init.png +0 -0
  63. data/app/assets/images/wibrfake/wibrfake-session-list.png +0 -0
  64. data/app/assets/images/wibrfake/wibrfake-session-new.png +0 -0
  65. data/app/assets/images/wibrfake/wibrfake-session-rename.png +0 -0
  66. data/app/assets/images/wibrfake/wibrfake-session-save.png +0 -0
  67. data/app/assets/images/wibrfake/wibrfake-web-server-apfake.png +0 -0
  68. data/app/assets/images/wibrfake/wibrfake-web-server-on.png +0 -0
  69. data/app/assets/images/wibrfake/wibrfake-web-server-show.png +0 -0
  70. data/app/assets/images/wibrfake/wibrfake-wkdump-loaded.png +0 -0
  71. data/app/assets/images/wibrfake/wibrfake-wkdump.png +0 -0
  72. data/app/assets/images/wibrfake/wibrfake.png +0 -0
  73. data/app/assets/stylesheets/application.css +15 -0
  74. data/app/assets/stylesheets/basic/page_basic.css +0 -0
  75. data/app/assets/stylesheets/facebook/page_facebook.css +93 -0
  76. data/app/assets/stylesheets/google/page_google.css +137 -0
  77. data/app/assets/stylesheets/instagram/page_instagram.css +94 -0
  78. data/app/assets/stylesheets/nauta_etecsa/page_nauta.css +325 -0
  79. data/app/assets/stylesheets/nauta_hogar/page_nauta.css +469 -0
  80. data/app/assets/stylesheets/wibrfake/page_wibrfake.css +1181 -0
  81. data/app/assets/stylesheets/wibrfake/page_wibrfake_about.css +486 -0
  82. data/app/channels/application_cable/channel.rb +4 -0
  83. data/app/channels/application_cable/connection.rb +4 -0
  84. data/app/controllers/application_controller.rb +15 -0
  85. data/app/controllers/concerns/.keep +0 -0
  86. data/app/controllers/sessions_controller.rb +111 -0
  87. data/app/helpers/application_helper.rb +2 -0
  88. data/app/helpers/sessions_helper.rb +2 -0
  89. data/app/javascript/application.js +3 -0
  90. data/app/javascript/bootstrap.bundle.min.js +7 -0
  91. data/app/javascript/controllers/application.js +9 -0
  92. data/app/javascript/controllers/hello_controller.js +7 -0
  93. data/app/javascript/controllers/index.js +4 -0
  94. data/app/javascript/google/page_google.js +50 -0
  95. data/app/javascript/incorrect3.js +29 -0
  96. data/app/javascript/page_wibrfake.js +50 -0
  97. data/app/javascript/page_wibrfake_about.js +147 -0
  98. data/app/jobs/application_job.rb +7 -0
  99. data/app/mailers/application_mailer.rb +4 -0
  100. data/app/models/application_record.rb +3 -0
  101. data/app/models/concerns/.keep +0 -0
  102. data/app/models/user.rb +6 -0
  103. data/app/views/layouts/application.html.erb +33 -0
  104. data/app/views/layouts/mailer.html.erb +13 -0
  105. data/app/views/layouts/mailer.text.erb +1 -0
  106. data/app/views/pwa/manifest.json.erb +22 -0
  107. data/app/views/pwa/service-worker.js +26 -0
  108. data/app/views/sessions/basic/basic.html.erb +21 -0
  109. data/app/views/sessions/facebook/facebook.html.erb +132 -0
  110. data/app/views/sessions/google/google.html.erb +72 -0
  111. data/app/views/sessions/index.html.erb +11 -0
  112. data/app/views/sessions/instagram/instagram.html.erb +69 -0
  113. data/app/views/sessions/nauta_etecsa/nauta_etecsa.html.erb +97 -0
  114. data/app/views/sessions/nauta_hogar/nauta_hogar.html.erb +135 -0
  115. data/app/views/sessions/wibrfake/about/about.html.erb +164 -0
  116. data/app/views/sessions/wibrfake/wibrfake.html.erb +417 -0
  117. data/bin/.names.rb.swp +0 -0
  118. data/bin/brakeman +7 -0
  119. data/bin/bundle +114 -0
  120. data/bin/docker-entrypoint +13 -0
  121. data/bin/google.wkdump +3 -0
  122. data/bin/importmap +4 -0
  123. data/bin/rails +4 -0
  124. data/bin/rake +4 -0
  125. data/bin/rubocop +8 -0
  126. data/bin/setup +37 -0
  127. data/bin/wibrfake +20 -0
  128. data/config/application.rb +26 -0
  129. data/config/boot.rb +4 -0
  130. data/config/cable.yml +10 -0
  131. data/config/credentials.yml.enc +1 -0
  132. data/config/database.yml +16 -0
  133. data/config/environment.rb +5 -0
  134. data/config/environments/development.rb +85 -0
  135. data/config/environments/production.rb +105 -0
  136. data/config/environments/test.rb +67 -0
  137. data/config/icons/application-x-wkdump.xml +13 -0
  138. data/config/icons/hicolor/16x16/mimetypes/application-x-wkdump.png +0 -0
  139. data/config/icons/hicolor/32x32/mimetypes/application-x-wkdump.png +0 -0
  140. data/config/icons/hicolor/48x48/mimetypes/application-x-wkdump.png +0 -0
  141. data/config/icons/hicolor/icon-theme.cache +0 -0
  142. data/config/icons/hicolor/index.theme +20 -0
  143. data/config/icons/wkdump.desktop +5 -0
  144. data/config/importmap.rb +7 -0
  145. data/config/initializers/assets.rb +12 -0
  146. data/config/initializers/content_security_policy.rb +25 -0
  147. data/config/initializers/devise.rb +313 -0
  148. data/config/initializers/filter_parameter_logging.rb +8 -0
  149. data/config/initializers/inflections.rb +16 -0
  150. data/config/initializers/permissions_policy.rb +13 -0
  151. data/config/locales/devise.en.yml +65 -0
  152. data/config/locales/en.yml +31 -0
  153. data/config/puma.rb +35 -0
  154. data/config/routes.rb +12 -0
  155. data/config/storage.yml +34 -0
  156. data/config.ru +6 -0
  157. data/db/development.sqlite3 +0 -0
  158. data/db/schema.rb +14 -0
  159. data/ext/wibrfake/extconf.rb +3 -0
  160. data/ext/wibrfake/wibrfake.h +0 -0
  161. data/images/ouilist.png +0 -0
  162. data/images/wibrfakehelp.png +0 -0
  163. data/images/wibrfakeinit.png +0 -0
  164. data/lib/assets/.keep +0 -0
  165. data/lib/tasks/.keep +0 -0
  166. data/lib/wibrfake/Config/ipaddr.rb +71 -0
  167. data/lib/wibrfake/Config/options.rb +98 -0
  168. data/lib/wibrfake/Config/security_wpa.rb +43 -0
  169. data/lib/wibrfake/DataBase/howuis.db +0 -0
  170. data/lib/wibrfake/DataBase/ouis.db +0 -0
  171. data/lib/wibrfake/DataBase/ouis.rb +24 -0
  172. data/lib/wibrfake/DataBase/rename.rb +15 -0
  173. data/lib/wibrfake/DataBase/show.rb +18 -0
  174. data/lib/wibrfake/Dnsmasq/dhcp.rb +149 -0
  175. data/lib/wibrfake/Dnsmasq/dhcp_prueba.rb +135 -0
  176. data/lib/wibrfake/Dnsmasq/dns.rb +34 -0
  177. data/lib/wibrfake/Dnsmasq/dns_prueba.rb +36 -0
  178. data/lib/wibrfake/Dnsmasq/dnsmasq.rb +33 -0
  179. data/lib/wibrfake/Listing/apfake.rb +28 -0
  180. data/lib/wibrfake/Listing/clients.rb +76 -0
  181. data/lib/wibrfake/Listing/ouis.rb +53 -0
  182. data/lib/wibrfake/Listing/process.rb +14 -0
  183. data/lib/wibrfake/Listing/sessions.rb +47 -0
  184. data/lib/wibrfake/Listing/web_server.rb +17 -0
  185. data/lib/wibrfake/Listing/wireless.rb +30 -0
  186. data/lib/wibrfake/Logs/credentials.log +59 -0
  187. data/lib/wibrfake/NetworkInterface/ip-routing.rb +99 -0
  188. data/lib/wibrfake/NetworkInterface/mac.rb +114 -0
  189. data/lib/wibrfake/NetworkInterface/monitor.rb +44 -0
  190. data/lib/wibrfake/NetworkInterface/ouis.db +0 -0
  191. data/lib/wibrfake/Process/id.rb +11 -0
  192. data/lib/wibrfake/Process/pids.log +5 -0
  193. data/lib/wibrfake/Process/pkill.rb +124 -0
  194. data/lib/wibrfake/Process/processes.rb +76 -0
  195. data/lib/wibrfake/Rails/login.rb +20 -0
  196. data/lib/wibrfake/Rails/routes.rb +76 -0
  197. data/lib/wibrfake/Rails/service.rb +58 -0
  198. data/lib/wibrfake/Rails/service_usage.rb +46 -0
  199. data/lib/wibrfake/Sessions/session.rb +188 -0
  200. data/lib/wibrfake/String/string.rb +109 -0
  201. data/lib/wibrfake/Tmp/config.yml +1 -0
  202. data/lib/wibrfake/config.rb +24 -0
  203. data/lib/wibrfake/help.rb +107 -0
  204. data/lib/wibrfake/optionpr.rb +68 -0
  205. data/lib/wibrfake/run.rb +9 -0
  206. data/lib/wibrfake/ty/apld.rb +110 -0
  207. data/lib/wibrfake/ty/prompt/answers_collector.rb +78 -0
  208. data/lib/wibrfake/ty/prompt/block_paginator.rb +59 -0
  209. data/lib/wibrfake/ty/prompt/choice.rb +147 -0
  210. data/lib/wibrfake/ty/prompt/choices.rb +129 -0
  211. data/lib/wibrfake/ty/prompt/confirm_question.rb +158 -0
  212. data/lib/wibrfake/ty/prompt/const.rb +17 -0
  213. data/lib/wibrfake/ty/prompt/converter_dsl.rb +21 -0
  214. data/lib/wibrfake/ty/prompt/converter_registry.rb +69 -0
  215. data/lib/wibrfake/ty/prompt/converters.rb +182 -0
  216. data/lib/wibrfake/ty/prompt/distance.rb +49 -0
  217. data/lib/wibrfake/ty/prompt/enum_list.rb +433 -0
  218. data/lib/wibrfake/ty/prompt/errors.rb +31 -0
  219. data/lib/wibrfake/ty/prompt/evaluator.rb +29 -0
  220. data/lib/wibrfake/ty/prompt/expander.rb +321 -0
  221. data/lib/wibrfake/ty/prompt/keypress.rb +98 -0
  222. data/lib/wibrfake/ty/prompt/list.rb +589 -0
  223. data/lib/wibrfake/ty/prompt/mask_question.rb +96 -0
  224. data/lib/wibrfake/ty/prompt/multi_list.rb +224 -0
  225. data/lib/wibrfake/ty/prompt/multiline.rb +72 -0
  226. data/lib/wibrfake/ty/prompt/paginator.rb +111 -0
  227. data/lib/wibrfake/ty/prompt/question/checks.rb +105 -0
  228. data/lib/wibrfake/ty/prompt/question/modifier.rb +96 -0
  229. data/lib/wibrfake/ty/prompt/question/validation.rb +72 -0
  230. data/lib/wibrfake/ty/prompt/question.rb +391 -0
  231. data/lib/wibrfake/ty/prompt/result.rb +42 -0
  232. data/lib/wibrfake/ty/prompt/selected_choices.rb +77 -0
  233. data/lib/wibrfake/ty/prompt/slider.rb +286 -0
  234. data/lib/wibrfake/ty/prompt/statement.rb +55 -0
  235. data/lib/wibrfake/ty/prompt/suggestion.rb +113 -0
  236. data/lib/wibrfake/ty/prompt/symbols.rb +89 -0
  237. data/lib/wibrfake/ty/prompt/test.rb +36 -0
  238. data/lib/wibrfake/ty/prompt/timer.rb +75 -0
  239. data/lib/wibrfake/ty/prompt/utils.rb +42 -0
  240. data/lib/wibrfake/ty/prompt/version.rb +7 -0
  241. data/lib/wibrfake/ty/prompt.rb +589 -0
  242. data/lib/wibrfake/version.rb +5 -0
  243. data/lib/wibrfake/wibrfake_cli.rb +683 -0
  244. data/log/.keep +0 -0
  245. data/pruebas/daemon1.pid +1 -0
  246. data/pruebas/daemon2.pid +1 -0
  247. data/pruebas/daemons.rb +44 -0
  248. data/public/404.html +67 -0
  249. data/public/406-unsupported-browser.html +66 -0
  250. data/public/422.html +67 -0
  251. data/public/500.html +66 -0
  252. data/public/icon.png +0 -0
  253. data/public/icon.svg +3 -0
  254. data/public/robots.txt +1 -0
  255. data/storage/.keep +0 -0
  256. data/test/application_system_test_case.rb +5 -0
  257. data/test/channels/application_cable/connection_test.rb +13 -0
  258. data/test/controllers/.keep +0 -0
  259. data/test/controllers/sessions_controller_test.rb +7 -0
  260. data/test/fixtures/files/.keep +0 -0
  261. data/test/fixtures/users.yml +11 -0
  262. data/test/helpers/.keep +0 -0
  263. data/test/integration/.keep +0 -0
  264. data/test/mailers/.keep +0 -0
  265. data/test/models/.keep +0 -0
  266. data/test/models/user_test.rb +7 -0
  267. data/test/system/.keep +0 -0
  268. data/test/test_helper.rb +15 -0
  269. data/tmp/.keep +0 -0
  270. data/tmp/pids/.keep +0 -0
  271. data/tmp/storage/.keep +0 -0
  272. data/vendor/.keep +0 -0
  273. data/vendor/javascript/.keep +0 -0
  274. data/wibrfake.gemspec +49 -0
  275. 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