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
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
require "logger"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
# A TUI script is a set of steps that are executed in order. Steps
|
|
5
|
+
# can be grouped int phases, and each step can either shell out
|
|
6
|
+
# to an external command or execute Ruby code.
|
|
7
|
+
#
|
|
8
|
+
# You are intended to subclass this class and implement `#execute`, which can use
|
|
9
|
+
# the various methods described here to describe the script. In the context of a
|
|
10
|
+
# Brut CLI, your subclass would be used inside the {Brut::CLI::Command#execute} method, where
|
|
11
|
+
# it would call `#run!` (not `#execute`).
|
|
12
|
+
#
|
|
13
|
+
# Inside `#execute` you should call `#phase` for each logical phase/grouping of steps. There must be at least
|
|
14
|
+
# one phase. Inside each `phase` you should call `#step` one or more times. When a phase is actually executed,
|
|
15
|
+
# it's contents are executed, so anything inside the block will be called. Intermediate variables to allow `step`s to
|
|
16
|
+
# communicate will work.
|
|
17
|
+
#
|
|
18
|
+
# After all phases, call `done` with a success message.
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# def execute
|
|
22
|
+
# phase "Set up test data" do
|
|
23
|
+
# step "Initializing database",
|
|
24
|
+
# exec: "bin/db rebuild -e test"
|
|
25
|
+
# step "Loading data" do
|
|
26
|
+
# MyData.each do |data|
|
|
27
|
+
# notify "Inserting #{data}"
|
|
28
|
+
# data.insert!
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
# phase "Checking data integrity" do
|
|
33
|
+
# problems = []
|
|
34
|
+
# step "Analyzing data" do
|
|
35
|
+
# MyData.each do |data|
|
|
36
|
+
# if !data.analyze
|
|
37
|
+
# problems << data
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
# end
|
|
41
|
+
# step "Checking problems" do
|
|
42
|
+
# abort = false
|
|
43
|
+
# problems.each do |problem|
|
|
44
|
+
# if problem.warning?
|
|
45
|
+
# warning "#{problem} may be an issue, but we can proceed"
|
|
46
|
+
# else
|
|
47
|
+
# error "#{problem} will prevent our test from working"
|
|
48
|
+
# abort = true
|
|
49
|
+
# end
|
|
50
|
+
# end
|
|
51
|
+
# if abort
|
|
52
|
+
# raise "Problems prevented script from working"
|
|
53
|
+
# end
|
|
54
|
+
# end
|
|
55
|
+
# end
|
|
56
|
+
#
|
|
57
|
+
# done "All ready to go!"
|
|
58
|
+
# end
|
|
59
|
+
class Brut::TUI::Script
|
|
60
|
+
|
|
61
|
+
autoload(:Step , "brut/tui/script/step")
|
|
62
|
+
autoload(:BlockStep , "brut/tui/script/block_step")
|
|
63
|
+
autoload(:ExecStep , "brut/tui/script/exec_step")
|
|
64
|
+
autoload(:LoggingSubscriber , "brut/tui/script/logging_subscriber")
|
|
65
|
+
autoload(:PutsSubscriber , "brut/tui/script/puts_subscriber")
|
|
66
|
+
autoload(:Events , "brut/tui/script/events")
|
|
67
|
+
|
|
68
|
+
# Return the basename of a log file unique to this script. This will use
|
|
69
|
+
# the subclass name to come up with a reasonable name, however your
|
|
70
|
+
# class can override this.
|
|
71
|
+
def self.log_filename
|
|
72
|
+
name = self.name.gsub(/Script$/,"").gsub(/::$/,"").split("::").last
|
|
73
|
+
name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Create the script. By default, two subscribers are set up: {Brut::TUI::Script::LoggingSubscriber}
|
|
77
|
+
# and {Brut::TUI::Script::PutsSubscriber}. The logging subscriber will output a detailed
|
|
78
|
+
# log into `logs/` using the file named by {.log_filename}. The `PutsSubscriber` will
|
|
79
|
+
# output ANSI-Fancy messages as the script proceeds.
|
|
80
|
+
#
|
|
81
|
+
# @param root_dir [String|Pathname] path to the root of the Brut project.
|
|
82
|
+
# @param ansi [true|false] if true, the {Brut::TUI::Script::PutsSubscriber} will use
|
|
83
|
+
# ANSI escape codes to format the output. If false, it won't.
|
|
84
|
+
def initialize(root_dir: nil, ansi: true)
|
|
85
|
+
@root_dir = root_dir ? Pathname(root_dir).expand_path : nil
|
|
86
|
+
logs_dir = if @root_dir
|
|
87
|
+
@root_dir / "logs"
|
|
88
|
+
else
|
|
89
|
+
Pathame(".") / "logs"
|
|
90
|
+
end
|
|
91
|
+
FileUtils.mkdir_p(logs_dir)
|
|
92
|
+
@event_loop = Brut::TUI::EventLoop.new
|
|
93
|
+
|
|
94
|
+
logging_subscriber = LoggingSubscriber.new($0, logfile: logs_dir / "#{self.class.log_filename}.log")
|
|
95
|
+
terminal = Brut::TUI::Terminal.new
|
|
96
|
+
theme = if ansi
|
|
97
|
+
Brut::TUI::TerminalTheme.based_on_background(terminal)
|
|
98
|
+
else
|
|
99
|
+
Brut::TUI::Themes::None.new(terminal)
|
|
100
|
+
end
|
|
101
|
+
puts_subscriber = Brut::TUI::Script::PutsSubscriber.new($0, terminal:, theme:)
|
|
102
|
+
|
|
103
|
+
@event_loop.subscribe(Brut::TUI::Events::EventLoopStarted,self)
|
|
104
|
+
@event_loop.subscribe_to_all(logging_subscriber)
|
|
105
|
+
@event_loop.subscribe_to_all(puts_subscriber)
|
|
106
|
+
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @!visibility private
|
|
110
|
+
def on_event_loop_started(event)
|
|
111
|
+
Thread.new do
|
|
112
|
+
begin
|
|
113
|
+
@phases = []
|
|
114
|
+
self.execute
|
|
115
|
+
@event_loop << Events::ScriptStarted.new(phases: @phases)
|
|
116
|
+
@phases.each_with_index do |(name,block), index|
|
|
117
|
+
step_number = index + 1
|
|
118
|
+
@event_loop << Events::PhaseStarted.new(name, step_number: step_number, total_steps: @phases.length)
|
|
119
|
+
block.()
|
|
120
|
+
@event_loop << Events::PhaseCompleted.new(name, step_number: step_number, total_steps: @phases.length)
|
|
121
|
+
end
|
|
122
|
+
@event_loop << Events::Message.new(message: @done_message || "Script completed successfully", type: :done)
|
|
123
|
+
rescue => ex
|
|
124
|
+
@event_loop << Brut::TUI::Events::Exception.new(ex)
|
|
125
|
+
end
|
|
126
|
+
@event_loop << Events::ScriptCompleted.new
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Entry point for a script. This method starts the event loop.
|
|
131
|
+
def run!
|
|
132
|
+
if !self.methods.include?(:execute)
|
|
133
|
+
raise "You must implement the execute method in your Brut::TUI::Script subclass"
|
|
134
|
+
end
|
|
135
|
+
@event_loop.run
|
|
136
|
+
0
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Create a new phase for the script. A phase is basically a named group
|
|
140
|
+
# of steps. The block is not executed immediately, so you may not pass
|
|
141
|
+
# data from one phase to another.
|
|
142
|
+
#
|
|
143
|
+
# @param name [String] The name of the phase
|
|
144
|
+
# @param block [Proc] The block to execute for the phase
|
|
145
|
+
def phase(name, &block)
|
|
146
|
+
@phases << [ name, block ]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# A step to run inside a phase. Steps within phases are executed immediately once the phase
|
|
150
|
+
# has started, so they can pass data to one anther.
|
|
151
|
+
#
|
|
152
|
+
# @param description [String] Message to show the user about this step.
|
|
153
|
+
# @param exec [String|nil] if non-nil, this step will execute this as a command. A block given is ignored.
|
|
154
|
+
# If `nil`, a block should be given that contains the step's code.
|
|
155
|
+
# @yield if `exec` is `nil`, this block will be executed for the step
|
|
156
|
+
def step(description, exec: nil, &block)
|
|
157
|
+
step = if exec.nil?
|
|
158
|
+
BlockStep.new(@event_loop, description, &block)
|
|
159
|
+
else
|
|
160
|
+
ExecStep.new(@event_loop, description, command: exec)
|
|
161
|
+
end
|
|
162
|
+
step.run!
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Notify the user of an event
|
|
166
|
+
#
|
|
167
|
+
# @param message [String] Message to show
|
|
168
|
+
def notify(message)
|
|
169
|
+
@event_loop << Events::Message.new(message:, type: :notification)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Warn the user of an event
|
|
173
|
+
#
|
|
174
|
+
# @param message [String] Message to show
|
|
175
|
+
def warning(message)
|
|
176
|
+
@event_loop << Events::Message.new(message:, type: :warning)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Let the user know something succeeded
|
|
180
|
+
#
|
|
181
|
+
# @param message [String] Message to show
|
|
182
|
+
def success(message)
|
|
183
|
+
@event_loop << Events::Message.new(message:, type: :success)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Message to show if the script completes successful. Should only be called once
|
|
187
|
+
# and not inside a phase or step.
|
|
188
|
+
#
|
|
189
|
+
# @param message [String] Message to show
|
|
190
|
+
def done(message)
|
|
191
|
+
@done_message = message
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Let the user know there was an error. Note that this will not stop
|
|
195
|
+
# the script. Raise an exception to do that.
|
|
196
|
+
#
|
|
197
|
+
# @param message [String] Message to show
|
|
198
|
+
def error(message)
|
|
199
|
+
@event_loop << Events::Message.new(message:, type: :error)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Wrap a fully-qualified filename in code markup and trim the path to only
|
|
203
|
+
# show the path relative to the root dir. This is much friendlier than showing a
|
|
204
|
+
# long expanded path.
|
|
205
|
+
#
|
|
206
|
+
# @param path [String|Pathname] a fully-qualified path to something inside your Brut app.
|
|
207
|
+
def filename(path)
|
|
208
|
+
path = Pathname(path).expand_path
|
|
209
|
+
"`" + (@root_dir ? path.relative_path_from(@root_dir) : path).to_s + "`"
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require "io/console"
|
|
2
|
+
require "timeout"
|
|
3
|
+
require "stringio"
|
|
4
|
+
|
|
5
|
+
# Stores metdata about the current temrinal in use. This should provide any metadata
|
|
6
|
+
# about the terminal that can be reliably determined.
|
|
7
|
+
class Brut::TUI::Terminal
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
trap("WINCH") do
|
|
11
|
+
@winsize = IO.console.winsize
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Number of rows for the current terminal. Should be consistent even after a resize
|
|
16
|
+
def rows = winsize[0]
|
|
17
|
+
# Number of columns for the current terminal. Should be consistent even after a resize
|
|
18
|
+
def cols = winsize[1]
|
|
19
|
+
|
|
20
|
+
# The IO used for outputing information to the terminal. Prefer this over `STDOUT`.
|
|
21
|
+
def io = $stdout
|
|
22
|
+
|
|
23
|
+
# The IO to use for requesting input from the user
|
|
24
|
+
def stdin = $stdin
|
|
25
|
+
|
|
26
|
+
# Best attempt to guess the background color of the current terminal.
|
|
27
|
+
# This will not refresh if that color is changed, and its behavior will
|
|
28
|
+
# default to black if determining the color doesn't work or isn't supported.
|
|
29
|
+
def background_color
|
|
30
|
+
@bakground_color ||= begin
|
|
31
|
+
io.write "\e]11;?\a"
|
|
32
|
+
io.flush
|
|
33
|
+
|
|
34
|
+
result = nil
|
|
35
|
+
|
|
36
|
+
begin
|
|
37
|
+
# Read the reply, which should look like:
|
|
38
|
+
# ESC ] 11 ; rgb:0000/0000/0000 BEL
|
|
39
|
+
Timeout.timeout(0.1) do
|
|
40
|
+
buf = +""
|
|
41
|
+
loop do
|
|
42
|
+
ch = stdin.getch
|
|
43
|
+
buf << ch
|
|
44
|
+
break if ch == "\a" || buf.end_with?("\e\\") # BEL or ST terminator
|
|
45
|
+
end
|
|
46
|
+
result = buf
|
|
47
|
+
end
|
|
48
|
+
rescue Timeout::Error
|
|
49
|
+
# no reply within 100ms — terminal probably doesn’t support it
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
rgb = [ 0, 0, 0 ]
|
|
53
|
+
if result
|
|
54
|
+
if result =~ /\e\]11;([^ \a\e]*)[\a\e\\]/
|
|
55
|
+
color = Regexp.last_match(1)
|
|
56
|
+
parts = color[/rgb:(.*)/, 1]&.split("/")
|
|
57
|
+
if parts
|
|
58
|
+
|
|
59
|
+
rgb = parts.map { it.to_i(16) / 0xffff.to_f * 256 }.map(&:to_i)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
rgb
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def winsize
|
|
71
|
+
@winsize ||= IO.console.winsize
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# A terminal theme is a set of semantic styles for the terminal that
|
|
2
|
+
# map to actual colors or styles based on the terminal's metadata or
|
|
3
|
+
# the user's preferences or both.
|
|
4
|
+
#
|
|
5
|
+
# This class is coupled to the behavior of ANSI escape
|
|
6
|
+
# codes in the terminal, in that they aren't markup, but more
|
|
7
|
+
# like drawing with a pen.
|
|
8
|
+
|
|
9
|
+
# That said, this class' API does abstract from the codes
|
|
10
|
+
# themselves and creates a semantic layer, for example
|
|
11
|
+
# the definition of "succes" or "weak".
|
|
12
|
+
|
|
13
|
+
# For subclass implementors, there are various private
|
|
14
|
+
# methods that do encapsulate the ANSI escape codes, and these
|
|
15
|
+
# can be used or overridden.
|
|
16
|
+
#
|
|
17
|
+
# This particular implementation avoids the use of black or white, and uses
|
|
18
|
+
# the normal ANSI colors. This should, in theory, work with any terminal
|
|
19
|
+
# theme where all colors are legible on the chosen background.
|
|
20
|
+
class Brut::TUI::TerminalTheme
|
|
21
|
+
|
|
22
|
+
def self.based_on_background(terminal)
|
|
23
|
+
if dark_background?(terminal)
|
|
24
|
+
Brut::TUI::Themes::Dark.new(terminal)
|
|
25
|
+
else
|
|
26
|
+
Brut::TUI::Themes::Light.new(terminal)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.dark_background?(terminal)
|
|
31
|
+
r, g, b = terminal.background_color.map { it / 255.0 }
|
|
32
|
+
luminance = (0.21 * r) + (0.72 * g) + (0.07 * b)
|
|
33
|
+
|
|
34
|
+
return luminance < 0.5
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initialize(terminal)
|
|
38
|
+
@terminal = terminal
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns a string with its markup turned into escape codes.
|
|
42
|
+
# Note that due to the way ANSI escape codes work, the state
|
|
43
|
+
# of the terminal may not be in the same state you found it.
|
|
44
|
+
#
|
|
45
|
+
# This method tries to turn features on and off (e.g. after a bold
|
|
46
|
+
# string, the codes to turn off bold are applied), but if a subclass
|
|
47
|
+
# mixes colors and styles, any text output after this one may
|
|
48
|
+
# not look like the text before it.
|
|
49
|
+
def with_markup(string, text: :normal, reset: true)
|
|
50
|
+
result = +""
|
|
51
|
+
result << send(text)
|
|
52
|
+
Brut::TUI::MarkupString.from_string(string).parse do |directive, value|
|
|
53
|
+
case directive
|
|
54
|
+
in :start
|
|
55
|
+
case value
|
|
56
|
+
in :bold
|
|
57
|
+
result << bold
|
|
58
|
+
in :strike
|
|
59
|
+
result << strike
|
|
60
|
+
in :code
|
|
61
|
+
result << code
|
|
62
|
+
in :weak
|
|
63
|
+
result << weak
|
|
64
|
+
end
|
|
65
|
+
in :stop
|
|
66
|
+
case value
|
|
67
|
+
in :bold
|
|
68
|
+
result << bold_off << send(text)
|
|
69
|
+
in :strike
|
|
70
|
+
result << strike_off << send(text)
|
|
71
|
+
in :code
|
|
72
|
+
result << code_off << send(text)
|
|
73
|
+
in :weak
|
|
74
|
+
result << weak_off << send(text)
|
|
75
|
+
end
|
|
76
|
+
in :text
|
|
77
|
+
result << value
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
if reset
|
|
81
|
+
result << self.reset
|
|
82
|
+
end
|
|
83
|
+
result
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def success = bold + bright_green
|
|
87
|
+
def error = bold + bright_red
|
|
88
|
+
def warning = bold + yellow
|
|
89
|
+
|
|
90
|
+
def bold = esc("1")
|
|
91
|
+
def bold_off = normal
|
|
92
|
+
|
|
93
|
+
def bright = esc("1")
|
|
94
|
+
def bright_off = normal
|
|
95
|
+
|
|
96
|
+
def italic = esc("3")
|
|
97
|
+
def italic_off = esc("23")
|
|
98
|
+
|
|
99
|
+
def strike = esc("9")
|
|
100
|
+
def strike_off = esc("29")
|
|
101
|
+
|
|
102
|
+
def weak = esc("2")
|
|
103
|
+
def weak_off = normal
|
|
104
|
+
|
|
105
|
+
def code = underline
|
|
106
|
+
def code_off = underline_off
|
|
107
|
+
|
|
108
|
+
def normal = esc("22")
|
|
109
|
+
|
|
110
|
+
def reset = esc("0")
|
|
111
|
+
|
|
112
|
+
def heading = bold + underline + bright_blue
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def underline = esc("4")
|
|
117
|
+
def underline_off = esc("24")
|
|
118
|
+
|
|
119
|
+
def black = esc("30")
|
|
120
|
+
def red = esc("31")
|
|
121
|
+
def green = esc("32")
|
|
122
|
+
def yellow = esc("33")
|
|
123
|
+
def blue = esc("34")
|
|
124
|
+
def magenta = esc("35")
|
|
125
|
+
def cyan = esc("36")
|
|
126
|
+
def white = esc("37")
|
|
127
|
+
|
|
128
|
+
def bright_black = esc("90")
|
|
129
|
+
def bright_red = esc("91")
|
|
130
|
+
def bright_green = esc("92")
|
|
131
|
+
def bright_yellow = esc("93")
|
|
132
|
+
def bright_blue = esc("94")
|
|
133
|
+
def bright_magenta = esc("95")
|
|
134
|
+
def bright_cyan = esc("96")
|
|
135
|
+
def bright_white = esc("97")
|
|
136
|
+
|
|
137
|
+
def esc(code)
|
|
138
|
+
"\e[#{code}m"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# A theme designed to work on a dark background terminal.
|
|
2
|
+
class Brut::TUI::Themes::Dark < Brut::TUI::TerminalTheme
|
|
3
|
+
def black = esc("38;2;96;96;96")
|
|
4
|
+
def bright_black = esc("38;2;128;128;128")
|
|
5
|
+
def white = esc("38;2;233;233;233")
|
|
6
|
+
def bright_white = esc("38;2;255;255;255")
|
|
7
|
+
def yellow = esc("38;2;191;191;0")
|
|
8
|
+
def bright_yellow = esc("38;2;255;255;0")
|
|
9
|
+
def normal = super + white
|
|
10
|
+
def code = esc("38;2;0;255;0")
|
|
11
|
+
def code_off = normal
|
|
12
|
+
def bright = bright_white
|
|
13
|
+
def bright_off = normal
|
|
14
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# A theme designed to work on a white background terminal.
|
|
2
|
+
class Brut::TUI::Themes::Light < Brut::TUI::TerminalTheme
|
|
3
|
+
def black = esc("38;2;0;0;0")
|
|
4
|
+
def bright_black = esc("38;2;64;64;64")
|
|
5
|
+
def bright_white = esc("38;2;128;128;128")
|
|
6
|
+
def white = esc("38;2;191;191;191")
|
|
7
|
+
def yellow = esc("38;2;191;191;0")
|
|
8
|
+
def bright_yellow = esc("38;2;255;255;0")
|
|
9
|
+
def normal = super + black
|
|
10
|
+
def code = esc("38;2;0;128;0")
|
|
11
|
+
def code_off = normal
|
|
12
|
+
def green = esc("38;2;25;105;0")
|
|
13
|
+
def bright_green = esc("38;2;34;155;0")
|
|
14
|
+
def bright = bright_blue
|
|
15
|
+
def bright_off = normal
|
|
16
|
+
end
|
|
17
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# A theme without any ANSI styling at all. This is suitable
|
|
2
|
+
# for showing to a user who does not want any colors or styles, but
|
|
3
|
+
# still wants to see output in a somewhat conventional way.
|
|
4
|
+
class Brut::TUI::Themes::None < Brut::TUI::TerminalTheme
|
|
5
|
+
def esc(*) = ""
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
data/lib/brut/tui.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Brut
|
|
2
|
+
# Brut provides a basic API for creating rich text user interfaces. Currently, Brut
|
|
3
|
+
# provides {Brut::TUI::Script} to create more user-friendly scripts like `bin/setup` or
|
|
4
|
+
# `bin/ci`.
|
|
5
|
+
module TUI
|
|
6
|
+
autoload(:EventLoop, "brut/tui/event_loop")
|
|
7
|
+
autoload(:Events, "brut/tui/events")
|
|
8
|
+
autoload(:AnsiEscapeCode, "brut/tui/ansi_escape_code")
|
|
9
|
+
autoload(:Script, "brut/tui/script")
|
|
10
|
+
autoload(:Terminal, "brut/tui/terminal")
|
|
11
|
+
autoload(:TerminalTheme, "brut/tui/terminal_theme")
|
|
12
|
+
autoload(:Themes, "brut/tui/themes")
|
|
13
|
+
autoload(:MarkupString, "brut/tui/markup_string")
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/brut/version.rb
CHANGED
data/lib/brut.rb
CHANGED
|
@@ -16,5 +16,6 @@ module Brut
|
|
|
16
16
|
autoload(:Instrumentation,"brut/instrumentation")
|
|
17
17
|
autoload(:SinatraHelpers, "brut/sinatra_helpers")
|
|
18
18
|
# DO NOT autoload(:CLI) - that is intended to be require-able on its own
|
|
19
|
+
# DO NOT autoload(:TUI) - that is intended to be require-able on its own
|
|
19
20
|
end
|
|
20
21
|
require "sequel/plugins"
|
data/mkbrut/Gemfile.lock
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "brut/tui"
|
|
3
|
+
|
|
4
|
+
RSpec.describe Brut::TUI::AnsiEscapeCode do
|
|
5
|
+
describe "#to_s" do
|
|
6
|
+
it "returns the ANSI escape code as a string" do
|
|
7
|
+
code = Brut::TUI::AnsiEscapeCode.new(:red, "31")
|
|
8
|
+
expect(code.to_s).to eq("\e[31m")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe ".ansi" do
|
|
13
|
+
it "returns the correct ANSI escape code for a given name" do
|
|
14
|
+
red_code = Brut::TUI::AnsiEscapeCode.ansi(:red)
|
|
15
|
+
expect(red_code.to_s).to eq("\e[31m")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "returns an RGB ANSI escape code when given RGB values" do
|
|
19
|
+
rgb_code = Brut::TUI::AnsiEscapeCode.ansi(255, 0, 0)
|
|
20
|
+
expect(rgb_code.to_s).to eq("\e[38;2;255;0;0m")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "returns a fluent objects when called without arguments" do
|
|
24
|
+
fluent = Brut::TUI::AnsiEscapeCode.ansi
|
|
25
|
+
expect(fluent.bold.to_s).to eq("\e[1m")
|
|
26
|
+
expect(fluent.red.to_s).to eq("\e[31m")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "brut/tui"
|
|
3
|
+
|
|
4
|
+
RSpec.describe Brut::TUI::EventLoop do
|
|
5
|
+
subject(:event_loop) { described_class.new(tick: false) }
|
|
6
|
+
|
|
7
|
+
class ExitEvent < Brut::TUI::Events::BaseEvent
|
|
8
|
+
def exit? = true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class RaiseExceptionEvent < Brut::TUI::Events::BaseEvent
|
|
12
|
+
def initialize(message)
|
|
13
|
+
@message = message
|
|
14
|
+
end
|
|
15
|
+
def raise_exception!
|
|
16
|
+
raise StandardError.new(@message)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class GeneralSubscriber
|
|
21
|
+
attr_reader :events
|
|
22
|
+
def initialize
|
|
23
|
+
@events = []
|
|
24
|
+
end
|
|
25
|
+
def on_any_event(event)
|
|
26
|
+
if event.respond_to?(:raise_exception!)
|
|
27
|
+
event.raise_exception!
|
|
28
|
+
else
|
|
29
|
+
@events << event
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "stops the loop when an event returns true for exit?" do
|
|
35
|
+
main_thread = Thread.new {
|
|
36
|
+
event_loop.run
|
|
37
|
+
}
|
|
38
|
+
event_loop << ExitEvent.new
|
|
39
|
+
main_thread.join(0.1)
|
|
40
|
+
expect(main_thread.alive?).to eq(false)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def start_loop(event_loop, wait_time: 0.1)
|
|
44
|
+
exception = nil
|
|
45
|
+
main_thread = Thread.new {
|
|
46
|
+
begin
|
|
47
|
+
event_loop.run
|
|
48
|
+
rescue => ex
|
|
49
|
+
exception = ex
|
|
50
|
+
end
|
|
51
|
+
}
|
|
52
|
+
main_thread.join(wait_time)
|
|
53
|
+
expect(exception).to eq(nil)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "fires the event LoopStarted event when run" do
|
|
57
|
+
general_subscriber = GeneralSubscriber.new
|
|
58
|
+
event_loop.subscribe_to_all(general_subscriber)
|
|
59
|
+
start_loop(event_loop)
|
|
60
|
+
expect(general_subscriber.events[0].class).to eq(Brut::TUI::Events::EventLoopStarted)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "fires exception events when errors are raised" do
|
|
64
|
+
general_subscriber = GeneralSubscriber.new
|
|
65
|
+
event_loop.subscribe_to_all(general_subscriber)
|
|
66
|
+
event_loop << RaiseExceptionEvent.new("Test exception")
|
|
67
|
+
start_loop(event_loop)
|
|
68
|
+
expect(general_subscriber.events[1].class).to eq(Brut::TUI::Events::Exception)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "brut/tui"
|
|
3
|
+
|
|
4
|
+
RSpec.describe Brut::TUI::Events::BaseEvent do
|
|
5
|
+
class TestEvent < Brut::TUI::Events::BaseEvent; end
|
|
6
|
+
module Deeply
|
|
7
|
+
module Nested
|
|
8
|
+
class TestEvent < Brut::TUI::Events::BaseEvent; end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe ".handler_method_name" do
|
|
13
|
+
it "returns the underscorized simple class name prefixed with 'on_'" do
|
|
14
|
+
|
|
15
|
+
expect(TestEvent.handler_method_name).to eq("on_test_event")
|
|
16
|
+
expect(Deeply::Nested::TestEvent.handler_method_name).to eq("on_test_event")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "#exit?" do
|
|
21
|
+
it "returns false by default" do
|
|
22
|
+
event = Brut::TUI::Events::BaseEvent.new
|
|
23
|
+
expect(event.exit?).to be false
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|