brut 0.16.0.pre.pre → 0.17.0

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 (456) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/CHANGELOG.md +9 -3
  4. data/Gemfile.lock +44 -34
  5. data/bin/docs +7 -0
  6. data/brut-css/package-lock.json +2 -2
  7. data/brut-css/package.json +1 -1
  8. data/brut-js/package-lock.json +311 -2
  9. data/brut-js/package.json +2 -1
  10. data/brut.gemspec +2 -0
  11. data/brutrb.com/jobs.md +1 -1
  12. data/docs/404.html +2 -2
  13. data/docs/adrs.html +4 -4
  14. data/docs/ai.html +4 -4
  15. data/docs/api/Brut/BackEnd/SeedData.html +2 -2
  16. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +2 -2
  17. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +2 -2
  18. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +2 -2
  19. data/docs/api/Brut/BackEnd/Sidekiq.html +2 -2
  20. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +2 -2
  21. data/docs/api/Brut/BackEnd/Validators.html +2 -2
  22. data/docs/api/Brut/BackEnd.html +2 -2
  23. data/docs/api/Brut/CLI/App.html +2 -2
  24. data/docs/api/Brut/CLI/AppRunner.html +2 -2
  25. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +2 -2
  26. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +2 -2
  27. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +3 -3
  28. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +2 -2
  29. data/docs/api/Brut/CLI/Apps/BuildAssets.html +2 -2
  30. data/docs/api/Brut/CLI/Apps/DB/Create.html +2 -2
  31. data/docs/api/Brut/CLI/Apps/DB/Drop.html +2 -2
  32. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +2 -2
  33. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +2 -2
  34. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +2 -2
  35. data/docs/api/Brut/CLI/Apps/DB/Seed.html +2 -2
  36. data/docs/api/Brut/CLI/Apps/DB/Status.html +2 -2
  37. data/docs/api/Brut/CLI/Apps/DB.html +2 -2
  38. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +2 -2
  39. data/docs/api/Brut/CLI/Apps/DeployBase.html +2 -2
  40. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +8 -6
  41. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +2 -2
  42. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +2 -2
  43. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +2 -2
  44. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +2 -2
  45. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +2 -2
  46. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +2 -2
  47. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +2 -2
  48. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +2 -2
  49. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +2 -2
  50. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +2 -2
  51. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +2 -2
  52. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +2 -2
  53. data/docs/api/Brut/CLI/Apps/Scaffold.html +2 -2
  54. data/docs/api/Brut/CLI/Apps/Test/Audit.html +15 -7
  55. data/docs/api/Brut/CLI/Apps/Test/E2e.html +2 -2
  56. data/docs/api/Brut/CLI/Apps/Test/JS.html +2 -2
  57. data/docs/api/Brut/CLI/Apps/Test/Run.html +2 -2
  58. data/docs/api/Brut/CLI/Apps/Test.html +2 -2
  59. data/docs/api/Brut/CLI/Apps.html +2 -2
  60. data/docs/api/Brut/CLI/Command.html +2 -2
  61. data/docs/api/Brut/CLI/Error.html +2 -2
  62. data/docs/api/Brut/CLI/ExecutionResults/Result.html +2 -2
  63. data/docs/api/Brut/CLI/ExecutionResults.html +2 -2
  64. data/docs/api/Brut/CLI/Executor.html +2 -2
  65. data/docs/api/Brut/CLI/InvalidOption.html +2 -2
  66. data/docs/api/Brut/CLI/Options.html +2 -2
  67. data/docs/api/Brut/CLI/Output.html +2 -2
  68. data/docs/api/Brut/CLI/SystemExecError.html +2 -2
  69. data/docs/api/Brut/CLI.html +2 -2
  70. data/docs/api/Brut/FactoryBot.html +2 -2
  71. data/docs/api/Brut/Framework/App.html +2 -2
  72. data/docs/api/Brut/Framework/Config.html +2 -2
  73. data/docs/api/Brut/Framework/Container.html +11 -7
  74. data/docs/api/Brut/Framework/Error.html +2 -2
  75. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +2 -2
  76. data/docs/api/Brut/Framework/Errors/Bug.html +2 -2
  77. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +2 -2
  78. data/docs/api/Brut/Framework/Errors/MissingParameter.html +2 -2
  79. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +2 -2
  80. data/docs/api/Brut/Framework/Errors/NotFound.html +2 -2
  81. data/docs/api/Brut/Framework/Errors/NotImplemented.html +2 -2
  82. data/docs/api/Brut/Framework/Errors.html +2 -2
  83. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +2 -2
  84. data/docs/api/Brut/Framework/MCP.html +3 -3
  85. data/docs/api/Brut/Framework/ProjectEnvironment.html +2 -2
  86. data/docs/api/Brut/Framework.html +2 -2
  87. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +2 -2
  88. data/docs/api/Brut/FrontEnd/Component/Helpers.html +2 -2
  89. data/docs/api/Brut/FrontEnd/Component.html +2 -2
  90. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +2 -2
  91. data/docs/api/Brut/FrontEnd/Components/FormTag.html +2 -2
  92. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +2 -2
  93. data/docs/api/Brut/FrontEnd/Components/Input.html +2 -2
  94. data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +2 -2
  95. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +2 -2
  96. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +2 -2
  97. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +2 -2
  98. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +2 -2
  99. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +2 -2
  100. data/docs/api/Brut/FrontEnd/Components/Inputs.html +2 -2
  101. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +2 -2
  102. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +2 -2
  103. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +2 -2
  104. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +2 -2
  105. data/docs/api/Brut/FrontEnd/Components.html +2 -2
  106. data/docs/api/Brut/FrontEnd/CsrfProtector.html +2 -2
  107. data/docs/api/Brut/FrontEnd/Download.html +2 -2
  108. data/docs/api/Brut/FrontEnd/Flash.html +2 -2
  109. data/docs/api/Brut/FrontEnd/Form.html +2 -2
  110. data/docs/api/Brut/FrontEnd/Forms/Button.html +2 -2
  111. data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +2 -2
  112. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +2 -2
  113. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +2 -2
  114. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +2 -2
  115. data/docs/api/Brut/FrontEnd/Forms/Input.html +2 -2
  116. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +2 -2
  117. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +2 -2
  118. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +2 -2
  119. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +2 -2
  120. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +2 -2
  121. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +2 -2
  122. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +2 -2
  123. data/docs/api/Brut/FrontEnd/Forms.html +2 -2
  124. data/docs/api/Brut/FrontEnd/GenericResponse.html +2 -2
  125. data/docs/api/Brut/FrontEnd/Handler.html +2 -2
  126. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +2 -2
  127. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +2 -2
  128. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +2 -2
  129. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +2 -2
  130. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +2 -2
  131. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +2 -2
  132. data/docs/api/Brut/FrontEnd/Handlers.html +2 -2
  133. data/docs/api/Brut/FrontEnd/HandlingResults.html +2 -2
  134. data/docs/api/Brut/FrontEnd/HttpMethod.html +2 -2
  135. data/docs/api/Brut/FrontEnd/HttpStatus.html +2 -2
  136. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +2 -2
  137. data/docs/api/Brut/FrontEnd/Layout.html +2 -2
  138. data/docs/api/Brut/FrontEnd/Middleware.html +2 -2
  139. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +2 -2
  140. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +2 -2
  141. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +2 -2
  142. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +2 -2
  143. data/docs/api/Brut/FrontEnd/Middlewares.html +2 -2
  144. data/docs/api/Brut/FrontEnd/Page.html +2 -2
  145. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +2 -2
  146. data/docs/api/Brut/FrontEnd/Pages.html +2 -2
  147. data/docs/api/Brut/FrontEnd/RequestContext.html +2 -2
  148. data/docs/api/Brut/FrontEnd/RouteHook.html +2 -2
  149. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +2 -2
  150. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +2 -2
  151. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +2 -2
  152. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +2 -2
  153. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +2 -2
  154. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +2 -2
  155. data/docs/api/Brut/FrontEnd/RouteHooks.html +2 -2
  156. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +2 -2
  157. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +2 -2
  158. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +2 -2
  159. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +2 -2
  160. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +2 -2
  161. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +2 -2
  162. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +2 -2
  163. data/docs/api/Brut/FrontEnd/Routing/Route.html +2 -2
  164. data/docs/api/Brut/FrontEnd/Routing.html +2 -2
  165. data/docs/api/Brut/FrontEnd/Session.html +2 -2
  166. data/docs/api/Brut/FrontEnd.html +2 -2
  167. data/docs/api/Brut/I18n/BaseMethods.html +2 -2
  168. data/docs/api/Brut/I18n/ForBackEnd.html +2 -2
  169. data/docs/api/Brut/I18n/ForCLI.html +2 -2
  170. data/docs/api/Brut/I18n/ForHTML.html +2 -2
  171. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +2 -2
  172. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +2 -2
  173. data/docs/api/Brut/I18n.html +2 -2
  174. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +2 -2
  175. data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +2 -2
  176. data/docs/api/Brut/Instrumentation/Methods.html +2 -2
  177. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +2 -2
  178. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +2 -2
  179. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +2 -2
  180. data/docs/api/Brut/Instrumentation.html +2 -2
  181. data/docs/api/Brut/RubocopConfig.html +2 -2
  182. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +2 -2
  183. data/docs/api/Brut/SinatraHelpers.html +2 -2
  184. data/docs/api/Brut/SpecSupport/ClockSupport.html +2 -2
  185. data/docs/api/Brut/SpecSupport/ComponentSupport.html +2 -2
  186. data/docs/api/Brut/SpecSupport/E2ETestServer.html +2 -2
  187. data/docs/api/Brut/SpecSupport/E2eSupport.html +2 -2
  188. data/docs/api/Brut/SpecSupport/EnhancedNode.html +2 -2
  189. data/docs/api/Brut/SpecSupport/FlashSupport.html +2 -2
  190. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +2 -2
  191. data/docs/api/Brut/SpecSupport/GeneralSupport.html +2 -2
  192. data/docs/api/Brut/SpecSupport/HandlerSupport.html +2 -2
  193. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +2 -2
  194. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +2 -2
  195. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +2 -2
  196. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +2 -2
  197. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +2 -2
  198. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +2 -2
  199. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +2 -2
  200. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +2 -2
  201. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +2 -2
  202. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +2 -2
  203. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +2 -2
  204. data/docs/api/Brut/SpecSupport/Matchers.html +2 -2
  205. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +2 -2
  206. data/docs/api/Brut/SpecSupport/RSpecSetup.html +2 -2
  207. data/docs/api/Brut/SpecSupport/SessionSupport.html +2 -2
  208. data/docs/api/Brut/SpecSupport.html +2 -2
  209. data/docs/api/Brut/TUI/AnsiEscapeCode/Mod.html +409 -0
  210. data/docs/api/Brut/TUI/AnsiEscapeCode.html +426 -0
  211. data/docs/api/Brut/TUI/EventLoop/Deque.html +531 -0
  212. data/docs/api/Brut/TUI/EventLoop.html +676 -0
  213. data/docs/api/Brut/TUI/Events/BaseEvent.html +449 -0
  214. data/docs/api/Brut/TUI/Events/EventBus.html +485 -0
  215. data/docs/api/Brut/TUI/Events/EventLoopStarted.html +211 -0
  216. data/docs/api/Brut/TUI/Events/Exception.html +523 -0
  217. data/docs/api/Brut/TUI/Events/Tick.html +294 -0
  218. data/docs/api/Brut/TUI/Events.html +131 -0
  219. data/docs/api/Brut/TUI/MarkupString.html +537 -0
  220. data/docs/api/Brut/TUI/Script/BlockStep.html +300 -0
  221. data/docs/api/Brut/TUI/Script/Events/CommandExecutionFailed.html +252 -0
  222. data/docs/api/Brut/TUI/Script/Events/CommandExecutionSucceeded.html +163 -0
  223. data/docs/api/Brut/TUI/Script/Events/CommandStdErr.html +163 -0
  224. data/docs/api/Brut/TUI/Script/Events/CommandStdOut.html +300 -0
  225. data/docs/api/Brut/TUI/Script/Events/ExecutingCommand.html +298 -0
  226. data/docs/api/Brut/TUI/Script/Events/Message.html +345 -0
  227. data/docs/api/Brut/TUI/Script/Events/PhaseCompleted.html +229 -0
  228. data/docs/api/Brut/TUI/Script/Events/PhaseStarted.html +350 -0
  229. data/docs/api/Brut/TUI/Script/Events/ScriptCompleted.html +282 -0
  230. data/docs/api/Brut/TUI/Script/Events/ScriptStarted.html +343 -0
  231. data/docs/api/Brut/TUI/Script/Events/StepCompleted.html +163 -0
  232. data/docs/api/Brut/TUI/Script/Events/StepStarted.html +346 -0
  233. data/docs/api/Brut/TUI/Script/Events.html +115 -0
  234. data/docs/api/Brut/TUI/Script/ExecStep/ProcessStatusFailed.html +210 -0
  235. data/docs/api/Brut/TUI/Script/ExecStep.html +493 -0
  236. data/docs/api/Brut/TUI/Script/LoggingSubscriber.html +914 -0
  237. data/docs/api/Brut/TUI/Script/PutsSubscriber.html +783 -0
  238. data/docs/api/Brut/TUI/Script/Step.html +313 -0
  239. data/docs/api/Brut/TUI/Script.html +1250 -0
  240. data/docs/api/Brut/TUI/Terminal.html +593 -0
  241. data/docs/api/Brut/TUI/TerminalTheme.html +1403 -0
  242. data/docs/api/Brut/TUI/Themes/Dark.html +706 -0
  243. data/docs/api/Brut/TUI/Themes/Light.html +804 -0
  244. data/docs/api/Brut/TUI/Themes/None.html +218 -0
  245. data/docs/api/Brut/TUI/Themes.html +115 -0
  246. data/docs/api/Brut/TUI.html +129 -0
  247. data/docs/api/Brut.html +4 -4
  248. data/docs/api/Clock.html +2 -2
  249. data/docs/api/ModuleName.html +2 -2
  250. data/docs/api/RichString.html +2 -2
  251. data/docs/api/SemanticLogger/Appender/Async.html +2 -2
  252. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +2 -2
  253. data/docs/api/Sequel/Extensions/BrutMigrations.html +2 -2
  254. data/docs/api/Sequel/Extensions.html +2 -2
  255. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +2 -2
  256. data/docs/api/Sequel/Plugins/CreatedAt.html +2 -2
  257. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +2 -2
  258. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +2 -2
  259. data/docs/api/Sequel/Plugins/ExternalId.html +2 -2
  260. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +2 -2
  261. data/docs/api/Sequel/Plugins/FindBang.html +2 -2
  262. data/docs/api/Sequel/Plugins.html +2 -2
  263. data/docs/api/Sequel.html +2 -2
  264. data/docs/api/_index.html +247 -2
  265. data/docs/api/class_list.html +1 -1
  266. data/docs/api/file.README.html +2 -2
  267. data/docs/api/index.html +2 -2
  268. data/docs/api/method_list.html +1551 -431
  269. data/docs/api/top-level-namespace.html +2 -2
  270. data/docs/assets/{app.Dm7v_ouO.js → app.B8jAEB7R.js} +1 -1
  271. data/docs/assets/chunks/@localSearchIndexroot.DJ8mocCj.js +1 -0
  272. data/docs/assets/chunks/{VPLocalSearchBox.DQK6jQou.js → VPLocalSearchBox.gF-Po_fz.js} +1 -1
  273. data/docs/assets/chunks/{theme.BuExsdM9.js → theme.BjPAOJkz.js} +2 -2
  274. data/docs/assets/{components.md.Dfd3w6UW.js → components.md.Ber8UBM0.js} +3 -3
  275. data/docs/assets/{configuration.md.DTYoV2Ea.js → configuration.md.DrJ6YVoZ.js} +1 -1
  276. data/docs/assets/{deployment.md.C1u5ep0g.js → deployment.md.CHTx2eTR.js} +10 -3
  277. data/docs/assets/{deployment.md.C1u5ep0g.lean.js → deployment.md.CHTx2eTR.lean.js} +1 -1
  278. data/docs/assets/{forms.md.DEkmJUvb.js → forms.md.RK0zkhm0.js} +2 -2
  279. data/docs/assets/{forms.md.DEkmJUvb.lean.js → forms.md.RK0zkhm0.lean.js} +1 -1
  280. data/docs/assets/{getting-started.md.DO-4eoGW.js → getting-started.md.CGJ44juQ.js} +2 -2
  281. data/docs/assets/jobs.md.Bi3qb3v6.js +25 -0
  282. data/docs/assets/jobs.md.Bi3qb3v6.lean.js +1 -0
  283. data/docs/assets/recipes_dev-env-secrets.md.DC_jVY9U.js +12 -0
  284. data/docs/assets/recipes_dev-env-secrets.md.DC_jVY9U.lean.js +1 -0
  285. data/docs/assets/roadmap.md.DqC1Y7Zt.js +1 -0
  286. data/docs/assets/{roadmap.md.CJsbUmK_.lean.js → roadmap.md.DqC1Y7Zt.lean.js} +1 -1
  287. data/docs/assets/{tutorials_02-dialog.md.D2vSjDVf.js → tutorials_02-dialog.md.DE5WfCXI.js} +1 -1
  288. data/docs/assets.html +4 -4
  289. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  290. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  291. data/docs/brut-js/api/Autosubmit.html +1 -1
  292. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  293. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  294. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  295. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  296. data/docs/brut-js/api/BufferedLogger.html +1 -1
  297. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  298. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  299. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  300. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  301. data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
  302. data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
  303. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  304. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  305. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  306. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  307. data/docs/brut-js/api/Form.html +1 -1
  308. data/docs/brut-js/api/Form.js.html +1 -1
  309. data/docs/brut-js/api/I18nTranslation.html +1 -1
  310. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  311. data/docs/brut-js/api/LocaleDetection.html +1 -1
  312. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  313. data/docs/brut-js/api/Logger.html +1 -1
  314. data/docs/brut-js/api/Logger.js.html +1 -1
  315. data/docs/brut-js/api/Message.html +1 -1
  316. data/docs/brut-js/api/Message.js.html +1 -1
  317. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  318. data/docs/brut-js/api/RichString.html +1 -1
  319. data/docs/brut-js/api/RichString.js.html +1 -1
  320. data/docs/brut-js/api/Tabs.html +1 -1
  321. data/docs/brut-js/api/Tabs.js.html +1 -1
  322. data/docs/brut-js/api/Toast.html +1 -1
  323. data/docs/brut-js/api/Toast.js.html +1 -1
  324. data/docs/brut-js/api/Tracing.html +1 -1
  325. data/docs/brut-js/api/Tracing.js.html +1 -1
  326. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  327. data/docs/brut-js/api/external-Performance.html +1 -1
  328. data/docs/brut-js/api/external-Promise.html +1 -1
  329. data/docs/brut-js/api/external-ValidityState.html +1 -1
  330. data/docs/brut-js/api/external-Window.html +1 -1
  331. data/docs/brut-js/api/external-fetch.html +1 -1
  332. data/docs/brut-js/api/global.html +1 -1
  333. data/docs/brut-js/api/index.html +1 -1
  334. data/docs/brut-js/api/index.js.html +1 -1
  335. data/docs/brut-js/api/module-testing.html +1 -1
  336. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  337. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  338. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  339. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  340. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  341. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  342. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  343. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  344. data/docs/brut-js/api/testing_index.js.html +1 -1
  345. data/docs/brut-js.html +4 -4
  346. data/docs/business-logic.html +4 -4
  347. data/docs/cli.html +4 -4
  348. data/docs/components.html +8 -8
  349. data/docs/configuration.html +5 -5
  350. data/docs/css.html +4 -4
  351. data/docs/custom-element-tests.html +4 -4
  352. data/docs/database-access.html +4 -4
  353. data/docs/database-schema.html +4 -4
  354. data/docs/deployment.html +13 -6
  355. data/docs/dev-environment.html +4 -4
  356. data/docs/dir-structure.html +4 -4
  357. data/docs/doc-conventions.html +4 -4
  358. data/docs/end-to-end-tests.html +4 -4
  359. data/docs/features.html +4 -4
  360. data/docs/flash-and-session.html +4 -4
  361. data/docs/form-constraints.html +4 -4
  362. data/docs/forms.html +6 -6
  363. data/docs/getting-started.html +7 -7
  364. data/docs/handlers.html +4 -4
  365. data/docs/hashmap.json +1 -1
  366. data/docs/hooks.html +4 -4
  367. data/docs/i18n.html +4 -4
  368. data/docs/index.html +3 -3
  369. data/docs/instrumentation.html +4 -4
  370. data/docs/javascript.html +4 -4
  371. data/docs/jobs.html +29 -5
  372. data/docs/keyword-injection.html +4 -4
  373. data/docs/layouts.html +4 -4
  374. data/docs/lsp.html +4 -4
  375. data/docs/markdown-examples.html +4 -4
  376. data/docs/middleware.html +4 -4
  377. data/docs/overview.html +4 -4
  378. data/docs/pages.html +4 -4
  379. data/docs/recipes/alternate-layouts.html +5 -5
  380. data/docs/recipes/authentication.html +5 -5
  381. data/docs/recipes/custom-flash.html +5 -5
  382. data/docs/recipes/dev-env-secrets.html +40 -0
  383. data/docs/recipes/form-errors.html +5 -5
  384. data/docs/recipes/indexed-forms.html +5 -5
  385. data/docs/recipes/migrations.html +5 -5
  386. data/docs/recipes/text-field-component.html +5 -5
  387. data/docs/roadmap.html +5 -5
  388. data/docs/routes.html +4 -4
  389. data/docs/security.html +4 -4
  390. data/docs/seed-data.html +4 -4
  391. data/docs/space-time-continuum.html +4 -4
  392. data/docs/tutorial.html +4 -4
  393. data/docs/tutorials/01-intro.html +4 -4
  394. data/docs/tutorials/02-dialog.html +5 -5
  395. data/docs/unit-tests.html +4 -4
  396. data/docs/why.html +4 -4
  397. data/lib/brut/cli/apps/build_assets.rb +1 -1
  398. data/lib/brut/cli/apps/heroku_container_based_deploy.rb +1 -1
  399. data/lib/brut/cli/apps/test.rb +6 -4
  400. data/lib/brut/tui/ansi_escape_code.rb +104 -0
  401. data/lib/brut/tui/event_loop.rb +168 -0
  402. data/lib/brut/tui/events/base_event.rb +29 -0
  403. data/lib/brut/tui/events/event_bus.rb +73 -0
  404. data/lib/brut/tui/events/event_loop_started.rb +5 -0
  405. data/lib/brut/tui/events/exception.rb +24 -0
  406. data/lib/brut/tui/events/tick.rb +12 -0
  407. data/lib/brut/tui/events.rb +7 -0
  408. data/lib/brut/tui/markup_string.rb +68 -0
  409. data/lib/brut/tui/script/block_step.rb +17 -0
  410. data/lib/brut/tui/script/events/command_execution_failed.rb +4 -0
  411. data/lib/brut/tui/script/events/command_execution_succeeded.rb +3 -0
  412. data/lib/brut/tui/script/events/command_std_err.rb +3 -0
  413. data/lib/brut/tui/script/events/command_std_out.rb +13 -0
  414. data/lib/brut/tui/script/events/executing_command.rb +12 -0
  415. data/lib/brut/tui/script/events/message.rb +15 -0
  416. data/lib/brut/tui/script/events/phase_completed.rb +4 -0
  417. data/lib/brut/tui/script/events/phase_started.rb +14 -0
  418. data/lib/brut/tui/script/events/script_completed.rb +5 -0
  419. data/lib/brut/tui/script/events/script_started.rb +12 -0
  420. data/lib/brut/tui/script/events/step_completed.rb +3 -0
  421. data/lib/brut/tui/script/events/step_started.rb +12 -0
  422. data/lib/brut/tui/script/events.rb +14 -0
  423. data/lib/brut/tui/script/exec_step.rb +60 -0
  424. data/lib/brut/tui/script/logging_subscriber.rb +98 -0
  425. data/lib/brut/tui/script/puts_subscriber.rb +109 -0
  426. data/lib/brut/tui/script/step.rb +13 -0
  427. data/lib/brut/tui/script.rb +211 -0
  428. data/lib/brut/tui/terminal.rb +74 -0
  429. data/lib/brut/tui/terminal_theme.rb +140 -0
  430. data/lib/brut/tui/themes/dark.rb +14 -0
  431. data/lib/brut/tui/themes/light.rb +17 -0
  432. data/lib/brut/tui/themes/none.rb +9 -0
  433. data/lib/brut/tui/themes.rb +5 -0
  434. data/lib/brut/tui.rb +15 -0
  435. data/lib/brut/version.rb +1 -1
  436. data/lib/brut.rb +1 -0
  437. data/mkbrut/Gemfile.lock +2 -1
  438. data/mkbrut/lib/mkbrut/version.rb +1 -1
  439. data/mkbrut/templates/segments/Heroku/deploy/Dockerfile +9 -7
  440. data/mkbrut/templates/segments/Heroku/deploy/heroku_config.rb +2 -2
  441. data/specs/brut/tui/ansi_escape_code.spec.rb +30 -0
  442. data/specs/brut/tui/event_loop.spec.rb +70 -0
  443. data/specs/brut/tui/events/base_event.spec.rb +26 -0
  444. data/specs/brut/tui/events/event_bus.spec.rb +141 -0
  445. data/specs/brut/tui/events/exception.spec.rb +19 -0
  446. data/specs/brut/tui/events/test_event.rb +5 -0
  447. data/specs/spec_helper.rb +4 -0
  448. metadata +131 -21
  449. data/docs/assets/chunks/@localSearchIndexroot.BiNFswvo.js +0 -1
  450. data/docs/assets/jobs.md.Bc7Y1YpK.js +0 -1
  451. data/docs/assets/jobs.md.Bc7Y1YpK.lean.js +0 -1
  452. data/docs/assets/roadmap.md.CJsbUmK_.js +0 -1
  453. /data/docs/assets/{components.md.Dfd3w6UW.lean.js → components.md.Ber8UBM0.lean.js} +0 -0
  454. /data/docs/assets/{configuration.md.DTYoV2Ea.lean.js → configuration.md.DrJ6YVoZ.lean.js} +0 -0
  455. /data/docs/assets/{getting-started.md.DO-4eoGW.lean.js → getting-started.md.CGJ44juQ.lean.js} +0 -0
  456. /data/docs/assets/{tutorials_02-dialog.md.D2vSjDVf.lean.js → tutorials_02-dialog.md.DE5WfCXI.lean.js} +0 -0
@@ -161,7 +161,7 @@ Manages a deploy process based on using Heroku's Container Registry. See
161
161
  end
162
162
 
163
163
  names = images.map(&:first).join(" ")
164
- deploy_command = "heroku container:release #{names}"
164
+ deploy_command = "heroku container:release #{names} -a #{heroku_app_name}"
165
165
  if options.deploy?
166
166
  out.puts "Deploying images to Heroku"
167
167
  system!(deploy_command)
@@ -175,28 +175,30 @@ class Brut::CLI::Apps::Test < Brut::CLI::App
175
175
  hash[:type] = :infrastructure
176
176
  hash[:test_expected] = false
177
177
  else
178
- hash[:type] = type.to_sym
178
+ hash[:type] = pathname.relative_path_from(Brut.container.back_end_src_dir).dirname
179
179
  end
180
180
  else
181
181
  hash[:type] = :other
182
182
  hash[:test_expected] = false
183
183
  end
184
184
  hash
185
- }.compact
185
+ }.compact.sort_by { it[:type].to_s + it[:source_file].to_s }
186
186
 
187
187
  files_missing = []
188
188
  printed_header = false
189
189
  audit.each do |file_audit|
190
190
  if !file_audit[:test_file].exist?
191
- if options.audit_type.nil? || file_audit[:type] == options.audit_type
191
+ if options.type.nil? || file_audit[:type] == options.type.to_sym
192
192
  if file_audit[:test_expected]
193
193
  files_missing << file_audit[:source_file]
194
194
  if !printed_header
195
195
  out.puts "These files are missing tests:"
196
196
  out.puts ""
197
+ out.printf "%-25s %s\n","Type", "Path"
198
+ out.puts "-------------------------------------------"
197
199
  printed_header = true
198
200
  end
199
- out.puts "#{file_audit[:type].to_s.ljust(15)} - #{file_audit[:source_file]}"
201
+ out.puts "#{file_audit[:type].to_s.ljust(25)} - #{file_audit[:source_file]}"
200
202
  end
201
203
  end
202
204
  end
@@ -0,0 +1,104 @@
1
+ # Maps ANSI escape codes to logical names to make it easier to use in code.
2
+ # This is not intended to be exhaustive, but could grow over time as needed.
3
+ class Brut::TUI::AnsiEscapeCode
4
+
5
+ attr_reader :name
6
+
7
+ # Create a new AnsiEscapeCode with the given name and code.
8
+ #
9
+ # @param name [String, Symbol] The logical name of the escape code.
10
+ # This should not have spaces and generally be able to be used as a Ruby identifier.
11
+ # @param code [String] The actual ANSI escape code (without the leading `\e[` and trailing `m`).
12
+ def initialize(name, code)
13
+ @name = name.to_sym
14
+ @code = code
15
+ end
16
+
17
+ # Returns the code suitable for sending to the terminal.
18
+ def to_s = "\e[#{@code}m"
19
+
20
+ # Defines methods for each known code. This module can be included
21
+ # into other classes so you can write `self.ansi.bright_blue` (e.g.)
22
+ # Note that `Brut::TUI::AnsiEscapeCode` _extends_ this module so you
23
+ # can always do `Brut::TUI::AnsiEscapeCode.ansi.bright_blue`.
24
+ module Mod
25
+ CODES = [
26
+ Brut::TUI::AnsiEscapeCode.new("reset" , "0") ,
27
+ Brut::TUI::AnsiEscapeCode.new("bold" , "1") ,
28
+ Brut::TUI::AnsiEscapeCode.new("normal" , "22") ,
29
+ Brut::TUI::AnsiEscapeCode.new("italic" , "3") ,
30
+ Brut::TUI::AnsiEscapeCode.new("italic_off" , "23") ,
31
+ Brut::TUI::AnsiEscapeCode.new("strike" , "9") ,
32
+ Brut::TUI::AnsiEscapeCode.new("strike_off" , "29") ,
33
+ Brut::TUI::AnsiEscapeCode.new("weak" , "2") ,
34
+ Brut::TUI::AnsiEscapeCode.new("underline" , "4") ,
35
+ Brut::TUI::AnsiEscapeCode.new("underline_off" , "24") ,
36
+ Brut::TUI::AnsiEscapeCode.new("overline" , "53") ,
37
+ Brut::TUI::AnsiEscapeCode.new("overline_off" , "55") ,
38
+ Brut::TUI::AnsiEscapeCode.new("black" , "30") ,
39
+ Brut::TUI::AnsiEscapeCode.new("red" , "31") ,
40
+ Brut::TUI::AnsiEscapeCode.new("green" , "32") ,
41
+ Brut::TUI::AnsiEscapeCode.new("yellow" , "33") ,
42
+ Brut::TUI::AnsiEscapeCode.new("blue" , "34") ,
43
+ Brut::TUI::AnsiEscapeCode.new("magenta" , "35") ,
44
+ Brut::TUI::AnsiEscapeCode.new("cyan" , "36") ,
45
+ Brut::TUI::AnsiEscapeCode.new("white" , "37") ,
46
+ Brut::TUI::AnsiEscapeCode.new("bright_black" , "90") ,
47
+ Brut::TUI::AnsiEscapeCode.new("bright_red" , "91") ,
48
+ Brut::TUI::AnsiEscapeCode.new("bright_green" , "92") ,
49
+ Brut::TUI::AnsiEscapeCode.new("bright_yellow" , "93") ,
50
+ Brut::TUI::AnsiEscapeCode.new("bright_blue" , "94") ,
51
+ Brut::TUI::AnsiEscapeCode.new("bright_magenta", "95") ,
52
+ Brut::TUI::AnsiEscapeCode.new("bright_cyan" , "96") ,
53
+ Brut::TUI::AnsiEscapeCode.new("bright_white" , "97") ,
54
+ ].map { [ it.name, it ] }.to_h.freeze
55
+
56
+ # The object returned by `#ansi` that has all the dynamically-defined methods
57
+ # on it. Generally don't call this method directly.
58
+ def object
59
+ @object ||= begin
60
+ object = Object.new
61
+ CODES.each do |name, code|
62
+ object.define_singleton_method(name) do
63
+ code
64
+ end
65
+ end
66
+ object
67
+ end
68
+ end
69
+
70
+ # Method for accessing the pre-defined ANSI escape codes. This method
71
+ # works in two ways: RGB mode and predefined mode based on the arguments passed.
72
+ #
73
+ # @param name [String|Symbol|Array<Integer>] If called with no arguments, return `#object`, allowing you to call
74
+ # a dynamically-defined method based on the AnsiEscapeCode names in `CODES`.
75
+ # If called with a String or Symbol, will return the AnsiEscapeCode for that name from `CODES`.
76
+ # Otherwise, this should be exactly three integers, each between 0 and 255 that
77
+ # represent red, green, and blue, respectively. In this case, the ANSI escape code
78
+ # for an RGB value is returned.
79
+ # @return [Object|Brut::TUI::AnsiEscapeCode] The corresponding ANSI escape code object or the special `#object`.
80
+ #
81
+ # @example Using predefined codes
82
+ # Brut::TUI::AnsiEscapeCode.ansi.red
83
+ # Brut::TUI::AnsiEscapeCode.ansi.bold
84
+ # Brut::TUI::AnsiEscapeCode.ansi.underline
85
+ #
86
+ # @example Using RGB codes
87
+ # Brut::TUI::AnsiEscapeCode.ansi(87, 255, 128)
88
+ #
89
+ # @example Using code names
90
+ # Brut::TUI::AnsiEscapeCode.ansi(:bright_blue)
91
+ #
92
+ def ansi(*name)
93
+ case name
94
+ in [ r, g, b ]
95
+ Brut::TUI::AnsiEscapeCode.new("rgb(#{r},#{g},#{b})", "38;2;#{r};#{g};#{b}")
96
+ in []
97
+ object
98
+ else
99
+ CODES.fetch(name[0].to_sym)
100
+ end
101
+ end
102
+ end
103
+ extend Mod
104
+ end
@@ -0,0 +1,168 @@
1
+ # An event loop used to power any TUI, including those that just print
2
+ # out messages. This is intended to be used across multiple threads, with this running on the "main" thread
3
+ # that is allowed to write to the screen.
4
+ class Brut::TUI::EventLoop
5
+
6
+ # Create a new EventLoop.
7
+ #
8
+ # @param tick [true|false] if true, a "tick" event is fired every 50ms to allow progress spinners to animate.
9
+ def initialize(tick: true)
10
+
11
+ @queue = Deque.new
12
+
13
+ @queue << Brut::TUI::Events::EventLoopStarted.new
14
+
15
+ @event_bus = Brut::TUI::Events::EventBus.new
16
+ @tick = tick
17
+ end
18
+
19
+ # Queue an event for later processing. This is safe to do from another thread.
20
+ #
21
+ # @param event [Brut::TUI::Events::BaseEvent] the event to queue.
22
+ def <<(event)
23
+ @queue << event
24
+ end
25
+
26
+ # Subscribe to a specific event. This requires that `subscriber` implement the handler method
27
+ # exposed by {Brut::TUI::Events::BaseEvent.handler_method_name}. The method's arguments must be
28
+ # one of three forms:
29
+ #
30
+ # * no-args (or a single `:rest` arg, like `(*)`) - the method is called when the event occurs, no arguments are passed, thus no information about the event is available.
31
+ # * single required arg (e.g. `(event)`) - the event instance is passed.
32
+ # * keyword args (e.g. `(description:, command:)`) - the event is splatted via `deconstruct_keys` and passed in for any keyword arg. If
33
+ # required keyword args aren't available from the event, an exception is raised. If optional keyword args aren't available from the event,
34
+ # their default values are provided. Each event should document what keyword args are available.
35
+ #
36
+ # In all cases, if the method raises an exception, it is captured and sent as a {Brut::TUI::Events::Exception} event, potentially to be
37
+ # handled by other subscribers. See `#run` for how this interacts with the loop.
38
+ #
39
+ # @param event_class [Class] the event `subscriber` should be notified about. This should be a subclass of {Brut::TUI::Events::BaseEvent}.
40
+ # @param subscriber [Object] object to be notified about the given event.
41
+ def subscribe(event_class, subscriber)
42
+ @event_bus.subscribe(event_class, subscriber)
43
+ end
44
+
45
+ # Subscribe to all events. `subscriber` will only be notified if it
46
+ # implements an event's {Brut::TUI::Events::BaseEvent.handler_method_name} *or* if the subscriber implements
47
+ # `on_any_event`. If both are implemented, only the more specific method is called. See `#subscribe` for a description of
48
+ # how the method is invoked. If a specific method is not provided, `on_any_event` is invoked with
49
+ # the event instance. There is no keyword splatting in this case.
50
+ #
51
+ # @param subscriber [Object] object to be notified about the given event.
52
+ def subscribe_to_all(subscriber)
53
+ @event_bus.subscribe_to_all(subscriber)
54
+ end
55
+
56
+ # Start the event loop. Don't call this more than once. It will block and continue running
57
+ # until an event is received that returns true for {Brut::TUI::Events::BaseEvent#exit?}
58
+ # or {Brut::TUI::Events::BaseEvent#drain_then_exit?}.
59
+ #
60
+ # If {Brut::TUI::Events::BaseEvent#exit?} returns true, the loop is exited and any events left
61
+ # in the queue are unprocessed, essentially ignored/discarded.
62
+ #
63
+ # If {Brut::TUI::Events::BaseEvent#drain_then_exit?}
64
+ # returns true, anything currently in the queue is processed before exiting. If any subscriber adds events to the queue
65
+ # they will not be processed. If no event handler produces errors, the CLI should exit cleanly. If, however, any
66
+ # of the event handlers themselves produce errors, those errors will be handled, but the script will exit nonzero.
67
+ def run
68
+ debug "EventLoop: starting"
69
+ start = Time.now
70
+ loop do
71
+ event = @queue.pop(timeout: 0.05)
72
+ debug "EventLoop: got event #{event.class.name}\n #{event.inspect}"
73
+ if event
74
+ errors = @event_bus.notify(event)
75
+ debug "EventLoop: notified subscribers of #{event.class.name}, got #{errors.length} errors"
76
+
77
+ # future screen rendering here
78
+
79
+ handle_errors_from_notify(errors)
80
+
81
+ if event.drain_then_exit?
82
+ debug "EventLoop: exiting"
83
+ all_errors = []
84
+ @queue.size.times do
85
+ event = @queue.pop(timeout: 0.05)
86
+ if event
87
+ debug "EventLoop (exiting): got event #{event.class.name}\n #{event.inspect}"
88
+ errors = @event_bus.notify(event)
89
+ all_errors = all_errors + errors
90
+ # future screen rendering here
91
+ end
92
+ end
93
+ handle_errors_from_notify(all_errors, immediate: true)
94
+ break
95
+ elsif event.exit?
96
+ debug "EventLoop: exiting"
97
+ break
98
+ end
99
+ end
100
+ if @tick
101
+ errors = @event_bus.notify(Brut::TUI::Events::Tick.new(Time.now - start))
102
+ handle_errors_from_notify(errors)
103
+ end
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def handle_errors_from_notify(errors, immediate: false)
110
+ exit_now = immediate || errors.any? { |it| it.kind_of?(Brut::TUI::Events::Exception) && it.exit? }
111
+ if exit_now
112
+ errors.each do
113
+ $stderr.puts("FATAL Exception: #{it.exception.class}: #{it.exception.message}\n #{it.exception.backtrace.join("\n ")}")
114
+ end
115
+ exit 1
116
+ else
117
+ errors.each { @queue.unshift(Brut::TUI::Events::Exception.new(it)) }
118
+ end
119
+ end
120
+
121
+ def debug(*) = nil#$stderr.puts(*)
122
+
123
+ # @!visibility private
124
+ class Deque
125
+ def initialize
126
+ @mutex = Thread::Mutex.new
127
+ @condition_variable = Thread::ConditionVariable.new
128
+ @array = []
129
+ end
130
+
131
+ def <<(val)
132
+ @mutex.synchronize {
133
+ @array << val
134
+ @condition_variable.signal
135
+ }
136
+ end
137
+
138
+ def unshift(val)
139
+ @mutex.synchronize {
140
+ @array.unshift(val)
141
+ @condition_variable.signal
142
+ }
143
+ end
144
+
145
+ def pop(timeout:)
146
+ @mutex.synchronize {
147
+ deadline = Time.now + timeout
148
+ while @array.empty?
149
+ remaining = deadline - Time.now
150
+ if remaining <= 0
151
+ return nil
152
+ end
153
+ @condition_variable.wait(@mutex, remaining)
154
+ end
155
+ @array.shift
156
+ }
157
+ end
158
+
159
+ def empty?
160
+ @mutex.synchronize { @array.empty? }
161
+ end
162
+
163
+ def size
164
+ @mutex.synchronize { @array.size }
165
+ end
166
+ end
167
+
168
+ end
@@ -0,0 +1,29 @@
1
+ require "brut/junk_drawer"
2
+
3
+ # Base class for all events the TUI will manage. You can create custom
4
+ # events but they must subclass this one (or conform to its interface, which may change).
5
+ class Brut::TUI::Events::BaseEvent
6
+ # Returns the method name that subscribers must implement to handle this event.
7
+ # By default, this is based on the underscorized simple class name (name without module namespacing)
8
+ # suffixed with `on_`.
9
+ def self.handler_method_name
10
+ @handler_method_name ||= begin
11
+ simple_class_name = RichString.new(self.name.split("::").last)
12
+ "on_#{simple_class_name.underscorized}"
13
+ end
14
+ end
15
+
16
+ # Provides `class_name` and `handler_method_name`. Subclasses are expected to call this
17
+ # so they are included with their keys.
18
+ def deconstruct_keys(keys=nil)
19
+ { class_name: self.class.name, handler_method_name: self.class.handler_method_name }
20
+ end
21
+
22
+ # True if the reception of this event indicates the app should exit right now, potentially
23
+ # leaving un-handled events.
24
+ def exit? = false
25
+
26
+ # True if this event indicates the TUI should exit, but draining any
27
+ # outstanding events is OK first.
28
+ def drain_then_exit? = false
29
+ end
@@ -0,0 +1,73 @@
1
+ # @!visibility private
2
+ class Brut::TUI::Events::EventBus
3
+ def initialize
4
+ @subscribers = {}
5
+ end
6
+
7
+ # Notify all subscribers of the given event.
8
+ def notify(event)
9
+ handler_method_name = event.class.handler_method_name
10
+
11
+ errors = []
12
+
13
+ subscribers(event.class).each do |subscriber|
14
+ begin
15
+ subscriber.send(handler_method_name, event)
16
+ rescue => ex
17
+ errors << ex
18
+ end
19
+ end
20
+
21
+ subscribers(:all).each do |subscriber|
22
+ begin
23
+ if subscriber.respond_to?(handler_method_name)
24
+ params = subscriber.method(handler_method_name).parameters
25
+ if params.size == 0 || (params.size == 1 && params[0][0] == :rest)
26
+ subscriber.send(handler_method_name)
27
+ elsif params.size == 1 && params[0][0] == :req
28
+ subscriber.send(handler_method_name, event)
29
+ elsif params.all? { |it| it[0] == :keyreq || it[0] == :key }
30
+ param_keys = params.map { |it| it[1] }
31
+ args = event.deconstruct_keys.slice(*param_keys)
32
+ subscriber.send(handler_method_name, **args)
33
+ else
34
+ raise "#{subscriber.class}##{handler_method_name} has unsupported parameters. It must take either zero parameters, one required parameter (the event), or keyword parameters matching the event's attributes. Method's parameters: #{params.inspect}"
35
+ end
36
+ elsif subscriber.respond_to?(:on_any_event)
37
+ params = subscriber.method(:on_any_event).parameters
38
+ if params.size == 1 && (params[0][0] == :req || params[0][0] == :rest)
39
+ subscriber.on_any_event(event)
40
+ else
41
+ raise "#{subscriber.class}#on_any_event has unsupported parameters. It must take one required parameter (the event). Method's parameters: #{params.inspect}"
42
+ end
43
+ end
44
+ rescue => ex
45
+ errors << ex
46
+ end
47
+ end
48
+ errors
49
+ end
50
+
51
+ # Subscribe to all events the subscriber can handle. If the subscriber implements
52
+ # the event's handler_method_name method, it will be called when the event is fired.
53
+ # If the subscriber implements on_any_event, that method will be called for every event.
54
+ def subscribe_to_all(subscriber)
55
+ subscribers(:all) << subscriber
56
+ end
57
+
58
+ # Subscribe to a specific event class. The subscriber must implement the event's
59
+ # handler_method_name method.
60
+ def subscribe(event_class, subscriber)
61
+ if subscriber.respond_to?(event_class.handler_method_name)
62
+ subscribers(event_class) << subscriber
63
+ else
64
+ raise ArgumentError, "Subscriber #{subscriber} does not implement handler method #{event_class.handler_method_name} for event #{event_class}"
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def subscribers(event_class_or_all)
71
+ @subscribers[event_class_or_all] ||= []
72
+ end
73
+ end
@@ -0,0 +1,5 @@
1
+ # Indicates that the loop has started. In general, this is the first event
2
+ # that will be fired for any TUI.
3
+ class Brut::TUI::Events::EventLoopStarted < Brut::TUI::Events::BaseEvent
4
+ def to_s = "EventLoopStarted"
5
+ end
@@ -0,0 +1,24 @@
1
+ # Fired when an exception is caught. In general, your code should endeavor
2
+ # to catch exceptions, wrap them in this, and fir it.
3
+ #
4
+ # You can control if the app should exit by setting `fatal: true` when
5
+ # creating the event. Note that by default, all exceptions are treated
6
+ # as fatal, since you generally don't want to use them for control flow.
7
+ class Brut::TUI::Events::Exception < Brut::TUI::Events::BaseEvent
8
+ attr_reader :exception
9
+ def initialize(exception)
10
+ @exception = exception
11
+ end
12
+
13
+ # Returns true if this event is not considered fatal.
14
+ def drain_then_exit? = !exit?
15
+
16
+ # By default, all exceptions should cause an immediate exit. You may subclass this event to do
17
+ # something different, noting that `#drain_then_exit?` returns true if this returns false.
18
+ def exit? = true
19
+
20
+ # Includes `exception`, which is the exception that triggered this event.
21
+ def deconstruct_keys(keys=nil)
22
+ super.merge({ exception: @exception})
23
+ end
24
+ end
@@ -0,0 +1,12 @@
1
+ # An event that indicates time has passed.
2
+ class Brut::TUI::Events::Tick < Brut::TUI::Events::BaseEvent
3
+ def initialize(elapsed_time)
4
+ @elapsed_time = elapsed_time
5
+ end
6
+
7
+ # Includes `elapsed_time`, which is the number of seconds since the
8
+ # event loop started.
9
+ def deconstruct_keys(keys=nil)
10
+ super.merge({ elapsed_time: @elapsed_time })
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ class Brut::TUI::Events
2
+ autoload(:BaseEvent, "brut/tui/events/base_event")
3
+ autoload(:EventLoopStarted, "brut/tui/events/event_loop_started")
4
+ autoload(:Exception, "brut/tui/events/exception")
5
+ autoload(:EventBus, "brut/tui/events/event_bus")
6
+ autoload(:Tick, "brut/tui/events/tick")
7
+ end
@@ -0,0 +1,68 @@
1
+ # A string that responds to limited markup that can be used to apply styles to the string
2
+ class Brut::TUI::MarkupString
3
+ # Create a MarkupString from a normal string.
4
+ #
5
+ # @param string [String|Brut::TUI::MarkupString] string to convert.
6
+ # @return [Brut::TUI::MarkupString] if `string` is a `Brut::TUI::MarkupString` already, returns that, otherwise, wraps
7
+ # `string` in a `Brut::TUI::MarkupString`.
8
+ def self.from_string(string)
9
+ string.kind_of?(Brut::TUI::MarkupString) ? string : self.new(string.to_s)
10
+ end
11
+
12
+ def initialize(string)
13
+ @string = string
14
+ end
15
+
16
+ DELIMITERS = {
17
+ "*" => :bold,
18
+ "_" => :weak,
19
+ "`" => :code,
20
+ "~" => :strike,
21
+ }.freeze
22
+
23
+ # Parse the string for known markup, yielding at key parsing events.
24
+ #
25
+ # @yield [directive, value] called for each parsing event, where value depends on directive. The block
26
+ # will be called for all parts of the string.
27
+ # @yieldparam directive [Symbol] one of `:start`, `:stop`, or `:text`. `:text` is for any text and doesn't include
28
+ # the markup characters. `:start` and `:stop` are called when a markup start or stop is found.
29
+ # @yieldparam value [String|Symbol] For the `:text` `directive`, this is the text fragment from the string, so
30
+ # for the string `"*foo*"`, `:text` would be called with `"foo"`. For `:start` or `:stop`, the value
31
+ # is the type of markup encountered, one of `:bold`, `:weak`, `:code`, or `:strike`.
32
+ def parse(&block)
33
+ in_delimiter = DELIMITERS.keys.map { [ it, false ] }.to_h
34
+
35
+ previous_character = nil
36
+ previous_previous_character = nil
37
+
38
+ @string.each_char do |char|
39
+ if DELIMITERS.key?(char) && previous_character != "\\" && previous_previous_character != "\\"
40
+ style = DELIMITERS[char]
41
+ if in_delimiter[char]
42
+ block.(:stop, style)
43
+ in_delimiter[char] = false
44
+ else
45
+ inside_code = in_delimiter["`"]
46
+ if inside_code
47
+ block.(:text, char)
48
+ else
49
+ block.(:start, style)
50
+ in_delimiter[char] = true
51
+ end
52
+ end
53
+ else
54
+ if char == "\\"
55
+ if previous_character == "\\"
56
+ block.(:text, char)
57
+ else
58
+ # eat it - it is escaping something maybe
59
+ end
60
+ else
61
+ block.(:text, char)
62
+ end
63
+ end
64
+ previous_previous_character = previous_character
65
+ previous_character = char
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,17 @@
1
+ # A step whose behavior is a given block of code.
2
+ # Fires {Brut::TUI::Script::Events::StepStarted} before
3
+ # the code executes and {Brut::TUI::Script::Events::StepCompleted}
4
+ # *only* if the block completes without an exception being thrown.
5
+ class Brut::TUI::Script::BlockStep < Brut::TUI::Script::Step
6
+ def initialize(event_loop, description, &block)
7
+ super(event_loop, description)
8
+ @block = block
9
+ end
10
+
11
+ def run!
12
+ event_loop << Events::StepStarted.new(step: self)
13
+ @block.().tap {
14
+ event_loop << Events::StepCompleted.new(step: self)
15
+ }
16
+ end
17
+ end
@@ -0,0 +1,4 @@
1
+ # Fired when a subcommand exited nonzero
2
+ class Brut::TUI::Script::Events::CommandExecutionFailed < Brut::TUI::Script::Events::ExecutingCommand
3
+ def exit? = true
4
+ end
@@ -0,0 +1,3 @@
1
+ # Fired when a subcommand exited zero (i.e. was successful)
2
+ class Brut::TUI::Script::Events::CommandExecutionSucceeded < Brut::TUI::Script::Events::ExecutingCommand
3
+ end
@@ -0,0 +1,3 @@
1
+ # Fired when a command has provided at least some output on its standard error.
2
+ class Brut::TUI::Script::Events::CommandStdErr < Brut::TUI::Script::Events::CommandStdOut
3
+ end
@@ -0,0 +1,13 @@
1
+ # Fired when a command has provided at least some output on its standard output.
2
+ class Brut::TUI::Script::Events::CommandStdOut < Brut::TUI::Events::BaseEvent
3
+ def initialize(step:, output:)
4
+ @step = step
5
+ @output = output
6
+ end
7
+
8
+ # Adds `description`, `command` and `output` to the event keywords. `output` is a string
9
+ # containing whatever output was available. This is likely not terminated with a newline.
10
+ def deconstruct_keys(keys=nil)
11
+ super.merge({ description: @step.description, command: @step.command, output: @output })
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # Fired when a command is executed (this also serves as a base class for the results
2
+ # of such execution).
3
+ class Brut::TUI::Script::Events::ExecutingCommand < Brut::TUI::Events::BaseEvent
4
+ def initialize(step:)
5
+ @step = step
6
+ end
7
+
8
+ # Includes `description` and `command` in the keyword arguments
9
+ def deconstruct_keys(keys=nil)
10
+ super.merge({ description: @step.description, command: @step.command })
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ # Fired when a step produces a message
2
+ class Brut::TUI::Script::Events::Message < Brut::TUI::Events::BaseEvent
3
+ def initialize(message:, type:)
4
+ @message = message
5
+ @type = type
6
+ end
7
+
8
+ def to_s = @message
9
+
10
+ # Includes `message` and `type`. `type` will be `:notify`, `:warning`, `:success`,
11
+ # `:error`, or `:done`, however it could be anything else the script may choose to use.
12
+ def deconstruct_keys(keys=nil)
13
+ super.merge({ message: @message, type: @type })
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ # Fired when a phase completes
2
+ class Brut::TUI::Script::Events::PhaseCompleted < Brut::TUI::Script::Events::PhaseStarted
3
+ def to_s = "#{@name} completed"
4
+ end
@@ -0,0 +1,14 @@
1
+ # Fired when a phase starts, but before any steps have executed
2
+ class Brut::TUI::Script::Events::PhaseStarted < Brut::TUI::Events::BaseEvent
3
+ def initialize(description, step_number:, total_steps:)
4
+ @description = description
5
+ @step_number = step_number
6
+ @total_steps = total_steps
7
+ end
8
+
9
+ # Adds `description`, `step_number` (1-based), and `total_steps` to the keyword arguments.
10
+ def deconstruct_keys(keys=nil)
11
+ super.merge({ description: @description, step_number: @step_number, total_steps: @total_steps })
12
+ end
13
+ def to_s = @description
14
+ end
@@ -0,0 +1,5 @@
1
+ # Fired when the script has completed
2
+ class Brut::TUI::Script::Events::ScriptCompleted < Brut::TUI::Events::BaseEvent
3
+ def drain_then_exit? = true
4
+ def to_s = "Done"
5
+ end
@@ -0,0 +1,12 @@
1
+ # Fired when the script is just starting
2
+ class Brut::TUI::Script::Events::ScriptStarted < Brut::TUI::Events::BaseEvent
3
+ def initialize(phases:)
4
+ @phases = phases
5
+ end
6
+ # Adds `phases` as an array of `[description,Proc]` representing the phases.
7
+ # You are discouraged from interacting with the `Proc` objects.
8
+ def deconstruct_keys(keys=nil)
9
+ super.merge({ phases: @phases })
10
+ end
11
+ def to_s = "Starting"
12
+ end
@@ -0,0 +1,3 @@
1
+ # Fired when a step completes.
2
+ class Brut::TUI::Script::Events::StepCompleted < Brut::TUI::Script::Events::StepStarted
3
+ end