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.
- checksums.yaml +4 -4
- data/.gitignore +6 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +4 -1
- data/bin/docs +7 -0
- data/brut-css/package-lock.json +2 -2
- data/brut-css/package.json +1 -1
- data/brut-js/package-lock.json +2 -2
- data/brut-js/package.json +1 -1
- data/brut.gemspec +2 -0
- data/docs/404.html +2 -2
- data/docs/adrs.html +3 -3
- data/docs/ai.html +3 -3
- data/docs/api/Brut/BackEnd/SeedData.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
- data/docs/api/Brut/BackEnd/Validators.html +1 -1
- data/docs/api/Brut/BackEnd.html +1 -1
- data/docs/api/Brut/CLI/App.html +1 -1
- data/docs/api/Brut/CLI/AppRunner.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +2 -2
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +2 -2
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +10 -6
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps.html +1 -1
- data/docs/api/Brut/CLI/Command.html +1 -1
- data/docs/api/Brut/CLI/Error.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
- data/docs/api/Brut/CLI/Executor.html +1 -1
- data/docs/api/Brut/CLI/InvalidOption.html +1 -1
- data/docs/api/Brut/CLI/Options.html +1 -1
- data/docs/api/Brut/CLI/Output.html +1 -1
- data/docs/api/Brut/CLI/SystemExecError.html +1 -1
- data/docs/api/Brut/CLI.html +1 -1
- data/docs/api/Brut/FactoryBot.html +1 -1
- data/docs/api/Brut/Framework/App.html +1 -1
- data/docs/api/Brut/Framework/Config.html +1 -1
- data/docs/api/Brut/Framework/Container.html +1 -1
- data/docs/api/Brut/Framework/Error.html +1 -1
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
- data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
- data/docs/api/Brut/Framework/Errors.html +1 -1
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
- data/docs/api/Brut/Framework/MCP.html +1 -1
- data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
- data/docs/api/Brut/Framework.html +1 -1
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
- data/docs/api/Brut/FrontEnd/Component.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
- data/docs/api/Brut/FrontEnd/Components.html +1 -1
- data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
- data/docs/api/Brut/FrontEnd/Download.html +1 -1
- data/docs/api/Brut/FrontEnd/Flash.html +1 -1
- data/docs/api/Brut/FrontEnd/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Button.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms.html +1 -1
- data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
- data/docs/api/Brut/FrontEnd/Handler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
- data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
- data/docs/api/Brut/FrontEnd/Layout.html +1 -1
- data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
- data/docs/api/Brut/FrontEnd/Page.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages.html +1 -1
- data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing.html +1 -1
- data/docs/api/Brut/FrontEnd/Session.html +1 -1
- data/docs/api/Brut/FrontEnd.html +1 -1
- data/docs/api/Brut/I18n/BaseMethods.html +1 -1
- data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
- data/docs/api/Brut/I18n/ForCLI.html +1 -1
- data/docs/api/Brut/I18n/ForHTML.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
- data/docs/api/Brut/I18n.html +1 -1
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
- data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +1 -1
- data/docs/api/Brut/Instrumentation/Methods.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
- data/docs/api/Brut/Instrumentation.html +1 -1
- data/docs/api/Brut/RubocopConfig.html +1 -1
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
- data/docs/api/Brut/SinatraHelpers.html +1 -1
- data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
- data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
- data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
- data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
- data/docs/api/Brut/SpecSupport.html +1 -1
- data/docs/api/Brut/TUI/AnsiEscapeCode/Mod.html +409 -0
- data/docs/api/Brut/TUI/AnsiEscapeCode.html +426 -0
- data/docs/api/Brut/TUI/EventLoop/Deque.html +531 -0
- data/docs/api/Brut/TUI/EventLoop.html +676 -0
- data/docs/api/Brut/TUI/Events/BaseEvent.html +449 -0
- data/docs/api/Brut/TUI/Events/EventBus.html +485 -0
- data/docs/api/Brut/TUI/Events/EventLoopStarted.html +211 -0
- data/docs/api/Brut/TUI/Events/Exception.html +523 -0
- data/docs/api/Brut/TUI/Events/Tick.html +294 -0
- data/docs/api/Brut/TUI/Events.html +131 -0
- data/docs/api/Brut/TUI/MarkupString.html +537 -0
- data/docs/api/Brut/TUI/Script/BlockStep.html +300 -0
- data/docs/api/Brut/TUI/Script/Events/CommandExecutionFailed.html +252 -0
- data/docs/api/Brut/TUI/Script/Events/CommandExecutionSucceeded.html +163 -0
- data/docs/api/Brut/TUI/Script/Events/CommandStdErr.html +163 -0
- data/docs/api/Brut/TUI/Script/Events/CommandStdOut.html +300 -0
- data/docs/api/Brut/TUI/Script/Events/ExecutingCommand.html +298 -0
- data/docs/api/Brut/TUI/Script/Events/Message.html +345 -0
- data/docs/api/Brut/TUI/Script/Events/PhaseCompleted.html +229 -0
- data/docs/api/Brut/TUI/Script/Events/PhaseStarted.html +350 -0
- data/docs/api/Brut/TUI/Script/Events/ScriptCompleted.html +282 -0
- data/docs/api/Brut/TUI/Script/Events/ScriptStarted.html +343 -0
- data/docs/api/Brut/TUI/Script/Events/StepCompleted.html +163 -0
- data/docs/api/Brut/TUI/Script/Events/StepStarted.html +346 -0
- data/docs/api/Brut/TUI/Script/Events.html +115 -0
- data/docs/api/Brut/TUI/Script/ExecStep/ProcessStatusFailed.html +210 -0
- data/docs/api/Brut/TUI/Script/ExecStep.html +493 -0
- data/docs/api/Brut/TUI/Script/LoggingSubscriber.html +914 -0
- data/docs/api/Brut/TUI/Script/PutsSubscriber.html +783 -0
- data/docs/api/Brut/TUI/Script/Step.html +313 -0
- data/docs/api/Brut/TUI/Script.html +1250 -0
- data/docs/api/Brut/TUI/Terminal.html +593 -0
- data/docs/api/Brut/TUI/TerminalTheme.html +1403 -0
- data/docs/api/Brut/TUI/Themes/Dark.html +706 -0
- data/docs/api/Brut/TUI/Themes/Light.html +804 -0
- data/docs/api/Brut/TUI/Themes/None.html +218 -0
- data/docs/api/Brut/TUI/Themes.html +115 -0
- data/docs/api/Brut/TUI.html +129 -0
- data/docs/api/Brut.html +3 -3
- data/docs/api/Clock.html +1 -1
- data/docs/api/ModuleName.html +1 -1
- data/docs/api/RichString.html +1 -1
- data/docs/api/SemanticLogger/Appender/Async.html +1 -1
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
- data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
- data/docs/api/Sequel/Extensions.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang.html +1 -1
- data/docs/api/Sequel/Plugins.html +1 -1
- data/docs/api/Sequel.html +1 -1
- data/docs/api/SpecSupport/Matchers/BeABug.html +143 -0
- data/docs/api/_index.html +246 -1
- data/docs/api/class_list.html +1 -1
- data/docs/api/file.README.html +1 -1
- data/docs/api/index.html +1 -1
- data/docs/api/method_list.html +1551 -431
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/{app.CovevI7X.js → app.B8jAEB7R.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.DJ8mocCj.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.CrvLAvKW.js → VPLocalSearchBox.gF-Po_fz.js} +1 -1
- data/docs/assets/chunks/{theme.BAi5_yQI.js → theme.BjPAOJkz.js} +2 -2
- data/docs/assets/{components.md.9sqJ27Oc.js → components.md.Ber8UBM0.js} +3 -3
- data/docs/assets/{configuration.md.Cb_oAR8Z.js → configuration.md.DrJ6YVoZ.js} +1 -1
- data/docs/assets/{forms.md.BdpYpNIk.js → forms.md.RK0zkhm0.js} +2 -2
- data/docs/assets/{forms.md.BdpYpNIk.lean.js → forms.md.RK0zkhm0.lean.js} +1 -1
- data/docs/assets/{getting-started.md.CKpNGvno.js → getting-started.md.CGJ44juQ.js} +2 -2
- data/docs/assets/{tutorials_02-dialog.md.Z_DOF2mU.js → tutorials_02-dialog.md.DE5WfCXI.js} +1 -1
- data/docs/assets.html +3 -3
- data/docs/brut-css/brut.css +1 -0
- data/docs/brut-js/api/AjaxSubmit.html +1 -1
- data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
- data/docs/brut-js/api/Autosubmit.html +1 -1
- data/docs/brut-js/api/Autosubmit.js.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
- data/docs/brut-js/api/BrutCustomElements.html +1 -1
- data/docs/brut-js/api/BufferedLogger.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
- data/docs/brut-js/api/Form.html +1 -1
- data/docs/brut-js/api/Form.js.html +1 -1
- data/docs/brut-js/api/I18nTranslation.html +1 -1
- data/docs/brut-js/api/I18nTranslation.js.html +1 -1
- data/docs/brut-js/api/LocaleDetection.html +1 -1
- data/docs/brut-js/api/LocaleDetection.js.html +1 -1
- data/docs/brut-js/api/Logger.html +1 -1
- data/docs/brut-js/api/Logger.js.html +1 -1
- data/docs/brut-js/api/Message.html +1 -1
- data/docs/brut-js/api/Message.js.html +1 -1
- data/docs/brut-js/api/PrefixedLogger.html +1 -1
- data/docs/brut-js/api/RichString.html +1 -1
- data/docs/brut-js/api/RichString.js.html +1 -1
- data/docs/brut-js/api/Tabs.html +1 -1
- data/docs/brut-js/api/Tabs.js.html +1 -1
- data/docs/brut-js/api/Toast.html +1 -1
- data/docs/brut-js/api/Toast.js.html +1 -1
- data/docs/brut-js/api/Tracing.html +1 -1
- data/docs/brut-js/api/Tracing.js.html +1 -1
- data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
- data/docs/brut-js/api/external-Performance.html +1 -1
- data/docs/brut-js/api/external-Promise.html +1 -1
- data/docs/brut-js/api/external-ValidityState.html +1 -1
- data/docs/brut-js/api/external-Window.html +1 -1
- data/docs/brut-js/api/external-fetch.html +1 -1
- data/docs/brut-js/api/global.html +1 -1
- data/docs/brut-js/api/index.html +1 -1
- data/docs/brut-js/api/index.js.html +1 -1
- data/docs/brut-js/api/module-testing.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
- data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
- data/docs/brut-js/api/testing.DOMCreator.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
- data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
- data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
- data/docs/brut-js/api/testing_index.js.html +1 -1
- data/docs/brut-js.html +3 -3
- data/docs/business-logic.html +3 -3
- data/docs/cli.html +3 -3
- data/docs/components.html +7 -7
- data/docs/configuration.html +5 -5
- data/docs/css.html +3 -3
- data/docs/custom-element-tests.html +3 -3
- data/docs/database-access.html +3 -3
- data/docs/database-schema.html +3 -3
- data/docs/deployment.html +3 -3
- data/docs/dev-environment.html +3 -3
- data/docs/dir-structure.html +3 -3
- data/docs/doc-conventions.html +3 -3
- data/docs/end-to-end-tests.html +3 -3
- data/docs/features.html +3 -3
- data/docs/flash-and-session.html +3 -3
- data/docs/form-constraints.html +3 -3
- data/docs/forms.html +5 -5
- data/docs/getting-started.html +6 -6
- data/docs/handlers.html +3 -3
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +3 -3
- data/docs/i18n.html +3 -3
- data/docs/index.html +3 -3
- data/docs/instrumentation.html +3 -3
- data/docs/javascript.html +3 -3
- data/docs/jobs.html +3 -3
- data/docs/keyword-injection.html +3 -3
- data/docs/layouts.html +3 -3
- data/docs/lsp.html +3 -3
- data/docs/markdown-examples.html +3 -3
- data/docs/middleware.html +3 -3
- data/docs/overview.html +3 -3
- data/docs/pages.html +3 -3
- data/docs/recipes/alternate-layouts.html +3 -3
- data/docs/recipes/authentication.html +3 -3
- data/docs/recipes/custom-flash.html +3 -3
- data/docs/recipes/dev-env-secrets.html +3 -3
- data/docs/recipes/form-errors.html +3 -3
- data/docs/recipes/indexed-forms.html +3 -3
- data/docs/recipes/migrations.html +3 -3
- data/docs/recipes/text-field-component.html +3 -3
- data/docs/roadmap.html +3 -3
- data/docs/routes.html +3 -3
- data/docs/security.html +3 -3
- data/docs/seed-data.html +3 -3
- data/docs/space-time-continuum.html +3 -3
- data/docs/tutorial.html +3 -3
- data/docs/tutorials/01-intro.html +3 -3
- data/docs/tutorials/02-dialog.html +5 -5
- data/docs/unit-tests.html +3 -3
- data/docs/why.html +3 -3
- data/lib/brut/cli/apps/build_assets.rb +1 -1
- data/lib/brut/cli/apps/heroku_container_based_deploy.rb +1 -1
- data/lib/brut/cli/apps/test.rb +6 -4
- data/lib/brut/tui/ansi_escape_code.rb +104 -0
- data/lib/brut/tui/event_loop.rb +168 -0
- data/lib/brut/tui/events/base_event.rb +29 -0
- data/lib/brut/tui/events/event_bus.rb +73 -0
- data/lib/brut/tui/events/event_loop_started.rb +5 -0
- data/lib/brut/tui/events/exception.rb +24 -0
- data/lib/brut/tui/events/tick.rb +12 -0
- data/lib/brut/tui/events.rb +7 -0
- data/lib/brut/tui/markup_string.rb +68 -0
- data/lib/brut/tui/script/block_step.rb +17 -0
- data/lib/brut/tui/script/events/command_execution_failed.rb +4 -0
- data/lib/brut/tui/script/events/command_execution_succeeded.rb +3 -0
- data/lib/brut/tui/script/events/command_std_err.rb +3 -0
- data/lib/brut/tui/script/events/command_std_out.rb +13 -0
- data/lib/brut/tui/script/events/executing_command.rb +12 -0
- data/lib/brut/tui/script/events/message.rb +15 -0
- data/lib/brut/tui/script/events/phase_completed.rb +4 -0
- data/lib/brut/tui/script/events/phase_started.rb +14 -0
- data/lib/brut/tui/script/events/script_completed.rb +5 -0
- data/lib/brut/tui/script/events/script_started.rb +12 -0
- data/lib/brut/tui/script/events/step_completed.rb +3 -0
- data/lib/brut/tui/script/events/step_started.rb +12 -0
- data/lib/brut/tui/script/events.rb +14 -0
- data/lib/brut/tui/script/exec_step.rb +60 -0
- data/lib/brut/tui/script/logging_subscriber.rb +98 -0
- data/lib/brut/tui/script/puts_subscriber.rb +109 -0
- data/lib/brut/tui/script/step.rb +13 -0
- data/lib/brut/tui/script.rb +211 -0
- data/lib/brut/tui/terminal.rb +74 -0
- data/lib/brut/tui/terminal_theme.rb +140 -0
- data/lib/brut/tui/themes/dark.rb +14 -0
- data/lib/brut/tui/themes/light.rb +17 -0
- data/lib/brut/tui/themes/none.rb +9 -0
- data/lib/brut/tui/themes.rb +5 -0
- data/lib/brut/tui.rb +15 -0
- data/lib/brut/version.rb +1 -1
- data/lib/brut.rb +1 -0
- data/mkbrut/Gemfile.lock +1 -1
- data/mkbrut/lib/mkbrut/version.rb +1 -1
- data/specs/brut/tui/ansi_escape_code.spec.rb +30 -0
- data/specs/brut/tui/event_loop.spec.rb +70 -0
- data/specs/brut/tui/events/base_event.spec.rb +26 -0
- data/specs/brut/tui/events/event_bus.spec.rb +141 -0
- data/specs/brut/tui/events/exception.spec.rb +19 -0
- data/specs/brut/tui/events/test_event.rb +5 -0
- data/specs/spec_helper.rb +4 -0
- metadata +124 -15
- data/docs/assets/chunks/@localSearchIndexroot.BiNc3tFI.js +0 -1
- /data/docs/assets/{components.md.9sqJ27Oc.lean.js → components.md.Ber8UBM0.lean.js} +0 -0
- /data/docs/assets/{configuration.md.Cb_oAR8Z.lean.js → configuration.md.DrJ6YVoZ.lean.js} +0 -0
- /data/docs/assets/{getting-started.md.CKpNGvno.lean.js → getting-started.md.CGJ44juQ.lean.js} +0 -0
- /data/docs/assets/{tutorials_02-dialog.md.Z_DOF2mU.lean.js → tutorials_02-dialog.md.DE5WfCXI.lean.js} +0 -0
data/docs/why.html
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
<link rel="preload stylesheet" href="/assets/style.B1z60PPQ.css" as="style">
|
|
10
10
|
<link rel="preload stylesheet" href="/vp-icons.css" as="style">
|
|
11
11
|
|
|
12
|
-
<script type="module" src="/assets/app.
|
|
13
|
-
<link rel="modulepreload" href="/assets/chunks/theme.
|
|
12
|
+
<script type="module" src="/assets/app.B8jAEB7R.js"></script>
|
|
13
|
+
<link rel="modulepreload" href="/assets/chunks/theme.BjPAOJkz.js">
|
|
14
14
|
<link rel="modulepreload" href="/assets/chunks/framework.C4nOkCZI.js">
|
|
15
15
|
<link rel="modulepreload" href="/assets/why.md.4WpxdrQ2.lean.js">
|
|
16
16
|
<link rel="icon" href="/favicon.ico">
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
</head>
|
|
24
24
|
<body>
|
|
25
25
|
<div id="app"><div class="Layout" data-v-d8b57b2d><!--[--><!--]--><!--[--><span tabindex="-1" data-v-fcbfc0e0></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-fcbfc0e0>Skip to content</a><!--]--><!----><header class="VPNav" data-v-d8b57b2d data-v-7ad780c2><div class="VPNavBar" data-v-7ad780c2 data-v-9fd4d1dd><div class="wrapper" data-v-9fd4d1dd><div class="container" data-v-9fd4d1dd><div class="title" data-v-9fd4d1dd><div class="VPNavBarTitle has-sidebar" data-v-9fd4d1dd data-v-9f43907a><a class="title" href="/" data-v-9f43907a><!--[--><!--]--><!----><span data-v-9f43907a>Brut RB</span><!--[--><!--]--></a></div></div><div class="content" data-v-9fd4d1dd><div class="content-body" data-v-9fd4d1dd><!--[--><!--]--><div class="VPNavBarSearch search" data-v-9fd4d1dd><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><span class="vp-icon DocSearch-Search-Icon"></span><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-9fd4d1dd data-v-afb2845e><span id="main-nav-aria-label" class="visually-hidden" data-v-afb2845e> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Home</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/getting-started.html" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Getting Started</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/overview.html" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Overview</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/api/index.html" target="_self" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Brut API</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/brut-js/api/index.html" target="_self" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>BrutJS</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/brut-css/index.html" target="_self" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>BrutCSS</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-9fd4d1dd data-v-3f90c1a5><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-3f90c1a5 data-v-be9742d9 data-v-b4ccac88><span class="check" data-v-b4ccac88><span class="icon" data-v-b4ccac88><!--[--><span class="vpi-sun sun" data-v-be9742d9></span><span class="vpi-moon moon" data-v-be9742d9></span><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-9fd4d1dd data-v-ef6192dc data-v-e71e869c><!--[--><a class="VPSocialLink no-icon" href="https://github.com/thirdtank/brut" aria-label="github" target="_blank" rel="noopener" data-v-e71e869c data-v-60a9a2d3><span class="vpi-social-github"></span></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-9fd4d1dd data-v-f953d92f data-v-bfe7971f><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-bfe7971f><span class="vpi-more-horizontal icon" data-v-bfe7971f></span></button><div class="menu" data-v-bfe7971f><div class="VPMenu" data-v-bfe7971f data-v-20ed86d6><!----><!--[--><!--[--><!----><div class="group" data-v-f953d92f><div class="item appearance" data-v-f953d92f><p class="label" data-v-f953d92f>Appearance</p><div class="appearance-action" data-v-f953d92f><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-f953d92f data-v-be9742d9 data-v-b4ccac88><span class="check" data-v-b4ccac88><span class="icon" data-v-b4ccac88><!--[--><span class="vpi-sun sun" data-v-be9742d9></span><span class="vpi-moon moon" data-v-be9742d9></span><!--]--></span></span></button></div></div></div><div class="group" data-v-f953d92f><div class="item social-links" data-v-f953d92f><div class="VPSocialLinks social-links-list" data-v-f953d92f data-v-e71e869c><!--[--><a class="VPSocialLink no-icon" href="https://github.com/thirdtank/brut" aria-label="github" target="_blank" rel="noopener" data-v-e71e869c data-v-60a9a2d3><span class="vpi-social-github"></span></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-9fd4d1dd data-v-6bee1efd><span class="container" data-v-6bee1efd><span class="top" data-v-6bee1efd></span><span class="middle" data-v-6bee1efd></span><span class="bottom" data-v-6bee1efd></span></span></button></div></div></div></div><div class="divider" data-v-9fd4d1dd><div class="divider-line" data-v-9fd4d1dd></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-d8b57b2d data-v-2488c25a><div class="container" data-v-2488c25a><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-2488c25a><span class="vpi-align-left menu-icon" data-v-2488c25a></span><span class="menu-text" data-v-2488c25a>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-2488c25a data-v-6b867909><button data-v-6b867909>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-d8b57b2d data-v-42c4c606><div class="curtain" data-v-42c4c606></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-42c4c606><span class="visually-hidden" id="sidebar-aria-label" data-v-42c4c606> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Overview</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/getting-started.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Getting Started</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/overview.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Concepts</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/features.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Features</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/dir-structure.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Directory Structure</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/dev-environment.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Dev Environment</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/tutorial.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Tutorial</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/doc-conventions.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Documentation Conventions</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Front-End</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/routes.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Routes</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/pages.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Pages</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/layouts.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Layouts</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/forms.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Forms</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/form-constraints.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Form Constraints</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/handlers.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Handlers and Actions</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/components.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Components</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/flash-and-session.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Flash and Session</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/space-time-continuum.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Space/Time Continuum</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/javascript.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>JavaScript</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/css.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>CSS</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/assets.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Assets</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/brut-js.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>BrutJS</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Back-End</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/database-schema.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Database Schema</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/database-access.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Database Access</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/seed-data.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Seed Data</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/jobs.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Jobs</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/business-logic.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Business Logic</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Framework</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/configuration.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Configuration</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/keyword-injection.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Keyword Injection</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/i18n.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>I18n</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/cli.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>CLI / Tasks</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/deployment.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Deployment</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Testing</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/unit-tests.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Unit Tests</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/end-to-end-tests.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>End-to-End Tests</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/custom-element-tests.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Testing Custom Elements</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible collapsed" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Advanced Topics</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/hooks.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Route Hooks</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/middleware.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Middleware</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/instrumentation.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Instrumentation</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/security.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Security</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/lsp.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>LSP Support</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible collapsed" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Recipes</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/alternate-layouts.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Alternate Layouts</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/authentication.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Authentication</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/custom-flash.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Custom Flash Class</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/indexed-forms.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Indexed Form Elements</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/dev-env-secrets.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Managing Secrets in the Dev Environment</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/migrations.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Migration Basics</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/form-errors.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Styling Form Errors</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/text-field-component.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Text Field Component</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible has-active" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Meta</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/why.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Why?!</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/adrs.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>ADRs</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/roadmap.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Roadmap to 1.0</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/ai.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>AI Declaration</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-d8b57b2d data-v-9a6c75ad><div class="VPDoc has-sidebar has-aside" data-v-9a6c75ad data-v-e6f2a212><!--[--><!--]--><div class="container" data-v-e6f2a212><div class="aside" data-v-e6f2a212><div class="aside-curtain" data-v-e6f2a212></div><div class="aside-container" data-v-e6f2a212><div class="aside-content" data-v-e6f2a212><div class="VPDocAside" data-v-e6f2a212 data-v-cb998dce><!--[--><!--]--><!--[--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-cb998dce data-v-f610f197><div class="content" data-v-f610f197><div class="outline-marker" data-v-f610f197></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-f610f197>On this page</div><ul class="VPDocOutlineItem root" data-v-f610f197 data-v-53c99d69><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-cb998dce></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-e6f2a212><div class="content-container" data-v-e6f2a212><!--[--><!--]--><main class="main" data-v-e6f2a212><div style="position:relative;" class="vp-doc _why" data-v-e6f2a212><div><h1 id="why-does-brut-exist" tabindex="-1">Why Does Brut Exist? <a class="header-anchor" href="#why-does-brut-exist" aria-label="Permalink to "Why Does Brut Exist?""></a></h1><p>I love writing Ruby, but grew tired of writing Rails. Rails is great, and has been great to me over the years. I've written a lot of books about it! But the churn and increasing configuration burden made me think: what if we had another way to build web apps in Ruby?</p><p>What if it was totally different, but still focused on being straightforward and simple? What if it had <em>fewer</em> abstractions, <em>less</em> configuration, and not as much <em>stuff</em>?</p><p>My thinking is, you need to know HTML, JavaScript, CSS, SQL, Ruby, HTTP, and a few other things to make a web app. What if we tried to limit the additional abstractions you'd have to learn?</p><p>That's what Brut is trying to be. Straightfoward, direct abstractions or translations of stuff you already know. The raw web…or at least as raw as it can be.</p></div></div></main><footer class="VPDocFooter" data-v-e6f2a212 data-v-1bcd8184><!--[--><!--]--><!----><nav class="prev-next" aria-labelledby="doc-footer-aria-label" data-v-1bcd8184><span class="visually-hidden" id="doc-footer-aria-label" data-v-1bcd8184>Pager</span><div class="pager" data-v-1bcd8184><a class="VPLink link pager-link prev" href="/recipes/text-field-component.html" data-v-1bcd8184><!--[--><span class="desc" data-v-1bcd8184>Previous page</span><span class="title" data-v-1bcd8184>Text Field Component</span><!--]--></a></div><div class="pager" data-v-1bcd8184><a class="VPLink link pager-link next" href="/adrs.html" data-v-1bcd8184><!--[--><span class="desc" data-v-1bcd8184>Next page</span><span class="title" data-v-1bcd8184>ADRs</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
|
|
26
|
-
<script>window.__VP_HASH_MAP__=JSON.parse("{\"adrs.md\":\"YglbWtQe\",\"ai.md\":\"ChLnvDAX\",\"assets.md\":\"BEF6Oz6K\",\"brut-js.md\":\"BMz0X1Rz\",\"business-logic.md\":\"DbuaOYGU\",\"cli.md\":\"DDMar_51\",\"components.md\":\"
|
|
26
|
+
<script>window.__VP_HASH_MAP__=JSON.parse("{\"adrs.md\":\"YglbWtQe\",\"ai.md\":\"ChLnvDAX\",\"assets.md\":\"BEF6Oz6K\",\"brut-js.md\":\"BMz0X1Rz\",\"business-logic.md\":\"DbuaOYGU\",\"cli.md\":\"DDMar_51\",\"components.md\":\"Ber8UBM0\",\"configuration.md\":\"DrJ6YVoZ\",\"css.md\":\"K5rOCOQY\",\"custom-element-tests.md\":\"DiLe-eFw\",\"database-access.md\":\"Dc8l2Plf\",\"database-schema.md\":\"BJ_JhXmO\",\"deployment.md\":\"CHTx2eTR\",\"dev-environment.md\":\"B1S9p5ZK\",\"dir-structure.md\":\"D1T2kGwj\",\"doc-conventions.md\":\"CDnWaEFg\",\"end-to-end-tests.md\":\"BJJdNDYL\",\"features.md\":\"BDWxnyNO\",\"flash-and-session.md\":\"CUsMxoNl\",\"form-constraints.md\":\"KlfXSKm2\",\"forms.md\":\"RK0zkhm0\",\"getting-started.md\":\"CGJ44juQ\",\"handlers.md\":\"C5tUwmmo\",\"hooks.md\":\"CoiYCKRc\",\"i18n.md\":\"DxkCKhUw\",\"index.md\":\"DnphWyQd\",\"instrumentation.md\":\"BcxjC4jd\",\"javascript.md\":\"D6fxhaQb\",\"jobs.md\":\"Bi3qb3v6\",\"keyword-injection.md\":\"CqLnnzIz\",\"layouts.md\":\"HEbeK7Jr\",\"lsp.md\":\"bE9dW8n9\",\"markdown-examples.md\":\"BPmtHlc-\",\"middleware.md\":\"BhOIsg59\",\"overview.md\":\"BpWAgPFH\",\"pages.md\":\"B3sQXpEd\",\"recipes_alternate-layouts.md\":\"C1QzVkA7\",\"recipes_authentication.md\":\"CyvoIW82\",\"recipes_custom-flash.md\":\"6gFqf2uL\",\"recipes_dev-env-secrets.md\":\"DC_jVY9U\",\"recipes_form-errors.md\":\"B5ptSzMO\",\"recipes_indexed-forms.md\":\"BYYQGW2C\",\"recipes_migrations.md\":\"Cid7-3cu\",\"recipes_text-field-component.md\":\"VhOsCtKI\",\"roadmap.md\":\"DqC1Y7Zt\",\"routes.md\":\"C1dgIBtD\",\"security.md\":\"Jn4SY1uK\",\"seed-data.md\":\"UZW0WxYN\",\"space-time-continuum.md\":\"D9rYGDFH\",\"tutorial.md\":\"BX6f6l00\",\"tutorials_01-intro.md\":\"CzZ3kpF_\",\"tutorials_02-dialog.md\":\"DE5WfCXI\",\"unit-tests.md\":\"vDsdBbO_\",\"why.md\":\"4WpxdrQ2\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Brut RB\",\"description\":\"Documentation for the Brut.RB web framework.\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"search\":{\"provider\":\"local\"},\"nav\":[{\"text\":\"Home\",\"link\":\"/\"},{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Overview\",\"link\":\"/overview\"},{\"text\":\"Brut API\",\"link\":\"/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutCSS\",\"link\":\"/brut-css/index.html\",\"target\":\"_self\"}],\"outline\":[2,3],\"sidebar\":[{\"text\":\"Overview\",\"collapsed\":false,\"items\":[{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Concepts\",\"link\":\"/overview\"},{\"text\":\"Features\",\"link\":\"/features\"},{\"text\":\"Directory Structure\",\"link\":\"/dir-structure\"},{\"text\":\"Dev Environment\",\"link\":\"/dev-environment\"},{\"text\":\"Tutorial\",\"link\":\"/tutorial\"},{\"text\":\"Documentation Conventions\",\"link\":\"/doc-conventions\"}]},{\"text\":\"Front-End\",\"collapsed\":false,\"items\":[{\"text\":\"Routes\",\"link\":\"/routes\"},{\"text\":\"Pages\",\"link\":\"/pages\"},{\"text\":\"Layouts\",\"link\":\"/layouts\"},{\"text\":\"Forms\",\"link\":\"/forms\"},{\"text\":\"Form Constraints\",\"link\":\"/form-constraints\"},{\"text\":\"Handlers and Actions\",\"link\":\"/handlers\"},{\"text\":\"Components\",\"link\":\"/components\"},{\"text\":\"Flash and Session\",\"link\":\"/flash-and-session\"},{\"text\":\"Space/Time Continuum\",\"link\":\"/space-time-continuum\"},{\"text\":\"JavaScript\",\"link\":\"/javascript\"},{\"text\":\"CSS\",\"link\":\"/css\"},{\"text\":\"Assets\",\"link\":\"/assets\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js\"}]},{\"text\":\"Back-End\",\"collapsed\":false,\"items\":[{\"text\":\"Database Schema\",\"link\":\"/database-schema\"},{\"text\":\"Database Access\",\"link\":\"/database-access\"},{\"text\":\"Seed Data\",\"link\":\"/seed-data\"},{\"text\":\"Jobs\",\"link\":\"/jobs\"},{\"text\":\"Business Logic\",\"link\":\"/business-logic\"}]},{\"text\":\"Framework\",\"collapsed\":false,\"items\":[{\"text\":\"Configuration\",\"link\":\"/configuration\"},{\"text\":\"Keyword Injection\",\"link\":\"/keyword-injection\"},{\"text\":\"I18n\",\"link\":\"/i18n\"},{\"text\":\"CLI / Tasks\",\"link\":\"/cli\"},{\"text\":\"Deployment\",\"link\":\"/deployment\"}]},{\"text\":\"Testing\",\"collapsed\":false,\"items\":[{\"text\":\"Unit Tests\",\"link\":\"/unit-tests\"},{\"text\":\"End-to-End Tests\",\"link\":\"/end-to-end-tests\"},{\"text\":\"Testing Custom Elements\",\"link\":\"/custom-element-tests\"}]},{\"text\":\"Advanced Topics\",\"collapsed\":true,\"items\":[{\"text\":\"Route Hooks\",\"link\":\"/hooks\"},{\"text\":\"Middleware\",\"link\":\"/middleware\"},{\"text\":\"Instrumentation\",\"link\":\"/instrumentation\"},{\"text\":\"Security\",\"link\":\"/security\"},{\"text\":\"LSP Support\",\"link\":\"/lsp\"}]},{\"text\":\"Recipes\",\"collapsed\":true,\"items\":[{\"text\":\"Alternate Layouts\",\"link\":\"/recipes/alternate-layouts\"},{\"text\":\"Authentication\",\"link\":\"/recipes/authentication\"},{\"text\":\"Custom Flash Class\",\"link\":\"/recipes/custom-flash\"},{\"text\":\"Indexed Form Elements\",\"link\":\"/recipes/indexed-forms\"},{\"text\":\"Managing Secrets in the Dev Environment\",\"link\":\"/recipes/dev-env-secrets\"},{\"text\":\"Migration Basics\",\"link\":\"/recipes/migrations\"},{\"text\":\"Styling Form Errors\",\"link\":\"/recipes/form-errors\"},{\"text\":\"Text Field Component\",\"link\":\"/recipes/text-field-component\"}]},{\"text\":\"Meta\",\"collapsed\":false,\"items\":[{\"text\":\"Why?!\",\"link\":\"/why\"},{\"text\":\"ADRs\",\"link\":\"/adrs\"},{\"text\":\"Roadmap to 1.0\",\"link\":\"/roadmap\"},{\"text\":\"AI Declaration\",\"link\":\"/ai\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/thirdtank/brut\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
|
|
27
27
|
|
|
28
28
|
</body>
|
|
29
29
|
</html>
|
|
@@ -33,7 +33,7 @@ This is to ensure that any images your code references will end up in the public
|
|
|
33
33
|
src_dir = Brut.container.images_src_dir
|
|
34
34
|
dest_dir = Brut.container.images_root_dir
|
|
35
35
|
|
|
36
|
-
command = "rsync --archive --delete --verbose #{src_dir}
|
|
36
|
+
command = "rsync --archive --delete --verbose \"#{src_dir}/\" \"#{dest_dir}\""
|
|
37
37
|
system! command
|
|
38
38
|
end
|
|
39
39
|
end
|
|
@@ -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)
|
data/lib/brut/cli/apps/test.rb
CHANGED
|
@@ -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] =
|
|
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.
|
|
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(
|
|
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,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
|