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.
Files changed (728) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +24 -3
  3. data/.nvim.lua +1 -0
  4. data/Dockerfile.dx +12 -3
  5. data/Gemfile.lock +9 -7
  6. data/README.md +0 -7
  7. data/Rakefile +6 -4
  8. data/bin/dev +20 -0
  9. data/bin/docs +27 -0
  10. data/bin/setup +47 -1
  11. data/brut-css/.nvim.lua +1 -0
  12. data/brut-css/README.md +28 -0
  13. data/brut-css/bin/build +31 -0
  14. data/brut-css/bin/dev +1 -0
  15. data/brut-css/bin/docs +15 -0
  16. data/brut-css/bin/setup +5 -0
  17. data/brut-css/config/media-queries-all.css +15 -0
  18. data/brut-css/config/media-queries-minimal.css +5 -0
  19. data/brut-css/config/postcss.config.cjs +7 -0
  20. data/brut-css/config/pseudo-classes-all.css +9 -0
  21. data/brut-css/dx +1 -0
  22. data/brut-css/package-lock.json +3217 -0
  23. data/brut-css/package.json +36 -0
  24. data/brut-css/src/css/appearance.css +145 -0
  25. data/brut-css/src/css/border.css +522 -0
  26. data/brut-css/src/css/colors.css +3502 -0
  27. data/brut-css/src/css/dimensions.css +548 -0
  28. data/brut-css/src/css/flex.css +179 -0
  29. data/brut-css/src/css/index.css +13 -0
  30. data/brut-css/src/css/layout.css +120 -0
  31. data/brut-css/src/css/list.css +41 -0
  32. data/brut-css/src/css/positioning.css +354 -0
  33. data/brut-css/src/css/properties/colors.css +455 -0
  34. data/brut-css/src/css/properties/index.css +3 -0
  35. data/brut-css/src/css/properties/spacing.css +140 -0
  36. data/brut-css/src/css/properties/typography.css +224 -0
  37. data/brut-css/src/css/reset.css +107 -0
  38. data/brut-css/src/css/spacing.css +585 -0
  39. data/brut-css/src/css/typography.css +519 -0
  40. data/brut-css/src/css/utils.css +104 -0
  41. data/brut-css/src/docs/1_getting-started/1_overview.md +46 -0
  42. data/brut-css/src/docs/1_getting-started/2_installation.md +25 -0
  43. data/brut-css/src/docs/1_getting-started/3_core-concepts.md +75 -0
  44. data/brut-css/src/docs/1_getting-started/4_simple-example.md +132 -0
  45. data/brut-css/src/docs/1_getting-started/page.html.ejs +10 -0
  46. data/brut-css/src/docs/2_properties/page.html.ejs +71 -0
  47. data/brut-css/src/docs/3_classes/color-demo.html.ejs +31 -0
  48. data/brut-css/src/docs/3_classes/page.html.ejs +87 -0
  49. data/brut-css/src/docs/4_customization/1_design-system.md +36 -0
  50. data/brut-css/src/docs/4_customization/2_breakpoints.md +75 -0
  51. data/brut-css/src/docs/4_customization/3_pseudo-classes.md +74 -0
  52. data/brut-css/src/docs/4_customization/4_advanced-configuration.md +40 -0
  53. data/brut-css/src/docs/4_customization/page.html.ejs +10 -0
  54. data/brut-css/src/docs/docs.css +98 -0
  55. data/brut-css/src/docs/includes/body-and-header.html.ejs +30 -0
  56. data/brut-css/src/docs/includes/footer-and-rest.html.ejs +9 -0
  57. data/brut-css/src/docs/includes/head.html.ejs +5 -0
  58. data/brut-css/src/docs/includes/nav.html.ejs +10 -0
  59. data/brut-css/src/docs/index.html.ejs +32 -0
  60. data/brut-css/src/docs/prism-twilight.min.css +1 -0
  61. data/brut-css/src/js/Logger.js +71 -0
  62. data/brut-css/src/js/build.js +111 -0
  63. data/brut-css/src/js/cli/CLIArgError.js +7 -0
  64. data/brut-css/src/js/cli/Debug.js +27 -0
  65. data/brut-css/src/js/cli/DocsDir.js +16 -0
  66. data/brut-css/src/js/cli/DocsTemplateSourceDir.js +16 -0
  67. data/brut-css/src/js/cli/InputFile.js +31 -0
  68. data/brut-css/src/js/cli/MediaQueryConfigFile.js +10 -0
  69. data/brut-css/src/js/cli/OutputFile.js +22 -0
  70. data/brut-css/src/js/cli/ParsedArg.js +17 -0
  71. data/brut-css/src/js/cli/PathToBrutCSSRoot.js +19 -0
  72. data/brut-css/src/js/cli/PseudoClassConfigFile.js +11 -0
  73. data/brut-css/src/js/cli.js +108 -0
  74. data/brut-css/src/js/docGenerator.js +467 -0
  75. data/brut-css/src/js/mediaQueryConfigParser.js +98 -0
  76. data/brut-css/src/js/post-css-plugins/addMediaQueriesPlugin.js +49 -0
  77. data/brut-css/src/js/post-css-plugins/addPseudoClassesPlugin.js +42 -0
  78. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Category.js +9 -0
  79. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/DocState.js +185 -0
  80. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Documentable.js +8 -0
  81. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Group.js +7 -0
  82. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/ParsedComment.js +73 -0
  83. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Property.js +9 -0
  84. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyCategory.js +4 -0
  85. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyGroup.js +8 -0
  86. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Rule.js +12 -0
  87. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleCategory.js +4 -0
  88. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleGroup.js +8 -0
  89. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeRef.js +5 -0
  90. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeURL.js +9 -0
  91. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin.js +49 -0
  92. data/brut-css/src/js/post-css-plugins/generateRootCustomPropertiesPlugin.js +45 -0
  93. data/brut-css/src/js/pseudoClassConfigParser.js +145 -0
  94. data/brut-js/.projections.json +10 -0
  95. data/brut-js/README.md +118 -0
  96. data/brut-js/bin/build +10 -0
  97. data/brut-js/bin/ci +5 -0
  98. data/brut-js/bin/setup +5 -0
  99. data/brut-js/docs/README.md +8 -0
  100. data/brut-js/docs/jsdoc-plugins/customElementTag.js +8 -0
  101. data/brut-js/docs/jsdoc-theme/publish.js +692 -0
  102. data/brut-js/docs/jsdoc-theme/static/scripts/linenumber.js +25 -0
  103. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/Apache-License-2.0.txt +202 -0
  104. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/lang-css.js +2 -0
  105. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/prettify.js +28 -0
  106. data/brut-js/docs/jsdoc-theme/static/styles/jsdoc-default.css +327 -0
  107. data/brut-js/docs/jsdoc-theme/static/styles/prettify-jsdoc.css +111 -0
  108. data/brut-js/docs/jsdoc-theme/static/styles/prettify-tomorrow.css +132 -0
  109. data/brut-js/docs/jsdoc-theme/tmpl/augments.tmpl +10 -0
  110. data/brut-js/docs/jsdoc-theme/tmpl/container.tmpl +199 -0
  111. data/brut-js/docs/jsdoc-theme/tmpl/details.tmpl +143 -0
  112. data/brut-js/docs/jsdoc-theme/tmpl/example.tmpl +2 -0
  113. data/brut-js/docs/jsdoc-theme/tmpl/examples.tmpl +13 -0
  114. data/brut-js/docs/jsdoc-theme/tmpl/exceptions.tmpl +32 -0
  115. data/brut-js/docs/jsdoc-theme/tmpl/layout.tmpl +38 -0
  116. data/brut-js/docs/jsdoc-theme/tmpl/mainpage.tmpl +14 -0
  117. data/brut-js/docs/jsdoc-theme/tmpl/members.tmpl +38 -0
  118. data/brut-js/docs/jsdoc-theme/tmpl/method.tmpl +131 -0
  119. data/brut-js/docs/jsdoc-theme/tmpl/modifies.tmpl +14 -0
  120. data/brut-js/docs/jsdoc-theme/tmpl/params.tmpl +131 -0
  121. data/brut-js/docs/jsdoc-theme/tmpl/properties.tmpl +108 -0
  122. data/brut-js/docs/jsdoc-theme/tmpl/returns.tmpl +19 -0
  123. data/brut-js/docs/jsdoc-theme/tmpl/source.tmpl +8 -0
  124. data/brut-js/docs/jsdoc-theme/tmpl/tutorial.tmpl +19 -0
  125. data/brut-js/docs/jsdoc-theme/tmpl/type.tmpl +7 -0
  126. data/brut-js/docs/jsdoc.config.json +23 -0
  127. data/brut-js/docs/package-lock.json +343 -0
  128. data/brut-js/docs/package.json +7 -0
  129. data/brut-js/package-lock.json +2171 -0
  130. data/brut-js/package.json +32 -0
  131. data/brut-js/specs/AjaxSubmit.spec.js +256 -0
  132. data/brut-js/specs/Autosubmit.spec.js +127 -0
  133. data/brut-js/specs/ConfirmSubmit.spec.js +193 -0
  134. data/brut-js/specs/ConstraintViolationMessage.spec.js +33 -0
  135. data/brut-js/specs/ConstraintViolationMessages.spec.js +29 -0
  136. data/brut-js/specs/CopyToClipboard.spec.js +35 -0
  137. data/brut-js/specs/Form.spec.js +181 -0
  138. data/brut-js/specs/I18nTranslation.spec.js +19 -0
  139. data/brut-js/specs/LocaleDetection.spec.js +22 -0
  140. data/brut-js/specs/Message.spec.js +15 -0
  141. data/brut-js/specs/SpecHelper.js +23 -0
  142. data/brut-js/specs/Tabs.spec.js +41 -0
  143. data/brut-js/specs/config/asset_metadata.json +7 -0
  144. data/brut-js/src/AjaxSubmit.js +384 -0
  145. data/brut-js/src/Autosubmit.js +63 -0
  146. data/brut-js/src/BaseCustomElement.js +261 -0
  147. data/brut-js/src/ConfirmSubmit.js +116 -0
  148. data/brut-js/src/ConfirmationDialog.js +143 -0
  149. data/brut-js/src/ConstraintViolationMessage.js +125 -0
  150. data/brut-js/src/ConstraintViolationMessages.js +98 -0
  151. data/brut-js/src/CopyToClipboard.js +96 -0
  152. data/brut-js/src/Form.js +151 -0
  153. data/brut-js/src/I18nTranslation.js +61 -0
  154. data/brut-js/src/LocaleDetection.js +117 -0
  155. data/brut-js/src/Logger.js +90 -0
  156. data/brut-js/src/Message.js +56 -0
  157. data/brut-js/src/RichString.js +113 -0
  158. data/brut-js/src/Tabs.js +168 -0
  159. data/brut-js/src/Tracing.js +247 -0
  160. data/brut-js/src/appForTestingOnly.js +15 -0
  161. data/brut-js/src/index.js +130 -0
  162. data/brut-js/src/testing/AssetMetadata.js +35 -0
  163. data/brut-js/src/testing/AssetMetadataLoader.js +25 -0
  164. data/brut-js/src/testing/CustomElementTest.js +235 -0
  165. data/brut-js/src/testing/DOMCreator.js +45 -0
  166. data/brut-js/src/testing/index.js +48 -0
  167. data/brutrb.com/.vitepress/config.mjs +106 -0
  168. data/brutrb.com/.vitepress/plugins/jsdocLinker.js +34 -0
  169. data/brutrb.com/.vitepress/plugins/rdocLinker.js +18 -0
  170. data/brutrb.com/.vitepress/theme/custom.css +7 -0
  171. data/brutrb.com/.vitepress/theme/index.js +18 -0
  172. data/brutrb.com/.vitepress/theme/style.css +149 -0
  173. data/brutrb.com/ai.md +68 -0
  174. data/brutrb.com/assets.md +138 -0
  175. data/brutrb.com/bin/build +5 -0
  176. data/brutrb.com/bin/deploy +7 -0
  177. data/brutrb.com/bin/dev +5 -0
  178. data/brutrb.com/bin/setup +5 -0
  179. data/brutrb.com/brut-js.md +117 -0
  180. data/brutrb.com/business-logic.md +55 -0
  181. data/brutrb.com/cli.md +278 -0
  182. data/brutrb.com/components.md +243 -0
  183. data/brutrb.com/configuration.md +257 -0
  184. data/brutrb.com/css.md +103 -0
  185. data/brutrb.com/custom-element-tests.md +149 -0
  186. data/brutrb.com/database-access.md +201 -0
  187. data/brutrb.com/database-schema.md +312 -0
  188. data/brutrb.com/deployment.md +66 -0
  189. data/brutrb.com/dev-environment.md +179 -0
  190. data/brutrb.com/doc-conventions.md +39 -0
  191. data/brutrb.com/end-to-end-tests.md +174 -0
  192. data/brutrb.com/flash-and-session.md +224 -0
  193. data/brutrb.com/forms.md +866 -0
  194. data/brutrb.com/getting-started.md +66 -0
  195. data/brutrb.com/handlers.md +153 -0
  196. data/brutrb.com/hooks.md +178 -0
  197. data/brutrb.com/i18n.md +188 -0
  198. data/brutrb.com/images/Makefile +10 -0
  199. data/brutrb.com/images/dev-env-overview.dot +54 -0
  200. data/brutrb.com/images/dev-env-overview.png +0 -0
  201. data/brutrb.com/images/dev-env-protocol.dot +37 -0
  202. data/brutrb.com/images/dev-env-protocol.png +0 -0
  203. data/brutrb.com/images/logo-300.png +0 -0
  204. data/brutrb.com/images/logo.png +0 -0
  205. data/brutrb.com/images/overview.graffle +0 -0
  206. data/brutrb.com/images/overview.png +0 -0
  207. data/brutrb.com/images/spa.dot +19 -0
  208. data/brutrb.com/images/spa.png +0 -0
  209. data/brutrb.com/images/workspace-protocol.dot +44 -0
  210. data/brutrb.com/images/workspace-protocol.png +0 -0
  211. data/brutrb.com/index.md +36 -0
  212. data/brutrb.com/instrumentation.md +183 -0
  213. data/brutrb.com/javascript.md +122 -0
  214. data/brutrb.com/jobs.md +14 -0
  215. data/{doc-src → brutrb.com}/keyword-injection.md +122 -68
  216. data/brutrb.com/markdown-examples.md +85 -0
  217. data/brutrb.com/middleware.md +80 -0
  218. data/brutrb.com/not-released.md +5 -0
  219. data/brutrb.com/overview.md +404 -0
  220. data/brutrb.com/package-lock.json +2404 -0
  221. data/brutrb.com/package.json +11 -0
  222. data/brutrb.com/pages.md +378 -0
  223. data/brutrb.com/public/images/logo-300.png +0 -0
  224. data/brutrb.com/public/images/logo.png +0 -0
  225. data/brutrb.com/routes.md +215 -0
  226. data/brutrb.com/security.md +105 -0
  227. data/brutrb.com/seed-data.md +63 -0
  228. data/brutrb.com/space-time-continuum.md +85 -0
  229. data/brutrb.com/tutorial.md +3 -0
  230. data/brutrb.com/unit-tests.md +148 -0
  231. data/docker-compose.dx.yml +6 -3
  232. data/docs/404.html +21 -0
  233. data/docs/CNAME +1 -0
  234. data/docs/ai.html +24 -0
  235. data/docs/api/Brut/BackEnd/SeedData.html +493 -0
  236. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +214 -0
  237. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +125 -0
  238. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +125 -0
  239. data/docs/api/Brut/BackEnd/Sidekiq.html +125 -0
  240. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +414 -0
  241. data/docs/api/Brut/BackEnd/Validators.html +128 -0
  242. data/docs/api/Brut/BackEnd.html +132 -0
  243. data/docs/api/Brut/CLI/App.html +1576 -0
  244. data/docs/api/Brut/CLI/AppRunner.html +491 -0
  245. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +264 -0
  246. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +306 -0
  247. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +262 -0
  248. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +314 -0
  249. data/docs/api/Brut/CLI/Apps/BuildAssets.html +183 -0
  250. data/docs/api/Brut/CLI/Apps/DB/Create.html +365 -0
  251. data/docs/api/Brut/CLI/Apps/DB/Drop.html +357 -0
  252. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +383 -0
  253. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +335 -0
  254. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +329 -0
  255. data/docs/api/Brut/CLI/Apps/DB/Seed.html +347 -0
  256. data/docs/api/Brut/CLI/Apps/DB/Status.html +383 -0
  257. data/docs/api/Brut/CLI/Apps/DB.html +183 -0
  258. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +303 -0
  259. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +512 -0
  260. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +398 -0
  261. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +374 -0
  262. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +410 -0
  263. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +262 -0
  264. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +303 -0
  265. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +480 -0
  266. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +450 -0
  267. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +380 -0
  268. data/docs/api/Brut/CLI/Apps/Scaffold.html +253 -0
  269. data/docs/api/Brut/CLI/Apps/Test/Audit.html +464 -0
  270. data/docs/api/Brut/CLI/Apps/Test/E2e.html +407 -0
  271. data/docs/api/Brut/CLI/Apps/Test/JS.html +262 -0
  272. data/docs/api/Brut/CLI/Apps/Test/Run.html +578 -0
  273. data/docs/api/Brut/CLI/Apps/Test.html +253 -0
  274. data/docs/api/Brut/CLI/Apps.html +125 -0
  275. data/docs/api/Brut/CLI/Command.html +2342 -0
  276. data/docs/api/Brut/CLI/Error.html +139 -0
  277. data/docs/api/Brut/CLI/ExecutionResults/Result.html +664 -0
  278. data/docs/api/Brut/CLI/ExecutionResults.html +675 -0
  279. data/docs/api/Brut/CLI/Executor.html +430 -0
  280. data/docs/api/Brut/CLI/InvalidOption.html +245 -0
  281. data/docs/api/Brut/CLI/Options.html +753 -0
  282. data/docs/api/Brut/CLI/Output.html +699 -0
  283. data/docs/api/Brut/CLI/SystemExecError.html +451 -0
  284. data/docs/api/Brut/CLI.html +263 -0
  285. data/docs/api/Brut/FactoryBot.html +225 -0
  286. data/docs/api/Brut/Framework/App.html +1097 -0
  287. data/docs/api/Brut/Framework/Config.html +1045 -0
  288. data/docs/api/Brut/Framework/Container.html +1379 -0
  289. data/docs/api/Brut/Framework/Error.html +140 -0
  290. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +144 -0
  291. data/docs/api/Brut/Framework/Errors/Bug.html +234 -0
  292. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +257 -0
  293. data/docs/api/Brut/Framework/Errors/MissingParameter.html +273 -0
  294. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +471 -0
  295. data/docs/api/Brut/Framework/Errors/NotFound.html +308 -0
  296. data/docs/api/Brut/Framework/Errors/NotImplemented.html +234 -0
  297. data/docs/api/Brut/Framework/Errors.html +328 -0
  298. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +392 -0
  299. data/docs/api/Brut/Framework/MCP.html +861 -0
  300. data/docs/api/Brut/Framework/ProjectEnvironment.html +648 -0
  301. data/docs/api/Brut/Framework.html +129 -0
  302. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +317 -0
  303. data/docs/api/Brut/FrontEnd/Component/Helpers.html +326 -0
  304. data/docs/api/Brut/FrontEnd/Component.html +365 -0
  305. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +470 -0
  306. data/docs/api/Brut/FrontEnd/Components/FormTag.html +518 -0
  307. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +317 -0
  308. data/docs/api/Brut/FrontEnd/Components/Input.html +195 -0
  309. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +339 -0
  310. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +660 -0
  311. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +417 -0
  312. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +918 -0
  313. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +651 -0
  314. data/docs/api/Brut/FrontEnd/Components/Inputs.html +125 -0
  315. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +367 -0
  316. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +336 -0
  317. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +655 -0
  318. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +352 -0
  319. data/docs/api/Brut/FrontEnd/Components.html +135 -0
  320. data/docs/api/Brut/FrontEnd/Download.html +467 -0
  321. data/docs/api/Brut/FrontEnd/Flash.html +1150 -0
  322. data/docs/api/Brut/FrontEnd/Form.html +1157 -0
  323. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +634 -0
  324. data/docs/api/Brut/FrontEnd/Forms/Input.html +615 -0
  325. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +547 -0
  326. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1318 -0
  327. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +609 -0
  328. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +587 -0
  329. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +613 -0
  330. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +582 -0
  331. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +609 -0
  332. data/docs/api/Brut/FrontEnd/Forms.html +127 -0
  333. data/docs/api/Brut/FrontEnd/GenericResponse.html +377 -0
  334. data/docs/api/Brut/FrontEnd/Handler.html +442 -0
  335. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +318 -0
  336. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +336 -0
  337. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +399 -0
  338. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +354 -0
  339. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +151 -0
  340. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +315 -0
  341. data/docs/api/Brut/FrontEnd/Handlers.html +125 -0
  342. data/docs/api/Brut/FrontEnd/HandlingResults.html +339 -0
  343. data/docs/api/Brut/FrontEnd/HttpMethod.html +661 -0
  344. data/docs/api/Brut/FrontEnd/HttpStatus.html +496 -0
  345. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +284 -0
  346. data/docs/api/Brut/FrontEnd/Layout.html +318 -0
  347. data/docs/api/Brut/FrontEnd/Middleware.html +135 -0
  348. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +288 -0
  349. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +292 -0
  350. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +324 -0
  351. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +372 -0
  352. data/docs/api/Brut/FrontEnd/Middlewares.html +125 -0
  353. data/docs/api/Brut/FrontEnd/Page.html +773 -0
  354. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +797 -0
  355. data/docs/api/Brut/FrontEnd/Pages.html +125 -0
  356. data/docs/api/Brut/FrontEnd/RequestContext.html +1312 -0
  357. data/docs/api/Brut/FrontEnd/RouteHook.html +424 -0
  358. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +242 -0
  359. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +249 -0
  360. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +264 -0
  361. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +261 -0
  362. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +284 -0
  363. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +252 -0
  364. data/docs/api/Brut/FrontEnd/RouteHooks.html +115 -0
  365. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +227 -0
  366. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +305 -0
  367. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +324 -0
  368. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +319 -0
  369. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +315 -0
  370. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +315 -0
  371. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +327 -0
  372. data/docs/api/Brut/FrontEnd/Routing/Route.html +761 -0
  373. data/docs/api/Brut/FrontEnd/Routing.html +927 -0
  374. data/docs/api/Brut/FrontEnd/Session.html +1195 -0
  375. data/docs/api/Brut/FrontEnd.html +134 -0
  376. data/docs/api/Brut/I18n/BaseMethods.html +931 -0
  377. data/docs/api/Brut/I18n/ForBackEnd.html +302 -0
  378. data/docs/api/Brut/I18n/ForCLI.html +302 -0
  379. data/docs/api/Brut/I18n/ForHTML.html +296 -0
  380. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +316 -0
  381. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +930 -0
  382. data/docs/api/Brut/I18n.html +127 -0
  383. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +435 -0
  384. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +286 -0
  385. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +302 -0
  386. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +864 -0
  387. data/docs/api/Brut/Instrumentation.html +126 -0
  388. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +532 -0
  389. data/docs/api/Brut/SinatraHelpers.html +281 -0
  390. data/docs/api/Brut/SpecSupport/ClockSupport.html +383 -0
  391. data/docs/api/Brut/SpecSupport/ComponentSupport.html +502 -0
  392. data/docs/api/Brut/SpecSupport/E2ETestServer.html +503 -0
  393. data/docs/api/Brut/SpecSupport/E2eSupport.html +142 -0
  394. data/docs/api/Brut/SpecSupport/EnhancedNode.html +403 -0
  395. data/docs/api/Brut/SpecSupport/FlashSupport.html +278 -0
  396. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +401 -0
  397. data/docs/api/Brut/SpecSupport/GeneralSupport.html +195 -0
  398. data/docs/api/Brut/SpecSupport/HandlerSupport.html +160 -0
  399. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +553 -0
  400. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +439 -0
  401. data/docs/api/Brut/SpecSupport/Matchers.html +125 -0
  402. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +335 -0
  403. data/docs/api/Brut/SpecSupport/RSpecSetup.html +602 -0
  404. data/docs/api/Brut/SpecSupport/SessionSupport.html +196 -0
  405. data/docs/api/Brut/SpecSupport.html +129 -0
  406. data/docs/api/Brut.html +225 -0
  407. data/docs/api/Clock.html +603 -0
  408. data/docs/api/RichString.html +968 -0
  409. data/docs/api/SemanticLogger/Appender/Async.html +219 -0
  410. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +115 -0
  411. data/docs/api/Sequel/Extensions/BrutMigrations.html +533 -0
  412. data/docs/api/Sequel/Extensions.html +117 -0
  413. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +105 -0
  414. data/docs/api/Sequel/Plugins/CreatedAt.html +125 -0
  415. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +207 -0
  416. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +186 -0
  417. data/docs/api/Sequel/Plugins/ExternalId.html +218 -0
  418. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +202 -0
  419. data/docs/api/Sequel/Plugins/FindBang.html +125 -0
  420. data/docs/api/Sequel/Plugins.html +117 -0
  421. data/docs/api/Sequel.html +117 -0
  422. data/docs/api/_index.html +1553 -0
  423. data/docs/api/class_list.html +54 -0
  424. data/docs/api/css/common.css +1 -0
  425. data/docs/api/css/full_list.css +58 -0
  426. data/docs/api/css/style.css +503 -0
  427. data/docs/api/file.README.html +127 -0
  428. data/docs/api/file_list.html +59 -0
  429. data/docs/api/frames.html +22 -0
  430. data/docs/api/index.html +127 -0
  431. data/docs/api/js/app.js +344 -0
  432. data/docs/api/js/full_list.js +242 -0
  433. data/docs/api/js/jquery.js +4 -0
  434. data/docs/api/method_list.html +3998 -0
  435. data/docs/api/top-level-namespace.html +112 -0
  436. data/docs/assets/ai.md.tZrjP9im.js +1 -0
  437. data/docs/assets/ai.md.tZrjP9im.lean.js +1 -0
  438. data/docs/assets/app.D_yaTITQ.js +1 -0
  439. data/docs/assets/assets.md.D3wunzLx.js +19 -0
  440. data/docs/assets/assets.md.D3wunzLx.lean.js +1 -0
  441. data/docs/assets/brut-js.md.o2DAO2s2.js +12 -0
  442. data/docs/assets/brut-js.md.o2DAO2s2.lean.js +1 -0
  443. data/docs/assets/business-logic.md.BY4hGy0m.js +1 -0
  444. data/docs/assets/business-logic.md.BY4hGy0m.lean.js +1 -0
  445. data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +1 -0
  446. data/docs/assets/chunks/VPLocalSearchBox.B2-ZzyTY.js +8 -0
  447. data/docs/assets/chunks/framework.1L-BeKqY.js +18 -0
  448. data/docs/assets/chunks/theme.CfGFVRvE.js +2 -0
  449. data/docs/assets/cli.md.RmeA2b0i.js +127 -0
  450. data/docs/assets/cli.md.RmeA2b0i.lean.js +1 -0
  451. data/docs/assets/components.md.eCttGlN-.js +104 -0
  452. data/docs/assets/components.md.eCttGlN-.lean.js +1 -0
  453. data/docs/assets/configuration.md.BRriU0cL.js +78 -0
  454. data/docs/assets/configuration.md.BRriU0cL.lean.js +1 -0
  455. data/docs/assets/css.md.DJgj2clw.js +21 -0
  456. data/docs/assets/css.md.DJgj2clw.lean.js +1 -0
  457. data/docs/assets/custom-element-tests.md.BrYJQEl3.js +69 -0
  458. data/docs/assets/custom-element-tests.md.BrYJQEl3.lean.js +1 -0
  459. data/docs/assets/database-access.md.C7l-Vuvb.js +63 -0
  460. data/docs/assets/database-access.md.C7l-Vuvb.lean.js +1 -0
  461. data/docs/assets/database-schema.md.BUjR0VS1.js +63 -0
  462. data/docs/assets/database-schema.md.BUjR0VS1.lean.js +1 -0
  463. data/docs/assets/deployment.md.Dbka4OTr.js +1 -0
  464. data/docs/assets/deployment.md.Dbka4OTr.lean.js +1 -0
  465. data/docs/assets/dev-env-overview.Gj7NWM8-.png +0 -0
  466. data/docs/assets/dev-env-protocol.DysDAtnz.png +0 -0
  467. data/docs/assets/dev-environment.md.BNc8AYiK.js +11 -0
  468. data/docs/assets/dev-environment.md.BNc8AYiK.lean.js +1 -0
  469. data/docs/assets/doc-conventions.md.DCfRXXi-.js +1 -0
  470. data/docs/assets/doc-conventions.md.DCfRXXi-.lean.js +1 -0
  471. data/docs/assets/end-to-end-tests.md.yfQHC0b5.js +26 -0
  472. data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +1 -0
  473. data/docs/assets/flash-and-session.md.BXY8RvT0.js +93 -0
  474. data/docs/assets/flash-and-session.md.BXY8RvT0.lean.js +1 -0
  475. data/docs/assets/forms.md.CBTYQ_Cz.js +379 -0
  476. data/docs/assets/forms.md.CBTYQ_Cz.lean.js +1 -0
  477. data/docs/assets/getting-started.md.Bz2s1Vjb.js +2 -0
  478. data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +1 -0
  479. data/docs/assets/handlers.md.089DVD3v.js +69 -0
  480. data/docs/assets/handlers.md.089DVD3v.lean.js +1 -0
  481. data/docs/assets/hooks.md.C4-moMny.js +80 -0
  482. data/docs/assets/hooks.md.C4-moMny.lean.js +1 -0
  483. data/docs/assets/i18n.md.Do9i1qWl.js +23 -0
  484. data/docs/assets/i18n.md.Do9i1qWl.lean.js +1 -0
  485. data/docs/assets/index.md.B28EwVpq.js +1 -0
  486. data/docs/assets/index.md.B28EwVpq.lean.js +1 -0
  487. data/docs/assets/instrumentation.md.CL6ax7nT.js +35 -0
  488. data/docs/assets/instrumentation.md.CL6ax7nT.lean.js +1 -0
  489. data/docs/assets/javascript.md.GWbhRS51.js +31 -0
  490. data/docs/assets/javascript.md.GWbhRS51.lean.js +1 -0
  491. data/docs/assets/jobs.md.S-2amAYp.js +1 -0
  492. data/docs/assets/jobs.md.S-2amAYp.lean.js +1 -0
  493. data/docs/assets/keyword-injection.md.Dt2tKREs.js +25 -0
  494. data/docs/assets/keyword-injection.md.Dt2tKREs.lean.js +1 -0
  495. data/docs/assets/markdown-examples.md.CCFEQO44.js +33 -0
  496. data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +1 -0
  497. data/docs/assets/middleware.md.Czz_UlJN.js +20 -0
  498. data/docs/assets/middleware.md.Czz_UlJN.lean.js +1 -0
  499. data/docs/assets/not-released.md.BBy28McC.js +1 -0
  500. data/docs/assets/not-released.md.BBy28McC.lean.js +1 -0
  501. data/docs/assets/overview.Da81cB9R.png +0 -0
  502. data/docs/assets/overview.md.CDalkuxV.js +133 -0
  503. data/docs/assets/overview.md.CDalkuxV.lean.js +1 -0
  504. data/docs/assets/pages.md.BE3kfOc5.js +122 -0
  505. data/docs/assets/pages.md.BE3kfOc5.lean.js +1 -0
  506. data/docs/assets/routes.md.BMM7peut.js +29 -0
  507. data/docs/assets/routes.md.BMM7peut.lean.js +1 -0
  508. data/docs/assets/security.md.C668yXCi.js +1 -0
  509. data/docs/assets/security.md.C668yXCi.lean.js +1 -0
  510. data/docs/assets/seed-data.md.BvFZlqIk.js +14 -0
  511. data/docs/assets/seed-data.md.BvFZlqIk.lean.js +1 -0
  512. data/docs/assets/spa.qejUdp-5.png +0 -0
  513. data/docs/assets/space-time-continuum.md.KPUIKysQ.js +1 -0
  514. data/docs/assets/space-time-continuum.md.KPUIKysQ.lean.js +1 -0
  515. data/docs/assets/style.D73IYGCX.css +1 -0
  516. data/docs/assets/tutorial.md.BnoGjrdK.js +1 -0
  517. data/docs/assets/tutorial.md.BnoGjrdK.lean.js +1 -0
  518. data/docs/assets/unit-tests.md.DUGrnLj5.js +13 -0
  519. data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +1 -0
  520. data/docs/assets/workspace-protocol.C0gXsoDb.png +0 -0
  521. data/docs/assets.html +42 -0
  522. data/docs/brut-css/brut.css +1 -0
  523. data/docs/brut-css/brut.max.css +22372 -0
  524. data/docs/brut-css/classes/appearances.html +783 -0
  525. data/docs/brut-css/classes/background-colors.html +3529 -0
  526. data/docs/brut-css/classes/border-colors.html +3529 -0
  527. data/docs/brut-css/classes/borders.html +2293 -0
  528. data/docs/brut-css/classes/dimensions.html +2581 -0
  529. data/docs/brut-css/classes/flex.html +917 -0
  530. data/docs/brut-css/classes/foreground-colors.html +3261 -0
  531. data/docs/brut-css/classes/junk-drawer.html +431 -0
  532. data/docs/brut-css/classes/layout.html +668 -0
  533. data/docs/brut-css/classes/lists.html +331 -0
  534. data/docs/brut-css/classes/positioning.html +1751 -0
  535. data/docs/brut-css/classes/spacings.html +2633 -0
  536. data/docs/brut-css/classes/typography.html +2206 -0
  537. data/docs/brut-css/customization/advanced-configuration.html +204 -0
  538. data/docs/brut-css/customization/breakpoints.html +227 -0
  539. data/docs/brut-css/customization/design-system.html +197 -0
  540. data/docs/brut-css/customization/pseudo-classes.html +228 -0
  541. data/docs/brut-css/docs.css +98 -0
  542. data/docs/brut-css/getting-started/core-concepts.html +234 -0
  543. data/docs/brut-css/getting-started/installation.html +190 -0
  544. data/docs/brut-css/getting-started/overview.html +210 -0
  545. data/docs/brut-css/getting-started/simple-example.html +285 -0
  546. data/docs/brut-css/index.html +193 -0
  547. data/docs/brut-css/prism-twilight.min.css +1 -0
  548. data/docs/brut-css/properties/colors.html +1548 -0
  549. data/docs/brut-css/properties/spacings.html +614 -0
  550. data/docs/brut-css/properties/typography.html +777 -0
  551. data/docs/brut-js/api/AjaxSubmit.html +374 -0
  552. data/docs/brut-js/api/AjaxSubmit.js.html +435 -0
  553. data/docs/brut-js/api/Autosubmit.html +192 -0
  554. data/docs/brut-js/api/Autosubmit.js.html +114 -0
  555. data/docs/brut-js/api/BaseCustomElement.html +1091 -0
  556. data/docs/brut-js/api/BaseCustomElement.js.html +312 -0
  557. data/docs/brut-js/api/BrutCustomElements.html +172 -0
  558. data/docs/brut-js/api/BufferedLogger.html +173 -0
  559. data/docs/brut-js/api/ConfirmSubmit.html +278 -0
  560. data/docs/brut-js/api/ConfirmSubmit.js.html +167 -0
  561. data/docs/brut-js/api/ConfirmationDialog.html +425 -0
  562. data/docs/brut-js/api/ConfirmationDialog.js.html +194 -0
  563. data/docs/brut-js/api/ConstraintViolationMessage.html +448 -0
  564. data/docs/brut-js/api/ConstraintViolationMessage.js.html +176 -0
  565. data/docs/brut-js/api/ConstraintViolationMessages.html +590 -0
  566. data/docs/brut-js/api/ConstraintViolationMessages.js.html +149 -0
  567. data/docs/brut-js/api/CopyToClipboard.html +345 -0
  568. data/docs/brut-js/api/CopyToClipboard.js.html +147 -0
  569. data/docs/brut-js/api/Form.html +294 -0
  570. data/docs/brut-js/api/Form.js.html +202 -0
  571. data/docs/brut-js/api/I18nTranslation.html +409 -0
  572. data/docs/brut-js/api/I18nTranslation.js.html +112 -0
  573. data/docs/brut-js/api/LocaleDetection.html +312 -0
  574. data/docs/brut-js/api/LocaleDetection.js.html +168 -0
  575. data/docs/brut-js/api/Logger.html +702 -0
  576. data/docs/brut-js/api/Logger.js.html +141 -0
  577. data/docs/brut-js/api/Message.html +238 -0
  578. data/docs/brut-js/api/Message.js.html +107 -0
  579. data/docs/brut-js/api/PrefixedLogger.html +369 -0
  580. data/docs/brut-js/api/RichString.html +1049 -0
  581. data/docs/brut-js/api/RichString.js.html +164 -0
  582. data/docs/brut-js/api/Tabs.html +295 -0
  583. data/docs/brut-js/api/Tabs.js.html +219 -0
  584. data/docs/brut-js/api/Tracing.html +277 -0
  585. data/docs/brut-js/api/Tracing.js.html +298 -0
  586. data/docs/brut-js/api/external-CustomElementRegistry.html +140 -0
  587. data/docs/brut-js/api/external-Performance.html +138 -0
  588. data/docs/brut-js/api/external-Promise.html +138 -0
  589. data/docs/brut-js/api/external-ValidityState.html +138 -0
  590. data/docs/brut-js/api/external-Window.html +233 -0
  591. data/docs/brut-js/api/external-fetch.html +138 -0
  592. data/docs/brut-js/api/global.html +400 -0
  593. data/docs/brut-js/api/index.html +168 -0
  594. data/docs/brut-js/api/index.js.html +181 -0
  595. data/docs/brut-js/api/module-testing.html +383 -0
  596. data/docs/brut-js/api/scripts/linenumber.js +25 -0
  597. data/docs/brut-js/api/scripts/prettify/Apache-License-2.0.txt +202 -0
  598. data/docs/brut-js/api/scripts/prettify/lang-css.js +2 -0
  599. data/docs/brut-js/api/scripts/prettify/prettify.js +28 -0
  600. data/docs/brut-js/api/styles/jsdoc-default.css +327 -0
  601. data/docs/brut-js/api/styles/prettify-jsdoc.css +111 -0
  602. data/docs/brut-js/api/styles/prettify-tomorrow.css +132 -0
  603. data/docs/brut-js/api/testing.AssetMetadata.html +172 -0
  604. data/docs/brut-js/api/testing.AssetMetadataLoader.html +171 -0
  605. data/docs/brut-js/api/testing.CustomElementTest.html +679 -0
  606. data/docs/brut-js/api/testing.DOMCreator.html +171 -0
  607. data/docs/brut-js/api/testing_AssetMetadata.js.html +86 -0
  608. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +76 -0
  609. data/docs/brut-js/api/testing_CustomElementTest.js.html +286 -0
  610. data/docs/brut-js/api/testing_DOMCreator.js.html +96 -0
  611. data/docs/brut-js/api/testing_index.js.html +99 -0
  612. data/docs/brut-js.html +35 -0
  613. data/docs/business-logic.html +24 -0
  614. data/docs/cli.html +150 -0
  615. data/docs/components.html +127 -0
  616. data/docs/configuration.html +101 -0
  617. data/docs/css.html +44 -0
  618. data/docs/custom-element-tests.html +92 -0
  619. data/docs/database-access.html +86 -0
  620. data/docs/database-schema.html +86 -0
  621. data/docs/deployment.html +24 -0
  622. data/docs/dev-environment.html +34 -0
  623. data/docs/doc-conventions.html +24 -0
  624. data/docs/end-to-end-tests.html +49 -0
  625. data/docs/flash-and-session.html +116 -0
  626. data/docs/forms.html +402 -0
  627. data/docs/getting-started.html +25 -0
  628. data/docs/handlers.html +92 -0
  629. data/docs/hashmap.json +1 -0
  630. data/docs/hooks.html +103 -0
  631. data/docs/i18n.html +46 -0
  632. data/docs/images/logo-300.png +0 -0
  633. data/docs/images/logo.png +0 -0
  634. data/docs/index.html +24 -0
  635. data/docs/instrumentation.html +58 -0
  636. data/docs/javascript.html +54 -0
  637. data/docs/jobs.html +24 -0
  638. data/docs/keyword-injection.html +48 -0
  639. data/docs/markdown-examples.html +56 -0
  640. data/docs/middleware.html +43 -0
  641. data/docs/not-released.html +24 -0
  642. data/docs/overview.html +156 -0
  643. data/docs/pages.html +145 -0
  644. data/docs/routes.html +52 -0
  645. data/docs/security.html +24 -0
  646. data/docs/seed-data.html +37 -0
  647. data/docs/space-time-continuum.html +24 -0
  648. data/docs/tutorial.html +24 -0
  649. data/docs/unit-tests.html +36 -0
  650. data/docs/vp-icons.css +1 -0
  651. data/lib/brut/back_end/seed_data.rb +19 -2
  652. data/lib/brut/back_end/sidekiq/middlewares/server.rb +2 -1
  653. data/lib/brut/back_end/sidekiq/middlewares.rb +2 -1
  654. data/lib/brut/back_end/sidekiq.rb +2 -1
  655. data/lib/brut/back_end/validator.rb +5 -1
  656. data/lib/brut/back_end.rb +4 -2
  657. data/lib/brut/cli/app_runner.rb +1 -1
  658. data/lib/brut/cli/apps/test.rb +5 -0
  659. data/lib/brut/cli.rb +4 -3
  660. data/lib/brut/factory_bot.rb +0 -5
  661. data/lib/brut/framework/app.rb +70 -5
  662. data/lib/brut/framework/config.rb +5 -3
  663. data/lib/brut/framework/container.rb +3 -2
  664. data/lib/brut/framework/errors.rb +12 -4
  665. data/lib/brut/framework/mcp.rb +58 -1
  666. data/lib/brut/framework/project_environment.rb +6 -2
  667. data/lib/brut/framework.rb +1 -1
  668. data/lib/brut/front_end/component.rb +69 -71
  669. data/lib/brut/front_end/components/constraint_violations.rb +1 -4
  670. data/lib/brut/front_end/components/form_tag.rb +1 -1
  671. data/lib/brut/front_end/components/input.rb +3 -3
  672. data/lib/brut/front_end/components/inputs/csrf_token.rb +1 -1
  673. data/lib/brut/front_end/components/inputs/{text_field.rb → input_tag.rb} +7 -9
  674. data/lib/brut/front_end/components/inputs/radio_button.rb +1 -1
  675. data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +187 -0
  676. data/lib/brut/front_end/components/inputs/{textarea.rb → textarea_tag.rb} +2 -2
  677. data/lib/brut/front_end/components/time_tag.rb +2 -1
  678. data/lib/brut/front_end/form.rb +4 -4
  679. data/lib/brut/front_end/forms/input.rb +2 -1
  680. data/lib/brut/front_end/forms/input_definition.rb +5 -2
  681. data/lib/brut/front_end/forms/radio_button_group_input.rb +2 -1
  682. data/lib/brut/front_end/forms/radio_button_group_input_definition.rb +2 -2
  683. data/lib/brut/front_end/forms/select_input.rb +2 -4
  684. data/lib/brut/front_end/forms/select_input_definition.rb +2 -2
  685. data/lib/brut/front_end/handler.rb +28 -26
  686. data/lib/brut/front_end/handlers/csp_reporting_handler.rb +5 -2
  687. data/lib/brut/front_end/handlers/instrumentation_handler.rb +8 -4
  688. data/lib/brut/front_end/handlers/locale_detection_handler.rb +9 -5
  689. data/lib/brut/front_end/handlers/missing_handler.rb +5 -2
  690. data/lib/brut/front_end/layout.rb +16 -0
  691. data/lib/brut/front_end/page.rb +52 -29
  692. data/lib/brut/front_end/request_context.rb +3 -2
  693. data/lib/brut/front_end/routing.rb +5 -1
  694. data/lib/brut/front_end.rb +4 -13
  695. data/lib/brut/i18n/base_methods.rb +167 -79
  696. data/lib/brut/i18n/for_back_end.rb +4 -0
  697. data/lib/brut/i18n/for_cli.rb +4 -0
  698. data/lib/brut/i18n/for_html.rb +32 -4
  699. data/lib/brut/i18n/http_accept_language.rb +47 -0
  700. data/lib/brut/instrumentation/open_telemetry.rb +36 -1
  701. data/lib/brut/instrumentation.rb +3 -5
  702. data/lib/brut/sinatra_helpers.rb +11 -3
  703. data/lib/brut/spec_support/component_support.rb +30 -16
  704. data/lib/brut/spec_support/e2e_support.rb +1 -1
  705. data/lib/brut/spec_support/e2e_test_server.rb +3 -0
  706. data/lib/brut/spec_support/general_support.rb +3 -0
  707. data/lib/brut/spec_support/handler_support.rb +6 -1
  708. data/lib/brut/spec_support/matcher.rb +1 -0
  709. data/lib/brut/spec_support/matchers/be_page_for.rb +1 -0
  710. data/lib/brut/spec_support/matchers/have_html_attribute.rb +1 -0
  711. data/lib/brut/spec_support/matchers/have_i18n_string.rb +2 -5
  712. data/lib/brut/spec_support/matchers/have_link_to.rb +1 -0
  713. data/lib/brut/spec_support/matchers/have_redirected_to.rb +1 -0
  714. data/lib/brut/spec_support/matchers/have_rendered.rb +1 -0
  715. data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +44 -0
  716. data/lib/brut/spec_support.rb +1 -1
  717. data/lib/brut/version.rb +1 -1
  718. data/lib/brut.rb +5 -4
  719. data/lib/sequel/extensions/brut_migrations.rb +1 -1
  720. metadata +648 -13
  721. data/doc-src/architecture.md +0 -102
  722. data/doc-src/assets.md +0 -98
  723. data/doc-src/forms.md +0 -214
  724. data/doc-src/handlers.md +0 -83
  725. data/doc-src/javascript.md +0 -265
  726. data/doc-src/pages.md +0 -210
  727. data/doc-src/route-hooks.md +0 -59
  728. data/lib/brut/front_end/components/inputs/select.rb +0 -117
@@ -1,16 +1,7 @@
1
- # In Brut, the _front end_ is considered anything that interacts directly with a web browser or HTTP. This includes rendering HTML,
2
- # managing JavaScript and CSS, and processing form submissions. It contrasts to {Brut::BackEnd}, which handles the business logic
3
- # and database.
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. The value returned is not HTML escaped,
13
- # assuming that you have not placed HTML injections in your own translation files. Interpolated
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
- # `"general.cv.this_field"` is used. This value, in English, is "this field", so a call
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 is designed to a balance predictabilitiy in what actual key is chosen
27
- # but without needless repetition on a page. If this value is provided, and is an array, the values
28
- # are joined with "." to form a key. If the value is not an array, that value is used directly.
29
- # Given this key, two values are checked for a translation: the key itself and
30
- # the key inside "general.". If this value is *not* provided, it is expected
31
- # taht the `**rest` hash includes page: or component:. See that parameter and the example.
32
- #
33
- # @param [Hash] rest values to use for interpolation of the key's translation. If `key` is omitted,
34
- # this hash should have a value for either `page:` or `component:` (not both). If
35
- # `page:` is present, it is assumed that the class that has included this module
36
- # is a `Brut::FrontEnd::Page` or is a page component. It's `page_name` will be used to create
37
- # a key based on the value of `page:`: `pages.«page_name».«page: value»`.
38
- # if `component:` is included, the behavior is the same but for `component` instead of `page`.
39
- # Note that if the page– or component–specific key is not found, this will check
40
- # `generalpage: value»`.
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
+ # `componentscomponent_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
- # general: {
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
- # general: {
65
- # actions: {
66
- # edit: "Make an edit"
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
- # general: {
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(page: :new_widget) # => Create new Widget
112
+ # t(:new_widget) # => Create new Widget
90
113
  # # in your code for WidgetsPage
91
- # t(page: :new_widget) # => Create New
92
- # # in your code for SomeOtherEPage
93
- # t(page: :new_widget) # => Make a New Widget
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 page: with an array
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
- # captions: {
102
- # new: "New Widgets"
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 HomePage
108
- # t(page: [ :captions, :new ]) # => New Widgets
109
- def t(key=:look_in_rest,**rest,&block)
110
- if key == :look_in_rest
111
-
112
- page = rest.delete(:page)
113
- component = rest.delete(:component)
114
-
115
- if !page.nil? && !component.nil?
116
- raise ArgumentError, "You may only specify page or component, not both"
117
- end
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
- subkey = nil
120
- if page
121
- subkey = Array(page).join(".")
122
- key = ["pages.#{self.page_name}.#{subkey}"]
123
- elsif component
124
- subkey = Array(component).join(".")
125
- key = ["components.#{self.component_name}.#{subkey}"]
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 rest[:block]
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
- rest[:block] = block_contents
212
+ interpolated_values[:block] = block_contents
140
213
  end
141
- t_direct(key,**rest)
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 use t_html *and* yield a block to it"
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("general.cv.this_field", raise: true)
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, CGI.escapeHTML(value) ]
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
- raise I18n::MissingInterpolationArgument.new(:count,interpolated_values,keys.join(","))
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
@@ -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
@@ -1,12 +1,40 @@
1
1
  # I18n for components or pages, which are assumed to be Phlex components.
2
- # To use this outside of a Phlex context, you must define these two
3
- # methods to ensure proper HTML escaping happens:
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 {Brut::Instrumentation::OpenTelemetry::Span#add_prefixed_attributes} instead.
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
@@ -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
 
@@ -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
- process_args = Brut::FrontEnd::RequestContext.current.as_method_args(handler,:handle,request_params: params,form: form,route:brut_route)
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!(**process_args)
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