brut 0.0.21 → 0.0.23
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/brutrb.com/keyword-injection.md +237 -0
- 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/cli/app_runner.rb +1 -1
- data/lib/brut/cli/apps/test.rb +5 -0
- data/lib/brut/framework/mcp.rb +0 -4
- data/lib/brut/front_end/component.rb +59 -84
- 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} +6 -8
- 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/page.rb +4 -6
- data/lib/brut/front_end/request_context.rb +3 -2
- data/lib/brut/i18n/base_methods.rb +136 -82
- data/lib/brut/i18n/for_back_end.rb +1 -0
- data/lib/brut/i18n/for_cli.rb +1 -0
- data/lib/brut/i18n/for_html.rb +32 -4
- data/lib/brut/instrumentation/open_telemetry.rb +12 -2
- data/lib/brut/sinatra_helpers.rb +10 -3
- data/lib/brut/spec_support/component_support.rb +18 -18
- data/lib/brut/spec_support/e2e_test_server.rb +3 -0
- data/lib/brut/version.rb +1 -1
- data/lib/sequel/extensions/brut_migrations.rb +12 -9
- metadata +647 -5
- data/lib/brut/front_end/components/inputs/select.rb +0 -117
@@ -0,0 +1,66 @@
|
|
1
|
+
# Getting Started
|
2
|
+
|
3
|
+
To make a new app with Brut, you'll need to clone a template app, initialize it, then set up your dev environment.
|
4
|
+
|
5
|
+
## Clone the App Template
|
6
|
+
|
7
|
+
To get started, clone the Brut app template:
|
8
|
+
|
9
|
+
```
|
10
|
+
> git clone https://github.com/thirdtank/brut-app-template your-app-name
|
11
|
+
```
|
12
|
+
|
13
|
+
## Init Your App
|
14
|
+
|
15
|
+
The template includes `init`, which will ask you a few questions to get everything set up.
|
16
|
+
|
17
|
+
```
|
18
|
+
> cd your-app-name
|
19
|
+
> ./init
|
20
|
+
```
|
21
|
+
|
22
|
+
You'll need to provide four pieces of info:
|
23
|
+
|
24
|
+
* Your app's name, suitable has a hostname or identifier
|
25
|
+
* A prefix for your app's externalizable ids
|
26
|
+
* A prefix for your app's custom elements
|
27
|
+
* An organization name, needed for deployment
|
28
|
+
|
29
|
+
::: tip
|
30
|
+
Choose your app's name wisely, however everything else can be easily changed later, so don't stress!
|
31
|
+
:::
|
32
|
+
|
33
|
+
## Set Up Your Dev Environment
|
34
|
+
|
35
|
+
Brut includes a dev environment based on Docker.
|
36
|
+
|
37
|
+
1. [Install Docker](https://docs.docker.com/get-started/get-docker/)
|
38
|
+
2. Build Your images
|
39
|
+
|
40
|
+
```
|
41
|
+
> dx/build
|
42
|
+
```
|
43
|
+
3. Start up the environment
|
44
|
+
|
45
|
+
```
|
46
|
+
> dx/start
|
47
|
+
```
|
48
|
+
4. Install gems and modules for your app. In another terminal:
|
49
|
+
|
50
|
+
```
|
51
|
+
> dx/exec bin/setup
|
52
|
+
```
|
53
|
+
|
54
|
+
Now, you're ready to go
|
55
|
+
|
56
|
+
## Run the App
|
57
|
+
|
58
|
+
```
|
59
|
+
> dx/exec bin/dev
|
60
|
+
```
|
61
|
+
|
62
|
+
You can now visit your app at `localhost:6502`
|
63
|
+
|
64
|
+
## Now Build The Rest of Your App 🦉
|
65
|
+
|
66
|
+
You can [follow the tutorial](/tutorial), check out the [conceptual overview](/overview), or dive straight into the API docs.
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# Handlers & Actions
|
2
|
+
|
3
|
+
Handlers process form submissions, and *actions* work similarly to process any arbitrary HTTP request.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
Where a [page](/pages) renders a web page in HTML, a *handler* responds to all other HTTP requests. To respond to such HTTP requests, you'd first create a [route](/routes), using `form`, `action`, or `path`.
|
8
|
+
|
9
|
+
### Declaring Routes
|
10
|
+
|
11
|
+
`form` and `action` are intended to be used when an HTTP form is being submitted. The latter—`action`—is for when your form has no user-editable elements. This is akin to Rails' `button_to` helper, where the contents of the URL contains everything needed to service the request.
|
12
|
+
|
13
|
+
`path` is for arbitrary HTTP requests and methods, so to create a webhook that responds to a PUT, you'd use:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
# inside app/src/app.rb
|
17
|
+
path "/webhooks/stripe", method: :put
|
18
|
+
```
|
19
|
+
|
20
|
+
In all cases, the class to receive and process these requests is a handler, whose name is conventional based on the route. For example, the webhook above would be handled by `Webhooks::StripeHandler`.
|
21
|
+
|
22
|
+
### Implementing Handlers
|
23
|
+
|
24
|
+
A handler works like a page, in that its initializer can receive any injectible arguments. These would include a form object, dynamic elements of the route, or anything else available from the request. See [Keyword Injection](/keyword-injection) for the details, noting that your handler can be inejcted with custom objects you've configured in a route hook.
|
25
|
+
|
26
|
+
After the handler is created, it's `before_handle` method is called. If it returns `nil`, `handle` is called to trigger whatever logic the handler needs to trigger.
|
27
|
+
|
28
|
+
Both `handle` and `before_handle` can return a variety of objects that determine what will happen:
|
29
|
+
|
30
|
+
* An instance of a page or component means that page or component is rendered. This is not a redirect to the page, so it is more like Rails' `render :new` and **not** a `redirect_to(new_widgets_path)`.
|
31
|
+
* A `Brut::FrontEnd::HttpStatus` which sends that status and an empty body back. This object can be created from
|
32
|
+
an integer using the `http_status` helper, available to all handlers.
|
33
|
+
* A `URI`, which will cause a redirect to that URI. You can create the `URI` yourself, or use the helper
|
34
|
+
`redirect_to`, which accepts a string.
|
35
|
+
* A two-element array with a page or component as the first element and an `Brut::FrontEnd::HttpStatus` as the
|
36
|
+
second. This will render that page or component's HTML, but use the given status instead of 200. This can be useful for Ajax requests where you want to use HTML your respond format, but also, say, a 422 status to indicate a constraint violation has occured.
|
37
|
+
* A `Brut::FrontEnd::Download`, which encapsulates a file to be downloaded.
|
38
|
+
* A `Brut::FrontEnd::GenericResponse`, which wraps any Rack response with a defined type.
|
39
|
+
|
40
|
+
`before_handle` may also return `nil` to indicate that `handle` should be called. `handle` may not return `nil`.
|
41
|
+
|
42
|
+
Supposing our `LoginForm` and `LoginHandler` wanted to use a common pattern of re-rendering `LoginPage` on constraint violations, and forwarding on to, say, a `DashboardPage`. Your handler might look like so:
|
43
|
+
|
44
|
+
```ruby {23-27}
|
45
|
+
# app/src/front_end/handlers/login_handler.rb
|
46
|
+
class LoginHandler < AppHandler
|
47
|
+
def initialize(form:, session:) # We'll discuss the session later
|
48
|
+
@form = form
|
49
|
+
@session = session
|
50
|
+
end
|
51
|
+
|
52
|
+
def handle
|
53
|
+
if !@form.constraint_violations?
|
54
|
+
authorized_user = AuthorizedUser.login(
|
55
|
+
email: form.email,
|
56
|
+
password: form.password
|
57
|
+
)
|
58
|
+
if authorized_user.nil?
|
59
|
+
@form.server_side_constraint_violation(
|
60
|
+
input_name: :email,
|
61
|
+
key: :login_not_found
|
62
|
+
)
|
63
|
+
else
|
64
|
+
session.authorized_user = authorized_user
|
65
|
+
end
|
66
|
+
end
|
67
|
+
if @form.constraint_violations?
|
68
|
+
LoginPage.new(form: @form)
|
69
|
+
else
|
70
|
+
redirect_to(DashboardPage.routing)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
> [!IMPORTANT]
|
77
|
+
> The only way to render something other than HTML is to do so as a
|
78
|
+
> `GenericResponse`, which is basically the low-level Rack API. Brut
|
79
|
+
> encourages Ajax responses to be HTML and for you to use the browser's
|
80
|
+
> APIs to interact with that HTML. Brut may make it easier to work
|
81
|
+
> with other types of content in the future.
|
82
|
+
|
83
|
+
## Testing
|
84
|
+
|
85
|
+
Testing handlers requires calling their *public API*, which is `handle!`. This is not the method you implement (which is the non-bang `handle`). The reason is that `handle!` manages the logic around calling `before_handle`, which allows your tests to always call `handle!` and know they are testing how the handler would be used in produciton.
|
86
|
+
|
87
|
+
Each handler spec includes `Brut::SpecSupport::HandlerSupport`, which allows you to create production-like flash, clock, and session objects. To assert the results of calling `handle!`, there are several RSpec matchers you can use to make your tests easier to write.
|
88
|
+
|
89
|
+
* `have_redirected_to` will check that the handler redirected to a give URI.
|
90
|
+
* `have_rendered` will check that the handler rendered a specific page
|
91
|
+
* `have_returned_http_status` will check that the handler returned an HTTP status
|
92
|
+
* `have_constraint_violation` will check if a form had a particular constraint violation set on it
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
require "spec_helper"
|
96
|
+
|
97
|
+
RSpec.describe LoginPage do
|
98
|
+
describe "#handle!" do
|
99
|
+
context "when login is not valid" do
|
100
|
+
it "re-renders LoginPage" do
|
101
|
+
form = LoginForm.new(params: {
|
102
|
+
email: "nonexistent@example.com",
|
103
|
+
password: "not a password",
|
104
|
+
})
|
105
|
+
result = described_class.new(
|
106
|
+
form:,
|
107
|
+
session: empty_session # empty_session provided by HandlerSupport
|
108
|
+
)
|
109
|
+
expect(result).to have_rendered(LoginPage)
|
110
|
+
expect(form).to have_constraint_violation(:email, key: :login_not_found)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
context "when login is valid" do
|
114
|
+
it "forward to the DashboardPage" do
|
115
|
+
user = create(:user, # Assume this is set up via FactoryBot
|
116
|
+
email: "pat@example.com",
|
117
|
+
password: "1q2w3e4r5t6y7u8i9o")
|
118
|
+
|
119
|
+
form = LoginForm.new(params: {
|
120
|
+
email: "pat@example.com",
|
121
|
+
password: "1q2w3e4r5t6y7u8i9o",
|
122
|
+
})
|
123
|
+
session = empty_session
|
124
|
+
result = described_class.new(
|
125
|
+
form:,
|
126
|
+
session:,
|
127
|
+
)
|
128
|
+
expect(result).to have_redirected_to(DashboardPage.routing)
|
129
|
+
expect(session.authorized_user).not_to eq(nil)
|
130
|
+
# Session will be explained later
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
## Recommended Practices
|
138
|
+
|
139
|
+
You should avoid having business logic in your handlers. Since handlers bridge the gap between HTTP and your app, their API is naturally simplistic and String-based. The handler should defer to business logic (which can be done by either passing the form object directly, or extracting its data and passing that). Based on the response, the handler will then decide what HTTP response is approriate.
|
140
|
+
|
141
|
+
This means that your handlers will be relatively simple and their tests will as well. It does mean that their tests may require the use of mocks or stubs, but that's fine. Mocks and stubs exist for a reason.
|
142
|
+
|
143
|
+
|
144
|
+
## Technical Notes
|
145
|
+
|
146
|
+
> [!IMPORTANT]
|
147
|
+
> Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
|
148
|
+
> internals, the source code is always more correct.
|
149
|
+
|
150
|
+
_Last Updated May 5, 2025_
|
151
|
+
|
152
|
+
None at this time.
|
153
|
+
|
data/brutrb.com/hooks.md
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
# Route Hooks
|
2
|
+
|
3
|
+
Route hooks are similar to [Middleware](/middleware), but have a richer API and aren't as low-level. Route
|
4
|
+
hooks can happen before a page or handler is called, or after.
|
5
|
+
|
6
|
+
## Overview
|
7
|
+
|
8
|
+
We've seen examples thusfar of using a route hook to place the authenticated user or account into the
|
9
|
+
request context for later injection into pages or handlers. Brut uses route hooks to locale detection and
|
10
|
+
for content security policies.
|
11
|
+
|
12
|
+
At its core, a before hook is a class that extends `Brut::FrontEnd::RouteHook` and implements `before`
|
13
|
+
and an after hook implements `after`. Both `before` and `after` can be [injected](/keyword-injection) with request-time information.
|
14
|
+
|
15
|
+
To register a hook, you'd call `before` or `after` in your `App`:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
class App < Brut::Framework::App
|
19
|
+
|
20
|
+
# ...
|
21
|
+
|
22
|
+
before :RequireAuthBeforeHook
|
23
|
+
|
24
|
+
# ...
|
25
|
+
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
The value can be a string or symbol, but should not be the class itself, as this can mess with load order.
|
30
|
+
|
31
|
+
The code for `RequireAuthBeforeHook` we saw before was marked with a red warning that it wasn't production ready. Let's
|
32
|
+
see what a more realistic hook would look like.
|
33
|
+
|
34
|
+
The purpose of `RequireAuthBeforeHook` is to detect if a user is logged in. If they are, all is well. If they are not,
|
35
|
+
we want to redirect them to a login page unless the page they've requested is allowed for logged-out users.
|
36
|
+
|
37
|
+
Let's suppose that the home page (`/`) and any page starting with `/auth/` are allowed for logged-out users (`/auth/` being pages related to logging in).
|
38
|
+
|
39
|
+
Brut also reserves some routes for its own, and we want those to be allowed to logged-out users as well. Brut sets
|
40
|
+
`"brut.owned_path"` in the Rack environment if the requested URL is one it is managing.
|
41
|
+
|
42
|
+
`before` will need access to the request context, session, Rack request, and Rack environment:
|
43
|
+
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
# app/src/front_end/route_hooks/require_auth_before_hook.rb
|
47
|
+
class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
|
48
|
+
def before(request_context:,session:,request:,env:)
|
49
|
+
# ...
|
50
|
+
end
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
We'll use the Rack request's `path_info` to check for allowed routes, and the aforementioned `env["brut.owned_path"]` to
|
55
|
+
check for a Brut-owned path:
|
56
|
+
|
57
|
+
```ruby {4-6}
|
58
|
+
# app/src/front_end/route_hooks/require_auth_before_hook.rb
|
59
|
+
class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
|
60
|
+
def before(request_context:,session:,request:,env:)
|
61
|
+
is_home_page = request.path_info.match(/^\/?$/)
|
62
|
+
is_auth_route = request.path_info.match?(/^\/auth\//)
|
63
|
+
is_brut_owned_path = env["brut.owned_path"]
|
64
|
+
|
65
|
+
# ...
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
Now, we can use this local variables to figure out if the route requires a user to be logged-in:
|
72
|
+
|
73
|
+
```ruby {8-10}
|
74
|
+
# app/src/front_end/route_hooks/require_auth_before_hook.rb
|
75
|
+
class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
|
76
|
+
def before(request_context:,session:,request:,env:)
|
77
|
+
is_home_page = request.path_info.match(/^\/?$/)
|
78
|
+
is_auth_route = request.path_info.match?(/^\/auth\//)
|
79
|
+
is_brut_owned_path = env["brut.owned_path"]
|
80
|
+
|
81
|
+
requires_login = !is_home_page &&
|
82
|
+
!is_auth_route &&
|
83
|
+
!is_brut_owned_path
|
84
|
+
|
85
|
+
# ...
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
Now, we'll check if someone *is* logged in. If they are, we'll set the `authenticated_account` in the request context.
|
92
|
+
|
93
|
+
```ruby {12-15}
|
94
|
+
# app/src/front_end/route_hooks/require_auth_before_hook.rb
|
95
|
+
class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
|
96
|
+
def before(request_context:,session:,request:,env:)
|
97
|
+
is_home_page = request.path_info.match(/^\/?$/)
|
98
|
+
is_auth_route = request.path_info.match?(/^\/auth\//)
|
99
|
+
is_brut_owned_path = env["brut.owned_path"]
|
100
|
+
|
101
|
+
requires_login = !is_home_page &&
|
102
|
+
!is_auth_route &&
|
103
|
+
!is_brut_owned_path
|
104
|
+
|
105
|
+
if session.logged_in?
|
106
|
+
request_context[:authenticated_account] = session.authenticated_account
|
107
|
+
requires_login = false
|
108
|
+
end
|
109
|
+
|
110
|
+
# ...
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
Now, we can test if the visitor needs to log in before proceeding. The return value of `before` controls
|
117
|
+
what will happen, similar to how handlers work.
|
118
|
+
|
119
|
+
|
120
|
+
* `URI` - the browser will be redirected to this URI. This can be done by using the `redirect_to` helper.
|
121
|
+
* `Brut::FrontEnd::HttpStatus` - the request will be terminated with this status. This can be done using the `http_status` helper.
|
122
|
+
* `false` - the request is terminated with a 500
|
123
|
+
* `true` or `nil` - the request will continue to the next hook or to the route handler. You are encouraged to use the `continue` helper to more clearly indicate that the request will proceed.
|
124
|
+
|
125
|
+
In our case, if the visitor requires a login, we'll `redirect_to` the `LoginPage`. Otherwise, we'll
|
126
|
+
`continue`.
|
127
|
+
|
128
|
+
```ruby {17-21}
|
129
|
+
# app/src/front_end/route_hooks/require_auth_before_hook.rb
|
130
|
+
class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
|
131
|
+
def before(request_context:,session:,request:,env:)
|
132
|
+
is_home_page = request.path_info.match(/^\/?$/)
|
133
|
+
is_auth_route = request.path_info.match?(/^\/auth\//)
|
134
|
+
is_brut_owned_path = env["brut.owned_path"]
|
135
|
+
|
136
|
+
requires_login = !is_home_page &&
|
137
|
+
!is_auth_route &&
|
138
|
+
!is_brut_owned_path
|
139
|
+
|
140
|
+
if session.logged_in?
|
141
|
+
request_context[:authenticated_account] = session.authenticated_account
|
142
|
+
requires_login = false
|
143
|
+
end
|
144
|
+
|
145
|
+
if requires_login
|
146
|
+
redirect_to(Auth::LoginPage)
|
147
|
+
else
|
148
|
+
continue
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
## Testing
|
156
|
+
|
157
|
+
Route hooks are normal classes, you could test them as you would a handler or other class. This may be
|
158
|
+
advisable for complex hooks, however it may be more realistic to test their behavior through end-to-end
|
159
|
+
tests as this will ensure they are configured correctly in the context of the app.
|
160
|
+
|
161
|
+
## Recommended Practices
|
162
|
+
|
163
|
+
Route hooks and [page hooks](/pages#hooks) serve similar purposes, so logic in one can be placed in other
|
164
|
+
other at your discretion. We recommend you use route hooks for cross-cutting issues across the entire
|
165
|
+
app, such as login checks or for adding context to a request.
|
166
|
+
|
167
|
+
For page- or use-case-specific behavior, it may be better to put the logic in a page hook.
|
168
|
+
|
169
|
+
## Technical Notes
|
170
|
+
|
171
|
+
> [!IMPORTANT]
|
172
|
+
> Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
|
173
|
+
> internals, the source code is always more correct.
|
174
|
+
|
175
|
+
_Last Updated June 12, 2025_
|
176
|
+
|
177
|
+
Route hooks and Middlewares do not share implementations, however they are similar in concept. These
|
178
|
+
concepts may be unified in the future.
|
data/brutrb.com/i18n.md
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
# Internationaliztion and Localization
|
2
|
+
|
3
|
+
Brut uses Ruby's i18n gem to provide support for localization and internationalization.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
`Brut::I18n::BaseMethods` provides the core implementation of Brut's i18n support, and it largely wraps the `t`
|
8
|
+
and `l` methods of the i18n gem.
|
9
|
+
|
10
|
+
|
11
|
+
Consider this:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
t("my.key", foo: "Bar")
|
15
|
+
```
|
16
|
+
|
17
|
+
This will locate the string with the key `my.key` and return it, replacing `%{foo}` with `"Bar"`, if `%{foo}` is
|
18
|
+
present in the string.
|
19
|
+
|
20
|
+
The keys are located in files in `app/config/i18n`. The directories there correspond to the locales your app
|
21
|
+
supports, e.g .`app/config/i18n/en` would hold translations for English.
|
22
|
+
|
23
|
+
The translation files themselves are 🎉**NOT YAML**🎊. They are Ruby files. By default, there are two files: `app/config/i18n/en/1_defaults.rb` and `app/config/i18n/en/2_app.rb` (noting that `/en/` is for English and other langauges are obviously supported).
|
24
|
+
|
25
|
+
`1_defaults.rb` provides values for keys Brut may require or use, such as for front-end constraint violations.
|
26
|
+
`2_app.rb` provides your app's keys. If this file contains the same keys as `1_defaults.rb`, your file's values
|
27
|
+
will be used.
|
28
|
+
|
29
|
+
The file is a giant Hash, so the key above might look like so:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
{
|
33
|
+
my: {
|
34
|
+
key: "Hello there %{foo}",
|
35
|
+
},
|
36
|
+
}
|
37
|
+
```
|
38
|
+
|
39
|
+
### Enhancements
|
40
|
+
|
41
|
+
Brut's `t` tries to balance predictability with flexibility. It will always try to tell you what keys it was
|
42
|
+
checking when it cannot find a translation or what interpolated values were missing.
|
43
|
+
|
44
|
+
#### Basic Usage
|
45
|
+
|
46
|
+
Often, keys contain dynamic elements. Rather that creating a key like `widget.status.#{widget.status}`, you can
|
47
|
+
pass an array in and it'll be joined for you:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
t([ :widget, :status, widget.status ]) #=> widget.status.active e.g.
|
51
|
+
```
|
52
|
+
|
53
|
+
`t` can also take a block that will be evaluated and substituted into the `block` interpolation value:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
{
|
57
|
+
supportlink: "To contact support %{block}',
|
58
|
+
}
|
59
|
+
|
60
|
+
t(:supportlink) do
|
61
|
+
"<a href='https://support.example.com'>Contact Support</a>"
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
See below for how this affects HTML generation.
|
66
|
+
|
67
|
+
#### Page- and Component-specific Values
|
68
|
+
|
69
|
+
If you call `t` inside `page_template`, or inside `view_template` of a [page private
|
70
|
+
component](/components#page-private-components), you can simplify the key specification.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
t(:nevermind)
|
74
|
+
```
|
75
|
+
|
76
|
+
If this is used on, say, `NewWidgetPage`, Brut will try to locate the key `pages.NewWidgetPage.nevermind`. This works on page private components as well.
|
77
|
+
|
78
|
+
Inside a normal component, it works simliarly. Suppose `FlashComponent` had this:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
if !flash
|
82
|
+
t(:default_message)
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
This would locate `components.FlashComponent.default_message`.
|
87
|
+
|
88
|
+
This saves some typing, but it can also assist refactoring. If you rename a page or component, calls to `t` will blow up, reminding you to move the translations inside `2_app.rb`.
|
89
|
+
|
90
|
+
### HTML Escaping
|
91
|
+
|
92
|
+
`Brut::I18n::BaseMethods` cannot be used directly. One of three submodules must be used: `ForHTML`, `ForCLI`, or
|
93
|
+
`ForBackend`. This is to allow the `ForHTML` module to properly escape HTML.
|
94
|
+
|
95
|
+
When a translation accepts a block, that block could be HTML and you would want that HTML be included in the
|
96
|
+
page, un-escaped. Brut achieves this by first using Phlex's `capture` method, then marking it as HTML safe using
|
97
|
+
`safe`.
|
98
|
+
|
99
|
+
Thus, as long as you aren't introducing injections in your translations file or source code, it is safe to do the following:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
def view_template
|
103
|
+
div do
|
104
|
+
raw(
|
105
|
+
t(page: :contact_support) do
|
106
|
+
a(href: "https://support.example.com") do
|
107
|
+
t(page: :support_link_name)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
The result of `t` is safe HTML, so you must use `raw` to avoid escaping it.
|
116
|
+
|
117
|
+
In a CLI or back-end context, HTML escaping is not relevant and can actually create problems, so `ForCLI` and
|
118
|
+
`ForBackend` no-op `safe` and `capture`.
|
119
|
+
|
120
|
+
When using `ForHTML`, all interpolated values are HTML-escaped. `ForCLI` and `ForBackend` are not.
|
121
|
+
|
122
|
+
### Localizing Dates and Times
|
123
|
+
|
124
|
+
`l` can be called and this defers to the Ruby I18n library.
|
125
|
+
|
126
|
+
Date and time formats can be configured in the translation files. `l` does not accept a full key for the format.
|
127
|
+
It is created dynamically by the library, so you must take care in which one you use. If you pass a `Date` into
|
128
|
+
`l`, `date.formats.«format»` is used. If you pass a `Time` in, `time.formats.«format»` is used.
|
129
|
+
|
130
|
+
The values of the formats are strings suitable for
|
131
|
+
[`strftime`](https://www.man7.org/linux/man-pages/man3/strftime.3.html). The site [strif.me](https://www.strfti.me/) can be helpful in conjuring the right value.
|
132
|
+
|
133
|
+
Brut includes translations for various formats that you can inspect in `app/config/i18n/«lang»/1_defaults.rb`.
|
134
|
+
|
135
|
+
### Displaying Dates and Times in HTML
|
136
|
+
|
137
|
+
While `l` will return a string you can use anywhere, you are most likely going to show dates and times in HTML.
|
138
|
+
For that, you should use a `<time>` element. Brut provides `Brut::FrontEnd::Components::TimeTag` (remember that if you `include Brut::FrontEnd::Components`, it's a Phlex *kit* and thus you can use `TimeTag(...)` directly) to do this. It contains additional behavior to make friendly dates and times.
|
139
|
+
|
140
|
+
* You can give it a `timestamp:` or `date:` to control which formatting style is used.
|
141
|
+
* `skip_year_if_same`, if true, will omit the year from any format if the current year is the same as the year being displayed. This is true by default
|
142
|
+
* `skip_dow_if_not_this_week`, if true, will omit the day of week if the date or time is more than 7 days in the past. This is true by default.
|
143
|
+
|
144
|
+
The way `skip_year_if_same` and `skip_dow_if_not_this_week` work is to append `no_year` and/or `no_dow` to
|
145
|
+
existing format strings which are assumed to omit this elements.
|
146
|
+
|
147
|
+
If you wish to create your own formats, you can add them as well.
|
148
|
+
|
149
|
+
### Constraint Violations and Field Names
|
150
|
+
|
151
|
+
The interpolated value `{field}` is special. It is assumed to be the name of a field in a constraint violation
|
152
|
+
message, e.g. `"%{field} is required"`. It is the only interpolated value that can be omitted without causing an
|
153
|
+
error.
|
154
|
+
|
155
|
+
If included, it will work as normal:
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
t("cv.be.required", field: "Email") # => Email is required
|
159
|
+
```
|
160
|
+
|
161
|
+
If omitted, the value of `"cv.this_field"` is used. This is included in `1_default.rb`, but if it's
|
162
|
+
missing, Brut will raise. Assuming the value is `"This field"`:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
t("cv.be.required") # => This field is required
|
166
|
+
```
|
167
|
+
|
168
|
+
## Testing
|
169
|
+
|
170
|
+
In tests, you can call `t` and `l` to examine values as needed.
|
171
|
+
|
172
|
+
> [!WARNING]
|
173
|
+
> This aspect of Brut could use improvement. It likely will not work for non-English locales.
|
174
|
+
|
175
|
+
## Recommended Practices
|
176
|
+
|
177
|
+
Could use help here - The implementation feels like a bare minium
|
178
|
+
|
179
|
+
|
180
|
+
## Technical Notes
|
181
|
+
|
182
|
+
> [!IMPORTANT]
|
183
|
+
> Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
|
184
|
+
> internals, the source code is always more correct.
|
185
|
+
|
186
|
+
_Last Updated May 7, 2025_
|
187
|
+
|
188
|
+
TBD
|
@@ -0,0 +1,54 @@
|
|
1
|
+
digraph G {
|
2
|
+
rankdir="LR"
|
3
|
+
Node[fontname=Baskerville]
|
4
|
+
subgraph cluster_docker {
|
5
|
+
label="Docker Container"
|
6
|
+
labeljust=left
|
7
|
+
fontname=Avenir
|
8
|
+
Ruby
|
9
|
+
NodeJS
|
10
|
+
OS
|
11
|
+
Filesystem
|
12
|
+
BrutApp
|
13
|
+
}
|
14
|
+
|
15
|
+
subgraph cluster_host {
|
16
|
+
label="Your Computer"
|
17
|
+
labeljust=left
|
18
|
+
fontname=Avenir
|
19
|
+
ProjectFiles
|
20
|
+
Editor
|
21
|
+
VersionControl
|
22
|
+
Browser
|
23
|
+
}
|
24
|
+
|
25
|
+
subgraph cluster_postgres {
|
26
|
+
label="Postgres\l«Docker Container»"
|
27
|
+
labeljust=left
|
28
|
+
fontname=Avenir
|
29
|
+
Postgres
|
30
|
+
}
|
31
|
+
Filesystem[shape=tab label="File system"]
|
32
|
+
ProjectFiles[shape=tab label="Project Files"]
|
33
|
+
OS[shape=box]
|
34
|
+
NodeJS[shape=component]
|
35
|
+
Ruby[shape=component]
|
36
|
+
Editor[shape=Msquare]
|
37
|
+
VersionControl[shape=Msquare label="Version\nControl"]
|
38
|
+
Postgres[shape=cylinder]
|
39
|
+
BrutApp[shape=box3d]
|
40
|
+
Browser[shape="Msquare" label="Web\nBrowser"]
|
41
|
+
|
42
|
+
BrutApp -> Ruby
|
43
|
+
BrutApp -> NodeJS
|
44
|
+
BrutApp -> Filesystem
|
45
|
+
BrutApp -> Postgres
|
46
|
+
|
47
|
+
Filesystem -> ProjectFiles[ dir=both label="synced"]
|
48
|
+
Editor -> ProjectFiles
|
49
|
+
Ruby -> Filesystem
|
50
|
+
NodeJS -> Filesystem
|
51
|
+
OS -> Filesystem
|
52
|
+
VersionControl -> ProjectFiles
|
53
|
+
Browser -> BrutApp
|
54
|
+
}
|
Binary file
|
@@ -0,0 +1,37 @@
|
|
1
|
+
digraph G {
|
2
|
+
|
3
|
+
rankdir="LR"
|
4
|
+
nodesep=0.55
|
5
|
+
compound=true
|
6
|
+
|
7
|
+
node[shape=box fontname=avenir]
|
8
|
+
|
9
|
+
Shutdown[label=<
|
10
|
+
<FONT face="avenir">Shutdown</FONT>
|
11
|
+
<br/>
|
12
|
+
<FONT face="Courier New">dx/stop</FONT>
|
13
|
+
>]
|
14
|
+
Build[label=<
|
15
|
+
<FONT face="avenir">Build</FONT>
|
16
|
+
<br/>
|
17
|
+
<FONT face="Courier New">dx/build</FONT>
|
18
|
+
>]
|
19
|
+
Start[label=<
|
20
|
+
<FONT face="avenir">Start</FONT>
|
21
|
+
<br/>
|
22
|
+
<FONT face="Courier New">dx/start</FONT>
|
23
|
+
>]
|
24
|
+
Exec[label=<
|
25
|
+
<FONT face="avenir">Execute</FONT>
|
26
|
+
<br/>
|
27
|
+
<FONT face="Courier New">dx/exec</FONT>
|
28
|
+
>]
|
29
|
+
DevCommands[label=<
|
30
|
+
<FONT face="avenir">Workspace</FONT>
|
31
|
+
<br/>
|
32
|
+
<FONT face="Courier New">bin/\*</FONT>
|
33
|
+
>]
|
34
|
+
Build -> Start -> Exec -> Shutdown
|
35
|
+
Exec -> DevCommands[style=dotted dir=none]
|
36
|
+
{ rank=same; Exec; DevCommands }
|
37
|
+
}
|
Binary file
|
Binary file
|