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,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "const"
4
+ require_relative "converter_dsl"
5
+
6
+ module TTY
7
+ class Prompt
8
+ module Converters
9
+ extend ConverterDSL
10
+
11
+ TRUE_VALUES = /^(t(rue)?|y(es)?|on|1)$/i.freeze
12
+ FALSE_VALUES = /^(f(alse)?|n(o)?|off|0)$/i.freeze
13
+
14
+ SINGLE_DIGIT_MATCHER = /^(?<digit>\-?\d+(\.\d+)?)$/.freeze
15
+ DIGIT_MATCHER = /^(?<open>-?\d+(\.\d+)?)
16
+ \s*(?<sep>(\.\s*){2,3}|-|,)\s*
17
+ (?<close>-?\d+(\.\d+)?)$
18
+ /x.freeze
19
+ LETTER_MATCHER = /^(?<open>\w)
20
+ \s*(?<sep>(\.\s*){2,3}|-|,)\s*
21
+ (?<close>\w)$
22
+ /x.freeze
23
+
24
+ converter(:boolean, :bool) do |input|
25
+ case input.to_s
26
+ when TRUE_VALUES then true
27
+ when FALSE_VALUES then false
28
+ else Const::Undefined
29
+ end
30
+ end
31
+
32
+ converter(:string, :str) do |input|
33
+ String(input).chomp
34
+ end
35
+
36
+ converter(:symbol, :sym) do |input|
37
+ input.to_sym
38
+ end
39
+
40
+ converter(:char) do |input|
41
+ String(input).chars.to_a[0]
42
+ end
43
+
44
+ converter(:date) do |input|
45
+ begin
46
+ require "date" unless defined?(::Date)
47
+ ::Date.parse(input)
48
+ rescue ArgumentError
49
+ Const::Undefined
50
+ end
51
+ end
52
+
53
+ converter(:datetime) do |input|
54
+ begin
55
+ require "date" unless defined?(::Date)
56
+ ::DateTime.parse(input.to_s)
57
+ rescue ArgumentError
58
+ Const::Undefined
59
+ end
60
+ end
61
+
62
+ converter(:time) do |input|
63
+ begin
64
+ require "time"
65
+ ::Time.parse(input.to_s)
66
+ rescue ArgumentError
67
+ Const::Undefined
68
+ end
69
+ end
70
+
71
+ converter(:integer, :int) do |input|
72
+ begin
73
+ Integer(input)
74
+ rescue ArgumentError
75
+ Const::Undefined
76
+ end
77
+ end
78
+
79
+ converter(:float) do |input|
80
+ begin
81
+ Float(input)
82
+ rescue TypeError, ArgumentError
83
+ Const::Undefined
84
+ end
85
+ end
86
+
87
+ # Convert string number to integer or float
88
+ #
89
+ # @return [Integer,Float,Const::Undefined]
90
+ #
91
+ # @api private
92
+ def cast_to_num(num)
93
+ ([convert(:int, num), convert(:float, num)] - [Const::Undefined]).first ||
94
+ Const::Undefined
95
+ end
96
+ module_function :cast_to_num
97
+
98
+ converter(:range) do |input|
99
+ if input.is_a?(::Range)
100
+ input
101
+ elsif match = input.to_s.match(SINGLE_DIGIT_MATCHER)
102
+ digit = cast_to_num(match[:digit])
103
+ ::Range.new(digit, digit)
104
+ elsif match = input.to_s.match(DIGIT_MATCHER)
105
+ open = cast_to_num(match[:open])
106
+ close = cast_to_num(match[:close])
107
+ ::Range.new(open, close, match[:sep].gsub(/\s*/, "") == "...")
108
+ elsif match = input.to_s.match(LETTER_MATCHER)
109
+ ::Range.new(match[:open], match[:close],
110
+ match[:sep].gsub(/\s*/, "") == "...")
111
+ else Const::Undefined
112
+ end
113
+ end
114
+
115
+ converter(:regexp) do |input|
116
+ Regexp.new(input)
117
+ end
118
+
119
+ converter(:filepath, :file) do |input|
120
+ ::File.expand_path(input)
121
+ end
122
+
123
+ converter(:pathname, :path) do |input|
124
+ require "pathname" unless defined?(::Pathname)
125
+ ::Pathname.new(input)
126
+ end
127
+
128
+ converter(:uri) do |input|
129
+ require "uri" unless defined?(::URI)
130
+ ::URI.parse(input)
131
+ end
132
+
133
+ converter(:list, :array) do |val|
134
+ (val.respond_to?(:to_a) ? val : val.split(/(?<!\\),/))
135
+ .map { |v| v.strip.gsub(/\\,/, ",") }
136
+ .reject(&:empty?)
137
+ end
138
+
139
+ converter(:hash, :map) do |val|
140
+ values = val.respond_to?(:to_a) ? val : val.split(/[& ]/)
141
+ values.each_with_object({}) do |pair, pairs|
142
+ key, value = pair.split(/[=:]/, 2)
143
+ if (current = pairs[key.to_sym])
144
+ pairs[key.to_sym] = Array(current) << value
145
+ else
146
+ pairs[key.to_sym] = value
147
+ end
148
+ pairs
149
+ end
150
+ end
151
+
152
+ converter_registry.keys.each do |type|
153
+ next if type =~ /list|array|map|hash/
154
+
155
+ [:"#{type}_list", :"#{type}_array", :"#{type}s"].each do |new_type|
156
+ converter(new_type) do |val|
157
+ converter_registry[:array].(val).map do |obj|
158
+ converter_registry[type].(obj)
159
+ end
160
+ end
161
+ end
162
+
163
+ [:"#{type}_map", :"#{type}_hash"].each do |new_type|
164
+ converter(new_type) do |val|
165
+ converter_registry[:hash].(val).each_with_object({}) do |(k, v), h|
166
+ h[k] = converter_registry[type].(v)
167
+ end
168
+ end
169
+ end
170
+
171
+ [:"string_#{type}_map", :"str_#{type}_map",
172
+ :"string_#{type}_hash", :"str_#{type}_hash"].each do |new_type|
173
+ converter(new_type) do |val|
174
+ converter_registry[:hash].(val).each_with_object({}) do |(k, v), h|
175
+ h[converter_registry[:string].(k)] = converter_registry[type].(v)
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end # Converters
181
+ end # Prompt
182
+ end # TTY
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTY
4
+ class Prompt
5
+ # A class responsible for string comparison
6
+ class Distance
7
+ # Calculate the optimal string alignment distance
8
+ #
9
+ # @api public
10
+ def distance(first, second)
11
+ distances = []
12
+ rows = first.to_s.length
13
+ cols = second.to_s.length
14
+
15
+ 0.upto(rows) do |index|
16
+ distances << [index] + [0] * cols
17
+ end
18
+ distances[0] = 0.upto(cols).to_a
19
+
20
+ 1.upto(rows) do |first_index|
21
+ 1.upto(cols) do |second_index|
22
+ first_char = first[first_index - 1]
23
+ second_char = second[second_index - 1]
24
+ cost = first_char == second_char ? 0 : 1
25
+
26
+ distances[first_index][second_index] = [
27
+ distances[first_index - 1][second_index], # deletion
28
+ distances[first_index][second_index - 1], # insertion
29
+ distances[first_index - 1][second_index - 1] # substitution
30
+ ].min + cost
31
+
32
+ if first_index > 1 && second_index > 1
33
+ first_previous_char = first[first_index - 2]
34
+ second_previous_char = second[second_index - 2]
35
+ if first_char == second_previous_char && second_char == first_previous_char
36
+ distances[first_index][second_index] = [
37
+ distances[first_index][second_index],
38
+ distances[first_index - 2][second_index - 2] + 1 # transposition
39
+ ].min
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ distances[rows][cols]
46
+ end
47
+ end # Distance
48
+ end # Prompt
49
+ end # TTY
@@ -0,0 +1,433 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "English"
4
+
5
+ require_relative "choices"
6
+ require_relative "block_paginator"
7
+ require_relative "paginator"
8
+
9
+ module TTY
10
+ class Prompt
11
+ # A class reponsible for rendering enumerated list menu.
12
+ # Used by {Prompt} to display static choice menu.
13
+ #
14
+ # @api private
15
+ class EnumList
16
+ PAGE_HELP = "(Press tab/right or left to reveal more choices)"
17
+
18
+ # Checks type of default parameter to be integer
19
+ INTEGER_MATCHER = /\A[-+]?\d+\Z/.freeze
20
+
21
+ # Create instance of EnumList menu.
22
+ #
23
+ # @api public
24
+ def initialize(prompt, **options)
25
+ @prompt = prompt
26
+ @prefix = options.fetch(:prefix) { @prompt.prefix }
27
+ @enum = options.fetch(:enum) { ")" }
28
+ @default = options.fetch(:default, nil)
29
+ @active_color = options.fetch(:active_color) { @prompt.active_color }
30
+ @help_color = options.fetch(:help_color) { @prompt.help_color }
31
+ @error_color = options.fetch(:error_color) { @prompt.error_color }
32
+ @cycle = options.fetch(:cycle, false)
33
+ @quiet = options.fetch(:quiet) { @prompt.quiet }
34
+ @symbols = @prompt.symbols.merge(options.fetch(:symbols, {}))
35
+ @input = nil
36
+ @done = false
37
+ @first_render = true
38
+ @failure = false
39
+ @active = @default
40
+ @choices = Choices.new
41
+ @per_page = options[:per_page]
42
+ @page_help = options[:page_help] || PAGE_HELP
43
+ @paginator = BlockPaginator.new
44
+ @page_active = @default
45
+ end
46
+
47
+ # Change symbols used by this prompt
48
+ #
49
+ # @param [Hash] new_symbols
50
+ # the new symbols to use
51
+ #
52
+ # @api public
53
+ def symbols(new_symbols = (not_set = true))
54
+ return @symbols if not_set
55
+
56
+ @symbols.merge!(new_symbols)
57
+ end
58
+
59
+ # Set default option selected
60
+ #
61
+ # @api public
62
+ def default(default)
63
+ @default = default
64
+ end
65
+
66
+ # Check if default value is set
67
+ #
68
+ # @return [Boolean]
69
+ #
70
+ # @api public
71
+ def default?
72
+ !@default.to_s.empty?
73
+ end
74
+
75
+ # Set number of items per page
76
+ #
77
+ # @api public
78
+ def per_page(value)
79
+ @per_page = value
80
+ end
81
+
82
+ def page_size
83
+ (@per_page || Paginator::DEFAULT_PAGE_SIZE)
84
+ end
85
+
86
+ # Check if list is paginated
87
+ #
88
+ # @return [Boolean]
89
+ #
90
+ # @api private
91
+ def paginated?
92
+ @choices.size > page_size
93
+ end
94
+
95
+ # @param [String] text
96
+ # the help text to display per page
97
+ # @api pbulic
98
+ def page_help(text)
99
+ @page_help = text
100
+ end
101
+
102
+ # Set selecting active index using number pad
103
+ #
104
+ # @api public
105
+ def enum(value)
106
+ @enum = value
107
+ end
108
+
109
+ # Set quiet mode
110
+ #
111
+ # @api public
112
+ def quiet(value)
113
+ @quiet = value
114
+ end
115
+
116
+ # Add a single choice
117
+ #
118
+ # @api public
119
+ def choice(*value, &block)
120
+ if block
121
+ @choices << (value << block)
122
+ else
123
+ @choices << value
124
+ end
125
+ end
126
+
127
+ # Add multiple choices
128
+ #
129
+ # @param [Array[Object]] values
130
+ # the values to add as choices
131
+ #
132
+ # @api public
133
+ def choices(values = (not_set = true))
134
+ if not_set
135
+ @choices
136
+ else
137
+ values.each { |val| @choices << val }
138
+ end
139
+ end
140
+
141
+ # Call the list menu by passing question and choices
142
+ #
143
+ # @param [String] question
144
+ #
145
+ # @param
146
+ # @api public
147
+ def call(question, possibilities, &block)
148
+ choices(possibilities)
149
+ @question = question
150
+ block[self] if block
151
+ setup_defaults
152
+ @prompt.subscribe(self) do
153
+ render
154
+ end
155
+ end
156
+
157
+ def keypress(event)
158
+ if %i[backspace delete].include?(event.key.name)
159
+ return if @input.empty?
160
+
161
+ @input.chop!
162
+ mark_choice_as_active
163
+ elsif event.value =~ /^\d+$/
164
+ @input += event.value
165
+ mark_choice_as_active
166
+ end
167
+ end
168
+
169
+ def keyreturn(*)
170
+ @failure = false
171
+ num = @input.to_i
172
+ choice_disabled = choices[num - 1] && choices[num - 1].disabled?
173
+ choice_in_range = num > 0 && num <= @choices.size
174
+
175
+ if choice_in_range && !choice_disabled || @input.empty?
176
+ @done = true
177
+ else
178
+ @input = ""
179
+ @failure = true
180
+ end
181
+ end
182
+ alias keyenter keyreturn
183
+
184
+ def keyright(*)
185
+ if (@page_active + page_size) <= @choices.size
186
+ @page_active += page_size
187
+ elsif @cycle
188
+ @page_active = 1
189
+ end
190
+ end
191
+ alias keytab keyright
192
+
193
+ def keyleft(*)
194
+ if (@page_active - page_size) >= 0
195
+ @page_active -= page_size
196
+ elsif @cycle
197
+ @page_active = @choices.size - 1
198
+ end
199
+ end
200
+
201
+ private
202
+
203
+ # Find active choice or set to default
204
+ #
205
+ # @return [nil]
206
+ #
207
+ # @api private
208
+ def mark_choice_as_active
209
+ next_active = @choices[@input.to_i - 1]
210
+
211
+ if next_active && next_active.disabled?
212
+ # noop
213
+ elsif (@input.to_i > 0) && next_active
214
+ @active = @input.to_i
215
+ else
216
+ @active = @default
217
+ end
218
+ @page_active = @active
219
+ end
220
+
221
+ # Validate default indexes to be within range
222
+ #
223
+ # @api private
224
+ def validate_defaults
225
+ msg = if @default.nil? || @default.to_s.empty?
226
+ "default index must be an integer in range (1 - #{choices.size})"
227
+ elsif @default.to_s !~ INTEGER_MATCHER
228
+ validate_default_name
229
+ elsif @default < 1 || @default > @choices.size
230
+ "default index #{@default} out of range (1 - #{@choices.size})"
231
+ elsif choices[@default - 1] && choices[@default - 1].disabled?
232
+ "default index #{@default} matches disabled choice item"
233
+ end
234
+
235
+ raise(ConfigurationError, msg) if msg
236
+ end
237
+
238
+ # Validate default choice name
239
+ #
240
+ # @return [String]
241
+ #
242
+ # @api private
243
+ def validate_default_name
244
+ default_choice = choices.find_by(:name, @default.to_s)
245
+ if default_choice.nil?
246
+ "no choice found for the default name: #{@default.inspect}"
247
+ elsif default_choice.disabled?
248
+ "default name #{@default.inspect} matches disabled choice"
249
+ end
250
+ end
251
+
252
+ # Setup default option and active selection
253
+ #
254
+ # @api private
255
+ def setup_defaults
256
+ if @default.to_s.empty?
257
+ @default = (0..choices.length).find { |i| !choices[i].disabled? } + 1
258
+ end
259
+ validate_defaults
260
+ if default_choice = choices.find_by(:name, @default)
261
+ @default = choices.index(default_choice) + 1
262
+ end
263
+ mark_choice_as_active
264
+ end
265
+
266
+ # Render a selection list.
267
+ #
268
+ # By default the result is printed out.
269
+ #
270
+ # @return [Object] value
271
+ # return the selected value
272
+ #
273
+ # @api private
274
+ def render
275
+ @input = ""
276
+ until @done
277
+ question = render_question
278
+ @prompt.print(question)
279
+ @prompt.print(render_error) if @failure
280
+ if paginated? && !@done
281
+ @prompt.print(render_page_help)
282
+ end
283
+ @prompt.read_keypress
284
+ question_lines = question.split($INPUT_RECORD_SEPARATOR, -1)
285
+ @prompt.print(refresh(question_lines_count(question_lines)))
286
+ end
287
+ @prompt.print(render_question) unless @quiet
288
+ answer
289
+ end
290
+
291
+ # Count how many screen lines the question spans
292
+ #
293
+ # @return [Integer]
294
+ #
295
+ # @api private
296
+ def question_lines_count(question_lines)
297
+ question_lines.reduce(0) do |acc, line|
298
+ acc + @prompt.count_screen_lines(line)
299
+ end
300
+ end
301
+
302
+ # Find value for the choice selected
303
+ #
304
+ # @return [nil, Object]
305
+ #
306
+ # @api private
307
+ def answer
308
+ @choices[@active - 1].value
309
+ end
310
+
311
+ # Determine area of the screen to clear
312
+ #
313
+ # @param [Integer] lines
314
+ # the lines to clear
315
+ #
316
+ # @return [String]
317
+ #
318
+ # @api private
319
+ def refresh(lines)
320
+ @prompt.clear_lines(lines) +
321
+ @prompt.cursor.clear_screen_down
322
+ end
323
+
324
+ # Render question with the menu options
325
+ #
326
+ # @return [String]
327
+ #
328
+ # @api private
329
+ def render_question
330
+ header = ["#{@prefix}#{@question} #{render_header}\n"]
331
+ unless @done
332
+ header << render_menu
333
+ header << render_footer
334
+ end
335
+ header.join
336
+ end
337
+
338
+ # Error message when incorrect index chosen
339
+ #
340
+ # @api private
341
+ def error_message
342
+ error = "Please enter a valid number"
343
+ "\n" + @prompt.decorate(">>", @error_color) + " " + error
344
+ end
345
+
346
+ # Render error message and return cursor to position of input
347
+ #
348
+ # @return [String]
349
+ #
350
+ # @api private
351
+ def render_error
352
+ error = error_message.dup
353
+ if !paginated?
354
+ error << @prompt.cursor.prev_line
355
+ error << @prompt.cursor.forward(render_footer.size)
356
+ end
357
+ error
358
+ end
359
+
360
+ # Render chosen option
361
+ #
362
+ # @return [String]
363
+ #
364
+ # @api private
365
+ def render_header
366
+ return "" unless @done
367
+ return "" unless @active
368
+
369
+ selected_item = @choices[@active - 1].name.to_s
370
+ @prompt.decorate(selected_item, @active_color)
371
+ end
372
+
373
+ # Render footer for the indexed menu
374
+ #
375
+ # @return [String]
376
+ #
377
+ # @api private
378
+ def render_footer
379
+ " Choose 1-#{@choices.size} [#{@default}]: #{@input}"
380
+ end
381
+
382
+ # Pagination help message
383
+ #
384
+ # @return [String]
385
+ #
386
+ # @api private
387
+ def page_help_message
388
+ return "" unless paginated?
389
+
390
+ "\n" + @prompt.decorate(@page_help, @help_color)
391
+ end
392
+
393
+ # Render page help
394
+ #
395
+ # @return [String]
396
+ #
397
+ # @api private
398
+ def render_page_help
399
+ help = page_help_message.dup
400
+ if @failure
401
+ help << @prompt.cursor.prev_line
402
+ end
403
+ help << @prompt.cursor.prev_line
404
+ help << @prompt.cursor.forward(render_footer.size)
405
+ end
406
+
407
+ # Render menu with indexed choices to select from
408
+ #
409
+ # @return [String]
410
+ #
411
+ # @api private
412
+ def render_menu
413
+ output = []
414
+
415
+ @paginator.paginate(@choices, @page_active, @per_page) do |choice, index|
416
+ num = (index + 1).to_s + @enum + " "
417
+ selected = num.to_s + choice.name.to_s
418
+ output << if index + 1 == @active && !choice.disabled?
419
+ (" " * 2) + @prompt.decorate(selected, @active_color)
420
+ elsif choice.disabled?
421
+ @prompt.decorate(@symbols[:cross], :red) + " " +
422
+ selected + " " + choice.disabled.to_s
423
+ else
424
+ (" " * 2) + selected
425
+ end
426
+ output << "\n"
427
+ end
428
+
429
+ output.join
430
+ end
431
+ end # EnumList
432
+ end # Prompt
433
+ end # TTY
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTY
4
+ class Prompt
5
+ Error = Class.new(StandardError)
6
+
7
+ # Raised when wrong parameter is used to configure prompt
8
+ ConfigurationError = Class.new(Error)
9
+
10
+ # Raised when type conversion cannot be performed
11
+ ConversionError = Class.new(Error)
12
+
13
+ # Raised when the passed in validation argument is of wrong type
14
+ ValidationCoercion = Class.new(Error)
15
+
16
+ # Raised when the required argument is not supplied
17
+ ArgumentRequired = Class.new(Error)
18
+
19
+ # Raised when the argument validation fails
20
+ ArgumentValidation = Class.new(Error)
21
+
22
+ # Raised when the argument is not expected
23
+ InvalidArgument = Class.new(Error)
24
+
25
+ # Raised when overriding already defined conversion
26
+ ConversionAlreadyDefined = Class.new(Error)
27
+
28
+ # Raised when conversion type isn't registered
29
+ UnsupportedConversion = Class.new(Error)
30
+ end # Prompt
31
+ end # TTY