brut 0.16.0 → 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 (444) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile.lock +4 -1
  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 +2 -2
  9. data/brut-js/package.json +1 -1
  10. data/brut.gemspec +2 -0
  11. data/docs/404.html +2 -2
  12. data/docs/adrs.html +3 -3
  13. data/docs/ai.html +3 -3
  14. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  15. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  16. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  17. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  18. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  19. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  20. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  21. data/docs/api/Brut/BackEnd.html +1 -1
  22. data/docs/api/Brut/CLI/App.html +1 -1
  23. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  24. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  25. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  26. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +2 -2
  27. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  28. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  29. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  30. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  31. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  32. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  33. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  34. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  35. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  36. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  37. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  39. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +2 -2
  40. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  42. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/Test/Audit.html +10 -6
  54. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  58. data/docs/api/Brut/CLI/Apps.html +1 -1
  59. data/docs/api/Brut/CLI/Command.html +1 -1
  60. data/docs/api/Brut/CLI/Error.html +1 -1
  61. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  62. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  63. data/docs/api/Brut/CLI/Executor.html +1 -1
  64. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  65. data/docs/api/Brut/CLI/Options.html +1 -1
  66. data/docs/api/Brut/CLI/Output.html +1 -1
  67. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  68. data/docs/api/Brut/CLI.html +1 -1
  69. data/docs/api/Brut/FactoryBot.html +1 -1
  70. data/docs/api/Brut/Framework/App.html +1 -1
  71. data/docs/api/Brut/Framework/Config.html +1 -1
  72. data/docs/api/Brut/Framework/Container.html +1 -1
  73. data/docs/api/Brut/Framework/Error.html +1 -1
  74. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  75. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  76. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  77. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  78. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  79. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  80. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  81. data/docs/api/Brut/Framework/Errors.html +1 -1
  82. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  83. data/docs/api/Brut/Framework/MCP.html +1 -1
  84. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  85. data/docs/api/Brut/Framework.html +1 -1
  86. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  87. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  88. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  89. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  90. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  91. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  92. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  93. data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +1 -1
  94. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  95. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  96. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  97. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
  98. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  99. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  105. data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  107. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  108. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Forms/Button.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  113. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  116. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  117. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  123. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  127. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  128. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  129. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  132. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  133. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  134. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  135. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  145. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  146. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  147. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  148. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  149. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  150. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  151. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  152. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  153. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  154. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  155. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  156. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  157. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  158. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  159. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  160. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  161. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  162. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  163. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  164. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  165. data/docs/api/Brut/FrontEnd.html +1 -1
  166. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  167. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  168. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  169. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  170. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  171. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  172. data/docs/api/Brut/I18n.html +1 -1
  173. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  174. data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +1 -1
  175. data/docs/api/Brut/Instrumentation/Methods.html +1 -1
  176. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  177. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  178. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  179. data/docs/api/Brut/Instrumentation.html +1 -1
  180. data/docs/api/Brut/RubocopConfig.html +1 -1
  181. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  182. data/docs/api/Brut/SinatraHelpers.html +1 -1
  183. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  184. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  185. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  186. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  187. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  188. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  189. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  190. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  191. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  192. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  193. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  194. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  195. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  196. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  197. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  198. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  199. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  200. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  201. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  202. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  203. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  204. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  205. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  206. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  207. data/docs/api/Brut/SpecSupport.html +1 -1
  208. data/docs/api/Brut/TUI/AnsiEscapeCode/Mod.html +409 -0
  209. data/docs/api/Brut/TUI/AnsiEscapeCode.html +426 -0
  210. data/docs/api/Brut/TUI/EventLoop/Deque.html +531 -0
  211. data/docs/api/Brut/TUI/EventLoop.html +676 -0
  212. data/docs/api/Brut/TUI/Events/BaseEvent.html +449 -0
  213. data/docs/api/Brut/TUI/Events/EventBus.html +485 -0
  214. data/docs/api/Brut/TUI/Events/EventLoopStarted.html +211 -0
  215. data/docs/api/Brut/TUI/Events/Exception.html +523 -0
  216. data/docs/api/Brut/TUI/Events/Tick.html +294 -0
  217. data/docs/api/Brut/TUI/Events.html +131 -0
  218. data/docs/api/Brut/TUI/MarkupString.html +537 -0
  219. data/docs/api/Brut/TUI/Script/BlockStep.html +300 -0
  220. data/docs/api/Brut/TUI/Script/Events/CommandExecutionFailed.html +252 -0
  221. data/docs/api/Brut/TUI/Script/Events/CommandExecutionSucceeded.html +163 -0
  222. data/docs/api/Brut/TUI/Script/Events/CommandStdErr.html +163 -0
  223. data/docs/api/Brut/TUI/Script/Events/CommandStdOut.html +300 -0
  224. data/docs/api/Brut/TUI/Script/Events/ExecutingCommand.html +298 -0
  225. data/docs/api/Brut/TUI/Script/Events/Message.html +345 -0
  226. data/docs/api/Brut/TUI/Script/Events/PhaseCompleted.html +229 -0
  227. data/docs/api/Brut/TUI/Script/Events/PhaseStarted.html +350 -0
  228. data/docs/api/Brut/TUI/Script/Events/ScriptCompleted.html +282 -0
  229. data/docs/api/Brut/TUI/Script/Events/ScriptStarted.html +343 -0
  230. data/docs/api/Brut/TUI/Script/Events/StepCompleted.html +163 -0
  231. data/docs/api/Brut/TUI/Script/Events/StepStarted.html +346 -0
  232. data/docs/api/Brut/TUI/Script/Events.html +115 -0
  233. data/docs/api/Brut/TUI/Script/ExecStep/ProcessStatusFailed.html +210 -0
  234. data/docs/api/Brut/TUI/Script/ExecStep.html +493 -0
  235. data/docs/api/Brut/TUI/Script/LoggingSubscriber.html +914 -0
  236. data/docs/api/Brut/TUI/Script/PutsSubscriber.html +783 -0
  237. data/docs/api/Brut/TUI/Script/Step.html +313 -0
  238. data/docs/api/Brut/TUI/Script.html +1250 -0
  239. data/docs/api/Brut/TUI/Terminal.html +593 -0
  240. data/docs/api/Brut/TUI/TerminalTheme.html +1403 -0
  241. data/docs/api/Brut/TUI/Themes/Dark.html +706 -0
  242. data/docs/api/Brut/TUI/Themes/Light.html +804 -0
  243. data/docs/api/Brut/TUI/Themes/None.html +218 -0
  244. data/docs/api/Brut/TUI/Themes.html +115 -0
  245. data/docs/api/Brut/TUI.html +129 -0
  246. data/docs/api/Brut.html +3 -3
  247. data/docs/api/Clock.html +1 -1
  248. data/docs/api/ModuleName.html +1 -1
  249. data/docs/api/RichString.html +1 -1
  250. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  251. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  252. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  253. data/docs/api/Sequel/Extensions.html +1 -1
  254. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  255. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  256. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  257. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  258. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  259. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  260. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  261. data/docs/api/Sequel/Plugins.html +1 -1
  262. data/docs/api/Sequel.html +1 -1
  263. data/docs/api/SpecSupport/Matchers/BeABug.html +143 -0
  264. data/docs/api/_index.html +246 -1
  265. data/docs/api/class_list.html +1 -1
  266. data/docs/api/file.README.html +1 -1
  267. data/docs/api/index.html +1 -1
  268. data/docs/api/method_list.html +1551 -431
  269. data/docs/api/top-level-namespace.html +1 -1
  270. data/docs/assets/{app.CovevI7X.js → app.B8jAEB7R.js} +1 -1
  271. data/docs/assets/chunks/@localSearchIndexroot.DJ8mocCj.js +1 -0
  272. data/docs/assets/chunks/{VPLocalSearchBox.CrvLAvKW.js → VPLocalSearchBox.gF-Po_fz.js} +1 -1
  273. data/docs/assets/chunks/{theme.BAi5_yQI.js → theme.BjPAOJkz.js} +2 -2
  274. data/docs/assets/{components.md.9sqJ27Oc.js → components.md.Ber8UBM0.js} +3 -3
  275. data/docs/assets/{configuration.md.Cb_oAR8Z.js → configuration.md.DrJ6YVoZ.js} +1 -1
  276. data/docs/assets/{forms.md.BdpYpNIk.js → forms.md.RK0zkhm0.js} +2 -2
  277. data/docs/assets/{forms.md.BdpYpNIk.lean.js → forms.md.RK0zkhm0.lean.js} +1 -1
  278. data/docs/assets/{getting-started.md.CKpNGvno.js → getting-started.md.CGJ44juQ.js} +2 -2
  279. data/docs/assets/{tutorials_02-dialog.md.Z_DOF2mU.js → tutorials_02-dialog.md.DE5WfCXI.js} +1 -1
  280. data/docs/assets.html +3 -3
  281. data/docs/brut-css/brut.css +1 -0
  282. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  283. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  284. data/docs/brut-js/api/Autosubmit.html +1 -1
  285. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  286. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  287. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  288. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  289. data/docs/brut-js/api/BufferedLogger.html +1 -1
  290. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  291. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  292. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  293. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  294. data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
  295. data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
  296. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  297. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  298. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  299. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  300. data/docs/brut-js/api/Form.html +1 -1
  301. data/docs/brut-js/api/Form.js.html +1 -1
  302. data/docs/brut-js/api/I18nTranslation.html +1 -1
  303. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  304. data/docs/brut-js/api/LocaleDetection.html +1 -1
  305. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  306. data/docs/brut-js/api/Logger.html +1 -1
  307. data/docs/brut-js/api/Logger.js.html +1 -1
  308. data/docs/brut-js/api/Message.html +1 -1
  309. data/docs/brut-js/api/Message.js.html +1 -1
  310. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  311. data/docs/brut-js/api/RichString.html +1 -1
  312. data/docs/brut-js/api/RichString.js.html +1 -1
  313. data/docs/brut-js/api/Tabs.html +1 -1
  314. data/docs/brut-js/api/Tabs.js.html +1 -1
  315. data/docs/brut-js/api/Toast.html +1 -1
  316. data/docs/brut-js/api/Toast.js.html +1 -1
  317. data/docs/brut-js/api/Tracing.html +1 -1
  318. data/docs/brut-js/api/Tracing.js.html +1 -1
  319. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  320. data/docs/brut-js/api/external-Performance.html +1 -1
  321. data/docs/brut-js/api/external-Promise.html +1 -1
  322. data/docs/brut-js/api/external-ValidityState.html +1 -1
  323. data/docs/brut-js/api/external-Window.html +1 -1
  324. data/docs/brut-js/api/external-fetch.html +1 -1
  325. data/docs/brut-js/api/global.html +1 -1
  326. data/docs/brut-js/api/index.html +1 -1
  327. data/docs/brut-js/api/index.js.html +1 -1
  328. data/docs/brut-js/api/module-testing.html +1 -1
  329. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  330. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  331. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  332. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  333. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  334. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  335. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  336. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  337. data/docs/brut-js/api/testing_index.js.html +1 -1
  338. data/docs/brut-js.html +3 -3
  339. data/docs/business-logic.html +3 -3
  340. data/docs/cli.html +3 -3
  341. data/docs/components.html +7 -7
  342. data/docs/configuration.html +5 -5
  343. data/docs/css.html +3 -3
  344. data/docs/custom-element-tests.html +3 -3
  345. data/docs/database-access.html +3 -3
  346. data/docs/database-schema.html +3 -3
  347. data/docs/deployment.html +3 -3
  348. data/docs/dev-environment.html +3 -3
  349. data/docs/dir-structure.html +3 -3
  350. data/docs/doc-conventions.html +3 -3
  351. data/docs/end-to-end-tests.html +3 -3
  352. data/docs/features.html +3 -3
  353. data/docs/flash-and-session.html +3 -3
  354. data/docs/form-constraints.html +3 -3
  355. data/docs/forms.html +5 -5
  356. data/docs/getting-started.html +6 -6
  357. data/docs/handlers.html +3 -3
  358. data/docs/hashmap.json +1 -1
  359. data/docs/hooks.html +3 -3
  360. data/docs/i18n.html +3 -3
  361. data/docs/index.html +3 -3
  362. data/docs/instrumentation.html +3 -3
  363. data/docs/javascript.html +3 -3
  364. data/docs/jobs.html +3 -3
  365. data/docs/keyword-injection.html +3 -3
  366. data/docs/layouts.html +3 -3
  367. data/docs/lsp.html +3 -3
  368. data/docs/markdown-examples.html +3 -3
  369. data/docs/middleware.html +3 -3
  370. data/docs/overview.html +3 -3
  371. data/docs/pages.html +3 -3
  372. data/docs/recipes/alternate-layouts.html +3 -3
  373. data/docs/recipes/authentication.html +3 -3
  374. data/docs/recipes/custom-flash.html +3 -3
  375. data/docs/recipes/dev-env-secrets.html +3 -3
  376. data/docs/recipes/form-errors.html +3 -3
  377. data/docs/recipes/indexed-forms.html +3 -3
  378. data/docs/recipes/migrations.html +3 -3
  379. data/docs/recipes/text-field-component.html +3 -3
  380. data/docs/roadmap.html +3 -3
  381. data/docs/routes.html +3 -3
  382. data/docs/security.html +3 -3
  383. data/docs/seed-data.html +3 -3
  384. data/docs/space-time-continuum.html +3 -3
  385. data/docs/tutorial.html +3 -3
  386. data/docs/tutorials/01-intro.html +3 -3
  387. data/docs/tutorials/02-dialog.html +5 -5
  388. data/docs/unit-tests.html +3 -3
  389. data/docs/why.html +3 -3
  390. data/lib/brut/cli/apps/build_assets.rb +1 -1
  391. data/lib/brut/cli/apps/heroku_container_based_deploy.rb +1 -1
  392. data/lib/brut/cli/apps/test.rb +6 -4
  393. data/lib/brut/tui/ansi_escape_code.rb +104 -0
  394. data/lib/brut/tui/event_loop.rb +168 -0
  395. data/lib/brut/tui/events/base_event.rb +29 -0
  396. data/lib/brut/tui/events/event_bus.rb +73 -0
  397. data/lib/brut/tui/events/event_loop_started.rb +5 -0
  398. data/lib/brut/tui/events/exception.rb +24 -0
  399. data/lib/brut/tui/events/tick.rb +12 -0
  400. data/lib/brut/tui/events.rb +7 -0
  401. data/lib/brut/tui/markup_string.rb +68 -0
  402. data/lib/brut/tui/script/block_step.rb +17 -0
  403. data/lib/brut/tui/script/events/command_execution_failed.rb +4 -0
  404. data/lib/brut/tui/script/events/command_execution_succeeded.rb +3 -0
  405. data/lib/brut/tui/script/events/command_std_err.rb +3 -0
  406. data/lib/brut/tui/script/events/command_std_out.rb +13 -0
  407. data/lib/brut/tui/script/events/executing_command.rb +12 -0
  408. data/lib/brut/tui/script/events/message.rb +15 -0
  409. data/lib/brut/tui/script/events/phase_completed.rb +4 -0
  410. data/lib/brut/tui/script/events/phase_started.rb +14 -0
  411. data/lib/brut/tui/script/events/script_completed.rb +5 -0
  412. data/lib/brut/tui/script/events/script_started.rb +12 -0
  413. data/lib/brut/tui/script/events/step_completed.rb +3 -0
  414. data/lib/brut/tui/script/events/step_started.rb +12 -0
  415. data/lib/brut/tui/script/events.rb +14 -0
  416. data/lib/brut/tui/script/exec_step.rb +60 -0
  417. data/lib/brut/tui/script/logging_subscriber.rb +98 -0
  418. data/lib/brut/tui/script/puts_subscriber.rb +109 -0
  419. data/lib/brut/tui/script/step.rb +13 -0
  420. data/lib/brut/tui/script.rb +211 -0
  421. data/lib/brut/tui/terminal.rb +74 -0
  422. data/lib/brut/tui/terminal_theme.rb +140 -0
  423. data/lib/brut/tui/themes/dark.rb +14 -0
  424. data/lib/brut/tui/themes/light.rb +17 -0
  425. data/lib/brut/tui/themes/none.rb +9 -0
  426. data/lib/brut/tui/themes.rb +5 -0
  427. data/lib/brut/tui.rb +15 -0
  428. data/lib/brut/version.rb +1 -1
  429. data/lib/brut.rb +1 -0
  430. data/mkbrut/Gemfile.lock +1 -1
  431. data/mkbrut/lib/mkbrut/version.rb +1 -1
  432. data/specs/brut/tui/ansi_escape_code.spec.rb +30 -0
  433. data/specs/brut/tui/event_loop.spec.rb +70 -0
  434. data/specs/brut/tui/events/base_event.spec.rb +26 -0
  435. data/specs/brut/tui/events/event_bus.spec.rb +141 -0
  436. data/specs/brut/tui/events/exception.spec.rb +19 -0
  437. data/specs/brut/tui/events/test_event.rb +5 -0
  438. data/specs/spec_helper.rb +4 -0
  439. metadata +124 -15
  440. data/docs/assets/chunks/@localSearchIndexroot.BiNc3tFI.js +0 -1
  441. /data/docs/assets/{components.md.9sqJ27Oc.lean.js → components.md.Ber8UBM0.lean.js} +0 -0
  442. /data/docs/assets/{configuration.md.Cb_oAR8Z.lean.js → configuration.md.DrJ6YVoZ.lean.js} +0 -0
  443. /data/docs/assets/{getting-started.md.CKpNGvno.lean.js → getting-started.md.CGJ44juQ.lean.js} +0 -0
  444. /data/docs/assets/{tutorials_02-dialog.md.Z_DOF2mU.lean.js → tutorials_02-dialog.md.DE5WfCXI.lean.js} +0 -0
@@ -0,0 +1,211 @@
1
+ require "logger"
2
+ require "fileutils"
3
+
4
+ # A TUI script is a set of steps that are executed in order. Steps
5
+ # can be grouped int phases, and each step can either shell out
6
+ # to an external command or execute Ruby code.
7
+ #
8
+ # You are intended to subclass this class and implement `#execute`, which can use
9
+ # the various methods described here to describe the script. In the context of a
10
+ # Brut CLI, your subclass would be used inside the {Brut::CLI::Command#execute} method, where
11
+ # it would call `#run!` (not `#execute`).
12
+ #
13
+ # Inside `#execute` you should call `#phase` for each logical phase/grouping of steps. There must be at least
14
+ # one phase. Inside each `phase` you should call `#step` one or more times. When a phase is actually executed,
15
+ # it's contents are executed, so anything inside the block will be called. Intermediate variables to allow `step`s to
16
+ # communicate will work.
17
+ #
18
+ # After all phases, call `done` with a success message.
19
+ #
20
+ # @example
21
+ # def execute
22
+ # phase "Set up test data" do
23
+ # step "Initializing database",
24
+ # exec: "bin/db rebuild -e test"
25
+ # step "Loading data" do
26
+ # MyData.each do |data|
27
+ # notify "Inserting #{data}"
28
+ # data.insert!
29
+ # end
30
+ # end
31
+ # end
32
+ # phase "Checking data integrity" do
33
+ # problems = []
34
+ # step "Analyzing data" do
35
+ # MyData.each do |data|
36
+ # if !data.analyze
37
+ # problems << data
38
+ # end
39
+ # end
40
+ # end
41
+ # step "Checking problems" do
42
+ # abort = false
43
+ # problems.each do |problem|
44
+ # if problem.warning?
45
+ # warning "#{problem} may be an issue, but we can proceed"
46
+ # else
47
+ # error "#{problem} will prevent our test from working"
48
+ # abort = true
49
+ # end
50
+ # end
51
+ # if abort
52
+ # raise "Problems prevented script from working"
53
+ # end
54
+ # end
55
+ # end
56
+ #
57
+ # done "All ready to go!"
58
+ # end
59
+ class Brut::TUI::Script
60
+
61
+ autoload(:Step , "brut/tui/script/step")
62
+ autoload(:BlockStep , "brut/tui/script/block_step")
63
+ autoload(:ExecStep , "brut/tui/script/exec_step")
64
+ autoload(:LoggingSubscriber , "brut/tui/script/logging_subscriber")
65
+ autoload(:PutsSubscriber , "brut/tui/script/puts_subscriber")
66
+ autoload(:Events , "brut/tui/script/events")
67
+
68
+ # Return the basename of a log file unique to this script. This will use
69
+ # the subclass name to come up with a reasonable name, however your
70
+ # class can override this.
71
+ def self.log_filename
72
+ name = self.name.gsub(/Script$/,"").gsub(/::$/,"").split("::").last
73
+ name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
74
+ end
75
+
76
+ # Create the script. By default, two subscribers are set up: {Brut::TUI::Script::LoggingSubscriber}
77
+ # and {Brut::TUI::Script::PutsSubscriber}. The logging subscriber will output a detailed
78
+ # log into `logs/` using the file named by {.log_filename}. The `PutsSubscriber` will
79
+ # output ANSI-Fancy messages as the script proceeds.
80
+ #
81
+ # @param root_dir [String|Pathname] path to the root of the Brut project.
82
+ # @param ansi [true|false] if true, the {Brut::TUI::Script::PutsSubscriber} will use
83
+ # ANSI escape codes to format the output. If false, it won't.
84
+ def initialize(root_dir: nil, ansi: true)
85
+ @root_dir = root_dir ? Pathname(root_dir).expand_path : nil
86
+ logs_dir = if @root_dir
87
+ @root_dir / "logs"
88
+ else
89
+ Pathame(".") / "logs"
90
+ end
91
+ FileUtils.mkdir_p(logs_dir)
92
+ @event_loop = Brut::TUI::EventLoop.new
93
+
94
+ logging_subscriber = LoggingSubscriber.new($0, logfile: logs_dir / "#{self.class.log_filename}.log")
95
+ terminal = Brut::TUI::Terminal.new
96
+ theme = if ansi
97
+ Brut::TUI::TerminalTheme.based_on_background(terminal)
98
+ else
99
+ Brut::TUI::Themes::None.new(terminal)
100
+ end
101
+ puts_subscriber = Brut::TUI::Script::PutsSubscriber.new($0, terminal:, theme:)
102
+
103
+ @event_loop.subscribe(Brut::TUI::Events::EventLoopStarted,self)
104
+ @event_loop.subscribe_to_all(logging_subscriber)
105
+ @event_loop.subscribe_to_all(puts_subscriber)
106
+
107
+ end
108
+
109
+ # @!visibility private
110
+ def on_event_loop_started(event)
111
+ Thread.new do
112
+ begin
113
+ @phases = []
114
+ self.execute
115
+ @event_loop << Events::ScriptStarted.new(phases: @phases)
116
+ @phases.each_with_index do |(name,block), index|
117
+ step_number = index + 1
118
+ @event_loop << Events::PhaseStarted.new(name, step_number: step_number, total_steps: @phases.length)
119
+ block.()
120
+ @event_loop << Events::PhaseCompleted.new(name, step_number: step_number, total_steps: @phases.length)
121
+ end
122
+ @event_loop << Events::Message.new(message: @done_message || "Script completed successfully", type: :done)
123
+ rescue => ex
124
+ @event_loop << Brut::TUI::Events::Exception.new(ex)
125
+ end
126
+ @event_loop << Events::ScriptCompleted.new
127
+ end
128
+ end
129
+
130
+ # Entry point for a script. This method starts the event loop.
131
+ def run!
132
+ if !self.methods.include?(:execute)
133
+ raise "You must implement the execute method in your Brut::TUI::Script subclass"
134
+ end
135
+ @event_loop.run
136
+ 0
137
+ end
138
+
139
+ # Create a new phase for the script. A phase is basically a named group
140
+ # of steps. The block is not executed immediately, so you may not pass
141
+ # data from one phase to another.
142
+ #
143
+ # @param name [String] The name of the phase
144
+ # @param block [Proc] The block to execute for the phase
145
+ def phase(name, &block)
146
+ @phases << [ name, block ]
147
+ end
148
+
149
+ # A step to run inside a phase. Steps within phases are executed immediately once the phase
150
+ # has started, so they can pass data to one anther.
151
+ #
152
+ # @param description [String] Message to show the user about this step.
153
+ # @param exec [String|nil] if non-nil, this step will execute this as a command. A block given is ignored.
154
+ # If `nil`, a block should be given that contains the step's code.
155
+ # @yield if `exec` is `nil`, this block will be executed for the step
156
+ def step(description, exec: nil, &block)
157
+ step = if exec.nil?
158
+ BlockStep.new(@event_loop, description, &block)
159
+ else
160
+ ExecStep.new(@event_loop, description, command: exec)
161
+ end
162
+ step.run!
163
+ end
164
+
165
+ # Notify the user of an event
166
+ #
167
+ # @param message [String] Message to show
168
+ def notify(message)
169
+ @event_loop << Events::Message.new(message:, type: :notification)
170
+ end
171
+
172
+ # Warn the user of an event
173
+ #
174
+ # @param message [String] Message to show
175
+ def warning(message)
176
+ @event_loop << Events::Message.new(message:, type: :warning)
177
+ end
178
+
179
+ # Let the user know something succeeded
180
+ #
181
+ # @param message [String] Message to show
182
+ def success(message)
183
+ @event_loop << Events::Message.new(message:, type: :success)
184
+ end
185
+
186
+ # Message to show if the script completes successful. Should only be called once
187
+ # and not inside a phase or step.
188
+ #
189
+ # @param message [String] Message to show
190
+ def done(message)
191
+ @done_message = message
192
+ end
193
+
194
+ # Let the user know there was an error. Note that this will not stop
195
+ # the script. Raise an exception to do that.
196
+ #
197
+ # @param message [String] Message to show
198
+ def error(message)
199
+ @event_loop << Events::Message.new(message:, type: :error)
200
+ end
201
+
202
+ # Wrap a fully-qualified filename in code markup and trim the path to only
203
+ # show the path relative to the root dir. This is much friendlier than showing a
204
+ # long expanded path.
205
+ #
206
+ # @param path [String|Pathname] a fully-qualified path to something inside your Brut app.
207
+ def filename(path)
208
+ path = Pathname(path).expand_path
209
+ "`" + (@root_dir ? path.relative_path_from(@root_dir) : path).to_s + "`"
210
+ end
211
+ end
@@ -0,0 +1,74 @@
1
+ require "io/console"
2
+ require "timeout"
3
+ require "stringio"
4
+
5
+ # Stores metdata about the current temrinal in use. This should provide any metadata
6
+ # about the terminal that can be reliably determined.
7
+ class Brut::TUI::Terminal
8
+
9
+ def initialize
10
+ trap("WINCH") do
11
+ @winsize = IO.console.winsize
12
+ end
13
+ end
14
+
15
+ # Number of rows for the current terminal. Should be consistent even after a resize
16
+ def rows = winsize[0]
17
+ # Number of columns for the current terminal. Should be consistent even after a resize
18
+ def cols = winsize[1]
19
+
20
+ # The IO used for outputing information to the terminal. Prefer this over `STDOUT`.
21
+ def io = $stdout
22
+
23
+ # The IO to use for requesting input from the user
24
+ def stdin = $stdin
25
+
26
+ # Best attempt to guess the background color of the current terminal.
27
+ # This will not refresh if that color is changed, and its behavior will
28
+ # default to black if determining the color doesn't work or isn't supported.
29
+ def background_color
30
+ @bakground_color ||= begin
31
+ io.write "\e]11;?\a"
32
+ io.flush
33
+
34
+ result = nil
35
+
36
+ begin
37
+ # Read the reply, which should look like:
38
+ # ESC ] 11 ; rgb:0000/0000/0000 BEL
39
+ Timeout.timeout(0.1) do
40
+ buf = +""
41
+ loop do
42
+ ch = stdin.getch
43
+ buf << ch
44
+ break if ch == "\a" || buf.end_with?("\e\\") # BEL or ST terminator
45
+ end
46
+ result = buf
47
+ end
48
+ rescue Timeout::Error
49
+ # no reply within 100ms — terminal probably doesn’t support it
50
+ end
51
+
52
+ rgb = [ 0, 0, 0 ]
53
+ if result
54
+ if result =~ /\e\]11;([^ \a\e]*)[\a\e\\]/
55
+ color = Regexp.last_match(1)
56
+ parts = color[/rgb:(.*)/, 1]&.split("/")
57
+ if parts
58
+
59
+ rgb = parts.map { it.to_i(16) / 0xffff.to_f * 256 }.map(&:to_i)
60
+ end
61
+ end
62
+ end
63
+ rgb
64
+ end
65
+ end
66
+
67
+
68
+ private
69
+
70
+ def winsize
71
+ @winsize ||= IO.console.winsize
72
+ end
73
+
74
+ end
@@ -0,0 +1,140 @@
1
+ # A terminal theme is a set of semantic styles for the terminal that
2
+ # map to actual colors or styles based on the terminal's metadata or
3
+ # the user's preferences or both.
4
+ #
5
+ # This class is coupled to the behavior of ANSI escape
6
+ # codes in the terminal, in that they aren't markup, but more
7
+ # like drawing with a pen.
8
+
9
+ # That said, this class' API does abstract from the codes
10
+ # themselves and creates a semantic layer, for example
11
+ # the definition of "succes" or "weak".
12
+
13
+ # For subclass implementors, there are various private
14
+ # methods that do encapsulate the ANSI escape codes, and these
15
+ # can be used or overridden.
16
+ #
17
+ # This particular implementation avoids the use of black or white, and uses
18
+ # the normal ANSI colors. This should, in theory, work with any terminal
19
+ # theme where all colors are legible on the chosen background.
20
+ class Brut::TUI::TerminalTheme
21
+
22
+ def self.based_on_background(terminal)
23
+ if dark_background?(terminal)
24
+ Brut::TUI::Themes::Dark.new(terminal)
25
+ else
26
+ Brut::TUI::Themes::Light.new(terminal)
27
+ end
28
+ end
29
+
30
+ def self.dark_background?(terminal)
31
+ r, g, b = terminal.background_color.map { it / 255.0 }
32
+ luminance = (0.21 * r) + (0.72 * g) + (0.07 * b)
33
+
34
+ return luminance < 0.5
35
+ end
36
+
37
+ def initialize(terminal)
38
+ @terminal = terminal
39
+ end
40
+
41
+ # Returns a string with its markup turned into escape codes.
42
+ # Note that due to the way ANSI escape codes work, the state
43
+ # of the terminal may not be in the same state you found it.
44
+ #
45
+ # This method tries to turn features on and off (e.g. after a bold
46
+ # string, the codes to turn off bold are applied), but if a subclass
47
+ # mixes colors and styles, any text output after this one may
48
+ # not look like the text before it.
49
+ def with_markup(string, text: :normal, reset: true)
50
+ result = +""
51
+ result << send(text)
52
+ Brut::TUI::MarkupString.from_string(string).parse do |directive, value|
53
+ case directive
54
+ in :start
55
+ case value
56
+ in :bold
57
+ result << bold
58
+ in :strike
59
+ result << strike
60
+ in :code
61
+ result << code
62
+ in :weak
63
+ result << weak
64
+ end
65
+ in :stop
66
+ case value
67
+ in :bold
68
+ result << bold_off << send(text)
69
+ in :strike
70
+ result << strike_off << send(text)
71
+ in :code
72
+ result << code_off << send(text)
73
+ in :weak
74
+ result << weak_off << send(text)
75
+ end
76
+ in :text
77
+ result << value
78
+ end
79
+ end
80
+ if reset
81
+ result << self.reset
82
+ end
83
+ result
84
+ end
85
+
86
+ def success = bold + bright_green
87
+ def error = bold + bright_red
88
+ def warning = bold + yellow
89
+
90
+ def bold = esc("1")
91
+ def bold_off = normal
92
+
93
+ def bright = esc("1")
94
+ def bright_off = normal
95
+
96
+ def italic = esc("3")
97
+ def italic_off = esc("23")
98
+
99
+ def strike = esc("9")
100
+ def strike_off = esc("29")
101
+
102
+ def weak = esc("2")
103
+ def weak_off = normal
104
+
105
+ def code = underline
106
+ def code_off = underline_off
107
+
108
+ def normal = esc("22")
109
+
110
+ def reset = esc("0")
111
+
112
+ def heading = bold + underline + bright_blue
113
+
114
+ private
115
+
116
+ def underline = esc("4")
117
+ def underline_off = esc("24")
118
+
119
+ def black = esc("30")
120
+ def red = esc("31")
121
+ def green = esc("32")
122
+ def yellow = esc("33")
123
+ def blue = esc("34")
124
+ def magenta = esc("35")
125
+ def cyan = esc("36")
126
+ def white = esc("37")
127
+
128
+ def bright_black = esc("90")
129
+ def bright_red = esc("91")
130
+ def bright_green = esc("92")
131
+ def bright_yellow = esc("93")
132
+ def bright_blue = esc("94")
133
+ def bright_magenta = esc("95")
134
+ def bright_cyan = esc("96")
135
+ def bright_white = esc("97")
136
+
137
+ def esc(code)
138
+ "\e[#{code}m"
139
+ end
140
+ end
@@ -0,0 +1,14 @@
1
+ # A theme designed to work on a dark background terminal.
2
+ class Brut::TUI::Themes::Dark < Brut::TUI::TerminalTheme
3
+ def black = esc("38;2;96;96;96")
4
+ def bright_black = esc("38;2;128;128;128")
5
+ def white = esc("38;2;233;233;233")
6
+ def bright_white = esc("38;2;255;255;255")
7
+ def yellow = esc("38;2;191;191;0")
8
+ def bright_yellow = esc("38;2;255;255;0")
9
+ def normal = super + white
10
+ def code = esc("38;2;0;255;0")
11
+ def code_off = normal
12
+ def bright = bright_white
13
+ def bright_off = normal
14
+ end
@@ -0,0 +1,17 @@
1
+ # A theme designed to work on a white background terminal.
2
+ class Brut::TUI::Themes::Light < Brut::TUI::TerminalTheme
3
+ def black = esc("38;2;0;0;0")
4
+ def bright_black = esc("38;2;64;64;64")
5
+ def bright_white = esc("38;2;128;128;128")
6
+ def white = esc("38;2;191;191;191")
7
+ def yellow = esc("38;2;191;191;0")
8
+ def bright_yellow = esc("38;2;255;255;0")
9
+ def normal = super + black
10
+ def code = esc("38;2;0;128;0")
11
+ def code_off = normal
12
+ def green = esc("38;2;25;105;0")
13
+ def bright_green = esc("38;2;34;155;0")
14
+ def bright = bright_blue
15
+ def bright_off = normal
16
+ end
17
+
@@ -0,0 +1,9 @@
1
+ # A theme without any ANSI styling at all. This is suitable
2
+ # for showing to a user who does not want any colors or styles, but
3
+ # still wants to see output in a somewhat conventional way.
4
+ class Brut::TUI::Themes::None < Brut::TUI::TerminalTheme
5
+ def esc(*) = ""
6
+ end
7
+
8
+
9
+
@@ -0,0 +1,5 @@
1
+ module Brut::TUI::Themes
2
+ autoload(:Light , "brut/tui/themes/light")
3
+ autoload(:Dark , "brut/tui/themes/dark")
4
+ autoload(:None , "brut/tui/themes/none")
5
+ end
data/lib/brut/tui.rb ADDED
@@ -0,0 +1,15 @@
1
+ module Brut
2
+ # Brut provides a basic API for creating rich text user interfaces. Currently, Brut
3
+ # provides {Brut::TUI::Script} to create more user-friendly scripts like `bin/setup` or
4
+ # `bin/ci`.
5
+ module TUI
6
+ autoload(:EventLoop, "brut/tui/event_loop")
7
+ autoload(:Events, "brut/tui/events")
8
+ autoload(:AnsiEscapeCode, "brut/tui/ansi_escape_code")
9
+ autoload(:Script, "brut/tui/script")
10
+ autoload(:Terminal, "brut/tui/terminal")
11
+ autoload(:TerminalTheme, "brut/tui/terminal_theme")
12
+ autoload(:Themes, "brut/tui/themes")
13
+ autoload(:MarkupString, "brut/tui/markup_string")
14
+ end
15
+ end
data/lib/brut/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Brut
2
2
  # @!visibility private
3
- VERSION = "0.16.0"
3
+ VERSION = "0.17.0"
4
4
  end
data/lib/brut.rb CHANGED
@@ -16,5 +16,6 @@ module Brut
16
16
  autoload(:Instrumentation,"brut/instrumentation")
17
17
  autoload(:SinatraHelpers, "brut/sinatra_helpers")
18
18
  # DO NOT autoload(:CLI) - that is intended to be require-able on its own
19
+ # DO NOT autoload(:TUI) - that is intended to be require-able on its own
19
20
  end
20
21
  require "sequel/plugins"
data/mkbrut/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mkbrut (0.16.0)
4
+ mkbrut (0.17.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -1,3 +1,3 @@
1
1
  module MKBrut
2
- VERSION = "0.16.0"
2
+ VERSION = "0.17.0"
3
3
  end
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+ require "brut/tui"
3
+
4
+ RSpec.describe Brut::TUI::AnsiEscapeCode do
5
+ describe "#to_s" do
6
+ it "returns the ANSI escape code as a string" do
7
+ code = Brut::TUI::AnsiEscapeCode.new(:red, "31")
8
+ expect(code.to_s).to eq("\e[31m")
9
+ end
10
+ end
11
+
12
+ describe ".ansi" do
13
+ it "returns the correct ANSI escape code for a given name" do
14
+ red_code = Brut::TUI::AnsiEscapeCode.ansi(:red)
15
+ expect(red_code.to_s).to eq("\e[31m")
16
+ end
17
+
18
+ it "returns an RGB ANSI escape code when given RGB values" do
19
+ rgb_code = Brut::TUI::AnsiEscapeCode.ansi(255, 0, 0)
20
+ expect(rgb_code.to_s).to eq("\e[38;2;255;0;0m")
21
+ end
22
+
23
+ it "returns a fluent objects when called without arguments" do
24
+ fluent = Brut::TUI::AnsiEscapeCode.ansi
25
+ expect(fluent.bold.to_s).to eq("\e[1m")
26
+ expect(fluent.red.to_s).to eq("\e[31m")
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,70 @@
1
+ require "spec_helper"
2
+ require "brut/tui"
3
+
4
+ RSpec.describe Brut::TUI::EventLoop do
5
+ subject(:event_loop) { described_class.new(tick: false) }
6
+
7
+ class ExitEvent < Brut::TUI::Events::BaseEvent
8
+ def exit? = true
9
+ end
10
+
11
+ class RaiseExceptionEvent < Brut::TUI::Events::BaseEvent
12
+ def initialize(message)
13
+ @message = message
14
+ end
15
+ def raise_exception!
16
+ raise StandardError.new(@message)
17
+ end
18
+ end
19
+
20
+ class GeneralSubscriber
21
+ attr_reader :events
22
+ def initialize
23
+ @events = []
24
+ end
25
+ def on_any_event(event)
26
+ if event.respond_to?(:raise_exception!)
27
+ event.raise_exception!
28
+ else
29
+ @events << event
30
+ end
31
+ end
32
+ end
33
+
34
+ it "stops the loop when an event returns true for exit?" do
35
+ main_thread = Thread.new {
36
+ event_loop.run
37
+ }
38
+ event_loop << ExitEvent.new
39
+ main_thread.join(0.1)
40
+ expect(main_thread.alive?).to eq(false)
41
+ end
42
+
43
+ def start_loop(event_loop, wait_time: 0.1)
44
+ exception = nil
45
+ main_thread = Thread.new {
46
+ begin
47
+ event_loop.run
48
+ rescue => ex
49
+ exception = ex
50
+ end
51
+ }
52
+ main_thread.join(wait_time)
53
+ expect(exception).to eq(nil)
54
+ end
55
+
56
+ it "fires the event LoopStarted event when run" do
57
+ general_subscriber = GeneralSubscriber.new
58
+ event_loop.subscribe_to_all(general_subscriber)
59
+ start_loop(event_loop)
60
+ expect(general_subscriber.events[0].class).to eq(Brut::TUI::Events::EventLoopStarted)
61
+ end
62
+
63
+ it "fires exception events when errors are raised" do
64
+ general_subscriber = GeneralSubscriber.new
65
+ event_loop.subscribe_to_all(general_subscriber)
66
+ event_loop << RaiseExceptionEvent.new("Test exception")
67
+ start_loop(event_loop)
68
+ expect(general_subscriber.events[1].class).to eq(Brut::TUI::Events::Exception)
69
+ end
70
+ end
@@ -0,0 +1,26 @@
1
+ require "spec_helper"
2
+ require "brut/tui"
3
+
4
+ RSpec.describe Brut::TUI::Events::BaseEvent do
5
+ class TestEvent < Brut::TUI::Events::BaseEvent; end
6
+ module Deeply
7
+ module Nested
8
+ class TestEvent < Brut::TUI::Events::BaseEvent; end
9
+ end
10
+ end
11
+
12
+ describe ".handler_method_name" do
13
+ it "returns the underscorized simple class name prefixed with 'on_'" do
14
+
15
+ expect(TestEvent.handler_method_name).to eq("on_test_event")
16
+ expect(Deeply::Nested::TestEvent.handler_method_name).to eq("on_test_event")
17
+ end
18
+ end
19
+
20
+ describe "#exit?" do
21
+ it "returns false by default" do
22
+ event = Brut::TUI::Events::BaseEvent.new
23
+ expect(event.exit?).to be false
24
+ end
25
+ end
26
+ end