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,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