brut 0.0.20 → 0.0.22
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 +24 -3
- data/.nvim.lua +1 -0
- data/Dockerfile.dx +12 -3
- data/Gemfile.lock +9 -7
- data/README.md +0 -7
- data/Rakefile +6 -4
- data/bin/dev +20 -0
- data/bin/docs +27 -0
- data/bin/setup +47 -1
- data/brut-css/.nvim.lua +1 -0
- data/brut-css/README.md +28 -0
- data/brut-css/bin/build +31 -0
- data/brut-css/bin/dev +1 -0
- data/brut-css/bin/docs +15 -0
- data/brut-css/bin/setup +5 -0
- data/brut-css/config/media-queries-all.css +15 -0
- data/brut-css/config/media-queries-minimal.css +5 -0
- data/brut-css/config/postcss.config.cjs +7 -0
- data/brut-css/config/pseudo-classes-all.css +9 -0
- data/brut-css/dx +1 -0
- data/brut-css/package-lock.json +3217 -0
- data/brut-css/package.json +36 -0
- data/brut-css/src/css/appearance.css +145 -0
- data/brut-css/src/css/border.css +522 -0
- data/brut-css/src/css/colors.css +3502 -0
- data/brut-css/src/css/dimensions.css +548 -0
- data/brut-css/src/css/flex.css +179 -0
- data/brut-css/src/css/index.css +13 -0
- data/brut-css/src/css/layout.css +120 -0
- data/brut-css/src/css/list.css +41 -0
- data/brut-css/src/css/positioning.css +354 -0
- data/brut-css/src/css/properties/colors.css +455 -0
- data/brut-css/src/css/properties/index.css +3 -0
- data/brut-css/src/css/properties/spacing.css +140 -0
- data/brut-css/src/css/properties/typography.css +224 -0
- data/brut-css/src/css/reset.css +107 -0
- data/brut-css/src/css/spacing.css +585 -0
- data/brut-css/src/css/typography.css +519 -0
- data/brut-css/src/css/utils.css +104 -0
- data/brut-css/src/docs/1_getting-started/1_overview.md +46 -0
- data/brut-css/src/docs/1_getting-started/2_installation.md +25 -0
- data/brut-css/src/docs/1_getting-started/3_core-concepts.md +75 -0
- data/brut-css/src/docs/1_getting-started/4_simple-example.md +132 -0
- data/brut-css/src/docs/1_getting-started/page.html.ejs +10 -0
- data/brut-css/src/docs/2_properties/page.html.ejs +71 -0
- data/brut-css/src/docs/3_classes/color-demo.html.ejs +31 -0
- data/brut-css/src/docs/3_classes/page.html.ejs +87 -0
- data/brut-css/src/docs/4_customization/1_design-system.md +36 -0
- data/brut-css/src/docs/4_customization/2_breakpoints.md +75 -0
- data/brut-css/src/docs/4_customization/3_pseudo-classes.md +74 -0
- data/brut-css/src/docs/4_customization/4_advanced-configuration.md +40 -0
- data/brut-css/src/docs/4_customization/page.html.ejs +10 -0
- data/brut-css/src/docs/docs.css +98 -0
- data/brut-css/src/docs/includes/body-and-header.html.ejs +30 -0
- data/brut-css/src/docs/includes/footer-and-rest.html.ejs +9 -0
- data/brut-css/src/docs/includes/head.html.ejs +5 -0
- data/brut-css/src/docs/includes/nav.html.ejs +10 -0
- data/brut-css/src/docs/index.html.ejs +32 -0
- data/brut-css/src/docs/prism-twilight.min.css +1 -0
- data/brut-css/src/js/Logger.js +71 -0
- data/brut-css/src/js/build.js +111 -0
- data/brut-css/src/js/cli/CLIArgError.js +7 -0
- data/brut-css/src/js/cli/Debug.js +27 -0
- data/brut-css/src/js/cli/DocsDir.js +16 -0
- data/brut-css/src/js/cli/DocsTemplateSourceDir.js +16 -0
- data/brut-css/src/js/cli/InputFile.js +31 -0
- data/brut-css/src/js/cli/MediaQueryConfigFile.js +10 -0
- data/brut-css/src/js/cli/OutputFile.js +22 -0
- data/brut-css/src/js/cli/ParsedArg.js +17 -0
- data/brut-css/src/js/cli/PathToBrutCSSRoot.js +19 -0
- data/brut-css/src/js/cli/PseudoClassConfigFile.js +11 -0
- data/brut-css/src/js/cli.js +108 -0
- data/brut-css/src/js/docGenerator.js +467 -0
- data/brut-css/src/js/mediaQueryConfigParser.js +98 -0
- data/brut-css/src/js/post-css-plugins/addMediaQueriesPlugin.js +49 -0
- data/brut-css/src/js/post-css-plugins/addPseudoClassesPlugin.js +42 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Category.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/DocState.js +185 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Documentable.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Group.js +7 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/ParsedComment.js +73 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Property.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyCategory.js +4 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyGroup.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Rule.js +12 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleCategory.js +4 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleGroup.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeRef.js +5 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeURL.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin.js +49 -0
- data/brut-css/src/js/post-css-plugins/generateRootCustomPropertiesPlugin.js +45 -0
- data/brut-css/src/js/pseudoClassConfigParser.js +145 -0
- data/brut-js/.projections.json +10 -0
- data/brut-js/README.md +118 -0
- data/brut-js/bin/build +10 -0
- data/brut-js/bin/ci +5 -0
- data/brut-js/bin/setup +5 -0
- data/brut-js/docs/README.md +8 -0
- data/brut-js/docs/jsdoc-plugins/customElementTag.js +8 -0
- data/brut-js/docs/jsdoc-theme/publish.js +692 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/linenumber.js +25 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/Apache-License-2.0.txt +202 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/lang-css.js +2 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/prettify.js +28 -0
- data/brut-js/docs/jsdoc-theme/static/styles/jsdoc-default.css +327 -0
- data/brut-js/docs/jsdoc-theme/static/styles/prettify-jsdoc.css +111 -0
- data/brut-js/docs/jsdoc-theme/static/styles/prettify-tomorrow.css +132 -0
- data/brut-js/docs/jsdoc-theme/tmpl/augments.tmpl +10 -0
- data/brut-js/docs/jsdoc-theme/tmpl/container.tmpl +199 -0
- data/brut-js/docs/jsdoc-theme/tmpl/details.tmpl +143 -0
- data/brut-js/docs/jsdoc-theme/tmpl/example.tmpl +2 -0
- data/brut-js/docs/jsdoc-theme/tmpl/examples.tmpl +13 -0
- data/brut-js/docs/jsdoc-theme/tmpl/exceptions.tmpl +32 -0
- data/brut-js/docs/jsdoc-theme/tmpl/layout.tmpl +38 -0
- data/brut-js/docs/jsdoc-theme/tmpl/mainpage.tmpl +14 -0
- data/brut-js/docs/jsdoc-theme/tmpl/members.tmpl +38 -0
- data/brut-js/docs/jsdoc-theme/tmpl/method.tmpl +131 -0
- data/brut-js/docs/jsdoc-theme/tmpl/modifies.tmpl +14 -0
- data/brut-js/docs/jsdoc-theme/tmpl/params.tmpl +131 -0
- data/brut-js/docs/jsdoc-theme/tmpl/properties.tmpl +108 -0
- data/brut-js/docs/jsdoc-theme/tmpl/returns.tmpl +19 -0
- data/brut-js/docs/jsdoc-theme/tmpl/source.tmpl +8 -0
- data/brut-js/docs/jsdoc-theme/tmpl/tutorial.tmpl +19 -0
- data/brut-js/docs/jsdoc-theme/tmpl/type.tmpl +7 -0
- data/brut-js/docs/jsdoc.config.json +23 -0
- data/brut-js/docs/package-lock.json +343 -0
- data/brut-js/docs/package.json +7 -0
- data/brut-js/package-lock.json +2171 -0
- data/brut-js/package.json +32 -0
- data/brut-js/specs/AjaxSubmit.spec.js +256 -0
- data/brut-js/specs/Autosubmit.spec.js +127 -0
- data/brut-js/specs/ConfirmSubmit.spec.js +193 -0
- data/brut-js/specs/ConstraintViolationMessage.spec.js +33 -0
- data/brut-js/specs/ConstraintViolationMessages.spec.js +29 -0
- data/brut-js/specs/CopyToClipboard.spec.js +35 -0
- data/brut-js/specs/Form.spec.js +181 -0
- data/brut-js/specs/I18nTranslation.spec.js +19 -0
- data/brut-js/specs/LocaleDetection.spec.js +22 -0
- data/brut-js/specs/Message.spec.js +15 -0
- data/brut-js/specs/SpecHelper.js +23 -0
- data/brut-js/specs/Tabs.spec.js +41 -0
- data/brut-js/specs/config/asset_metadata.json +7 -0
- data/brut-js/src/AjaxSubmit.js +384 -0
- data/brut-js/src/Autosubmit.js +63 -0
- data/brut-js/src/BaseCustomElement.js +261 -0
- data/brut-js/src/ConfirmSubmit.js +116 -0
- data/brut-js/src/ConfirmationDialog.js +143 -0
- data/brut-js/src/ConstraintViolationMessage.js +125 -0
- data/brut-js/src/ConstraintViolationMessages.js +98 -0
- data/brut-js/src/CopyToClipboard.js +96 -0
- data/brut-js/src/Form.js +151 -0
- data/brut-js/src/I18nTranslation.js +61 -0
- data/brut-js/src/LocaleDetection.js +117 -0
- data/brut-js/src/Logger.js +90 -0
- data/brut-js/src/Message.js +56 -0
- data/brut-js/src/RichString.js +113 -0
- data/brut-js/src/Tabs.js +168 -0
- data/brut-js/src/Tracing.js +247 -0
- data/brut-js/src/appForTestingOnly.js +15 -0
- data/brut-js/src/index.js +130 -0
- data/brut-js/src/testing/AssetMetadata.js +35 -0
- data/brut-js/src/testing/AssetMetadataLoader.js +25 -0
- data/brut-js/src/testing/CustomElementTest.js +235 -0
- data/brut-js/src/testing/DOMCreator.js +45 -0
- data/brut-js/src/testing/index.js +48 -0
- data/brutrb.com/.vitepress/config.mjs +106 -0
- data/brutrb.com/.vitepress/plugins/jsdocLinker.js +34 -0
- data/brutrb.com/.vitepress/plugins/rdocLinker.js +18 -0
- data/brutrb.com/.vitepress/theme/custom.css +7 -0
- data/brutrb.com/.vitepress/theme/index.js +18 -0
- data/brutrb.com/.vitepress/theme/style.css +149 -0
- data/brutrb.com/ai.md +68 -0
- data/brutrb.com/assets.md +138 -0
- data/brutrb.com/bin/build +5 -0
- data/brutrb.com/bin/deploy +7 -0
- data/brutrb.com/bin/dev +5 -0
- data/brutrb.com/bin/setup +5 -0
- data/brutrb.com/brut-js.md +117 -0
- data/brutrb.com/business-logic.md +55 -0
- data/brutrb.com/cli.md +278 -0
- data/brutrb.com/components.md +243 -0
- data/brutrb.com/configuration.md +257 -0
- data/brutrb.com/css.md +103 -0
- data/brutrb.com/custom-element-tests.md +149 -0
- data/brutrb.com/database-access.md +201 -0
- data/brutrb.com/database-schema.md +312 -0
- data/brutrb.com/deployment.md +66 -0
- data/brutrb.com/dev-environment.md +179 -0
- data/brutrb.com/doc-conventions.md +39 -0
- data/brutrb.com/end-to-end-tests.md +174 -0
- data/brutrb.com/flash-and-session.md +224 -0
- data/brutrb.com/forms.md +866 -0
- data/brutrb.com/getting-started.md +66 -0
- data/brutrb.com/handlers.md +153 -0
- data/brutrb.com/hooks.md +178 -0
- data/brutrb.com/i18n.md +188 -0
- data/brutrb.com/images/Makefile +10 -0
- data/brutrb.com/images/dev-env-overview.dot +54 -0
- data/brutrb.com/images/dev-env-overview.png +0 -0
- data/brutrb.com/images/dev-env-protocol.dot +37 -0
- data/brutrb.com/images/dev-env-protocol.png +0 -0
- data/brutrb.com/images/logo-300.png +0 -0
- data/brutrb.com/images/logo.png +0 -0
- data/brutrb.com/images/overview.graffle +0 -0
- data/brutrb.com/images/overview.png +0 -0
- data/brutrb.com/images/spa.dot +19 -0
- data/brutrb.com/images/spa.png +0 -0
- data/brutrb.com/images/workspace-protocol.dot +44 -0
- data/brutrb.com/images/workspace-protocol.png +0 -0
- data/brutrb.com/index.md +36 -0
- data/brutrb.com/instrumentation.md +183 -0
- data/brutrb.com/javascript.md +122 -0
- data/brutrb.com/jobs.md +14 -0
- data/{doc-src → brutrb.com}/keyword-injection.md +122 -68
- data/brutrb.com/markdown-examples.md +85 -0
- data/brutrb.com/middleware.md +80 -0
- data/brutrb.com/not-released.md +5 -0
- data/brutrb.com/overview.md +404 -0
- data/brutrb.com/package-lock.json +2404 -0
- data/brutrb.com/package.json +11 -0
- data/brutrb.com/pages.md +378 -0
- data/brutrb.com/public/images/logo-300.png +0 -0
- data/brutrb.com/public/images/logo.png +0 -0
- data/brutrb.com/routes.md +215 -0
- data/brutrb.com/security.md +105 -0
- data/brutrb.com/seed-data.md +63 -0
- data/brutrb.com/space-time-continuum.md +85 -0
- data/brutrb.com/tutorial.md +3 -0
- data/brutrb.com/unit-tests.md +148 -0
- data/docker-compose.dx.yml +6 -3
- data/docs/404.html +21 -0
- data/docs/CNAME +1 -0
- data/docs/ai.html +24 -0
- data/docs/api/Brut/BackEnd/SeedData.html +493 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +214 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +125 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +125 -0
- data/docs/api/Brut/BackEnd/Sidekiq.html +125 -0
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +414 -0
- data/docs/api/Brut/BackEnd/Validators.html +128 -0
- data/docs/api/Brut/BackEnd.html +132 -0
- data/docs/api/Brut/CLI/App.html +1576 -0
- data/docs/api/Brut/CLI/AppRunner.html +491 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +264 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +306 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +262 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +314 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +183 -0
- data/docs/api/Brut/CLI/Apps/DB/Create.html +365 -0
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +357 -0
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +383 -0
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +335 -0
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +329 -0
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +347 -0
- data/docs/api/Brut/CLI/Apps/DB/Status.html +383 -0
- data/docs/api/Brut/CLI/Apps/DB.html +183 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +303 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +512 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +398 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +374 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +410 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +262 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +303 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +480 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +450 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +380 -0
- data/docs/api/Brut/CLI/Apps/Scaffold.html +253 -0
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +464 -0
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +407 -0
- data/docs/api/Brut/CLI/Apps/Test/JS.html +262 -0
- data/docs/api/Brut/CLI/Apps/Test/Run.html +578 -0
- data/docs/api/Brut/CLI/Apps/Test.html +253 -0
- data/docs/api/Brut/CLI/Apps.html +125 -0
- data/docs/api/Brut/CLI/Command.html +2342 -0
- data/docs/api/Brut/CLI/Error.html +139 -0
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +664 -0
- data/docs/api/Brut/CLI/ExecutionResults.html +675 -0
- data/docs/api/Brut/CLI/Executor.html +430 -0
- data/docs/api/Brut/CLI/InvalidOption.html +245 -0
- data/docs/api/Brut/CLI/Options.html +753 -0
- data/docs/api/Brut/CLI/Output.html +699 -0
- data/docs/api/Brut/CLI/SystemExecError.html +451 -0
- data/docs/api/Brut/CLI.html +263 -0
- data/docs/api/Brut/FactoryBot.html +225 -0
- data/docs/api/Brut/Framework/App.html +1097 -0
- data/docs/api/Brut/Framework/Config.html +1045 -0
- data/docs/api/Brut/Framework/Container.html +1379 -0
- data/docs/api/Brut/Framework/Error.html +140 -0
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +144 -0
- data/docs/api/Brut/Framework/Errors/Bug.html +234 -0
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +257 -0
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +273 -0
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +471 -0
- data/docs/api/Brut/Framework/Errors/NotFound.html +308 -0
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +234 -0
- data/docs/api/Brut/Framework/Errors.html +328 -0
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +392 -0
- data/docs/api/Brut/Framework/MCP.html +861 -0
- data/docs/api/Brut/Framework/ProjectEnvironment.html +648 -0
- data/docs/api/Brut/Framework.html +129 -0
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +317 -0
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +326 -0
- data/docs/api/Brut/FrontEnd/Component.html +365 -0
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +470 -0
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +518 -0
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +317 -0
- data/docs/api/Brut/FrontEnd/Components/Input.html +195 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +339 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +660 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +417 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +918 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +651 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +125 -0
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +367 -0
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +336 -0
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +655 -0
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +352 -0
- data/docs/api/Brut/FrontEnd/Components.html +135 -0
- data/docs/api/Brut/FrontEnd/Download.html +467 -0
- data/docs/api/Brut/FrontEnd/Flash.html +1150 -0
- data/docs/api/Brut/FrontEnd/Form.html +1157 -0
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +634 -0
- data/docs/api/Brut/FrontEnd/Forms/Input.html +615 -0
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +547 -0
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1318 -0
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +609 -0
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +587 -0
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +613 -0
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +582 -0
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +609 -0
- data/docs/api/Brut/FrontEnd/Forms.html +127 -0
- data/docs/api/Brut/FrontEnd/GenericResponse.html +377 -0
- data/docs/api/Brut/FrontEnd/Handler.html +442 -0
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +318 -0
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +336 -0
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +399 -0
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +354 -0
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +151 -0
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +315 -0
- data/docs/api/Brut/FrontEnd/Handlers.html +125 -0
- data/docs/api/Brut/FrontEnd/HandlingResults.html +339 -0
- data/docs/api/Brut/FrontEnd/HttpMethod.html +661 -0
- data/docs/api/Brut/FrontEnd/HttpStatus.html +496 -0
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +284 -0
- data/docs/api/Brut/FrontEnd/Layout.html +318 -0
- data/docs/api/Brut/FrontEnd/Middleware.html +135 -0
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +288 -0
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +292 -0
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +324 -0
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +372 -0
- data/docs/api/Brut/FrontEnd/Middlewares.html +125 -0
- data/docs/api/Brut/FrontEnd/Page.html +773 -0
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +797 -0
- data/docs/api/Brut/FrontEnd/Pages.html +125 -0
- data/docs/api/Brut/FrontEnd/RequestContext.html +1312 -0
- data/docs/api/Brut/FrontEnd/RouteHook.html +424 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +242 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +249 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +264 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +261 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +284 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +252 -0
- data/docs/api/Brut/FrontEnd/RouteHooks.html +115 -0
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +227 -0
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +305 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +324 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +319 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +315 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +315 -0
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +327 -0
- data/docs/api/Brut/FrontEnd/Routing/Route.html +761 -0
- data/docs/api/Brut/FrontEnd/Routing.html +927 -0
- data/docs/api/Brut/FrontEnd/Session.html +1195 -0
- data/docs/api/Brut/FrontEnd.html +134 -0
- data/docs/api/Brut/I18n/BaseMethods.html +931 -0
- data/docs/api/Brut/I18n/ForBackEnd.html +302 -0
- data/docs/api/Brut/I18n/ForCLI.html +302 -0
- data/docs/api/Brut/I18n/ForHTML.html +296 -0
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +316 -0
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +930 -0
- data/docs/api/Brut/I18n.html +127 -0
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +435 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +286 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +302 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +864 -0
- data/docs/api/Brut/Instrumentation.html +126 -0
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +532 -0
- data/docs/api/Brut/SinatraHelpers.html +281 -0
- data/docs/api/Brut/SpecSupport/ClockSupport.html +383 -0
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +502 -0
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +503 -0
- data/docs/api/Brut/SpecSupport/E2eSupport.html +142 -0
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +403 -0
- data/docs/api/Brut/SpecSupport/FlashSupport.html +278 -0
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +401 -0
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +195 -0
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +160 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +553 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +439 -0
- data/docs/api/Brut/SpecSupport/Matchers.html +125 -0
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +335 -0
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +602 -0
- data/docs/api/Brut/SpecSupport/SessionSupport.html +196 -0
- data/docs/api/Brut/SpecSupport.html +129 -0
- data/docs/api/Brut.html +225 -0
- data/docs/api/Clock.html +603 -0
- data/docs/api/RichString.html +968 -0
- data/docs/api/SemanticLogger/Appender/Async.html +219 -0
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +115 -0
- data/docs/api/Sequel/Extensions/BrutMigrations.html +533 -0
- data/docs/api/Sequel/Extensions.html +117 -0
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +105 -0
- data/docs/api/Sequel/Plugins/CreatedAt.html +125 -0
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +207 -0
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +186 -0
- data/docs/api/Sequel/Plugins/ExternalId.html +218 -0
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +202 -0
- data/docs/api/Sequel/Plugins/FindBang.html +125 -0
- data/docs/api/Sequel/Plugins.html +117 -0
- data/docs/api/Sequel.html +117 -0
- data/docs/api/_index.html +1553 -0
- data/docs/api/class_list.html +54 -0
- data/docs/api/css/common.css +1 -0
- data/docs/api/css/full_list.css +58 -0
- data/docs/api/css/style.css +503 -0
- data/docs/api/file.README.html +127 -0
- data/docs/api/file_list.html +59 -0
- data/docs/api/frames.html +22 -0
- data/docs/api/index.html +127 -0
- data/docs/api/js/app.js +344 -0
- data/docs/api/js/full_list.js +242 -0
- data/docs/api/js/jquery.js +4 -0
- data/docs/api/method_list.html +3998 -0
- data/docs/api/top-level-namespace.html +112 -0
- data/docs/assets/ai.md.tZrjP9im.js +1 -0
- data/docs/assets/ai.md.tZrjP9im.lean.js +1 -0
- data/docs/assets/app.D_yaTITQ.js +1 -0
- data/docs/assets/assets.md.D3wunzLx.js +19 -0
- data/docs/assets/assets.md.D3wunzLx.lean.js +1 -0
- data/docs/assets/brut-js.md.o2DAO2s2.js +12 -0
- data/docs/assets/brut-js.md.o2DAO2s2.lean.js +1 -0
- data/docs/assets/business-logic.md.BY4hGy0m.js +1 -0
- data/docs/assets/business-logic.md.BY4hGy0m.lean.js +1 -0
- data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +1 -0
- data/docs/assets/chunks/VPLocalSearchBox.B2-ZzyTY.js +8 -0
- data/docs/assets/chunks/framework.1L-BeKqY.js +18 -0
- data/docs/assets/chunks/theme.CfGFVRvE.js +2 -0
- data/docs/assets/cli.md.RmeA2b0i.js +127 -0
- data/docs/assets/cli.md.RmeA2b0i.lean.js +1 -0
- data/docs/assets/components.md.eCttGlN-.js +104 -0
- data/docs/assets/components.md.eCttGlN-.lean.js +1 -0
- data/docs/assets/configuration.md.BRriU0cL.js +78 -0
- data/docs/assets/configuration.md.BRriU0cL.lean.js +1 -0
- data/docs/assets/css.md.DJgj2clw.js +21 -0
- data/docs/assets/css.md.DJgj2clw.lean.js +1 -0
- data/docs/assets/custom-element-tests.md.BrYJQEl3.js +69 -0
- data/docs/assets/custom-element-tests.md.BrYJQEl3.lean.js +1 -0
- data/docs/assets/database-access.md.C7l-Vuvb.js +63 -0
- data/docs/assets/database-access.md.C7l-Vuvb.lean.js +1 -0
- data/docs/assets/database-schema.md.BUjR0VS1.js +63 -0
- data/docs/assets/database-schema.md.BUjR0VS1.lean.js +1 -0
- data/docs/assets/deployment.md.Dbka4OTr.js +1 -0
- data/docs/assets/deployment.md.Dbka4OTr.lean.js +1 -0
- data/docs/assets/dev-env-overview.Gj7NWM8-.png +0 -0
- data/docs/assets/dev-env-protocol.DysDAtnz.png +0 -0
- data/docs/assets/dev-environment.md.BNc8AYiK.js +11 -0
- data/docs/assets/dev-environment.md.BNc8AYiK.lean.js +1 -0
- data/docs/assets/doc-conventions.md.DCfRXXi-.js +1 -0
- data/docs/assets/doc-conventions.md.DCfRXXi-.lean.js +1 -0
- data/docs/assets/end-to-end-tests.md.yfQHC0b5.js +26 -0
- data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +1 -0
- data/docs/assets/flash-and-session.md.BXY8RvT0.js +93 -0
- data/docs/assets/flash-and-session.md.BXY8RvT0.lean.js +1 -0
- data/docs/assets/forms.md.CBTYQ_Cz.js +379 -0
- data/docs/assets/forms.md.CBTYQ_Cz.lean.js +1 -0
- data/docs/assets/getting-started.md.Bz2s1Vjb.js +2 -0
- data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +1 -0
- data/docs/assets/handlers.md.089DVD3v.js +69 -0
- data/docs/assets/handlers.md.089DVD3v.lean.js +1 -0
- data/docs/assets/hooks.md.C4-moMny.js +80 -0
- data/docs/assets/hooks.md.C4-moMny.lean.js +1 -0
- data/docs/assets/i18n.md.Do9i1qWl.js +23 -0
- data/docs/assets/i18n.md.Do9i1qWl.lean.js +1 -0
- data/docs/assets/index.md.B28EwVpq.js +1 -0
- data/docs/assets/index.md.B28EwVpq.lean.js +1 -0
- data/docs/assets/instrumentation.md.CL6ax7nT.js +35 -0
- data/docs/assets/instrumentation.md.CL6ax7nT.lean.js +1 -0
- data/docs/assets/javascript.md.GWbhRS51.js +31 -0
- data/docs/assets/javascript.md.GWbhRS51.lean.js +1 -0
- data/docs/assets/jobs.md.S-2amAYp.js +1 -0
- data/docs/assets/jobs.md.S-2amAYp.lean.js +1 -0
- data/docs/assets/keyword-injection.md.Dt2tKREs.js +25 -0
- data/docs/assets/keyword-injection.md.Dt2tKREs.lean.js +1 -0
- data/docs/assets/markdown-examples.md.CCFEQO44.js +33 -0
- data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +1 -0
- data/docs/assets/middleware.md.Czz_UlJN.js +20 -0
- data/docs/assets/middleware.md.Czz_UlJN.lean.js +1 -0
- data/docs/assets/not-released.md.BBy28McC.js +1 -0
- data/docs/assets/not-released.md.BBy28McC.lean.js +1 -0
- data/docs/assets/overview.Da81cB9R.png +0 -0
- data/docs/assets/overview.md.CDalkuxV.js +133 -0
- data/docs/assets/overview.md.CDalkuxV.lean.js +1 -0
- data/docs/assets/pages.md.BE3kfOc5.js +122 -0
- data/docs/assets/pages.md.BE3kfOc5.lean.js +1 -0
- data/docs/assets/routes.md.BMM7peut.js +29 -0
- data/docs/assets/routes.md.BMM7peut.lean.js +1 -0
- data/docs/assets/security.md.C668yXCi.js +1 -0
- data/docs/assets/security.md.C668yXCi.lean.js +1 -0
- data/docs/assets/seed-data.md.BvFZlqIk.js +14 -0
- data/docs/assets/seed-data.md.BvFZlqIk.lean.js +1 -0
- data/docs/assets/spa.qejUdp-5.png +0 -0
- data/docs/assets/space-time-continuum.md.KPUIKysQ.js +1 -0
- data/docs/assets/space-time-continuum.md.KPUIKysQ.lean.js +1 -0
- data/docs/assets/style.D73IYGCX.css +1 -0
- data/docs/assets/tutorial.md.BnoGjrdK.js +1 -0
- data/docs/assets/tutorial.md.BnoGjrdK.lean.js +1 -0
- data/docs/assets/unit-tests.md.DUGrnLj5.js +13 -0
- data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +1 -0
- data/docs/assets/workspace-protocol.C0gXsoDb.png +0 -0
- data/docs/assets.html +42 -0
- data/docs/brut-css/brut.css +1 -0
- data/docs/brut-css/brut.max.css +22372 -0
- data/docs/brut-css/classes/appearances.html +783 -0
- data/docs/brut-css/classes/background-colors.html +3529 -0
- data/docs/brut-css/classes/border-colors.html +3529 -0
- data/docs/brut-css/classes/borders.html +2293 -0
- data/docs/brut-css/classes/dimensions.html +2581 -0
- data/docs/brut-css/classes/flex.html +917 -0
- data/docs/brut-css/classes/foreground-colors.html +3261 -0
- data/docs/brut-css/classes/junk-drawer.html +431 -0
- data/docs/brut-css/classes/layout.html +668 -0
- data/docs/brut-css/classes/lists.html +331 -0
- data/docs/brut-css/classes/positioning.html +1751 -0
- data/docs/brut-css/classes/spacings.html +2633 -0
- data/docs/brut-css/classes/typography.html +2206 -0
- data/docs/brut-css/customization/advanced-configuration.html +204 -0
- data/docs/brut-css/customization/breakpoints.html +227 -0
- data/docs/brut-css/customization/design-system.html +197 -0
- data/docs/brut-css/customization/pseudo-classes.html +228 -0
- data/docs/brut-css/docs.css +98 -0
- data/docs/brut-css/getting-started/core-concepts.html +234 -0
- data/docs/brut-css/getting-started/installation.html +190 -0
- data/docs/brut-css/getting-started/overview.html +210 -0
- data/docs/brut-css/getting-started/simple-example.html +285 -0
- data/docs/brut-css/index.html +193 -0
- data/docs/brut-css/prism-twilight.min.css +1 -0
- data/docs/brut-css/properties/colors.html +1548 -0
- data/docs/brut-css/properties/spacings.html +614 -0
- data/docs/brut-css/properties/typography.html +777 -0
- data/docs/brut-js/api/AjaxSubmit.html +374 -0
- data/docs/brut-js/api/AjaxSubmit.js.html +435 -0
- data/docs/brut-js/api/Autosubmit.html +192 -0
- data/docs/brut-js/api/Autosubmit.js.html +114 -0
- data/docs/brut-js/api/BaseCustomElement.html +1091 -0
- data/docs/brut-js/api/BaseCustomElement.js.html +312 -0
- data/docs/brut-js/api/BrutCustomElements.html +172 -0
- data/docs/brut-js/api/BufferedLogger.html +173 -0
- data/docs/brut-js/api/ConfirmSubmit.html +278 -0
- data/docs/brut-js/api/ConfirmSubmit.js.html +167 -0
- data/docs/brut-js/api/ConfirmationDialog.html +425 -0
- data/docs/brut-js/api/ConfirmationDialog.js.html +194 -0
- data/docs/brut-js/api/ConstraintViolationMessage.html +448 -0
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +176 -0
- data/docs/brut-js/api/ConstraintViolationMessages.html +590 -0
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +149 -0
- data/docs/brut-js/api/CopyToClipboard.html +345 -0
- data/docs/brut-js/api/CopyToClipboard.js.html +147 -0
- data/docs/brut-js/api/Form.html +294 -0
- data/docs/brut-js/api/Form.js.html +202 -0
- data/docs/brut-js/api/I18nTranslation.html +409 -0
- data/docs/brut-js/api/I18nTranslation.js.html +112 -0
- data/docs/brut-js/api/LocaleDetection.html +312 -0
- data/docs/brut-js/api/LocaleDetection.js.html +168 -0
- data/docs/brut-js/api/Logger.html +702 -0
- data/docs/brut-js/api/Logger.js.html +141 -0
- data/docs/brut-js/api/Message.html +238 -0
- data/docs/brut-js/api/Message.js.html +107 -0
- data/docs/brut-js/api/PrefixedLogger.html +369 -0
- data/docs/brut-js/api/RichString.html +1049 -0
- data/docs/brut-js/api/RichString.js.html +164 -0
- data/docs/brut-js/api/Tabs.html +295 -0
- data/docs/brut-js/api/Tabs.js.html +219 -0
- data/docs/brut-js/api/Tracing.html +277 -0
- data/docs/brut-js/api/Tracing.js.html +298 -0
- data/docs/brut-js/api/external-CustomElementRegistry.html +140 -0
- data/docs/brut-js/api/external-Performance.html +138 -0
- data/docs/brut-js/api/external-Promise.html +138 -0
- data/docs/brut-js/api/external-ValidityState.html +138 -0
- data/docs/brut-js/api/external-Window.html +233 -0
- data/docs/brut-js/api/external-fetch.html +138 -0
- data/docs/brut-js/api/global.html +400 -0
- data/docs/brut-js/api/index.html +168 -0
- data/docs/brut-js/api/index.js.html +181 -0
- data/docs/brut-js/api/module-testing.html +383 -0
- data/docs/brut-js/api/scripts/linenumber.js +25 -0
- data/docs/brut-js/api/scripts/prettify/Apache-License-2.0.txt +202 -0
- data/docs/brut-js/api/scripts/prettify/lang-css.js +2 -0
- data/docs/brut-js/api/scripts/prettify/prettify.js +28 -0
- data/docs/brut-js/api/styles/jsdoc-default.css +327 -0
- data/docs/brut-js/api/styles/prettify-jsdoc.css +111 -0
- data/docs/brut-js/api/styles/prettify-tomorrow.css +132 -0
- data/docs/brut-js/api/testing.AssetMetadata.html +172 -0
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +171 -0
- data/docs/brut-js/api/testing.CustomElementTest.html +679 -0
- data/docs/brut-js/api/testing.DOMCreator.html +171 -0
- data/docs/brut-js/api/testing_AssetMetadata.js.html +86 -0
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +76 -0
- data/docs/brut-js/api/testing_CustomElementTest.js.html +286 -0
- data/docs/brut-js/api/testing_DOMCreator.js.html +96 -0
- data/docs/brut-js/api/testing_index.js.html +99 -0
- data/docs/brut-js.html +35 -0
- data/docs/business-logic.html +24 -0
- data/docs/cli.html +150 -0
- data/docs/components.html +127 -0
- data/docs/configuration.html +101 -0
- data/docs/css.html +44 -0
- data/docs/custom-element-tests.html +92 -0
- data/docs/database-access.html +86 -0
- data/docs/database-schema.html +86 -0
- data/docs/deployment.html +24 -0
- data/docs/dev-environment.html +34 -0
- data/docs/doc-conventions.html +24 -0
- data/docs/end-to-end-tests.html +49 -0
- data/docs/flash-and-session.html +116 -0
- data/docs/forms.html +402 -0
- data/docs/getting-started.html +25 -0
- data/docs/handlers.html +92 -0
- data/docs/hashmap.json +1 -0
- data/docs/hooks.html +103 -0
- data/docs/i18n.html +46 -0
- data/docs/images/logo-300.png +0 -0
- data/docs/images/logo.png +0 -0
- data/docs/index.html +24 -0
- data/docs/instrumentation.html +58 -0
- data/docs/javascript.html +54 -0
- data/docs/jobs.html +24 -0
- data/docs/keyword-injection.html +48 -0
- data/docs/markdown-examples.html +56 -0
- data/docs/middleware.html +43 -0
- data/docs/not-released.html +24 -0
- data/docs/overview.html +156 -0
- data/docs/pages.html +145 -0
- data/docs/routes.html +52 -0
- data/docs/security.html +24 -0
- data/docs/seed-data.html +37 -0
- data/docs/space-time-continuum.html +24 -0
- data/docs/tutorial.html +24 -0
- data/docs/unit-tests.html +36 -0
- data/docs/vp-icons.css +1 -0
- data/lib/brut/back_end/seed_data.rb +19 -2
- data/lib/brut/back_end/sidekiq/middlewares/server.rb +2 -1
- data/lib/brut/back_end/sidekiq/middlewares.rb +2 -1
- data/lib/brut/back_end/sidekiq.rb +2 -1
- data/lib/brut/back_end/validator.rb +5 -1
- data/lib/brut/back_end.rb +4 -2
- data/lib/brut/cli/app_runner.rb +1 -1
- data/lib/brut/cli/apps/test.rb +5 -0
- data/lib/brut/cli.rb +4 -3
- data/lib/brut/factory_bot.rb +0 -5
- data/lib/brut/framework/app.rb +70 -5
- data/lib/brut/framework/config.rb +5 -3
- data/lib/brut/framework/container.rb +3 -2
- data/lib/brut/framework/errors.rb +12 -4
- data/lib/brut/framework/mcp.rb +58 -1
- data/lib/brut/framework/project_environment.rb +6 -2
- data/lib/brut/framework.rb +1 -1
- data/lib/brut/front_end/component.rb +69 -71
- data/lib/brut/front_end/components/constraint_violations.rb +1 -4
- data/lib/brut/front_end/components/form_tag.rb +1 -1
- data/lib/brut/front_end/components/input.rb +3 -3
- data/lib/brut/front_end/components/inputs/csrf_token.rb +1 -1
- data/lib/brut/front_end/components/inputs/{text_field.rb → input_tag.rb} +7 -9
- data/lib/brut/front_end/components/inputs/radio_button.rb +1 -1
- data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +187 -0
- data/lib/brut/front_end/components/inputs/{textarea.rb → textarea_tag.rb} +2 -2
- data/lib/brut/front_end/components/time_tag.rb +2 -1
- data/lib/brut/front_end/form.rb +4 -4
- data/lib/brut/front_end/forms/input.rb +2 -1
- data/lib/brut/front_end/forms/input_definition.rb +5 -2
- data/lib/brut/front_end/forms/radio_button_group_input.rb +2 -1
- data/lib/brut/front_end/forms/radio_button_group_input_definition.rb +2 -2
- data/lib/brut/front_end/forms/select_input.rb +2 -4
- data/lib/brut/front_end/forms/select_input_definition.rb +2 -2
- data/lib/brut/front_end/handler.rb +28 -26
- data/lib/brut/front_end/handlers/csp_reporting_handler.rb +5 -2
- data/lib/brut/front_end/handlers/instrumentation_handler.rb +8 -4
- data/lib/brut/front_end/handlers/locale_detection_handler.rb +9 -5
- data/lib/brut/front_end/handlers/missing_handler.rb +5 -2
- data/lib/brut/front_end/layout.rb +16 -0
- data/lib/brut/front_end/page.rb +52 -29
- data/lib/brut/front_end/request_context.rb +3 -2
- data/lib/brut/front_end/routing.rb +5 -1
- data/lib/brut/front_end.rb +4 -13
- data/lib/brut/i18n/base_methods.rb +167 -79
- data/lib/brut/i18n/for_back_end.rb +4 -0
- data/lib/brut/i18n/for_cli.rb +4 -0
- data/lib/brut/i18n/for_html.rb +32 -4
- data/lib/brut/i18n/http_accept_language.rb +47 -0
- data/lib/brut/instrumentation/open_telemetry.rb +36 -1
- data/lib/brut/instrumentation.rb +3 -5
- data/lib/brut/sinatra_helpers.rb +11 -3
- data/lib/brut/spec_support/component_support.rb +30 -16
- data/lib/brut/spec_support/e2e_support.rb +1 -1
- data/lib/brut/spec_support/e2e_test_server.rb +3 -0
- data/lib/brut/spec_support/general_support.rb +3 -0
- data/lib/brut/spec_support/handler_support.rb +6 -1
- data/lib/brut/spec_support/matcher.rb +1 -0
- data/lib/brut/spec_support/matchers/be_page_for.rb +1 -0
- data/lib/brut/spec_support/matchers/have_html_attribute.rb +1 -0
- data/lib/brut/spec_support/matchers/have_i18n_string.rb +2 -5
- data/lib/brut/spec_support/matchers/have_link_to.rb +1 -0
- data/lib/brut/spec_support/matchers/have_redirected_to.rb +1 -0
- data/lib/brut/spec_support/matchers/have_rendered.rb +1 -0
- data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +44 -0
- data/lib/brut/spec_support.rb +1 -1
- data/lib/brut/version.rb +1 -1
- data/lib/brut.rb +5 -4
- data/lib/sequel/extensions/brut_migrations.rb +1 -1
- metadata +648 -13
- data/doc-src/architecture.md +0 -102
- data/doc-src/assets.md +0 -98
- data/doc-src/forms.md +0 -214
- data/doc-src/handlers.md +0 -83
- data/doc-src/javascript.md +0 -265
- data/doc-src/pages.md +0 -210
- data/doc-src/route-hooks.md +0 -59
- data/lib/brut/front_end/components/inputs/select.rb +0 -117
@@ -0,0 +1,2 @@
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/chunks/VPLocalSearchBox.B2-ZzyTY.js","assets/chunks/framework.1L-BeKqY.js"])))=>i.map(i=>d[i]);
|
2
|
+
import{d as m,c as u,r as c,n as M,o as a,a as z,t as I,b as k,w as f,T as ue,e as h,_ as g,u as Be,i as Ee,f as De,g as de,h as y,j as d,k as r,l as W,m as ae,p as T,q as F,s as Y,v as j,x as ve,y as pe,z as Fe,A as Oe,F as w,B as H,C as K,D as ye,E as Q,G as _,H as E,I as Pe,J as Z,K as U,L as x,M as Ge,N as Le,O as re,P as Ve,Q as Se,R as ee,S as Ue,U as je,V as ze,W as Te,X as Ne,Y as We,Z as Ke,$ as Re,a0 as qe,a1 as Je,a2 as Xe}from"./framework.1L-BeKqY.js";const Ye=m({__name:"VPBadge",props:{text:{},type:{default:"tip"}},setup(s){return(e,t)=>(a(),u("span",{class:M(["VPBadge",e.type])},[c(e.$slots,"default",{},()=>[z(I(e.text),1)])],2))}}),Qe={key:0,class:"VPBackdrop"},Ze=m({__name:"VPBackdrop",props:{show:{type:Boolean}},setup(s){return(e,t)=>(a(),k(ue,{name:"fade"},{default:f(()=>[e.show?(a(),u("div",Qe)):h("",!0)]),_:1}))}}),xe=g(Ze,[["__scopeId","data-v-54a304ca"]]),L=Be;function et(s,e){let t,o=!1;return()=>{t&&clearTimeout(t),o?t=setTimeout(s,e):(s(),(o=!0)&&setTimeout(()=>o=!1,e))}}function ie(s){return s.startsWith("/")?s:`/${s}`}function fe(s){const{pathname:e,search:t,hash:o,protocol:n}=new URL(s,"http://a.com");if(Ee(s)||s.startsWith("#")||!n.startsWith("http")||!De(e))return s;const{site:i}=L(),l=e.endsWith("/")||e.endsWith(".html")?s:s.replace(/(?:(^\.+)\/)?.*$/,`$1${e.replace(/(\.md)?$/,i.value.cleanUrls?"":".html")}${t}${o}`);return de(l)}function q({correspondingLink:s=!1}={}){const{site:e,localeIndex:t,page:o,theme:n,hash:i}=L(),l=y(()=>{var p,$;return{label:(p=e.value.locales[t.value])==null?void 0:p.label,link:(($=e.value.locales[t.value])==null?void 0:$.link)||(t.value==="root"?"/":`/${t.value}/`)}});return{localeLinks:y(()=>Object.entries(e.value.locales).flatMap(([p,$])=>l.value.label===$.label?[]:{text:$.label,link:tt($.link||(p==="root"?"/":`/${p}/`),n.value.i18nRouting!==!1&&s,o.value.relativePath.slice(l.value.link.length-1),!e.value.cleanUrls)+i.value})),currentLang:l}}function tt(s,e,t,o){return e?s.replace(/\/$/,"")+ie(t.replace(/(^|\/)index\.md$/,"$1").replace(/\.md$/,o?".html":"")):s}const nt={class:"NotFound"},ot={class:"code"},st={class:"title"},at={class:"quote"},rt={class:"action"},it=["href","aria-label"],lt=m({__name:"NotFound",setup(s){const{theme:e}=L(),{currentLang:t}=q();return(o,n)=>{var i,l,v,p,$;return a(),u("div",nt,[d("p",ot,I(((i=r(e).notFound)==null?void 0:i.code)??"404"),1),d("h1",st,I(((l=r(e).notFound)==null?void 0:l.title)??"PAGE NOT FOUND"),1),n[0]||(n[0]=d("div",{class:"divider"},null,-1)),d("blockquote",at,I(((v=r(e).notFound)==null?void 0:v.quote)??"But if you don't change your direction, and if you keep looking, you may end up where you are heading."),1),d("div",rt,[d("a",{class:"link",href:r(de)(r(t).link),"aria-label":((p=r(e).notFound)==null?void 0:p.linkLabel)??"go to home"},I((($=r(e).notFound)==null?void 0:$.linkText)??"Take me home"),9,it)])])}}}),ct=g(lt,[["__scopeId","data-v-6ff51ddd"]]);function Me(s,e){if(Array.isArray(s))return J(s);if(s==null)return[];e=ie(e);const t=Object.keys(s).sort((n,i)=>i.split("/").length-n.split("/").length).find(n=>e.startsWith(ie(n))),o=t?s[t]:[];return Array.isArray(o)?J(o):J(o.items,o.base)}function ut(s){const e=[];let t=0;for(const o in s){const n=s[o];if(n.items){t=e.push(n);continue}e[t]||e.push({items:[]}),e[t].items.push(n)}return e}function dt(s){const e=[];function t(o){for(const n of o)n.text&&n.link&&e.push({text:n.text,link:n.link,docFooterText:n.docFooterText}),n.items&&t(n.items)}return t(s),e}function le(s,e){return Array.isArray(e)?e.some(t=>le(s,t)):W(s,e.link)?!0:e.items?le(s,e.items):!1}function J(s,e){return[...s].map(t=>{const o={...t},n=o.base||e;return n&&o.link&&(o.link=n+o.link),o.items&&(o.items=J(o.items,n)),o})}function O(){const{frontmatter:s,page:e,theme:t}=L(),o=ae("(min-width: 960px)"),n=T(!1),i=y(()=>{const C=t.value.sidebar,S=e.value.relativePath;return C?Me(C,S):[]}),l=T(i.value);F(i,(C,S)=>{JSON.stringify(C)!==JSON.stringify(S)&&(l.value=i.value)});const v=y(()=>s.value.sidebar!==!1&&l.value.length>0&&s.value.layout!=="home"),p=y(()=>$?s.value.aside==null?t.value.aside==="left":s.value.aside==="left":!1),$=y(()=>s.value.layout==="home"?!1:s.value.aside!=null?!!s.value.aside:t.value.aside!==!1),V=y(()=>v.value&&o.value),b=y(()=>v.value?ut(l.value):[]);function P(){n.value=!0}function N(){n.value=!1}function A(){n.value?N():P()}return{isOpen:n,sidebar:l,sidebarGroups:b,hasSidebar:v,hasAside:$,leftAside:p,isSidebarEnabled:V,open:P,close:N,toggle:A}}function vt(s,e){let t;Y(()=>{t=s.value?document.activeElement:void 0}),j(()=>{window.addEventListener("keyup",o)}),ve(()=>{window.removeEventListener("keyup",o)});function o(n){n.key==="Escape"&&s.value&&(e(),t==null||t.focus())}}function pt(s){const{page:e,hash:t}=L(),o=T(!1),n=y(()=>s.value.collapsed!=null),i=y(()=>!!s.value.link),l=T(!1),v=()=>{l.value=W(e.value.relativePath,s.value.link)};F([e,s,t],v),j(v);const p=y(()=>l.value?!0:s.value.items?le(e.value.relativePath,s.value.items):!1),$=y(()=>!!(s.value.items&&s.value.items.length));Y(()=>{o.value=!!(n.value&&s.value.collapsed)}),pe(()=>{(l.value||p.value)&&(o.value=!1)});function V(){n.value&&(o.value=!o.value)}return{collapsed:o,collapsible:n,isLink:i,isActiveLink:l,hasActiveLink:p,hasChildren:$,toggle:V}}function ft(){const{hasSidebar:s}=O(),e=ae("(min-width: 960px)"),t=ae("(min-width: 1280px)");return{isAsideEnabled:y(()=>!t.value&&!e.value?!1:s.value?t.value:e.value)}}const ht=/\b(?:VPBadge|header-anchor|footnote-ref|ignore-header)\b/,ce=[];function Ie(s){return typeof s.outline=="object"&&!Array.isArray(s.outline)&&s.outline.label||s.outlineTitle||"On this page"}function he(s){const e=[...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")].filter(t=>t.id&&t.hasChildNodes()).map(t=>{const o=Number(t.tagName[1]);return{element:t,title:mt(t),link:"#"+t.id,level:o}});return _t(e,s)}function mt(s){let e="";for(const t of s.childNodes)if(t.nodeType===1){if(ht.test(t.className))continue;e+=t.textContent}else t.nodeType===3&&(e+=t.textContent);return e.trim()}function _t(s,e){if(e===!1)return[];const t=(typeof e=="object"&&!Array.isArray(e)?e.level:e)||2,[o,n]=typeof t=="number"?[t,t]:t==="deep"?[2,6]:t;return gt(s,o,n)}function kt(s,e){const{isAsideEnabled:t}=ft(),o=et(i,100);let n=null;j(()=>{requestAnimationFrame(i),window.addEventListener("scroll",o)}),Fe(()=>{l(location.hash)}),ve(()=>{window.removeEventListener("scroll",o)});function i(){if(!t.value)return;const v=window.scrollY,p=window.innerHeight,$=document.body.offsetHeight,V=Math.abs(v+p-$)<1,b=ce.map(({element:N,link:A})=>({link:A,top:bt(N)})).filter(({top:N})=>!Number.isNaN(N)).sort((N,A)=>N.top-A.top);if(!b.length){l(null);return}if(v<1){l(null);return}if(V){l(b[b.length-1].link);return}let P=null;for(const{link:N,top:A}of b){if(A>v+Oe()+4)break;P=N}l(P)}function l(v){n&&n.classList.remove("active"),v==null?n=null:n=s.value.querySelector(`a[href="${decodeURIComponent(v)}"]`);const p=n;p?(p.classList.add("active"),e.value.style.top=p.offsetTop+39+"px",e.value.style.opacity="1"):(e.value.style.top="33px",e.value.style.opacity="0")}}function bt(s){let e=0;for(;s!==document.body;){if(s===null)return NaN;e+=s.offsetTop,s=s.offsetParent}return e}function gt(s,e,t){ce.length=0;const o=[],n=[];return s.forEach(i=>{const l={...i,children:[]};let v=n[n.length-1];for(;v&&v.level>=l.level;)n.pop(),v=n[n.length-1];if(l.element.classList.contains("ignore-header")||v&&"shouldIgnore"in v){n.push({level:l.level,shouldIgnore:!0});return}l.level>t||l.level<e||(ce.push({element:l.element,link:l.link}),v?v.children.push(l):o.push(l),n.push(l))}),o}const $t=["href","title"],yt=m({__name:"VPDocOutlineItem",props:{headers:{},root:{type:Boolean}},setup(s){function e({target:t}){const o=t.href.split("#")[1],n=document.getElementById(decodeURIComponent(o));n==null||n.focus({preventScroll:!0})}return(t,o)=>{const n=K("VPDocOutlineItem",!0);return a(),u("ul",{class:M(["VPDocOutlineItem",t.root?"root":"nested"])},[(a(!0),u(w,null,H(t.headers,({children:i,link:l,title:v})=>(a(),u("li",null,[d("a",{class:"outline-link",href:l,onClick:e,title:v},I(v),9,$t),i!=null&&i.length?(a(),k(n,{key:0,headers:i},null,8,["headers"])):h("",!0)]))),256))],2)}}}),we=g(yt,[["__scopeId","data-v-53c99d69"]]),Pt={class:"content"},Lt={"aria-level":"2",class:"outline-title",id:"doc-outline-aria-label",role:"heading"},Vt=m({__name:"VPDocAsideOutline",setup(s){const{frontmatter:e,theme:t}=L(),o=ye([]);Q(()=>{o.value=he(e.value.outline??t.value.outline)});const n=T(),i=T();return kt(n,i),(l,v)=>(a(),u("nav",{"aria-labelledby":"doc-outline-aria-label",class:M(["VPDocAsideOutline",{"has-outline":o.value.length>0}]),ref_key:"container",ref:n},[d("div",Pt,[d("div",{class:"outline-marker",ref_key:"marker",ref:i},null,512),d("div",Lt,I(r(Ie)(r(t))),1),_(we,{headers:o.value,root:!0},null,8,["headers"])])],2))}}),St=g(Vt,[["__scopeId","data-v-f610f197"]]),Tt={class:"VPDocAsideCarbonAds"},Nt=m({__name:"VPDocAsideCarbonAds",props:{carbonAds:{}},setup(s){const e=()=>null;return(t,o)=>(a(),u("div",Tt,[_(r(e),{"carbon-ads":t.carbonAds},null,8,["carbon-ads"])]))}}),Mt={class:"VPDocAside"},It=m({__name:"VPDocAside",setup(s){const{theme:e}=L();return(t,o)=>(a(),u("div",Mt,[c(t.$slots,"aside-top",{},void 0,!0),c(t.$slots,"aside-outline-before",{},void 0,!0),_(St),c(t.$slots,"aside-outline-after",{},void 0,!0),o[0]||(o[0]=d("div",{class:"spacer"},null,-1)),c(t.$slots,"aside-ads-before",{},void 0,!0),r(e).carbonAds?(a(),k(Nt,{key:0,"carbon-ads":r(e).carbonAds},null,8,["carbon-ads"])):h("",!0),c(t.$slots,"aside-ads-after",{},void 0,!0),c(t.$slots,"aside-bottom",{},void 0,!0)]))}}),wt=g(It,[["__scopeId","data-v-cb998dce"]]);function At(){const{theme:s,page:e}=L();return y(()=>{const{text:t="Edit this page",pattern:o=""}=s.value.editLink||{};let n;return typeof o=="function"?n=o(e.value):n=o.replace(/:path/g,e.value.filePath),{url:n,text:t}})}function Ct(){const{page:s,theme:e,frontmatter:t}=L();return y(()=>{var $,V,b,P,N,A,C,S;const o=Me(e.value.sidebar,s.value.relativePath),n=dt(o),i=Ht(n,B=>B.link.replace(/[?#].*$/,"")),l=i.findIndex(B=>W(s.value.relativePath,B.link)),v=(($=e.value.docFooter)==null?void 0:$.prev)===!1&&!t.value.prev||t.value.prev===!1,p=((V=e.value.docFooter)==null?void 0:V.next)===!1&&!t.value.next||t.value.next===!1;return{prev:v?void 0:{text:(typeof t.value.prev=="string"?t.value.prev:typeof t.value.prev=="object"?t.value.prev.text:void 0)??((b=i[l-1])==null?void 0:b.docFooterText)??((P=i[l-1])==null?void 0:P.text),link:(typeof t.value.prev=="object"?t.value.prev.link:void 0)??((N=i[l-1])==null?void 0:N.link)},next:p?void 0:{text:(typeof t.value.next=="string"?t.value.next:typeof t.value.next=="object"?t.value.next.text:void 0)??((A=i[l+1])==null?void 0:A.docFooterText)??((C=i[l+1])==null?void 0:C.text),link:(typeof t.value.next=="object"?t.value.next.link:void 0)??((S=i[l+1])==null?void 0:S.link)}}})}function Ht(s,e){const t=new Set;return s.filter(o=>{const n=e(o);return t.has(n)?!1:t.add(n)})}const D=m({__name:"VPLink",props:{tag:{},href:{},noIcon:{type:Boolean},target:{},rel:{}},setup(s){const e=s,t=y(()=>e.tag??(e.href?"a":"span")),o=y(()=>e.href&&Pe.test(e.href)||e.target==="_blank");return(n,i)=>(a(),k(E(t.value),{class:M(["VPLink",{link:n.href,"vp-external-link-icon":o.value,"no-icon":n.noIcon}]),href:n.href?r(fe)(n.href):void 0,target:n.target??(o.value?"_blank":void 0),rel:n.rel??(o.value?"noreferrer":void 0)},{default:f(()=>[c(n.$slots,"default")]),_:3},8,["class","href","target","rel"]))}}),Bt={class:"VPLastUpdated"},Et=["datetime"],Dt=m({__name:"VPDocFooterLastUpdated",setup(s){const{theme:e,page:t,lang:o}=L(),n=y(()=>new Date(t.value.lastUpdated)),i=y(()=>n.value.toISOString()),l=T("");return j(()=>{Y(()=>{var v,p,$;l.value=new Intl.DateTimeFormat((p=(v=e.value.lastUpdated)==null?void 0:v.formatOptions)!=null&&p.forceLocale?o.value:void 0,(($=e.value.lastUpdated)==null?void 0:$.formatOptions)??{dateStyle:"short",timeStyle:"short"}).format(n.value)})}),(v,p)=>{var $;return a(),u("p",Bt,[z(I((($=r(e).lastUpdated)==null?void 0:$.text)||r(e).lastUpdatedText||"Last updated")+": ",1),d("time",{datetime:i.value},I(l.value),9,Et)])}}}),Ft=g(Dt,[["__scopeId","data-v-1bb0c8a8"]]),Ot={key:0,class:"VPDocFooter"},Gt={key:0,class:"edit-info"},Ut={key:0,class:"edit-link"},jt={key:1,class:"last-updated"},zt={key:1,class:"prev-next","aria-labelledby":"doc-footer-aria-label"},Wt={class:"pager"},Kt=["innerHTML"],Rt=["innerHTML"],qt={class:"pager"},Jt=["innerHTML"],Xt=["innerHTML"],Yt=m({__name:"VPDocFooter",setup(s){const{theme:e,page:t,frontmatter:o}=L(),n=At(),i=Ct(),l=y(()=>e.value.editLink&&o.value.editLink!==!1),v=y(()=>t.value.lastUpdated),p=y(()=>l.value||v.value||i.value.prev||i.value.next);return($,V)=>{var b,P,N,A;return p.value?(a(),u("footer",Ot,[c($.$slots,"doc-footer-before",{},void 0,!0),l.value||v.value?(a(),u("div",Gt,[l.value?(a(),u("div",Ut,[_(D,{class:"edit-link-button",href:r(n).url,"no-icon":!0},{default:f(()=>[V[0]||(V[0]=d("span",{class:"vpi-square-pen edit-link-icon"},null,-1)),z(" "+I(r(n).text),1)]),_:1},8,["href"])])):h("",!0),v.value?(a(),u("div",jt,[_(Ft)])):h("",!0)])):h("",!0),(b=r(i).prev)!=null&&b.link||(P=r(i).next)!=null&&P.link?(a(),u("nav",zt,[V[1]||(V[1]=d("span",{class:"visually-hidden",id:"doc-footer-aria-label"},"Pager",-1)),d("div",Wt,[(N=r(i).prev)!=null&&N.link?(a(),k(D,{key:0,class:"pager-link prev",href:r(i).prev.link},{default:f(()=>{var C;return[d("span",{class:"desc",innerHTML:((C=r(e).docFooter)==null?void 0:C.prev)||"Previous page"},null,8,Kt),d("span",{class:"title",innerHTML:r(i).prev.text},null,8,Rt)]}),_:1},8,["href"])):h("",!0)]),d("div",qt,[(A=r(i).next)!=null&&A.link?(a(),k(D,{key:0,class:"pager-link next",href:r(i).next.link},{default:f(()=>{var C;return[d("span",{class:"desc",innerHTML:((C=r(e).docFooter)==null?void 0:C.next)||"Next page"},null,8,Jt),d("span",{class:"title",innerHTML:r(i).next.text},null,8,Xt)]}),_:1},8,["href"])):h("",!0)])])):h("",!0)])):h("",!0)}}}),Qt=g(Yt,[["__scopeId","data-v-1bcd8184"]]),Zt={class:"container"},xt={class:"aside-container"},en={class:"aside-content"},tn={class:"content"},nn={class:"content-container"},on={class:"main"},sn=m({__name:"VPDoc",setup(s){const{theme:e}=L(),t=Z(),{hasSidebar:o,hasAside:n,leftAside:i}=O(),l=y(()=>t.path.replace(/[./]+/g,"_").replace(/_html$/,""));return(v,p)=>{const $=K("Content");return a(),u("div",{class:M(["VPDoc",{"has-sidebar":r(o),"has-aside":r(n)}])},[c(v.$slots,"doc-top",{},void 0,!0),d("div",Zt,[r(n)?(a(),u("div",{key:0,class:M(["aside",{"left-aside":r(i)}])},[p[0]||(p[0]=d("div",{class:"aside-curtain"},null,-1)),d("div",xt,[d("div",en,[_(wt,null,{"aside-top":f(()=>[c(v.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":f(()=>[c(v.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":f(()=>[c(v.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":f(()=>[c(v.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":f(()=>[c(v.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":f(()=>[c(v.$slots,"aside-ads-after",{},void 0,!0)]),_:3})])])],2)):h("",!0),d("div",tn,[d("div",nn,[c(v.$slots,"doc-before",{},void 0,!0),d("main",on,[_($,{class:M(["vp-doc",[l.value,r(e).externalLinkIcon&&"external-link-icon-enabled"]])},null,8,["class"])]),_(Qt,null,{"doc-footer-before":f(()=>[c(v.$slots,"doc-footer-before",{},void 0,!0)]),_:3}),c(v.$slots,"doc-after",{},void 0,!0)])])]),c(v.$slots,"doc-bottom",{},void 0,!0)],2)}}}),an=g(sn,[["__scopeId","data-v-e6f2a212"]]),rn=m({__name:"VPButton",props:{tag:{},size:{default:"medium"},theme:{default:"brand"},text:{},href:{},target:{},rel:{}},setup(s){const e=s,t=y(()=>e.href&&Pe.test(e.href)),o=y(()=>e.tag||(e.href?"a":"button"));return(n,i)=>(a(),k(E(o.value),{class:M(["VPButton",[n.size,n.theme]]),href:n.href?r(fe)(n.href):void 0,target:e.target??(t.value?"_blank":void 0),rel:e.rel??(t.value?"noreferrer":void 0)},{default:f(()=>[z(I(n.text),1)]),_:1},8,["class","href","target","rel"]))}}),ln=g(rn,[["__scopeId","data-v-93dc4167"]]),cn=["src","alt"],un=m({inheritAttrs:!1,__name:"VPImage",props:{image:{},alt:{}},setup(s){return(e,t)=>{const o=K("VPImage",!0);return e.image?(a(),u(w,{key:0},[typeof e.image=="string"||"src"in e.image?(a(),u("img",U({key:0,class:"VPImage"},typeof e.image=="string"?e.$attrs:{...e.image,...e.$attrs},{src:r(de)(typeof e.image=="string"?e.image:e.image.src),alt:e.alt??(typeof e.image=="string"?"":e.image.alt||"")}),null,16,cn)):(a(),u(w,{key:1},[_(o,U({class:"dark",image:e.image.dark,alt:e.image.alt},e.$attrs),null,16,["image","alt"]),_(o,U({class:"light",image:e.image.light,alt:e.image.alt},e.$attrs),null,16,["image","alt"])],64))],64)):h("",!0)}}}),X=g(un,[["__scopeId","data-v-ab19afbb"]]),dn={class:"container"},vn={class:"main"},pn={class:"heading"},fn=["innerHTML"],hn=["innerHTML"],mn=["innerHTML"],_n={key:0,class:"actions"},kn={key:0,class:"image"},bn={class:"image-container"},gn=m({__name:"VPHero",props:{name:{},text:{},tagline:{},image:{},actions:{}},setup(s){const e=x("hero-image-slot-exists");return(t,o)=>(a(),u("div",{class:M(["VPHero",{"has-image":t.image||r(e)}])},[d("div",dn,[d("div",vn,[c(t.$slots,"home-hero-info-before",{},void 0,!0),c(t.$slots,"home-hero-info",{},()=>[d("h1",pn,[t.name?(a(),u("span",{key:0,innerHTML:t.name,class:"name clip"},null,8,fn)):h("",!0),t.text?(a(),u("span",{key:1,innerHTML:t.text,class:"text"},null,8,hn)):h("",!0)]),t.tagline?(a(),u("p",{key:0,innerHTML:t.tagline,class:"tagline"},null,8,mn)):h("",!0)],!0),c(t.$slots,"home-hero-info-after",{},void 0,!0),t.actions?(a(),u("div",_n,[(a(!0),u(w,null,H(t.actions,n=>(a(),u("div",{key:n.link,class:"action"},[_(ln,{tag:"a",size:"medium",theme:n.theme,text:n.text,href:n.link,target:n.target,rel:n.rel},null,8,["theme","text","href","target","rel"])]))),128))])):h("",!0),c(t.$slots,"home-hero-actions-after",{},void 0,!0)]),t.image||r(e)?(a(),u("div",kn,[d("div",bn,[o[0]||(o[0]=d("div",{class:"image-bg"},null,-1)),c(t.$slots,"home-hero-image",{},()=>[t.image?(a(),k(X,{key:0,class:"image-src",image:t.image},null,8,["image"])):h("",!0)],!0)])])):h("",!0)])],2))}}),$n=g(gn,[["__scopeId","data-v-dd8814ff"]]),yn=m({__name:"VPHomeHero",setup(s){const{frontmatter:e}=L();return(t,o)=>r(e).hero?(a(),k($n,{key:0,class:"VPHomeHero",name:r(e).hero.name,text:r(e).hero.text,tagline:r(e).hero.tagline,image:r(e).hero.image,actions:r(e).hero.actions},{"home-hero-info-before":f(()=>[c(t.$slots,"home-hero-info-before")]),"home-hero-info":f(()=>[c(t.$slots,"home-hero-info")]),"home-hero-info-after":f(()=>[c(t.$slots,"home-hero-info-after")]),"home-hero-actions-after":f(()=>[c(t.$slots,"home-hero-actions-after")]),"home-hero-image":f(()=>[c(t.$slots,"home-hero-image")]),_:3},8,["name","text","tagline","image","actions"])):h("",!0)}}),Pn={class:"box"},Ln={key:0,class:"icon"},Vn=["innerHTML"],Sn=["innerHTML"],Tn=["innerHTML"],Nn={key:4,class:"link-text"},Mn={class:"link-text-value"},In=m({__name:"VPFeature",props:{icon:{},title:{},details:{},link:{},linkText:{},rel:{},target:{}},setup(s){return(e,t)=>(a(),k(D,{class:"VPFeature",href:e.link,rel:e.rel,target:e.target,"no-icon":!0,tag:e.link?"a":"div"},{default:f(()=>[d("article",Pn,[typeof e.icon=="object"&&e.icon.wrap?(a(),u("div",Ln,[_(X,{image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])])):typeof e.icon=="object"?(a(),k(X,{key:1,image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])):e.icon?(a(),u("div",{key:2,class:"icon",innerHTML:e.icon},null,8,Vn)):h("",!0),d("h2",{class:"title",innerHTML:e.title},null,8,Sn),e.details?(a(),u("p",{key:3,class:"details",innerHTML:e.details},null,8,Tn)):h("",!0),e.linkText?(a(),u("div",Nn,[d("p",Mn,[z(I(e.linkText)+" ",1),t[0]||(t[0]=d("span",{class:"vpi-arrow-right link-text-icon"},null,-1))])])):h("",!0)])]),_:1},8,["href","rel","target","tag"]))}}),wn=g(In,[["__scopeId","data-v-bd37d1a2"]]),An={key:0,class:"VPFeatures"},Cn={class:"container"},Hn={class:"items"},Bn=m({__name:"VPFeatures",props:{features:{}},setup(s){const e=s,t=y(()=>{const o=e.features.length;if(o){if(o===2)return"grid-2";if(o===3)return"grid-3";if(o%3===0)return"grid-6";if(o>3)return"grid-4"}else return});return(o,n)=>o.features?(a(),u("div",An,[d("div",Cn,[d("div",Hn,[(a(!0),u(w,null,H(o.features,i=>(a(),u("div",{key:i.title,class:M(["item",[t.value]])},[_(wn,{icon:i.icon,title:i.title,details:i.details,link:i.link,"link-text":i.linkText,rel:i.rel,target:i.target},null,8,["icon","title","details","link","link-text","rel","target"])],2))),128))])])])):h("",!0)}}),En=g(Bn,[["__scopeId","data-v-b1eea84a"]]),Dn=m({__name:"VPHomeFeatures",setup(s){const{frontmatter:e}=L();return(t,o)=>r(e).features?(a(),k(En,{key:0,class:"VPHomeFeatures",features:r(e).features},null,8,["features"])):h("",!0)}}),Fn=m({__name:"VPHomeContent",setup(s){const{width:e}=Ge({initialWidth:0,includeScrollbar:!1});return(t,o)=>(a(),u("div",{class:"vp-doc container",style:Le(r(e)?{"--vp-offset":`calc(50% - ${r(e)/2}px)`}:{})},[c(t.$slots,"default",{},void 0,!0)],4))}}),On=g(Fn,[["__scopeId","data-v-c141a4bd"]]),Gn=m({__name:"VPHome",setup(s){const{frontmatter:e,theme:t}=L();return(o,n)=>{const i=K("Content");return a(),u("div",{class:M(["VPHome",{"external-link-icon-enabled":r(t).externalLinkIcon}])},[c(o.$slots,"home-hero-before",{},void 0,!0),_(yn,null,{"home-hero-info-before":f(()=>[c(o.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":f(()=>[c(o.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":f(()=>[c(o.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":f(()=>[c(o.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":f(()=>[c(o.$slots,"home-hero-image",{},void 0,!0)]),_:3}),c(o.$slots,"home-hero-after",{},void 0,!0),c(o.$slots,"home-features-before",{},void 0,!0),_(Dn),c(o.$slots,"home-features-after",{},void 0,!0),r(e).markdownStyles!==!1?(a(),k(On,{key:0},{default:f(()=>[_(i)]),_:1})):(a(),k(i,{key:1}))],2)}}}),Un=g(Gn,[["__scopeId","data-v-e07eaea7"]]),jn={},zn={class:"VPPage"};function Wn(s,e){const t=K("Content");return a(),u("div",zn,[c(s.$slots,"page-top"),_(t),c(s.$slots,"page-bottom")])}const Kn=g(jn,[["render",Wn]]),Rn=m({__name:"VPContent",setup(s){const{page:e,frontmatter:t}=L(),{hasSidebar:o}=O();return(n,i)=>(a(),u("div",{class:M(["VPContent",{"has-sidebar":r(o),"is-home":r(t).layout==="home"}]),id:"VPContent"},[r(e).isNotFound?c(n.$slots,"not-found",{key:0},()=>[_(ct)],!0):r(t).layout==="page"?(a(),k(Kn,{key:1},{"page-top":f(()=>[c(n.$slots,"page-top",{},void 0,!0)]),"page-bottom":f(()=>[c(n.$slots,"page-bottom",{},void 0,!0)]),_:3})):r(t).layout==="home"?(a(),k(Un,{key:2},{"home-hero-before":f(()=>[c(n.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":f(()=>[c(n.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":f(()=>[c(n.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":f(()=>[c(n.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":f(()=>[c(n.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":f(()=>[c(n.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":f(()=>[c(n.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":f(()=>[c(n.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":f(()=>[c(n.$slots,"home-features-after",{},void 0,!0)]),_:3})):r(t).layout&&r(t).layout!=="doc"?(a(),k(E(r(t).layout),{key:3})):(a(),k(an,{key:4},{"doc-top":f(()=>[c(n.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":f(()=>[c(n.$slots,"doc-bottom",{},void 0,!0)]),"doc-footer-before":f(()=>[c(n.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":f(()=>[c(n.$slots,"doc-before",{},void 0,!0)]),"doc-after":f(()=>[c(n.$slots,"doc-after",{},void 0,!0)]),"aside-top":f(()=>[c(n.$slots,"aside-top",{},void 0,!0)]),"aside-outline-before":f(()=>[c(n.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":f(()=>[c(n.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":f(()=>[c(n.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":f(()=>[c(n.$slots,"aside-ads-after",{},void 0,!0)]),"aside-bottom":f(()=>[c(n.$slots,"aside-bottom",{},void 0,!0)]),_:3}))],2))}}),qn=g(Rn,[["__scopeId","data-v-9a6c75ad"]]),Jn={class:"container"},Xn=["innerHTML"],Yn=["innerHTML"],Qn=m({__name:"VPFooter",setup(s){const{theme:e,frontmatter:t}=L(),{hasSidebar:o}=O();return(n,i)=>r(e).footer&&r(t).footer!==!1?(a(),u("footer",{key:0,class:M(["VPFooter",{"has-sidebar":r(o)}])},[d("div",Jn,[r(e).footer.message?(a(),u("p",{key:0,class:"message",innerHTML:r(e).footer.message},null,8,Xn)):h("",!0),r(e).footer.copyright?(a(),u("p",{key:1,class:"copyright",innerHTML:r(e).footer.copyright},null,8,Yn)):h("",!0)])],2)):h("",!0)}}),Zn=g(Qn,[["__scopeId","data-v-566314d4"]]);function xn(){const{theme:s,frontmatter:e}=L(),t=ye([]),o=y(()=>t.value.length>0);return Q(()=>{t.value=he(e.value.outline??s.value.outline)}),{headers:t,hasLocalNav:o}}const eo={class:"menu-text"},to={class:"header"},no={class:"outline"},oo=m({__name:"VPLocalNavOutlineDropdown",props:{headers:{},navHeight:{}},setup(s){const e=s,{theme:t}=L(),o=T(!1),n=T(0),i=T(),l=T();function v(b){var P;(P=i.value)!=null&&P.contains(b.target)||(o.value=!1)}F(o,b=>{if(b){document.addEventListener("click",v);return}document.removeEventListener("click",v)}),re("Escape",()=>{o.value=!1}),Q(()=>{o.value=!1});function p(){o.value=!o.value,n.value=window.innerHeight+Math.min(window.scrollY-e.navHeight,0)}function $(b){b.target.classList.contains("outline-link")&&(l.value&&(l.value.style.transition="none"),Ve(()=>{o.value=!1}))}function V(){o.value=!1,window.scrollTo({top:0,left:0,behavior:"smooth"})}return(b,P)=>(a(),u("div",{class:"VPLocalNavOutlineDropdown",style:Le({"--vp-vh":n.value+"px"}),ref_key:"main",ref:i},[b.headers.length>0?(a(),u("button",{key:0,onClick:p,class:M({open:o.value})},[d("span",eo,I(r(Ie)(r(t))),1),P[0]||(P[0]=d("span",{class:"vpi-chevron-right icon"},null,-1))],2)):(a(),u("button",{key:1,onClick:V},I(r(t).returnToTopLabel||"Return to top"),1)),_(ue,{name:"flyout"},{default:f(()=>[o.value?(a(),u("div",{key:0,ref_key:"items",ref:l,class:"items",onClick:$},[d("div",to,[d("a",{class:"top-link",href:"#",onClick:V},I(r(t).returnToTopLabel||"Return to top"),1)]),d("div",no,[_(we,{headers:b.headers},null,8,["headers"])])],512)):h("",!0)]),_:1})],4))}}),so=g(oo,[["__scopeId","data-v-6b867909"]]),ao={class:"container"},ro=["aria-expanded"],io={class:"menu-text"},lo=m({__name:"VPLocalNav",props:{open:{type:Boolean}},emits:["open-menu"],setup(s){const{theme:e,frontmatter:t}=L(),{hasSidebar:o}=O(),{headers:n}=xn(),{y:i}=Se(),l=T(0);j(()=>{l.value=parseInt(getComputedStyle(document.documentElement).getPropertyValue("--vp-nav-height"))}),Q(()=>{n.value=he(t.value.outline??e.value.outline)});const v=y(()=>n.value.length===0),p=y(()=>v.value&&!o.value),$=y(()=>({VPLocalNav:!0,"has-sidebar":o.value,empty:v.value,fixed:p.value}));return(V,b)=>r(t).layout!=="home"&&(!p.value||r(i)>=l.value)?(a(),u("div",{key:0,class:M($.value)},[d("div",ao,[r(o)?(a(),u("button",{key:0,class:"menu","aria-expanded":V.open,"aria-controls":"VPSidebarNav",onClick:b[0]||(b[0]=P=>V.$emit("open-menu"))},[b[1]||(b[1]=d("span",{class:"vpi-align-left menu-icon"},null,-1)),d("span",io,I(r(e).sidebarMenuLabel||"Menu"),1)],8,ro)):h("",!0),_(so,{headers:r(n),navHeight:l.value},null,8,["headers","navHeight"])])],2)):h("",!0)}}),co=g(lo,[["__scopeId","data-v-2488c25a"]]);function uo(){const s=T(!1);function e(){s.value=!0,window.addEventListener("resize",n)}function t(){s.value=!1,window.removeEventListener("resize",n)}function o(){s.value?t():e()}function n(){window.outerWidth>=768&&t()}const i=Z();return F(()=>i.path,t),{isScreenOpen:s,openScreen:e,closeScreen:t,toggleScreen:o}}const vo={},po={class:"VPSwitch",type:"button",role:"switch"},fo={class:"check"},ho={key:0,class:"icon"};function mo(s,e){return a(),u("button",po,[d("span",fo,[s.$slots.default?(a(),u("span",ho,[c(s.$slots,"default",{},void 0,!0)])):h("",!0)])])}const _o=g(vo,[["render",mo],["__scopeId","data-v-b4ccac88"]]),ko=m({__name:"VPSwitchAppearance",setup(s){const{isDark:e,theme:t}=L(),o=x("toggle-appearance",()=>{e.value=!e.value}),n=T("");return pe(()=>{n.value=e.value?t.value.lightModeSwitchTitle||"Switch to light theme":t.value.darkModeSwitchTitle||"Switch to dark theme"}),(i,l)=>(a(),k(_o,{title:n.value,class:"VPSwitchAppearance","aria-checked":r(e),onClick:r(o)},{default:f(()=>l[0]||(l[0]=[d("span",{class:"vpi-sun sun"},null,-1),d("span",{class:"vpi-moon moon"},null,-1)])),_:1},8,["title","aria-checked","onClick"]))}}),me=g(ko,[["__scopeId","data-v-be9742d9"]]),bo={key:0,class:"VPNavBarAppearance"},go=m({__name:"VPNavBarAppearance",setup(s){const{site:e}=L();return(t,o)=>r(e).appearance&&r(e).appearance!=="force-dark"&&r(e).appearance!=="force-auto"?(a(),u("div",bo,[_(me)])):h("",!0)}}),$o=g(go,[["__scopeId","data-v-3f90c1a5"]]),_e=T();let Ae=!1,se=0;function yo(s){const e=T(!1);if(ee){!Ae&&Po(),se++;const t=F(_e,o=>{var n,i,l;o===s.el.value||(n=s.el.value)!=null&&n.contains(o)?(e.value=!0,(i=s.onFocus)==null||i.call(s)):(e.value=!1,(l=s.onBlur)==null||l.call(s))});ve(()=>{t(),se--,se||Lo()})}return Ue(e)}function Po(){document.addEventListener("focusin",Ce),Ae=!0,_e.value=document.activeElement}function Lo(){document.removeEventListener("focusin",Ce)}function Ce(){_e.value=document.activeElement}const Vo={class:"VPMenuLink"},So=["innerHTML"],To=m({__name:"VPMenuLink",props:{item:{}},setup(s){const{page:e}=L();return(t,o)=>(a(),u("div",Vo,[_(D,{class:M({active:r(W)(r(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,target:t.item.target,rel:t.item.rel,"no-icon":t.item.noIcon},{default:f(()=>[d("span",{innerHTML:t.item.text},null,8,So)]),_:1},8,["class","href","target","rel","no-icon"])]))}}),te=g(To,[["__scopeId","data-v-7eeeb2dc"]]),No={class:"VPMenuGroup"},Mo={key:0,class:"title"},Io=m({__name:"VPMenuGroup",props:{text:{},items:{}},setup(s){return(e,t)=>(a(),u("div",No,[e.text?(a(),u("p",Mo,I(e.text),1)):h("",!0),(a(!0),u(w,null,H(e.items,o=>(a(),u(w,null,["link"in o?(a(),k(te,{key:0,item:o},null,8,["item"])):h("",!0)],64))),256))]))}}),wo=g(Io,[["__scopeId","data-v-a6b0397c"]]),Ao={class:"VPMenu"},Co={key:0,class:"items"},Ho=m({__name:"VPMenu",props:{items:{}},setup(s){return(e,t)=>(a(),u("div",Ao,[e.items?(a(),u("div",Co,[(a(!0),u(w,null,H(e.items,o=>(a(),u(w,{key:JSON.stringify(o)},["link"in o?(a(),k(te,{key:0,item:o},null,8,["item"])):"component"in o?(a(),k(E(o.component),U({key:1,ref_for:!0},o.props),null,16)):(a(),k(wo,{key:2,text:o.text,items:o.items},null,8,["text","items"]))],64))),128))])):h("",!0),c(e.$slots,"default",{},void 0,!0)]))}}),Bo=g(Ho,[["__scopeId","data-v-20ed86d6"]]),Eo=["aria-expanded","aria-label"],Do={key:0,class:"text"},Fo=["innerHTML"],Oo={key:1,class:"vpi-more-horizontal icon"},Go={class:"menu"},Uo=m({__name:"VPFlyout",props:{icon:{},button:{},label:{},items:{}},setup(s){const e=T(!1),t=T();yo({el:t,onBlur:o});function o(){e.value=!1}return(n,i)=>(a(),u("div",{class:"VPFlyout",ref_key:"el",ref:t,onMouseenter:i[1]||(i[1]=l=>e.value=!0),onMouseleave:i[2]||(i[2]=l=>e.value=!1)},[d("button",{type:"button",class:"button","aria-haspopup":"true","aria-expanded":e.value,"aria-label":n.label,onClick:i[0]||(i[0]=l=>e.value=!e.value)},[n.button||n.icon?(a(),u("span",Do,[n.icon?(a(),u("span",{key:0,class:M([n.icon,"option-icon"])},null,2)):h("",!0),n.button?(a(),u("span",{key:1,innerHTML:n.button},null,8,Fo)):h("",!0),i[3]||(i[3]=d("span",{class:"vpi-chevron-down text-icon"},null,-1))])):(a(),u("span",Oo))],8,Eo),d("div",Go,[_(Bo,{items:n.items},{default:f(()=>[c(n.$slots,"default",{},void 0,!0)]),_:3},8,["items"])])],544))}}),ke=g(Uo,[["__scopeId","data-v-bfe7971f"]]),jo=["href","aria-label","innerHTML"],zo=m({__name:"VPSocialLink",props:{icon:{},link:{},ariaLabel:{}},setup(s){const e=s,t=T();j(async()=>{var i;await Ve();const n=(i=t.value)==null?void 0:i.children[0];n instanceof HTMLElement&&n.className.startsWith("vpi-social-")&&(getComputedStyle(n).maskImage||getComputedStyle(n).webkitMaskImage)==="none"&&n.style.setProperty("--icon",`url('https://api.iconify.design/simple-icons/${e.icon}.svg')`)});const o=y(()=>typeof e.icon=="object"?e.icon.svg:`<span class="vpi-social-${e.icon}"></span>`);return(n,i)=>(a(),u("a",{ref_key:"el",ref:t,class:"VPSocialLink no-icon",href:n.link,"aria-label":n.ariaLabel??(typeof n.icon=="string"?n.icon:""),target:"_blank",rel:"noopener",innerHTML:o.value},null,8,jo))}}),Wo=g(zo,[["__scopeId","data-v-60a9a2d3"]]),Ko={class:"VPSocialLinks"},Ro=m({__name:"VPSocialLinks",props:{links:{}},setup(s){return(e,t)=>(a(),u("div",Ko,[(a(!0),u(w,null,H(e.links,({link:o,icon:n,ariaLabel:i})=>(a(),k(Wo,{key:o,icon:n,link:o,ariaLabel:i},null,8,["icon","link","ariaLabel"]))),128))]))}}),be=g(Ro,[["__scopeId","data-v-e71e869c"]]),qo={key:0,class:"group translations"},Jo={class:"trans-title"},Xo={key:1,class:"group"},Yo={class:"item appearance"},Qo={class:"label"},Zo={class:"appearance-action"},xo={key:2,class:"group"},es={class:"item social-links"},ts=m({__name:"VPNavBarExtra",setup(s){const{site:e,theme:t}=L(),{localeLinks:o,currentLang:n}=q({correspondingLink:!0}),i=y(()=>o.value.length&&n.value.label||e.value.appearance||t.value.socialLinks);return(l,v)=>i.value?(a(),k(ke,{key:0,class:"VPNavBarExtra",label:"extra navigation"},{default:f(()=>[r(o).length&&r(n).label?(a(),u("div",qo,[d("p",Jo,I(r(n).label),1),(a(!0),u(w,null,H(r(o),p=>(a(),k(te,{key:p.link,item:p},null,8,["item"]))),128))])):h("",!0),r(e).appearance&&r(e).appearance!=="force-dark"&&r(e).appearance!=="force-auto"?(a(),u("div",Xo,[d("div",Yo,[d("p",Qo,I(r(t).darkModeSwitchLabel||"Appearance"),1),d("div",Zo,[_(me)])])])):h("",!0),r(t).socialLinks?(a(),u("div",xo,[d("div",es,[_(be,{class:"social-links-list",links:r(t).socialLinks},null,8,["links"])])])):h("",!0)]),_:1})):h("",!0)}}),ns=g(ts,[["__scopeId","data-v-f953d92f"]]),os=["aria-expanded"],ss=m({__name:"VPNavBarHamburger",props:{active:{type:Boolean}},emits:["click"],setup(s){return(e,t)=>(a(),u("button",{type:"button",class:M(["VPNavBarHamburger",{active:e.active}]),"aria-label":"mobile navigation","aria-expanded":e.active,"aria-controls":"VPNavScreen",onClick:t[0]||(t[0]=o=>e.$emit("click"))},t[1]||(t[1]=[d("span",{class:"container"},[d("span",{class:"top"}),d("span",{class:"middle"}),d("span",{class:"bottom"})],-1)]),10,os))}}),as=g(ss,[["__scopeId","data-v-6bee1efd"]]),rs=["innerHTML"],is=m({__name:"VPNavBarMenuLink",props:{item:{}},setup(s){const{page:e}=L();return(t,o)=>(a(),k(D,{class:M({VPNavBarMenuLink:!0,active:r(W)(r(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,target:t.item.target,rel:t.item.rel,"no-icon":t.item.noIcon,tabindex:"0"},{default:f(()=>[d("span",{innerHTML:t.item.text},null,8,rs)]),_:1},8,["class","href","target","rel","no-icon"]))}}),ls=g(is,[["__scopeId","data-v-815115f5"]]),cs=m({__name:"VPNavBarMenuGroup",props:{item:{}},setup(s){const e=s,{page:t}=L(),o=i=>"component"in i?!1:"link"in i?W(t.value.relativePath,i.link,!!e.item.activeMatch):i.items.some(o),n=y(()=>o(e.item));return(i,l)=>(a(),k(ke,{class:M({VPNavBarMenuGroup:!0,active:r(W)(r(t).relativePath,i.item.activeMatch,!!i.item.activeMatch)||n.value}),button:i.item.text,items:i.item.items},null,8,["class","button","items"]))}}),us={key:0,"aria-labelledby":"main-nav-aria-label",class:"VPNavBarMenu"},ds=m({__name:"VPNavBarMenu",setup(s){const{theme:e}=L();return(t,o)=>r(e).nav?(a(),u("nav",us,[o[0]||(o[0]=d("span",{id:"main-nav-aria-label",class:"visually-hidden"}," Main Navigation ",-1)),(a(!0),u(w,null,H(r(e).nav,n=>(a(),u(w,{key:JSON.stringify(n)},["link"in n?(a(),k(ls,{key:0,item:n},null,8,["item"])):"component"in n?(a(),k(E(n.component),U({key:1,ref_for:!0},n.props),null,16)):(a(),k(cs,{key:2,item:n},null,8,["item"]))],64))),128))])):h("",!0)}}),vs=g(ds,[["__scopeId","data-v-afb2845e"]]);function ps(s){const{localeIndex:e,theme:t}=L();function o(n){var A,C,S;const i=n.split("."),l=(A=t.value.search)==null?void 0:A.options,v=l&&typeof l=="object",p=v&&((S=(C=l.locales)==null?void 0:C[e.value])==null?void 0:S.translations)||null,$=v&&l.translations||null;let V=p,b=$,P=s;const N=i.pop();for(const B of i){let G=null;const R=P==null?void 0:P[B];R&&(G=P=R);const ne=b==null?void 0:b[B];ne&&(G=b=ne);const oe=V==null?void 0:V[B];oe&&(G=V=oe),R||(P=G),ne||(b=G),oe||(V=G)}return(V==null?void 0:V[N])??(b==null?void 0:b[N])??(P==null?void 0:P[N])??""}return o}const fs=["aria-label"],hs={class:"DocSearch-Button-Container"},ms={class:"DocSearch-Button-Placeholder"},ge=m({__name:"VPNavBarSearchButton",setup(s){const t=ps({button:{buttonText:"Search",buttonAriaLabel:"Search"}});return(o,n)=>(a(),u("button",{type:"button",class:"DocSearch DocSearch-Button","aria-label":r(t)("button.buttonAriaLabel")},[d("span",hs,[n[0]||(n[0]=d("span",{class:"vp-icon DocSearch-Search-Icon"},null,-1)),d("span",ms,I(r(t)("button.buttonText")),1)]),n[1]||(n[1]=d("span",{class:"DocSearch-Button-Keys"},[d("kbd",{class:"DocSearch-Button-Key"}),d("kbd",{class:"DocSearch-Button-Key"},"K")],-1))],8,fs))}}),_s={class:"VPNavBarSearch"},ks={id:"local-search"},bs={key:1,id:"docsearch"},gs=m({__name:"VPNavBarSearch",setup(s){const e=je(()=>ze(()=>import("./VPLocalSearchBox.B2-ZzyTY.js"),__vite__mapDeps([0,1]))),t=()=>null,{theme:o}=L(),n=T(!1),i=T(!1);j(()=>{});function l(){n.value||(n.value=!0,setTimeout(v,16))}function v(){const b=new Event("keydown");b.key="k",b.metaKey=!0,window.dispatchEvent(b),setTimeout(()=>{document.querySelector(".DocSearch-Modal")||v()},16)}function p(b){const P=b.target,N=P.tagName;return P.isContentEditable||N==="INPUT"||N==="SELECT"||N==="TEXTAREA"}const $=T(!1);re("k",b=>{(b.ctrlKey||b.metaKey)&&(b.preventDefault(),$.value=!0)}),re("/",b=>{p(b)||(b.preventDefault(),$.value=!0)});const V="local";return(b,P)=>{var N;return a(),u("div",_s,[r(V)==="local"?(a(),u(w,{key:0},[$.value?(a(),k(r(e),{key:0,onClose:P[0]||(P[0]=A=>$.value=!1)})):h("",!0),d("div",ks,[_(ge,{onClick:P[1]||(P[1]=A=>$.value=!0)})])],64)):r(V)==="algolia"?(a(),u(w,{key:1},[n.value?(a(),k(r(t),{key:0,algolia:((N=r(o).search)==null?void 0:N.options)??r(o).algolia,onVnodeBeforeMount:P[2]||(P[2]=A=>i.value=!0)},null,8,["algolia"])):h("",!0),i.value?h("",!0):(a(),u("div",bs,[_(ge,{onClick:l})]))],64)):h("",!0)])}}}),$s=m({__name:"VPNavBarSocialLinks",setup(s){const{theme:e}=L();return(t,o)=>r(e).socialLinks?(a(),k(be,{key:0,class:"VPNavBarSocialLinks",links:r(e).socialLinks},null,8,["links"])):h("",!0)}}),ys=g($s,[["__scopeId","data-v-ef6192dc"]]),Ps=["href","rel","target"],Ls=["innerHTML"],Vs={key:2},Ss=m({__name:"VPNavBarTitle",setup(s){const{site:e,theme:t}=L(),{hasSidebar:o}=O(),{currentLang:n}=q(),i=y(()=>{var p;return typeof t.value.logoLink=="string"?t.value.logoLink:(p=t.value.logoLink)==null?void 0:p.link}),l=y(()=>{var p;return typeof t.value.logoLink=="string"||(p=t.value.logoLink)==null?void 0:p.rel}),v=y(()=>{var p;return typeof t.value.logoLink=="string"||(p=t.value.logoLink)==null?void 0:p.target});return(p,$)=>(a(),u("div",{class:M(["VPNavBarTitle",{"has-sidebar":r(o)}])},[d("a",{class:"title",href:i.value??r(fe)(r(n).link),rel:l.value,target:v.value},[c(p.$slots,"nav-bar-title-before",{},void 0,!0),r(t).logo?(a(),k(X,{key:0,class:"logo",image:r(t).logo},null,8,["image"])):h("",!0),r(t).siteTitle?(a(),u("span",{key:1,innerHTML:r(t).siteTitle},null,8,Ls)):r(t).siteTitle===void 0?(a(),u("span",Vs,I(r(e).title),1)):h("",!0),c(p.$slots,"nav-bar-title-after",{},void 0,!0)],8,Ps)],2))}}),Ts=g(Ss,[["__scopeId","data-v-9f43907a"]]),Ns={class:"items"},Ms={class:"title"},Is=m({__name:"VPNavBarTranslations",setup(s){const{theme:e}=L(),{localeLinks:t,currentLang:o}=q({correspondingLink:!0});return(n,i)=>r(t).length&&r(o).label?(a(),k(ke,{key:0,class:"VPNavBarTranslations",icon:"vpi-languages",label:r(e).langMenuLabel||"Change language"},{default:f(()=>[d("div",Ns,[d("p",Ms,I(r(o).label),1),(a(!0),u(w,null,H(r(t),l=>(a(),k(te,{key:l.link,item:l},null,8,["item"]))),128))])]),_:1},8,["label"])):h("",!0)}}),ws=g(Is,[["__scopeId","data-v-acee064b"]]),As={class:"wrapper"},Cs={class:"container"},Hs={class:"title"},Bs={class:"content"},Es={class:"content-body"},Ds=m({__name:"VPNavBar",props:{isScreenOpen:{type:Boolean}},emits:["toggle-screen"],setup(s){const e=s,{y:t}=Se(),{hasSidebar:o}=O(),{frontmatter:n}=L(),i=T({});return pe(()=>{i.value={"has-sidebar":o.value,home:n.value.layout==="home",top:t.value===0,"screen-open":e.isScreenOpen}}),(l,v)=>(a(),u("div",{class:M(["VPNavBar",i.value])},[d("div",As,[d("div",Cs,[d("div",Hs,[_(Ts,null,{"nav-bar-title-before":f(()=>[c(l.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":f(()=>[c(l.$slots,"nav-bar-title-after",{},void 0,!0)]),_:3})]),d("div",Bs,[d("div",Es,[c(l.$slots,"nav-bar-content-before",{},void 0,!0),_(gs,{class:"search"}),_(vs,{class:"menu"}),_(ws,{class:"translations"}),_($o,{class:"appearance"}),_(ys,{class:"social-links"}),_(ns,{class:"extra"}),c(l.$slots,"nav-bar-content-after",{},void 0,!0),_(as,{class:"hamburger",active:l.isScreenOpen,onClick:v[0]||(v[0]=p=>l.$emit("toggle-screen"))},null,8,["active"])])])])]),v[1]||(v[1]=d("div",{class:"divider"},[d("div",{class:"divider-line"})],-1))],2))}}),Fs=g(Ds,[["__scopeId","data-v-9fd4d1dd"]]),Os={key:0,class:"VPNavScreenAppearance"},Gs={class:"text"},Us=m({__name:"VPNavScreenAppearance",setup(s){const{site:e,theme:t}=L();return(o,n)=>r(e).appearance&&r(e).appearance!=="force-dark"&&r(e).appearance!=="force-auto"?(a(),u("div",Os,[d("p",Gs,I(r(t).darkModeSwitchLabel||"Appearance"),1),_(me)])):h("",!0)}}),js=g(Us,[["__scopeId","data-v-a3e2920d"]]),zs=["innerHTML"],Ws=m({__name:"VPNavScreenMenuLink",props:{item:{}},setup(s){const e=x("close-screen");return(t,o)=>(a(),k(D,{class:"VPNavScreenMenuLink",href:t.item.link,target:t.item.target,rel:t.item.rel,"no-icon":t.item.noIcon,onClick:r(e)},{default:f(()=>[d("span",{innerHTML:t.item.text},null,8,zs)]),_:1},8,["href","target","rel","no-icon","onClick"]))}}),Ks=g(Ws,[["__scopeId","data-v-fa963d97"]]),Rs=["innerHTML"],qs=m({__name:"VPNavScreenMenuGroupLink",props:{item:{}},setup(s){const e=x("close-screen");return(t,o)=>(a(),k(D,{class:"VPNavScreenMenuGroupLink",href:t.item.link,target:t.item.target,rel:t.item.rel,"no-icon":t.item.noIcon,onClick:r(e)},{default:f(()=>[d("span",{innerHTML:t.item.text},null,8,Rs)]),_:1},8,["href","target","rel","no-icon","onClick"]))}}),He=g(qs,[["__scopeId","data-v-e04f3e85"]]),Js={class:"VPNavScreenMenuGroupSection"},Xs={key:0,class:"title"},Ys=m({__name:"VPNavScreenMenuGroupSection",props:{text:{},items:{}},setup(s){return(e,t)=>(a(),u("div",Js,[e.text?(a(),u("p",Xs,I(e.text),1)):h("",!0),(a(!0),u(w,null,H(e.items,o=>(a(),k(He,{key:o.text,item:o},null,8,["item"]))),128))]))}}),Qs=g(Ys,[["__scopeId","data-v-f60dbfa7"]]),Zs=["aria-controls","aria-expanded"],xs=["innerHTML"],ea=["id"],ta={key:0,class:"item"},na={key:1,class:"item"},oa={key:2,class:"group"},sa=m({__name:"VPNavScreenMenuGroup",props:{text:{},items:{}},setup(s){const e=s,t=T(!1),o=y(()=>`NavScreenGroup-${e.text.replace(" ","-").toLowerCase()}`);function n(){t.value=!t.value}return(i,l)=>(a(),u("div",{class:M(["VPNavScreenMenuGroup",{open:t.value}])},[d("button",{class:"button","aria-controls":o.value,"aria-expanded":t.value,onClick:n},[d("span",{class:"button-text",innerHTML:i.text},null,8,xs),l[0]||(l[0]=d("span",{class:"vpi-plus button-icon"},null,-1))],8,Zs),d("div",{id:o.value,class:"items"},[(a(!0),u(w,null,H(i.items,v=>(a(),u(w,{key:JSON.stringify(v)},["link"in v?(a(),u("div",ta,[_(He,{item:v},null,8,["item"])])):"component"in v?(a(),u("div",na,[(a(),k(E(v.component),U({ref_for:!0},v.props,{"screen-menu":""}),null,16))])):(a(),u("div",oa,[_(Qs,{text:v.text,items:v.items},null,8,["text","items"])]))],64))),128))],8,ea)],2))}}),aa=g(sa,[["__scopeId","data-v-d99bfeec"]]),ra={key:0,class:"VPNavScreenMenu"},ia=m({__name:"VPNavScreenMenu",setup(s){const{theme:e}=L();return(t,o)=>r(e).nav?(a(),u("nav",ra,[(a(!0),u(w,null,H(r(e).nav,n=>(a(),u(w,{key:JSON.stringify(n)},["link"in n?(a(),k(Ks,{key:0,item:n},null,8,["item"])):"component"in n?(a(),k(E(n.component),U({key:1,ref_for:!0},n.props,{"screen-menu":""}),null,16)):(a(),k(aa,{key:2,text:n.text||"",items:n.items},null,8,["text","items"]))],64))),128))])):h("",!0)}}),la=m({__name:"VPNavScreenSocialLinks",setup(s){const{theme:e}=L();return(t,o)=>r(e).socialLinks?(a(),k(be,{key:0,class:"VPNavScreenSocialLinks",links:r(e).socialLinks},null,8,["links"])):h("",!0)}}),ca={class:"list"},ua=m({__name:"VPNavScreenTranslations",setup(s){const{localeLinks:e,currentLang:t}=q({correspondingLink:!0}),o=T(!1);function n(){o.value=!o.value}return(i,l)=>r(e).length&&r(t).label?(a(),u("div",{key:0,class:M(["VPNavScreenTranslations",{open:o.value}])},[d("button",{class:"title",onClick:n},[l[0]||(l[0]=d("span",{class:"vpi-languages icon lang"},null,-1)),z(" "+I(r(t).label)+" ",1),l[1]||(l[1]=d("span",{class:"vpi-chevron-down icon chevron"},null,-1))]),d("ul",ca,[(a(!0),u(w,null,H(r(e),v=>(a(),u("li",{key:v.link,class:"item"},[_(D,{class:"link",href:v.link},{default:f(()=>[z(I(v.text),1)]),_:2},1032,["href"])]))),128))])],2)):h("",!0)}}),da=g(ua,[["__scopeId","data-v-516e4bc3"]]),va={class:"container"},pa=m({__name:"VPNavScreen",props:{open:{type:Boolean}},setup(s){const e=T(null),t=Te(ee?document.body:null);return(o,n)=>(a(),k(ue,{name:"fade",onEnter:n[0]||(n[0]=i=>t.value=!0),onAfterLeave:n[1]||(n[1]=i=>t.value=!1)},{default:f(()=>[o.open?(a(),u("div",{key:0,class:"VPNavScreen",ref_key:"screen",ref:e,id:"VPNavScreen"},[d("div",va,[c(o.$slots,"nav-screen-content-before",{},void 0,!0),_(ia,{class:"menu"}),_(da,{class:"translations"}),_(js,{class:"appearance"}),_(la,{class:"social-links"}),c(o.$slots,"nav-screen-content-after",{},void 0,!0)])],512)):h("",!0)]),_:3}))}}),fa=g(pa,[["__scopeId","data-v-2dd6d0c7"]]),ha={key:0,class:"VPNav"},ma=m({__name:"VPNav",setup(s){const{isScreenOpen:e,closeScreen:t,toggleScreen:o}=uo(),{frontmatter:n}=L(),i=y(()=>n.value.navbar!==!1);return Ne("close-screen",t),Y(()=>{ee&&document.documentElement.classList.toggle("hide-nav",!i.value)}),(l,v)=>i.value?(a(),u("header",ha,[_(Fs,{"is-screen-open":r(e),onToggleScreen:r(o)},{"nav-bar-title-before":f(()=>[c(l.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":f(()=>[c(l.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":f(()=>[c(l.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":f(()=>[c(l.$slots,"nav-bar-content-after",{},void 0,!0)]),_:3},8,["is-screen-open","onToggleScreen"]),_(fa,{open:r(e)},{"nav-screen-content-before":f(()=>[c(l.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":f(()=>[c(l.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3},8,["open"])])):h("",!0)}}),_a=g(ma,[["__scopeId","data-v-7ad780c2"]]),ka=["role","tabindex"],ba={key:1,class:"items"},ga=m({__name:"VPSidebarItem",props:{item:{},depth:{}},setup(s){const e=s,{collapsed:t,collapsible:o,isLink:n,isActiveLink:i,hasActiveLink:l,hasChildren:v,toggle:p}=pt(y(()=>e.item)),$=y(()=>v.value?"section":"div"),V=y(()=>n.value?"a":"div"),b=y(()=>v.value?e.depth+2===7?"p":`h${e.depth+2}`:"p"),P=y(()=>n.value?void 0:"button"),N=y(()=>[[`level-${e.depth}`],{collapsible:o.value},{collapsed:t.value},{"is-link":n.value},{"is-active":i.value},{"has-active":l.value}]);function A(S){"key"in S&&S.key!=="Enter"||!e.item.link&&p()}function C(){e.item.link&&p()}return(S,B)=>{const G=K("VPSidebarItem",!0);return a(),k(E($.value),{class:M(["VPSidebarItem",N.value])},{default:f(()=>[S.item.text?(a(),u("div",U({key:0,class:"item",role:P.value},We(S.item.items?{click:A,keydown:A}:{},!0),{tabindex:S.item.items&&0}),[B[1]||(B[1]=d("div",{class:"indicator"},null,-1)),S.item.link?(a(),k(D,{key:0,tag:V.value,class:"link",href:S.item.link,rel:S.item.rel,target:S.item.target},{default:f(()=>[(a(),k(E(b.value),{class:"text",innerHTML:S.item.text},null,8,["innerHTML"]))]),_:1},8,["tag","href","rel","target"])):(a(),k(E(b.value),{key:1,class:"text",innerHTML:S.item.text},null,8,["innerHTML"])),S.item.collapsed!=null&&S.item.items&&S.item.items.length?(a(),u("div",{key:2,class:"caret",role:"button","aria-label":"toggle section",onClick:C,onKeydown:Ke(C,["enter"]),tabindex:"0"},B[0]||(B[0]=[d("span",{class:"vpi-chevron-right caret-icon"},null,-1)]),32)):h("",!0)],16,ka)):h("",!0),S.item.items&&S.item.items.length?(a(),u("div",ba,[S.depth<5?(a(!0),u(w,{key:0},H(S.item.items,R=>(a(),k(G,{key:R.text,item:R,depth:S.depth+1},null,8,["item","depth"]))),128)):h("",!0)])):h("",!0)]),_:1},8,["class"])}}}),$a=g(ga,[["__scopeId","data-v-0009425e"]]),ya=m({__name:"VPSidebarGroup",props:{items:{}},setup(s){const e=T(!0);let t=null;return j(()=>{t=setTimeout(()=>{t=null,e.value=!1},300)}),Re(()=>{t!=null&&(clearTimeout(t),t=null)}),(o,n)=>(a(!0),u(w,null,H(o.items,i=>(a(),u("div",{key:i.text,class:M(["group",{"no-transition":e.value}])},[_($a,{item:i,depth:0},null,8,["item"])],2))),128))}}),Pa=g(ya,[["__scopeId","data-v-51288d80"]]),La={class:"nav",id:"VPSidebarNav","aria-labelledby":"sidebar-aria-label",tabindex:"-1"},Va=m({__name:"VPSidebar",props:{open:{type:Boolean}},setup(s){const{sidebarGroups:e,hasSidebar:t}=O(),o=s,n=T(null),i=Te(ee?document.body:null);F([o,n],()=>{var v;o.open?(i.value=!0,(v=n.value)==null||v.focus()):i.value=!1},{immediate:!0,flush:"post"});const l=T(0);return F(e,()=>{l.value+=1},{deep:!0}),(v,p)=>r(t)?(a(),u("aside",{key:0,class:M(["VPSidebar",{open:v.open}]),ref_key:"navEl",ref:n,onClick:p[0]||(p[0]=qe(()=>{},["stop"]))},[p[2]||(p[2]=d("div",{class:"curtain"},null,-1)),d("nav",La,[p[1]||(p[1]=d("span",{class:"visually-hidden",id:"sidebar-aria-label"}," Sidebar Navigation ",-1)),c(v.$slots,"sidebar-nav-before",{},void 0,!0),(a(),k(Pa,{items:r(e),key:l.value},null,8,["items"])),c(v.$slots,"sidebar-nav-after",{},void 0,!0)])],2)):h("",!0)}}),Sa=g(Va,[["__scopeId","data-v-42c4c606"]]),Ta=m({__name:"VPSkipLink",setup(s){const{theme:e}=L(),t=Z(),o=T();F(()=>t.path,()=>o.value.focus());function n({target:i}){const l=document.getElementById(decodeURIComponent(i.hash).slice(1));if(l){const v=()=>{l.removeAttribute("tabindex"),l.removeEventListener("blur",v)};l.setAttribute("tabindex","-1"),l.addEventListener("blur",v),l.focus(),window.scrollTo(0,0)}}return(i,l)=>(a(),u(w,null,[d("span",{ref_key:"backToTop",ref:o,tabindex:"-1"},null,512),d("a",{href:"#VPContent",class:"VPSkipLink visually-hidden",onClick:n},I(r(e).skipToContentLabel||"Skip to content"),1)],64))}}),Na=g(Ta,[["__scopeId","data-v-fcbfc0e0"]]),Ma=m({__name:"Layout",setup(s){const{isOpen:e,open:t,close:o}=O(),n=Z();F(()=>n.path,o),vt(e,o);const{frontmatter:i}=L(),l=Je(),v=y(()=>!!l["home-hero-image"]);return Ne("hero-image-slot-exists",v),(p,$)=>{const V=K("Content");return r(i).layout!==!1?(a(),u("div",{key:0,class:M(["Layout",r(i).pageClass])},[c(p.$slots,"layout-top",{},void 0,!0),_(Na),_(xe,{class:"backdrop",show:r(e),onClick:r(o)},null,8,["show","onClick"]),_(_a,null,{"nav-bar-title-before":f(()=>[c(p.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":f(()=>[c(p.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":f(()=>[c(p.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":f(()=>[c(p.$slots,"nav-bar-content-after",{},void 0,!0)]),"nav-screen-content-before":f(()=>[c(p.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":f(()=>[c(p.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3}),_(co,{open:r(e),onOpenMenu:r(t)},null,8,["open","onOpenMenu"]),_(Sa,{open:r(e)},{"sidebar-nav-before":f(()=>[c(p.$slots,"sidebar-nav-before",{},void 0,!0)]),"sidebar-nav-after":f(()=>[c(p.$slots,"sidebar-nav-after",{},void 0,!0)]),_:3},8,["open"]),_(qn,null,{"page-top":f(()=>[c(p.$slots,"page-top",{},void 0,!0)]),"page-bottom":f(()=>[c(p.$slots,"page-bottom",{},void 0,!0)]),"not-found":f(()=>[c(p.$slots,"not-found",{},void 0,!0)]),"home-hero-before":f(()=>[c(p.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":f(()=>[c(p.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":f(()=>[c(p.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":f(()=>[c(p.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":f(()=>[c(p.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":f(()=>[c(p.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":f(()=>[c(p.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":f(()=>[c(p.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":f(()=>[c(p.$slots,"home-features-after",{},void 0,!0)]),"doc-footer-before":f(()=>[c(p.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":f(()=>[c(p.$slots,"doc-before",{},void 0,!0)]),"doc-after":f(()=>[c(p.$slots,"doc-after",{},void 0,!0)]),"doc-top":f(()=>[c(p.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":f(()=>[c(p.$slots,"doc-bottom",{},void 0,!0)]),"aside-top":f(()=>[c(p.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":f(()=>[c(p.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":f(()=>[c(p.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":f(()=>[c(p.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":f(()=>[c(p.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":f(()=>[c(p.$slots,"aside-ads-after",{},void 0,!0)]),_:3}),_(Zn),c(p.$slots,"layout-bottom",{},void 0,!0)],2)):(a(),k(V,{key:1}))}}}),Ia=g(Ma,[["__scopeId","data-v-d8b57b2d"]]),$e={Layout:Ia,enhanceApp:({app:s})=>{s.component("Badge",Ye)}},Aa={extends:$e,Layout:()=>Xe($e.Layout,null,{}),enhanceApp({app:s,router:e,siteData:t}){}};export{Aa as R,ps as c,L as u};
|
@@ -0,0 +1,127 @@
|
|
1
|
+
import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const c=JSON.parse('{"title":"CLI and Tasks","description":"","frontmatter":{},"headers":[],"relativePath":"cli.md","filePath":"cli.md"}'),t={name:"cli.md"};function p(l,s,h,o,r,d){return n(),i("div",null,s[0]||(s[0]=[e(`<h1 id="cli-and-tasks" tabindex="-1">CLI and Tasks <a class="header-anchor" href="#cli-and-tasks" aria-label="Permalink to "CLI and Tasks""></a></h1><p>Your app will likely need command-line or non-browser-based tasks. Outside of standard needs like running a dev server or managing the database, you will have app-specific needs, like transforming data or performing one-time bulk operations.</p><p>In Brut, these are done as Ruby CLI apps, powered by the standard library's <code>OptionParser</code>.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to "Overview""></a></h2><p>The various commands installed with your Brut app in <code>bin/</code> are powered by Brut's CLI support. You can use this support to create your own tasks.</p><p>The main feature this provides is a consistent startup of your app's internals and configuration. This startup is almost exactly the same as the web app, so you can safely rely on connections to the database, the ability to queue jobs, or the behavior of your business logic. There's just no web server and no front-end.</p><h3 id="brut-cli-user-interface" tabindex="-1">Brut CLI User Interface <a class="header-anchor" href="#brut-cli-user-interface" aria-label="Permalink to "Brut CLI User Interface""></a></h3><p>All Brut CLI apps produce a user interface that should be familiar if you've used CLI apps that support subcommands, like <code>git</code>:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>> git status</span></span>
|
2
|
+
<span class="line"><span>> git checkout</span></span>
|
3
|
+
<span class="line"><span>> git commit</span></span></code></pre></div><p>Each Brut CLI invocation has six parts:</p><ul><li>The executable, e.g. <code>bin/db</code></li><li><em>Global Options</em> which are strings that start witih one or two dashes and control the behavior of the entire app, e.g. <code>--log-level=debug</code> These are optional and there can be any number of them.</li><li>The subcommand, which is a single string indicating what actual function to perform, e.g. <code>rebuild</code> in <code>bin/db rebuild</code></li><li><em>Command Options</em> which are just like global options, but they come <em>after</em> the command and apply only to that command</li><li>Arguments, which are any strings left over after the <em>command options</em> are parsed.</li></ul><p>All of this is powered by Ruby's <code>OptionParser</code>, which results in a canonical, UNIX-like UI.</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>> bin/my_cli --global-option list --command-option arg1 arg2</span></span>
|
4
|
+
<span class="line"><span> \\----+---/ \\-------+-----/ \\-+/ \\--------+-----/ \\---+---/</span></span>
|
5
|
+
<span class="line"><span> | | | | |</span></span>
|
6
|
+
<span class="line"><span> Executable | | | |</span></span>
|
7
|
+
<span class="line"><span> | | | |</span></span>
|
8
|
+
<span class="line"><span> Command | | |</span></span>
|
9
|
+
<span class="line"><span> Options | |</span></span>
|
10
|
+
<span class="line"><span> | | |</span></span>
|
11
|
+
<span class="line"><span> Subcommand | |</span></span>
|
12
|
+
<span class="line"><span> | |</span></span>
|
13
|
+
<span class="line"><span> Command |</span></span>
|
14
|
+
<span class="line"><span> Options |</span></span>
|
15
|
+
<span class="line"><span> |</span></span>
|
16
|
+
<span class="line"><span> |</span></span>
|
17
|
+
<span class="line"><span> Arguments</span></span></code></pre></div><p>Brut CLI apps all respond to <code>-h</code> and <code>--help</code> to view the list of subcommands, global options, and any environment variables that affect the behavior.</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>> bin/db -h </span></span>
|
18
|
+
<span class="line"><span>bin/db [global options] commands [command options] [args]</span></span>
|
19
|
+
<span class="line"><span></span></span>
|
20
|
+
<span class="line"><span> Manage your database in development, test, and production</span></span>
|
21
|
+
<span class="line"><span></span></span>
|
22
|
+
<span class="line"><span>GLOBAL OPTIONS</span></span>
|
23
|
+
<span class="line"><span></span></span>
|
24
|
+
<span class="line"><span> -h, --help Get help</span></span>
|
25
|
+
<span class="line"><span> --log-level=LEVEL Set log level. Allowed values: debug, info, warn, error, fatal. Default 'fatal'</span></span>
|
26
|
+
<span class="line"><span> --verbose Set log level to 'debug', which will produce maximum output</span></span>
|
27
|
+
<span class="line"><span></span></span>
|
28
|
+
<span class="line"><span>ENVIRONMENT VARIABLES</span></span>
|
29
|
+
<span class="line"><span></span></span>
|
30
|
+
<span class="line"><span> BRUT_CLI_RAISE_ON_ERROR - if set, shows backtrace on errors</span></span>
|
31
|
+
<span class="line"><span> LOG_LEVEL - log level if --log-level or --verbose is omitted</span></span>
|
32
|
+
<span class="line"><span></span></span>
|
33
|
+
<span class="line"><span></span></span>
|
34
|
+
<span class="line"><span>COMMANDS</span></span>
|
35
|
+
<span class="line"><span></span></span>
|
36
|
+
<span class="line"><span> help - Get help on a command</span></span>
|
37
|
+
<span class="line"><span> create - Create the database if it does not exist</span></span>
|
38
|
+
<span class="line"><span> drop - Drop the database if it exists</span></span>
|
39
|
+
<span class="line"><span> migrate - Apply any outstanding migrations to the database</span></span>
|
40
|
+
<span class="line"><span> new_migration - Create a new migration file</span></span>
|
41
|
+
<span class="line"><span> rebuild - Drop, re-create, and run migrations, effecitvely rebuilding the entire database</span></span>
|
42
|
+
<span class="line"><span> seed - Load seed data into the database</span></span>
|
43
|
+
<span class="line"><span> status - Check the status of the database and migrations</span></span></code></pre></div><p>Brut CLI apps also support the subcommand <code>help</code> which will show help on a given subcommand, including the command options and arguments.</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>> bin/db help rebuild</span></span>
|
44
|
+
<span class="line"><span>Usage: bin/db [global options] rebuild [command options] </span></span>
|
45
|
+
<span class="line"><span></span></span>
|
46
|
+
<span class="line"><span> Drop, re-create, and run migrations, effecitvely rebuilding the entire database</span></span>
|
47
|
+
<span class="line"><span></span></span>
|
48
|
+
<span class="line"><span>GLOBAL OPTIONS</span></span>
|
49
|
+
<span class="line"><span></span></span>
|
50
|
+
<span class="line"><span> -h, --help Get help</span></span>
|
51
|
+
<span class="line"><span> --log-level=LEVEL Set log level. Allowed values: debug, info, warn, error, fatal. Default 'fatal'</span></span>
|
52
|
+
<span class="line"><span> --verbose Set log level to 'debug', which will produce maximum output</span></span>
|
53
|
+
<span class="line"><span></span></span>
|
54
|
+
<span class="line"><span>ENVIRONMENT VARIABLES</span></span>
|
55
|
+
<span class="line"><span></span></span>
|
56
|
+
<span class="line"><span> BRUT_CLI_RAISE_ON_ERROR - if set, shows backtrace on errors</span></span>
|
57
|
+
<span class="line"><span> LOG_LEVEL - log level if --log-level or --verbose is omitted</span></span>
|
58
|
+
<span class="line"><span> RACK_ENV - default project environment when --env is omitted</span></span>
|
59
|
+
<span class="line"><span></span></span>
|
60
|
+
<span class="line"><span></span></span>
|
61
|
+
<span class="line"><span>COMMAND OPTIONS</span></span>
|
62
|
+
<span class="line"><span></span></span>
|
63
|
+
<span class="line"><span> --env=ENVIRONMENT Project environment (default 'development')</span></span></code></pre></div><p>All of this means that the bulk of CLI-specific code you will write is specifying these options and documentation, then deferring to your business logic.</p><h3 id="basic-cli" tabindex="-1">Basic CLI <a class="header-anchor" href="#basic-cli" aria-label="Permalink to "Basic CLI""></a></h3><p>Every CLI app is a class that extends <a href="/api/Brut/CLI/App.html" target="_self" rel="noopener" data-no-router><code>Brut::CLI::App</code></a>. This class should contain one inner class for each subcommand. Those classes should extend <a href="/api/Brut/CLI/App.html" target="_self" rel="noopener" data-no-router><code>Brut::CLI::App</code></a>.</p><p>Inside your <a href="/api/Brut/CLI/App.html" target="_self" rel="noopener" data-no-router><code>Brut::CLI::App</code></a> class, you can call a few class methods to declare aspects of the UI. In particular, <code>opts</code> returns the <code>OptionParser</code> in play that you can use to declare global options. Unlike <code>OptionParser</code>'s <code>on</code> method, Brut's does not require providing a block. Brut will store the runtime options in a hash (see below).</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> MyAppCLI</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">CLI</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">App</span></span>
|
64
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> description </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"My awesome command line app"</span></span>
|
65
|
+
<span class="line"></span>
|
66
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> opts.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"--dry-run"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Only show what would happen; don't change anything"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
67
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> opts.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"--status STATUS"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Set the status you'd like to see"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
68
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>This code means your app's global options are <code>--dry-run</code>, which will not accept an argument, and <code>--status</code> which <em>must</em> be given an argument. The arguments to <code>on</code> are the same as those for Ruby's <code>OptionParser</code>.</p><p>Declaring subcommands provides a similar API. Let's say our app has a "status" subcommand, and a "run" subcommand.</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> MyAppCLI</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">CLI</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">App</span></span>
|
69
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> description </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"My awesome command line app"</span></span>
|
70
|
+
<span class="line"></span>
|
71
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> opts.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"--dry-run"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Only show what would happen; don't change anything"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
72
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> opts.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"--status STATUS"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Set the status you'd like to see"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
73
|
+
<span class="line"></span>
|
74
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Status</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">CLI</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Command</span></span>
|
75
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> description </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Show the status"</span></span>
|
76
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> args </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"files to get the status of"</span></span>
|
77
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> opts.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"-l"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"--long"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Show long-format"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
78
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
79
|
+
<span class="line"></span>
|
80
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Run</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">CLI</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Command</span></span>
|
81
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> description </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Run any outstanding tasks"</span></span>
|
82
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> opts.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"--exit-status STATUS"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Exit status on success"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
83
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
84
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>This enables commands like <code>bin/my_app status -l foo.txt bar.rb</code> or <code>bin/my_app status --exit-status=3</code>.</p><p>The names are derived from the class name. You can override them by using <code>command_name</code> inside a command class.</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Run</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">CLI</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Command</span></span>
|
85
|
+
<span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> command_name </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"exec"</span></span>
|
86
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> </span></span>
|
87
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
|
88
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>The only thing left is to specify what happens for each subcommand. To do that, implement <code>execute</code>.</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Status</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">CLI</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Command</span></span>
|
89
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> description </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Show the status"</span></span>
|
90
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> args </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"files to get the status of"</span></span>
|
91
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> opts.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"-l"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"--long"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Show long-format"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
92
|
+
<span class="line"></span>
|
93
|
+
<span class="line highlighted"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> execute</span></span>
|
94
|
+
<span class="line highlighted"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
|
95
|
+
<span class="line highlighted"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
96
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span>
|
97
|
+
<span class="line"></span>
|
98
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Run</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">CLI</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Command</span></span>
|
99
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> description </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Run any outstanding tasks"</span></span>
|
100
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> opts.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"--exit-status STATUS"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Exit status on success"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
101
|
+
<span class="line"></span>
|
102
|
+
<span class="line highlighted"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> execute</span></span>
|
103
|
+
<span class="line highlighted"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
|
104
|
+
<span class="line highlighted"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
105
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><h3 id="implementing-execute" tabindex="-1">Implementing <code>execute</code> <a class="header-anchor" href="#implementing-execute" aria-label="Permalink to "Implementing \`execute\`""></a></h3><p>Once <code>execute</code> is called, your app's internals will have been setup and bootstrapped. That means all you data models can access the database, and any other setup will have ocurred. Generally, <code>execute</code> can then have whatever code makes sense.</p><p>That said, <code>execute</code> has access to a few values to understand the command line invocation and to support testing.</p><ul><li><code>#options</code> - the command options passed on the command line, as a <a href="/api/Brut/CLI/Options.html" target="_self" rel="noopener" data-no-router><code>Brut::CLI::Options</code></a></li><li><code>#global_options</code> - the global options passed on the command line, as a <a href="/api/Brut/CLI/Options.html" target="_self" rel="noopener" data-no-router><code>Brut::CLI::Options</code></a></li><li><code>#args</code> - the args passed on the command line, as an array of strings</li><li><code>#out</code> - an IO you should use to print messages to the standard out.</li><li><code>#err</code> - on IO you should use to print messages to the standard error.</li><li><code>#system!</code> - the method you should use to spawn child processes. This is preferable to <code>Kernel.system</code> because the command executed will be logged, and your app will raise if the command fails. This makes it more straightfoward to safely script other command line invocations.</li></ul><h3 id="advanced-options" tabindex="-1">Advanced Options <a class="header-anchor" href="#advanced-options" aria-label="Permalink to "Advanced Options""></a></h3><p>The API documentation will show you other options for creating your CLI UI, but there are a few aspects to highlight.</p><ul><li>Calling <code>configure_only!</code> in your app, will run your app's subcommands without starting up Brut. This is generally not needed, but can be useful if you want to do basic scripting without worrying about connecting to the database.</li><li><code>default_command</code> can be used to specify the command to run if none is given. <code>bin/test</code> uses this to run <code>bin/test run</code>.</li><li><code>requires_project_env</code> can be used at the app or command level to indicate that <code>--env</code> is accepted on the command line to set the project environment for the code to run in. Omitting this means that the actual project env used when the app runs is undefined.</li><li>Use <code>env_var</code> to document environment variables your app will use that affect its behavior.</li></ul><h3 id="the-file-in-bin" tabindex="-1">The file in <code>bin</code> <a class="header-anchor" href="#the-file-in-bin" aria-label="Permalink to "The file in \`bin\`""></a></h3><p>Currently, Brut doesn't provide a way to create this file, but it's relatively straightforward. It's almost entirely boilerplate except for your class:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">#!/usr/bin/env ruby</span></span>
|
106
|
+
<span class="line"></span>
|
107
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">require</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "bundler"</span></span>
|
108
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Bundler</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">require</span></span>
|
109
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">require</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "pathname"</span></span>
|
110
|
+
<span class="line"></span>
|
111
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">require</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "brut/cli"</span></span>
|
112
|
+
<span class="line"></span>
|
113
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">APP_PATH</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> File</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">join</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">File</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">dirname</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">($0),</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">".."</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"app"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"src"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
114
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">$: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"><<</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> APP_PATH</span></span>
|
115
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">require</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "cli/my_app"</span></span>
|
116
|
+
<span class="line"></span>
|
117
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">exit</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">CLI</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">app</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span></span>
|
118
|
+
<span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> MyAppCLI</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
119
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> project_root:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Pathname</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">($0).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">dirname</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> /</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> ".."</span></span>
|
120
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></div><h2 id="testing" tabindex="-1">Testing <a class="header-anchor" href="#testing" aria-label="Permalink to "Testing""></a></h2><p>Depending on how your CLI is impelmented, testing it may not be that beneficial. If <code>execute</code> simply defers to your back-end, your tests of that back-end will generally suffice.</p><p>That said, your command classes are normal Ruby classes, so you can test them in a conventional way.</p><p>The initalizer of each command class looks like so:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span></span>
|
121
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> command_options:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
122
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> global_options:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
123
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> args:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
124
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> out:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
125
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> err:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
126
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> executor:</span></span>
|
127
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></div><p><code>command_options</code> and <code>global_options</code> accept a <a href="/api/Brut/CLI/Options.html" target="_self" rel="noopener" data-no-router><code>Brut::CLI::Options</code></a>, which can be created with a Hash to represent the parsed command line options.</p><p><code>args</code> is a list of Strings. <code>out</code> and <code>err</code> are IOs, so you can use the standard library's <code>StringIO</code> to both capture the output and supress it from your test's standard output/error.</p><p><code>executor</code> is a <a href="/api/Brut/CLI/Executor.html" target="_self" rel="noopener" data-no-router><code>Brut::CLI::Executor</code></a>, which is a wrapper around the standard library's <code>Open3</code>. You can mock this to set expectations on what child processes are launched and how they behave.</p><h2 id="recommended-practices" tabindex="-1">Recommended Practices <a class="header-anchor" href="#recommended-practices" aria-label="Permalink to "Recommended Practices""></a></h2><p><code>execute</code> should defer to classes in your back-end, ideally a single method of a single class. Excessive logic or UI in your CLI will be hard to test and maintain.</p><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to "Technical Notes""></a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's internals, the source code is always more correct.</p></div><p><em>Last Updated May 9, 2025</em></p>`,53)]))}const u=a(t,[["render",p]]);export{c as __pageData,u as default};
|
@@ -0,0 +1 @@
|
|
1
|
+
import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const c=JSON.parse('{"title":"CLI and Tasks","description":"","frontmatter":{},"headers":[],"relativePath":"cli.md","filePath":"cli.md"}'),t={name:"cli.md"};function p(l,s,h,o,r,d){return n(),i("div",null,s[0]||(s[0]=[e("",53)]))}const u=a(t,[["render",p]]);export{c as __pageData,u as default};
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import{_ as i,c as a,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const c=JSON.parse('{"title":"Components","description":"","frontmatter":{},"headers":[],"relativePath":"components.md","filePath":"components.md"}'),t={name:"components.md"};function l(p,s,h,k,o,d){return n(),a("div",null,s[0]||(s[0]=[e(`<h1 id="components" tabindex="-1">Components <a class="header-anchor" href="#components" aria-label="Permalink to "Components""></a></h1><p>Components in Brut are Phlex Components: a class that can hold data and use that to generate HTML. Components are the primary way you achieve re-use of markup or view logic.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to "Overview""></a></h2><p><a href="/api/Brut/FrontEnd/Component.html" target="_self" rel="noopener" data-no-router><code>Brut::FrontEnd::Component</code></a> inherits from <code>Phlex::HTML</code>, which means that to create a component you must do three things:</p><ol><li>Create a class in <code>app/src/front_end/components</code> that inherites from <code>AppComponent</code> (which is part of your app and inherits from <a href="/api/Brut/FrontEnd/Component.html" target="_self" rel="noopener" data-no-router><code>Brut::FrontEnd::Component</code></a>)</li><li>Implement an initializer that receives anything your component needs to do its job. It is recommended (but not required) that your initializer use only keyword arguments.</li><li>Implement <code>view_template</code> in which you make calls to Phlex' API.</li></ol><h3 id="simple-component" tabindex="-1">Simple Component <a class="header-anchor" href="#simple-component" aria-label="Permalink to "Simple Component""></a></h3><p>For example, suppose you want a re-usable button component whose HTML would look like so:</p><div class="language-html vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">html</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"button button__small button__red"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">></span></span>
|
2
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Delete Files</span></span>
|
3
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"></</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">></span></span></code></pre></div><p>Your button can be large (the default) or small, and could be gray (default), green, or red. Your button could also have an optional <code>formaction</code> attribute. Let's also suppose the label could be a string or embedded HTML.</p><p>Your constructor would need to accept the size, color, label, and formaction.</p><p>You can create a scaffold via <code>bin/scaffold</code>:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>> bin/scaffold component Button</span></span></code></pre></div><p>You'll first implement the initializer, like so:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># app/src/front_end/components/button.rb</span></span>
|
4
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Button</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
|
5
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span></span>
|
6
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> size:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :large</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
7
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> color:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :gray</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
8
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> formaction:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> nil</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
9
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> label:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :use_block</span></span>
|
10
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> )</span></span>
|
11
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @size </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> size</span></span>
|
12
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @color </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> color</span></span>
|
13
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @formaction </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> formaction</span></span>
|
14
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @label </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> label</span></span>
|
15
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
16
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>This initializer is rather simplistic. You may want to validate the values here to prevent the construction of an invalid component.</p><p>Now, implement <code>view_template</code>. This method will receive a block if one is given when the component is used. We'll see an example in a minute.</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># app/src/front_end/components/button.rb</span></span>
|
17
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Button</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
|
18
|
+
<span class="line"></span>
|
19
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
|
20
|
+
<span class="line"></span>
|
21
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
|
22
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> attributes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> = {</span></span>
|
23
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> class:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [</span></span>
|
24
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "button"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
25
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "button__</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">#{</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">@size</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">}</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
26
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "button__#@{color}"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
27
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ],</span></span>
|
28
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> formaction:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @formaction,</span></span>
|
29
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
30
|
+
<span class="line"></span>
|
31
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">**</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">attributes) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
32
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @label </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :use_block</span></span>
|
33
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> yield</span></span>
|
34
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> else</span></span>
|
35
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @label</span></span>
|
36
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
37
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
38
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
39
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>If you've never used Phlex before, it's refreshingly straightforward:</p><ul><li>There's a method for each HTML element.</li><li>The method's parameters produce attributes in the HTML that is generated.</li><li>If a parameter's value is an array (like <code>class:</code>), the values are joined with strings to form the atttribute's value in HTML.</li><li>If the element can have inner content, whatever happens inside a yielded block becomes that inner content.</li></ul><p>To use this component, we can call <code>render</code> in either the <code>view_template</code> of another component or the <code>page_template</code> of a <a href="/pages.html">page</a>:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
|
40
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> form </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
41
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> render </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">label:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "Submit"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
42
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> render </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">label:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "Nevermind"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">size:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :small</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">color:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :red</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
43
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> render </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">color:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :green</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
44
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> img</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">src:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "/images/ok.png"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">alt:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "OK icon"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
45
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
46
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
47
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Note that the block passed to <code>render</code> is the block available when <code>yield</code> is called inside <code>view_template</code>.</p><p>There are two special types of components beyond what we have just seen. <em>Global</em> Components and <em>Page private</em> components.</p><h3 id="global-components" tabindex="-1">Global Components <a class="header-anchor" href="#global-components" aria-label="Permalink to "Global Components""></a></h3><p>As we saw above, creating a component is just like creating any Ruby class: you call <code>.new</code> on it. If you create a component that uses request-level data, such as the flash or session, it would mean that any page or component that used <em>that</em> component would need to accept the flash or session as a parameter to its initializer, even if it it was otherwise not needed.</p><p>In those cases, <code>global_component</code> can be used to leverage <a href="/keyword-injection.html">keyword injection</a> to have Brut create the component. That way, its initializer's parameters don't need to be passed into the page or component using the global component.</p><p>Suppose we had a component to display the flash:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> FlashMessage</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
|
48
|
+
<span class="line"></span>
|
49
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">flash:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
50
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @flash </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> flash</span></span>
|
51
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
52
|
+
<span class="line"></span>
|
53
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
|
54
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @flash.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">notice?</span></span>
|
55
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">role:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "status"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) { @flash.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">notice</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
56
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> elsif</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @flash.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">alert?</span></span>
|
57
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">role:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "alert"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) { @flash.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">alert</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
58
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
59
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
60
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>To use it without having to instantiate it, call <code>global_component</code> with the component's class:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> HomePage</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppPage</span></span>
|
61
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> page_template</span></span>
|
62
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> header </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
63
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> render </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">global_component</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">FlashMessage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
64
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
65
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
66
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Brut currently requires an all-or-nothing approach to global components. Either the component can be injected with all its initializer parameters or it must be created explicitly by the page or component. You cannot have a component receive request-level keyword injection for some parameters with the page providing the rest.</p></div><p>Components can also be scoped to a page.</p><h3 id="page-private-components" tabindex="-1">Page Private Components <a class="header-anchor" href="#page-private-components" aria-label="Permalink to "Page Private Components""></a></h3><p>Often, components are helpful to simplifying a page's template or managing re-use within a page, but such a component isn't designed for use outside that page. For example, if the page renders a table, but the logic for each row is complex, you may want that in a separate component, even though it would be useless outside the page.</p><p>Brut provides a way to create a <em>page private</em> component that exists as an inner class of a page. It's not truly private, since it's still a Ruby class anyone can use, but it's form and source location communicate intent.</p><p>Suppose our <code>HomePage</code> has a list of widgets on it, but we want each widget's HTML managed by a separate component:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> HomePage</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppPage</span></span>
|
67
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> page_template</span></span>
|
68
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> header </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
69
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h1 { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Check out these Widgets!"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
70
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
71
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> main </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
72
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ul </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
73
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> DB</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Widget</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">all</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">each</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> |widget|</span></span>
|
74
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> render</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">HomePage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">WidgetListItem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">widget:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">))</span></span>
|
75
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
76
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
77
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
78
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
79
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p><code>HomePage::WidgetListItem</code> can be created like so:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>bin/scaffold component --page=HomePage WidgetListItem</span></span></code></pre></div><p>This will create <code>app/src/front_end/pages/home_page/widget_list_item.rb</code>, which you can then implement like a normal component:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># app/src/front_end/pages/home_page/widget_list_item.rb</span></span>
|
80
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> HomePage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">WidgetListItem</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
|
81
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">widget:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
82
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @widget </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> widget</span></span>
|
83
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
84
|
+
<span class="line"></span>
|
85
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
|
86
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> li </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
87
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h2 { @widget.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
88
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { @widget.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">description</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
89
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
90
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
91
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>The only special thing about a page private component is that it can access the page's I18n translations. As we'll discussion in <a href="/i18n.html">I18n</a>, The <code>t</code> method will try to locate translations based on the page on which <code>t</code> is called. A page private component will also trigger this behavior, but a normal component will not.</p><p>For example, the following code will look for <code>pages.HomePage.status.«status»</code> when generated the <code><h3></code>:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># app/src/front_end/pages/home_page/widget_list_item.rb</span></span>
|
92
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> HomePage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">WidgetListItem</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
|
93
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">widget:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
94
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @widget </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> widget</span></span>
|
95
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
96
|
+
<span class="line"></span>
|
97
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
|
98
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> li </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
99
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h2 { @widget.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
100
|
+
<span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h3 { </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">t</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">([ </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:status</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, @widget.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">status</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ]) }</span></span>
|
101
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { @widget.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">description</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
102
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
103
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
104
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><h2 id="testing" tabindex="-1">Testing <a class="header-anchor" href="#testing" aria-label="Permalink to "Testing""></a></h2><p>Test widgets exactly as you would <a href="/pages.html#testing">pages</a>. The only difference is that components always render HTML and have no <code>before_generate</code> concept.</p><h2 id="recommended-practices" tabindex="-1">Recommended Practices <a class="header-anchor" href="#recommended-practices" aria-label="Permalink to "Recommended Practices""></a></h2><p>The <a href="/pages.html#recommended-practices">recommended practices for pages</a> all apply to components, too.</p><p>Beyond that, components are intended to be lightweight, so use them liberally. Any page or component that has complex markup can be extracted to another component and more easily unit-tested.</p><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to "Technical Notes""></a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's internals, the source code is always more correct.</p></div><p><em>Last Updated May 5, 2025</em></p><p>As mentioned, components are Phlex components, but have various helpers mixed-into them. Components are currently tightly coupled to Phlex and there is no plan to allow alternate implementations of view logic that isn't supported by Phlex.</p>`,53)]))}const g=i(t,[["render",l]]);export{c as __pageData,g as default};
|
@@ -0,0 +1 @@
|
|
1
|
+
import{_ as i,c as a,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const c=JSON.parse('{"title":"Components","description":"","frontmatter":{},"headers":[],"relativePath":"components.md","filePath":"components.md"}'),t={name:"components.md"};function l(p,s,h,k,o,d){return n(),a("div",null,s[0]||(s[0]=[e("",53)]))}const g=i(t,[["render",l]]);export{c as __pageData,g as default};
|