brut 0.16.0.pre.pre → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +6 -0
- data/CHANGELOG.md +9 -3
- data/Gemfile.lock +44 -34
- 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 +311 -2
- data/brut-js/package.json +2 -1
- data/brut.gemspec +2 -0
- data/brutrb.com/jobs.md +1 -1
- data/docs/404.html +2 -2
- data/docs/adrs.html +4 -4
- data/docs/ai.html +4 -4
- data/docs/api/Brut/BackEnd/SeedData.html +2 -2
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +2 -2
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +2 -2
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +2 -2
- data/docs/api/Brut/BackEnd/Sidekiq.html +2 -2
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +2 -2
- data/docs/api/Brut/BackEnd/Validators.html +2 -2
- data/docs/api/Brut/BackEnd.html +2 -2
- data/docs/api/Brut/CLI/App.html +2 -2
- data/docs/api/Brut/CLI/AppRunner.html +2 -2
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +2 -2
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +2 -2
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +3 -3
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +2 -2
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +2 -2
- data/docs/api/Brut/CLI/Apps/DB/Create.html +2 -2
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +2 -2
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +2 -2
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +2 -2
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +2 -2
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +2 -2
- data/docs/api/Brut/CLI/Apps/DB/Status.html +2 -2
- data/docs/api/Brut/CLI/Apps/DB.html +2 -2
- data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +2 -2
- data/docs/api/Brut/CLI/Apps/DeployBase.html +2 -2
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +8 -6
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold.html +2 -2
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +15 -7
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +2 -2
- data/docs/api/Brut/CLI/Apps/Test/JS.html +2 -2
- data/docs/api/Brut/CLI/Apps/Test/Run.html +2 -2
- data/docs/api/Brut/CLI/Apps/Test.html +2 -2
- data/docs/api/Brut/CLI/Apps.html +2 -2
- data/docs/api/Brut/CLI/Command.html +2 -2
- data/docs/api/Brut/CLI/Error.html +2 -2
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +2 -2
- data/docs/api/Brut/CLI/ExecutionResults.html +2 -2
- data/docs/api/Brut/CLI/Executor.html +2 -2
- data/docs/api/Brut/CLI/InvalidOption.html +2 -2
- data/docs/api/Brut/CLI/Options.html +2 -2
- data/docs/api/Brut/CLI/Output.html +2 -2
- data/docs/api/Brut/CLI/SystemExecError.html +2 -2
- data/docs/api/Brut/CLI.html +2 -2
- data/docs/api/Brut/FactoryBot.html +2 -2
- data/docs/api/Brut/Framework/App.html +2 -2
- data/docs/api/Brut/Framework/Config.html +2 -2
- data/docs/api/Brut/Framework/Container.html +11 -7
- data/docs/api/Brut/Framework/Error.html +2 -2
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +2 -2
- data/docs/api/Brut/Framework/Errors/Bug.html +2 -2
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +2 -2
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +2 -2
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +2 -2
- data/docs/api/Brut/Framework/Errors/NotFound.html +2 -2
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +2 -2
- data/docs/api/Brut/Framework/Errors.html +2 -2
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +2 -2
- data/docs/api/Brut/Framework/MCP.html +3 -3
- data/docs/api/Brut/Framework/ProjectEnvironment.html +2 -2
- data/docs/api/Brut/Framework.html +2 -2
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +2 -2
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +2 -2
- data/docs/api/Brut/FrontEnd/Component.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/Input.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +2 -2
- data/docs/api/Brut/FrontEnd/Components.html +2 -2
- data/docs/api/Brut/FrontEnd/CsrfProtector.html +2 -2
- data/docs/api/Brut/FrontEnd/Download.html +2 -2
- data/docs/api/Brut/FrontEnd/Flash.html +2 -2
- data/docs/api/Brut/FrontEnd/Form.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms/Button.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms/Input.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +2 -2
- data/docs/api/Brut/FrontEnd/Forms.html +2 -2
- data/docs/api/Brut/FrontEnd/GenericResponse.html +2 -2
- data/docs/api/Brut/FrontEnd/Handler.html +2 -2
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +2 -2
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +2 -2
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +2 -2
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +2 -2
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +2 -2
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +2 -2
- data/docs/api/Brut/FrontEnd/Handlers.html +2 -2
- data/docs/api/Brut/FrontEnd/HandlingResults.html +2 -2
- data/docs/api/Brut/FrontEnd/HttpMethod.html +2 -2
- data/docs/api/Brut/FrontEnd/HttpStatus.html +2 -2
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +2 -2
- data/docs/api/Brut/FrontEnd/Layout.html +2 -2
- data/docs/api/Brut/FrontEnd/Middleware.html +2 -2
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +2 -2
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +2 -2
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +2 -2
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +2 -2
- data/docs/api/Brut/FrontEnd/Middlewares.html +2 -2
- data/docs/api/Brut/FrontEnd/Page.html +2 -2
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +2 -2
- data/docs/api/Brut/FrontEnd/Pages.html +2 -2
- data/docs/api/Brut/FrontEnd/RequestContext.html +2 -2
- data/docs/api/Brut/FrontEnd/RouteHook.html +2 -2
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +2 -2
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +2 -2
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +2 -2
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +2 -2
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +2 -2
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +2 -2
- data/docs/api/Brut/FrontEnd/RouteHooks.html +2 -2
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +2 -2
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +2 -2
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +2 -2
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +2 -2
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +2 -2
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +2 -2
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +2 -2
- data/docs/api/Brut/FrontEnd/Routing/Route.html +2 -2
- data/docs/api/Brut/FrontEnd/Routing.html +2 -2
- data/docs/api/Brut/FrontEnd/Session.html +2 -2
- data/docs/api/Brut/FrontEnd.html +2 -2
- data/docs/api/Brut/I18n/BaseMethods.html +2 -2
- data/docs/api/Brut/I18n/ForBackEnd.html +2 -2
- data/docs/api/Brut/I18n/ForCLI.html +2 -2
- data/docs/api/Brut/I18n/ForHTML.html +2 -2
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +2 -2
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +2 -2
- data/docs/api/Brut/I18n.html +2 -2
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +2 -2
- data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +2 -2
- data/docs/api/Brut/Instrumentation/Methods.html +2 -2
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +2 -2
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +2 -2
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +2 -2
- data/docs/api/Brut/Instrumentation.html +2 -2
- data/docs/api/Brut/RubocopConfig.html +2 -2
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +2 -2
- data/docs/api/Brut/SinatraHelpers.html +2 -2
- data/docs/api/Brut/SpecSupport/ClockSupport.html +2 -2
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +2 -2
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +2 -2
- data/docs/api/Brut/SpecSupport/E2eSupport.html +2 -2
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +2 -2
- data/docs/api/Brut/SpecSupport/FlashSupport.html +2 -2
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +2 -2
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +2 -2
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +2 -2
- data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +2 -2
- data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +2 -2
- data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +2 -2
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +2 -2
- data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +2 -2
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +2 -2
- data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +2 -2
- data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +2 -2
- data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +2 -2
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +2 -2
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +2 -2
- data/docs/api/Brut/SpecSupport/Matchers.html +2 -2
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +2 -2
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +2 -2
- data/docs/api/Brut/SpecSupport/SessionSupport.html +2 -2
- data/docs/api/Brut/SpecSupport.html +2 -2
- 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 +4 -4
- data/docs/api/Clock.html +2 -2
- data/docs/api/ModuleName.html +2 -2
- data/docs/api/RichString.html +2 -2
- data/docs/api/SemanticLogger/Appender/Async.html +2 -2
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +2 -2
- data/docs/api/Sequel/Extensions/BrutMigrations.html +2 -2
- data/docs/api/Sequel/Extensions.html +2 -2
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +2 -2
- data/docs/api/Sequel/Plugins/CreatedAt.html +2 -2
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +2 -2
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +2 -2
- data/docs/api/Sequel/Plugins/ExternalId.html +2 -2
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +2 -2
- data/docs/api/Sequel/Plugins/FindBang.html +2 -2
- data/docs/api/Sequel/Plugins.html +2 -2
- data/docs/api/Sequel.html +2 -2
- data/docs/api/_index.html +247 -2
- data/docs/api/class_list.html +1 -1
- data/docs/api/file.README.html +2 -2
- data/docs/api/index.html +2 -2
- data/docs/api/method_list.html +1551 -431
- data/docs/api/top-level-namespace.html +2 -2
- data/docs/assets/{app.Dm7v_ouO.js → app.B8jAEB7R.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.DJ8mocCj.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.DQK6jQou.js → VPLocalSearchBox.gF-Po_fz.js} +1 -1
- data/docs/assets/chunks/{theme.BuExsdM9.js → theme.BjPAOJkz.js} +2 -2
- data/docs/assets/{components.md.Dfd3w6UW.js → components.md.Ber8UBM0.js} +3 -3
- data/docs/assets/{configuration.md.DTYoV2Ea.js → configuration.md.DrJ6YVoZ.js} +1 -1
- data/docs/assets/{deployment.md.C1u5ep0g.js → deployment.md.CHTx2eTR.js} +10 -3
- data/docs/assets/{deployment.md.C1u5ep0g.lean.js → deployment.md.CHTx2eTR.lean.js} +1 -1
- data/docs/assets/{forms.md.DEkmJUvb.js → forms.md.RK0zkhm0.js} +2 -2
- data/docs/assets/{forms.md.DEkmJUvb.lean.js → forms.md.RK0zkhm0.lean.js} +1 -1
- data/docs/assets/{getting-started.md.DO-4eoGW.js → getting-started.md.CGJ44juQ.js} +2 -2
- data/docs/assets/jobs.md.Bi3qb3v6.js +25 -0
- data/docs/assets/jobs.md.Bi3qb3v6.lean.js +1 -0
- data/docs/assets/recipes_dev-env-secrets.md.DC_jVY9U.js +12 -0
- data/docs/assets/recipes_dev-env-secrets.md.DC_jVY9U.lean.js +1 -0
- data/docs/assets/roadmap.md.DqC1Y7Zt.js +1 -0
- data/docs/assets/{roadmap.md.CJsbUmK_.lean.js → roadmap.md.DqC1Y7Zt.lean.js} +1 -1
- data/docs/assets/{tutorials_02-dialog.md.D2vSjDVf.js → tutorials_02-dialog.md.DE5WfCXI.js} +1 -1
- data/docs/assets.html +4 -4
- 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 +4 -4
- data/docs/business-logic.html +4 -4
- data/docs/cli.html +4 -4
- data/docs/components.html +8 -8
- data/docs/configuration.html +5 -5
- data/docs/css.html +4 -4
- data/docs/custom-element-tests.html +4 -4
- data/docs/database-access.html +4 -4
- data/docs/database-schema.html +4 -4
- data/docs/deployment.html +13 -6
- data/docs/dev-environment.html +4 -4
- data/docs/dir-structure.html +4 -4
- data/docs/doc-conventions.html +4 -4
- data/docs/end-to-end-tests.html +4 -4
- data/docs/features.html +4 -4
- data/docs/flash-and-session.html +4 -4
- data/docs/form-constraints.html +4 -4
- data/docs/forms.html +6 -6
- data/docs/getting-started.html +7 -7
- data/docs/handlers.html +4 -4
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +4 -4
- data/docs/i18n.html +4 -4
- data/docs/index.html +3 -3
- data/docs/instrumentation.html +4 -4
- data/docs/javascript.html +4 -4
- data/docs/jobs.html +29 -5
- data/docs/keyword-injection.html +4 -4
- data/docs/layouts.html +4 -4
- data/docs/lsp.html +4 -4
- data/docs/markdown-examples.html +4 -4
- data/docs/middleware.html +4 -4
- data/docs/overview.html +4 -4
- data/docs/pages.html +4 -4
- data/docs/recipes/alternate-layouts.html +5 -5
- data/docs/recipes/authentication.html +5 -5
- data/docs/recipes/custom-flash.html +5 -5
- data/docs/recipes/dev-env-secrets.html +40 -0
- data/docs/recipes/form-errors.html +5 -5
- data/docs/recipes/indexed-forms.html +5 -5
- data/docs/recipes/migrations.html +5 -5
- data/docs/recipes/text-field-component.html +5 -5
- data/docs/roadmap.html +5 -5
- data/docs/routes.html +4 -4
- data/docs/security.html +4 -4
- data/docs/seed-data.html +4 -4
- data/docs/space-time-continuum.html +4 -4
- data/docs/tutorial.html +4 -4
- data/docs/tutorials/01-intro.html +4 -4
- data/docs/tutorials/02-dialog.html +5 -5
- data/docs/unit-tests.html +4 -4
- data/docs/why.html +4 -4
- 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 +2 -1
- data/mkbrut/lib/mkbrut/version.rb +1 -1
- data/mkbrut/templates/segments/Heroku/deploy/Dockerfile +9 -7
- data/mkbrut/templates/segments/Heroku/deploy/heroku_config.rb +2 -2
- 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 +131 -21
- data/docs/assets/chunks/@localSearchIndexroot.BiNFswvo.js +0 -1
- data/docs/assets/jobs.md.Bc7Y1YpK.js +0 -1
- data/docs/assets/jobs.md.Bc7Y1YpK.lean.js +0 -1
- data/docs/assets/roadmap.md.CJsbUmK_.js +0 -1
- /data/docs/assets/{components.md.Dfd3w6UW.lean.js → components.md.Ber8UBM0.lean.js} +0 -0
- /data/docs/assets/{configuration.md.DTYoV2Ea.lean.js → configuration.md.DrJ6YVoZ.lean.js} +0 -0
- /data/docs/assets/{getting-started.md.DO-4eoGW.lean.js → getting-started.md.CGJ44juQ.lean.js} +0 -0
- /data/docs/assets/{tutorials_02-dialog.md.D2vSjDVf.lean.js → tutorials_02-dialog.md.DE5WfCXI.lean.js} +0 -0
|
@@ -161,7 +161,7 @@ Manages a deploy process based on using Heroku's Container Registry. See
|
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
names = images.map(&:first).join(" ")
|
|
164
|
-
deploy_command = "heroku container:release #{names}"
|
|
164
|
+
deploy_command = "heroku container:release #{names} -a #{heroku_app_name}"
|
|
165
165
|
if options.deploy?
|
|
166
166
|
out.puts "Deploying images to Heroku"
|
|
167
167
|
system!(deploy_command)
|
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
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# A string that responds to limited markup that can be used to apply styles to the string
|
|
2
|
+
class Brut::TUI::MarkupString
|
|
3
|
+
# Create a MarkupString from a normal string.
|
|
4
|
+
#
|
|
5
|
+
# @param string [String|Brut::TUI::MarkupString] string to convert.
|
|
6
|
+
# @return [Brut::TUI::MarkupString] if `string` is a `Brut::TUI::MarkupString` already, returns that, otherwise, wraps
|
|
7
|
+
# `string` in a `Brut::TUI::MarkupString`.
|
|
8
|
+
def self.from_string(string)
|
|
9
|
+
string.kind_of?(Brut::TUI::MarkupString) ? string : self.new(string.to_s)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(string)
|
|
13
|
+
@string = string
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
DELIMITERS = {
|
|
17
|
+
"*" => :bold,
|
|
18
|
+
"_" => :weak,
|
|
19
|
+
"`" => :code,
|
|
20
|
+
"~" => :strike,
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
# Parse the string for known markup, yielding at key parsing events.
|
|
24
|
+
#
|
|
25
|
+
# @yield [directive, value] called for each parsing event, where value depends on directive. The block
|
|
26
|
+
# will be called for all parts of the string.
|
|
27
|
+
# @yieldparam directive [Symbol] one of `:start`, `:stop`, or `:text`. `:text` is for any text and doesn't include
|
|
28
|
+
# the markup characters. `:start` and `:stop` are called when a markup start or stop is found.
|
|
29
|
+
# @yieldparam value [String|Symbol] For the `:text` `directive`, this is the text fragment from the string, so
|
|
30
|
+
# for the string `"*foo*"`, `:text` would be called with `"foo"`. For `:start` or `:stop`, the value
|
|
31
|
+
# is the type of markup encountered, one of `:bold`, `:weak`, `:code`, or `:strike`.
|
|
32
|
+
def parse(&block)
|
|
33
|
+
in_delimiter = DELIMITERS.keys.map { [ it, false ] }.to_h
|
|
34
|
+
|
|
35
|
+
previous_character = nil
|
|
36
|
+
previous_previous_character = nil
|
|
37
|
+
|
|
38
|
+
@string.each_char do |char|
|
|
39
|
+
if DELIMITERS.key?(char) && previous_character != "\\" && previous_previous_character != "\\"
|
|
40
|
+
style = DELIMITERS[char]
|
|
41
|
+
if in_delimiter[char]
|
|
42
|
+
block.(:stop, style)
|
|
43
|
+
in_delimiter[char] = false
|
|
44
|
+
else
|
|
45
|
+
inside_code = in_delimiter["`"]
|
|
46
|
+
if inside_code
|
|
47
|
+
block.(:text, char)
|
|
48
|
+
else
|
|
49
|
+
block.(:start, style)
|
|
50
|
+
in_delimiter[char] = true
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
if char == "\\"
|
|
55
|
+
if previous_character == "\\"
|
|
56
|
+
block.(:text, char)
|
|
57
|
+
else
|
|
58
|
+
# eat it - it is escaping something maybe
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
block.(:text, char)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
previous_previous_character = previous_character
|
|
65
|
+
previous_character = char
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# A step whose behavior is a given block of code.
|
|
2
|
+
# Fires {Brut::TUI::Script::Events::StepStarted} before
|
|
3
|
+
# the code executes and {Brut::TUI::Script::Events::StepCompleted}
|
|
4
|
+
# *only* if the block completes without an exception being thrown.
|
|
5
|
+
class Brut::TUI::Script::BlockStep < Brut::TUI::Script::Step
|
|
6
|
+
def initialize(event_loop, description, &block)
|
|
7
|
+
super(event_loop, description)
|
|
8
|
+
@block = block
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def run!
|
|
12
|
+
event_loop << Events::StepStarted.new(step: self)
|
|
13
|
+
@block.().tap {
|
|
14
|
+
event_loop << Events::StepCompleted.new(step: self)
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Fired when a command has provided at least some output on its standard output.
|
|
2
|
+
class Brut::TUI::Script::Events::CommandStdOut < Brut::TUI::Events::BaseEvent
|
|
3
|
+
def initialize(step:, output:)
|
|
4
|
+
@step = step
|
|
5
|
+
@output = output
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Adds `description`, `command` and `output` to the event keywords. `output` is a string
|
|
9
|
+
# containing whatever output was available. This is likely not terminated with a newline.
|
|
10
|
+
def deconstruct_keys(keys=nil)
|
|
11
|
+
super.merge({ description: @step.description, command: @step.command, output: @output })
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Fired when a command is executed (this also serves as a base class for the results
|
|
2
|
+
# of such execution).
|
|
3
|
+
class Brut::TUI::Script::Events::ExecutingCommand < Brut::TUI::Events::BaseEvent
|
|
4
|
+
def initialize(step:)
|
|
5
|
+
@step = step
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Includes `description` and `command` in the keyword arguments
|
|
9
|
+
def deconstruct_keys(keys=nil)
|
|
10
|
+
super.merge({ description: @step.description, command: @step.command })
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Fired when a step produces a message
|
|
2
|
+
class Brut::TUI::Script::Events::Message < Brut::TUI::Events::BaseEvent
|
|
3
|
+
def initialize(message:, type:)
|
|
4
|
+
@message = message
|
|
5
|
+
@type = type
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def to_s = @message
|
|
9
|
+
|
|
10
|
+
# Includes `message` and `type`. `type` will be `:notify`, `:warning`, `:success`,
|
|
11
|
+
# `:error`, or `:done`, however it could be anything else the script may choose to use.
|
|
12
|
+
def deconstruct_keys(keys=nil)
|
|
13
|
+
super.merge({ message: @message, type: @type })
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Fired when a phase starts, but before any steps have executed
|
|
2
|
+
class Brut::TUI::Script::Events::PhaseStarted < Brut::TUI::Events::BaseEvent
|
|
3
|
+
def initialize(description, step_number:, total_steps:)
|
|
4
|
+
@description = description
|
|
5
|
+
@step_number = step_number
|
|
6
|
+
@total_steps = total_steps
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Adds `description`, `step_number` (1-based), and `total_steps` to the keyword arguments.
|
|
10
|
+
def deconstruct_keys(keys=nil)
|
|
11
|
+
super.merge({ description: @description, step_number: @step_number, total_steps: @total_steps })
|
|
12
|
+
end
|
|
13
|
+
def to_s = @description
|
|
14
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Fired when the script is just starting
|
|
2
|
+
class Brut::TUI::Script::Events::ScriptStarted < Brut::TUI::Events::BaseEvent
|
|
3
|
+
def initialize(phases:)
|
|
4
|
+
@phases = phases
|
|
5
|
+
end
|
|
6
|
+
# Adds `phases` as an array of `[description,Proc]` representing the phases.
|
|
7
|
+
# You are discouraged from interacting with the `Proc` objects.
|
|
8
|
+
def deconstruct_keys(keys=nil)
|
|
9
|
+
super.merge({ phases: @phases })
|
|
10
|
+
end
|
|
11
|
+
def to_s = "Starting"
|
|
12
|
+
end
|