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
data/lib/brut/front_end.rb
CHANGED
@@ -1,16 +1,7 @@
|
|
1
|
-
# In Brut, the _front end_ is considered anything that interacts directly
|
2
|
-
#
|
3
|
-
# and
|
4
|
-
#
|
5
|
-
# You {Brut::App} defines pages, forms, and actions. A page is backed by a subclass of {Brut::FrontEnd::Page}, which provides
|
6
|
-
# dynamic data for rendering. A page can reference {Brut::FrontEnd::Component} subclasses to allow functional decomposition of front
|
7
|
-
# end logic and markup, as well as re-use. Both pages and components have ERB files that describe the HTML to be rendered.
|
8
|
-
#
|
9
|
-
# A {Brut::FrontEnd::Form} subclass defines a form that a browser will submit to your app. That
|
10
|
-
# submission is processed by a {Brut::FrontEnd::Handler} subclass. Handlers can also respond to other HTTP requests.
|
11
|
-
#
|
12
|
-
# In addition to responding to requests, you can subclass {Brut::FrontEnd::RouteHook} or {Brut::FrontEnd::Middleware} to perform
|
13
|
-
# further manipulation of the request.
|
1
|
+
# In Brut, the _front end_ is considered anything that interacts directly
|
2
|
+
# with a web browser or HTTP. This includes rendering HTML, managing
|
3
|
+
# JavaScript and CSS, and processing form submissions. It contrasts to
|
4
|
+
# {Brut::BackEnd}, which handles the business logic and database.
|
14
5
|
#
|
15
6
|
# The entire front-end is based on Rack, so you should be able to achieve anything you need to.
|
16
7
|
module Brut::FrontEnd
|
@@ -6,39 +6,72 @@
|
|
6
6
|
# * {Brut::I18n::ForCLI} for CLI apps
|
7
7
|
# * {Brut::I18n::ForBackEnd} for back-end classes that aren't generating HTML
|
8
8
|
#
|
9
|
+
# This module assumes the existence of a three-method protocol that's used for HTML escaping in an
|
10
|
+
# HTML-generating web context:
|
11
|
+
#
|
12
|
+
# * `capture` accepts the block yieled to {#t} and returns whatever it generates. This needs to exist
|
13
|
+
# because Phlex's API renders to an internal buffer by default. This module needs to allow Phlex
|
14
|
+
# API methods to render to a different buffer.
|
15
|
+
# * `safe` accepts a string and returns a string that is presumed to be HTML safe.
|
16
|
+
# * `html_escape` accepts a string and returns a string that is HTML escaped.
|
17
|
+
#
|
18
|
+
# This module does not implement these methods and assumes that either the class using this module will
|
19
|
+
# implement them or that a submodule being used does. All submodules provided by Brut provide implementations,
|
20
|
+
# so this information is only relevant if you are using this module directly.
|
9
21
|
module Brut::I18n::BaseMethods
|
10
22
|
|
11
23
|
# Access a translation and insert interpolated elemens as needed. This will use the provided key to determine
|
12
|
-
# the actual full key to the translation, as described below.
|
13
|
-
#
|
14
|
-
# values *are* HTML escaped, so external input is safe to provide.
|
15
|
-
#
|
16
|
-
# This method also may take a block, and the results of the block are inserted into the `%{block}`
|
17
|
-
# interpolation value in the i18n string, if it's present.
|
24
|
+
# the actual full key to the translation, as described below. See {Brut::I18n::ForHTML#t} for details
|
25
|
+
# on how this works in the context of a {Brut::FrontEnd::Component} or {Brut::FrontEnd::Page}.
|
18
26
|
#
|
19
27
|
# Any missing interpolation will result in an exception, *except* for the value `field`. When
|
20
28
|
# a string has `%{field}` in it, but `field:` is omitted in this call, the value for
|
21
|
-
# `"
|
29
|
+
# `"cv.this_field"` is used. This value, in English, is "this field", so a call
|
22
30
|
# to `t("email.required")` would generate `"This field is required"`, while a call
|
23
31
|
# to `t("email.required", field: "E-mail address")` would generate `"E-mail address is required"`.
|
24
32
|
#
|
25
33
|
# @param [String,Symbol,Array<String>,Array<Symbol>] key used to create one or more keys to be translated.
|
26
|
-
# This value's behavior
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
34
|
+
# This value's behavior balances predictabilitiy with what key is used and some flexibilty
|
35
|
+
# to allow page– or component–specific translations when needed, and fallbacks when not.
|
36
|
+
#
|
37
|
+
# When an array if given, the values are turned into strings and joined with a "." to
|
38
|
+
# form a full key.
|
39
|
+
#
|
40
|
+
# Depending on what class this module is mixed into, additional keys will be tried:
|
41
|
+
#
|
42
|
+
# * If this is a page, `pages.«page_name».` will be prepended and tried before the key passed in.
|
43
|
+
# If the `pages.«page_name»` version is not found, the exact key passed in is checked.
|
44
|
+
# * If this is a page private component, `pages.«page_name».` will be prepended and tried before
|
45
|
+
# the key passed in. The value for `«page_name»` is determined by the outer class that this
|
46
|
+
# component is a part of.
|
47
|
+
# * If this is a component (include if it is a page private component),
|
48
|
+
# `components.«component_name».` will be prepended and tried before the key passed in.
|
49
|
+
# If the `components.«component_name»` version is not found, the exact key passed in is checked.
|
50
|
+
#
|
51
|
+
# The priority of the keys are as follows:
|
52
|
+
#
|
53
|
+
# 1. Component key is checked (unless this is a page)
|
54
|
+
# 2. Page key is checked (unless this is a non-page private component)
|
55
|
+
# 3. The literal key passed-in is checked
|
56
|
+
#
|
57
|
+
# If no value is found for any key an exception is raised.
|
58
|
+
#
|
59
|
+
# @param [Hash] interpolated_values values to use for interpolation of the key's translation. Note that if
|
60
|
+
# `:block` is part of this has, you may not pass a block to this method. Note also
|
61
|
+
# that `:count` can be used if the key is expected to be pluralized. This value
|
62
|
+
# is required for keys that are designed for pluralization. See examples below.
|
41
63
|
# @option interpolated_values [Numeric] count Special interpolation to control pluralization.
|
64
|
+
# @option interpolated_values [String] block Value to use for `%{block}`. If this is used, a block may not be
|
65
|
+
# yielded.
|
66
|
+
# @yield If a block is passed, it is used for the value of `%{block}`. No parameters are yielded to the block.
|
67
|
+
# @yieldreturn [String] The value to use for the `%{block}` interpolation value. There is some nuance to
|
68
|
+
# how this works. The value returned is given to `capture`, and *that* value
|
69
|
+
# is given to `safe`. Outside of an HTML-rendering context, these methods
|
70
|
+
# simply pass through the contents of the block. In an HTML-rendering
|
71
|
+
# context, however, these methods are assumed to be from
|
72
|
+
# [`Phlex::HTML`](https://phlex.fun). `capture` will create a new Phlex
|
73
|
+
# context and capture any HTML built inside the block. That HTML is assumed
|
74
|
+
# to be safe, thus `safe` is called to communicate this to Phlex.
|
42
75
|
#
|
43
76
|
# @raise [I18n::MissingTranslation] if no translation is found
|
44
77
|
# @raise [I18n::MissingInterpolationArgument] if interpolation arguments are missing, or if the key
|
@@ -47,25 +80,17 @@ module Brut::I18n::BaseMethods
|
|
47
80
|
# @example Simplest usage
|
48
81
|
# # in your translations file
|
49
82
|
# en: {
|
50
|
-
#
|
51
|
-
# hello: "Hi!"
|
52
|
-
# },
|
53
|
-
# formalized: {
|
54
|
-
# hello: "Greetings!"
|
55
|
-
# }
|
83
|
+
# hello: "Hi!"
|
56
84
|
# }
|
57
85
|
# # in your code
|
58
86
|
# t(:hello) # => Hi!
|
59
|
-
# t("formalized.hello") # => Greetings!
|
60
87
|
#
|
61
88
|
# @example Using an array for the key
|
62
89
|
# # in your translations file
|
63
90
|
# en: {
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
# }
|
68
|
-
# },
|
91
|
+
# actions: {
|
92
|
+
# edit: "Make an edit"
|
93
|
+
# }
|
69
94
|
# }
|
70
95
|
# # in your code
|
71
96
|
# t([:actions, :edit]) # => Make an edit
|
@@ -73,9 +98,7 @@ module Brut::I18n::BaseMethods
|
|
73
98
|
# @example Using page:
|
74
99
|
# # in your translations file
|
75
100
|
# en: {
|
76
|
-
#
|
77
|
-
# new_widget: "Make a New Widget",
|
78
|
-
# },
|
101
|
+
# new_widget: "Make a New Widget",
|
79
102
|
# pages: {
|
80
103
|
# HomePage: {
|
81
104
|
# new_widget: "Create new Widget"
|
@@ -86,62 +109,112 @@ module Brut::I18n::BaseMethods
|
|
86
109
|
# },
|
87
110
|
# }
|
88
111
|
# # in your code for HomePage
|
89
|
-
# t(
|
112
|
+
# t(:new_widget) # => Create new Widget
|
90
113
|
# # in your code for WidgetsPage
|
91
|
-
# t(
|
92
|
-
# # in your code for
|
93
|
-
# t(
|
114
|
+
# t(:new_widget) # => Create New
|
115
|
+
# # in your code for SomeOtherPage
|
116
|
+
# t(:new_widget) # => Make a New Widget
|
94
117
|
#
|
95
|
-
# @example Using
|
118
|
+
# @example Using in a component
|
96
119
|
# # in your translations file
|
97
120
|
# en: {
|
121
|
+
# status: {
|
122
|
+
# ready: "Available",
|
123
|
+
# stalled: "Stalled",
|
124
|
+
# completed: "Done",
|
125
|
+
# },
|
126
|
+
# components: {
|
127
|
+
# TagComponent: {
|
128
|
+
# status: {
|
129
|
+
# ready: "Ready",
|
130
|
+
# stalled: "Waiting",
|
131
|
+
# }
|
132
|
+
# },
|
133
|
+
# },
|
134
|
+
# }
|
135
|
+
# # in your code for TagComponent
|
136
|
+
# t(page: [ :status, :ready ]) # => Ready
|
137
|
+
# t(page: [ :status, :completed ]) # => Done
|
138
|
+
# # in your code for StatusComponent
|
139
|
+
# t(page: [ :status, :ready ]) # => Available
|
140
|
+
# t(page: [ :status, :completed ]) # => Done
|
141
|
+
#
|
142
|
+
# @example Using in a page-private component
|
143
|
+
# # in your translations file
|
144
|
+
# en: {
|
145
|
+
# status: {
|
146
|
+
# ready: "Available",
|
147
|
+
# stalled: "Stalled",
|
148
|
+
# completed: "Done",
|
149
|
+
# },
|
98
150
|
# pages: {
|
99
151
|
# WidgetsPage: {
|
100
|
-
# new_widget: "Create New"
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
152
|
+
# new_widget: "Create New",
|
153
|
+
# nevermind: "Don't Create One",
|
154
|
+
# },
|
155
|
+
# }
|
156
|
+
# components: {
|
157
|
+
# "WidgetsPage::WidgetComponent": {
|
158
|
+
# new_widget: "Make New Widget",
|
104
159
|
# },
|
105
160
|
# },
|
106
161
|
# }
|
107
|
-
# # in your code for
|
108
|
-
# t(page:
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
162
|
+
# # in your code for WidgetsPage::WidgetComponent
|
163
|
+
# t(page: :new_widget) # => Make New Widget
|
164
|
+
# t(page: :nevermind) # => Don't Create One
|
165
|
+
#
|
166
|
+
# @example Using a block in a page or component
|
167
|
+
# # in your translations file
|
168
|
+
# en: {
|
169
|
+
# greeting: "Hello there %{name}, you may %{block}",
|
170
|
+
# }
|
171
|
+
# # Inside a component where
|
172
|
+
# # Brut::I18n::ForHTML has been included
|
173
|
+
# def view_template
|
174
|
+
# h1 do
|
175
|
+
# raw(t(:greeting), name: user.name) do
|
176
|
+
# a(href: "https://support.example.com") do
|
177
|
+
# "contact support"
|
178
|
+
# end
|
179
|
+
# end
|
180
|
+
# end
|
181
|
+
# end
|
182
|
+
# # This will produce this HTML, assuming user.name is "Pat":
|
183
|
+
# <h1>
|
184
|
+
# Hell there Pat, you may
|
185
|
+
# <a href="https://support.example.com">
|
186
|
+
# contact support
|
187
|
+
# </a>
|
188
|
+
# </h1>
|
189
|
+
def t(key,**interpolated_values,&block)
|
190
|
+
keys_to_check = []
|
191
|
+
key = Array(key).join('.')
|
192
|
+
is_page_private_component = self.kind_of?(Brut::FrontEnd::Component) &&
|
193
|
+
self.page_private?
|
194
|
+
is_page = self.kind_of?(Brut::FrontEnd::Page)
|
195
|
+
is_component = self.kind_of?(Brut::FrontEnd::Component) && !is_page
|
118
196
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
else
|
127
|
-
raise ArgumentError, "If you omit an explicit key, you must specify page or component"
|
128
|
-
end
|
129
|
-
key << "general.#{subkey}"
|
130
|
-
else
|
131
|
-
key = Array(key).join('.')
|
132
|
-
key = [key,"general.#{key}"]
|
197
|
+
if is_component
|
198
|
+
keys_to_check << "components.#{self.component_name}.#{key}"
|
199
|
+
end
|
200
|
+
if is_page
|
201
|
+
keys_to_check << "pages.#{self.page_name}.#{key}"
|
202
|
+
elsif is_page_private_component
|
203
|
+
keys_to_check << "pages.#{self.containing_page_name}.#{key}"
|
133
204
|
end
|
205
|
+
keys_to_check << key
|
206
|
+
|
134
207
|
if !block.nil?
|
135
|
-
if
|
208
|
+
if interpolated_values[:block]
|
136
209
|
raise ArgumentError,"t was given a block and a block: param. You can't do both "
|
137
210
|
end
|
138
211
|
block_contents = safe(capture(&block))
|
139
|
-
|
212
|
+
interpolated_values[:block] = block_contents
|
140
213
|
end
|
141
|
-
t_direct(key
|
214
|
+
t_direct(keys_to_check,**interpolated_values.merge(key_given: key))
|
142
215
|
rescue I18n::MissingInterpolationArgument => ex
|
143
216
|
if ex.key.to_s == "block"
|
144
|
-
raise ArgumentError,"One of the keys #{key.join(", ")} contained a %{block} interpolation value: '#{ex.string}'. This means you must
|
217
|
+
raise ArgumentError,"One of the keys #{key.join(", ")} contained a %{block} interpolation value: '#{ex.string}'. This means you must yield a block to `t`"
|
145
218
|
else
|
146
219
|
raise
|
147
220
|
end
|
@@ -152,7 +225,7 @@ module Brut::I18n::BaseMethods
|
|
152
225
|
end
|
153
226
|
|
154
227
|
def this_field_value
|
155
|
-
@__this_field_value ||= ::I18n.t("
|
228
|
+
@__this_field_value ||= ::I18n.t("cv.this_field", raise: true)
|
156
229
|
end
|
157
230
|
|
158
231
|
# Directly access translations without trying to be smart about deriving the key. This is useful
|
@@ -160,28 +233,43 @@ module Brut::I18n::BaseMethods
|
|
160
233
|
#
|
161
234
|
# @param [Array<String>,Array<Symbol>] keys list of keys representing what is to be translated. The
|
162
235
|
# first key found will be used. If no key in the list is found
|
163
|
-
# will raise a I18n::MissingTranslation
|
236
|
+
# will raise a I18n::MissingTranslation.
|
164
237
|
# @param [Hash] interpolated_values value to use for interpolation of the key's translation
|
165
238
|
# @option interpolated_values [Numeric] count Special interpolation to control pluralization.
|
239
|
+
# @option interpolated_values [String|Symbol] key_given If included, this is not used for interpolation, but
|
240
|
+
# will be used in error messages to represent the key
|
241
|
+
# given to `t`.
|
166
242
|
#
|
167
243
|
# @raise [I18n::MissingTranslation] if no translation is found
|
168
244
|
# @raise [I18n::MissingInterpolationArgument] if interpolation arguments are missing, or if the key
|
169
245
|
# has pluralizations and no count: was given
|
170
246
|
def t_direct(keys,interpolated_values={})
|
171
247
|
keys = Array(keys).map(&:to_sym)
|
248
|
+
key_given = interpolated_values.delete(:key_given)
|
172
249
|
default_interpolated_values = {
|
173
250
|
field: this_field_value,
|
174
251
|
}
|
175
252
|
escaped_interpolated_values = interpolated_values.map { |key,value|
|
176
253
|
if value.kind_of?(String)
|
177
|
-
[ key,
|
254
|
+
[ key, html_escape(value) ]
|
178
255
|
else
|
179
256
|
[ key, value ]
|
180
257
|
end
|
181
258
|
}.to_h
|
182
259
|
result = ::I18n.t(keys.first, default: keys[1..-1],raise: true, **default_interpolated_values.merge(escaped_interpolated_values))
|
183
260
|
if result.kind_of?(Hash)
|
184
|
-
|
261
|
+
incorrect_pluralization = result.keys.none? { |key| key == :one }
|
262
|
+
if incorrect_pluralization
|
263
|
+
key_message = if key_given
|
264
|
+
"Key '#{key_given}'"
|
265
|
+
else
|
266
|
+
"One of the keys"
|
267
|
+
end
|
268
|
+
raise Brut::Framework::Errors::Bug,
|
269
|
+
"#{key_message} resulted in a Hash that doesn't appear to be created for pluralizations. This means that you may have given a key expecting it to map to a translation but it is actually a namespace for other keys. Please adjust your translations file to avoid this situation. Keys checked:\n#{keys.join(", ")}\nSub keys found:\n#{result.keys.join(', ')}"
|
270
|
+
else
|
271
|
+
raise I18n::MissingInterpolationArgument.new(:count,interpolated_values,keys.join(","))
|
272
|
+
end
|
185
273
|
end
|
186
274
|
result
|
187
275
|
end
|
@@ -1,5 +1,9 @@
|
|
1
|
+
# Use this to access translations in any back-end code.
|
2
|
+
# This implementation does support blocks yielded to {#t}, however
|
3
|
+
# their values are not necessarily HTML-escaped.
|
1
4
|
module Brut::I18n::ForBackEnd
|
2
5
|
include Brut::I18n::BaseMethods
|
3
6
|
def safe(string) = string
|
4
7
|
def capture(&block) = block.()
|
8
|
+
def html_escape(value) = value
|
5
9
|
end
|
data/lib/brut/i18n/for_cli.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
# Use this to access translations in any CLI.
|
2
|
+
# This implementation does support blocks yielded to {#t}, however
|
3
|
+
# their values are not necessarily HTML-escaped.
|
1
4
|
module Brut::I18n::ForCLI
|
2
5
|
include Brut::I18n::BaseMethods
|
3
6
|
def safe(string) = string
|
4
7
|
def capture(&block) = block.()
|
8
|
+
def html_escape(value) = value
|
5
9
|
end
|
data/lib/brut/i18n/for_html.rb
CHANGED
@@ -1,12 +1,40 @@
|
|
1
1
|
# I18n for components or pages, which are assumed to be Phlex components.
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# This will do HTML escaping as follows:
|
3
|
+
#
|
4
|
+
# * Interpolated values are always HTML-escaped
|
5
|
+
# * When a block is used, that value is assumed to be safe HTML,
|
6
|
+
# and generated outside the current Phlex context. It's value is
|
7
|
+
# captured (via `#capture`) and then declared HTML safe by being
|
8
|
+
# passed to `#safe`.
|
9
|
+
#
|
10
|
+
# Unless you put HTML injections into your translations file, this should result
|
11
|
+
# in safe HTML in any translation.
|
12
|
+
#
|
13
|
+
# This module provides two features that aren't part of {#Brut::I18n::BaseMethods}:
|
14
|
+
#
|
15
|
+
# * {#t} calls `#safe` on its return value, indicating that the string is safe. To use
|
16
|
+
# this string in a Phlex view, you *must* call `#raw` and pass it the string.
|
17
|
+
# * {#html_escape} is implemented to escape values it's given.
|
18
|
+
#
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# class StatusComponent < AppComponent
|
22
|
+
# def initialize(status:)
|
23
|
+
# @status = status
|
24
|
+
# end
|
25
|
+
# def view_template
|
26
|
+
# div do
|
27
|
+
# raw(
|
28
|
+
# t( [ :status, @status ] )
|
29
|
+
# )
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
# end
|
4
33
|
#
|
5
|
-
# * `safe` to accept a string and return a string.
|
6
|
-
# * `capture` to accept a block and return its contents as a string.
|
7
34
|
module Brut::I18n::ForHTML
|
8
35
|
include Brut::I18n::BaseMethods
|
9
36
|
def t(...)
|
10
37
|
safe(super)
|
11
38
|
end
|
39
|
+
def html_escape(value) = CGI.escapeHTML(value)
|
12
40
|
end
|
@@ -1,8 +1,19 @@
|
|
1
|
+
# Manages the value for the HTTP
|
2
|
+
# [Accept-Language](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Language)
|
3
|
+
# header. Generally, you would not interact with this class directly, however it is used
|
4
|
+
# by Brut to make a guess as to which Locale a browser is reporting.
|
1
5
|
class Brut::I18n::HTTPAcceptLanguage
|
6
|
+
# A locale with the weight (value for q=) it was given in the Accept-Language header
|
2
7
|
WeightedLocale = Data.define(:locale, :q) do
|
8
|
+
# Returns the primary locale for whatever locale
|
9
|
+
# this is holding. For example, the primary locale
|
10
|
+
# of "en-US" is "en".
|
3
11
|
def primary_locale = self.locale.gsub(/\-.*$/,"")
|
12
|
+
|
13
|
+
# True if this locale is a primary locale
|
4
14
|
def primary? = self.primary_locale == self.locale
|
5
15
|
|
16
|
+
# Return a new WeightedLocale that is the primary locale.
|
6
17
|
def primary_only
|
7
18
|
self.class.new(locale: self.primary_locale, q: self.q)
|
8
19
|
end
|
@@ -12,6 +23,11 @@ class Brut::I18n::HTTPAcceptLanguage
|
|
12
23
|
end
|
13
24
|
end
|
14
25
|
|
26
|
+
# Parse the value stored in the session.
|
27
|
+
#
|
28
|
+
# @param [String] session_value the value stored in the session.
|
29
|
+
# @return [Brut::I18n::HTTPAcceptLanguage] a usable object. If the provided value
|
30
|
+
# is blank, #{Brut::I18n::HTTPAcceptLanguage::AlwaysEnglish} is returned.
|
15
31
|
def self.from_session(session_value)
|
16
32
|
values = session_value.to_s.split(/,/).map { |value|
|
17
33
|
locale,q = value.split(/;/)
|
@@ -24,6 +40,19 @@ class Brut::I18n::HTTPAcceptLanguage
|
|
24
40
|
end
|
25
41
|
end
|
26
42
|
|
43
|
+
# Parse the value provided by the browser via
|
44
|
+
# {Brut::FrontEnd::Handlers::LocaleDetectionHandler} via
|
45
|
+
# the `brut-locale-detection` custom element (which
|
46
|
+
# uses `Intl.DateTimeFormat().resolvedOptions()` to determine
|
47
|
+
# the locale).
|
48
|
+
#
|
49
|
+
# Because this value is not in the same format as the Accept-Language
|
50
|
+
# header, it's `q` is assumed to be 1.
|
51
|
+
#
|
52
|
+
# @param [String] value the value provided by the brower.
|
53
|
+
#
|
54
|
+
# @return [Brut::I18n::HTTPAcceptLanguage] a usable object. If the provided value
|
55
|
+
# is blank, #{Brut::I18n::HTTPAcceptLanguage::AlwaysEnglish} is returned.
|
27
56
|
def self.from_browser(value)
|
28
57
|
value = value.to_s.strip
|
29
58
|
if value == ""
|
@@ -33,6 +62,10 @@ class Brut::I18n::HTTPAcceptLanguage
|
|
33
62
|
end
|
34
63
|
end
|
35
64
|
|
65
|
+
# Parse from the HTTP Accept-Language header.
|
66
|
+
#
|
67
|
+
# @return [Brut::I18n::HTTPAcceptLanguage] a usable object. If the provided value
|
68
|
+
# is blank, #{Brut::I18n::HTTPAcceptLanguage::AlwaysEnglish} is returned.
|
36
69
|
def self.from_header(header_value)
|
37
70
|
header_value = header_value.to_s.strip
|
38
71
|
if header_value == "*" || header_value == ""
|
@@ -50,14 +83,28 @@ class Brut::I18n::HTTPAcceptLanguage
|
|
50
83
|
end
|
51
84
|
end
|
52
85
|
|
86
|
+
# Ordered list of locales, from highest-weighted to lowest.
|
53
87
|
attr_reader :weighted_locales
|
88
|
+
# @param [Array<Brut::I18n::HTTPAcceptLanguage::WeightedLocale>] weighted_locales locales to use. They do not
|
89
|
+
# need to be ordered
|
54
90
|
def initialize(weighted_locales)
|
55
91
|
@weighted_locales = weighted_locales.sort_by(&:q).reverse
|
56
92
|
end
|
93
|
+
|
94
|
+
# True if the values inside this object represent known locales, and not a guess based on missing information.
|
95
|
+
# In general, this returns true if the values came from the Accept-Language header, or from the browser.
|
57
96
|
def known? = true
|
97
|
+
|
98
|
+
# Serialize for storage in the session
|
99
|
+
#
|
100
|
+
# @return [String] a string that can be stored in the session and later deserialized via {.from_session}.
|
58
101
|
def for_session = @weighted_locales.map { |weighted_locale| "#{weighted_locale.locale};#{weighted_locale.q}" }.join(",")
|
59
102
|
def to_s = self.for_session
|
60
103
|
|
104
|
+
# A subclass that represents the use of English and only English. This is
|
105
|
+
# used when attempts to determine the locale fail. Instances of this class
|
106
|
+
# are considered "unknown" ({#known?} returns false), which allows Brut
|
107
|
+
# to replace this with a known value later on.
|
61
108
|
class AlwaysEnglish < Brut::I18n::HTTPAcceptLanguage
|
62
109
|
def initialize
|
63
110
|
super([ WeightedLocale.new(locale: "en", q: 1) ])
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# Class to interact with the OpenTelemetry standard in a simpler way than
|
2
|
+
# the provided Ruby gem does. In general, you should use this class
|
3
|
+
# via `Brut.container.instrumentation`, and you should *not* use the
|
4
|
+
# OpenTelemetry ruby library directly. You probably wouldn't want to, anyway.
|
1
5
|
class Brut::Instrumentation::OpenTelemetry
|
2
6
|
# Create a span around the given block of code.
|
3
7
|
#
|
@@ -39,20 +43,51 @@ class Brut::Instrumentation::OpenTelemetry
|
|
39
43
|
timestamp:)
|
40
44
|
end
|
41
45
|
|
46
|
+
# Record an exception. In general, use this only if:
|
47
|
+
#
|
48
|
+
# * You need to have the parent span record this particular exception
|
49
|
+
# * You are not going to re-raise the exception.
|
50
|
+
#
|
51
|
+
# Otherwise, look at {#record_and_reraise_exception!}.
|
52
|
+
#
|
53
|
+
# @param [Exception] ex the exception to record.
|
54
|
+
# @param [Hash] attributes any attributes to attach that will show up in your OTel provider
|
42
55
|
def record_exception(ex,attributes=nil)
|
43
56
|
current_span = OpenTelemetry::Trace.current_span
|
44
57
|
current_span.record_exception(ex,attributes: NormalizedAttributes.new(nil,attributes).to_h)
|
45
58
|
end
|
46
59
|
|
60
|
+
# Record an exception and re-raise it. This is useful if you want
|
61
|
+
# the exception recorded as part of the parent span, but still plan
|
62
|
+
# to let it raise. Don't do this for every exception you intend to raise.
|
63
|
+
# @param [Exception] ex the exception to record.
|
64
|
+
# @param [Hash] attributes any attributes to attach that will show up in your OTel provider
|
65
|
+
# @raise [Exception] the exception passed in.
|
66
|
+
def record_and_reraise_exception!(ex,attributes=nil)
|
67
|
+
reecord_exception(ex,attributes)
|
68
|
+
raise ex
|
69
|
+
end
|
70
|
+
|
71
|
+
|
47
72
|
# Adds attributes to the span, converting the hash or keyword arguments to strings. This will use
|
48
73
|
# the app's Otel prefix for all attributes, so you do not have to prefix them.
|
49
|
-
# If you need to set standard attributes, you should use {
|
74
|
+
# If you need to set standard attributes, you should use {#add_prefixed_attributes} instead.
|
50
75
|
# @param [Hash] attributes any attributes to attach to the event.
|
51
76
|
def add_attributes(attributes)
|
52
77
|
current_span = OpenTelemetry::Trace.current_span
|
53
78
|
current_span.add_attributes(NormalizedAttributes.new(:detect,attributes).to_h)
|
54
79
|
end
|
55
80
|
|
81
|
+
# Adds attributes to the span, prefixing each key with the given prefix, then converting the hash or keyword arguments to strings. For example, if the prefix is 'my_app' and you add the attributes 'type' and 'reason', the actual attribute names will be 'my_app.type' and 'my_app.reason'.
|
82
|
+
#
|
83
|
+
# @see #add_attributes
|
84
|
+
def add_prefixed_attributes(prefix,attributes)
|
85
|
+
current_span = OpenTelemetry::Trace.current_span
|
86
|
+
current_span.add_attributes(
|
87
|
+
NormalizedAttributes.new(prefix,attributes).to_h
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
56
91
|
private
|
57
92
|
|
58
93
|
class NormalizedAttributes
|
data/lib/brut/instrumentation.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
+
# Namespace for instrumentation setup and support. Brut strives to provide useful
|
2
|
+
# instrumentation by default.
|
3
|
+
#
|
1
4
|
module Brut::Instrumentation
|
2
5
|
autoload(:OpenTelemetry,"brut/instrumentation/open_telemetry")
|
3
6
|
autoload(:LoggerSpanExporter,"brut/instrumentation/logger_span_exporter")
|
4
|
-
|
5
|
-
# Convenience method to add attributes to create a span without accessing the instrumentation instance directly.
|
6
|
-
def span(name,**attributes,&block)
|
7
|
-
Brut.container.instrumentation.span(name,**attributes,&block)
|
8
|
-
end
|
9
7
|
end
|
10
8
|
|
data/lib/brut/sinatra_helpers.rb
CHANGED
@@ -9,6 +9,7 @@ module Brut::SinatraHelpers
|
|
9
9
|
sinatra_app.path("/__brut/locale_detection",method: :post)
|
10
10
|
sinatra_app.path("/__brut/instrumentation",method: :get)
|
11
11
|
sinatra_app.set :host_authorization, permitted_hosts: Brut.container.permitted_hosts
|
12
|
+
sinatra_app.set :show_exceptions, false
|
12
13
|
end
|
13
14
|
|
14
15
|
# @private
|
@@ -178,16 +179,23 @@ module Brut::SinatraHelpers
|
|
178
179
|
form_class: form_class,
|
179
180
|
)
|
180
181
|
|
181
|
-
handler = handler_class.new
|
182
182
|
form = if form_class.nil?
|
183
183
|
nil
|
184
184
|
else
|
185
185
|
form_class.new(params: params)
|
186
186
|
end
|
187
187
|
|
188
|
-
|
188
|
+
constructor_args = Brut::FrontEnd::RequestContext.current.as_constructor_args(
|
189
|
+
handler_class,
|
190
|
+
request_params: params,
|
191
|
+
route: brut_route,
|
192
|
+
form: form,
|
193
|
+
)
|
194
|
+
|
195
|
+
handler = handler_class.new(**constructor_args)
|
189
196
|
|
190
|
-
result = handler.handle!
|
197
|
+
result = handler.handle!
|
198
|
+
span.add_prefixed_attributes("brut", result_class: result.class)
|
191
199
|
|
192
200
|
case result
|
193
201
|
in URI => uri
|